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 +#include +#include +#include +#include +#include +#include +#include + +#define MAX_EVENTS 10 + +class LinuxInput : public Observable, public concurrency::OSThread +{ + public: + explicit LinuxInput(const char *name); + void deInit(); // Strictly for cleanly "rebooting" the binary on native + + protected: + virtual int32_t runOnce() override; + + private: + const char *_originName; + bool firstTime = 1; + int shift = 0; + char key = 0; + char prevkey = 0; + + InputEvent eventqueue[50]; // The Linux API will return multiple keypresses at a time. Queue them to not miss any. + int queue_length = 0; + int queue_progress = 0; + + struct epoll_event events[MAX_EVENTS]; + int fd = -1; + int ret; + uint8_t report[8]; + int epollfd; + struct epoll_event ev; + uint8_t modifiers = 0; + std::map keymap{ + {KEY_A, 'a'}, {KEY_B, 'b'}, {KEY_C, 'c'}, {KEY_D, 'd'}, {KEY_E, 'e'}, + {KEY_F, 'f'}, {KEY_G, 'g'}, {KEY_H, 'h'}, {KEY_I, 'i'}, {KEY_J, 'j'}, + {KEY_K, 'k'}, {KEY_L, 'l'}, {KEY_M, 'm'}, {KEY_N, 'n'}, {KEY_O, 'o'}, + {KEY_P, 'p'}, {KEY_Q, 'q'}, {KEY_R, 'r'}, {KEY_S, 's'}, {KEY_T, 't'}, + {KEY_U, 'u'}, {KEY_V, 'v'}, {KEY_W, 'w'}, {KEY_X, 'x'}, {KEY_Y, 'y'}, + {KEY_Z, 'z'}, {KEY_BACKSPACE, 0x08}, {KEY_SPACE, ' '}, {KEY_1, '1'}, {KEY_2, '2'}, + {KEY_3, '3'}, {KEY_4, '4'}, {KEY_5, '5'}, {KEY_6, '6'}, {KEY_7, '7'}, + {KEY_8, '8'}, {KEY_9, '9'}, {KEY_0, '0'}, {KEY_DOT, '.'}, {KEY_COMMA, ','}, + {KEY_MINUS, '-'}, {KEY_EQUAL, '='}, {KEY_LEFTBRACE, '['}, {KEY_RIGHTBRACE, ']'}, {KEY_BACKSLASH, '\\'}, + {KEY_SEMICOLON, ';'}, {KEY_APOSTROPHE, '\''}, {KEY_SLASH, '/'}, {KEY_TAB, 0x09}}; + std::map uppers{{'a', 'A'}, {'b', 'B'}, {'c', 'C'}, {'d', 'D'}, {'e', 'E'}, {'f', 'F'}, {'g', 'G'}, {'h', 'H'}, + {'i', 'I'}, {'j', 'J'}, {'k', 'K'}, {'l', 'L'}, {'m', 'M'}, {'n', 'N'}, {'o', 'O'}, {'p', 'P'}, + {'q', 'Q'}, {'r', 'R'}, {'s', 'S'}, {'t', 'T'}, {'u', 'U'}, {'v', 'V'}, {'w', 'W'}, {'x', 'X'}, + {'y', 'Y'}, {'z', 'Z'}, {'1', '!'}, {'2', '@'}, {'3', '#'}, {'4', '$'}, {'5', '%'}, {'6', '^'}, + {'7', '&'}, {'8', '*'}, {'9', '('}, {'0', ')'}, {'.', '>'}, {',', '<'}, {'-', '_'}, {'=', '+'}, + {'[', '{'}, {']', '}'}, {'\\', '|'}, {';', ':'}, {'\'', '"'}, {'/', '?'}}; +}; +#endif \ No newline at end of file diff --git a/src/input/LinuxInputImpl.cpp b/src/input/LinuxInputImpl.cpp new file mode 100644 index 000000000..4ddda1923 --- /dev/null +++ b/src/input/LinuxInputImpl.cpp @@ -0,0 +1,15 @@ +#include "configuration.h" +#if ARCH_PORTDUINO +#include "InputBroker.h" +#include "LinuxInputImpl.h" + +LinuxInputImpl *aLinuxInputImpl; + +LinuxInputImpl::LinuxInputImpl() : LinuxInput("LinuxInput") {} + +void LinuxInputImpl::init() +{ + inputBroker->registerSource(this); +} + +#endif \ No newline at end of file diff --git a/src/input/LinuxInputImpl.h b/src/input/LinuxInputImpl.h new file mode 100644 index 000000000..e734b0294 --- /dev/null +++ b/src/input/LinuxInputImpl.h @@ -0,0 +1,21 @@ +#ifdef ARCH_PORTDUINO +#pragma once +#include "LinuxInput.h" +#include "main.h" + +/** + * @brief The idea behind this class to have static methods for the event handlers. + * Check attachInterrupt() at RotaryEncoderInteruptBase.cpp + * Technically you can have as many rotary encoders hardver attached + * to your device as you wish, but you always need to have separate event + * handlers, thus you need to have a RotaryEncoderInterrupt implementation. + */ + +class LinuxInputImpl : public LinuxInput +{ + public: + LinuxInputImpl(); + void init(); +}; +extern LinuxInputImpl *aLinuxInputImpl; +#endif \ No newline at end of file diff --git a/src/input/RotaryEncoderInterruptBase.cpp b/src/input/RotaryEncoderInterruptBase.cpp index 19b507f6c..0b8e8325d 100644 --- a/src/input/RotaryEncoderInterruptBase.cpp +++ b/src/input/RotaryEncoderInterruptBase.cpp @@ -104,7 +104,6 @@ RotaryEncoderInterruptBaseStateType RotaryEncoderInterruptBase::intHandler(bool newState = ROTARY_EVENT_OCCURRED; if ((this->action != ROTARY_ACTION_PRESSED) && (this->action != action)) { this->action = action; - LOG_DEBUG("Rotary action\n"); } } } else if (!actualPinRaising && (otherPinLevel == HIGH)) { diff --git a/src/input/TouchScreenImpl1.cpp b/src/input/TouchScreenImpl1.cpp index b3152c88a..c863ead69 100644 --- a/src/input/TouchScreenImpl1.cpp +++ b/src/input/TouchScreenImpl1.cpp @@ -2,6 +2,11 @@ #include "InputBroker.h" #include "PowerFSM.h" #include "configuration.h" +#include "modules/ExternalNotificationModule.h" + +#if ARCH_PORTDUINO +#include "platform/portduino/PortduinoGlue.h" +#endif TouchScreenImpl1 *touchScreenImpl1; @@ -12,7 +17,14 @@ TouchScreenImpl1::TouchScreenImpl1(uint16_t width, uint16_t height, bool (*getTo void TouchScreenImpl1::init() { -#if !HAS_TOUCHSCREEN +#if ARCH_PORTDUINO + if (settingsMap[touchscreenModule]) { + TouchScreenBase::init(true); + inputBroker->registerSource(this); + } else { + TouchScreenBase::init(false); + } +#elif !HAS_TOUCHSCREEN TouchScreenBase::init(false); return; #else @@ -63,7 +75,11 @@ void TouchScreenImpl1::onEvent(const TouchEvent &event) break; } case TOUCH_ACTION_TAP: { - powerFSM.trigger(EVENT_INPUT); + if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) { + externalNotificationModule->stopNow(); + } else { + powerFSM.trigger(EVENT_INPUT); + } break; } default: diff --git a/src/input/UpDownInterruptBase.cpp b/src/input/UpDownInterruptBase.cpp index ecc3b944a..b1f83c56b 100644 --- a/src/input/UpDownInterruptBase.cpp +++ b/src/input/UpDownInterruptBase.cpp @@ -1,7 +1,7 @@ #include "UpDownInterruptBase.h" #include "configuration.h" -UpDownInterruptBase::UpDownInterruptBase(const char *name) +UpDownInterruptBase::UpDownInterruptBase(const char *name) : concurrency::OSThread(name) { this->_originName = name; } @@ -24,31 +24,48 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, attachInterrupt(this->_pinUp, onIntUp, RISING); LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)\n", this->_pinUp, this->_pinDown, pinPress); + + this->setInterval(100); +} + +int32_t UpDownInterruptBase::runOnce() +{ + InputEvent e; + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; + + if (this->action == UPDOWN_ACTION_PRESSED) { + LOG_DEBUG("GPIO event Press\n"); + e.inputEvent = this->_eventPressed; + } else if (this->action == UPDOWN_ACTION_UP) { + LOG_DEBUG("GPIO event Up\n"); + e.inputEvent = this->_eventUp; + } else if (this->action == UPDOWN_ACTION_DOWN) { + LOG_DEBUG("GPIO event Down\n"); + e.inputEvent = this->_eventDown; + } + + if (e.inputEvent != meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE) { + e.source = this->_originName; + e.kbchar = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; + this->notifyObservers(&e); + } + + this->action = UPDOWN_ACTION_NONE; + + return 100; } void UpDownInterruptBase::intPressHandler() { - InputEvent e; - e.source = this->_originName; - LOG_DEBUG("GPIO event Press\n"); - e.inputEvent = this->_eventPressed; - this->notifyObservers(&e); + this->action = UPDOWN_ACTION_PRESSED; } void UpDownInterruptBase::intDownHandler() { - InputEvent e; - e.source = this->_originName; - LOG_DEBUG("GPIO event Down\n"); - e.inputEvent = this->_eventDown; - this->notifyObservers(&e); + this->action = UPDOWN_ACTION_DOWN; } void UpDownInterruptBase::intUpHandler() { - InputEvent e; - e.source = this->_originName; - LOG_DEBUG("GPIO event Up\n"); - e.inputEvent = this->_eventUp; - this->notifyObservers(&e); + this->action = UPDOWN_ACTION_UP; } diff --git a/src/input/UpDownInterruptBase.h b/src/input/UpDownInterruptBase.h index afa64d28d..7060a0d80 100644 --- a/src/input/UpDownInterruptBase.h +++ b/src/input/UpDownInterruptBase.h @@ -3,7 +3,7 @@ #include "InputBroker.h" #include "mesh/NodeDB.h" -class UpDownInterruptBase : public Observable +class UpDownInterruptBase : public Observable, public concurrency::OSThread { public: explicit UpDownInterruptBase(const char *name); @@ -13,6 +13,13 @@ class UpDownInterruptBase : public Observable void intDownHandler(); void intUpHandler(); + int32_t runOnce() override; + + protected: + enum UpDownInterruptBaseActionType { UPDOWN_ACTION_NONE, UPDOWN_ACTION_PRESSED, UPDOWN_ACTION_UP, UPDOWN_ACTION_DOWN }; + + volatile UpDownInterruptBaseActionType action = UPDOWN_ACTION_NONE; + private: uint8_t _pinDown = 0; uint8_t _pinUp = 0; diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 366e7fbb1..74a6c718d 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -187,7 +187,7 @@ int32_t KbI2cBase::runOnce() i2cBus->requestFrom((int)cardkb_found.address, 1); - while (i2cBus->available()) { + if (i2cBus->available()) { char c = i2cBus->read(); InputEvent e; e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; @@ -216,13 +216,27 @@ int32_t KbI2cBase::runOnce() e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT; e.kbchar = 0xb7; break; + case 0x90: // fn+r + case 0x91: // fn+t + case 0x9b: // fn+s + case 0xac: // fn+m + case 0x9e: // fn+g + case 0xaf: // fn+space + // just pass those unmodified + e.inputEvent = ANYKEY; + e.kbchar = c; + break; case 0x0d: // Enter e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; break; case 0x00: // nopress e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; break; - default: // all other keys + default: // all other keys + if (c > 127) { // bogus key value + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; + break; + } e.inputEvent = ANYKEY; e.kbchar = c; break; @@ -238,4 +252,4 @@ int32_t KbI2cBase::runOnce() LOG_WARN("Unknown kb_model 0x%02x\n", kb_model); } return 300; -} +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 60b38891b..4277e5e92 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,7 @@ +#include "configuration.h" +#if !MESHTASTIC_EXCLUDE_GPS #include "GPS.h" +#endif #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" @@ -6,7 +9,7 @@ #include "ReliableRouter.h" #include "airtime.h" #include "buzz.h" -#include "configuration.h" + #include "error.h" #include "power.h" // #include "debug.h" @@ -32,14 +35,15 @@ #include // #include -#include "mesh/eth/ethClient.h" -#include "mesh/http/WiFiAPClient.h" - #ifdef ARCH_ESP32 +#if !MESHTASTIC_EXCLUDE_WEBSERVER #include "mesh/http/WebServer.h" +#endif +#if !MESHTASTIC_EXCLUDE_BLUETOOTH #include "nimble/NimbleBluetooth.h" NimbleBluetooth *nimbleBluetooth; #endif +#endif #ifdef ARCH_NRF52 #include "NRF52Bluetooth.h" @@ -48,36 +52,46 @@ NRF52Bluetooth *nrf52Bluetooth; #if HAS_WIFI #include "mesh/api/WiFiServerAPI.h" +#include "mesh/wifi/WiFiAPClient.h" #endif #if HAS_ETHERNET #include "mesh/api/ethServerAPI.h" +#include "mesh/eth/ethClient.h" #endif + +#if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" +#endif #include "LLCC68Interface.h" #include "RF95Interface.h" #include "SX1262Interface.h" #include "SX1268Interface.h" #include "SX1280Interface.h" +#include "detect/LoRaRadioType.h" + #ifdef ARCH_STM32WL #include "STM32WLE5JCInterface.h" #endif + #if !HAS_RADIO && defined(ARCH_PORTDUINO) #include "platform/portduino/SimRadio.h" #endif -#ifdef ARCH_RASPBERRY_PI -#include "platform/portduino/PiHal.h" +#ifdef ARCH_PORTDUINO +#include "linux/LinuxHardwareI2C.h" +#include "mesh/raspihttp/PiWebServer.h" #include "platform/portduino/PortduinoGlue.h" #include #include #include #endif -#if HAS_BUTTON +#if HAS_BUTTON || defined(ARCH_PORTDUINO) #include "ButtonThread.h" #endif + #include "PowerFSMThread.h" #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) @@ -85,6 +99,11 @@ NRF52Bluetooth *nrf52Bluetooth; #include "AmbientLightingThread.h" #endif +#ifdef HAS_I2S +#include "AudioThread.h" +AudioThread *audioThread; +#endif + using namespace concurrency; // We always create a screen object, but we only init it if we find the hardware @@ -123,6 +142,10 @@ ATECCX08A atecc; #ifdef T_WATCH_S3 Adafruit_DRV2605 drv; #endif + +// Global LoRa radio type +LoRaRadioType radioType = NO_RADIO; + bool isVibrating = false; bool eink_found = true; @@ -136,32 +159,12 @@ std::pair nodeTelemetrySensorsMap[_meshtastic_TelemetrySenso Router *router = NULL; // Users of router don't care what sort of subclass implements that API -#ifdef ARCH_RASPBERRY_PI -void getPiMacAddr(uint8_t *dmac) -{ - std::fstream macIdentity; - macIdentity.open("/sys/kernel/debug/bluetooth/hci0/identity", std::ios::in); - std::string macLine; - getline(macIdentity, macLine); - macIdentity.close(); - - dmac[0] = strtol(macLine.substr(0, 2).c_str(), NULL, 16); - dmac[1] = strtol(macLine.substr(3, 2).c_str(), NULL, 16); - dmac[2] = strtol(macLine.substr(6, 2).c_str(), NULL, 16); - dmac[3] = strtol(macLine.substr(9, 2).c_str(), NULL, 16); - dmac[4] = strtol(macLine.substr(12, 2).c_str(), NULL, 16); - dmac[5] = strtol(macLine.substr(15, 2).c_str(), NULL, 16); -} -#endif - const char *getDeviceName() { uint8_t dmac[6]; -#ifdef ARCH_RASPBERRY_PI - getPiMacAddr(dmac); -#else + getMacAddr(dmac); -#endif + // Meshtastic_ab3c or Shortname_abcd static char name[20]; snprintf(name, sizeof(name), "%02x%02x", dmac[4], dmac[5]); @@ -174,25 +177,6 @@ const char *getDeviceName() return name; } -#ifdef VEXT_ENABLE_V03 - -#include - -static uint32_t calibrate_one(rtc_cal_sel_t cal_clk, const char *name) -{ - const uint32_t cal_count = 1000; - uint32_t cali_val; - for (int i = 0; i < 5; ++i) { - cali_val = rtc_clk_cal(cal_clk, cal_count); - } - return cali_val; -} - -int heltec_version = 3; - -#define CALIBRATE_ONE(cali_clk) calibrate_one(cali_clk, #cali_clk) -#endif - static int32_t ledBlinker() { static bool ledOn; @@ -206,16 +190,8 @@ static int32_t ledBlinker() uint32_t timeLastPowered = 0; -#if HAS_BUTTON -bool ButtonThread::shutdown_on_long_stop = false; -#endif - static Periodic *ledPeriodic; static OSThread *powerFSMthread; -#if HAS_BUTTON -static OSThread *buttonThread; -uint32_t ButtonThread::longPressTime = 0; -#endif static OSThread *accelerometerThread; static OSThread *ambientLightingThread; SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); @@ -257,62 +233,43 @@ void setup() initDeepSleep(); - // Testing this fix für erratic T-Echo boot behaviour -#if defined(TTGO_T_ECHO) && defined(PIN_EINK_PWR_ON) - pinMode(PIN_EINK_PWR_ON, OUTPUT); - digitalWrite(PIN_EINK_PWR_ON, HIGH); + // power on peripherals +#if defined(TTGO_T_ECHO) && defined(PIN_POWER_EN) + pinMode(PIN_POWER_EN, OUTPUT); + digitalWrite(PIN_POWER_EN, HIGH); + // digitalWrite(PIN_POWER_EN1, INPUT); #endif -#ifdef ST7735_BL_V03 // Heltec Wireless Tracker PCB Change Detect/Hack - - rtc_clk_32k_enable(true); - CALIBRATE_ONE(RTC_CAL_RTC_MUX); - if (CALIBRATE_ONE(RTC_CAL_32K_XTAL) != 0) { - rtc_clk_slow_freq_set(RTC_SLOW_FREQ_32K_XTAL); - CALIBRATE_ONE(RTC_CAL_RTC_MUX); - CALIBRATE_ONE(RTC_CAL_32K_XTAL); - } - - if (rtc_clk_slow_freq_get() != RTC_SLOW_FREQ_32K_XTAL) { - heltec_version = 3; - } else { - heltec_version = 5; - } +#if defined(LORA_TCXO_GPIO) + pinMode(LORA_TCXO_GPIO, OUTPUT); + digitalWrite(LORA_TCXO_GPIO, HIGH); #endif #if defined(VEXT_ENABLE_V03) - if (heltec_version == 3) { - pinMode(VEXT_ENABLE_V03, OUTPUT); - digitalWrite(VEXT_ENABLE_V03, 0); // turn on the display power - LOG_DEBUG("HELTEC Detect Tracker V1.0\n"); - } else { - pinMode(VEXT_ENABLE_V05, OUTPUT); - digitalWrite(VEXT_ENABLE_V05, 1); // turn on the display power - LOG_DEBUG("HELTEC Detect Tracker V1.1\n"); - } + pinMode(VEXT_ENABLE_V03, OUTPUT); + pinMode(ST7735_BL_V03, OUTPUT); + digitalWrite(VEXT_ENABLE_V03, 0); // turn on the display power and antenna boost + digitalWrite(ST7735_BL_V03, 1); // display backligth on + LOG_DEBUG("HELTEC Detect Tracker V1.0\n"); +#elif defined(VEXT_ENABLE_V05) + pinMode(VEXT_ENABLE_V05, OUTPUT); + pinMode(ST7735_BL_V05, OUTPUT); + digitalWrite(VEXT_ENABLE_V05, 1); // turn on the lora antenna boost + digitalWrite(ST7735_BL_V05, 1); // turn on display backligth + LOG_DEBUG("HELTEC Detect Tracker V1.1\n"); #elif defined(VEXT_ENABLE) pinMode(VEXT_ENABLE, OUTPUT); digitalWrite(VEXT_ENABLE, 0); // turn on the display power #endif #if defined(VGNSS_CTRL_V03) - if (heltec_version == 3) { - pinMode(VGNSS_CTRL_V03, OUTPUT); - digitalWrite(VGNSS_CTRL_V03, LOW); - } else { - pinMode(VGNSS_CTRL_V05, OUTPUT); - digitalWrite(VGNSS_CTRL_V05, LOW); - } + pinMode(VGNSS_CTRL_V03, OUTPUT); + digitalWrite(VGNSS_CTRL_V03, LOW); #endif #if defined(VTFT_CTRL_V03) - if (heltec_version == 3) { - pinMode(VTFT_CTRL_V03, OUTPUT); - digitalWrite(VTFT_CTRL_V03, LOW); - } else { - pinMode(VTFT_CTRL_V05, OUTPUT); - digitalWrite(VTFT_CTRL_V05, LOW); - } + pinMode(VTFT_CTRL_V03, OUTPUT); + digitalWrite(VTFT_CTRL_V03, LOW); #endif #if defined(VGNSS_CTRL) @@ -364,12 +321,27 @@ void setup() #endif -#ifdef I2C_SDA1 +#if defined(I2C_SDA1) && defined(ARCH_RP2040) + Wire1.setSDA(I2C_SDA1); + Wire1.setSCL(I2C_SCL1); + Wire1.begin(); +#elif defined(I2C_SDA1) && !defined(ARCH_RP2040) Wire1.begin(I2C_SDA1, I2C_SCL1); #endif -#ifdef I2C_SDA +#if defined(I2C_SDA) && defined(ARCH_RP2040) + Wire.setSDA(I2C_SDA); + Wire.setSCL(I2C_SCL); + Wire.begin(); +#elif defined(I2C_SDA) && !defined(ARCH_RP2040) Wire.begin(I2C_SDA, I2C_SCL); +#elif defined(ARCH_PORTDUINO) + if (settingsStrings[i2cdev] != "") { + LOG_INFO("Using %s as I2C device.\n", settingsStrings[i2cdev]); + Wire.begin(settingsStrings[i2cdev].c_str()); + } else { + LOG_INFO("No I2C device configured, skipping.\n"); + } #elif HAS_WIRE Wire.begin(); #endif @@ -389,7 +361,7 @@ void setup() pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); #endif -#ifndef USE_EINK +#ifdef AQ_SET_PIN // RAK-12039 set pin for Air quality sensor pinMode(AQ_SET_PIN, OUTPUT); digitalWrite(AQ_SET_PIN, HIGH); @@ -415,17 +387,33 @@ void setup() // We need to scan here to decide if we have a screen for nodeDB.init() and because power has been applied to // accessories auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); - +#if HAS_WIRE LOG_INFO("Scanning for i2c devices...\n"); +#endif -#ifdef I2C_SDA1 +#if defined(I2C_SDA1) && defined(ARCH_RP2040) + Wire1.setSDA(I2C_SDA1); + Wire1.setSCL(I2C_SCL1); + Wire1.begin(); + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1); +#elif defined(I2C_SDA1) && !defined(ARCH_RP2040) Wire1.begin(I2C_SDA1, I2C_SCL1); i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1); #endif -#ifdef I2C_SDA +#if defined(I2C_SDA) && defined(ARCH_RP2040) + Wire.setSDA(I2C_SDA); + Wire.setSCL(I2C_SCL); + Wire.begin(); + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE); +#elif defined(I2C_SDA) && !defined(ARCH_RP2040) Wire.begin(I2C_SDA, I2C_SCL); i2cScanner->scanPort(ScanI2C::I2CPort::WIRE); +#elif defined(ARCH_PORTDUINO) + if (settingsStrings[i2cdev] != "") { + LOG_INFO("Scanning for i2c devices...\n"); + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE); + } #elif HAS_WIRE i2cScanner->scanPort(ScanI2C::I2CPort::WIRE); #endif @@ -433,6 +421,10 @@ void setup() auto i2cCount = i2cScanner->countDevices(); if (i2cCount == 0) { LOG_INFO("No I2C devices found\n"); + Wire.end(); +#ifdef I2C_SDA1 + Wire1.end(); +#endif } else { LOG_INFO("%i I2C devices found\n", i2cCount); } @@ -528,6 +520,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BME_680, meshtastic_TelemetrySensorType_BME680) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BME_280, meshtastic_TelemetrySensorType_BME280) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BMP_280, meshtastic_TelemetrySensorType_BMP280) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::BMP_085, meshtastic_TelemetrySensorType_BMP085) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221) @@ -575,15 +568,18 @@ void setup() // We do this as early as possible because this loads preferences from flash // but we need to do this after main cpu init (esp32setup), because we need the random seed set - nodeDB.init(); + nodeDB = new NodeDB; - // If we're taking on the repeater role, use flood router - if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) + // If we're taking on the repeater role, use flood router and turn off 3V3_S rail because peripherals are not needed + if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) { router = new FloodingRouter(); - else +#ifdef PIN_3V3_EN + digitalWrite(PIN_3V3_EN, LOW); +#endif + } else router = new ReliableRouter(); -#if HAS_BUTTON +#if HAS_BUTTON || defined(ARCH_PORTDUINO) // Buttons. Moved here cause we need NodeDB to be initialized buttonThread = new ButtonThread(); #endif @@ -628,39 +624,62 @@ void setup() initSPI(); #ifdef ARCH_RP2040 #ifdef HW_SPI1_DEVICE - SPI1.setSCK(RF95_SCK); - SPI1.setTX(RF95_MOSI); - SPI1.setRX(RF95_MISO); - pinMode(RF95_NSS, OUTPUT); - digitalWrite(RF95_NSS, HIGH); + SPI1.setSCK(LORA_SCK); + SPI1.setTX(LORA_MOSI); + SPI1.setRX(LORA_MISO); + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, HIGH); SPI1.begin(false); #else // HW_SPI1_DEVICE - SPI.setSCK(RF95_SCK); - SPI.setTX(RF95_MOSI); - SPI.setRX(RF95_MISO); + SPI.setSCK(LORA_SCK); + SPI.setTX(LORA_MOSI); + SPI.setRX(LORA_MISO); SPI.begin(false); #endif // HW_SPI1_DEVICE #elif !defined(ARCH_ESP32) // ARCH_RP2040 SPI.begin(); #else // ESP32 - SPI.begin(RF95_SCK, RF95_MISO, RF95_MOSI, RF95_NSS); - LOG_WARN("SPI.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)\n", RF95_SCK, RF95_MISO, RF95_MOSI, RF95_NSS); + SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS); + LOG_DEBUG("SPI.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)\n", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS); SPI.setFrequency(4000000); #endif // Initialize the screen first so we can show the logo while we start up everything else. screen = new graphics::Screen(screen_found, screen_model, screen_geometry); + // setup TZ prior to time actions. + if (*config.device.tzdef) { + setenv("TZ", config.device.tzdef, 1); + } else { + setenv("TZ", "GMT0", 1); + } + tzset(); + LOG_DEBUG("Set Timezone to %s\n", getenv("TZ")); + readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time) - gps = GPS::createGps(); - if (gps) { - gpsStatus->observe(&gps->newStatus); - } else { - LOG_DEBUG("Running without GPS.\n"); +#if !MESHTASTIC_EXCLUDE_GPS + // If we're taking on the repeater role, ignore GPS + if (HAS_GPS) { + if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && + config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) { + gps = GPS::createGps(); + if (gps) { + gpsStatus->observe(&gps->newStatus); + } else { + LOG_DEBUG("Running without GPS.\n"); + } + } } - nodeStatus->observe(&nodeDB.newStatus); +#endif + + nodeStatus->observe(&nodeDB->newStatus); + +#ifdef HAS_I2S + LOG_DEBUG("Starting audio thread\n"); + audioThread = new AudioThread(); +#endif config.lora.next_hop_routing = true; // FIXME - remove this before merging LOG_INFO("USING NEXT-HOP ROUTING\n"); @@ -678,8 +697,12 @@ void setup() // Don't call screen setup until after nodedb is setup (because we need // the current region name) -#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) +#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) screen->setup(); +#elif defined(ARCH_PORTDUINO) + if (screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) { + screen->setup(); + } #else if (screen_found.port != ScanI2C::I2CPort::NO_I2C) screen->setup(); @@ -693,10 +716,16 @@ void setup() digitalWrite(SX126X_ANT_SW, 1); #endif -#ifdef ARCH_RASPBERRY_PI +#ifdef PIN_PWR_DELAY_MS + // This may be required to give the peripherals time to power up. + delay(PIN_PWR_DELAY_MS); +#endif + +#ifdef ARCH_PORTDUINO if (settingsMap[use_sx1262]) { if (!rIf) { - PiHal *RadioLibHAL = new PiHal(1); + LOG_DEBUG("Attempting to activate sx1262 radio on SPI port %s\n", settingsStrings[spidev].c_str()); + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings); rIf = new SX1262Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], settingsMap[busy]); if (!rIf->init()) { @@ -709,7 +738,8 @@ void setup() } } else if (settingsMap[use_rf95]) { if (!rIf) { - PiHal *RadioLibHAL = new PiHal(1); + LOG_DEBUG("Attempting to activate rf95 radio on SPI port %s\n", settingsStrings[spidev].c_str()); + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings); rIf = new RF95Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], settingsMap[busy]); if (!rIf->init()) { @@ -721,6 +751,21 @@ void setup() LOG_INFO("RF95 Radio init succeeded, using RF95 radio\n"); } } + } else if (settingsMap[use_sx1280]) { + if (!rIf) { + LOG_DEBUG("Attempting to activate sx1280 radio on SPI port %s\n", settingsStrings[spidev].c_str()); + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings); + rIf = new SX1280Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], + settingsMap[busy]); + if (!rIf->init()) { + LOG_ERROR("Failed to find SX1280 radio\n"); + delete rIf; + rIf = NULL; + exit(EXIT_FAILURE); + } else { + LOG_INFO("SX1280 Radio init succeeded, using SX1280 radio\n"); + } + } } #elif defined(HW_SPI1_DEVICE) @@ -739,6 +784,7 @@ void setup() rIf = NULL; } else { LOG_INFO("STM32WL Radio init succeeded, using STM32WL radio\n"); + radioType = STM32WLx_RADIO; } } #endif @@ -752,24 +798,26 @@ void setup() rIf = NULL; } else { LOG_INFO("Using SIMULATED radio!\n"); + radioType = SIM_RADIO; } } #endif #if defined(RF95_IRQ) if (!rIf) { - rIf = new RF95Interface(RadioLibHAL, RF95_NSS, RF95_IRQ, RF95_RESET, RF95_DIO1); + rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1); if (!rIf->init()) { LOG_WARN("Failed to find RF95 radio\n"); delete rIf; rIf = NULL; } else { LOG_INFO("RF95 Radio init succeeded, using RF95 radio\n"); + radioType = RF95_RADIO; } } #endif -#if defined(USE_SX1262) && !defined(ARCH_RASPBERRY_PI) +#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) if (!rIf) { rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); if (!rIf->init()) { @@ -778,6 +826,7 @@ void setup() rIf = NULL; } else { LOG_INFO("SX1262 Radio init succeeded, using SX1262 radio\n"); + radioType = SX1262_RADIO; } } #endif @@ -791,6 +840,7 @@ void setup() rIf = NULL; } else { LOG_INFO("SX1268 Radio init succeeded, using SX1268 radio\n"); + radioType = SX1268_RADIO; } } #endif @@ -804,6 +854,7 @@ void setup() rIf = NULL; } else { LOG_INFO("LLCC68 Radio init succeeded, using LLCC68 radio\n"); + radioType = LLCC68_RADIO; } } #endif @@ -817,6 +868,7 @@ void setup() rIf = NULL; } else { LOG_INFO("SX1280 Radio init succeeded, using SX1280 radio\n"); + radioType = SX1280_RADIO; } } #endif @@ -826,7 +878,7 @@ void setup() if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (!rIf->wideLora())) { LOG_WARN("Radio chip does not support 2.4GHz LoRa. Reverting to unset.\n"); config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET; - nodeDB.saveToDisk(SEGMENT_CONFIG); + nodeDB->saveToDisk(SEGMENT_CONFIG); if (!rIf->reconfigure()) { LOG_WARN("Reconfigure failed, rebooting\n"); screen->startRebootScreen(); @@ -834,22 +886,34 @@ void setup() } } +#if !MESHTASTIC_EXCLUDE_MQTT mqttInit(); +#endif #ifndef ARCH_PORTDUINO - // Initialize Wifi - initWifi(); + // Initialize Wifi +#if HAS_WIFI + initWifi(); +#endif + +#if HAS_ETHERNET // Initialize Ethernet initEthernet(); #endif +#endif -#ifdef ARCH_ESP32 +#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WEBSERVER // Start web server thread. webServerThread = new WebServerThread(); #endif #ifdef ARCH_PORTDUINO +#if __has_include() + if (settingsMap[webserverport] != -1) { + piwebServerThread = new PiWebServerThread(); + } +#endif initApiServer(TCPPort); #endif @@ -870,7 +934,6 @@ void setup() // This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values PowerFSM_setup(); // we will transition to ON in a couple of seconds, FIXME, only do this for cold boots, not waking from SDS powerFSMthread = new PowerFSMThread(); - setCPUFast(false); // 80MHz is fine for our slow peripherals } diff --git a/src/main.h b/src/main.h index 5c9de1b81..bb812b7b6 100644 --- a/src/main.h +++ b/src/main.h @@ -22,6 +22,11 @@ extern NimbleBluetooth *nimbleBluetooth; extern NRF52Bluetooth *nrf52Bluetooth; #endif +#if ARCH_PORTDUINO +extern HardwareSPI *DisplaySPI; +extern HardwareSPI *LoraSPI; + +#endif extern ScanI2C::DeviceAddress screen_found; extern ScanI2C::DeviceAddress cardkb_found; extern uint8_t kb_model; @@ -42,12 +47,19 @@ extern ATECCX08A atecc; #include extern Adafruit_DRV2605 drv; #endif + +#ifdef HAS_I2S +#include "AudioThread.h" +extern AudioThread *audioThread; +#endif + extern bool isVibrating; extern int TCPPort; // set by Portduino // Global Screen singleton. extern graphics::Screen *screen; + // extern Observable newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class // extern meshtastic::PowerStatus *powerStatus; @@ -56,7 +68,6 @@ extern graphics::Screen *screen; // Return a human readable string of the form "Meshtastic_ab13" const char *getDeviceName(); -void getPiMacAddr(uint8_t *dmac); extern uint32_t timeLastPowered; @@ -65,13 +76,11 @@ extern uint32_t shutdownAtMsec; extern uint32_t serialSinceMsec; -extern int heltec_version; - // If a thread does something that might need for it to be rescheduled ASAP it can set this flag // This will suppress the current delay and instead try to run ASAP. extern bool runASAP; -void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop(), rp2040Setup(), clearBonds(); +void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop(), rp2040Setup(), clearBonds(), enterDfuMode(); meshtastic_DeviceMetadata getDeviceMetadata(); diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index 9974297fa..079af4eca 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -2,10 +2,15 @@ #include "CryptoEngine.h" #include "DisplayFormatters.h" #include "NodeDB.h" +#include "RadioInterface.h" #include "configuration.h" #include +#if !MESHTASTIC_EXCLUDE_MQTT +#include "mqtt/MQTT.h" +#endif + /// 16 bytes of random PSK for our _public_ default channel that all devices power up on (AES128) static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0x01}; @@ -15,6 +20,9 @@ Channels channels; const char *Channels::adminChannel = "admin"; const char *Channels::gpioChannel = "gpio"; const char *Channels::serialChannel = "serial"; +#if !MESHTASTIC_EXCLUDE_MQTT +const char *Channels::mqttChannel = "mqtt"; +#endif uint8_t xorHash(const uint8_t *p, size_t len) { @@ -86,6 +94,8 @@ void Channels::initDefaultChannel(ChannelIndex chIndex) channelSettings.psk.bytes[0] = defaultpskIndex; channelSettings.psk.size = 1; strncpy(channelSettings.name, "", sizeof(channelSettings.name)); + channelSettings.module_settings.position_precision = 32; // default to sending location on the primary channel + channelSettings.has_module_settings = true; ch.has_settings = true; ch.role = meshtastic_Channel_Role_PRIMARY; @@ -184,11 +194,17 @@ void Channels::onConfigChanged() { // Make sure the phone hasn't mucked anything up for (int i = 0; i < channelFile.channels_count; i++) { - meshtastic_Channel &ch = fixupChannel(i); + const meshtastic_Channel &ch = fixupChannel(i); if (ch.role == meshtastic_Channel_Role_PRIMARY) primaryIndex = i; } +#if !MESHTASTIC_EXCLUDE_MQTT + if (channels.anyMqttEnabled() && mqtt && !mqtt->isEnabled()) { + LOG_DEBUG("MQTT is enabled on at least one channel, so set MQTT thread to run immediately\n"); + mqtt->start(); + } +#endif } meshtastic_Channel &Channels::getByIndex(ChannelIndex chIndex) @@ -233,6 +249,16 @@ void Channels::setChannel(const meshtastic_Channel &c) old = c; // slam in the new settings/role } +bool Channels::anyMqttEnabled() +{ + for (int i = 0; i < getNumChannels(); i++) + if (channelFile.channels[i].role != meshtastic_Channel_Role_DISABLED && channelFile.channels[i].has_settings && + (channelFile.channels[i].settings.downlink_enabled || channelFile.channels[i].settings.uplink_enabled)) + return true; + + return false; +} + const char *Channels::getName(size_t chIndex) { // Convert the short "" representation for Default into a usable string @@ -251,38 +277,23 @@ const char *Channels::getName(size_t chIndex) return channelName; } -/** -* Generate a short suffix used to disambiguate channels that might have the same "name" entered by the human but different PSKs. -* The ideas is that the PSK changing should be visible to the user so that they see they probably messed up and that's why they -their nodes -* aren't talking to each other. -* -* This string is of the form "#name-X". -* -* Where X is either: -* (for custom PSKS) a letter from A to Z (base26), and formed by xoring all the bytes of the PSK together, -* -* This function will also need to be implemented in GUI apps that talk to the radio. -* -* https://github.com/meshtastic/firmware/issues/269 -*/ -const char *Channels::getPrimaryName() +bool Channels::hasDefaultChannel() { - static char buf[32]; - - char suffix; - // auto channelSettings = getPrimary(); - // if (channelSettings.psk.size != 1) { - // We have a standard PSK, so generate a letter based hash. - uint8_t code = getHash(primaryIndex); - - suffix = 'A' + (code % 26); - /* } else { - suffix = '0' + channelSettings.psk.bytes[0]; - } */ - - snprintf(buf, sizeof(buf), "#%s-%c", getName(primaryIndex), suffix); - return buf; + // If we don't use a preset or the default frequency slot, or we override the frequency, we don't have a default channel + if (!config.lora.use_preset || !RadioInterface::uses_default_frequency_slot || config.lora.override_frequency) + return false; + // Check if any of the channels are using the default name and PSK + for (size_t i = 0; i < getNumChannels(); i++) { + const auto &ch = getByIndex(i); + if (ch.settings.psk.size == 1 && ch.settings.psk.bytes[0] == 1) { + const char *name = getName(i); + const char *presetName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false); + // Check if the name is the default derived from the modem preset + if (strcmp(name, presetName) == 0) + return true; + } + } + return false; } /** Given a channel hash setup crypto for decoding that channel (or the primary channel if that channel is unsecured) @@ -313,4 +324,4 @@ bool Channels::decryptForHash(ChannelIndex chIndex, ChannelHash channelHash) int16_t Channels::setActiveByIndex(ChannelIndex channelIndex) { return setCrypto(channelIndex); -} +} \ No newline at end of file diff --git a/src/mesh/Channels.h b/src/mesh/Channels.h index b4bdcbd5c..952445a1d 100644 --- a/src/mesh/Channels.h +++ b/src/mesh/Channels.h @@ -32,7 +32,7 @@ class Channels Channels() {} /// Well known channel names - static const char *adminChannel, *gpioChannel, *serialChannel; + static const char *adminChannel, *gpioChannel, *serialChannel, *mqttChannel; const meshtastic_ChannelSettings &getPrimary() { return getByIndex(getPrimaryIndex()).settings; } @@ -61,25 +61,6 @@ class Channels ChannelIndex getNumChannels() { return channelFile.channels_count; } - /** - * Generate a short suffix used to disambiguate channels that might have the same "name" entered by the human but different - PSKs. - * The ideas is that the PSK changing should be visible to the user so that they see they probably messed up and that's why - they their nodes - * aren't talking to each other. - * - * This string is of the form "#name-X". - * - * Where X is either: - * (for custom PSKS) a letter from A to Z (base26), and formed by xoring all the bytes of the PSK together, - * OR (for the standard minimially secure PSKs) a number from 0 to 9. - * - * This function will also need to be implemented in GUI apps that talk to the radio. - * - * https://github.com/meshtastic/firmware/issues/269 - */ - const char *getPrimaryName(); - /// Called by NodeDB on initial boot when the radio config settings are unset. Set a default single channel config. void initDefaults(); @@ -102,6 +83,12 @@ class Channels */ int16_t setActiveByIndex(ChannelIndex channelIndex); + // Returns true if we can be reached via a channel with the default settings given a region and modem preset + bool hasDefaultChannel(); + + // Returns true if any of our channels have enabled MQTT uplink or downlink + bool anyMqttEnabled(); + private: /** Given a channel index, change to use the crypto key specified by that index * @@ -139,4 +126,4 @@ class Channels }; /// Singleton channel table -extern Channels channels; +extern Channels channels; \ No newline at end of file diff --git a/src/mesh/Default.cpp b/src/mesh/Default.cpp new file mode 100644 index 000000000..db058c5b0 --- /dev/null +++ b/src/mesh/Default.cpp @@ -0,0 +1,23 @@ +#include "Default.h" + +uint32_t Default::getConfiguredOrDefaultMs(uint32_t configuredInterval) +{ + if (configuredInterval > 0) + return configuredInterval * 1000; + return default_broadcast_interval_secs * 1000; +} + +uint32_t Default::getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval) +{ + if (configuredInterval > 0) + return configuredInterval * 1000; + return defaultInterval * 1000; +} + +uint32_t Default::getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue) +{ + if (configured > 0) + return configured; + + return defaultValue; +} \ No newline at end of file diff --git a/src/mesh/Default.h b/src/mesh/Default.h new file mode 100644 index 000000000..95723744b --- /dev/null +++ b/src/mesh/Default.h @@ -0,0 +1,31 @@ +#pragma once +#include +#include +#define ONE_DAY 24 * 60 * 60 +#define ONE_MINUTE_MS 60 * 1000 + +#define default_gps_update_interval IF_ROUTER(ONE_DAY, 2 * 60) +#define default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 15 * 60) +#define default_wait_bluetooth_secs IF_ROUTER(1, 60) +#define default_sds_secs IF_ROUTER(ONE_DAY, UINT32_MAX) // Default to forever super deep sleep +#define default_ls_secs IF_ROUTER(ONE_DAY, 5 * 60) +#define default_min_wake_secs 10 +#define default_screen_on_secs IF_ROUTER(1, 60 * 10) +#define default_node_info_broadcast_secs 3 * 60 * 60 +#define min_node_info_broadcast_secs 60 * 60 // No regular broadcasts of more than once an hour + +#define default_mqtt_address "mqtt.meshtastic.org" +#define default_mqtt_username "meshdev" +#define default_mqtt_password "large4cats" +#define default_mqtt_root "msh" + +#define IF_ROUTER(routerVal, normalVal) \ + ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) ? (routerVal) : (normalVal)) + +class Default +{ + public: + static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval); + static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval); + static uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue); +}; \ No newline at end of file diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index d0061ed8a..7fd720480 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -21,7 +21,7 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) { if (wasSeenRecently(p)) { // Note: this will also add a recent packet record printPacket("Ignoring incoming msg, because we've already seen it", p); - if (!moduleConfig.mqtt.enabled && config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER && + if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER && config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT && config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) { // cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater! @@ -49,15 +49,6 @@ void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas tosend->hop_limit--; // bump down the hop count - if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { - // If it is a traceRoute request, update the route that it went via me - if (traceRouteModule && traceRouteModule->wantPacket(p)) - traceRouteModule->updateRoute(tosend); - // If it is a neighborInfo packet, update last_sent_by_id - if (neighborInfoModule && neighborInfoModule->wantPacket(p)) - neighborInfoModule->updateLastSentById(tosend); - } - LOG_INFO("Rebroadcasting received floodmsg to neighbors\n"); // Note: we are careful to resend using the original senders node id // We are careful not to call our hooked version of send() - because we don't want to check this again diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h index 309035cb3..a3adfe70c 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -2,8 +2,6 @@ #include "PacketHistory.h" #include "Router.h" -#include "modules/NeighborInfoModule.h" -#include "modules/TraceRouteModule.h" /** * This is a mixin that extends Router with the ability to do Naive Flooding (in the standard mesh protocol sense) diff --git a/src/mesh/MemoryPool.h b/src/mesh/MemoryPool.h index 84cac7eff..d30404b9f 100644 --- a/src/mesh/MemoryPool.h +++ b/src/mesh/MemoryPool.h @@ -73,58 +73,3 @@ template class MemoryDynamic : public Allocator return p; } }; - -/** - * A pool based allocator - * - */ -template class MemoryPool : public Allocator -{ - PointerQueue dead; - - T *buf; // our large raw block of memory - - size_t maxElements; - - public: - explicit MemoryPool(size_t _maxElements) : dead(_maxElements), maxElements(_maxElements) - { - buf = new T[maxElements]; - - // prefill dead - for (size_t i = 0; i < maxElements; i++) - release(&buf[i]); - } - - ~MemoryPool() { delete[] buf; } - - /// Return a buffer for use by others - void release(T *p) - { - assert(p >= buf && - (size_t)(p - buf) < - maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool - assert(dead.enqueue(p, 0)); - } - -#ifdef HAS_FREE_RTOS - /// Return a buffer from an ISR, if higherPriWoken is set to true you have some work to do ;-) - void releaseFromISR(T *p, BaseType_t *higherPriWoken) - { - assert(p >= buf && - (size_t)(p - buf) < - maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool - assert(dead.enqueueFromISR(p, higherPriWoken)); - } -#endif - - protected: - /// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you - /// probably don't want this version). - virtual T *alloc(TickType_t maxWait) - { - T *p = dead.dequeuePtr(maxWait); - assert(p); - return p; - } -}; diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index a1e719721..2ef46e4db 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -12,7 +12,7 @@ const meshtastic_MeshPacket *MeshModule::currentRequest; /** * If any of the current chain of modules has already sent a reply, it will be here. This is useful to allow - * the RoutingPlugin to avoid sending redundant acks + * the RoutingModule to avoid sending redundant acks */ meshtastic_MeshPacket *MeshModule::currentReply; @@ -32,14 +32,15 @@ MeshModule::~MeshModule() assert(0); // FIXME - remove from list of modules once someone needs this feature } -meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex) +meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, + uint8_t hopStart, uint8_t hopLimit) { meshtastic_Routing c = meshtastic_Routing_init_default; c.error_reason = err; c.which_variant = meshtastic_Routing_error_reason_tag; - // Now that we have moded sendAckNak up one level into the class hierarchy we can no longer assume we are a RoutingPlugin + // Now that we have moded sendAckNak up one level into the class hierarchy we can no longer assume we are a RoutingModule // So we manually call pb_encode_to_bytes and specify routing port number // auto p = allocDataProtobuf(c); meshtastic_MeshPacket *p = router->allocForSending(); @@ -49,11 +50,12 @@ meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, Nod p->priority = meshtastic_MeshPacket_Priority_ACK; - p->hop_limit = config.lora.hop_limit; // Flood ACK back to original sender + p->hop_limit = routingModule->getHopLimitForResponse(hopStart, hopLimit); // Flood ACK back to original sender p->to = to; p->decoded.request_id = idFrom; p->channel = chIndex; - LOG_ERROR("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id); + if (err != meshtastic_Routing_Error_NONE) + LOG_ERROR("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id); return p; } @@ -67,7 +69,7 @@ meshtastic_MeshPacket *MeshModule::allocErrorResponse(meshtastic_Routing_Error e return r; } -void MeshModule::callPlugins(const meshtastic_MeshPacket &mp, RxSource src) +void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src) { // LOG_DEBUG("In call modules\n"); bool moduleFound = false; @@ -80,7 +82,7 @@ void MeshModule::callPlugins(const meshtastic_MeshPacket &mp, RxSource src) bool ignoreRequest = false; // No module asked to ignore the request yet // Was this message directed to us specifically? Will be false if we are sniffing someone elses packets - auto ourNodeNum = nodeDB.getNodeNum(); + auto ourNodeNum = nodeDB->getNodeNum(); bool toUs = mp.to == NODENUM_BROADCAST || mp.to == ourNodeNum; for (auto i = modules->begin(); i != modules->end(); ++i) { @@ -124,9 +126,10 @@ void MeshModule::callPlugins(const meshtastic_MeshPacket &mp, RxSource src) } else printPacket("packet on wrong channel, but can't respond", &mp); } else { - ProcessMessage handled = pi.handleReceived(mp); + pi.alterReceived(mp); + // Possibly send replies (but only if the message was directed to us specifically, i.e. not for promiscious // sniffing) also: we only let the one module send a reply, once that happens, remaining modules are not // considered @@ -175,7 +178,8 @@ void MeshModule::callPlugins(const meshtastic_MeshPacket &mp, RxSource src) // SECURITY NOTE! I considered sending back a different error code if we didn't find the psk (i.e. !isDecoded) // but opted NOT TO. Because it is not a good idea to let remote nodes 'probe' to find out which PSKs were "good" vs // bad. - routingModule->sendAckNak(meshtastic_Routing_Error_NO_RESPONSE, getFrom(&mp), mp.id, mp.channel); + routingModule->sendAckNak(meshtastic_Routing_Error_NO_RESPONSE, getFrom(&mp), mp.id, mp.channel, mp.hop_start, + mp.hop_limit); } } @@ -216,6 +220,7 @@ void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to) assert(p->which_payload_variant == meshtastic_MeshPacket_decoded_tag); // Should already be set by now p->to = getFrom(&to); // Make sure that if we are sending to the local node, we use our local node addr, not 0 p->channel = to.channel; // Use the same channel that the request came in on + p->hop_limit = routingModule->getHopLimitForResponse(to.hop_start, to.hop_limit); // No need for an ack if we are just delivering locally (it just generates an ignored ack) p->want_ack = (to.from != 0) ? to.want_ack : false; @@ -254,7 +259,7 @@ void MeshModule::observeUIEvents(Observer *observer) } } -AdminMessageHandleResult MeshModule::handleAdminMessageForAllPlugins(const meshtastic_MeshPacket &mp, +AdminMessageHandleResult MeshModule::handleAdminMessageForAllModules(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, meshtastic_AdminMessage *response) { @@ -275,4 +280,4 @@ AdminMessageHandleResult MeshModule::handleAdminMessageForAllPlugins(const mesht } } return handled; -} +} \ No newline at end of file diff --git a/src/mesh/MeshModule.h b/src/mesh/MeshModule.h index 323cc8595..2e2af33e0 100644 --- a/src/mesh/MeshModule.h +++ b/src/mesh/MeshModule.h @@ -64,18 +64,15 @@ class MeshModule /** For use only by MeshService */ - static void callPlugins(const meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO); + static void callModules(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO); static std::vector GetMeshModulesWithUIFrames(); static void observeUIEvents(Observer *observer); - static AdminMessageHandleResult handleAdminMessageForAllPlugins(const meshtastic_MeshPacket &mp, + static AdminMessageHandleResult handleAdminMessageForAllModules(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, meshtastic_AdminMessage *response); #if HAS_SCREEN - virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - return; - } + virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { return; } #endif protected: const char *name; @@ -135,10 +132,12 @@ class MeshModule @return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for it */ - virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) - { - return ProcessMessage::CONTINUE; - } + virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) { return ProcessMessage::CONTINUE; } + + /** Called to change a particular incoming message + This allows the module to change the message before it is passed through the rest of the call-chain. + */ + virtual void alterReceived(meshtastic_MeshPacket &mp) {} /** Messages can be received that have the want_response bit set. If set, this callback will be invoked * so that subclasses can (optionally) send a response back to the original sender. @@ -151,16 +150,11 @@ class MeshModule /*** * @return true if you want to be alloced a UI screen frame */ - virtual bool wantUIFrame() - { - return false; - } - virtual Observable *getUIFrameObservable() - { - return NULL; - } + virtual bool wantUIFrame() { return false; } + virtual Observable *getUIFrameObservable() { return NULL; } - meshtastic_MeshPacket *allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex); + meshtastic_MeshPacket *allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, + uint8_t hopStart = 0, uint8_t hopLimit = 0); /// Send an error response for the specified packet. meshtastic_MeshPacket *allocErrorResponse(meshtastic_Routing_Error err, const meshtastic_MeshPacket *p); @@ -201,4 +195,4 @@ class MeshModule /** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet * This ensures that if the request packet was sent reliably, the reply is sent that way as well. */ -void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to); +void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to); \ No newline at end of file diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 44094f1bb..2c1969e30 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -1,10 +1,11 @@ #include "configuration.h" -#include -#include + +#if !MESHTASTIC_EXCLUDE_GPS +#include "GPS.h" +#endif #include "../concurrency/Periodic.h" #include "BluetoothCommon.h" // needed for updateBatteryLevel, FIXME, eventually when we pull mesh out into a lib we shouldn't be whacking bluetooth from here -#include "GPS.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" @@ -15,8 +16,10 @@ #include "modules/NodeInfoModule.h" #include "modules/PositionModule.h" #include "power.h" +#include +#include -#ifdef ARCH_ESP32 +#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH #include "nimble/NimbleBluetooth.h" #endif @@ -72,22 +75,23 @@ void MeshService::init() { // moved much earlier in boot (called from setup()) // nodeDB.init(); - +#if HAS_GPS if (gps) gpsObserver.observe(&gps->newStatus); +#endif } int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp) { powerFSM.trigger(EVENT_PACKET_FOR_PHONE); // Possibly keep the node from sleeping - nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio + nodeDB->updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp->decoded.portnum == meshtastic_PortNum_TELEMETRY_APP && mp->decoded.request_id > 0) { LOG_DEBUG( "Received telemetry response. Skip sending our NodeInfo because this potentially a Repeater which will ignore our " "request for its NodeInfo.\n"); - } else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB.getMeshNode(mp->from)->has_user && + } else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user && nodeInfoModule) { LOG_INFO("Heard a node on channel %d we don't know, sending NodeInfo and asking for a response.\n", mp->channel); nodeInfoModule->sendOurNodeInfo(mp->from, true, mp->channel); @@ -108,8 +112,9 @@ void MeshService::loop() (void)sendQueueStatusToPhone(qs, 0, 0); } if (oldFromNum != fromNum) { // We don't want to generate extra notifies for multiple new packets - fromNumChanged.notifyObservers(fromNum); - oldFromNum = fromNum; + int result = fromNumChanged.notifyObservers(fromNum); + if (result == 0) // If any observer returns non-zero, we will try again + oldFromNum = fromNum; } } @@ -119,10 +124,10 @@ bool MeshService::reloadConfig(int saveWhat) // If we can successfully set this radio to these settings, save them to disk // This will also update the region as needed - bool didReset = nodeDB.resetRadioConfig(); // Don't let the phone send us fatally bad settings + bool didReset = nodeDB->resetRadioConfig(); // Don't let the phone send us fatally bad settings configChanged.notifyObservers(NULL); // This will cause radio hardware to change freqs etc - nodeDB.saveToDisk(saveWhat); + nodeDB->saveToDisk(saveWhat); return didReset; } @@ -132,7 +137,7 @@ void MeshService::reloadOwner(bool shouldSave) { // LOG_DEBUG("reloadOwner()\n"); // update our local data directly - nodeDB.updateUser(nodeDB.getNodeNum(), owner); + nodeDB->updateUser(nodeDB->getNodeNum(), owner); assert(nodeInfoModule); // update everyone else and save to disk if (nodeInfoModule && shouldSave) { @@ -140,6 +145,22 @@ void MeshService::reloadOwner(bool shouldSave) } } +// search the queue for a request id and return the matching nodenum +NodeNum MeshService::getNodenumFromRequestId(uint32_t request_id) +{ + NodeNum nodenum = 0; + for (int i = 0; i < toPhoneQueue.numUsed(); i++) { + meshtastic_MeshPacket *p = toPhoneQueue.dequeuePtr(0); + if (p->id == request_id) { + nodenum = p->to; + // make sure to continue this to make one full loop + } + // put it right back on the queue + toPhoneQueue.enqueue(p, 0); + } + return nodenum; +} + /** * Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh) * Called by PhoneAPI.handleToRadio. Note: p is a scratch buffer, this function is allowed to write to it but it can not keep a @@ -175,7 +196,7 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p) LOG_WARN("phone tried to pick a nodenum, we don't allow that.\n"); p.from = 0; } else { - // p.from = nodeDB.getNodeNum(); + // p.from = nodeDB->getNodeNum(); } if (p.id == 0) @@ -200,7 +221,7 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p) /** Attempt to cancel a previously sent packet from this _local_ node. Returns true if a packet was found we could cancel */ bool MeshService::cancelSending(PacketId id) { - return router->cancelSending(nodeDB.getNodeNum(), id); + return router->cancelSending(nodeDB->getNodeNum(), id); } ErrorCode MeshService::sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, ErrorCode res, uint32_t mesh_packet_id) @@ -228,7 +249,7 @@ ErrorCode MeshService::sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPhone) { uint32_t mesh_packet_id = p->id; - nodeDB.updateFrom(*p); // update our local DB for this packet (because phone might have sent position packets etc...) + nodeDB->updateFrom(*p); // update our local DB for this packet (because phone might have sent position packets etc...) // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it ErrorCode res = router->sendLocal(p, src); @@ -248,16 +269,18 @@ void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPh void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies) { - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum()); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); assert(node); if (hasValidPosition(node)) { +#if HAS_GPS if (positionModule) { LOG_INFO("Sending position ping to 0x%x, wantReplies=%d, channel=%d\n", dest, wantReplies, node->channel); positionModule->sendOurPosition(dest, wantReplies, node->channel); } } else { +#endif if (nodeInfoModule) { LOG_INFO("Sending nodeinfo ping to 0x%x, wantReplies=%d, channel=%d\n", dest, wantReplies, node->channel); nodeInfoModule->sendOurNodeInfo(dest, wantReplies, node->channel); @@ -303,7 +326,7 @@ void MeshService::sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage meshtastic_NodeInfoLite *MeshService::refreshLocalMeshNode() { - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum()); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); assert(node); // We might not have a position yet for our local node, in that case, at least try to send the time @@ -320,11 +343,14 @@ meshtastic_NodeInfoLite *MeshService::refreshLocalMeshNode() position.time = getValidTime(RTCQualityFromNet); - updateBatteryLevel(powerStatus->getBatteryChargePercent()); + if (powerStatus->getHasBattery() == 1) { + updateBatteryLevel(powerStatus->getBatteryChargePercent()); + } return node; } +#if HAS_GPS int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus) { // Update our local node info with our position (even if we don't decide to update anyone else) @@ -340,7 +366,7 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus) LOG_DEBUG("onGPSchanged() - lost validLocation\n"); #endif } - // Used fixed position if configured regalrdless of GPS lock + // Used fixed position if configured regardless of GPS lock if (config.position.fixed_position) { LOG_WARN("Using fixed position\n"); pos = TypeConversions::ConvertToPosition(node->position); @@ -354,11 +380,11 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus) pos.longitude_i, pos.altitude); // Update our current position in the local DB - nodeDB.updatePosition(nodeDB.getNodeNum(), pos, RX_SRC_LOCAL); + nodeDB->updatePosition(nodeDB->getNodeNum(), pos, RX_SRC_LOCAL); return 0; } - +#endif bool MeshService::isToPhoneQueueEmpty() { return toPhoneQueue.isEmpty(); diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index eb40b7712..8d1434030 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -23,9 +23,10 @@ extern Allocator &mqttClientProxyMessagePool; */ class MeshService { +#if HAS_GPS CallbackObserver gpsObserver = CallbackObserver(this, &MeshService::onGPSChanged); - +#endif /// received packets waiting for the phone to process them /// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure /// we never hang because android hasn't been there in a while @@ -82,6 +83,9 @@ class MeshService /// Return the next MqttClientProxyMessage packet destined to the phone. meshtastic_MqttClientProxyMessage *getMqttClientProxyMessageForPhone() { return toPhoneMqttProxyQueue.dequeuePtr(0); } + // search the queue for a request id and return the matching nodenum + NodeNum getNodenumFromRequestId(uint32_t request_id); + // Release QueueStatus packet to pool void releaseQueueStatusToPool(meshtastic_QueueStatus *p) { queueStatusPool.release(p); } @@ -126,17 +130,18 @@ class MeshService bool isToPhoneQueueEmpty(); + ErrorCode sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, ErrorCode res, uint32_t mesh_packet_id); + private: +#if HAS_GPS /// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh /// returns 0 to allow further processing int onGPSChanged(const meshtastic::GPSStatus *arg); - +#endif /// Handle a packet that just arrived from the radio. This method does _ReliableRouternot_ free the provided packet. If it /// needs to keep the packet around it makes a copy int handleFromRadio(const meshtastic_MeshPacket *p); friend class RoutingModule; - - ErrorCode sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, ErrorCode res, uint32_t mesh_packet_id); }; extern MeshService service; \ No newline at end of file diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp index 61a5d9dfd..8b29217b6 100644 --- a/src/mesh/NextHopRouter.cpp +++ b/src/mesh/NextHopRouter.cpp @@ -19,7 +19,7 @@ ErrorCode NextHopRouter::send(meshtastic_MeshPacket *p) bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) { if (wasSeenRecently(p)) { // Note: this will also add a recent packet record - if (p->next_hop == nodeDB.getLastByteOfNodeNum(getNodeNum())) { + if (p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) { LOG_DEBUG("Ignoring incoming msg, because we've already seen it.\n"); } else { LOG_DEBUG("Ignoring incoming msg, because we've already seen it and cancel any outgoing packets.\n"); @@ -41,7 +41,7 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast // not 0 or ourselves (means implicit ACK or someone is relaying our ACK) if (p->from != 0 && p->from != ourNodeNum) { if (p->relay_node) { - meshtastic_NodeInfoLite *origTx = nodeDB.getMeshNode(p->from); + meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from); if (origTx) { LOG_DEBUG("Update next hop of 0x%x to 0x%x based on received DM or ACK.\n", p->from, p->relay_node); origTx->next_hop = p->relay_node; @@ -52,19 +52,10 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast if (config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE) { if ((p->to != ourNodeNum) && (getFrom(p) != ourNodeNum)) { - if (p->next_hop == nodeDB.getLastByteOfNodeNum(ourNodeNum)) { + if (p->next_hop == nodeDB->getLastByteOfNodeNum(ourNodeNum)) { meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it LOG_INFO("Relaying received next-hop message coming from %x\n", p->relay_node); - if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { - // If it is a traceRoute request, update the route that it went via me - if (traceRouteModule && traceRouteModule->wantPacket(tosend)) - traceRouteModule->updateRoute(tosend); - // If it is a neighborInfo packet, update last_sent_by_id - if (neighborInfoModule && neighborInfoModule->wantPacket(tosend)) - neighborInfoModule->updateLastSentById(tosend); - } - tosend->hop_limit--; // bump down the hop count NextHopRouter::send(tosend); } else if (p->next_hop == NO_NEXT_HOP_PREFERENCE) { @@ -86,7 +77,7 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast */ uint8_t NextHopRouter::getNextHop(NodeNum to, uint8_t relay_node) { - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(to); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(to); if (node) { // We are careful not to return the relay node as the next hop if (node->next_hop != relay_node) { diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 8468cf7f0..b6fc0e57d 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1,10 +1,12 @@ #include "configuration.h" - +#if !MESHTASTIC_EXCLUDE_GPS +#include "GPS.h" +#endif #include "../detect/ScanI2C.h" #include "Channels.h" #include "CryptoEngine.h" +#include "Default.h" #include "FSCommon.h" -#include "GPS.h" #include "MeshRadio.h" #include "NodeDB.h" #include "PacketHistory.h" @@ -17,22 +19,31 @@ #include "mesh-pb-constants.h" #include "modules/NeighborInfoModule.h" #include +#include +#include #include #include +#include #ifdef ARCH_ESP32 -#include "mesh/http/WiFiAPClient.h" +#if !MESHTASTIC_EXCLUDE_WIFI +#include "mesh/wifi/WiFiAPClient.h" +#endif #include "modules/esp32/StoreForwardModule.h" #include #include #endif +#ifdef ARCH_PORTDUINO +#include "platform/portduino/PortduinoGlue.h" +#endif + #ifdef ARCH_NRF52 #include #include #endif -NodeDB nodeDB; +NodeDB *nodeDB = nullptr; // we have plenty of ram so statically alloc this tempbuf (for now) EXT_RAM_ATTR meshtastic_DeviceState devicestate; @@ -42,6 +53,26 @@ meshtastic_LocalModuleConfig moduleConfig; meshtastic_ChannelFile channelFile; meshtastic_OEMStore oemStore; +bool meshtastic_DeviceState_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field) +{ + if (ostream) { + std::vector *vec = (std::vector *)field->pData; + for (auto item : *vec) { + if (!pb_encode_tag_for_field(ostream, field)) + return false; + pb_encode_submessage(ostream, meshtastic_NodeInfoLite_fields, &item); + } + } + if (istream) { + meshtastic_NodeInfoLite node; // this gets good data + std::vector *vec = (std::vector *)field->pData; + + if (istream->bytes_left && pb_decode(istream, meshtastic_NodeInfoLite_fields, &node)) + vec->push_back(node); + } + return true; +} + /** The current change # for radio settings. Starts at 0 on boot and any time the radio settings * might have changed is incremented. Allows others to detect they might now be on a new channel. */ @@ -64,7 +95,63 @@ uint32_t error_address = 0; static uint8_t ourMacAddr[6]; -NodeDB::NodeDB() : meshNodes(devicestate.node_db_lite), numMeshNodes(&devicestate.node_db_lite_count) {} +NodeDB::NodeDB() +{ + LOG_INFO("Initializing NodeDB\n"); + loadFromDisk(); + cleanupMeshDB(); + + uint32_t devicestateCRC = crc32Buffer(&devicestate, sizeof(devicestate)); + uint32_t configCRC = crc32Buffer(&config, sizeof(config)); + uint32_t channelFileCRC = crc32Buffer(&channelFile, sizeof(channelFile)); + + int saveWhat = 0; + + // likewise - we always want the app requirements to come from the running appload + myNodeInfo.min_app_version = 30200; // format is Mmmss (where M is 1+the numeric major number. i.e. 30200 means 2.2.00 + // Note! We do this after loading saved settings, so that if somehow an invalid nodenum was stored in preferences we won't + // keep using that nodenum forever. Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts) + pickNewNodeNum(); + + // Set our board type so we can share it with others + owner.hw_model = HW_VENDOR; + // Ensure user (nodeinfo) role is set to whatever we're configured to + owner.role = config.device.role; + + // Include our owner in the node db under our nodenum + meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum()); + info->user = owner; + info->has_user = true; + +#ifdef ARCH_ESP32 + Preferences preferences; + preferences.begin("meshtastic", false); + myNodeInfo.reboot_count = preferences.getUInt("rebootCounter", 0); + preferences.end(); + LOG_DEBUG("Number of Device Reboots: %d\n", myNodeInfo.reboot_count); +#endif + + resetRadioConfig(); // If bogus settings got saved, then fix them + // nodeDB->LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d\n", config.lora.region, myNodeInfo.my_node_num, numMeshNodes); + + if (devicestateCRC != crc32Buffer(&devicestate, sizeof(devicestate))) + saveWhat |= SEGMENT_DEVICESTATE; + if (configCRC != crc32Buffer(&config, sizeof(config))) + saveWhat |= SEGMENT_CONFIG; + if (channelFileCRC != crc32Buffer(&channelFile, sizeof(channelFile))) + saveWhat |= SEGMENT_CHANNELS; + + if (!devicestate.node_remote_hardware_pins) { + meshtastic_NodeRemoteHardwarePin empty[12] = {meshtastic_RemoteHardwarePin_init_default}; + memcpy(devicestate.node_remote_hardware_pins, empty, sizeof(empty)); + } + + if (config.position.gps_enabled) { + config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; + config.position.gps_enabled = 0; + } + saveToDisk(saveWhat); +} /** * Most (but not always) of the time we want to treat packets 'from' the local phone (where from == 0), as if they originated on @@ -72,7 +159,7 @@ NodeDB::NodeDB() : meshNodes(devicestate.node_db_lite), numMeshNodes(&devicestat */ NodeNum getFrom(const meshtastic_MeshPacket *p) { - return (p->from == 0) ? nodeDB.getNodeNum() : p->from; + return (p->from == 0) ? nodeDB->getNodeNum() : p->from; } bool NodeDB::resetRadioConfig(bool factory_reset) @@ -93,22 +180,6 @@ bool NodeDB::resetRadioConfig(bool factory_reset) channels.onConfigChanged(); - // temp hack for quicker testing - // devicestate.no_save = true; - if (devicestate.no_save) { - LOG_DEBUG("***** DEVELOPMENT MODE - DO NOT RELEASE *****\n"); - - // Sleep quite frequently to stress test the BLE comms, broadcast position every 6 mins - config.display.screen_on_secs = 10; - config.power.wait_bluetooth_secs = 10; - config.position.position_broadcast_secs = 6 * 60; - config.power.ls_secs = 60; - config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_TW; - - // Enter super deep sleep soon and stay there not very long - // radioConfig.preferences.sds_secs = 60; - } - // Update the global myRegion initRegion(); @@ -126,6 +197,9 @@ bool NodeDB::factoryReset() LOG_INFO("Performing factory reset!\n"); // first, remove the "/prefs" (this removes most prefs) rmDir("/prefs"); + if (FSCom.exists("/static/rangetest.csv") && !FSCom.remove("/static/rangetest.csv")) { + LOG_ERROR("Could not remove rangetest.csv file\n"); + } // second, install default state (this will deal with the duplicate mac address issue) installDefaultDeviceState(); installDefaultConfig(); @@ -159,7 +233,7 @@ void NodeDB::installDefaultConfig() config.has_position = true; config.has_power = true; config.has_network = true; - config.has_bluetooth = true; + config.has_bluetooth = (HAS_BLUETOOTH ? true : false); config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_ALL; config.lora.sx126x_rx_boosted_gain = true; @@ -169,6 +243,7 @@ void NodeDB::installDefaultConfig() config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET; config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; config.lora.hop_limit = HOP_RELIABLE; + config.lora.ignore_mqtt = false; #ifdef PIN_GPS_EN config.position.gps_en_gpio = PIN_GPS_EN; #endif @@ -177,12 +252,21 @@ void NodeDB::installDefaultConfig() #else config.device.disable_triple_click = true; #endif - config.position.gps_enabled = true; +#if !HAS_GPS || defined(T_DECK) + config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT; +#elif !defined(GPS_RX_PIN) + if (config.position.rx_gpio == 0) + config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT; + else + config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED; +#else + config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; +#endif config.position.position_broadcast_smart_enabled = true; config.position.broadcast_smart_minimum_distance = 100; config.position.broadcast_smart_minimum_interval_secs = 30; if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER) - config.device.node_info_broadcast_secs = 3 * 60 * 60; + config.device.node_info_broadcast_secs = default_node_info_broadcast_secs; config.device.serial_enabled = true; resetRadioConfig(); strncpy(config.network.ntp_server, "0.pool.ntp.org", 32); @@ -191,6 +275,12 @@ void NodeDB::installDefaultConfig() config.bluetooth.fixed_pin = defaultBLEPin; #if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) bool hasScreen = true; +#elif ARCH_PORTDUINO + bool hasScreen = false; + if (settingsMap[displayPanel]) + hasScreen = true; + else + hasScreen = screen_found.port != ScanI2C::I2CPort::NO_I2C; #else bool hasScreen = screen_found.port != ScanI2C::I2CPort::NO_I2C; #endif @@ -213,7 +303,6 @@ void NodeDB::installDefaultConfig() void NodeDB::initConfigIntervals() { config.position.gps_update_interval = default_gps_update_interval; - config.position.gps_attempt_time = default_gps_attempt_time; config.position.position_broadcast_secs = default_broadcast_interval_secs; config.power.ls_secs = default_ls_secs; @@ -222,6 +311,12 @@ void NodeDB::initConfigIntervals() config.power.wait_bluetooth_secs = default_wait_bluetooth_secs; config.display.screen_on_secs = default_screen_on_secs; + +#if defined(T_WATCH_S3) || defined(T_DECK) + config.power.is_power_saving = true; + config.display.screen_on_secs = 30; + config.power.wait_bluetooth_secs = 30; +#endif } void NodeDB::installDefaultModuleConfig() @@ -245,9 +340,21 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.external_notification.output_ms = 1000; moduleConfig.external_notification.nag_timeout = 60; #endif -#ifdef T_WATCH_S3 - // Don't worry about the other settings, we'll use the DRV2056 behavior for notifications +#ifdef HAS_I2S + // Don't worry about the other settings for T-Watch, we'll also use the DRV2056 behavior for notifications moduleConfig.external_notification.enabled = true; + moduleConfig.external_notification.use_i2s_as_buzzer = true; + moduleConfig.external_notification.alert_message_buzzer = true; + moduleConfig.external_notification.nag_timeout = 60; +#endif +#ifdef NANO_G2_ULTRA + moduleConfig.external_notification.enabled = true; + moduleConfig.external_notification.alert_message = true; + moduleConfig.external_notification.output_ms = 100; + moduleConfig.external_notification.active = true; +#endif +#ifdef TTGO_T_ECHO + config.display.wake_on_tap_or_motion = true; // Enable touch button for screen-on / refresh #endif moduleConfig.has_canned_message = true; @@ -282,11 +389,12 @@ void NodeDB::installRoleDefaults(meshtastic_Config_DeviceConfig_Role role) initModuleConfigIntervals(); } else if (role == meshtastic_Config_DeviceConfig_Role_REPEATER) { config.display.screen_on_secs = 1; - } else if (role == meshtastic_Config_DeviceConfig_Role_TRACKER) { - config.position.gps_update_interval = 30; } else if (role == meshtastic_Config_DeviceConfig_Role_SENSOR) { moduleConfig.telemetry.environment_measurement_enabled = true; moduleConfig.telemetry.environment_update_interval = 300; + } else if (role == meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND) { + config.position.position_broadcast_smart_enabled = false; + config.position.position_broadcast_secs = 300; // Every 5 minutes } else if (role == meshtastic_Config_DeviceConfig_Role_TAK) { config.device.node_info_broadcast_secs = ONE_DAY; config.position.position_broadcast_smart_enabled = false; @@ -296,6 +404,26 @@ void NodeDB::installRoleDefaults(meshtastic_Config_DeviceConfig_Role role) (meshtastic_Config_PositionConfig_PositionFlags_ALTITUDE | meshtastic_Config_PositionConfig_PositionFlags_SPEED | meshtastic_Config_PositionConfig_PositionFlags_HEADING | meshtastic_Config_PositionConfig_PositionFlags_DOP); moduleConfig.telemetry.device_update_interval = ONE_DAY; + } else if (role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) { + config.device.node_info_broadcast_secs = ONE_DAY; + config.position.position_broadcast_smart_enabled = true; + config.position.position_broadcast_secs = 3 * 60; // Every 3 minutes + config.position.broadcast_smart_minimum_distance = 20; + config.position.broadcast_smart_minimum_interval_secs = 15; + // Remove Altitude MSL from flags since CoTs use HAE (height above ellipsoid) + config.position.position_flags = + (meshtastic_Config_PositionConfig_PositionFlags_ALTITUDE | meshtastic_Config_PositionConfig_PositionFlags_SPEED | + meshtastic_Config_PositionConfig_PositionFlags_HEADING | meshtastic_Config_PositionConfig_PositionFlags_DOP); + moduleConfig.telemetry.device_update_interval = ONE_DAY; + } else if (role == meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) { + config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_LOCAL_ONLY; + config.device.node_info_broadcast_secs = UINT32_MAX; + config.position.position_broadcast_smart_enabled = false; + config.position.position_broadcast_secs = UINT32_MAX; + moduleConfig.neighbor_info.update_interval = UINT32_MAX; + moduleConfig.telemetry.device_update_interval = UINT32_MAX; + moduleConfig.telemetry.environment_update_interval = UINT32_MAX; + moduleConfig.telemetry.air_quality_interval = UINT32_MAX; } } @@ -316,8 +444,9 @@ void NodeDB::installDefaultChannels() void NodeDB::resetNodes() { - devicestate.node_db_lite_count = 1; - std::fill(&devicestate.node_db_lite[1], &devicestate.node_db_lite[MAX_NUM_NODES - 1], meshtastic_NodeInfoLite()); + numMeshNodes = 1; + std::fill(devicestate.node_db_lite.begin() + 1, devicestate.node_db_lite.end(), meshtastic_NodeInfoLite()); + clearLocalPosition(); saveDeviceStateToDisk(); if (neighborInfoModule && moduleConfig.neighbor_info.enabled) neighborInfoModule->resetNeighbors(); @@ -326,41 +455,56 @@ void NodeDB::resetNodes() void NodeDB::removeNodeByNum(uint nodeNum) { int newPos = 0, removed = 0; - for (int i = 0; i < *numMeshNodes; i++) { - if (meshNodes[i].num != nodeNum) - meshNodes[newPos++] = meshNodes[i]; + for (int i = 0; i < numMeshNodes; i++) { + if (meshNodes->at(i).num != nodeNum) + meshNodes->at(newPos++) = meshNodes->at(i); else removed++; } - *numMeshNodes -= removed; + numMeshNodes -= removed; + std::fill(devicestate.node_db_lite.begin() + numMeshNodes, devicestate.node_db_lite.begin() + numMeshNodes + 1, + meshtastic_NodeInfoLite()); LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Saving changes...\n", removed); saveDeviceStateToDisk(); } +void NodeDB::clearLocalPosition() +{ + meshtastic_NodeInfoLite *node = getMeshNode(nodeDB->getNodeNum()); + node->position.latitude_i = 0; + node->position.longitude_i = 0; + node->position.altitude = 0; + node->position.time = 0; + setLocalPosition(meshtastic_Position_init_default); +} + void NodeDB::cleanupMeshDB() { int newPos = 0, removed = 0; - for (int i = 0; i < *numMeshNodes; i++) { - if (meshNodes[i].has_user) - meshNodes[newPos++] = meshNodes[i]; + for (int i = 0; i < numMeshNodes; i++) { + if (meshNodes->at(i).has_user) + meshNodes->at(newPos++) = meshNodes->at(i); else removed++; } - *numMeshNodes -= removed; + numMeshNodes -= removed; + std::fill(devicestate.node_db_lite.begin() + numMeshNodes, devicestate.node_db_lite.begin() + numMeshNodes + removed, + meshtastic_NodeInfoLite()); LOG_DEBUG("cleanupMeshDB purged %d entries\n", removed); } void NodeDB::installDefaultDeviceState() { LOG_INFO("Installing default DeviceState\n"); - memset(&devicestate, 0, sizeof(meshtastic_DeviceState)); + // memset(&devicestate, 0, sizeof(meshtastic_DeviceState)); - *numMeshNodes = 0; + numMeshNodes = 0; + meshNodes = &devicestate.node_db_lite; // init our devicestate with valid flags so protobuf writing/reading will work devicestate.has_my_node = true; devicestate.has_owner = true; - devicestate.node_db_lite_count = 0; + // devicestate.node_db_lite_count = 0; devicestate.version = DEVICESTATE_CUR_VER; devicestate.receive_queue_count = 0; // Not yet implemented FIXME @@ -370,63 +514,10 @@ void NodeDB::installDefaultDeviceState() pickNewNodeNum(); // based on macaddr now snprintf(owner.long_name, sizeof(owner.long_name), "Meshtastic %02x%02x", ourMacAddr[4], ourMacAddr[5]); snprintf(owner.short_name, sizeof(owner.short_name), "%02x%02x", ourMacAddr[4], ourMacAddr[5]); - snprintf(owner.id, sizeof(owner.id), "!%08x", getNodeNum()); // Default node ID now based on nodenum memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr)); } -void NodeDB::init() -{ - LOG_INFO("Initializing NodeDB\n"); - loadFromDisk(); - cleanupMeshDB(); - - uint32_t devicestateCRC = crc32Buffer(&devicestate, sizeof(devicestate)); - uint32_t configCRC = crc32Buffer(&config, sizeof(config)); - uint32_t channelFileCRC = crc32Buffer(&channelFile, sizeof(channelFile)); - - int saveWhat = 0; - - // likewise - we always want the app requirements to come from the running appload - myNodeInfo.min_app_version = 30200; // format is Mmmss (where M is 1+the numeric major number. i.e. 30200 means 2.2.00 - // Note! We do this after loading saved settings, so that if somehow an invalid nodenum was stored in preferences we won't - // keep using that nodenum forever. Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts) - pickNewNodeNum(); - - // Set our board type so we can share it with others - owner.hw_model = HW_VENDOR; - - // Include our owner in the node db under our nodenum - meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum()); - info->user = owner; - info->has_user = true; - -#ifdef ARCH_ESP32 - Preferences preferences; - preferences.begin("meshtastic", false); - myNodeInfo.reboot_count = preferences.getUInt("rebootCounter", 0); - preferences.end(); - LOG_DEBUG("Number of Device Reboots: %d\n", myNodeInfo.reboot_count); -#endif - - resetRadioConfig(); // If bogus settings got saved, then fix them - LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d\n", config.lora.region, myNodeInfo.my_node_num, *numMeshNodes); - - if (devicestateCRC != crc32Buffer(&devicestate, sizeof(devicestate))) - saveWhat |= SEGMENT_DEVICESTATE; - if (configCRC != crc32Buffer(&config, sizeof(config))) - saveWhat |= SEGMENT_CONFIG; - if (channelFileCRC != crc32Buffer(&channelFile, sizeof(channelFile))) - saveWhat |= SEGMENT_CHANNELS; - - if (!devicestate.node_remote_hardware_pins) { - meshtastic_NodeRemoteHardwarePin empty[12] = {meshtastic_RemoteHardwarePin_init_default}; - memcpy(devicestate.node_remote_hardware_pins, empty, sizeof(empty)); - } - - saveToDisk(saveWhat); -} - // We reserve a few nodenums for future use #define NUM_RESERVED 4 @@ -435,14 +526,12 @@ void NodeDB::init() */ void NodeDB::pickNewNodeNum() { -#ifdef ARCH_RASPBERRY_PI - getPiMacAddr(ourMacAddr); // Make sure ourMacAddr is set -#else + NodeNum nodeNum = myNodeInfo.my_node_num; getMacAddr(ourMacAddr); // Make sure ourMacAddr is set -#endif - - // Pick an initial nodenum based on the macaddr - NodeNum nodeNum = (ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5]; + if (nodeNum == 0) { + // Pick an initial nodenum based on the macaddr + nodeNum = (ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5]; + } meshtastic_NodeInfoLite *found; while ((nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED) || @@ -451,7 +540,7 @@ void NodeDB::pickNewNodeNum() LOG_WARN("NOTE! Our desired nodenum 0x%x is invalid or in use, so trying for 0x%x\n", nodeNum, candidate); nodeNum = candidate; } - LOG_WARN("Using nodenum 0x%x \n", nodeNum); + LOG_DEBUG("Using nodenum 0x%x \n", nodeNum); myNodeInfo.my_node_num = nodeNum; } @@ -462,12 +551,17 @@ static const char *moduleConfigFileName = "/prefs/module.proto"; static const char *channelFileName = "/prefs/channels.proto"; static const char *oemConfigFile = "/oem/oem.proto"; -/** Load a protobuf from a file, return true for success */ -bool NodeDB::loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, void *dest_struct) +/** Load a protobuf from a file, return LoadFileResult */ +LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, + void *dest_struct) { - bool okay = false; + LoadFileResult state = LoadFileResult::OTHER_FAILURE; #ifdef FSCom - // static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM + + if (!FSCom.exists(filename)) { + LOG_INFO("File %s not found\n", filename); + return LoadFileResult::NOT_FOUND; + } auto f = FSCom.open(filename, FILE_O_READ); @@ -475,42 +569,49 @@ bool NodeDB::loadProto(const char *filename, size_t protoSize, size_t objSize, c LOG_INFO("Loading %s\n", filename); pb_istream_t stream = {&readcb, &f, protoSize}; - // LOG_DEBUG("Preload channel name=%s\n", channelSettings.name); - memset(dest_struct, 0, objSize); if (!pb_decode(&stream, fields, dest_struct)) { LOG_ERROR("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream)); + state = LoadFileResult::DECODE_FAILED; } else { - okay = true; + LOG_INFO("Loaded %s successfully\n", filename); + state = LoadFileResult::SUCCESS; } - f.close(); } else { - LOG_INFO("No %s preferences found\n", filename); + LOG_ERROR("Could not open / read %s\n", filename); } #else LOG_ERROR("ERROR: Filesystem not implemented\n"); + state = LoadFileState::NO_FILESYSTEM; #endif - return okay; + return state; } void NodeDB::loadFromDisk() { // static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM - if (!loadProto(prefFileName, meshtastic_DeviceState_size, sizeof(meshtastic_DeviceState), &meshtastic_DeviceState_msg, - &devicestate)) { + auto state = loadProto(prefFileName, sizeof(meshtastic_DeviceState) + MAX_NUM_NODES * sizeof(meshtastic_NodeInfo), + sizeof(meshtastic_DeviceState), &meshtastic_DeviceState_msg, &devicestate); + + if (state != LoadFileResult::SUCCESS) { installDefaultDeviceState(); // Our in RAM copy might now be corrupt } else { if (devicestate.version < DEVICESTATE_MIN_VER) { LOG_WARN("Devicestate %d is old, discarding\n", devicestate.version); factoryReset(); } else { - LOG_INFO("Loaded saved devicestate version %d\n", devicestate.version); + LOG_INFO("Loaded saved devicestate version %d, with nodecount: %d\n", devicestate.version, + devicestate.node_db_lite.size()); + meshNodes = &devicestate.node_db_lite; + numMeshNodes = devicestate.node_db_lite.size(); } } + meshNodes->resize(MAX_NUM_NODES); - if (!loadProto(configFileName, meshtastic_LocalConfig_size, sizeof(meshtastic_LocalConfig), &meshtastic_LocalConfig_msg, - &config)) { + state = loadProto(configFileName, meshtastic_LocalConfig_size, sizeof(meshtastic_LocalConfig), &meshtastic_LocalConfig_msg, + &config); + if (state != LoadFileResult::SUCCESS) { installDefaultConfig(); // Our in RAM copy might now be corrupt } else { if (config.version < DEVICESTATE_MIN_VER) { @@ -521,8 +622,9 @@ void NodeDB::loadFromDisk() } } - if (!loadProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, sizeof(meshtastic_LocalModuleConfig), - &meshtastic_LocalModuleConfig_msg, &moduleConfig)) { + state = loadProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, sizeof(meshtastic_LocalModuleConfig), + &meshtastic_LocalModuleConfig_msg, &moduleConfig); + if (state != LoadFileResult::SUCCESS) { installDefaultModuleConfig(); // Our in RAM copy might now be corrupt } else { if (moduleConfig.version < DEVICESTATE_MIN_VER) { @@ -533,8 +635,9 @@ void NodeDB::loadFromDisk() } } - if (!loadProto(channelFileName, meshtastic_ChannelFile_size, sizeof(meshtastic_ChannelFile), &meshtastic_ChannelFile_msg, - &channelFile)) { + state = loadProto(channelFileName, meshtastic_ChannelFile_size, sizeof(meshtastic_ChannelFile), &meshtastic_ChannelFile_msg, + &channelFile); + if (state != LoadFileResult::SUCCESS) { installDefaultChannels(); // Our in RAM copy might now be corrupt } else { if (channelFile.version < DEVICESTATE_MIN_VER) { @@ -545,7 +648,8 @@ void NodeDB::loadFromDisk() } } - if (loadProto(oemConfigFile, meshtastic_OEMStore_size, sizeof(meshtastic_OEMStore), &meshtastic_OEMStore_msg, &oemStore)) { + state = loadProto(oemConfigFile, meshtastic_OEMStore_size, sizeof(meshtastic_OEMStore), &meshtastic_OEMStore_msg, &oemStore); + if (state == LoadFileResult::SUCCESS) { LOG_INFO("Loaded OEMStore\n"); } } @@ -584,9 +688,13 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_ static uint8_t failedCounter = 0; failedCounter++; if (failedCounter >= 2) { - FSCom.format(); - // After formatting, the device needs to be restarted - nodeDB.resetRadioConfig(true); + LOG_ERROR("Failed to save file twice. Rebooting...\n"); + delay(100); + NVIC_SystemReset(); + // We used to blow away the filesystem here, but that's a bit extreme + // FSCom.format(); + // // After formatting, the device needs to be restarted + // nodeDB->resetRadioConfig(true); } #endif } @@ -598,68 +706,68 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_ void NodeDB::saveChannelsToDisk() { - if (!devicestate.no_save) { #ifdef FSCom - FSCom.mkdir("/prefs"); + FSCom.mkdir("/prefs"); #endif - saveProto(channelFileName, meshtastic_ChannelFile_size, &meshtastic_ChannelFile_msg, &channelFile); - } + saveProto(channelFileName, meshtastic_ChannelFile_size, &meshtastic_ChannelFile_msg, &channelFile); } void NodeDB::saveDeviceStateToDisk() { - if (!devicestate.no_save) { #ifdef FSCom - FSCom.mkdir("/prefs"); + FSCom.mkdir("/prefs"); #endif - saveProto(prefFileName, meshtastic_DeviceState_size, &meshtastic_DeviceState_msg, &devicestate); - } + saveProto(prefFileName, sizeof(devicestate) + numMeshNodes * meshtastic_NodeInfoLite_size, &meshtastic_DeviceState_msg, + &devicestate); } void NodeDB::saveToDisk(int saveWhat) { - if (!devicestate.no_save) { #ifdef FSCom - FSCom.mkdir("/prefs"); + FSCom.mkdir("/prefs"); #endif - if (saveWhat & SEGMENT_DEVICESTATE) { - saveDeviceStateToDisk(); - } + if (saveWhat & SEGMENT_DEVICESTATE) { + saveDeviceStateToDisk(); + } - if (saveWhat & SEGMENT_CONFIG) { - config.has_device = true; - config.has_display = true; - config.has_lora = true; - config.has_position = true; - config.has_power = true; - config.has_network = true; - config.has_bluetooth = true; - saveProto(configFileName, meshtastic_LocalConfig_size, &meshtastic_LocalConfig_msg, &config); - } + if (saveWhat & SEGMENT_CONFIG) { + config.has_device = true; + config.has_display = true; + config.has_lora = true; + config.has_position = true; + config.has_power = true; + config.has_network = true; + config.has_bluetooth = true; - if (saveWhat & SEGMENT_MODULECONFIG) { - moduleConfig.has_canned_message = true; - moduleConfig.has_external_notification = true; - moduleConfig.has_mqtt = true; - moduleConfig.has_range_test = true; - moduleConfig.has_serial = true; - moduleConfig.has_store_forward = true; - moduleConfig.has_telemetry = true; - saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig); - } + saveProto(configFileName, meshtastic_LocalConfig_size, &meshtastic_LocalConfig_msg, &config); + } - if (saveWhat & SEGMENT_CHANNELS) { - saveChannelsToDisk(); - } - } else { - LOG_DEBUG("***** DEVELOPMENT MODE - DO NOT RELEASE - not saving to flash *****\n"); + if (saveWhat & SEGMENT_MODULECONFIG) { + moduleConfig.has_canned_message = true; + moduleConfig.has_external_notification = true; + moduleConfig.has_mqtt = true; + moduleConfig.has_range_test = true; + moduleConfig.has_serial = true; + moduleConfig.has_store_forward = true; + moduleConfig.has_telemetry = true; + moduleConfig.has_neighbor_info = true; + moduleConfig.has_detection_sensor = true; + moduleConfig.has_ambient_lighting = true; + moduleConfig.has_audio = true; + moduleConfig.has_paxcounter = true; + + saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig); + } + + if (saveWhat & SEGMENT_CHANNELS) { + saveChannelsToDisk(); } } const meshtastic_NodeInfoLite *NodeDB::readNextMeshNode(uint32_t &readIndex) { - if (readIndex < *numMeshNodes) - return &meshNodes[readIndex++]; + if (readIndex < numMeshNodes) + return &meshNodes->at(readIndex++); else return NULL; } @@ -689,19 +797,23 @@ uint32_t sinceReceived(const meshtastic_MeshPacket *p) #define NUM_ONLINE_SECS (60 * 60 * 2) // 2 hrs to consider someone offline -size_t NodeDB::getNumOnlineMeshNodes() +size_t NodeDB::getNumOnlineMeshNodes(bool localOnly) { size_t numseen = 0; // FIXME this implementation is kinda expensive - for (int i = 0; i < *numMeshNodes; i++) - if (sinceLastSeen(&meshNodes[i]) < NUM_ONLINE_SECS) + for (int i = 0; i < numMeshNodes; i++) { + if (localOnly && meshNodes->at(i).via_mqtt) + continue; + if (sinceLastSeen(&meshNodes->at(i)) < NUM_ONLINE_SECS) numseen++; + } return numseen; } #include "MeshModule.h" +#include "Throttle.h" /** Update position info for this node based on received position data */ @@ -770,22 +882,25 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS notifyObservers(true); // Force an update whether or not our node counts have changed } -/** Update user info for this node based on received user data +/** Update user info and channel for this node based on received user data */ -bool NodeDB::updateUser(uint32_t nodeId, const meshtastic_User &p) +bool NodeDB::updateUser(uint32_t nodeId, const meshtastic_User &p, uint8_t channelIndex) { meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId); if (!info) { return false; } - LOG_DEBUG("old user %s/%s/%s\n", info->user.id, info->user.long_name, info->user.short_name); + LOG_DEBUG("old user %s/%s/%s, channel=%d\n", info->user.id, info->user.long_name, info->user.short_name, info->channel); - bool changed = memcmp(&info->user, &p, - sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay + // Both of info->user and p start as filled with zero so I think this is okay + bool changed = memcmp(&info->user, &p, sizeof(info->user)) || (info->channel != channelIndex); info->user = p; - LOG_DEBUG("updating changed=%d user %s/%s/%s\n", changed, info->user.id, info->user.long_name, info->user.short_name); + if (nodeId != getNodeNum()) + info->channel = channelIndex; // Set channel we need to use to reach this node (but don't set our own channel) + LOG_DEBUG("updating changed=%d user %s/%s/%s, channel=%d\n", changed, info->user.id, info->user.long_name, + info->user.short_name, info->channel); info->has_user = true; if (changed) { @@ -793,8 +908,10 @@ bool NodeDB::updateUser(uint32_t nodeId, const meshtastic_User &p) powerFSM.trigger(EVENT_NODEDB_UPDATED); notifyObservers(true); // Force an update whether or not our node counts have changed - // We just changed something important about the user, store our DB - saveToDisk(SEGMENT_DEVICESTATE); + // We just changed something about the user, store our DB + Throttle::execute( + &lastNodeDbSave, ONE_MINUTE_MS, []() { nodeDB->saveToDisk(SEGMENT_DEVICESTATE); }, + []() { LOG_DEBUG("Deferring NodeDB saveToDisk for now, since we saved less than a minute ago\n"); }); } return changed; @@ -805,7 +922,7 @@ bool NodeDB::updateUser(uint32_t nodeId, const meshtastic_User &p) void NodeDB::updateFrom(const meshtastic_MeshPacket &mp) { if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) { - LOG_DEBUG("Update DB node 0x%x, rx_time=%u, channel=%d\n", mp.from, mp.rx_time, mp.channel); + LOG_DEBUG("Update DB node 0x%x, rx_time=%u\n", mp.from, mp.rx_time); meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getFrom(&mp)); if (!info) { @@ -818,16 +935,16 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp) if (mp.rx_snr) info->snr = mp.rx_snr; // keep the most recent SNR we received for this node. - if (mp.decoded.portnum == meshtastic_PortNum_NODEINFO_APP) { - info->channel = mp.channel; - } + info->via_mqtt = mp.via_mqtt; // Store if we received this packet via MQTT - // If this packet didn't travel any hops, then it was sent directly to us, so we know what to use as next hop to this node - if (mp.original_hop_limit == mp.hop_limit) { - info->next_hop = getLastByteOfNodeNum(mp.from); - } else if (mp.relay_node && (mp.original_hop_limit - mp.hop_limit == 1)) { - // This packet traveled one hop, so we can use the relay_node as next_hop - info->next_hop = getLastByteOfNodeNum(mp.relay_node); + // If hopStart was set and there wasn't someone messing with the limit in the middle, add hopsAway + if (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start) { + info->hops_away = mp.hop_start - mp.hop_limit; + if (info->hops_away == 0) { + info->next_hop = getLastByteOfNodeNum(mp.from); + } else if (info->hops_away == 1) { + info->next_hop = getLastByteOfNodeNum(mp.relay_node); + } } } } @@ -845,9 +962,9 @@ uint8_t NodeDB::getMeshNodeChannel(NodeNum n) /// NOTE: This function might be called from an ISR meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n) { - for (int i = 0; i < *numMeshNodes; i++) - if (meshNodes[i].num == n) - return &meshNodes[i]; + for (int i = 0; i < numMeshNodes; i++) + if (meshNodes->at(i).num == n) + return &meshNodes->at(i); return NULL; } @@ -858,26 +975,27 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n) meshtastic_NodeInfoLite *lite = getMeshNode(n); if (!lite) { - if ((*numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < meshtastic_NodeInfoLite_size * 3)) { + if ((numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < meshtastic_NodeInfoLite_size * 3)) { if (screen) - screen->print("warning: node_db_lite full! erasing oldest entry\n"); + screen->print("Warn: node database full!\nErasing oldest entry\n"); + LOG_WARN("Node database full! Erasing oldest entry\n"); // look for oldest node and erase it uint32_t oldest = UINT32_MAX; int oldestIndex = -1; - for (int i = 0; i < *numMeshNodes; i++) { - if (meshNodes[i].last_heard < oldest) { - oldest = meshNodes[i].last_heard; + for (int i = 1; i < numMeshNodes; i++) { + if (!meshNodes->at(i).is_favorite && meshNodes->at(i).last_heard < oldest) { + oldest = meshNodes->at(i).last_heard; oldestIndex = i; } } // Shove the remaining nodes down the chain - for (int i = oldestIndex; i < *numMeshNodes - 1; i++) { - meshNodes[i] = meshNodes[i + 1]; + for (int i = oldestIndex; i < numMeshNodes - 1; i++) { + meshNodes->at(i) = meshNodes->at(i + 1); } - (*numMeshNodes)--; + (numMeshNodes)--; } // add the node at the end - lite = &meshNodes[(*numMeshNodes)++]; + lite = &meshNodes->at((numMeshNodes)++); // everything is missing except the nodenum memset(lite, 0, sizeof(*lite)); diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 795104280..169d91b67 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -3,6 +3,7 @@ #include "Observer.h" #include #include +#include #include "MeshTypes.h" #include "NodeStatus.h" @@ -37,6 +38,19 @@ uint32_t sinceLastSeen(const meshtastic_NodeInfoLite *n); /// Given a packet, return how many seconds in the past (vs now) it was received uint32_t sinceReceived(const meshtastic_MeshPacket *p); +enum LoadFileResult { + // Successfully opened the file + SUCCESS = 1, + // File does not exist + NOT_FOUND = 2, + // Device does not have a filesystem + NO_FILESYSTEM = 3, + // File exists, but could not decode protobufs + DECODE_FAILED = 4, + // File exists, but open failed for some reason + OTHER_FAILURE = 5 +}; + class NodeDB { // NodeNum provisionalNodeNum; // if we are trying to find a node num this is our current attempt @@ -45,21 +59,18 @@ class NodeDB // Eventually use a smarter datastructure // HashMap nodes; // Note: these two references just point into our static array we serialize to/from disk - meshtastic_NodeInfoLite *meshNodes; - pb_size_t *numMeshNodes; public: + std::vector *meshNodes; bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled meshtastic_NodeInfoLite *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI Observable newStatus; + pb_size_t numMeshNodes; /// don't do mesh based algorithm for node id assignment (initially) /// instead just store in flash - possibly even in the initial alpha release do this hack NodeDB(); - /// Called from service after app start, to do init which can only be done after OS load - void init(); - /// write to flash void saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS), saveChannelsToDisk(), saveDeviceStateToDisk(); @@ -84,9 +95,9 @@ class NodeDB */ void updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxSource src = RX_SRC_RADIO); - /** Update user info for this node based on received user data + /** Update user info and channel for this node based on received user data */ - bool updateUser(uint32_t nodeId, const meshtastic_User &p); + bool updateUser(uint32_t nodeId, const meshtastic_User &p, uint8_t channelIndex = 0); /// @return our node number NodeNum getNodeNum() { return myNodeInfo.my_node_num; } @@ -111,14 +122,17 @@ class NodeDB // get channel channel index we heard a nodeNum on, defaults to 0 if not found uint8_t getMeshNodeChannel(NodeNum n); - /// Return the number of nodes we've heard from recently (within the last 2 hrs?) - size_t getNumOnlineMeshNodes(); + /* Return the number of nodes we've heard from recently (within the last 2 hrs?) + * @param localOnly if true, ignore nodes heard via MQTT + */ + size_t getNumOnlineMeshNodes(bool localOnly = false); void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(uint nodeNum); bool factoryReset(); - bool loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, void *dest_struct); + LoadFileResult loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, + void *dest_struct); bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct); void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role); @@ -127,21 +141,29 @@ class NodeDB meshtastic_NodeInfoLite *getMeshNodeByIndex(size_t x) { - assert(x < *numMeshNodes); - return &meshNodes[x]; + assert(x < numMeshNodes); + return &meshNodes->at(x); } meshtastic_NodeInfoLite *getMeshNode(NodeNum n); - size_t getNumMeshNodes() { return *numMeshNodes; } + size_t getNumMeshNodes() { return numMeshNodes; } - void setLocalPosition(meshtastic_Position position) + void clearLocalPosition(); + + void setLocalPosition(meshtastic_Position position, bool timeOnly = false) { + if (timeOnly) { + LOG_DEBUG("Setting local position time only: time=%i\n", position.time); + localPosition.time = position.time; + return; + } LOG_DEBUG("Setting local position: latitude=%i, longitude=%i, time=%i\n", position.latitude_i, position.longitude_i, position.time); localPosition = position; } private: + uint32_t lastNodeDbSave = 0; // when we last saved our db to flash /// Find a node in our DB, create an empty NodeInfoLite if missing meshtastic_NodeInfoLite *getOrCreateMeshNode(NodeNum n); @@ -163,7 +185,7 @@ class NodeDB void installDefaultDeviceState(), installDefaultChannels(), installDefaultConfig(), installDefaultModuleConfig(); }; -extern NodeDB nodeDB; +extern NodeDB *nodeDB; /* If is_router is set, we use a number of different default values @@ -182,55 +204,11 @@ extern NodeDB nodeDB; prefs.gps_update_interval = oneday prefs.is_power_saving = True - - # allow up to five minutes for each new GPS lock attempt - prefs.gps_attempt_time = 300 */ // Our delay functions check for this for times that should never expire #define NODE_DELAY_FOREVER 0xffffffff -#define IF_ROUTER(routerVal, normalVal) \ - ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) ? (routerVal) : (normalVal)) - -#define ONE_DAY 24 * 60 * 60 - -#define default_gps_attempt_time IF_ROUTER(5 * 60, 15 * 60) -#define default_gps_update_interval IF_ROUTER(ONE_DAY, 2 * 60) -#define default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 15 * 60) -#define default_wait_bluetooth_secs IF_ROUTER(1, 60) -#define default_sds_secs IF_ROUTER(ONE_DAY, UINT32_MAX) // Default to forever super deep sleep -#define default_ls_secs IF_ROUTER(ONE_DAY, 5 * 60) -#define default_min_wake_secs 10 -#define default_screen_on_secs IF_ROUTER(1, 60 * 10) - -#define default_mqtt_address "mqtt.meshtastic.org" -#define default_mqtt_username "meshdev" -#define default_mqtt_password "large4cats" -#define default_mqtt_root "msh" - -inline uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval) -{ - if (configuredInterval > 0) - return configuredInterval * 1000; - return default_broadcast_interval_secs * 1000; -} - -inline uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval) -{ - if (configuredInterval > 0) - return configuredInterval * 1000; - return defaultInterval * 1000; -} - -inline uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue) -{ - if (configured > 0) - return configured; - - return defaultValue; -} - /// Sometimes we will have Position objects that only have a time, so check for /// valid lat/lon static inline bool hasValidPosition(const meshtastic_NodeInfoLite *n) @@ -253,4 +231,6 @@ extern uint32_t error_address; #define Module_Config_size \ (ModuleConfig_CannedMessageConfig_size + ModuleConfig_ExternalNotificationConfig_size + ModuleConfig_MQTTConfig_size + \ ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \ - ModuleConfig_TelemetryConfig_size + ModuleConfig_size) \ No newline at end of file + ModuleConfig_TelemetryConfig_size + ModuleConfig_size) + +// Please do not remove this comment, it makes trunk and compiler happy at the same time. \ No newline at end of file diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp index 9ecad47cd..26a73a3fe 100644 --- a/src/mesh/PacketHistory.cpp +++ b/src/mesh/PacketHistory.cpp @@ -2,6 +2,10 @@ #include "configuration.h" #include "mesh-pb-constants.h" +#ifdef ARCH_PORTDUINO +#include "platform/portduino/PortduinoGlue.h" +#endif + PacketHistory::PacketHistory() { recentPackets.reserve(MAX_NUM_NODES); // Prealloc the worst case # of records - to prevent heap fragmentation diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index a01647bfa..2a69d6d56 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -1,12 +1,16 @@ -#include "PhoneAPI.h" -#include "Channels.h" +#include "configuration.h" +#if !MESHTASTIC_EXCLUDE_GPS #include "GPS.h" +#endif + +#include "Channels.h" +#include "Default.h" #include "MeshService.h" #include "NodeDB.h" +#include "PhoneAPI.h" #include "PowerFSM.h" #include "RadioInterface.h" #include "TypeConversions.h" -#include "configuration.h" #include "main.h" #include "xmodem.h" @@ -17,8 +21,9 @@ #if ToRadio_size > MAX_TO_FROM_RADIO_SIZE #error ToRadio is too big #endif - +#if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" +#endif PhoneAPI::PhoneAPI() { @@ -62,15 +67,17 @@ void PhoneAPI::close() } } -void PhoneAPI::checkConnectionTimeout() +bool PhoneAPI::checkConnectionTimeout() { if (isConnected()) { bool newContact = checkIsConnected(); if (!newContact) { LOG_INFO("Lost phone connection\n"); close(); + return true; } } + return false; } /** @@ -101,12 +108,21 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) LOG_INFO("Got xmodem packet\n"); xModem.handlePacket(toRadioScratch.xmodemPacket); break; +#if !MESHTASTIC_EXCLUDE_MQTT case meshtastic_ToRadio_mqttClientProxyMessage_tag: LOG_INFO("Got MqttClientProxy message\n"); - if (mqtt && moduleConfig.mqtt.proxy_to_client_enabled) { + if (mqtt && moduleConfig.mqtt.proxy_to_client_enabled && moduleConfig.mqtt.enabled && + (channels.anyMqttEnabled() || moduleConfig.mqtt.map_reporting_enabled)) { mqtt->onClientProxyReceive(toRadioScratch.mqttClientProxyMessage); + } else { + LOG_WARN("MqttClientProxy received but proxy is not enabled, no channels have up/downlink, or map reporting " + "not enabled\n"); } break; +#endif + case meshtastic_ToRadio_heartbeat_tag: + LOG_DEBUG("Got client heartbeat\n"); + break; default: // Ignore nop messages // LOG_DEBUG("Error: unexpected ToRadio variant\n"); @@ -298,6 +314,10 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_ambient_lighting_tag; fromRadioScratch.moduleConfig.payload_variant.ambient_lighting = moduleConfig.ambient_lighting; break; + case meshtastic_ModuleConfig_paxcounter_tag: + fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag; + fromRadioScratch.moduleConfig.payload_variant.paxcounter = moduleConfig.paxcounter; + break; default: LOG_ERROR("Unknown module config type %d\n", config_state); } @@ -407,9 +427,12 @@ bool PhoneAPI::available() case STATE_SEND_NODEINFO: if (nodeInfoForPhone.num == 0) { - auto nextNode = nodeDB.readNextMeshNode(readIndex); + auto nextNode = nodeDB->readNextMeshNode(readIndex); if (nextNode) { nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(nextNode); + nodeInfoForPhone.hops_away = nodeInfoForPhone.num == nodeDB->getNodeNum() ? 0 : nodeInfoForPhone.hops_away; + nodeInfoForPhone.is_favorite = + nodeInfoForPhone.is_favorite || nodeInfoForPhone.num == nodeDB->getNodeNum(); // Our node is always a favorite } } return true; // Always say we have something, because we might need to advance our state machine @@ -457,8 +480,8 @@ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p) /// If the mesh service tells us fromNum has changed, tell the phone int PhoneAPI::onNotify(uint32_t newValue) { - checkConnectionTimeout(); // a handy place to check if we've heard from the phone (since the BLE version doesn't call this - // from idle) + bool timeout = checkConnectionTimeout(); // a handy place to check if we've heard from the phone (since the BLE version + // doesn't call this from idle) if (state == STATE_SEND_PACKETS) { LOG_INFO("Telling client we have new packets %u\n", newValue); @@ -467,5 +490,5 @@ int PhoneAPI::onNotify(uint32_t newValue) LOG_DEBUG("(Client not yet interested in packets)\n"); } - return 0; + return timeout ? -1 : 0; // If we timed out, MeshService should stop iterating through observers as we just removed one } \ No newline at end of file diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 65a06bc6b..450649d7b 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -108,8 +108,8 @@ class PhoneAPI /// Hookable to find out when connection changes virtual void onConnectionChanged(bool connected) {} - /// If we haven't heard from the other side in a while then say not connected - void checkConnectionTimeout(); + /// If we haven't heard from the other side in a while then say not connected. Returns true if timeout occurred + bool checkConnectionTimeout(); /// Check the current underlying physical link to see if the client is currently connected virtual bool checkIsConnected() = 0; @@ -142,4 +142,4 @@ class PhoneAPI /// If the mesh service tells us fromNum has changed, tell the phone virtual int onNotify(uint32_t newValue) override; -}; +}; \ No newline at end of file diff --git a/src/mesh/ProtobufModule.h b/src/mesh/ProtobufModule.h index 1771f4322..a2e89e98a 100644 --- a/src/mesh/ProtobufModule.h +++ b/src/mesh/ProtobufModule.h @@ -30,6 +30,10 @@ template class ProtobufModule : protected SinglePortModule */ virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, T *decoded) = 0; + /** Called to make changes to a particular incoming message + */ + virtual void alterReceivedProtobuf(meshtastic_MeshPacket &mp, T *decoded){}; + /** * Return a mesh packet which has been preinited with a particular protobuf data payload and port number. * You can then send this packet (after customizing any of the payload fields you might need) with @@ -52,7 +56,7 @@ template class ProtobufModule : protected SinglePortModule */ const char *getSenderShortName(const meshtastic_MeshPacket &mp) { - auto node = nodeDB.getMeshNode(getFrom(&mp)); + auto node = nodeDB->getMeshNode(getFrom(&mp)); const char *sender = (node) ? node->user.short_name : "???"; return sender; } @@ -86,4 +90,26 @@ template class ProtobufModule : protected SinglePortModule return handleReceivedProtobuf(mp, decoded) ? ProcessMessage::STOP : ProcessMessage::CONTINUE; } + + /** Called to alter a particular incoming message + */ + virtual void alterReceived(meshtastic_MeshPacket &mp) override + { + auto &p = mp.decoded; + + T scratch; + T *decoded = NULL; + if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.decoded.portnum == ourPortNum) { + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, fields, &scratch)) { + decoded = &scratch; + } else { + LOG_ERROR("Error decoding protobuf module!\n"); + // if we can't decode it, nobody can process it! + return; + } + + return alterReceivedProtobuf(mp, decoded); + } + } }; \ No newline at end of file diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index d7f319f8e..8c6c349fd 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -4,6 +4,10 @@ #include "configuration.h" #include "error.h" +#if ARCH_PORTDUINO +#include "PortduinoGlue.h" +#endif + #define MAX_POWER 20 // if we use 20 we are limited to 1% duty cycle or hw might overheat. For continuous operation set a limit of 17 // In theory up to 27 dBm is possible, but the modules installed in most radios can cope with a max of 20. So BIG WARNING @@ -15,7 +19,7 @@ RF95Interface::RF95Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIO RADIOLIB_PIN_TYPE busy) : RadioLibInterface(hal, cs, irq, rst, busy) { - LOG_WARN("RF95Interface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy); + LOG_DEBUG("RF95Interface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy); } /** Some boards require GPIO control of tx vs rx paths */ @@ -23,10 +27,18 @@ void RF95Interface::setTransmitEnable(bool txon) { #ifdef RF95_TXEN digitalWrite(RF95_TXEN, txon ? 1 : 0); +#elif ARCH_PORTDUINO + if (settingsMap[txen] != RADIOLIB_NC) { + digitalWrite(settingsMap[txen], txon ? 1 : 0); + } #endif #ifdef RF95_RXEN digitalWrite(RF95_RXEN, txon ? 0 : 1); +#elif ARCH_PORTDUINO + if (settingsMap[rxen] != RADIOLIB_NC) { + digitalWrite(settingsMap[rxen], txon ? 0 : 1); + } #endif } @@ -62,6 +74,16 @@ bool RF95Interface::init() #ifdef RF95_RXEN pinMode(RF95_RXEN, OUTPUT); digitalWrite(RF95_RXEN, 1); +#endif +#if ARCH_PORTDUINO + if (settingsMap[txen] != RADIOLIB_NC) { + pinMode(settingsMap[txen], OUTPUT); + digitalWrite(settingsMap[txen], 0); + } + if (settingsMap[rxen] != RADIOLIB_NC) { + pinMode(settingsMap[rxen], OUTPUT); + digitalWrite(settingsMap[rxen], 0); + } #endif setTransmitEnable(false); @@ -106,12 +128,18 @@ bool RF95Interface::reconfigure() RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); err = lora->setSyncWord(syncWord); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting RF95 setSyncWord!\n", err); assert(err == RADIOLIB_ERR_NONE); err = lora->setCurrentLimit(currentLimit); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting RF95 setCurrentLimit!\n", err); assert(err == RADIOLIB_ERR_NONE); err = lora->setPreambleLength(preambleLength); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting RF95 setPreambleLength!\n", err); assert(err == RADIOLIB_ERR_NONE); err = lora->setFrequency(getFreq()); @@ -142,6 +170,8 @@ void RF95Interface::addReceiveMetadata(meshtastic_MeshPacket *mp) void RF95Interface::setStandby() { int err = lora->standby(); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting RF95 standby!\n", err); assert(err == RADIOLIB_ERR_NONE); isReceiving = false; // If we were receiving, not any more @@ -163,6 +193,8 @@ void RF95Interface::startReceive() setTransmitEnable(false); setStandby(); int err = lora->startReceive(); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting RF95 startReceive!\n", err); assert(err == RADIOLIB_ERR_NONE); isReceiving = true; @@ -183,6 +215,8 @@ bool RF95Interface::isChannelActive() // LOG_DEBUG("Channel is busy!\n"); return true; } + if (result != RADIOLIB_CHANNEL_FREE) + LOG_ERROR("Radiolib error %d when attempting RF95 isChannelActive!\n", result); assert(result != RADIOLIB_ERR_WRONG_MODEM); // LOG_DEBUG("Channel is free!\n"); @@ -202,4 +236,4 @@ bool RF95Interface::sleep() lora->sleep(); return true; -} +} \ No newline at end of file diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index b2ce489ff..0f6cfde60 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -1,5 +1,6 @@ #include "RadioInterface.h" #include "Channels.h" +#include "DisplayFormatters.h" #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" @@ -107,10 +108,32 @@ const RegionInfo regions[] = { */ RDEF(UA_868, 868.0f, 868.6f, 1, 0, 14, true, false, false), + /* + Malaysia + 433 - 435 MHz at 100mW, no restrictions. + https://www.mcmc.gov.my/skmmgovmy/media/General/pdf/Short-Range-Devices-Specification.pdf + */ + RDEF(MY_433, 433.0f, 435.0f, 100, 0, 20, true, false, false), + + /* + Malaysia + 919 - 923 Mhz at 500mW, no restrictions. + 923 - 924 MHz at 500mW with 1% duty cycle OR frequency hopping. + Frequency hopping is used for 919 - 923 MHz. + https://www.mcmc.gov.my/skmmgovmy/media/General/pdf/Short-Range-Devices-Specification.pdf + */ + RDEF(MY_919, 919.0f, 924.0f, 100, 0, 27, true, true, false), + + /* + Singapore + SG_923 Band 30d: 917 - 925 MHz at 100mW, no restrictions. + https://www.imda.gov.sg/-/media/imda/files/regulation-licensing-and-consultations/ict-standards/telecommunication-standards/radio-comms/imdatssrd.pdf + */ + RDEF(SG_923, 917.0f, 925.0f, 100, 0, 20, true, false, false), + /* 2.4 GHZ WLAN Band equivalent. Only for SX128x chips. */ - RDEF(LORA_24, 2400.0f, 2483.5f, 100, 0, 10, true, false, true), /* @@ -121,16 +144,23 @@ const RegionInfo regions[] = { }; const RegionInfo *myRegion; +bool RadioInterface::uses_default_frequency_slot = true; static uint8_t bytes[MAX_RHPACKETLEN]; void initRegion() { const RegionInfo *r = regions; +#ifdef LORA_REGIONCODE + for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != LORA_REGIONCODE; r++) + ; + LOG_INFO("Wanted region %d, regulatory override to %s\n", config.lora.region, r->name); +#else for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != config.lora.region; r++) ; - myRegion = r; LOG_INFO("Wanted region %d, using %s\n", config.lora.region, r->name); +#endif + myRegion = r; } /** @@ -272,15 +302,16 @@ void printPacket(const char *prefix, const meshtastic_MeshPacket *p) out += " encrypted"; } - if (p->rx_time != 0) { + if (p->rx_time != 0) out += DEBUG_PORT.mt_sprintf(" rxtime=%u", p->rx_time); - } - if (p->rx_snr != 0.0) { + if (p->rx_snr != 0.0) out += DEBUG_PORT.mt_sprintf(" rxSNR=%g", p->rx_snr); - } - if (p->rx_rssi != 0) { + if (p->rx_rssi != 0) out += DEBUG_PORT.mt_sprintf(" rxRSSI=%i", p->rx_rssi); - } + if (p->via_mqtt != 0) + out += DEBUG_PORT.mt_sprintf(" via MQTT"); + if (p->hop_start != 0) + out += DEBUG_PORT.mt_sprintf(" hopStart=%d", p->hop_start); if (p->priority != 0) out += DEBUG_PORT.mt_sprintf(" priority=%d", p->priority); @@ -309,8 +340,8 @@ bool RadioInterface::init() notifyDeepSleepObserver.observe(¬ifyDeepSleep); // we now expect interfaces to operate in promiscuous mode - // radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor - // time. + // radioIf.setThisAddress(nodeDB->getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at + // constructor time. applyModemConfig(); @@ -328,9 +359,9 @@ int RadioInterface::notifyDeepSleepCb(void *unused) * djb2 by Dan Bernstein. * http://www.cse.yorku.ca/~oz/hash.html */ -unsigned long hash(const char *str) +uint32_t hash(const char *str) { - unsigned long hash = 5381; + uint32_t hash = 5381; int c; while ((c = *str++) != 0) @@ -463,6 +494,10 @@ void RadioInterface::applyModemConfig() // channel_num is actually (channel_num - 1), since modulus (%) returns values from 0 to (numChannels - 1) int channel_num = (loraConfig.channel_num ? loraConfig.channel_num - 1 : hash(channelName)) % numChannels; + // Check if we use the default frequency slot + RadioInterface::uses_default_frequency_slot = + channel_num == hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false)) % numChannels; + // Old frequency selection formula // float freq = myRegion->freqStart + ((((myRegion->freqEnd - myRegion->freqStart) / numChannels) / 2) * channel_num); @@ -539,8 +574,8 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p) LOG_WARN("hop limit %d is too high, setting to %d\n", p->hop_limit, HOP_RELIABLE); p->hop_limit = HOP_RELIABLE; } - h->flags = p->hop_limit | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0); - h->flags |= (p->original_hop_limit & PACKET_FLAGS_HOP_MASK) << PACKET_FLAGS_ORIG_HOP_SHIFT; + h->flags = p->hop_limit | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0) | (p->via_mqtt ? PACKET_FLAGS_VIA_MQTT_MASK : 0); + h->flags |= (p->hop_start << PACKET_FLAGS_HOP_START_SHIFT) & PACKET_FLAGS_HOP_START_MASK; h->next_hop = p->next_hop; h->relay_node = p->relay_node; @@ -551,4 +586,4 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p) sendingPacket = p; return p->encrypted.size + sizeof(PacketHeader); -} \ No newline at end of file +} diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 60d7be4ea..d5b6685b2 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -10,10 +10,11 @@ #define MAX_RHPACKETLEN 256 -#define PACKET_FLAGS_HOP_MASK 0x07 +#define PACKET_FLAGS_HOP_LIMIT_MASK 0x07 #define PACKET_FLAGS_WANT_ACK_MASK 0x08 -#define PACKET_FLAGS_ORIG_HOP_MASK 0x70 -#define PACKET_FLAGS_ORIG_HOP_SHIFT 4 +#define PACKET_FLAGS_VIA_MQTT_MASK 0x10 +#define PACKET_FLAGS_HOP_START_MASK 0xE0 +#define PACKET_FLAGS_HOP_START_SHIFT 5 /** * This structure has to exactly match the wire layout when sent over the radio link. Used to keep compatibility @@ -180,6 +181,9 @@ class RadioInterface /// Some boards (1st gen Pinetab Lora module) have broken IRQ wires, so we need to poll via i2c registers virtual bool isIRQPending() { return false; } + // Whether we use the default frequency slot given our LoRa config (region and modem preset) + static bool uses_default_frequency_slot; + protected: int8_t power = 17; // Set by applyModemConfig() diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 7bbac6ff8..8ecc65fd5 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -22,6 +22,12 @@ void LockingArduinoHal::spiEndTransaction() ArduinoHal::spiEndTransaction(); } +#if ARCH_PORTDUINO +void LockingArduinoHal::spiTransfer(uint8_t *out, size_t len, uint8_t *in) +{ + spi->transfer(out, in, len); +} +#endif RadioLibInterface::RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, PhysicalLayer *_iface) @@ -313,7 +319,7 @@ void RadioLibInterface::handleReceiveInterrupt() // when this is called, we should be in receive mode - if we are not, just jump out instead of bombing. Possible Race // Condition? if (!isReceiving) { - LOG_DEBUG("*** WAS_ASSERT *** handleReceiveInterrupt called when not in receive mode\n"); + LOG_ERROR("handleReceiveInterrupt called when not in receive mode, which shouldn't happen.\n"); return; } @@ -359,10 +365,11 @@ void RadioLibInterface::handleReceiveInterrupt() mp->to = h->to; mp->id = h->id; mp->channel = h->channel; - assert(HOP_MAX <= PACKET_FLAGS_HOP_MASK); // If hopmax changes, carefully check this code - mp->hop_limit = h->flags & PACKET_FLAGS_HOP_MASK; - mp->original_hop_limit = (h->flags & PACKET_FLAGS_ORIG_HOP_MASK) >> PACKET_FLAGS_ORIG_HOP_SHIFT; + assert(HOP_MAX <= PACKET_FLAGS_HOP_LIMIT_MASK); // If hopmax changes, carefully check this code + mp->hop_limit = h->flags & PACKET_FLAGS_HOP_LIMIT_MASK; + mp->hop_start = (h->flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT; mp->want_ack = !!(h->flags & PACKET_FLAGS_WANT_ACK_MASK); + mp->via_mqtt = !!(h->flags & PACKET_FLAGS_VIA_MQTT_MASK); mp->next_hop = h->next_hop; mp->relay_node = h->relay_node; diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 4634ca7ee..62720cfc9 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -25,6 +25,9 @@ class LockingArduinoHal : public ArduinoHal void spiBeginTransaction() override; void spiEndTransaction() override; +#if ARCH_PORTDUINO + void spiTransfer(uint8_t *out, size_t len, uint8_t *in) override; +#endif }; #if defined(USE_STM32WLx) diff --git a/src/mesh/RadioLibRF95.cpp b/src/mesh/RadioLibRF95.cpp index 0fa6c7fe8..1fe7869a3 100644 --- a/src/mesh/RadioLibRF95.cpp +++ b/src/mesh/RadioLibRF95.cpp @@ -1,9 +1,6 @@ #include "RadioLibRF95.h" #include "configuration.h" -#define RF95_CHIP_VERSION 0x12 -#define RF95_ALT_VERSION 0x11 // Supposedly some versions of the chip have id 0x11 - // From datasheet but radiolib doesn't know anything about this #define SX127X_REG_TCXO 0x4B @@ -13,9 +10,8 @@ int16_t RadioLibRF95::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_ uint8_t gain) { // execute common part - int16_t state = SX127x::begin(RF95_CHIP_VERSION, syncWord, preambleLength); - if (state != RADIOLIB_ERR_NONE) - state = SX127x::begin(RF95_ALT_VERSION, syncWord, preambleLength); + uint8_t rf95versions[2] = {0x12, 0x11}; + int16_t state = SX127x::begin(rf95versions, sizeof(rf95versions), syncWord, preambleLength); RADIOLIB_ASSERT(state); // current limit was removed from module' ctor @@ -25,7 +21,7 @@ int16_t RadioLibRF95::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_ LOG_DEBUG("Current limit set result %d\n", state); // configure settings not accessible by API - state = config(); + // state = config(); RADIOLIB_ASSERT(state); #ifdef RF95_TCXO @@ -79,5 +75,6 @@ bool RadioLibRF95::isReceiving() uint8_t RadioLibRF95::readReg(uint8_t addr) { + Module *mod = this->getMod(); return mod->SPIreadRegister(addr); } \ No newline at end of file diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index fa85b1af4..08a42fe5f 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -71,12 +71,12 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) i->second.nextTxMsec += iface->getPacketTime(p); } - /* Resend implicit ACKs for repeated packets (hop limit is the original setting) this way if an implicit ACK is dropped and a - * packet is resent we'll rebroadcast again. Resending real ACKs is omitted, as you might receive a packet multiple times due - * to flooding and flooding this ACK back to the original sender already adds redundancy. */ - if (wasSeenRecently(p, false) && (p->original_hop_limit == p->hop_limit) && !MeshModule::currentReply && - p->to != nodeDB.getNodeNum()) { - // retransmission on broadcast has hop_limit still equal to HOP_RELIABLE + /* Resend implicit ACKs for repeated packets (hopStart equals hopLimit); + * this way if an implicit ACK is dropped and a packet is resent we'll rebroadcast again. + * Resending real ACKs is omitted, as you might receive a packet multiple times due to flooding and + * flooding this ACK back to the original sender already adds redundancy. */ + bool isRepeated = p->hop_start == 0 ? (p->hop_limit == HOP_RELIABLE) : (p->hop_start == p->hop_limit); + if (wasSeenRecently(p, false) && isRepeated && !MeshModule::currentReply && p->to != nodeDB->getNodeNum()) { LOG_DEBUG("Resending implicit ack for a repeated floodmsg\n"); meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); tosend->hop_limit--; // bump down the hop count @@ -107,10 +107,11 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas if (MeshModule::currentReply) { LOG_DEBUG("Some other module has replied to this message, no need for a 2nd ack\n"); } else if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { - sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel); + sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, p->hop_start, p->hop_limit); } else { // Send a 'NO_CHANNEL' error on the primary channel if want_ack packet destined for us cannot be decoded - sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex()); + sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex(), p->hop_start, + p->hop_limit); } } @@ -167,12 +168,16 @@ bool ReliableRouter::stopRetransmission(GlobalPacketId key) auto old = findPendingPacket(key); if (old) { auto p = old->packet; + /* Only when we already transmitted a packet via LoRa, we will cancel the packet in the Tx queue + to avoid canceling a transmission if it was ACKed super fast via MQTT */ + if (old->numRetransmissions < NUM_RETRANSMISSIONS - 1) { + // remove the 'original' (identified by originator and packet->id) from the txqueue and free it + cancelSending(getFrom(p), p->id); + // now free the pooled copy for retransmission too + packetPool.release(p); + } auto numErased = pending.erase(key); assert(numErased == 1); - // remove the 'original' (identified by originator and packet->id) from the txqueue and free it - cancelSending(getFrom(p), p->id); - // now free the pooled copy for retransmission too - packetPool.release(p); return true; } else return false; @@ -228,7 +233,7 @@ int32_t ReliableRouter::doRetransmissions() // Last retransmission, reset next_hop (fallback to FloodingRouter) p.packet->next_hop = NO_NEXT_HOP_PREFERENCE; // Also reset it in the nodeDB - meshtastic_NodeInfoLite *sentTo = nodeDB.getMeshNode(p.packet->to); + meshtastic_NodeInfoLite *sentTo = nodeDB->getMeshNode(p.packet->to); if (sentTo) { LOG_DEBUG("Resetting next hop for packet with dest 0x%x\n", p.packet->to); sentTo->next_hop = NO_NEXT_HOP_PREFERENCE; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index c33078c87..6a07fe1e1 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -8,12 +8,9 @@ #include "main.h" #include "mesh-pb-constants.h" #include "modules/RoutingModule.h" -extern "C" { -#include "mesh/compression/unishox2.h" -} - +#if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" - +#endif /** * Router todo * @@ -119,7 +116,7 @@ meshtastic_MeshPacket *Router::allocForSending() meshtastic_MeshPacket *p = packetPool.allocZeroed(); p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // Assume payload is decoded at start. - p->from = nodeDB.getNodeNum(); + p->from = nodeDB->getNodeNum(); p->to = NODENUM_BROADCAST; p->hop_limit = (config.lora.hop_limit >= HOP_MAX) ? HOP_MAX : config.lora.hop_limit; p->id = generatePacketId(); @@ -132,9 +129,10 @@ meshtastic_MeshPacket *Router::allocForSending() /** * Send an ack or a nak packet back towards whoever sent idFrom */ -void Router::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex) +void Router::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopStart, + uint8_t hopLimit) { - routingModule->sendAckNak(err, to, idFrom, chIndex); + routingModule->sendAckNak(err, to, idFrom, chIndex, hopStart, hopLimit); } void Router::abortSendAndNak(meshtastic_Routing_Error err, meshtastic_MeshPacket *p) @@ -164,7 +162,7 @@ meshtastic_QueueStatus Router::getQueueStatus() ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src) { // No need to deliver externally if the destination is the local node - if (p->to == nodeDB.getNodeNum()) { + if (p->to == nodeDB->getNodeNum()) { printPacket("Enqueued local", p); enqueueReceivedMessage(p); return ERRNO_OK; @@ -181,7 +179,7 @@ ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src) } if (!p->channel) { // don't override if a channel was requested - p->channel = nodeDB.getMeshNodeChannel(p->to); + p->channel = nodeDB->getMeshNodeChannel(p->to); LOG_DEBUG("localSend to channel %d\n", p->channel); } @@ -204,7 +202,7 @@ void printBytes(const char *label, const uint8_t *p, size_t numbytes) */ ErrorCode Router::send(meshtastic_MeshPacket *p) { - if (p->to == nodeDB.getNodeNum()) { + if (p->to == nodeDB->getNodeNum()) { LOG_ERROR("BUG! send() called with packet destined for local node!\n"); packetPool.release(p); return meshtastic_Routing_Error_BAD_REQUEST; @@ -219,7 +217,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) LOG_WARN("Duty cycle limit exceeded. Aborting send for now, you can send again in %d minutes.\n", silentMinutes); #endif meshtastic_Routing_Error err = meshtastic_Routing_Error_DUTY_CYCLE_LIMIT; - if (getFrom(p) == nodeDB.getNodeNum()) { // only send NAK to API, not to the mesh + if (getFrom(p) == nodeDB->getNodeNum()) { // only send NAK to API, not to the mesh abortSendAndNak(err, p); } else { packetPool.release(p); @@ -240,11 +238,10 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) // the lora we need to make sure we have replaced it with our local address p->from = getFrom(p); - // If we are the original transmitter, set the original hop limit + p->relay_node = nodeDB->getLastByteOfNodeNum(getNodeNum()); // set the relayer to us + // If we are the original transmitter, set the hop limit with which we start if (p->from == getNodeNum()) - p->original_hop_limit = config.lora.hop_limit ? config.lora.hop_limit : HOP_RELIABLE; - - p->relay_node = nodeDB.getLastByteOfNodeNum(getNodeNum()); // set the relayer to us + p->hop_start = p->hop_limit; // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) @@ -254,28 +251,21 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) // If the packet is not yet encrypted, do so now if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it - - if (moduleConfig.mqtt.enabled) { - - LOG_INFO("Should encrypt MQTT?: %d\n", moduleConfig.mqtt.encryption_enabled); - - // the packet is currently in a decrypted state. send it now if they want decrypted packets - if (mqtt && !moduleConfig.mqtt.encryption_enabled) - mqtt->onSend(*p, chIndex); - } + meshtastic_MeshPacket *p_decoded = packetPool.allocCopy(*p); auto encodeResult = perhapsEncode(p); if (encodeResult != meshtastic_Routing_Error_NONE) { + packetPool.release(p_decoded); abortSendAndNak(encodeResult, p); return encodeResult; // FIXME - this isn't a valid ErrorCode } - - if (moduleConfig.mqtt.enabled) { - // the packet is now encrypted. - // check if we should send encrypted packets to mqtt - if (mqtt && moduleConfig.mqtt.encryption_enabled) - mqtt->onSend(*p, chIndex); +#if !MESHTASTIC_EXCLUDE_MQTT + // Only publish to MQTT if we're the original transmitter of the packet + if (moduleConfig.mqtt.enabled && p->from == nodeDB->getNodeNum() && mqtt) { + mqtt->onSend(*p, *p_decoded, chIndex); } +#endif + packetPool.release(p_decoded); } assert(iface); // This should have been detected already in sendLocal (or we just received a packet from outside) @@ -305,6 +295,12 @@ bool perhapsDecode(meshtastic_MeshPacket *p) config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_ALL_SKIP_DECODING) return false; + if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY && + (nodeDB->getMeshNode(p->from) == NULL || !nodeDB->getMeshNode(p->from)->has_user)) { + LOG_DEBUG("Node 0x%x not in nodeDB-> Rebroadcast mode KNOWN_ONLY will ignore packet\n", p->from); + return false; + } + if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) return true; // If packet was already decoded just return @@ -334,6 +330,7 @@ bool perhapsDecode(meshtastic_MeshPacket *p) p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded p->channel = chIndex; // change to store the index instead of the hash + /* Not actually ever used. // Decompress if needed. jm if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP) { // Decompress the payload @@ -351,7 +348,7 @@ bool perhapsDecode(meshtastic_MeshPacket *p) // Switch the port from PortNum_TEXT_MESSAGE_COMPRESSED_APP to PortNum_TEXT_MESSAGE_APP p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; - } + } */ printPacket("decoded message", p); return true; @@ -373,6 +370,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded); + /* Not actually used, so save the cycles // Only allow encryption on the text message app. // TODO: Allow modules to opt into compression. if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) { @@ -406,7 +404,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP; } - } + } */ if (numbytes > MAX_RHPACKETLEN) return meshtastic_Routing_Error_TOO_LARGE; @@ -434,7 +432,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) NodeNum Router::getNodeNum() { - return nodeDB.getNodeNum(); + return nodeDB->getNodeNum(); } /** @@ -443,8 +441,11 @@ NodeNum Router::getNodeNum() */ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) { + bool skipHandle = false; // Also, we should set the time from the ISR and it should have msec level resolution p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone + // Store a copy of encrypted packet for MQTT + meshtastic_MeshPacket *p_encrypted = packetPool.allocCopy(*p); // Take those raw bytes and convert them back into a well structured protobuf we can understand bool decoded = perhapsDecode(p); @@ -456,21 +457,40 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) printPacket("handleReceived(USER)", p); else printPacket("handleReceived(REMOTE)", p); + + // Neighbor info module is disabled, ignore expensive neighbor info packets + if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && + p->decoded.portnum == meshtastic_PortNum_NEIGHBORINFO_APP && + (!moduleConfig.has_neighbor_info || !moduleConfig.neighbor_info.enabled)) { + LOG_DEBUG("Neighbor info module is disabled, ignoring neighbor packet\n"); + cancelSending(p->from, p->id); + skipHandle = true; + } } else { printPacket("packet decoding failed or skipped (no PSK?)", p); } // call modules here - MeshModule::callPlugins(*p, src); + if (!skipHandle) { + MeshModule::callModules(*p, src); + +#if !MESHTASTIC_EXCLUDE_MQTT + // After potentially altering it, publish received message to MQTT if we're not the original transmitter of the packet + if (decoded && moduleConfig.mqtt.enabled && getFrom(p) != nodeDB->getNodeNum() && mqtt) + mqtt->onSend(*p_encrypted, *p, p->channel); +#endif + } + + packetPool.release(p_encrypted); // Release the encrypted packet } void Router::perhapsHandleReceived(meshtastic_MeshPacket *p) { // assert(radioConfig.has_preferences); - bool ignore = is_in_repeated(config.lora.ignore_incoming, p->from); + bool ignore = is_in_repeated(config.lora.ignore_incoming, p->from) || (config.lora.ignore_mqtt && p->via_mqtt); if (ignore) { - LOG_DEBUG("Ignoring incoming message, 0x%x is in our ignore list\n", p->from); + LOG_DEBUG("Ignoring incoming message, 0x%x is in our ignore list or came via MQTT\n", p->from); } else if (ignore |= shouldFilterReceived(p)) { LOG_DEBUG("Incoming message was filtered 0x%x\n", p->from); } diff --git a/src/mesh/Router.h b/src/mesh/Router.h index db810e42e..98486745b 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -104,7 +104,8 @@ class Router : protected concurrency::OSThread /** * Send an ack or a nak packet back towards whoever sent idFrom */ - void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex); + void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopStart = 0, + uint8_t hopLimit = 0); private: /** diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 5083eeb53..afaa13b7f 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -2,7 +2,7 @@ #include "configuration.h" #include "error.h" #include "mesh/NodeDB.h" -#ifdef ARCH_RASPBERRY_PI +#ifdef ARCH_PORTDUINO #include "PortduinoGlue.h" #endif @@ -17,7 +17,7 @@ SX126xInterface::SX126xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs RADIOLIB_PIN_TYPE busy) : RadioLibInterface(hal, cs, irq, rst, busy, &lora), lora(&module) { - LOG_WARN("SX126xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy); + LOG_DEBUG("SX126xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy); } /// Initialise the Driver transport hardware and software. @@ -26,22 +26,29 @@ SX126xInterface::SX126xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs template bool SX126xInterface::init() { #ifdef SX126X_POWER_EN - digitalWrite(SX126X_POWER_EN, HIGH); pinMode(SX126X_POWER_EN, OUTPUT); + digitalWrite(SX126X_POWER_EN, HIGH); #endif +#if ARCH_PORTDUINO + float tcxoVoltage = 0; + if (settingsMap[dio3_tcxo_voltage]) + tcxoVoltage = 1.8; // FIXME: correct logic to default to not using TCXO if no voltage is specified for SX126X_DIO3_TCXO_VOLTAGE -#if !defined(SX126X_DIO3_TCXO_VOLTAGE) +#elif !defined(SX126X_DIO3_TCXO_VOLTAGE) float tcxoVoltage = 0; // "TCXO reference voltage to be set on DIO3. Defaults to 1.6 V, set to 0 to skip." per // https://github.com/jgromes/RadioLib/blob/690a050ebb46e6097c5d00c371e961c1caa3b52e/src/modules/SX126x/SX126x.h#L471C26-L471C104 // (DIO3 is free to be used as an IRQ) - LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage\n"); #else float tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; - LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V\n", SX126X_DIO3_TCXO_VOLTAGE); // (DIO3 is not free to be used as an IRQ) #endif + if (tcxoVoltage == 0) + LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage\n"); + else + LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V\n", tcxoVoltage); + // FIXME: May want to set depending on a definition, currently all SX126x variant files use the DC-DC regulator option bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC? @@ -77,7 +84,7 @@ template bool SX126xInterface::init() #ifdef SX126X_DIO2_AS_RF_SWITCH LOG_DEBUG("Setting DIO2 as RF switch\n"); bool dio2AsRfSwitch = true; -#elif defined(ARCH_RASPBERRY_PI) +#elif defined(ARCH_PORTDUINO) bool dio2AsRfSwitch = false; if (settingsMap[dio2_as_rf_switch]) { LOG_DEBUG("Setting DIO2 as RF switch\n"); @@ -93,6 +100,12 @@ template bool SX126xInterface::init() // If a pin isn't defined, we set it to RADIOLIB_NC, it is safe to always do external RF switching with RADIOLIB_NC as it has // no effect +#if ARCH_PORTDUINO + if (res == RADIOLIB_ERR_NONE) { + LOG_DEBUG("Using MCU pin %i as RXEN and pin %i as TXEN to control RF switching\n", settingsMap[rxen], settingsMap[txen]); + lora.setRfSwitchPins(settingsMap[rxen], settingsMap[txen]); + } +#else #ifndef SX126X_RXEN #define SX126X_RXEN RADIOLIB_NC LOG_DEBUG("SX126X_RXEN not defined, defaulting to RADIOLIB_NC\n"); @@ -105,7 +118,7 @@ template bool SX126xInterface::init() LOG_DEBUG("Using MCU pin %i as RXEN and pin %i as TXEN to control RF switching\n", SX126X_RXEN, SX126X_TXEN); lora.setRfSwitchPins(SX126X_RXEN, SX126X_TXEN); } - +#endif if (config.lora.sx126x_rx_boosted_gain) { uint16_t result = lora.setRxBoostedGainMode(true); LOG_INFO("Set RX gain to boosted mode; result: %d\n", result); @@ -167,18 +180,19 @@ template bool SX126xInterface::reconfigure() if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); - // Hmm - seems to lower SNR when the signal levels are high. Leaving off for now... - // TODO: Confirm gain registers are okay now - // err = lora.setRxGain(true); - // assert(err == RADIOLIB_ERR_NONE); - err = lora.setSyncWord(syncWord); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting SX126X setSyncWord!\n", err); assert(err == RADIOLIB_ERR_NONE); err = lora.setCurrentLimit(currentLimit); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting SX126X setCurrentLimit!\n", err); assert(err == RADIOLIB_ERR_NONE); err = lora.setPreambleLength(preambleLength); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting SX126X setPreambleLength!\n", err); assert(err == RADIOLIB_ERR_NONE); err = lora.setFrequency(getFreq()); @@ -189,6 +203,8 @@ template bool SX126xInterface::reconfigure() power = SX126X_MAX_POWER; err = lora.setOutputPower(power); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting SX126X setOutputPower!\n", err); assert(err == RADIOLIB_ERR_NONE); startReceive(); // restart receiving @@ -207,10 +223,8 @@ template void SX126xInterface::setStandby() int err = lora.standby(); - if (err != RADIOLIB_ERR_NONE) { + if (err != RADIOLIB_ERR_NONE) LOG_DEBUG("SX126x standby failed with error %d\n", err); - } - assert(err == RADIOLIB_ERR_NONE); isReceiving = false; // If we were receiving, not any more @@ -250,8 +264,10 @@ template void SX126xInterface::startReceive() // We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly. // Furthermore, we need the PREAMBLE_DETECTED and HEADER_VALID IRQ flag to detect whether we are actively receiving int err = lora.startReceiveDutyCycleAuto(preambleLength, 8, - RADIOLIB_SX126X_IRQ_RX_DEFAULT | RADIOLIB_SX126X_IRQ_RADIOLIB_PREAMBLE_DETECTED | + RADIOLIB_SX126X_IRQ_RX_DEFAULT | RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting SX126X startReceiveDutyCycleAuto!\n", err); assert(err == RADIOLIB_ERR_NONE); isReceiving = true; @@ -271,7 +287,8 @@ template bool SX126xInterface::isChannelActive() result = lora.scanChannel(); if (result == RADIOLIB_LORA_DETECTED) return true; - + if (result != RADIOLIB_CHANNEL_FREE) + LOG_ERROR("Radiolib error %d when attempting SX126X scanChannel!\n", result); assert(result != RADIOLIB_ERR_WRONG_MODEM); return false; @@ -284,7 +301,7 @@ template bool SX126xInterface::isActivelyReceiving() // received and handled the interrupt for reading the packet/handling errors. uint16_t irq = lora.getIrqStatus(); - bool detected = (irq & (RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_RADIOLIB_PREAMBLE_DETECTED)); + bool detected = (irq & (RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED)); // Handle false detections if (detected) { uint32_t now = millis(); diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index 1916c8042..9e4fbfa77 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -3,6 +3,10 @@ #include "error.h" #include "mesh/NodeDB.h" +#if ARCH_PORTDUINO +#include "PortduinoGlue.h" +#endif + // Particular boards might define a different max power based on what their hardware can do #ifndef SX128X_MAX_POWER #define SX128X_MAX_POWER 13 @@ -13,7 +17,7 @@ SX128xInterface::SX128xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs RADIOLIB_PIN_TYPE busy) : RadioLibInterface(hal, cs, irq, rst, busy, &lora), lora(&module) { - LOG_WARN("SX128xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy); + LOG_DEBUG("SX128xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy); } /// Initialise the Driver transport hardware and software. @@ -22,8 +26,8 @@ SX128xInterface::SX128xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs template bool SX128xInterface::init() { #ifdef SX128X_POWER_EN - digitalWrite(SX128X_POWER_EN, HIGH); pinMode(SX128X_POWER_EN, OUTPUT); + digitalWrite(SX128X_POWER_EN, HIGH); #endif #ifdef RF95_FAN_EN @@ -31,13 +35,24 @@ template bool SX128xInterface::init() digitalWrite(RF95_FAN_EN, 1); #endif +#if ARCH_PORTDUINO + if (settingsMap[rxen] != RADIOLIB_NC) { + pinMode(settingsMap[rxen], OUTPUT); + digitalWrite(settingsMap[rxen], LOW); // Set low before becoming an output + } + if (settingsMap[txen] != RADIOLIB_NC) { + pinMode(settingsMap[txen], OUTPUT); + digitalWrite(settingsMap[txen], LOW); // Set low before becoming an output + } +#else #if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // set not rx or tx mode - digitalWrite(SX128X_RXEN, LOW); // Set low before becoming an output pinMode(SX128X_RXEN, OUTPUT); + digitalWrite(SX128X_RXEN, LOW); // Set low before becoming an output #endif #if defined(SX128X_TXEN) && (SX128X_TXEN != RADIOLIB_NC) - digitalWrite(SX128X_TXEN, LOW); pinMode(SX128X_TXEN, OUTPUT); + digitalWrite(SX128X_TXEN, LOW); +#endif #endif RadioLibInterface::init(); @@ -56,7 +71,7 @@ template bool SX128xInterface::init() if ((config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (res == RADIOLIB_ERR_INVALID_FREQUENCY)) { LOG_WARN("Radio chip only supports 2.4GHz LoRa. Adjusting Region and rebooting.\n"); config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_LORA_24; - nodeDB.saveToDisk(SEGMENT_CONFIG); + nodeDB->saveToDisk(SEGMENT_CONFIG); delay(2000); #if defined(ARCH_ESP32) ESP.restart(); @@ -75,6 +90,10 @@ template bool SX128xInterface::init() if (res == RADIOLIB_ERR_NONE) { lora.setRfSwitchPins(SX128X_RXEN, SX128X_TXEN); } +#elif ARCH_PORTDUINO + if (res == RADIOLIB_ERR_NONE && settingsMap[rxen] != RADIOLIB_NC && settingsMap[txen] != RADIOLIB_NC) { + lora.setRfSwitchPins(settingsMap[rxen], settingsMap[txen]); + } #endif if (res == RADIOLIB_ERR_NONE) @@ -106,15 +125,14 @@ template bool SX128xInterface::reconfigure() if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); - // Hmm - seems to lower SNR when the signal levels are high. Leaving off for now... - // TODO: Confirm gain registers are okay now - // err = lora.setRxGain(true); - // assert(err == RADIOLIB_ERR_NONE); - err = lora.setSyncWord(syncWord); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting SX128X setSyncWord!\n", err); assert(err == RADIOLIB_ERR_NONE); err = lora.setPreambleLength(preambleLength); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting SX128X setPreambleLength!\n", err); assert(err == RADIOLIB_ERR_NONE); err = lora.setFrequency(getFreq()); @@ -125,6 +143,8 @@ template bool SX128xInterface::reconfigure() power = SX128X_MAX_POWER; err = lora.setOutputPower(power); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting SX128X setOutputPower!\n", err); assert(err == RADIOLIB_ERR_NONE); startReceive(); // restart receiving @@ -148,19 +168,24 @@ template void SX128xInterface::setStandby() int err = lora.standby(); - if (err != RADIOLIB_ERR_NONE) { + if (err != RADIOLIB_ERR_NONE) LOG_ERROR("SX128x standby failed with error %d\n", err); - } - assert(err == RADIOLIB_ERR_NONE); - +#if ARCH_PORTDUINO + if (settingsMap[rxen] != RADIOLIB_NC) { + digitalWrite(settingsMap[rxen], LOW); + } + if (settingsMap[txen] != RADIOLIB_NC) { + digitalWrite(settingsMap[txen], LOW); + } +#else #if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn off RX and TX power digitalWrite(SX128X_RXEN, LOW); #endif #if defined(SX128X_TXEN) && (SX128X_TXEN != RADIOLIB_NC) digitalWrite(SX128X_TXEN, LOW); #endif - +#endif isReceiving = false; // If we were receiving, not any more activeReceiveStart = 0; disableInterrupt(); @@ -181,11 +206,21 @@ template void SX128xInterface::addReceiveMetadata(meshtastic_Mes */ template void SX128xInterface::configHardwareForSend() { +#if ARCH_PORTDUINO + if (settingsMap[txen] != RADIOLIB_NC) { + digitalWrite(settingsMap[txen], HIGH); + } + if (settingsMap[rxen] != RADIOLIB_NC) { + digitalWrite(settingsMap[rxen], LOW); + } + +#else #if defined(SX128X_TXEN) && (SX128X_TXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn on TX power / off RX power digitalWrite(SX128X_TXEN, HIGH); #endif #if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) digitalWrite(SX128X_RXEN, LOW); +#endif #endif RadioLibInterface::configHardwareForSend(); @@ -202,18 +237,30 @@ template void SX128xInterface::startReceive() setStandby(); +#if ARCH_PORTDUINO + if (settingsMap[rxen] != RADIOLIB_NC) { + digitalWrite(settingsMap[rxen], HIGH); + } + if (settingsMap[txen] != RADIOLIB_NC) { + digitalWrite(settingsMap[txen], LOW); + } + +#else #if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn on RX power / off TX power digitalWrite(SX128X_RXEN, HIGH); #endif #if defined(SX128X_TXEN) && (SX128X_TXEN != RADIOLIB_NC) digitalWrite(SX128X_TXEN, LOW); +#endif #endif // We use the PREAMBLE_DETECTED and HEADER_VALID IRQ flag to detect whether we are actively receiving - int err = lora.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF, RADIOLIB_SX128X_IRQ_RX_DEFAULT | - RADIOLIB_SX128X_IRQ_RADIOLIB_PREAMBLE_DETECTED | - RADIOLIB_SX128X_IRQ_HEADER_VALID); + int err = + lora.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF, RADIOLIB_SX128X_IRQ_RX_DEFAULT | RADIOLIB_SX128X_IRQ_PREAMBLE_DETECTED | + RADIOLIB_SX128X_IRQ_HEADER_VALID); + if (err != RADIOLIB_ERR_NONE) + LOG_ERROR("Radiolib error %d when attempting SX128X startReceive!\n", err); assert(err == RADIOLIB_ERR_NONE); isReceiving = true; @@ -233,7 +280,8 @@ template bool SX128xInterface::isChannelActive() result = lora.scanChannel(); if (result == RADIOLIB_LORA_DETECTED) return true; - + if (result != RADIOLIB_CHANNEL_FREE) + LOG_ERROR("Radiolib error %d when attempting SX128X scanChannel!\n", result); assert(result != RADIOLIB_ERR_WRONG_MODEM); return false; @@ -243,7 +291,7 @@ template bool SX128xInterface::isChannelActive() template bool SX128xInterface::isActivelyReceiving() { uint16_t irq = lora.getIrqStatus(); - bool detected = (irq & (RADIOLIB_SX128X_IRQ_HEADER_VALID | RADIOLIB_SX128X_IRQ_RADIOLIB_PREAMBLE_DETECTED)); + bool detected = (irq & (RADIOLIB_SX128X_IRQ_HEADER_VALID | RADIOLIB_SX128X_IRQ_PREAMBLE_DETECTED)); // Handle false detections if (detected) { @@ -286,4 +334,4 @@ template bool SX128xInterface::sleep() #endif return true; -} +} \ No newline at end of file diff --git a/src/mesh/Throttle.cpp b/src/mesh/Throttle.cpp new file mode 100644 index 000000000..d8f23f9dc --- /dev/null +++ b/src/mesh/Throttle.cpp @@ -0,0 +1,27 @@ +#include "Throttle.h" +#include + +/// @brief Execute a function throttled to a minimum interval +/// @param lastExecutionMs Pointer to the last execution time in milliseconds +/// @param minumumIntervalMs Minimum execution interval in milliseconds +/// @param throttleFunc Function to execute if the execution is not deferred +/// @param onDefer Default to NULL, execute the function if the execution is deferred +/// @return true if the function was executed, false if it was deferred +bool Throttle::execute(uint32_t *lastExecutionMs, uint32_t minumumIntervalMs, void (*throttleFunc)(void), void (*onDefer)(void)) +{ + if (*lastExecutionMs == 0) { + *lastExecutionMs = millis(); + throttleFunc(); + return true; + } + uint32_t now = millis(); + + if ((now - *lastExecutionMs) >= minumumIntervalMs) { + throttleFunc(); + *lastExecutionMs = now; + return true; + } else if (onDefer != NULL) { + onDefer(); + } + return false; +} \ No newline at end of file diff --git a/src/mesh/Throttle.h b/src/mesh/Throttle.h new file mode 100644 index 000000000..8115595a4 --- /dev/null +++ b/src/mesh/Throttle.h @@ -0,0 +1,9 @@ +#pragma once +#include +#include + +class Throttle +{ + public: + static bool execute(uint32_t *lastExecutionMs, uint32_t minumumIntervalMs, void (*func)(void), void (*onDefer)(void) = NULL); +}; \ No newline at end of file diff --git a/src/mesh/TypeConversions.cpp b/src/mesh/TypeConversions.cpp index 4e0fdd385..bcd600f24 100644 --- a/src/mesh/TypeConversions.cpp +++ b/src/mesh/TypeConversions.cpp @@ -10,6 +10,9 @@ meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfo info.snr = lite->snr; info.last_heard = lite->last_heard; info.channel = lite->channel; + info.via_mqtt = lite->via_mqtt; + info.hops_away = lite->hops_away; + info.is_favorite = lite->is_favorite; if (lite->has_position) { info.has_position = true; diff --git a/src/mesh/TypedQueue.h b/src/mesh/TypedQueue.h index c08f9183b..c96edae8e 100644 --- a/src/mesh/TypedQueue.h +++ b/src/mesh/TypedQueue.h @@ -27,6 +27,8 @@ template class TypedQueue bool isEmpty() { return uxQueueMessagesWaiting(h) == 0; } + int numUsed() { return uxQueueMessagesWaiting(h); } + /** euqueue a packet. Also, maxWait used to default to portMAX_DELAY, but we now want to callers to THINK about what blocking * they want */ bool enqueue(T x, TickType_t maxWait) @@ -80,6 +82,8 @@ template class TypedQueue bool isEmpty() { return q.empty(); } + int numUsed() { return q.size(); } + bool enqueue(T x, TickType_t maxWait = portMAX_DELAY) { if (reader) { diff --git a/src/mesh/api/WiFiServerAPI.cpp b/src/mesh/api/WiFiServerAPI.cpp index 5f86dbe85..ba31f76e5 100644 --- a/src/mesh/api/WiFiServerAPI.cpp +++ b/src/mesh/api/WiFiServerAPI.cpp @@ -15,6 +15,10 @@ void initApiServer(int port) apiPort->init(); } } +void deInitApiServer() +{ + delete apiPort; +} WiFiServerAPI::WiFiServerAPI(WiFiClient &_client) : ServerAPI(_client) { @@ -22,4 +26,4 @@ WiFiServerAPI::WiFiServerAPI(WiFiClient &_client) : ServerAPI(_client) } WiFiServerPort::WiFiServerPort(int port) : APIServerPort(port) {} -#endif +#endif \ No newline at end of file diff --git a/src/mesh/api/WiFiServerAPI.h b/src/mesh/api/WiFiServerAPI.h index 11b494d23..7a3d2967f 100644 --- a/src/mesh/api/WiFiServerAPI.h +++ b/src/mesh/api/WiFiServerAPI.h @@ -23,3 +23,4 @@ class WiFiServerPort : public APIServerPort }; void initApiServer(int port = 4403); +void deInitApiServer(); \ No newline at end of file diff --git a/src/mesh/eth/ethClient.cpp b/src/mesh/eth/ethClient.cpp index f10c96866..5373f243e 100644 --- a/src/mesh/eth/ethClient.cpp +++ b/src/mesh/eth/ethClient.cpp @@ -2,9 +2,12 @@ #include "NodeDB.h" #include "RTC.h" #include "concurrency/Periodic.h" +#include "configuration.h" #include "main.h" #include "mesh/api/ethServerAPI.h" +#if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" +#endif #include "target_specific.h" #include #include @@ -66,11 +69,12 @@ static int32_t reconnectETH() ethStartupComplete = true; } - +#if !MESHTASTIC_EXCLUDE_MQTT // FIXME this is kinda yucky, instead we should just have an observable for 'wifireconnected' if (mqtt && !moduleConfig.mqtt.proxy_to_client_enabled && !mqtt->isConnectedDirectly()) { mqtt->reconnect(); } +#endif } #ifndef DISABLE_NTP @@ -97,11 +101,6 @@ static int32_t reconnectETH() return 5000; // every 5 seconds } -static uint32_t bigToLittleEndian(uint32_t value) -{ - return ((value >> 24) & 0xFF) | ((value >> 8) & 0xFF00) | ((value << 8) & 0xFF0000) | ((value << 24) & 0xFF000000); -} - // Startup Ethernet bool initEthernet() { @@ -130,14 +129,7 @@ bool initEthernet() status = Ethernet.begin(mac); } else if (config.network.address_mode == meshtastic_Config_NetworkConfig_AddressMode_STATIC) { LOG_INFO("starting Ethernet Static\n"); - - IPAddress ip = IPAddress(bigToLittleEndian(config.network.ipv4_config.ip)); - IPAddress dns = IPAddress(bigToLittleEndian(config.network.ipv4_config.dns)); - IPAddress gateway = IPAddress(bigToLittleEndian(config.network.ipv4_config.gateway)); - IPAddress subnet = IPAddress(bigToLittleEndian(config.network.ipv4_config.subnet)); - - Ethernet.begin(mac, ip, dns, gateway, subnet); - + Ethernet.begin(mac, config.network.ipv4_config.ip, config.network.ipv4_config.dns, config.network.ipv4_config.subnet); status = 1; } else { LOG_INFO("Ethernet Disabled\n"); diff --git a/src/mesh/generated/meshtastic/admin.pb.c b/src/mesh/generated/meshtastic/admin.pb.cpp similarity index 93% rename from src/mesh/generated/meshtastic/admin.pb.c rename to src/mesh/generated/meshtastic/admin.pb.cpp index 92835c89c..339960302 100644 --- a/src/mesh/generated/meshtastic/admin.pb.c +++ b/src/mesh/generated/meshtastic/admin.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/admin.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 9978c5591..d692a3f30 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_ADMIN_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_ADMIN_PB_H_INCLUDED @@ -7,7 +7,6 @@ #include "meshtastic/channel.pb.h" #include "meshtastic/config.pb.h" #include "meshtastic/connection_status.pb.h" -#include "meshtastic/deviceonly.pb.h" #include "meshtastic/mesh.pb.h" #include "meshtastic/module_config.pb.h" @@ -59,7 +58,9 @@ typedef enum _meshtastic_AdminMessage_ModuleConfigType { /* TODO: REPLACE */ meshtastic_AdminMessage_ModuleConfigType_AMBIENTLIGHTING_CONFIG = 10, /* TODO: REPLACE */ - meshtastic_AdminMessage_ModuleConfigType_DETECTIONSENSOR_CONFIG = 11 + meshtastic_AdminMessage_ModuleConfigType_DETECTIONSENSOR_CONFIG = 11, + /* TODO: REPLACE */ + meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12 } meshtastic_AdminMessage_ModuleConfigType; /* Struct definitions */ @@ -129,6 +130,11 @@ typedef struct _meshtastic_AdminMessage { bool get_node_remote_hardware_pins_request; /* Respond with the mesh's nodes with their available gpio pins for RemoteHardware module use */ meshtastic_NodeRemoteHardwarePinsResponse get_node_remote_hardware_pins_response; + /* Enter (UF2) DFU mode + Only implemented on NRF52 currently */ + bool enter_dfu_mode_request; + /* Delete the file by the specified path from the device */ + char delete_file_request[201]; /* Set the owner for this node */ meshtastic_User set_owner; /* Set channels (using the new API). @@ -147,6 +153,14 @@ typedef struct _meshtastic_AdminMessage { char set_ringtone_message[231]; /* Remove the node by the specified node-num from the NodeDB on the device */ uint32_t remove_by_nodenum; + /* Set specified node-num to be favorited on the NodeDB on the device */ + uint32_t set_favorite_node; + /* Set specified node-num to be un-favorited on the NodeDB on the device */ + uint32_t remove_favorite_node; + /* Set fixed position data on the node and then set the position.fixed_position = true */ + meshtastic_Position set_fixed_position; + /* Clear fixed position coordinates and then set position.fixed_position = false */ + bool remove_fixed_position; /* Begins an edit transaction for config, module config, owner, and channel settings changes This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) */ bool begin_edit_settings; @@ -180,8 +194,8 @@ extern "C" { #define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_BLUETOOTH_CONFIG+1)) #define _meshtastic_AdminMessage_ModuleConfigType_MIN meshtastic_AdminMessage_ModuleConfigType_MQTT_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_DETECTIONSENSOR_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_DETECTIONSENSOR_CONFIG+1)) +#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG +#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG+1)) #define meshtastic_AdminMessage_payload_variant_get_config_request_ENUMTYPE meshtastic_AdminMessage_ConfigType #define meshtastic_AdminMessage_payload_variant_get_module_config_request_ENUMTYPE meshtastic_AdminMessage_ModuleConfigType @@ -222,6 +236,8 @@ extern "C" { #define meshtastic_AdminMessage_set_ham_mode_tag 18 #define meshtastic_AdminMessage_get_node_remote_hardware_pins_request_tag 19 #define meshtastic_AdminMessage_get_node_remote_hardware_pins_response_tag 20 +#define meshtastic_AdminMessage_enter_dfu_mode_request_tag 21 +#define meshtastic_AdminMessage_delete_file_request_tag 22 #define meshtastic_AdminMessage_set_owner_tag 32 #define meshtastic_AdminMessage_set_channel_tag 33 #define meshtastic_AdminMessage_set_config_tag 34 @@ -229,6 +245,10 @@ extern "C" { #define meshtastic_AdminMessage_set_canned_message_module_messages_tag 36 #define meshtastic_AdminMessage_set_ringtone_message_tag 37 #define meshtastic_AdminMessage_remove_by_nodenum_tag 38 +#define meshtastic_AdminMessage_set_favorite_node_tag 39 +#define meshtastic_AdminMessage_remove_favorite_node_tag 40 +#define meshtastic_AdminMessage_set_fixed_position_tag 41 +#define meshtastic_AdminMessage_remove_fixed_position_tag 42 #define meshtastic_AdminMessage_begin_edit_settings_tag 64 #define meshtastic_AdminMessage_commit_edit_settings_tag 65 #define meshtastic_AdminMessage_reboot_ota_seconds_tag 95 @@ -259,6 +279,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,get_device_connection_status X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_ham_mode,set_ham_mode), 18) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,get_node_remote_hardware_pins_request,get_node_remote_hardware_pins_request), 19) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,get_node_remote_hardware_pins_response,get_node_remote_hardware_pins_response), 20) \ +X(a, STATIC, ONEOF, BOOL, (payload_variant,enter_dfu_mode_request,enter_dfu_mode_request), 21) \ +X(a, STATIC, ONEOF, STRING, (payload_variant,delete_file_request,delete_file_request), 22) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_owner,set_owner), 32) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_channel,set_channel), 33) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_config,set_config), 34) \ @@ -266,6 +288,10 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_module_config,set_module X(a, STATIC, ONEOF, STRING, (payload_variant,set_canned_message_module_messages,set_canned_message_module_messages), 36) \ X(a, STATIC, ONEOF, STRING, (payload_variant,set_ringtone_message,set_ringtone_message), 37) \ X(a, STATIC, ONEOF, UINT32, (payload_variant,remove_by_nodenum,remove_by_nodenum), 38) \ +X(a, STATIC, ONEOF, UINT32, (payload_variant,set_favorite_node,set_favorite_node), 39) \ +X(a, STATIC, ONEOF, UINT32, (payload_variant,remove_favorite_node,remove_favorite_node), 40) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_fixed_position,set_fixed_position), 41) \ +X(a, STATIC, ONEOF, BOOL, (payload_variant,remove_fixed_position,remove_fixed_position), 42) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,begin_edit_settings,begin_edit_settings), 64) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,commit_edit_settings,commit_edit_settings), 65) \ X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_ota_seconds,reboot_ota_seconds), 95) \ @@ -288,6 +314,7 @@ X(a, STATIC, ONEOF, INT32, (payload_variant,nodedb_reset,nodedb_reset), #define meshtastic_AdminMessage_payload_variant_set_channel_MSGTYPE meshtastic_Channel #define meshtastic_AdminMessage_payload_variant_set_config_MSGTYPE meshtastic_Config #define meshtastic_AdminMessage_payload_variant_set_module_config_MSGTYPE meshtastic_ModuleConfig +#define meshtastic_AdminMessage_payload_variant_set_fixed_position_MSGTYPE meshtastic_Position #define meshtastic_HamParameters_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, STRING, call_sign, 1) \ @@ -313,6 +340,7 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePinsResponse_msg; #define meshtastic_NodeRemoteHardwarePinsResponse_fields &meshtastic_NodeRemoteHardwarePinsResponse_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_ADMIN_PB_H_MAX_SIZE meshtastic_AdminMessage_size #define meshtastic_AdminMessage_size 500 #define meshtastic_HamParameters_size 32 #define meshtastic_NodeRemoteHardwarePinsResponse_size 496 diff --git a/src/mesh/generated/meshtastic/apponly.pb.c b/src/mesh/generated/meshtastic/apponly.pb.cpp similarity index 89% rename from src/mesh/generated/meshtastic/apponly.pb.c rename to src/mesh/generated/meshtastic/apponly.pb.cpp index 8c3801ed7..44b0ea3cc 100644 --- a/src/mesh/generated/meshtastic/apponly.pb.c +++ b/src/mesh/generated/meshtastic/apponly.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/apponly.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h index 65093506f..b834d7d2e 100644 --- a/src/mesh/generated/meshtastic/apponly.pb.h +++ b/src/mesh/generated/meshtastic/apponly.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_APPONLY_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_APPONLY_PB_H_INCLUDED @@ -54,7 +54,8 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg; #define meshtastic_ChannelSet_fields &meshtastic_ChannelSet_msg /* Maximum encoded size of messages (where known) */ -#define meshtastic_ChannelSet_size 593 +#define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size +#define meshtastic_ChannelSet_size 660 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/atak.pb.cpp b/src/mesh/generated/meshtastic/atak.pb.cpp new file mode 100644 index 000000000..491336bcf --- /dev/null +++ b/src/mesh/generated/meshtastic/atak.pb.cpp @@ -0,0 +1,29 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.8 */ + +#include "meshtastic/atak.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(meshtastic_TAKPacket, meshtastic_TAKPacket, 2) + + +PB_BIND(meshtastic_GeoChat, meshtastic_GeoChat, 2) + + +PB_BIND(meshtastic_Group, meshtastic_Group, AUTO) + + +PB_BIND(meshtastic_Status, meshtastic_Status, AUTO) + + +PB_BIND(meshtastic_Contact, meshtastic_Contact, AUTO) + + +PB_BIND(meshtastic_PLI, meshtastic_PLI, AUTO) + + + + + diff --git a/src/mesh/generated/meshtastic/atak.pb.h b/src/mesh/generated/meshtastic/atak.pb.h new file mode 100644 index 000000000..c094727ed --- /dev/null +++ b/src/mesh/generated/meshtastic/atak.pb.h @@ -0,0 +1,275 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.8 */ + +#ifndef PB_MESHTASTIC_MESHTASTIC_ATAK_PB_H_INCLUDED +#define PB_MESHTASTIC_MESHTASTIC_ATAK_PB_H_INCLUDED +#include + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Enum definitions */ +typedef enum _meshtastic_Team { + /* Unspecifed */ + meshtastic_Team_Unspecifed_Color = 0, + /* White */ + meshtastic_Team_White = 1, + /* Yellow */ + meshtastic_Team_Yellow = 2, + /* Orange */ + meshtastic_Team_Orange = 3, + /* Magenta */ + meshtastic_Team_Magenta = 4, + /* Red */ + meshtastic_Team_Red = 5, + /* Maroon */ + meshtastic_Team_Maroon = 6, + /* Purple */ + meshtastic_Team_Purple = 7, + /* Dark Blue */ + meshtastic_Team_Dark_Blue = 8, + /* Blue */ + meshtastic_Team_Blue = 9, + /* Cyan */ + meshtastic_Team_Cyan = 10, + /* Teal */ + meshtastic_Team_Teal = 11, + /* Green */ + meshtastic_Team_Green = 12, + /* Dark Green */ + meshtastic_Team_Dark_Green = 13, + /* Brown */ + meshtastic_Team_Brown = 14 +} meshtastic_Team; + +/* Role of the group member */ +typedef enum _meshtastic_MemberRole { + /* Unspecifed */ + meshtastic_MemberRole_Unspecifed = 0, + /* Team Member */ + meshtastic_MemberRole_TeamMember = 1, + /* Team Lead */ + meshtastic_MemberRole_TeamLead = 2, + /* Headquarters */ + meshtastic_MemberRole_HQ = 3, + /* Airsoft enthusiast */ + meshtastic_MemberRole_Sniper = 4, + /* Medic */ + meshtastic_MemberRole_Medic = 5, + /* ForwardObserver */ + meshtastic_MemberRole_ForwardObserver = 6, + /* Radio Telephone Operator */ + meshtastic_MemberRole_RTO = 7, + /* Doggo */ + meshtastic_MemberRole_K9 = 8 +} meshtastic_MemberRole; + +/* Struct definitions */ +/* ATAK GeoChat message */ +typedef struct _meshtastic_GeoChat { + /* The text message */ + char message[200]; + /* Uid recipient of the message */ + bool has_to; + char to[120]; +} meshtastic_GeoChat; + +/* ATAK Group + <__group role='Team Member' name='Cyan'/> */ +typedef struct _meshtastic_Group { + /* Role of the group member */ + meshtastic_MemberRole role; + /* Team (color) + Default Cyan */ + meshtastic_Team team; +} meshtastic_Group; + +/* ATAK EUD Status + */ +typedef struct _meshtastic_Status { + /* Battery level */ + uint8_t battery; +} meshtastic_Status; + +/* ATAK Contact + */ +typedef struct _meshtastic_Contact { + /* Callsign */ + char callsign[120]; + /* Device callsign */ + char device_callsign[120]; /* IP address of endpoint in integer form (0.0.0.0 default) */ +} meshtastic_Contact; + +/* Position Location Information from ATAK */ +typedef struct _meshtastic_PLI { + /* The new preferred location encoding, multiply by 1e-7 to get degrees + in floating point */ + int32_t latitude_i; + /* The new preferred location encoding, multiply by 1e-7 to get degrees + in floating point */ + int32_t longitude_i; + /* Altitude (ATAK prefers HAE) */ + int32_t altitude; + /* Speed */ + uint32_t speed; + /* Course in degrees */ + uint16_t course; +} meshtastic_PLI; + +/* Packets for the official ATAK Plugin */ +typedef struct _meshtastic_TAKPacket { + /* Are the payloads strings compressed for LoRA transport? */ + bool is_compressed; + /* The contact / callsign for ATAK user */ + bool has_contact; + meshtastic_Contact contact; + /* The group for ATAK user */ + bool has_group; + meshtastic_Group group; + /* The status of the ATAK EUD */ + bool has_status; + meshtastic_Status status; + pb_size_t which_payload_variant; + union { + /* TAK position report */ + meshtastic_PLI pli; + /* ATAK GeoChat message */ + meshtastic_GeoChat chat; + } payload_variant; +} meshtastic_TAKPacket; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Helper constants for enums */ +#define _meshtastic_Team_MIN meshtastic_Team_Unspecifed_Color +#define _meshtastic_Team_MAX meshtastic_Team_Brown +#define _meshtastic_Team_ARRAYSIZE ((meshtastic_Team)(meshtastic_Team_Brown+1)) + +#define _meshtastic_MemberRole_MIN meshtastic_MemberRole_Unspecifed +#define _meshtastic_MemberRole_MAX meshtastic_MemberRole_K9 +#define _meshtastic_MemberRole_ARRAYSIZE ((meshtastic_MemberRole)(meshtastic_MemberRole_K9+1)) + + + +#define meshtastic_Group_role_ENUMTYPE meshtastic_MemberRole +#define meshtastic_Group_team_ENUMTYPE meshtastic_Team + + + + + +/* Initializer values for message structs */ +#define meshtastic_TAKPacket_init_default {0, false, meshtastic_Contact_init_default, false, meshtastic_Group_init_default, false, meshtastic_Status_init_default, 0, {meshtastic_PLI_init_default}} +#define meshtastic_GeoChat_init_default {"", false, ""} +#define meshtastic_Group_init_default {_meshtastic_MemberRole_MIN, _meshtastic_Team_MIN} +#define meshtastic_Status_init_default {0} +#define meshtastic_Contact_init_default {"", ""} +#define meshtastic_PLI_init_default {0, 0, 0, 0, 0} +#define meshtastic_TAKPacket_init_zero {0, false, meshtastic_Contact_init_zero, false, meshtastic_Group_init_zero, false, meshtastic_Status_init_zero, 0, {meshtastic_PLI_init_zero}} +#define meshtastic_GeoChat_init_zero {"", false, ""} +#define meshtastic_Group_init_zero {_meshtastic_MemberRole_MIN, _meshtastic_Team_MIN} +#define meshtastic_Status_init_zero {0} +#define meshtastic_Contact_init_zero {"", ""} +#define meshtastic_PLI_init_zero {0, 0, 0, 0, 0} + +/* Field tags (for use in manual encoding/decoding) */ +#define meshtastic_GeoChat_message_tag 1 +#define meshtastic_GeoChat_to_tag 2 +#define meshtastic_Group_role_tag 1 +#define meshtastic_Group_team_tag 2 +#define meshtastic_Status_battery_tag 1 +#define meshtastic_Contact_callsign_tag 1 +#define meshtastic_Contact_device_callsign_tag 2 +#define meshtastic_PLI_latitude_i_tag 1 +#define meshtastic_PLI_longitude_i_tag 2 +#define meshtastic_PLI_altitude_tag 3 +#define meshtastic_PLI_speed_tag 4 +#define meshtastic_PLI_course_tag 5 +#define meshtastic_TAKPacket_is_compressed_tag 1 +#define meshtastic_TAKPacket_contact_tag 2 +#define meshtastic_TAKPacket_group_tag 3 +#define meshtastic_TAKPacket_status_tag 4 +#define meshtastic_TAKPacket_pli_tag 5 +#define meshtastic_TAKPacket_chat_tag 6 + +/* Struct field encoding specification for nanopb */ +#define meshtastic_TAKPacket_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, BOOL, is_compressed, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, contact, 2) \ +X(a, STATIC, OPTIONAL, MESSAGE, group, 3) \ +X(a, STATIC, OPTIONAL, MESSAGE, status, 4) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,pli,payload_variant.pli), 5) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,chat,payload_variant.chat), 6) +#define meshtastic_TAKPacket_CALLBACK NULL +#define meshtastic_TAKPacket_DEFAULT NULL +#define meshtastic_TAKPacket_contact_MSGTYPE meshtastic_Contact +#define meshtastic_TAKPacket_group_MSGTYPE meshtastic_Group +#define meshtastic_TAKPacket_status_MSGTYPE meshtastic_Status +#define meshtastic_TAKPacket_payload_variant_pli_MSGTYPE meshtastic_PLI +#define meshtastic_TAKPacket_payload_variant_chat_MSGTYPE meshtastic_GeoChat + +#define meshtastic_GeoChat_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, message, 1) \ +X(a, STATIC, OPTIONAL, STRING, to, 2) +#define meshtastic_GeoChat_CALLBACK NULL +#define meshtastic_GeoChat_DEFAULT NULL + +#define meshtastic_Group_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, role, 1) \ +X(a, STATIC, SINGULAR, UENUM, team, 2) +#define meshtastic_Group_CALLBACK NULL +#define meshtastic_Group_DEFAULT NULL + +#define meshtastic_Status_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, battery, 1) +#define meshtastic_Status_CALLBACK NULL +#define meshtastic_Status_DEFAULT NULL + +#define meshtastic_Contact_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, callsign, 1) \ +X(a, STATIC, SINGULAR, STRING, device_callsign, 2) +#define meshtastic_Contact_CALLBACK NULL +#define meshtastic_Contact_DEFAULT NULL + +#define meshtastic_PLI_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 1) \ +X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 2) \ +X(a, STATIC, SINGULAR, INT32, altitude, 3) \ +X(a, STATIC, SINGULAR, UINT32, speed, 4) \ +X(a, STATIC, SINGULAR, UINT32, course, 5) +#define meshtastic_PLI_CALLBACK NULL +#define meshtastic_PLI_DEFAULT NULL + +extern const pb_msgdesc_t meshtastic_TAKPacket_msg; +extern const pb_msgdesc_t meshtastic_GeoChat_msg; +extern const pb_msgdesc_t meshtastic_Group_msg; +extern const pb_msgdesc_t meshtastic_Status_msg; +extern const pb_msgdesc_t meshtastic_Contact_msg; +extern const pb_msgdesc_t meshtastic_PLI_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define meshtastic_TAKPacket_fields &meshtastic_TAKPacket_msg +#define meshtastic_GeoChat_fields &meshtastic_GeoChat_msg +#define meshtastic_Group_fields &meshtastic_Group_msg +#define meshtastic_Status_fields &meshtastic_Status_msg +#define meshtastic_Contact_fields &meshtastic_Contact_msg +#define meshtastic_PLI_fields &meshtastic_PLI_msg + +/* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_ATAK_PB_H_MAX_SIZE meshtastic_TAKPacket_size +#define meshtastic_Contact_size 242 +#define meshtastic_GeoChat_size 323 +#define meshtastic_Group_size 4 +#define meshtastic_PLI_size 31 +#define meshtastic_Status_size 3 +#define meshtastic_TAKPacket_size 584 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/mesh/generated/meshtastic/cannedmessages.pb.c b/src/mesh/generated/meshtastic/cannedmessages.pb.cpp similarity index 90% rename from src/mesh/generated/meshtastic/cannedmessages.pb.c rename to src/mesh/generated/meshtastic/cannedmessages.pb.cpp index fffa3fdf9..71e659be2 100644 --- a/src/mesh/generated/meshtastic/cannedmessages.pb.c +++ b/src/mesh/generated/meshtastic/cannedmessages.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/cannedmessages.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/cannedmessages.pb.h b/src/mesh/generated/meshtastic/cannedmessages.pb.h index b81f65d0d..c3f9a8b9b 100644 --- a/src/mesh/generated/meshtastic/cannedmessages.pb.h +++ b/src/mesh/generated/meshtastic/cannedmessages.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_CANNEDMESSAGES_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_CANNEDMESSAGES_PB_H_INCLUDED @@ -40,6 +40,7 @@ extern const pb_msgdesc_t meshtastic_CannedMessageModuleConfig_msg; #define meshtastic_CannedMessageModuleConfig_fields &meshtastic_CannedMessageModuleConfig_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_CANNEDMESSAGES_PB_H_MAX_SIZE meshtastic_CannedMessageModuleConfig_size #define meshtastic_CannedMessageModuleConfig_size 203 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/channel.pb.c b/src/mesh/generated/meshtastic/channel.pb.cpp similarity index 76% rename from src/mesh/generated/meshtastic/channel.pb.c rename to src/mesh/generated/meshtastic/channel.pb.cpp index 62585fd17..fe76d8140 100644 --- a/src/mesh/generated/meshtastic/channel.pb.c +++ b/src/mesh/generated/meshtastic/channel.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/channel.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -9,6 +9,9 @@ PB_BIND(meshtastic_ChannelSettings, meshtastic_ChannelSettings, AUTO) +PB_BIND(meshtastic_ModuleSettings, meshtastic_ModuleSettings, AUTO) + + PB_BIND(meshtastic_Channel, meshtastic_Channel, AUTO) diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h index 535962ae6..185a47a98 100644 --- a/src/mesh/generated/meshtastic/channel.pb.h +++ b/src/mesh/generated/meshtastic/channel.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_INCLUDED @@ -30,6 +30,12 @@ typedef enum _meshtastic_Channel_Role { } meshtastic_Channel_Role; /* Struct definitions */ +/* This message is specifically for modules to store per-channel configuration data. */ +typedef struct _meshtastic_ModuleSettings { + /* Bits of precision for the location sent in position packets. */ + uint32_t position_precision; +} meshtastic_ModuleSettings; + typedef PB_BYTES_ARRAY_T(32) meshtastic_ChannelSettings_psk_t; /* This information can be encoded as a QRcode/url so that other users can configure their radio to join the same channel. @@ -85,6 +91,9 @@ typedef struct _meshtastic_ChannelSettings { bool uplink_enabled; /* If true, messages seen on the internet will be forwarded to the local mesh. */ bool downlink_enabled; + /* Per-channel module settings. */ + bool has_module_settings; + meshtastic_ModuleSettings module_settings; } meshtastic_ChannelSettings; /* A pair of a channel number, mode and the (sharable) settings for that channel */ @@ -111,22 +120,27 @@ extern "C" { #define _meshtastic_Channel_Role_ARRAYSIZE ((meshtastic_Channel_Role)(meshtastic_Channel_Role_SECONDARY+1)) + #define meshtastic_Channel_role_ENUMTYPE meshtastic_Channel_Role /* Initializer values for message structs */ -#define meshtastic_ChannelSettings_init_default {0, {0, {0}}, "", 0, 0, 0} +#define meshtastic_ChannelSettings_init_default {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default} +#define meshtastic_ModuleSettings_init_default {0} #define meshtastic_Channel_init_default {0, false, meshtastic_ChannelSettings_init_default, _meshtastic_Channel_Role_MIN} -#define meshtastic_ChannelSettings_init_zero {0, {0, {0}}, "", 0, 0, 0} +#define meshtastic_ChannelSettings_init_zero {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero} +#define meshtastic_ModuleSettings_init_zero {0} #define meshtastic_Channel_init_zero {0, false, meshtastic_ChannelSettings_init_zero, _meshtastic_Channel_Role_MIN} /* Field tags (for use in manual encoding/decoding) */ +#define meshtastic_ModuleSettings_position_precision_tag 1 #define meshtastic_ChannelSettings_channel_num_tag 1 #define meshtastic_ChannelSettings_psk_tag 2 #define meshtastic_ChannelSettings_name_tag 3 #define meshtastic_ChannelSettings_id_tag 4 #define meshtastic_ChannelSettings_uplink_enabled_tag 5 #define meshtastic_ChannelSettings_downlink_enabled_tag 6 +#define meshtastic_ChannelSettings_module_settings_tag 7 #define meshtastic_Channel_index_tag 1 #define meshtastic_Channel_settings_tag 2 #define meshtastic_Channel_role_tag 3 @@ -138,9 +152,16 @@ X(a, STATIC, SINGULAR, BYTES, psk, 2) \ X(a, STATIC, SINGULAR, STRING, name, 3) \ X(a, STATIC, SINGULAR, FIXED32, id, 4) \ X(a, STATIC, SINGULAR, BOOL, uplink_enabled, 5) \ -X(a, STATIC, SINGULAR, BOOL, downlink_enabled, 6) +X(a, STATIC, SINGULAR, BOOL, downlink_enabled, 6) \ +X(a, STATIC, OPTIONAL, MESSAGE, module_settings, 7) #define meshtastic_ChannelSettings_CALLBACK NULL #define meshtastic_ChannelSettings_DEFAULT NULL +#define meshtastic_ChannelSettings_module_settings_MSGTYPE meshtastic_ModuleSettings + +#define meshtastic_ModuleSettings_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, position_precision, 1) +#define meshtastic_ModuleSettings_CALLBACK NULL +#define meshtastic_ModuleSettings_DEFAULT NULL #define meshtastic_Channel_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, INT32, index, 1) \ @@ -151,15 +172,19 @@ X(a, STATIC, SINGULAR, UENUM, role, 3) #define meshtastic_Channel_settings_MSGTYPE meshtastic_ChannelSettings extern const pb_msgdesc_t meshtastic_ChannelSettings_msg; +extern const pb_msgdesc_t meshtastic_ModuleSettings_msg; extern const pb_msgdesc_t meshtastic_Channel_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_ChannelSettings_fields &meshtastic_ChannelSettings_msg +#define meshtastic_ModuleSettings_fields &meshtastic_ModuleSettings_msg #define meshtastic_Channel_fields &meshtastic_Channel_msg /* Maximum encoded size of messages (where known) */ -#define meshtastic_ChannelSettings_size 62 -#define meshtastic_Channel_size 77 +#define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size +#define meshtastic_ChannelSettings_size 70 +#define meshtastic_Channel_size 85 +#define meshtastic_ModuleSettings_size 6 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/clientonly.pb.c b/src/mesh/generated/meshtastic/clientonly.pb.cpp similarity index 89% rename from src/mesh/generated/meshtastic/clientonly.pb.c rename to src/mesh/generated/meshtastic/clientonly.pb.cpp index ebc2ffabc..44c6f95ce 100644 --- a/src/mesh/generated/meshtastic/clientonly.pb.c +++ b/src/mesh/generated/meshtastic/clientonly.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/clientonly.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/clientonly.pb.h b/src/mesh/generated/meshtastic/clientonly.pb.h index 0f70e09c6..dc323292a 100644 --- a/src/mesh/generated/meshtastic/clientonly.pb.h +++ b/src/mesh/generated/meshtastic/clientonly.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_CLIENTONLY_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_CLIENTONLY_PB_H_INCLUDED diff --git a/src/mesh/generated/meshtastic/config.pb.c b/src/mesh/generated/meshtastic/config.pb.cpp similarity index 96% rename from src/mesh/generated/meshtastic/config.pb.c rename to src/mesh/generated/meshtastic/config.pb.cpp index 361e28d7c..f05e47573 100644 --- a/src/mesh/generated/meshtastic/config.pb.c +++ b/src/mesh/generated/meshtastic/config.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/config.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -45,3 +45,4 @@ PB_BIND(meshtastic_Config_BluetoothConfig, meshtastic_Config_BluetoothConfig, AU + diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 29158733a..7471d32f0 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_CONFIG_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_CONFIG_PB_H_INCLUDED @@ -12,36 +12,53 @@ /* Enum definitions */ /* Defines the device's role on the Mesh network */ typedef enum _meshtastic_Config_DeviceConfig_Role { - /* Client device role */ + /* Description: App connected or stand alone messaging device. + Technical Details: Default Role */ meshtastic_Config_DeviceConfig_Role_CLIENT = 0, - /* Client Mute device role - Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh. */ + /* Description: Device that does not forward packets from other devices. */ meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE = 1, - /* Router device role. - Mesh packets will prefer to be routed over this node. This node will not be used by client apps. - The wifi/ble radios and the oled screen will be put to sleep. + /* Description: Infrastructure node for extending network coverage by relaying messages. Visible in Nodes list. + Technical Details: Mesh packets will prefer to be routed over this node. This node will not be used by client apps. + The wifi radio and the oled screen will be put to sleep. This mode may still potentially have higher power usage due to it's preference in message rebroadcasting on the mesh. */ meshtastic_Config_DeviceConfig_Role_ROUTER = 2, - /* Router Client device role - Mesh packets will prefer to be routed over this node. The Router Client can be used as both a Router and an app connected Client. */ + /* Description: Combination of both ROUTER and CLIENT. Not for mobile devices. */ meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT = 3, - /* Repeater device role - Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry + /* Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list. + Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate. */ meshtastic_Config_DeviceConfig_Role_REPEATER = 4, - /* Tracker device role - Position Mesh packets will be prioritized higher and sent more frequently by default. */ + /* Description: Broadcasts GPS position packets as priority. + Technical Details: Position Mesh packets will be prioritized higher and sent more frequently by default. + When used in conjunction with power.is_power_saving = true, nodes will wake up, + send position, and then sleep for position.position_broadcast_secs seconds. */ meshtastic_Config_DeviceConfig_Role_TRACKER = 5, - /* Sensor device role - Telemetry Mesh packets will be prioritized higher and sent more frequently by default. - When used in conjunction with power.is_power_saving = true, nodes will wake up, + /* Description: Broadcasts telemetry packets as priority. + Technical Details: Telemetry Mesh packets will be prioritized higher and sent more frequently by default. + When used in conjunction with power.is_power_saving = true, nodes will wake up, send environment telemetry, and then sleep for telemetry.environment_update_interval seconds. */ meshtastic_Config_DeviceConfig_Role_SENSOR = 6, - /* TAK device role - Used for nodes dedicated for connection to an ATAK EUD. + /* Description: Optimized for ATAK system communication and reduces routine broadcasts. + Technical Details: Used for nodes dedicated for connection to an ATAK EUD. Turns off many of the routine broadcasts to favor CoT packet stream from the Meshtastic ATAK plugin -> IMeshService -> Node */ - meshtastic_Config_DeviceConfig_Role_TAK = 7 + meshtastic_Config_DeviceConfig_Role_TAK = 7, + /* Description: Device that only broadcasts as needed for stealth or power savings. + Technical Details: Used for nodes that "only speak when spoken to" + Turns all of the routine broadcasts but allows for ad-hoc communication + Still rebroadcasts, but with local only rebroadcast mode (known meshes only) + Can be used for clandestine operation or to dramatically reduce airtime / power consumption */ + meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN = 8, + /* Description: Broadcasts location as message to default channel regularly for to assist with device recovery. + Technical Details: Used to automatically send a text message to the mesh + with the current position of the device on a frequent interval: + "I'm lost! Position: lat / long" */ + meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND = 9, + /* Description: Enables automatic TAK PLI broadcasts and reduces routine broadcasts. + Technical Details: Turns off many of the routine broadcasts to favor ATAK CoT packet stream + and automatic TAK PLI (position location information) broadcasts. + Uses position module configuration to determine TAK PLI broadcast interval. */ + meshtastic_Config_DeviceConfig_Role_TAK_TRACKER = 10 } meshtastic_Config_DeviceConfig_Role; /* Defines the device's behavior for how messages are rebroadcast */ @@ -54,7 +71,10 @@ typedef enum _meshtastic_Config_DeviceConfig_RebroadcastMode { meshtastic_Config_DeviceConfig_RebroadcastMode_ALL_SKIP_DECODING = 1, /* Ignores observed messages from foreign meshes that are open or those which it cannot decrypt. Only rebroadcasts message on the nodes local primary / secondary channels. */ - meshtastic_Config_DeviceConfig_RebroadcastMode_LOCAL_ONLY = 2 + meshtastic_Config_DeviceConfig_RebroadcastMode_LOCAL_ONLY = 2, + /* Ignores observed messages from foreign meshes like LOCAL_ONLY, + but takes it step further by also ignoring messages from nodenums not in the node's known list (NodeDB) */ + meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY = 3 } meshtastic_Config_DeviceConfig_RebroadcastMode; /* Bit field of boolean configuration options, indicating which optional @@ -92,6 +112,15 @@ typedef enum _meshtastic_Config_PositionConfig_PositionFlags { meshtastic_Config_PositionConfig_PositionFlags_SPEED = 512 } meshtastic_Config_PositionConfig_PositionFlags; +typedef enum _meshtastic_Config_PositionConfig_GpsMode { + /* GPS is present but disabled */ + meshtastic_Config_PositionConfig_GpsMode_DISABLED = 0, + /* GPS is present and enabled */ + meshtastic_Config_PositionConfig_GpsMode_ENABLED = 1, + /* GPS is not present on the device */ + meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT = 2 +} meshtastic_Config_PositionConfig_GpsMode; + typedef enum _meshtastic_Config_NetworkConfig_AddressMode { /* obtain ip address via DHCP */ meshtastic_Config_NetworkConfig_AddressMode_DHCP = 0, @@ -185,7 +214,13 @@ typedef enum _meshtastic_Config_LoRaConfig_RegionCode { /* Ukraine 433mhz */ meshtastic_Config_LoRaConfig_RegionCode_UA_433 = 14, /* Ukraine 868mhz */ - meshtastic_Config_LoRaConfig_RegionCode_UA_868 = 15 + meshtastic_Config_LoRaConfig_RegionCode_UA_868 = 15, + /* Malaysia 433mhz */ + meshtastic_Config_LoRaConfig_RegionCode_MY_433 = 16, + /* Malaysia 919mhz */ + meshtastic_Config_LoRaConfig_RegionCode_MY_919 = 17, + /* Singapore 923mhz */ + meshtastic_Config_LoRaConfig_RegionCode_SG_923 = 18 } meshtastic_Config_LoRaConfig_RegionCode; /* Standard predefined channel settings @@ -246,6 +281,8 @@ typedef struct _meshtastic_Config_DeviceConfig { bool is_managed; /* Disables the triple-press of user button to enable or disable GPS */ bool disable_triple_click; + /* POSIX Timezone definition string from https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv. */ + char tzdef[65]; } meshtastic_Config_DeviceConfig; /* Position Config */ @@ -265,10 +302,7 @@ typedef struct _meshtastic_Config_PositionConfig { or zero for the default of once every 30 seconds or a very large value (maxint) to update only once at boot. */ uint32_t gps_update_interval; - /* How long should we try to get our position during each gps_update_interval attempt? (in seconds) - Or if zero, use the default of 30 seconds. - If we don't get a new gps fix in that time, the gps will be put into sleep until the next gps_update_rate - window. */ + /* Deprecated in favor of using smart / regular broadcast intervals as implicit attempt time */ uint32_t gps_attempt_time; /* Bit field of boolean configuration options for POSITION messages (bitwise OR of PositionFlags) */ @@ -283,40 +317,37 @@ typedef struct _meshtastic_Config_PositionConfig { uint32_t broadcast_smart_minimum_interval_secs; /* (Re)define PIN_GPS_EN for your board. */ uint32_t gps_en_gpio; + /* Set where GPS is enabled, disabled, or not present */ + meshtastic_Config_PositionConfig_GpsMode gps_mode; } meshtastic_Config_PositionConfig; /* Power Config\ See [Power Config](/docs/settings/config/power) for additional power config details. */ typedef struct _meshtastic_Config_PowerConfig { - /* If set, we are powered from a low-current source (i.e. solar), so even if it looks like we have power flowing in - we should try to minimize power consumption as much as possible. - YOU DO NOT NEED TO SET THIS IF YOU'VE set is_router (it is implied in that case). - Advanced Option */ + /* Description: Will sleep everything as much as possible, for the tracker and sensor role this will also include the lora radio. + Don't use this setting if you want to use your device with the phone apps or are using a device without a user button. + Technical Details: Works for ESP32 devices and NRF52 devices in the Sensor or Tracker roles */ bool is_power_saving; - /* If non-zero, the device will fully power off this many seconds after external power is removed. */ + /* Description: If non-zero, the device will fully power off this many seconds after external power is removed. */ uint32_t on_battery_shutdown_after_secs; /* Ratio of voltage divider for battery pin eg. 3.20 (R1=100k, R2=220k) Overrides the ADC_MULTIPLIER defined in variant for battery voltage calculation. - Should be set to floating point value between 2 and 4 - Fixes issues on Heltec v2 */ + https://meshtastic.org/docs/configuration/radio/power/#adc-multiplier-override + Should be set to floating point value between 2 and 6 */ float adc_multiplier_override; - /* Wait Bluetooth Seconds - The number of seconds for to wait before turning off BLE in No Bluetooth states - 0 for default of 1 minute */ + /* Description: The number of seconds for to wait before turning off BLE in No Bluetooth states + Technical Details: ESP32 Only 0 for default of 1 minute */ uint32_t wait_bluetooth_secs; /* Super Deep Sleep Seconds While in Light Sleep if mesh_sds_timeout_secs is exceeded we will lower into super deep sleep for this value (default 1 year) or a button press 0 for default of one year */ uint32_t sds_secs; - /* Light Sleep Seconds - In light sleep the CPU is suspended, LoRa radio is on, BLE is off an GPS is on - ESP32 Only - 0 for default of 300 */ + /* Description: In light sleep the CPU is suspended, LoRa radio is on, BLE is off an GPS is on + Technical Details: ESP32 Only 0 for default of 300 */ uint32_t ls_secs; - /* Minimum Wake Seconds - While in light sleep when we receive packets on the LoRa radio we will wake and handle them and stay awake in no BLE mode for this value - 0 for default of 10 seconds */ + /* Description: While in light sleep when we receive packets on the LoRa radio we will wake and handle them and stay awake in no BLE mode for this value + Technical Details: ESP32 Only 0 for default of 10 seconds */ uint32_t min_wake_secs; /* I2C address of INA_2XX to use for reading device battery voltage */ uint8_t device_battery_ina_address; @@ -448,6 +479,8 @@ typedef struct _meshtastic_Config_LoRaConfig { in ignore_incoming will have packets they send dropped on receive (by router.cpp) */ pb_size_t ignore_incoming_count; uint32_t ignore_incoming[3]; + /* If true, the device will not process any packets received via LoRa that passed via MQTT anywhere on the path towards it. */ + bool ignore_mqtt; } meshtastic_Config_LoRaConfig; typedef struct _meshtastic_Config_BluetoothConfig { @@ -479,17 +512,21 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_Config_DeviceConfig_Role_MIN meshtastic_Config_DeviceConfig_Role_CLIENT -#define _meshtastic_Config_DeviceConfig_Role_MAX meshtastic_Config_DeviceConfig_Role_TAK -#define _meshtastic_Config_DeviceConfig_Role_ARRAYSIZE ((meshtastic_Config_DeviceConfig_Role)(meshtastic_Config_DeviceConfig_Role_TAK+1)) +#define _meshtastic_Config_DeviceConfig_Role_MAX meshtastic_Config_DeviceConfig_Role_TAK_TRACKER +#define _meshtastic_Config_DeviceConfig_Role_ARRAYSIZE ((meshtastic_Config_DeviceConfig_Role)(meshtastic_Config_DeviceConfig_Role_TAK_TRACKER+1)) #define _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN meshtastic_Config_DeviceConfig_RebroadcastMode_ALL -#define _meshtastic_Config_DeviceConfig_RebroadcastMode_MAX meshtastic_Config_DeviceConfig_RebroadcastMode_LOCAL_ONLY -#define _meshtastic_Config_DeviceConfig_RebroadcastMode_ARRAYSIZE ((meshtastic_Config_DeviceConfig_RebroadcastMode)(meshtastic_Config_DeviceConfig_RebroadcastMode_LOCAL_ONLY+1)) +#define _meshtastic_Config_DeviceConfig_RebroadcastMode_MAX meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY +#define _meshtastic_Config_DeviceConfig_RebroadcastMode_ARRAYSIZE ((meshtastic_Config_DeviceConfig_RebroadcastMode)(meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY+1)) #define _meshtastic_Config_PositionConfig_PositionFlags_MIN meshtastic_Config_PositionConfig_PositionFlags_UNSET #define _meshtastic_Config_PositionConfig_PositionFlags_MAX meshtastic_Config_PositionConfig_PositionFlags_SPEED #define _meshtastic_Config_PositionConfig_PositionFlags_ARRAYSIZE ((meshtastic_Config_PositionConfig_PositionFlags)(meshtastic_Config_PositionConfig_PositionFlags_SPEED+1)) +#define _meshtastic_Config_PositionConfig_GpsMode_MIN meshtastic_Config_PositionConfig_GpsMode_DISABLED +#define _meshtastic_Config_PositionConfig_GpsMode_MAX meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT +#define _meshtastic_Config_PositionConfig_GpsMode_ARRAYSIZE ((meshtastic_Config_PositionConfig_GpsMode)(meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT+1)) + #define _meshtastic_Config_NetworkConfig_AddressMode_MIN meshtastic_Config_NetworkConfig_AddressMode_DHCP #define _meshtastic_Config_NetworkConfig_AddressMode_MAX meshtastic_Config_NetworkConfig_AddressMode_STATIC #define _meshtastic_Config_NetworkConfig_AddressMode_ARRAYSIZE ((meshtastic_Config_NetworkConfig_AddressMode)(meshtastic_Config_NetworkConfig_AddressMode_STATIC+1)) @@ -511,8 +548,8 @@ extern "C" { #define _meshtastic_Config_DisplayConfig_DisplayMode_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DisplayMode)(meshtastic_Config_DisplayConfig_DisplayMode_COLOR+1)) #define _meshtastic_Config_LoRaConfig_RegionCode_MIN meshtastic_Config_LoRaConfig_RegionCode_UNSET -#define _meshtastic_Config_LoRaConfig_RegionCode_MAX meshtastic_Config_LoRaConfig_RegionCode_UA_868 -#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_UA_868+1)) +#define _meshtastic_Config_LoRaConfig_RegionCode_MAX meshtastic_Config_LoRaConfig_RegionCode_SG_923 +#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_SG_923+1)) #define _meshtastic_Config_LoRaConfig_ModemPreset_MIN meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST #define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE @@ -526,6 +563,7 @@ extern "C" { #define meshtastic_Config_DeviceConfig_role_ENUMTYPE meshtastic_Config_DeviceConfig_Role #define meshtastic_Config_DeviceConfig_rebroadcast_mode_ENUMTYPE meshtastic_Config_DeviceConfig_RebroadcastMode +#define meshtastic_Config_PositionConfig_gps_mode_ENUMTYPE meshtastic_Config_PositionConfig_GpsMode #define meshtastic_Config_NetworkConfig_address_mode_ENUMTYPE meshtastic_Config_NetworkConfig_AddressMode @@ -544,22 +582,22 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_Config_init_default {0, {meshtastic_Config_DeviceConfig_init_default}} -#define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0} -#define meshtastic_Config_PositionConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, ""} +#define meshtastic_Config_PositionConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} #define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0} -#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}} +#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} #define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}} -#define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0} -#define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, ""} +#define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} #define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, ""} #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0} -#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}} +#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0} #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} /* Field tags (for use in manual encoding/decoding) */ @@ -573,6 +611,7 @@ extern "C" { #define meshtastic_Config_DeviceConfig_double_tap_as_button_press_tag 8 #define meshtastic_Config_DeviceConfig_is_managed_tag 9 #define meshtastic_Config_DeviceConfig_disable_triple_click_tag 10 +#define meshtastic_Config_DeviceConfig_tzdef_tag 11 #define meshtastic_Config_PositionConfig_position_broadcast_secs_tag 1 #define meshtastic_Config_PositionConfig_position_broadcast_smart_enabled_tag 2 #define meshtastic_Config_PositionConfig_fixed_position_tag 3 @@ -585,6 +624,7 @@ extern "C" { #define meshtastic_Config_PositionConfig_broadcast_smart_minimum_distance_tag 10 #define meshtastic_Config_PositionConfig_broadcast_smart_minimum_interval_secs_tag 11 #define meshtastic_Config_PositionConfig_gps_en_gpio_tag 12 +#define meshtastic_Config_PositionConfig_gps_mode_tag 13 #define meshtastic_Config_PowerConfig_is_power_saving_tag 1 #define meshtastic_Config_PowerConfig_on_battery_shutdown_after_secs_tag 2 #define meshtastic_Config_PowerConfig_adc_multiplier_override_tag 3 @@ -631,6 +671,7 @@ extern "C" { #define meshtastic_Config_LoRaConfig_override_frequency_tag 14 #define meshtastic_Config_LoRaConfig_next_hop_routing_tag 15 #define meshtastic_Config_LoRaConfig_ignore_incoming_tag 103 +#define meshtastic_Config_LoRaConfig_ignore_mqtt_tag 104 #define meshtastic_Config_BluetoothConfig_enabled_tag 1 #define meshtastic_Config_BluetoothConfig_mode_tag 2 #define meshtastic_Config_BluetoothConfig_fixed_pin_tag 3 @@ -671,7 +712,8 @@ X(a, STATIC, SINGULAR, UENUM, rebroadcast_mode, 6) \ X(a, STATIC, SINGULAR, UINT32, node_info_broadcast_secs, 7) \ X(a, STATIC, SINGULAR, BOOL, double_tap_as_button_press, 8) \ X(a, STATIC, SINGULAR, BOOL, is_managed, 9) \ -X(a, STATIC, SINGULAR, BOOL, disable_triple_click, 10) +X(a, STATIC, SINGULAR, BOOL, disable_triple_click, 10) \ +X(a, STATIC, SINGULAR, STRING, tzdef, 11) #define meshtastic_Config_DeviceConfig_CALLBACK NULL #define meshtastic_Config_DeviceConfig_DEFAULT NULL @@ -687,7 +729,8 @@ X(a, STATIC, SINGULAR, UINT32, rx_gpio, 8) \ X(a, STATIC, SINGULAR, UINT32, tx_gpio, 9) \ X(a, STATIC, SINGULAR, UINT32, broadcast_smart_minimum_distance, 10) \ X(a, STATIC, SINGULAR, UINT32, broadcast_smart_minimum_interval_secs, 11) \ -X(a, STATIC, SINGULAR, UINT32, gps_en_gpio, 12) +X(a, STATIC, SINGULAR, UINT32, gps_en_gpio, 12) \ +X(a, STATIC, SINGULAR, UENUM, gps_mode, 13) #define meshtastic_Config_PositionConfig_CALLBACK NULL #define meshtastic_Config_PositionConfig_DEFAULT NULL @@ -754,7 +797,8 @@ X(a, STATIC, SINGULAR, BOOL, override_duty_cycle, 12) \ X(a, STATIC, SINGULAR, BOOL, sx126x_rx_boosted_gain, 13) \ X(a, STATIC, SINGULAR, FLOAT, override_frequency, 14) \ X(a, STATIC, SINGULAR, BOOL, next_hop_routing, 15) \ -X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103) +X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103) \ +X(a, STATIC, SINGULAR, BOOL, ignore_mqtt, 104) #define meshtastic_Config_LoRaConfig_CALLBACK NULL #define meshtastic_Config_LoRaConfig_DEFAULT NULL @@ -787,13 +831,14 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg; #define meshtastic_Config_BluetoothConfig_fields &meshtastic_Config_BluetoothConfig_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size #define meshtastic_Config_BluetoothConfig_size 10 -#define meshtastic_Config_DeviceConfig_size 32 +#define meshtastic_Config_DeviceConfig_size 98 #define meshtastic_Config_DisplayConfig_size 28 -#define meshtastic_Config_LoRaConfig_size 79 +#define meshtastic_Config_LoRaConfig_size 82 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20 #define meshtastic_Config_NetworkConfig_size 196 -#define meshtastic_Config_PositionConfig_size 60 +#define meshtastic_Config_PositionConfig_size 62 #define meshtastic_Config_PowerConfig_size 40 #define meshtastic_Config_size 199 diff --git a/src/mesh/generated/meshtastic/connection_status.pb.c b/src/mesh/generated/meshtastic/connection_status.pb.cpp similarity index 95% rename from src/mesh/generated/meshtastic/connection_status.pb.c rename to src/mesh/generated/meshtastic/connection_status.pb.cpp index 0675bc815..fc5a364dd 100644 --- a/src/mesh/generated/meshtastic/connection_status.pb.c +++ b/src/mesh/generated/meshtastic/connection_status.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/connection_status.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/connection_status.pb.h b/src/mesh/generated/meshtastic/connection_status.pb.h index 19ed69455..1c618e4d4 100644 --- a/src/mesh/generated/meshtastic/connection_status.pb.h +++ b/src/mesh/generated/meshtastic/connection_status.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_CONNECTION_STATUS_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_CONNECTION_STATUS_PB_H_INCLUDED @@ -175,6 +175,7 @@ extern const pb_msgdesc_t meshtastic_SerialConnectionStatus_msg; #define meshtastic_SerialConnectionStatus_fields &meshtastic_SerialConnectionStatus_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_CONNECTION_STATUS_PB_H_MAX_SIZE meshtastic_DeviceConnectionStatus_size #define meshtastic_BluetoothConnectionStatus_size 19 #define meshtastic_DeviceConnectionStatus_size 106 #define meshtastic_EthernetConnectionStatus_size 13 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.c b/src/mesh/generated/meshtastic/deviceonly.pb.cpp similarity index 72% rename from src/mesh/generated/meshtastic/deviceonly.pb.c rename to src/mesh/generated/meshtastic/deviceonly.pb.cpp index 82c3fc44c..672192f67 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.c +++ b/src/mesh/generated/meshtastic/deviceonly.pb.cpp @@ -1,18 +1,18 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/deviceonly.pb.h" #if PB_PROTO_HEADER_VERSION != 40 #error Regenerate this file with the current version of nanopb generator. #endif -PB_BIND(meshtastic_DeviceState, meshtastic_DeviceState, 4) +PB_BIND(meshtastic_PositionLite, meshtastic_PositionLite, AUTO) PB_BIND(meshtastic_NodeInfoLite, meshtastic_NodeInfoLite, AUTO) -PB_BIND(meshtastic_PositionLite, meshtastic_PositionLite, AUTO) +PB_BIND(meshtastic_DeviceState, meshtastic_DeviceState, 2) PB_BIND(meshtastic_ChannelFile, meshtastic_ChannelFile, 2) @@ -21,8 +21,5 @@ PB_BIND(meshtastic_ChannelFile, meshtastic_ChannelFile, 2) PB_BIND(meshtastic_OEMStore, meshtastic_OEMStore, 2) -PB_BIND(meshtastic_NodeRemoteHardwarePin, meshtastic_NodeRemoteHardwarePin, AUTO) - - diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index c0e747f81..3c2d98d9a 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -1,21 +1,22 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_INCLUDED #include +#include #include "meshtastic/channel.pb.h" #include "meshtastic/localonly.pb.h" #include "meshtastic/mesh.pb.h" -#include "meshtastic/telemetry.pb.h" #include "meshtastic/module_config.pb.h" +#include "meshtastic/telemetry.pb.h" #if PB_PROTO_HEADER_VERSION != 40 #error Regenerate this file with the current version of nanopb generator. #endif /* Enum definitions */ -/* TODO: REPLACE */ +/* Font sizes for the device screen */ typedef enum _meshtastic_ScreenFonts { /* TODO: REPLACE */ meshtastic_ScreenFonts_FONT_SMALL = 0, @@ -65,10 +66,59 @@ typedef struct _meshtastic_NodeInfoLite { meshtastic_DeviceMetrics device_metrics; /* local channel index we heard that node on. Only populated if its not the default channel. */ uint8_t channel; - /* Last byte of the node number of the node to use as a next hop in order to reach this node. */ + /* True if we witnessed the node over MQTT instead of LoRA transport */ + bool via_mqtt; + /* Number of hops away from us this node is (0 if adjacent) */ + uint8_t hops_away; + /* True if node is in our favorites list + Persists between NodeDB internal clean ups */ + bool is_favorite; + /* Last byte of the node number of the node that should be used as the next hop to reach this node. */ uint8_t next_hop; } meshtastic_NodeInfoLite; +/* This message is never sent over the wire, but it is used for serializing DB + state to flash in the device code + FIXME, since we write this each time we enter deep sleep (and have infinite + flash) it would be better to use some sort of append only data structure for + the receive queue and use the preferences store for the other stuff */ +typedef struct _meshtastic_DeviceState { + /* Read only settings/info about this node */ + bool has_my_node; + meshtastic_MyNodeInfo my_node; + /* My owner info */ + bool has_owner; + meshtastic_User owner; + /* Received packets saved for delivery to the phone */ + pb_size_t receive_queue_count; + meshtastic_MeshPacket receive_queue[1]; + /* We keep the last received text message (only) stored in the device flash, + so we can show it on the screen. + Might be null */ + bool has_rx_text_message; + meshtastic_MeshPacket rx_text_message; + /* A version integer used to invalidate old save files when we make + incompatible changes This integer is set at build time and is private to + NodeDB.cpp in the device code. */ + uint32_t version; + /* Used only during development. + Indicates developer is testing and changes should never be saved to flash. + Deprecated in 2.3.1 */ + bool no_save; + /* Some GPS receivers seem to have bogus settings from the factory, so we always do one factory reset. */ + bool did_gps_reset; + /* We keep the last received waypoint stored in the device flash, + so we can show it on the screen. + Might be null */ + bool has_rx_waypoint; + meshtastic_MeshPacket rx_waypoint; + /* The mesh's nodes with their available gpio pins for RemoteHardware module */ + pb_size_t node_remote_hardware_pins_count; + meshtastic_NodeRemoteHardwarePin node_remote_hardware_pins[12]; + /* New lite version of NodeDB to decrease memory footprint */ + std::vector node_db_lite; +} meshtastic_DeviceState; + /* The on-disk saved channels */ typedef struct _meshtastic_ChannelFile { /* The channels our node knows about */ @@ -105,57 +155,6 @@ typedef struct _meshtastic_OEMStore { meshtastic_LocalModuleConfig oem_local_module_config; } meshtastic_OEMStore; -/* RemoteHardwarePins associated with a node */ -typedef struct _meshtastic_NodeRemoteHardwarePin { - /* The node_num exposing the available gpio pin */ - uint32_t node_num; - /* The the available gpio pin for usage with RemoteHardware module */ - bool has_pin; - meshtastic_RemoteHardwarePin pin; -} meshtastic_NodeRemoteHardwarePin; - -/* This message is never sent over the wire, but it is used for serializing DB - state to flash in the device code - FIXME, since we write this each time we enter deep sleep (and have infinite - flash) it would be better to use some sort of append only data structure for - the receive queue and use the preferences store for the other stuff */ -typedef struct _meshtastic_DeviceState { - /* Read only settings/info about this node */ - bool has_my_node; - meshtastic_MyNodeInfo my_node; - /* My owner info */ - bool has_owner; - meshtastic_User owner; - /* Received packets saved for delivery to the phone */ - pb_size_t receive_queue_count; - meshtastic_MeshPacket receive_queue[1]; - /* We keep the last received text message (only) stored in the device flash, - so we can show it on the screen. - Might be null */ - bool has_rx_text_message; - meshtastic_MeshPacket rx_text_message; - /* A version integer used to invalidate old save files when we make - incompatible changes This integer is set at build time and is private to - NodeDB.cpp in the device code. */ - uint32_t version; - /* Used only during development. - Indicates developer is testing and changes should never be saved to flash. */ - bool no_save; - /* Some GPS receivers seem to have bogus settings from the factory, so we always do one factory reset. */ - bool did_gps_reset; - /* We keep the last received waypoint stored in the device flash, - so we can show it on the screen. - Might be null */ - bool has_rx_waypoint; - meshtastic_MeshPacket rx_waypoint; - /* The mesh's nodes with their available gpio pins for RemoteHardware module */ - pb_size_t node_remote_hardware_pins_count; - meshtastic_NodeRemoteHardwarePin node_remote_hardware_pins[12]; - /* New lite version of NodeDB to decrease memory footprint */ - pb_size_t node_db_lite_count; - meshtastic_NodeInfoLite node_db_lite[100]; -} meshtastic_DeviceState; - #ifdef __cplusplus extern "C" { @@ -166,28 +165,25 @@ extern "C" { #define _meshtastic_ScreenFonts_MAX meshtastic_ScreenFonts_FONT_LARGE #define _meshtastic_ScreenFonts_ARRAYSIZE ((meshtastic_ScreenFonts)(meshtastic_ScreenFonts_FONT_LARGE+1)) - - #define meshtastic_PositionLite_location_source_ENUMTYPE meshtastic_Position_LocSource + + #define meshtastic_OEMStore_oem_font_ENUMTYPE meshtastic_ScreenFonts - /* Initializer values for message structs */ -#define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}, 0, {meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default, meshtastic_NodeInfoLite_init_default}} -#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_User_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0} #define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} +#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_User_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0, 0, 0} +#define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}, {0}} #define meshtastic_ChannelFile_init_default {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0} #define meshtastic_OEMStore_init_default {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default} -#define meshtastic_NodeRemoteHardwarePin_init_default {0, false, meshtastic_RemoteHardwarePin_init_default} -#define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}, 0, {meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero, meshtastic_NodeInfoLite_init_zero}} -#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0} #define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN} +#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0, 0, 0} +#define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}, {0}} #define meshtastic_ChannelFile_init_zero {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0} #define meshtastic_OEMStore_init_zero {0, 0, {0, {0}}, _meshtastic_ScreenFonts_MIN, "", {0, {0}}, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero} -#define meshtastic_NodeRemoteHardwarePin_init_zero {0, false, meshtastic_RemoteHardwarePin_init_zero} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_PositionLite_latitude_i_tag 1 @@ -202,19 +198,10 @@ extern "C" { #define meshtastic_NodeInfoLite_last_heard_tag 5 #define meshtastic_NodeInfoLite_device_metrics_tag 6 #define meshtastic_NodeInfoLite_channel_tag 7 -#define meshtastic_NodeInfoLite_next_hop_tag 8 -#define meshtastic_ChannelFile_channels_tag 1 -#define meshtastic_ChannelFile_version_tag 2 -#define meshtastic_OEMStore_oem_icon_width_tag 1 -#define meshtastic_OEMStore_oem_icon_height_tag 2 -#define meshtastic_OEMStore_oem_icon_bits_tag 3 -#define meshtastic_OEMStore_oem_font_tag 4 -#define meshtastic_OEMStore_oem_text_tag 5 -#define meshtastic_OEMStore_oem_aes_key_tag 6 -#define meshtastic_OEMStore_oem_local_config_tag 7 -#define meshtastic_OEMStore_oem_local_module_config_tag 8 -#define meshtastic_NodeRemoteHardwarePin_node_num_tag 1 -#define meshtastic_NodeRemoteHardwarePin_pin_tag 2 +#define meshtastic_NodeInfoLite_via_mqtt_tag 8 +#define meshtastic_NodeInfoLite_hops_away_tag 9 +#define meshtastic_NodeInfoLite_is_favorite_tag 10 +#define meshtastic_NodeInfoLite_next_hop_tag 11 #define meshtastic_DeviceState_my_node_tag 2 #define meshtastic_DeviceState_owner_tag 3 #define meshtastic_DeviceState_receive_queue_tag 5 @@ -225,8 +212,45 @@ extern "C" { #define meshtastic_DeviceState_rx_waypoint_tag 12 #define meshtastic_DeviceState_node_remote_hardware_pins_tag 13 #define meshtastic_DeviceState_node_db_lite_tag 14 +#define meshtastic_ChannelFile_channels_tag 1 +#define meshtastic_ChannelFile_version_tag 2 +#define meshtastic_OEMStore_oem_icon_width_tag 1 +#define meshtastic_OEMStore_oem_icon_height_tag 2 +#define meshtastic_OEMStore_oem_icon_bits_tag 3 +#define meshtastic_OEMStore_oem_font_tag 4 +#define meshtastic_OEMStore_oem_text_tag 5 +#define meshtastic_OEMStore_oem_aes_key_tag 6 +#define meshtastic_OEMStore_oem_local_config_tag 7 +#define meshtastic_OEMStore_oem_local_module_config_tag 8 /* Struct field encoding specification for nanopb */ +#define meshtastic_PositionLite_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 1) \ +X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 2) \ +X(a, STATIC, SINGULAR, INT32, altitude, 3) \ +X(a, STATIC, SINGULAR, FIXED32, time, 4) \ +X(a, STATIC, SINGULAR, UENUM, location_source, 5) +#define meshtastic_PositionLite_CALLBACK NULL +#define meshtastic_PositionLite_DEFAULT NULL + +#define meshtastic_NodeInfoLite_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, num, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \ +X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \ +X(a, STATIC, SINGULAR, FLOAT, snr, 4) \ +X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \ +X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \ +X(a, STATIC, SINGULAR, UINT32, channel, 7) \ +X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \ +X(a, STATIC, SINGULAR, UINT32, hops_away, 9) \ +X(a, STATIC, SINGULAR, BOOL, is_favorite, 10) \ +X(a, STATIC, SINGULAR, UINT32, next_hop, 11) +#define meshtastic_NodeInfoLite_CALLBACK NULL +#define meshtastic_NodeInfoLite_DEFAULT NULL +#define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_User +#define meshtastic_NodeInfoLite_position_MSGTYPE meshtastic_PositionLite +#define meshtastic_NodeInfoLite_device_metrics_MSGTYPE meshtastic_DeviceMetrics + #define meshtastic_DeviceState_FIELDLIST(X, a) \ X(a, STATIC, OPTIONAL, MESSAGE, my_node, 2) \ X(a, STATIC, OPTIONAL, MESSAGE, owner, 3) \ @@ -237,8 +261,9 @@ X(a, STATIC, SINGULAR, BOOL, no_save, 9) \ X(a, STATIC, SINGULAR, BOOL, did_gps_reset, 11) \ X(a, STATIC, OPTIONAL, MESSAGE, rx_waypoint, 12) \ X(a, STATIC, REPEATED, MESSAGE, node_remote_hardware_pins, 13) \ -X(a, STATIC, REPEATED, MESSAGE, node_db_lite, 14) -#define meshtastic_DeviceState_CALLBACK NULL +X(a, CALLBACK, REPEATED, MESSAGE, node_db_lite, 14) +extern bool meshtastic_DeviceState_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field); +#define meshtastic_DeviceState_CALLBACK meshtastic_DeviceState_callback #define meshtastic_DeviceState_DEFAULT NULL #define meshtastic_DeviceState_my_node_MSGTYPE meshtastic_MyNodeInfo #define meshtastic_DeviceState_owner_MSGTYPE meshtastic_User @@ -248,30 +273,6 @@ X(a, STATIC, REPEATED, MESSAGE, node_db_lite, 14) #define meshtastic_DeviceState_node_remote_hardware_pins_MSGTYPE meshtastic_NodeRemoteHardwarePin #define meshtastic_DeviceState_node_db_lite_MSGTYPE meshtastic_NodeInfoLite -#define meshtastic_NodeInfoLite_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UINT32, num, 1) \ -X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \ -X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \ -X(a, STATIC, SINGULAR, FLOAT, snr, 4) \ -X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \ -X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \ -X(a, STATIC, SINGULAR, UINT32, channel, 7) \ -X(a, STATIC, SINGULAR, UINT32, next_hop, 8) -#define meshtastic_NodeInfoLite_CALLBACK NULL -#define meshtastic_NodeInfoLite_DEFAULT NULL -#define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_User -#define meshtastic_NodeInfoLite_position_MSGTYPE meshtastic_PositionLite -#define meshtastic_NodeInfoLite_device_metrics_MSGTYPE meshtastic_DeviceMetrics - -#define meshtastic_PositionLite_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 1) \ -X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 2) \ -X(a, STATIC, SINGULAR, INT32, altitude, 3) \ -X(a, STATIC, SINGULAR, FIXED32, time, 4) \ -X(a, STATIC, SINGULAR, UENUM, location_source, 5) -#define meshtastic_PositionLite_CALLBACK NULL -#define meshtastic_PositionLite_DEFAULT NULL - #define meshtastic_ChannelFile_FIELDLIST(X, a) \ X(a, STATIC, REPEATED, MESSAGE, channels, 1) \ X(a, STATIC, SINGULAR, UINT32, version, 2) @@ -293,34 +294,25 @@ X(a, STATIC, OPTIONAL, MESSAGE, oem_local_module_config, 8) #define meshtastic_OEMStore_oem_local_config_MSGTYPE meshtastic_LocalConfig #define meshtastic_OEMStore_oem_local_module_config_MSGTYPE meshtastic_LocalModuleConfig -#define meshtastic_NodeRemoteHardwarePin_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UINT32, node_num, 1) \ -X(a, STATIC, OPTIONAL, MESSAGE, pin, 2) -#define meshtastic_NodeRemoteHardwarePin_CALLBACK NULL -#define meshtastic_NodeRemoteHardwarePin_DEFAULT NULL -#define meshtastic_NodeRemoteHardwarePin_pin_MSGTYPE meshtastic_RemoteHardwarePin - -extern const pb_msgdesc_t meshtastic_DeviceState_msg; -extern const pb_msgdesc_t meshtastic_NodeInfoLite_msg; extern const pb_msgdesc_t meshtastic_PositionLite_msg; +extern const pb_msgdesc_t meshtastic_NodeInfoLite_msg; +extern const pb_msgdesc_t meshtastic_DeviceState_msg; extern const pb_msgdesc_t meshtastic_ChannelFile_msg; extern const pb_msgdesc_t meshtastic_OEMStore_msg; -extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ -#define meshtastic_DeviceState_fields &meshtastic_DeviceState_msg -#define meshtastic_NodeInfoLite_fields &meshtastic_NodeInfoLite_msg #define meshtastic_PositionLite_fields &meshtastic_PositionLite_msg +#define meshtastic_NodeInfoLite_fields &meshtastic_NodeInfoLite_msg +#define meshtastic_DeviceState_fields &meshtastic_DeviceState_msg #define meshtastic_ChannelFile_fields &meshtastic_ChannelFile_msg #define meshtastic_OEMStore_fields &meshtastic_OEMStore_msg -#define meshtastic_NodeRemoteHardwarePin_fields &meshtastic_NodeRemoteHardwarePin_msg /* Maximum encoded size of messages (where known) */ -#define meshtastic_ChannelFile_size 638 -#define meshtastic_DeviceState_size 17193 -#define meshtastic_NodeInfoLite_size 154 -#define meshtastic_NodeRemoteHardwarePin_size 29 -#define meshtastic_OEMStore_size 3231 +/* meshtastic_DeviceState_size depends on runtime parameters */ +#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size +#define meshtastic_ChannelFile_size 702 +#define meshtastic_NodeInfoLite_size 169 +#define meshtastic_OEMStore_size 3346 #define meshtastic_PositionLite_size 28 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/localonly.pb.c b/src/mesh/generated/meshtastic/localonly.pb.cpp similarity index 91% rename from src/mesh/generated/meshtastic/localonly.pb.c rename to src/mesh/generated/meshtastic/localonly.pb.cpp index 8fc3f1139..9bc98fb85 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.c +++ b/src/mesh/generated/meshtastic/localonly.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/localonly.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 9d3e63eee..1799f49da 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_INCLUDED @@ -81,6 +81,9 @@ typedef struct _meshtastic_LocalModuleConfig { /* The part of the config that is specific to the Detection Sensor module */ bool has_detection_sensor; meshtastic_ModuleConfig_DetectionSensorConfig detection_sensor; + /* Paxcounter Config */ + bool has_paxcounter; + meshtastic_ModuleConfig_PaxcounterConfig paxcounter; } meshtastic_LocalModuleConfig; @@ -90,9 +93,9 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_LocalConfig_init_default {false, meshtastic_Config_DeviceConfig_init_default, false, meshtastic_Config_PositionConfig_init_default, false, meshtastic_Config_PowerConfig_init_default, false, meshtastic_Config_NetworkConfig_init_default, false, meshtastic_Config_DisplayConfig_init_default, false, meshtastic_Config_LoRaConfig_init_default, false, meshtastic_Config_BluetoothConfig_init_default, 0} -#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default} +#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default} #define meshtastic_LocalConfig_init_zero {false, meshtastic_Config_DeviceConfig_init_zero, false, meshtastic_Config_PositionConfig_init_zero, false, meshtastic_Config_PowerConfig_init_zero, false, meshtastic_Config_NetworkConfig_init_zero, false, meshtastic_Config_DisplayConfig_init_zero, false, meshtastic_Config_LoRaConfig_init_zero, false, meshtastic_Config_BluetoothConfig_init_zero, 0} -#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero} +#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_LocalConfig_device_tag 1 @@ -116,6 +119,7 @@ extern "C" { #define meshtastic_LocalModuleConfig_neighbor_info_tag 11 #define meshtastic_LocalModuleConfig_ambient_lighting_tag 12 #define meshtastic_LocalModuleConfig_detection_sensor_tag 13 +#define meshtastic_LocalModuleConfig_paxcounter_tag 14 /* Struct field encoding specification for nanopb */ #define meshtastic_LocalConfig_FIELDLIST(X, a) \ @@ -150,7 +154,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, audio, 9) \ X(a, STATIC, OPTIONAL, MESSAGE, remote_hardware, 10) \ X(a, STATIC, OPTIONAL, MESSAGE, neighbor_info, 11) \ X(a, STATIC, OPTIONAL, MESSAGE, ambient_lighting, 12) \ -X(a, STATIC, OPTIONAL, MESSAGE, detection_sensor, 13) +X(a, STATIC, OPTIONAL, MESSAGE, detection_sensor, 13) \ +X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) #define meshtastic_LocalModuleConfig_CALLBACK NULL #define meshtastic_LocalModuleConfig_DEFAULT NULL #define meshtastic_LocalModuleConfig_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig @@ -165,6 +170,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, detection_sensor, 13) #define meshtastic_LocalModuleConfig_neighbor_info_MSGTYPE meshtastic_ModuleConfig_NeighborInfoConfig #define meshtastic_LocalModuleConfig_ambient_lighting_MSGTYPE meshtastic_ModuleConfig_AmbientLightingConfig #define meshtastic_LocalModuleConfig_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig +#define meshtastic_LocalModuleConfig_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig extern const pb_msgdesc_t meshtastic_LocalConfig_msg; extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; @@ -174,8 +180,9 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; #define meshtastic_LocalModuleConfig_fields &meshtastic_LocalModuleConfig_msg /* Maximum encoded size of messages (where known) */ -#define meshtastic_LocalConfig_size 465 -#define meshtastic_LocalModuleConfig_size 621 +#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size +#define meshtastic_LocalConfig_size 537 +#define meshtastic_LocalModuleConfig_size 663 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/mesh.pb.c b/src/mesh/generated/meshtastic/mesh.pb.cpp similarity index 88% rename from src/mesh/generated/meshtastic/mesh.pb.c rename to src/mesh/generated/meshtastic/mesh.pb.cpp index 790f8be2d..4907affc6 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.c +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/mesh.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -60,6 +60,12 @@ PB_BIND(meshtastic_Neighbor, meshtastic_Neighbor, AUTO) PB_BIND(meshtastic_DeviceMetadata, meshtastic_DeviceMetadata, AUTO) +PB_BIND(meshtastic_Heartbeat, meshtastic_Heartbeat, AUTO) + + +PB_BIND(meshtastic_NodeRemoteHardwarePin, meshtastic_NodeRemoteHardwarePin, AUTO) + + diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 76168beb1..eebb62661 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_MESH_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_MESH_PB_H_INCLUDED @@ -67,6 +67,16 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_STATION_G1 = 25, /* RAK11310 (RP2040 + SX1262) */ meshtastic_HardwareModel_RAK11310 = 26, + /* Makerfabs SenseLoRA Receiver (RP2040 + RFM96) */ + meshtastic_HardwareModel_SENSELORA_RP2040 = 27, + /* Makerfabs SenseLoRA Industrial Monitor (ESP32-S3 + RFM96) */ + meshtastic_HardwareModel_SENSELORA_S3 = 28, + /* Canary Radio Company - CanaryOne: https://canaryradio.io/products/canaryone */ + meshtastic_HardwareModel_CANARYONE = 29, + /* Waveshare RP2040 LoRa - https://www.waveshare.com/rp2040-lora.htm */ + meshtastic_HardwareModel_RP2040_LORA = 30, + /* B&Q Consulting Station G2: https://wiki.uniteng.com/en/meshtastic/station-g2 */ + meshtastic_HardwareModel_STATION_G2 = 31, /* --------------------------------------------------------------------------- Less common/prototype boards listed here (needs one more byte over the air) --------------------------------------------------------------------------- */ @@ -101,7 +111,8 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_BETAFPV_900_NANO_TX = 46, /* Raspberry Pi Pico (W) with Waveshare SX1262 LoRa Node Module */ meshtastic_HardwareModel_RPI_PICO = 47, - /* Heltec Wireless Tracker with ESP32-S3 CPU, built-in GPS, and TFT */ + /* Heltec Wireless Tracker with ESP32-S3 CPU, built-in GPS, and TFT + Newer V1.1, version is written on the PCB near the display. */ meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER = 48, /* Heltec Wireless Paper with ESP32-S3 CPU and E-Ink display */ meshtastic_HardwareModel_HELTEC_WIRELESS_PAPER = 49, @@ -115,6 +126,28 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_HELTEC_HT62 = 53, /* EBYTE SPI LoRa module and ESP32-S3 */ meshtastic_HardwareModel_EBYTE_ESP32_S3 = 54, + /* Waveshare ESP32-S3-PICO with PICO LoRa HAT and 2.9inch e-Ink */ + meshtastic_HardwareModel_ESP32_S3_PICO = 55, + /* CircuitMess Chatter 2 LLCC68 Lora Module and ESP32 Wroom + Lora module can be swapped out for a Heltec RA-62 which is "almost" pin compatible + with one cut and one jumper Meshtastic works */ + meshtastic_HardwareModel_CHATTER_2 = 56, + /* Heltec Wireless Paper, With ESP32-S3 CPU and E-Ink display + Older "V1.0" Variant, has no "version sticker" + E-Ink model is DEPG0213BNS800 + Tab on the screen protector is RED + Flex connector marking is FPC-7528B */ + meshtastic_HardwareModel_HELTEC_WIRELESS_PAPER_V1_0 = 57, + /* Heltec Wireless Tracker with ESP32-S3 CPU, built-in GPS, and TFT + Older "V1.0" Variant */ + meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V1_0 = 58, + /* unPhone with ESP32-S3, TFT touchscreen, LSM6DS3TR-C accelerometer and gyroscope */ + meshtastic_HardwareModel_UNPHONE = 59, + /* Teledatics TD-LORAC NRF52840 based M.2 LoRA module + Compatible with the TD-WRLS development board */ + meshtastic_HardwareModel_TD_LORAC = 60, + /* CDEBYTE EoRa-S3 board using their own MM modules, clone of LILYGO T3S3 */ + meshtastic_HardwareModel_CDEBYTE_EORA_S3 = 61, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ @@ -356,6 +389,8 @@ typedef struct _meshtastic_Position { /* A sequence number, incremented with each Position message to help detect lost updates if needed */ uint32_t seq_number; + /* Indicates the bits of precision set by the sending node */ + uint32_t precision_bits; } meshtastic_Position; /* Broadcast when a newly powered mesh node wants to find a node num it can use @@ -402,6 +437,8 @@ typedef struct _meshtastic_User { If this user is a licensed operator, set this flag. Also, "long_name" should be their licence number. */ bool is_licensed; + /* Indicates that the user's role in the mesh */ + meshtastic_Config_DeviceConfig_Role role; } meshtastic_User; /* A message used in our Dynamic Source Routing protocol (RFC 4728 based) */ @@ -502,11 +539,9 @@ typedef PB_BYTES_ARRAY_T(256) meshtastic_MeshPacket_encrypted_t; typedef struct _meshtastic_MeshPacket { /* The sending node number. Note: Our crypto implementation uses this field as well. - See [crypto](/docs/overview/encryption) for details. - FIXME - really should be fixed32 instead, this encoding only hurts the ble link though. */ + See [crypto](/docs/overview/encryption) for details. */ uint32_t from; - /* The (immediatSee Priority description for more details.y should be fixed32 instead, this encoding only - hurts the ble link though. */ + /* The (immediate) destination for this packet */ uint32_t to; /* (Usually) If set, this indicates the index in the secondary_channels table that this packet was sent/received on. If unset, packet was on the primary channel. @@ -530,9 +565,7 @@ typedef struct _meshtastic_MeshPacket { needs to be unique for a few minutes (long enough to last for the length of any ACK or the completion of a mesh broadcast flood). Note: Our crypto implementation uses this id as well. - See [crypto](/docs/overview/encryption) for details. - FIXME - really should be fixed32 instead, this encoding only - hurts the ble link though. */ + See [crypto](/docs/overview/encryption) for details. */ uint32_t id; /* The time this message was received by the esp32 (secs since 1970). Note: this field is _never_ sent on the radio link itself (to save space) Times @@ -565,13 +598,15 @@ typedef struct _meshtastic_MeshPacket { int32_t rx_rssi; /* Describe if this message is delayed */ meshtastic_MeshPacket_Delayed delayed; + /* Describes whether this packet passed via MQTT somewhere along the path it currently took. */ + bool via_mqtt; + /* Hop limit with which the original packet started. Sent via LoRa using three bits in the unencrypted header. + When receiving a packet, the difference between hop_start and hop_limit gives how many hops it traveled. */ + uint8_t hop_start; /* Last byte of the node number of the node that should be used as the next hop in routing. */ uint8_t next_hop; /* Last byte of the node number of the node that will relay/relayed this packet. */ uint8_t relay_node; - /* The hop limit setting of the original transmitter. - Useful to determine the amount of hops a packet traveled upon reception, which is this value minus the `hop_limit` with which it arrived. */ - uint32_t original_hop_limit; } meshtastic_MeshPacket; /* The bluetooth to device link: @@ -610,6 +645,13 @@ typedef struct _meshtastic_NodeInfo { meshtastic_DeviceMetrics device_metrics; /* local channel index we heard that node on. Only populated if its not the default channel. */ uint8_t channel; + /* True if we witnessed the node over MQTT instead of LoRA transport */ + bool via_mqtt; + /* Number of hops away from us this node is (0 if adjacent) */ + uint8_t hops_away; + /* True if node is in our favorites list + Persists between NodeDB internal clean ups */ + bool is_favorite; } meshtastic_NodeInfo; /* Unique local debugging info for this node @@ -654,32 +696,6 @@ typedef struct _meshtastic_QueueStatus { uint32_t mesh_packet_id; } meshtastic_QueueStatus; -/* Packets/commands to the radio will be written (reliably) to the toRadio characteristic. - Once the write completes the phone can assume it is handled. */ -typedef struct _meshtastic_ToRadio { - pb_size_t which_payload_variant; - union { - /* Send this packet on the mesh */ - meshtastic_MeshPacket packet; - /* Phone wants radio to send full node db to the phone, This is - typically the first packet sent to the radio when the phone gets a - bluetooth connection. The radio will respond by sending back a - MyNodeInfo, a owner, a radio config and a series of - FromRadio.node_infos, and config_complete - the integer you write into this field will be reported back in the - config_complete_id response this allows clients to never be confused by - a stale old partially sent config. */ - uint32_t want_config_id; - /* Tell API server we are disconnecting now. - This is useful for serial links where there is no hardware/protocol based notification that the client has dropped the link. - (Sending this message is optional for clients) */ - bool disconnect; - meshtastic_XModem xmodemPacket; - /* MQTT Client Proxy Message (for client / phone subscribed to MQTT sending to device) */ - meshtastic_MqttClientProxyMessage mqttClientProxyMessage; - }; -} meshtastic_ToRadio; - typedef PB_BYTES_ARRAY_T(237) meshtastic_Compressed_data_t; /* Compressed message payload */ typedef struct _meshtastic_Compressed { @@ -695,7 +711,7 @@ typedef struct _meshtastic_Neighbor { uint32_t node_id; /* SNR of last heard message */ float snr; - /* Reception time of last message that was sent by this ID. + /* Reception time (in secs since 1970) of last message that was last sent by this ID. Note: this is for local storage only and will not be sent out over the mesh. */ uint32_t last_rx_time; /* Broadcast interval of this neighbor (in seconds). @@ -787,6 +803,49 @@ typedef struct _meshtastic_FromRadio { }; } meshtastic_FromRadio; +/* A heartbeat message is sent to the node from the client to keep the connection alive. + This is currently only needed to keep serial connections alive, but can be used by any PhoneAPI. */ +typedef struct _meshtastic_Heartbeat { + char dummy_field; +} meshtastic_Heartbeat; + +/* Packets/commands to the radio will be written (reliably) to the toRadio characteristic. + Once the write completes the phone can assume it is handled. */ +typedef struct _meshtastic_ToRadio { + pb_size_t which_payload_variant; + union { + /* Send this packet on the mesh */ + meshtastic_MeshPacket packet; + /* Phone wants radio to send full node db to the phone, This is + typically the first packet sent to the radio when the phone gets a + bluetooth connection. The radio will respond by sending back a + MyNodeInfo, a owner, a radio config and a series of + FromRadio.node_infos, and config_complete + the integer you write into this field will be reported back in the + config_complete_id response this allows clients to never be confused by + a stale old partially sent config. */ + uint32_t want_config_id; + /* Tell API server we are disconnecting now. + This is useful for serial links where there is no hardware/protocol based notification that the client has dropped the link. + (Sending this message is optional for clients) */ + bool disconnect; + meshtastic_XModem xmodemPacket; + /* MQTT Client Proxy Message (for client / phone subscribed to MQTT sending to device) */ + meshtastic_MqttClientProxyMessage mqttClientProxyMessage; + /* Heartbeat message (used to keep the device connection awake on serial) */ + meshtastic_Heartbeat heartbeat; + }; +} meshtastic_ToRadio; + +/* RemoteHardwarePins associated with a node */ +typedef struct _meshtastic_NodeRemoteHardwarePin { + /* The node_num exposing the available gpio pin */ + uint32_t node_num; + /* The the available gpio pin for usage with RemoteHardware module */ + bool has_pin; + meshtastic_RemoteHardwarePin pin; +} meshtastic_NodeRemoteHardwarePin; + #ifdef __cplusplus extern "C" { @@ -833,6 +892,7 @@ extern "C" { #define meshtastic_Position_altitude_source_ENUMTYPE meshtastic_Position_AltSource #define meshtastic_User_hw_model_ENUMTYPE meshtastic_HardwareModel +#define meshtastic_User_role_ENUMTYPE meshtastic_Config_DeviceConfig_Role #define meshtastic_Routing_variant_error_reason_ENUMTYPE meshtastic_Routing_Error @@ -859,16 +919,18 @@ extern "C" { #define meshtastic_DeviceMetadata_hw_model_ENUMTYPE meshtastic_HardwareModel + + /* Initializer values for message structs */ -#define meshtastic_Position_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_User_init_default {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0} +#define meshtastic_Position_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_User_init_default {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN} #define meshtastic_RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}} #define meshtastic_Routing_init_default {0, {meshtastic_RouteDiscovery_init_default}} #define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_default {0, 0, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} -#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, 0} -#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0} +#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, 0, 0} +#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, 0, 0} #define meshtastic_MyNodeInfo_init_default {0, 0, 0} #define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_default {0, 0, 0, 0} @@ -878,15 +940,17 @@ extern "C" { #define meshtastic_NeighborInfo_init_default {0, 0, 0, 0, {meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default}} #define meshtastic_Neighbor_init_default {0, 0, 0, 0} #define meshtastic_DeviceMetadata_init_default {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0} -#define meshtastic_Position_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_User_init_zero {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0} +#define meshtastic_Heartbeat_init_default {0} +#define meshtastic_NodeRemoteHardwarePin_init_default {0, false, meshtastic_RemoteHardwarePin_init_default} +#define meshtastic_Position_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_User_init_zero {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN} #define meshtastic_RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}} #define meshtastic_Routing_init_zero {0, {meshtastic_RouteDiscovery_init_zero}} #define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_zero {0, 0, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} -#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, 0} -#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0} +#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, 0, 0} +#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, 0, 0} #define meshtastic_MyNodeInfo_init_zero {0, 0, 0} #define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_zero {0, 0, 0, 0} @@ -896,6 +960,8 @@ extern "C" { #define meshtastic_NeighborInfo_init_zero {0, 0, 0, 0, {meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero}} #define meshtastic_Neighbor_init_zero {0, 0, 0, 0} #define meshtastic_DeviceMetadata_init_zero {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0} +#define meshtastic_Heartbeat_init_zero {0} +#define meshtastic_NodeRemoteHardwarePin_init_zero {0, false, meshtastic_RemoteHardwarePin_init_zero} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_Position_latitude_i_tag 1 @@ -920,12 +986,14 @@ extern "C" { #define meshtastic_Position_sensor_id_tag 20 #define meshtastic_Position_next_update_tag 21 #define meshtastic_Position_seq_number_tag 22 +#define meshtastic_Position_precision_bits_tag 23 #define meshtastic_User_id_tag 1 #define meshtastic_User_long_name_tag 2 #define meshtastic_User_short_name_tag 3 #define meshtastic_User_macaddr_tag 4 #define meshtastic_User_hw_model_tag 5 #define meshtastic_User_is_licensed_tag 6 +#define meshtastic_User_role_tag 7 #define meshtastic_RouteDiscovery_route_tag 1 #define meshtastic_Routing_route_request_tag 1 #define meshtastic_Routing_route_reply_tag 2 @@ -963,9 +1031,10 @@ extern "C" { #define meshtastic_MeshPacket_priority_tag 11 #define meshtastic_MeshPacket_rx_rssi_tag 12 #define meshtastic_MeshPacket_delayed_tag 13 -#define meshtastic_MeshPacket_next_hop_tag 14 -#define meshtastic_MeshPacket_relay_node_tag 15 -#define meshtastic_MeshPacket_original_hop_limit_tag 16 +#define meshtastic_MeshPacket_via_mqtt_tag 14 +#define meshtastic_MeshPacket_hop_start_tag 15 +#define meshtastic_MeshPacket_next_hop_tag 16 +#define meshtastic_MeshPacket_relay_node_tag 17 #define meshtastic_NodeInfo_num_tag 1 #define meshtastic_NodeInfo_user_tag 2 #define meshtastic_NodeInfo_position_tag 3 @@ -973,6 +1042,9 @@ extern "C" { #define meshtastic_NodeInfo_last_heard_tag 5 #define meshtastic_NodeInfo_device_metrics_tag 6 #define meshtastic_NodeInfo_channel_tag 7 +#define meshtastic_NodeInfo_via_mqtt_tag 8 +#define meshtastic_NodeInfo_hops_away_tag 9 +#define meshtastic_NodeInfo_is_favorite_tag 10 #define meshtastic_MyNodeInfo_my_node_num_tag 1 #define meshtastic_MyNodeInfo_reboot_count_tag 8 #define meshtastic_MyNodeInfo_min_app_version_tag 11 @@ -984,11 +1056,6 @@ extern "C" { #define meshtastic_QueueStatus_free_tag 2 #define meshtastic_QueueStatus_maxlen_tag 3 #define meshtastic_QueueStatus_mesh_packet_id_tag 4 -#define meshtastic_ToRadio_packet_tag 1 -#define meshtastic_ToRadio_want_config_id_tag 3 -#define meshtastic_ToRadio_disconnect_tag 4 -#define meshtastic_ToRadio_xmodemPacket_tag 5 -#define meshtastic_ToRadio_mqttClientProxyMessage_tag 6 #define meshtastic_Compressed_portnum_tag 1 #define meshtastic_Compressed_data_tag 2 #define meshtastic_Neighbor_node_id_tag 1 @@ -1023,6 +1090,14 @@ extern "C" { #define meshtastic_FromRadio_xmodemPacket_tag 12 #define meshtastic_FromRadio_metadata_tag 13 #define meshtastic_FromRadio_mqttClientProxyMessage_tag 14 +#define meshtastic_ToRadio_packet_tag 1 +#define meshtastic_ToRadio_want_config_id_tag 3 +#define meshtastic_ToRadio_disconnect_tag 4 +#define meshtastic_ToRadio_xmodemPacket_tag 5 +#define meshtastic_ToRadio_mqttClientProxyMessage_tag 6 +#define meshtastic_ToRadio_heartbeat_tag 7 +#define meshtastic_NodeRemoteHardwarePin_node_num_tag 1 +#define meshtastic_NodeRemoteHardwarePin_pin_tag 2 /* Struct field encoding specification for nanopb */ #define meshtastic_Position_FIELDLIST(X, a) \ @@ -1047,7 +1122,8 @@ X(a, STATIC, SINGULAR, UINT32, fix_type, 18) \ X(a, STATIC, SINGULAR, UINT32, sats_in_view, 19) \ X(a, STATIC, SINGULAR, UINT32, sensor_id, 20) \ X(a, STATIC, SINGULAR, UINT32, next_update, 21) \ -X(a, STATIC, SINGULAR, UINT32, seq_number, 22) +X(a, STATIC, SINGULAR, UINT32, seq_number, 22) \ +X(a, STATIC, SINGULAR, UINT32, precision_bits, 23) #define meshtastic_Position_CALLBACK NULL #define meshtastic_Position_DEFAULT NULL @@ -1057,7 +1133,8 @@ X(a, STATIC, SINGULAR, STRING, long_name, 2) \ X(a, STATIC, SINGULAR, STRING, short_name, 3) \ X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, macaddr, 4) \ X(a, STATIC, SINGULAR, UENUM, hw_model, 5) \ -X(a, STATIC, SINGULAR, BOOL, is_licensed, 6) +X(a, STATIC, SINGULAR, BOOL, is_licensed, 6) \ +X(a, STATIC, SINGULAR, UENUM, role, 7) #define meshtastic_User_CALLBACK NULL #define meshtastic_User_DEFAULT NULL @@ -1121,9 +1198,10 @@ X(a, STATIC, SINGULAR, BOOL, want_ack, 10) \ X(a, STATIC, SINGULAR, UENUM, priority, 11) \ X(a, STATIC, SINGULAR, INT32, rx_rssi, 12) \ X(a, STATIC, SINGULAR, UENUM, delayed, 13) \ -X(a, STATIC, SINGULAR, UINT32, next_hop, 14) \ -X(a, STATIC, SINGULAR, UINT32, relay_node, 15) \ -X(a, STATIC, SINGULAR, UINT32, original_hop_limit, 16) +X(a, STATIC, SINGULAR, BOOL, via_mqtt, 14) \ +X(a, STATIC, SINGULAR, UINT32, hop_start, 15) \ +X(a, STATIC, SINGULAR, UINT32, next_hop, 16) \ +X(a, STATIC, SINGULAR, UINT32, relay_node, 17) #define meshtastic_MeshPacket_CALLBACK NULL #define meshtastic_MeshPacket_DEFAULT NULL #define meshtastic_MeshPacket_payload_variant_decoded_MSGTYPE meshtastic_Data @@ -1135,7 +1213,10 @@ X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \ X(a, STATIC, SINGULAR, FLOAT, snr, 4) \ X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \ X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \ -X(a, STATIC, SINGULAR, UINT32, channel, 7) +X(a, STATIC, SINGULAR, UINT32, channel, 7) \ +X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \ +X(a, STATIC, SINGULAR, UINT32, hops_away, 9) \ +X(a, STATIC, SINGULAR, BOOL, is_favorite, 10) #define meshtastic_NodeInfo_CALLBACK NULL #define meshtastic_NodeInfo_DEFAULT NULL #define meshtastic_NodeInfo_user_MSGTYPE meshtastic_User @@ -1199,12 +1280,14 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,packet,packet), 1) \ X(a, STATIC, ONEOF, UINT32, (payload_variant,want_config_id,want_config_id), 3) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,disconnect,disconnect), 4) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,xmodemPacket,xmodemPacket), 5) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 6) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 6) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,heartbeat,heartbeat), 7) #define meshtastic_ToRadio_CALLBACK NULL #define meshtastic_ToRadio_DEFAULT NULL #define meshtastic_ToRadio_payload_variant_packet_MSGTYPE meshtastic_MeshPacket #define meshtastic_ToRadio_payload_variant_xmodemPacket_MSGTYPE meshtastic_XModem #define meshtastic_ToRadio_payload_variant_mqttClientProxyMessage_MSGTYPE meshtastic_MqttClientProxyMessage +#define meshtastic_ToRadio_payload_variant_heartbeat_MSGTYPE meshtastic_Heartbeat #define meshtastic_Compressed_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UENUM, portnum, 1) \ @@ -1224,7 +1307,7 @@ X(a, STATIC, REPEATED, MESSAGE, neighbors, 4) #define meshtastic_Neighbor_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, node_id, 1) \ X(a, STATIC, SINGULAR, FLOAT, snr, 2) \ -X(a, STATIC, SINGULAR, UINT32, last_rx_time, 3) \ +X(a, STATIC, SINGULAR, FIXED32, last_rx_time, 3) \ X(a, STATIC, SINGULAR, UINT32, node_broadcast_interval_secs, 4) #define meshtastic_Neighbor_CALLBACK NULL #define meshtastic_Neighbor_DEFAULT NULL @@ -1243,6 +1326,18 @@ X(a, STATIC, SINGULAR, BOOL, hasRemoteHardware, 10) #define meshtastic_DeviceMetadata_CALLBACK NULL #define meshtastic_DeviceMetadata_DEFAULT NULL +#define meshtastic_Heartbeat_FIELDLIST(X, a) \ + +#define meshtastic_Heartbeat_CALLBACK NULL +#define meshtastic_Heartbeat_DEFAULT NULL + +#define meshtastic_NodeRemoteHardwarePin_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, node_num, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, pin, 2) +#define meshtastic_NodeRemoteHardwarePin_CALLBACK NULL +#define meshtastic_NodeRemoteHardwarePin_DEFAULT NULL +#define meshtastic_NodeRemoteHardwarePin_pin_MSGTYPE meshtastic_RemoteHardwarePin + extern const pb_msgdesc_t meshtastic_Position_msg; extern const pb_msgdesc_t meshtastic_User_msg; extern const pb_msgdesc_t meshtastic_RouteDiscovery_msg; @@ -1261,6 +1356,8 @@ extern const pb_msgdesc_t meshtastic_Compressed_msg; extern const pb_msgdesc_t meshtastic_NeighborInfo_msg; extern const pb_msgdesc_t meshtastic_Neighbor_msg; extern const pb_msgdesc_t meshtastic_DeviceMetadata_msg; +extern const pb_msgdesc_t meshtastic_Heartbeat_msg; +extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_Position_fields &meshtastic_Position_msg @@ -1281,25 +1378,30 @@ extern const pb_msgdesc_t meshtastic_DeviceMetadata_msg; #define meshtastic_NeighborInfo_fields &meshtastic_NeighborInfo_msg #define meshtastic_Neighbor_fields &meshtastic_Neighbor_msg #define meshtastic_DeviceMetadata_fields &meshtastic_DeviceMetadata_msg +#define meshtastic_Heartbeat_fields &meshtastic_Heartbeat_msg +#define meshtastic_NodeRemoteHardwarePin_fields &meshtastic_NodeRemoteHardwarePin_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_MESH_PB_H_MAX_SIZE meshtastic_FromRadio_size #define meshtastic_Compressed_size 243 #define meshtastic_Data_size 270 #define meshtastic_DeviceMetadata_size 46 #define meshtastic_FromRadio_size 510 +#define meshtastic_Heartbeat_size 0 #define meshtastic_LogRecord_size 81 #define meshtastic_MeshPacket_size 334 #define meshtastic_MqttClientProxyMessage_size 501 #define meshtastic_MyNodeInfo_size 18 -#define meshtastic_NeighborInfo_size 268 -#define meshtastic_Neighbor_size 23 -#define meshtastic_NodeInfo_size 261 -#define meshtastic_Position_size 137 +#define meshtastic_NeighborInfo_size 258 +#define meshtastic_Neighbor_size 22 +#define meshtastic_NodeInfo_size 283 +#define meshtastic_NodeRemoteHardwarePin_size 29 +#define meshtastic_Position_size 144 #define meshtastic_QueueStatus_size 23 #define meshtastic_RouteDiscovery_size 40 #define meshtastic_Routing_size 42 #define meshtastic_ToRadio_size 504 -#define meshtastic_User_size 77 +#define meshtastic_User_size 79 #define meshtastic_Waypoint_size 165 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/module_config.pb.c b/src/mesh/generated/meshtastic/module_config.pb.cpp similarity index 82% rename from src/mesh/generated/meshtastic/module_config.pb.c rename to src/mesh/generated/meshtastic/module_config.pb.cpp index 7318d34f7..88a771d5b 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.c +++ b/src/mesh/generated/meshtastic/module_config.pb.cpp @@ -1,15 +1,18 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/module_config.pb.h" #if PB_PROTO_HEADER_VERSION != 40 #error Regenerate this file with the current version of nanopb generator. #endif -PB_BIND(meshtastic_ModuleConfig, meshtastic_ModuleConfig, AUTO) +PB_BIND(meshtastic_ModuleConfig, meshtastic_ModuleConfig, 2) -PB_BIND(meshtastic_ModuleConfig_MQTTConfig, meshtastic_ModuleConfig_MQTTConfig, AUTO) +PB_BIND(meshtastic_ModuleConfig_MQTTConfig, meshtastic_ModuleConfig_MQTTConfig, 2) + + +PB_BIND(meshtastic_ModuleConfig_MapReportSettings, meshtastic_ModuleConfig_MapReportSettings, AUTO) PB_BIND(meshtastic_ModuleConfig_RemoteHardwareConfig, meshtastic_ModuleConfig_RemoteHardwareConfig, AUTO) @@ -24,6 +27,9 @@ PB_BIND(meshtastic_ModuleConfig_DetectionSensorConfig, meshtastic_ModuleConfig_D PB_BIND(meshtastic_ModuleConfig_AudioConfig, meshtastic_ModuleConfig_AudioConfig, AUTO) +PB_BIND(meshtastic_ModuleConfig_PaxcounterConfig, meshtastic_ModuleConfig_PaxcounterConfig, AUTO) + + PB_BIND(meshtastic_ModuleConfig_SerialConfig, meshtastic_ModuleConfig_SerialConfig, AUTO) diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index b9f43e352..ffda48704 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_MODULE_CONFIG_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_MODULE_CONFIG_PB_H_INCLUDED @@ -84,6 +84,14 @@ typedef enum _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar { } meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar; /* Struct definitions */ +/* Settings for reporting unencrypted information about our node to a map via MQTT */ +typedef struct _meshtastic_ModuleConfig_MapReportSettings { + /* How often we should report our info to the map (in seconds) */ + uint32_t publish_interval_secs; + /* Bits of precision for the location sent (default of 32 is full precision). */ + uint32_t position_precision; +} meshtastic_ModuleConfig_MapReportSettings; + /* MQTT Client Config */ typedef struct _meshtastic_ModuleConfig_MQTTConfig { /* If a meshtastic node is able to reach the internet it will normally attempt to gateway any channels that are marked as @@ -111,9 +119,14 @@ typedef struct _meshtastic_ModuleConfig_MQTTConfig { bool tls_enabled; /* The root topic to use for MQTT messages. Default is "msh". This is useful if you want to use a single MQTT server for multiple meshtastic networks and separate them via ACLs */ - char root[16]; + char root[32]; /* If true, we can use the connected phone / client to proxy messages to MQTT instead of a direct connection */ bool proxy_to_client_enabled; + /* If true, we will periodically report unencrypted information about our node to a map via MQTT */ + bool map_reporting_enabled; + /* Settings for reporting information about our node to a map via MQTT */ + bool has_map_report_settings; + meshtastic_ModuleConfig_MapReportSettings map_report_settings; } meshtastic_ModuleConfig_MQTTConfig; /* NeighborInfoModule Config */ @@ -170,6 +183,13 @@ typedef struct _meshtastic_ModuleConfig_AudioConfig { uint8_t i2s_sck; } meshtastic_ModuleConfig_AudioConfig; +/* Config for the Paxcounter Module */ +typedef struct _meshtastic_ModuleConfig_PaxcounterConfig { + /* Enable the Paxcounter Module */ + bool enabled; + uint32_t paxcounter_update_interval; +} meshtastic_ModuleConfig_PaxcounterConfig; + /* Serial Config */ typedef struct _meshtastic_ModuleConfig_SerialConfig { /* Preferences for the SerialModule */ @@ -384,6 +404,8 @@ typedef struct _meshtastic_ModuleConfig { meshtastic_ModuleConfig_AmbientLightingConfig ambient_lighting; /* TODO: REPLACE */ meshtastic_ModuleConfig_DetectionSensorConfig detection_sensor; + /* TODO: REPLACE */ + meshtastic_ModuleConfig_PaxcounterConfig paxcounter; } payload_variant; } meshtastic_ModuleConfig; @@ -418,8 +440,10 @@ extern "C" { + #define meshtastic_ModuleConfig_AudioConfig_bitrate_ENUMTYPE meshtastic_ModuleConfig_AudioConfig_Audio_Baud + #define meshtastic_ModuleConfig_SerialConfig_baud_ENUMTYPE meshtastic_ModuleConfig_SerialConfig_Serial_Baud #define meshtastic_ModuleConfig_SerialConfig_mode_ENUMTYPE meshtastic_ModuleConfig_SerialConfig_Serial_Mode @@ -437,11 +461,13 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_ModuleConfig_init_default {0, {meshtastic_ModuleConfig_MQTTConfig_init_default}} -#define meshtastic_ModuleConfig_MQTTConfig_init_default {0, "", "", "", 0, 0, 0, "", 0} +#define meshtastic_ModuleConfig_MQTTConfig_init_default {0, "", "", "", 0, 0, 0, "", 0, 0, false, meshtastic_ModuleConfig_MapReportSettings_init_default} +#define meshtastic_ModuleConfig_MapReportSettings_init_default {0, 0} #define meshtastic_ModuleConfig_RemoteHardwareConfig_init_default {0, 0, 0, {meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default}} #define meshtastic_ModuleConfig_NeighborInfoConfig_init_default {0, 0} #define meshtastic_ModuleConfig_DetectionSensorConfig_init_default {0, 0, 0, 0, "", 0, 0, 0} #define meshtastic_ModuleConfig_AudioConfig_init_default {0, 0, _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} +#define meshtastic_ModuleConfig_PaxcounterConfig_init_default {0, 0} #define meshtastic_ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0} #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0} @@ -451,11 +477,13 @@ extern "C" { #define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0} #define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN} #define meshtastic_ModuleConfig_init_zero {0, {meshtastic_ModuleConfig_MQTTConfig_init_zero}} -#define meshtastic_ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0, 0, "", 0} +#define meshtastic_ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0, 0, "", 0, 0, false, meshtastic_ModuleConfig_MapReportSettings_init_zero} +#define meshtastic_ModuleConfig_MapReportSettings_init_zero {0, 0} #define meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero {0, 0, 0, {meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero}} #define meshtastic_ModuleConfig_NeighborInfoConfig_init_zero {0, 0} #define meshtastic_ModuleConfig_DetectionSensorConfig_init_zero {0, 0, 0, 0, "", 0, 0, 0} #define meshtastic_ModuleConfig_AudioConfig_init_zero {0, 0, _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} +#define meshtastic_ModuleConfig_PaxcounterConfig_init_zero {0, 0} #define meshtastic_ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0} #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0} @@ -466,6 +494,8 @@ extern "C" { #define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN} /* Field tags (for use in manual encoding/decoding) */ +#define meshtastic_ModuleConfig_MapReportSettings_publish_interval_secs_tag 1 +#define meshtastic_ModuleConfig_MapReportSettings_position_precision_tag 2 #define meshtastic_ModuleConfig_MQTTConfig_enabled_tag 1 #define meshtastic_ModuleConfig_MQTTConfig_address_tag 2 #define meshtastic_ModuleConfig_MQTTConfig_username_tag 3 @@ -475,6 +505,8 @@ extern "C" { #define meshtastic_ModuleConfig_MQTTConfig_tls_enabled_tag 7 #define meshtastic_ModuleConfig_MQTTConfig_root_tag 8 #define meshtastic_ModuleConfig_MQTTConfig_proxy_to_client_enabled_tag 9 +#define meshtastic_ModuleConfig_MQTTConfig_map_reporting_enabled_tag 10 +#define meshtastic_ModuleConfig_MQTTConfig_map_report_settings_tag 11 #define meshtastic_ModuleConfig_NeighborInfoConfig_enabled_tag 1 #define meshtastic_ModuleConfig_NeighborInfoConfig_update_interval_tag 2 #define meshtastic_ModuleConfig_DetectionSensorConfig_enabled_tag 1 @@ -492,6 +524,8 @@ extern "C" { #define meshtastic_ModuleConfig_AudioConfig_i2s_sd_tag 5 #define meshtastic_ModuleConfig_AudioConfig_i2s_din_tag 6 #define meshtastic_ModuleConfig_AudioConfig_i2s_sck_tag 7 +#define meshtastic_ModuleConfig_PaxcounterConfig_enabled_tag 1 +#define meshtastic_ModuleConfig_PaxcounterConfig_paxcounter_update_interval_tag 2 #define meshtastic_ModuleConfig_SerialConfig_enabled_tag 1 #define meshtastic_ModuleConfig_SerialConfig_echo_tag 2 #define meshtastic_ModuleConfig_SerialConfig_rxd_tag 3 @@ -567,6 +601,7 @@ extern "C" { #define meshtastic_ModuleConfig_neighbor_info_tag 10 #define meshtastic_ModuleConfig_ambient_lighting_tag 11 #define meshtastic_ModuleConfig_detection_sensor_tag 12 +#define meshtastic_ModuleConfig_paxcounter_tag 13 /* Struct field encoding specification for nanopb */ #define meshtastic_ModuleConfig_FIELDLIST(X, a) \ @@ -581,7 +616,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,audio,payload_variant.audio) X(a, STATIC, ONEOF, MESSAGE, (payload_variant,remote_hardware,payload_variant.remote_hardware), 9) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,neighbor_info,payload_variant.neighbor_info), 10) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ambient_lighting,payload_variant.ambient_lighting), 11) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,detection_sensor,payload_variant.detection_sensor), 12) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,detection_sensor,payload_variant.detection_sensor), 12) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.paxcounter), 13) #define meshtastic_ModuleConfig_CALLBACK NULL #define meshtastic_ModuleConfig_DEFAULT NULL #define meshtastic_ModuleConfig_payload_variant_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig @@ -596,6 +632,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,detection_sensor,payload_var #define meshtastic_ModuleConfig_payload_variant_neighbor_info_MSGTYPE meshtastic_ModuleConfig_NeighborInfoConfig #define meshtastic_ModuleConfig_payload_variant_ambient_lighting_MSGTYPE meshtastic_ModuleConfig_AmbientLightingConfig #define meshtastic_ModuleConfig_payload_variant_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig +#define meshtastic_ModuleConfig_payload_variant_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig #define meshtastic_ModuleConfig_MQTTConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ @@ -606,9 +643,18 @@ X(a, STATIC, SINGULAR, BOOL, encryption_enabled, 5) \ X(a, STATIC, SINGULAR, BOOL, json_enabled, 6) \ X(a, STATIC, SINGULAR, BOOL, tls_enabled, 7) \ X(a, STATIC, SINGULAR, STRING, root, 8) \ -X(a, STATIC, SINGULAR, BOOL, proxy_to_client_enabled, 9) +X(a, STATIC, SINGULAR, BOOL, proxy_to_client_enabled, 9) \ +X(a, STATIC, SINGULAR, BOOL, map_reporting_enabled, 10) \ +X(a, STATIC, OPTIONAL, MESSAGE, map_report_settings, 11) #define meshtastic_ModuleConfig_MQTTConfig_CALLBACK NULL #define meshtastic_ModuleConfig_MQTTConfig_DEFAULT NULL +#define meshtastic_ModuleConfig_MQTTConfig_map_report_settings_MSGTYPE meshtastic_ModuleConfig_MapReportSettings + +#define meshtastic_ModuleConfig_MapReportSettings_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, publish_interval_secs, 1) \ +X(a, STATIC, SINGULAR, UINT32, position_precision, 2) +#define meshtastic_ModuleConfig_MapReportSettings_CALLBACK NULL +#define meshtastic_ModuleConfig_MapReportSettings_DEFAULT NULL #define meshtastic_ModuleConfig_RemoteHardwareConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ @@ -647,6 +693,12 @@ X(a, STATIC, SINGULAR, UINT32, i2s_sck, 7) #define meshtastic_ModuleConfig_AudioConfig_CALLBACK NULL #define meshtastic_ModuleConfig_AudioConfig_DEFAULT NULL +#define meshtastic_ModuleConfig_PaxcounterConfig_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ +X(a, STATIC, SINGULAR, UINT32, paxcounter_update_interval, 2) +#define meshtastic_ModuleConfig_PaxcounterConfig_CALLBACK NULL +#define meshtastic_ModuleConfig_PaxcounterConfig_DEFAULT NULL + #define meshtastic_ModuleConfig_SerialConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ X(a, STATIC, SINGULAR, BOOL, echo, 2) \ @@ -741,10 +793,12 @@ X(a, STATIC, SINGULAR, UENUM, type, 3) extern const pb_msgdesc_t meshtastic_ModuleConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_MQTTConfig_msg; +extern const pb_msgdesc_t meshtastic_ModuleConfig_MapReportSettings_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_RemoteHardwareConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_NeighborInfoConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_DetectionSensorConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_AudioConfig_msg; +extern const pb_msgdesc_t meshtastic_ModuleConfig_PaxcounterConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_SerialConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_ExternalNotificationConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_StoreForwardConfig_msg; @@ -757,10 +811,12 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_ModuleConfig_fields &meshtastic_ModuleConfig_msg #define meshtastic_ModuleConfig_MQTTConfig_fields &meshtastic_ModuleConfig_MQTTConfig_msg +#define meshtastic_ModuleConfig_MapReportSettings_fields &meshtastic_ModuleConfig_MapReportSettings_msg #define meshtastic_ModuleConfig_RemoteHardwareConfig_fields &meshtastic_ModuleConfig_RemoteHardwareConfig_msg #define meshtastic_ModuleConfig_NeighborInfoConfig_fields &meshtastic_ModuleConfig_NeighborInfoConfig_msg #define meshtastic_ModuleConfig_DetectionSensorConfig_fields &meshtastic_ModuleConfig_DetectionSensorConfig_msg #define meshtastic_ModuleConfig_AudioConfig_fields &meshtastic_ModuleConfig_AudioConfig_msg +#define meshtastic_ModuleConfig_PaxcounterConfig_fields &meshtastic_ModuleConfig_PaxcounterConfig_msg #define meshtastic_ModuleConfig_SerialConfig_fields &meshtastic_ModuleConfig_SerialConfig_msg #define meshtastic_ModuleConfig_ExternalNotificationConfig_fields &meshtastic_ModuleConfig_ExternalNotificationConfig_msg #define meshtastic_ModuleConfig_StoreForwardConfig_fields &meshtastic_ModuleConfig_StoreForwardConfig_msg @@ -771,19 +827,22 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_RemoteHardwarePin_fields &meshtastic_RemoteHardwarePin_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_MODULE_CONFIG_PB_H_MAX_SIZE meshtastic_ModuleConfig_size #define meshtastic_ModuleConfig_AmbientLightingConfig_size 14 #define meshtastic_ModuleConfig_AudioConfig_size 19 #define meshtastic_ModuleConfig_CannedMessageConfig_size 49 #define meshtastic_ModuleConfig_DetectionSensorConfig_size 44 #define meshtastic_ModuleConfig_ExternalNotificationConfig_size 42 -#define meshtastic_ModuleConfig_MQTTConfig_size 222 +#define meshtastic_ModuleConfig_MQTTConfig_size 254 +#define meshtastic_ModuleConfig_MapReportSettings_size 12 #define meshtastic_ModuleConfig_NeighborInfoConfig_size 8 +#define meshtastic_ModuleConfig_PaxcounterConfig_size 8 #define meshtastic_ModuleConfig_RangeTestConfig_size 10 #define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96 #define meshtastic_ModuleConfig_SerialConfig_size 28 #define meshtastic_ModuleConfig_StoreForwardConfig_size 22 #define meshtastic_ModuleConfig_TelemetryConfig_size 36 -#define meshtastic_ModuleConfig_size 225 +#define meshtastic_ModuleConfig_size 257 #define meshtastic_RemoteHardwarePin_size 21 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/mqtt.pb.c b/src/mesh/generated/meshtastic/mqtt.pb.cpp similarity index 75% rename from src/mesh/generated/meshtastic/mqtt.pb.c rename to src/mesh/generated/meshtastic/mqtt.pb.cpp index 3046e6109..f00dd823b 100644 --- a/src/mesh/generated/meshtastic/mqtt.pb.c +++ b/src/mesh/generated/meshtastic/mqtt.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/mqtt.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -9,4 +9,7 @@ PB_BIND(meshtastic_ServiceEnvelope, meshtastic_ServiceEnvelope, AUTO) +PB_BIND(meshtastic_MapReport, meshtastic_MapReport, AUTO) + + diff --git a/src/mesh/generated/meshtastic/mqtt.pb.h b/src/mesh/generated/meshtastic/mqtt.pb.h index 12e83c724..8ec9f98c3 100644 --- a/src/mesh/generated/meshtastic/mqtt.pb.h +++ b/src/mesh/generated/meshtastic/mqtt.pb.h @@ -1,9 +1,10 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_MQTT_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_MQTT_PB_H_INCLUDED #include +#include "meshtastic/config.pb.h" #include "meshtastic/mesh.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -23,6 +24,38 @@ typedef struct _meshtastic_ServiceEnvelope { char *gateway_id; } meshtastic_ServiceEnvelope; +/* Information about a node intended to be reported unencrypted to a map using MQTT. */ +typedef struct _meshtastic_MapReport { + /* A full name for this user, i.e. "Kevin Hester" */ + char long_name[40]; + /* A VERY short name, ideally two characters. + Suitable for a tiny OLED screen */ + char short_name[5]; + /* Role of the node that applies specific settings for a particular use-case */ + meshtastic_Config_DeviceConfig_Role role; + /* Hardware model of the node, i.e. T-Beam, Heltec V3, etc... */ + meshtastic_HardwareModel hw_model; + /* Device firmware version string */ + char firmware_version[18]; + /* The region code for the radio (US, CN, EU433, etc...) */ + meshtastic_Config_LoRaConfig_RegionCode region; + /* Modem preset used by the radio (LongFast, MediumSlow, etc...) */ + meshtastic_Config_LoRaConfig_ModemPreset modem_preset; + /* Whether the node has a channel with default PSK and name (LongFast, MediumSlow, etc...) + and it uses the default frequency slot given the region and modem preset. */ + bool has_default_channel; + /* Latitude: multiply by 1e-7 to get degrees in floating point */ + int32_t latitude_i; + /* Longitude: multiply by 1e-7 to get degrees in floating point */ + int32_t longitude_i; + /* Altitude in meters above MSL */ + int32_t altitude; + /* Indicates the bits of precision for latitude and longitude set by the sending node */ + uint32_t position_precision; + /* Number of online nodes (heard in the last 2 hours) this node has in its list that were received locally (not via MQTT) */ + uint16_t num_online_local_nodes; +} meshtastic_MapReport; + #ifdef __cplusplus extern "C" { @@ -30,12 +63,27 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_ServiceEnvelope_init_default {NULL, NULL, NULL} +#define meshtastic_MapReport_init_default {"", "", _meshtastic_Config_DeviceConfig_Role_MIN, _meshtastic_HardwareModel_MIN, "", _meshtastic_Config_LoRaConfig_RegionCode_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, 0, 0} #define meshtastic_ServiceEnvelope_init_zero {NULL, NULL, NULL} +#define meshtastic_MapReport_init_zero {"", "", _meshtastic_Config_DeviceConfig_Role_MIN, _meshtastic_HardwareModel_MIN, "", _meshtastic_Config_LoRaConfig_RegionCode_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, 0, 0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_ServiceEnvelope_packet_tag 1 #define meshtastic_ServiceEnvelope_channel_id_tag 2 #define meshtastic_ServiceEnvelope_gateway_id_tag 3 +#define meshtastic_MapReport_long_name_tag 1 +#define meshtastic_MapReport_short_name_tag 2 +#define meshtastic_MapReport_role_tag 3 +#define meshtastic_MapReport_hw_model_tag 4 +#define meshtastic_MapReport_firmware_version_tag 5 +#define meshtastic_MapReport_region_tag 6 +#define meshtastic_MapReport_modem_preset_tag 7 +#define meshtastic_MapReport_has_default_channel_tag 8 +#define meshtastic_MapReport_latitude_i_tag 9 +#define meshtastic_MapReport_longitude_i_tag 10 +#define meshtastic_MapReport_altitude_tag 11 +#define meshtastic_MapReport_position_precision_tag 12 +#define meshtastic_MapReport_num_online_local_nodes_tag 13 /* Struct field encoding specification for nanopb */ #define meshtastic_ServiceEnvelope_FIELDLIST(X, a) \ @@ -46,13 +94,34 @@ X(a, POINTER, SINGULAR, STRING, gateway_id, 3) #define meshtastic_ServiceEnvelope_DEFAULT NULL #define meshtastic_ServiceEnvelope_packet_MSGTYPE meshtastic_MeshPacket +#define meshtastic_MapReport_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, long_name, 1) \ +X(a, STATIC, SINGULAR, STRING, short_name, 2) \ +X(a, STATIC, SINGULAR, UENUM, role, 3) \ +X(a, STATIC, SINGULAR, UENUM, hw_model, 4) \ +X(a, STATIC, SINGULAR, STRING, firmware_version, 5) \ +X(a, STATIC, SINGULAR, UENUM, region, 6) \ +X(a, STATIC, SINGULAR, UENUM, modem_preset, 7) \ +X(a, STATIC, SINGULAR, BOOL, has_default_channel, 8) \ +X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 9) \ +X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 10) \ +X(a, STATIC, SINGULAR, INT32, altitude, 11) \ +X(a, STATIC, SINGULAR, UINT32, position_precision, 12) \ +X(a, STATIC, SINGULAR, UINT32, num_online_local_nodes, 13) +#define meshtastic_MapReport_CALLBACK NULL +#define meshtastic_MapReport_DEFAULT NULL + extern const pb_msgdesc_t meshtastic_ServiceEnvelope_msg; +extern const pb_msgdesc_t meshtastic_MapReport_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_ServiceEnvelope_fields &meshtastic_ServiceEnvelope_msg +#define meshtastic_MapReport_fields &meshtastic_MapReport_msg /* Maximum encoded size of messages (where known) */ /* meshtastic_ServiceEnvelope_size depends on runtime parameters */ +#define MESHTASTIC_MESHTASTIC_MQTT_PB_H_MAX_SIZE meshtastic_MapReport_size +#define meshtastic_MapReport_size 108 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/paxcount.pb.cpp b/src/mesh/generated/meshtastic/paxcount.pb.cpp new file mode 100644 index 000000000..67f07a31b --- /dev/null +++ b/src/mesh/generated/meshtastic/paxcount.pb.cpp @@ -0,0 +1,12 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.8 */ + +#include "meshtastic/paxcount.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(meshtastic_Paxcount, meshtastic_Paxcount, AUTO) + + + diff --git a/src/mesh/generated/meshtastic/paxcount.pb.h b/src/mesh/generated/meshtastic/paxcount.pb.h new file mode 100644 index 000000000..09377d833 --- /dev/null +++ b/src/mesh/generated/meshtastic/paxcount.pb.h @@ -0,0 +1,58 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.8 */ + +#ifndef PB_MESHTASTIC_MESHTASTIC_PAXCOUNT_PB_H_INCLUDED +#define PB_MESHTASTIC_MESHTASTIC_PAXCOUNT_PB_H_INCLUDED +#include + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Struct definitions */ +/* TODO: REPLACE */ +typedef struct _meshtastic_Paxcount { + /* seen Wifi devices */ + uint32_t wifi; + /* Seen BLE devices */ + uint32_t ble; + /* Uptime in seconds */ + uint32_t uptime; +} meshtastic_Paxcount; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initializer values for message structs */ +#define meshtastic_Paxcount_init_default {0, 0, 0} +#define meshtastic_Paxcount_init_zero {0, 0, 0} + +/* Field tags (for use in manual encoding/decoding) */ +#define meshtastic_Paxcount_wifi_tag 1 +#define meshtastic_Paxcount_ble_tag 2 +#define meshtastic_Paxcount_uptime_tag 3 + +/* Struct field encoding specification for nanopb */ +#define meshtastic_Paxcount_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, wifi, 1) \ +X(a, STATIC, SINGULAR, UINT32, ble, 2) \ +X(a, STATIC, SINGULAR, UINT32, uptime, 3) +#define meshtastic_Paxcount_CALLBACK NULL +#define meshtastic_Paxcount_DEFAULT NULL + +extern const pb_msgdesc_t meshtastic_Paxcount_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define meshtastic_Paxcount_fields &meshtastic_Paxcount_msg + +/* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_PAXCOUNT_PB_H_MAX_SIZE meshtastic_Paxcount_size +#define meshtastic_Paxcount_size 18 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/mesh/generated/meshtastic/portnums.pb.c b/src/mesh/generated/meshtastic/portnums.pb.cpp similarity index 86% rename from src/mesh/generated/meshtastic/portnums.pb.c rename to src/mesh/generated/meshtastic/portnums.pb.cpp index dd0d00e20..8f32c0851 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.c +++ b/src/mesh/generated/meshtastic/portnums.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/portnums.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index d354b7a42..233e8d653 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_PORTNUMS_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_PORTNUMS_PB_H_INCLUDED @@ -38,19 +38,19 @@ typedef enum _meshtastic_PortNum { ENCODING: Protobuf */ meshtastic_PortNum_REMOTE_HARDWARE_APP = 2, /* The built-in position messaging app. - Payload is a [Position](/docs/developers/protobufs/api#position) message + Payload is a Position message. ENCODING: Protobuf */ meshtastic_PortNum_POSITION_APP = 3, /* The built-in user info app. - Payload is a [User](/docs/developers/protobufs/api#user) message + Payload is a User message. ENCODING: Protobuf */ meshtastic_PortNum_NODEINFO_APP = 4, /* Protocol control packets for mesh protocol use. - Payload is a [Routing](/docs/developers/protobufs/api#routing) message + Payload is a Routing message. ENCODING: Protobuf */ meshtastic_PortNum_ROUTING_APP = 5, /* Admin control packets. - Payload is a [AdminMessage](/docs/developers/protobufs/api#adminmessage) message + Payload is a AdminMessage message. ENCODING: Protobuf */ meshtastic_PortNum_ADMIN_APP = 6, /* Compressed TEXT_MESSAGE payloads. @@ -60,7 +60,7 @@ typedef enum _meshtastic_PortNum { any incoming TEXT_MESSAGE_COMPRESSED_APP payload and convert to TEXT_MESSAGE_APP. */ meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP = 7, /* Waypoint payloads. - Payload is a [Waypoint](/docs/developers/protobufs/api#waypoint) message + Payload is a Waypoint message. ENCODING: Protobuf */ meshtastic_PortNum_WAYPOINT_APP = 8, /* Audio Payloads. @@ -69,7 +69,8 @@ typedef enum _meshtastic_PortNum { NOTE: audio frames contain a 3 byte header (0xc0 0xde 0xc2) and a one byte marker for the decompressed bitrate. This marker comes from the 'moduleConfig.audio.bitrate' enum minus one. */ meshtastic_PortNum_AUDIO_APP = 9, - /* Same as Text Message but originating from Detection Sensor Module. */ + /* Same as Text Message but originating from Detection Sensor Module. + NOTE: This portnum traffic is not sent to the public MQTT starting at firmware version 2.2.9 */ meshtastic_PortNum_DETECTION_SENSOR_APP = 10, /* Provides a 'ping' service that replies to any packet it receives. Also serves as a small example module. @@ -78,6 +79,9 @@ typedef enum _meshtastic_PortNum { /* Used for the python IP tunnel feature ENCODING: IP Packet. Handled by the python API, firmware ignores this one and pases on. */ meshtastic_PortNum_IP_TUNNEL_APP = 33, + /* Paxcounter lib included in the firmware + ENCODING: protobuf */ + meshtastic_PortNum_PAXCOUNTER_APP = 34, /* Provides a hardware serial interface to send and receive from the Meshtastic network. Connect to the RX/TX pins of a device with 38400 8N1. Packets received from the Meshtastic network is forwarded to the RX pin while sending a packet to TX will go out to the Mesh network. @@ -90,7 +94,8 @@ typedef enum _meshtastic_PortNum { ENCODING: Protobuf */ meshtastic_PortNum_STORE_FORWARD_APP = 65, /* Optional port for messages for the range test module. - ENCODING: ASCII Plaintext */ + ENCODING: ASCII Plaintext + NOTE: This portnum traffic is not sent to the public MQTT starting at firmware version 2.2.9 */ meshtastic_PortNum_RANGE_TEST_APP = 66, /* Provides a format to send and receive telemetry data from the Meshtastic network. Maintained by Charles Crossan (crossan007) : crossan007@gmail.com @@ -114,6 +119,11 @@ typedef enum _meshtastic_PortNum { /* Aggregates edge info for the network by sending out a list of each node's neighbors ENCODING: Protobuf */ meshtastic_PortNum_NEIGHBORINFO_APP = 71, + /* ATAK Plugin + Portnum for payloads from the official Meshtastic ATAK plugin */ + meshtastic_PortNum_ATAK_PLUGIN = 72, + /* Provides unencrypted information about a node for consumption by a map via MQTT */ + meshtastic_PortNum_MAP_REPORT_APP = 73, /* Private applications should use portnums >= 256. To simplify initial development and testing you can use "PRIVATE_APP" in your code without needing to rebuild protobuf files (via [regen-protos.sh](https://github.com/meshtastic/firmware/blob/master/bin/regen-protos.sh)) */ diff --git a/src/mesh/generated/meshtastic/remote_hardware.pb.c b/src/mesh/generated/meshtastic/remote_hardware.pb.cpp similarity index 90% rename from src/mesh/generated/meshtastic/remote_hardware.pb.c rename to src/mesh/generated/meshtastic/remote_hardware.pb.cpp index f368ec1ef..4a23698b2 100644 --- a/src/mesh/generated/meshtastic/remote_hardware.pb.c +++ b/src/mesh/generated/meshtastic/remote_hardware.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/remote_hardware.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/remote_hardware.pb.h b/src/mesh/generated/meshtastic/remote_hardware.pb.h index 26df97616..936034b62 100644 --- a/src/mesh/generated/meshtastic/remote_hardware.pb.h +++ b/src/mesh/generated/meshtastic/remote_hardware.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_REMOTE_HARDWARE_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_REMOTE_HARDWARE_PB_H_INCLUDED @@ -84,6 +84,7 @@ extern const pb_msgdesc_t meshtastic_HardwareMessage_msg; #define meshtastic_HardwareMessage_fields &meshtastic_HardwareMessage_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_REMOTE_HARDWARE_PB_H_MAX_SIZE meshtastic_HardwareMessage_size #define meshtastic_HardwareMessage_size 24 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/rtttl.pb.c b/src/mesh/generated/meshtastic/rtttl.pb.cpp similarity index 89% rename from src/mesh/generated/meshtastic/rtttl.pb.c rename to src/mesh/generated/meshtastic/rtttl.pb.cpp index 685bbde45..8367fdbce 100644 --- a/src/mesh/generated/meshtastic/rtttl.pb.c +++ b/src/mesh/generated/meshtastic/rtttl.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/rtttl.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/rtttl.pb.h b/src/mesh/generated/meshtastic/rtttl.pb.h index aa55d0b7d..452b0cf4b 100644 --- a/src/mesh/generated/meshtastic/rtttl.pb.h +++ b/src/mesh/generated/meshtastic/rtttl.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_RTTTL_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_RTTTL_PB_H_INCLUDED @@ -40,6 +40,7 @@ extern const pb_msgdesc_t meshtastic_RTTTLConfig_msg; #define meshtastic_RTTTLConfig_fields &meshtastic_RTTTLConfig_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_RTTTL_PB_H_MAX_SIZE meshtastic_RTTTLConfig_size #define meshtastic_RTTTLConfig_size 232 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/storeforward.pb.c b/src/mesh/generated/meshtastic/storeforward.pb.cpp similarity index 94% rename from src/mesh/generated/meshtastic/storeforward.pb.c rename to src/mesh/generated/meshtastic/storeforward.pb.cpp index 44a1c70c1..5b3fadd9a 100644 --- a/src/mesh/generated/meshtastic/storeforward.pb.c +++ b/src/mesh/generated/meshtastic/storeforward.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/storeforward.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/storeforward.pb.h b/src/mesh/generated/meshtastic/storeforward.pb.h index e6cb51f61..311596c7f 100644 --- a/src/mesh/generated/meshtastic/storeforward.pb.h +++ b/src/mesh/generated/meshtastic/storeforward.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_STOREFORWARD_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_STOREFORWARD_PB_H_INCLUDED @@ -30,6 +30,10 @@ typedef enum _meshtastic_StoreAndForward_RequestResponse { meshtastic_StoreAndForward_RequestResponse_ROUTER_HISTORY = 6, /* Router is responding to a request for stats. */ meshtastic_StoreAndForward_RequestResponse_ROUTER_STATS = 7, + /* Router sends a text message from its history that was a direct message. */ + meshtastic_StoreAndForward_RequestResponse_ROUTER_TEXT_DIRECT = 8, + /* Router sends a text message from its history that was a broadcast. */ + meshtastic_StoreAndForward_RequestResponse_ROUTER_TEXT_BROADCAST = 9, /* Client is an in error state. */ meshtastic_StoreAndForward_RequestResponse_CLIENT_ERROR = 64, /* Client has requested a replay from the router. */ @@ -62,9 +66,9 @@ typedef struct _meshtastic_StoreAndForward_Statistics { uint32_t requests_history; /* Is the heartbeat enabled on the server? */ bool heartbeat; - /* Is the heartbeat enabled on the server? */ + /* Maximum number of messages the server will return. */ uint32_t return_max; - /* Is the heartbeat enabled on the server? */ + /* Maximum history window in minutes the server will return messages from. */ uint32_t return_window; } meshtastic_StoreAndForward_Statistics; @@ -74,18 +78,20 @@ typedef struct _meshtastic_StoreAndForward_History { uint32_t history_messages; /* The window of messages that was used to filter the history client requested */ uint32_t window; - /* The window of messages that was used to filter the history client requested */ + /* Index in the packet history of the last message sent in a previous request to the server. + Will be sent to the client before sending the history and can be set in a subsequent request to avoid getting packets the server already sent to the client. */ uint32_t last_request; } meshtastic_StoreAndForward_History; /* TODO: REPLACE */ typedef struct _meshtastic_StoreAndForward_Heartbeat { - /* Number of that will be sent to the client */ + /* Period in seconds that the heartbeat is sent out that will be sent to the client */ uint32_t period; /* If set, this is not the primary Store & Forward router on the mesh */ uint32_t secondary; } meshtastic_StoreAndForward_Heartbeat; +typedef PB_BYTES_ARRAY_T(237) meshtastic_StoreAndForward_text_t; /* TODO: REPLACE */ typedef struct _meshtastic_StoreAndForward { /* TODO: REPLACE */ @@ -98,8 +104,8 @@ typedef struct _meshtastic_StoreAndForward { meshtastic_StoreAndForward_History history; /* TODO: REPLACE */ meshtastic_StoreAndForward_Heartbeat heartbeat; - /* Empty Payload */ - bool empty; + /* Text from history message. */ + meshtastic_StoreAndForward_text_t text; } variant; } meshtastic_StoreAndForward; @@ -148,7 +154,7 @@ extern "C" { #define meshtastic_StoreAndForward_stats_tag 2 #define meshtastic_StoreAndForward_history_tag 3 #define meshtastic_StoreAndForward_heartbeat_tag 4 -#define meshtastic_StoreAndForward_empty_tag 5 +#define meshtastic_StoreAndForward_text_tag 5 /* Struct field encoding specification for nanopb */ #define meshtastic_StoreAndForward_FIELDLIST(X, a) \ @@ -156,7 +162,7 @@ X(a, STATIC, SINGULAR, UENUM, rr, 1) \ X(a, STATIC, ONEOF, MESSAGE, (variant,stats,variant.stats), 2) \ X(a, STATIC, ONEOF, MESSAGE, (variant,history,variant.history), 3) \ X(a, STATIC, ONEOF, MESSAGE, (variant,heartbeat,variant.heartbeat), 4) \ -X(a, STATIC, ONEOF, BOOL, (variant,empty,variant.empty), 5) +X(a, STATIC, ONEOF, BYTES, (variant,text,variant.text), 5) #define meshtastic_StoreAndForward_CALLBACK NULL #define meshtastic_StoreAndForward_DEFAULT NULL #define meshtastic_StoreAndForward_variant_stats_MSGTYPE meshtastic_StoreAndForward_Statistics @@ -201,10 +207,11 @@ extern const pb_msgdesc_t meshtastic_StoreAndForward_Heartbeat_msg; #define meshtastic_StoreAndForward_Heartbeat_fields &meshtastic_StoreAndForward_Heartbeat_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_STOREFORWARD_PB_H_MAX_SIZE meshtastic_StoreAndForward_size #define meshtastic_StoreAndForward_Heartbeat_size 12 #define meshtastic_StoreAndForward_History_size 18 #define meshtastic_StoreAndForward_Statistics_size 50 -#define meshtastic_StoreAndForward_size 54 +#define meshtastic_StoreAndForward_size 242 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/telemetry.pb.c b/src/mesh/generated/meshtastic/telemetry.pb.cpp similarity index 94% rename from src/mesh/generated/meshtastic/telemetry.pb.c rename to src/mesh/generated/meshtastic/telemetry.pb.cpp index 046998ae9..6388e37a0 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.c +++ b/src/mesh/generated/meshtastic/telemetry.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/telemetry.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index fc2780a96..b6d3811a4 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_INCLUDED @@ -41,7 +41,9 @@ typedef enum _meshtastic_TelemetrySensorType { /* PM2.5 air quality sensor */ meshtastic_TelemetrySensorType_PMSA003I = 13, /* INA3221 3 Channel Voltage / Current Sensor */ - meshtastic_TelemetrySensorType_INA3221 = 14 + meshtastic_TelemetrySensorType_INA3221 = 14, + /* BMP085/BMP180 High accuracy temperature and pressure (older Version of BMP280) */ + meshtastic_TelemetrySensorType_BMP085 = 15 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -55,6 +57,8 @@ typedef struct _meshtastic_DeviceMetrics { float channel_utilization; /* Percent of airtime for transmission used within the last hour. */ float air_util_tx; + /* How long the device has been running since the last reboot (in seconds) */ + uint32_t uptime_seconds; } meshtastic_DeviceMetrics; /* Weather station or other environmental metrics */ @@ -71,6 +75,9 @@ typedef struct _meshtastic_EnvironmentMetrics { float voltage; /* Current measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x) */ float current; + /* relative scale IAQ value as measured by Bosch BME680 . value 0-500. + Belongs to Air Quality but is not particle but VOC measurement. Other VOC values can also be put in here. */ + uint16_t iaq; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ @@ -141,8 +148,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_INA3221 -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_INA3221+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_BMP085 +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_BMP085+1)) @@ -151,13 +158,13 @@ extern "C" { /* Initializer values for message structs */ -#define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0} +#define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} -#define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0} -#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0} +#define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0, 0} +#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0} #define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} @@ -167,12 +174,14 @@ extern "C" { #define meshtastic_DeviceMetrics_voltage_tag 2 #define meshtastic_DeviceMetrics_channel_utilization_tag 3 #define meshtastic_DeviceMetrics_air_util_tx_tag 4 +#define meshtastic_DeviceMetrics_uptime_seconds_tag 5 #define meshtastic_EnvironmentMetrics_temperature_tag 1 #define meshtastic_EnvironmentMetrics_relative_humidity_tag 2 #define meshtastic_EnvironmentMetrics_barometric_pressure_tag 3 #define meshtastic_EnvironmentMetrics_gas_resistance_tag 4 #define meshtastic_EnvironmentMetrics_voltage_tag 5 #define meshtastic_EnvironmentMetrics_current_tag 6 +#define meshtastic_EnvironmentMetrics_iaq_tag 7 #define meshtastic_PowerMetrics_ch1_voltage_tag 1 #define meshtastic_PowerMetrics_ch1_current_tag 2 #define meshtastic_PowerMetrics_ch2_voltage_tag 3 @@ -202,7 +211,8 @@ extern "C" { X(a, STATIC, SINGULAR, UINT32, battery_level, 1) \ X(a, STATIC, SINGULAR, FLOAT, voltage, 2) \ X(a, STATIC, SINGULAR, FLOAT, channel_utilization, 3) \ -X(a, STATIC, SINGULAR, FLOAT, air_util_tx, 4) +X(a, STATIC, SINGULAR, FLOAT, air_util_tx, 4) \ +X(a, STATIC, SINGULAR, UINT32, uptime_seconds, 5) #define meshtastic_DeviceMetrics_CALLBACK NULL #define meshtastic_DeviceMetrics_DEFAULT NULL @@ -212,7 +222,8 @@ X(a, STATIC, SINGULAR, FLOAT, relative_humidity, 2) \ X(a, STATIC, SINGULAR, FLOAT, barometric_pressure, 3) \ X(a, STATIC, SINGULAR, FLOAT, gas_resistance, 4) \ X(a, STATIC, SINGULAR, FLOAT, voltage, 5) \ -X(a, STATIC, SINGULAR, FLOAT, current, 6) +X(a, STATIC, SINGULAR, FLOAT, current, 6) \ +X(a, STATIC, SINGULAR, UINT32, iaq, 7) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL @@ -269,9 +280,10 @@ extern const pb_msgdesc_t meshtastic_Telemetry_msg; #define meshtastic_Telemetry_fields &meshtastic_Telemetry_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size #define meshtastic_AirQualityMetrics_size 72 -#define meshtastic_DeviceMetrics_size 21 -#define meshtastic_EnvironmentMetrics_size 30 +#define meshtastic_DeviceMetrics_size 27 +#define meshtastic_EnvironmentMetrics_size 34 #define meshtastic_PowerMetrics_size 30 #define meshtastic_Telemetry_size 79 diff --git a/src/mesh/generated/meshtastic/xmodem.pb.c b/src/mesh/generated/meshtastic/xmodem.pb.cpp similarity index 89% rename from src/mesh/generated/meshtastic/xmodem.pb.c rename to src/mesh/generated/meshtastic/xmodem.pb.cpp index 9692a5eb4..8e5cde457 100644 --- a/src/mesh/generated/meshtastic/xmodem.pb.c +++ b/src/mesh/generated/meshtastic/xmodem.pb.cpp @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #include "meshtastic/xmodem.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/mesh/generated/meshtastic/xmodem.pb.h b/src/mesh/generated/meshtastic/xmodem.pb.h index 48d5aa5cd..67bd0869f 100644 --- a/src/mesh/generated/meshtastic/xmodem.pb.h +++ b/src/mesh/generated/meshtastic/xmodem.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.7 */ +/* Generated by nanopb-0.4.8 */ #ifndef PB_MESHTASTIC_MESHTASTIC_XMODEM_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_XMODEM_PB_H_INCLUDED @@ -68,6 +68,7 @@ extern const pb_msgdesc_t meshtastic_XModem_msg; #define meshtastic_XModem_fields &meshtastic_XModem_msg /* Maximum encoded size of messages (where known) */ +#define MESHTASTIC_MESHTASTIC_XMODEM_PB_H_MAX_SIZE meshtastic_XModem_size #define meshtastic_XModem_size 141 #ifdef __cplusplus diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 2ea2a76a5..7f9df058d 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -1,3 +1,4 @@ +#if !MESHTASTIC_EXCLUDE_WEBSERVER #include "NodeDB.h" #include "PowerFSM.h" #include "RadioLibInterface.h" @@ -5,7 +6,9 @@ #include "main.h" #include "mesh/http/ContentHelper.h" #include "mesh/http/WebServer.h" -#include "mesh/http/WiFiAPClient.h" +#if !MESHTASTIC_EXCLUDE_WIFI +#include "mesh/wifi/WiFiAPClient.h" +#endif #include "mqtt/JSON.h" #include "power.h" #include "sleep.h" @@ -398,7 +401,7 @@ void handleStatic(HTTPRequest *req, HTTPResponse *res) if (!file.available()) { LOG_WARN("File not available - %s\n", filenameGzip.c_str()); res->println("Web server is running.

The content you are looking for can't be found. Please see: FAQ.

FAQ.

admin"); return; @@ -854,4 +857,5 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res) JSONValue *value = new JSONValue(jsonObjOuter); res->print(value->Stringify().c_str()); delete value; -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/mesh/http/ContentHandler.h b/src/mesh/http/ContentHandler.h index 903d5ee08..987e3ffef 100644 --- a/src/mesh/http/ContentHandler.h +++ b/src/mesh/http/ContentHandler.h @@ -1,5 +1,4 @@ #pragma once - void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer); // Declare some handler functions for the various URLs on the server @@ -34,4 +33,4 @@ class HttpAPI : public PhoneAPI protected: /// Check the current underlying physical link to see if the client is currently connected virtual bool checkIsConnected() override { return true; } // FIXME, be smarter about this -}; +}; \ No newline at end of file diff --git a/src/mesh/http/ContentHelper.cpp b/src/mesh/http/ContentHelper.cpp index 249dcbde6..8f283932b 100644 --- a/src/mesh/http/ContentHelper.cpp +++ b/src/mesh/http/ContentHelper.cpp @@ -1,6 +1,6 @@ #include "mesh/http/ContentHelper.h" -//#include -//#include "main.h" +// #include +// #include "main.h" void replaceAll(std::string &str, const std::string &from, const std::string &to) { diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp index 2b045c0be..fc8535257 100644 --- a/src/mesh/http/WebServer.cpp +++ b/src/mesh/http/WebServer.cpp @@ -1,13 +1,14 @@ -#include "mesh/http/WebServer.h" +#include "configuration.h" +#if !MESHTASTIC_EXCLUDE_WEBSERVER #include "NodeDB.h" #include "graphics/Screen.h" #include "main.h" -#include "mesh/http/WiFiAPClient.h" +#include "mesh/http/WebServer.h" +#include "mesh/wifi/WiFiAPClient.h" #include "sleep.h" #include #include #include - #include #include @@ -92,7 +93,6 @@ static void taskCreateCert(void *parameter) LOG_DEBUG("Retrieved Private Key: %d Bytes\n", cert->getPKLength()); LOG_DEBUG("Retrieved Certificate: %d Bytes\n", cert->getCertLength()); - } else { LOG_INFO("Creating the certificate. This may take a while. Please wait...\n"); @@ -105,7 +105,6 @@ static void taskCreateCert(void *parameter) if (createCertResult != 0) { LOG_ERROR("Creating the certificate failed\n"); - } else { LOG_INFO("Creating the certificate was successful\n"); @@ -211,3 +210,4 @@ void initWebServer() LOG_ERROR("Web Servers Failed! ;-( \n"); } } +#endif \ No newline at end of file diff --git a/src/mesh/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h index 22a80c8e3..b8ef236c9 100644 --- a/src/mesh/mesh-pb-constants.h +++ b/src/mesh/mesh-pb-constants.h @@ -1,4 +1,5 @@ #pragma once +#include #include "mesh/generated/meshtastic/admin.pb.h" #include "mesh/generated/meshtastic/deviceonly.pb.h" @@ -16,7 +17,9 @@ #define MAX_RX_TOPHONE 32 /// max number of nodes allowed in the mesh -#define MAX_NUM_NODES (member_size(meshtastic_DeviceState, node_db_lite) / member_size(meshtastic_DeviceState, node_db_lite[0])) +#ifndef MAX_NUM_NODES +#define MAX_NUM_NODES 100 +#endif /// Max number of channels allowed #define MAX_NUM_CHANNELS (member_size(meshtastic_ChannelFile, channels) / member_size(meshtastic_ChannelFile, channels[0])) diff --git a/src/mesh/raspihttp/PiWebServer.cpp b/src/mesh/raspihttp/PiWebServer.cpp new file mode 100644 index 000000000..bffd6c340 --- /dev/null +++ b/src/mesh/raspihttp/PiWebServer.cpp @@ -0,0 +1,530 @@ +/* +Adds a WebServer and WebService callbacks to meshtastic as Linux Version. The WebServer & Webservices +runs in a real linux thread beside the portdunio threading emulation. It replaces the complete ESP32 +Webserver libs including generation of SSL certifcicates, because the use ESP specific details in +the lib that can't be emulated. + +The WebServices adapt to the two major phoneapi functions "handleAPIv1FromRadio,handleAPIv1ToRadio" +The WebServer just adds basaic support to deliver WebContent, so it can be used to +deliver the WebGui definded by the WebClient Project. + +Steps to get it running: +1.) Add these Linux Libs to the compile and target machine: + + sudo apt update && \ + apt -y install openssl libssl-dev libopenssl libsdl2-dev \ + libulfius-dev liborcania-dev + +2.) Configure the root directory of the web Content in the config.yaml file. + The followinng tags should be included and set at your needs + + Example entry in the config.yaml + Webserver: + Port: 9001 # Port for Webserver & Webservices + RootPath: /home/marc/web # Root Dir of WebServer + +3.) Checkout the web project + https://github.com/meshtastic/web.git + + Build it and copy the content of the folder web/dist/* to the folder you did set as "RootPath" + +!!!The WebServer should not be used as production system or exposed to the Internet. Its a raw basic version!!! + +Author: Marc Philipp Hammermann +mail: marchammermann@googlemail.com + +*/ +#ifdef PORTDUINO_LINUX_HARDWARE +#if __has_include() +#include "PiWebServer.h" +#include "NodeDB.h" +#include "PhoneAPI.h" +#include "PowerFSM.h" +#include "RadioLibInterface.h" +#include "airtime.h" +#include "graphics/Screen.h" +#include "main.h" +#include "mesh/wifi/WiFiAPClient.h" +#include "sleep.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "PortduinoFS.h" +#include "platform/portduino/PortduinoGlue.h" + +#define DEFAULT_REALM "default_realm" +#define PREFIX "" + +struct _file_config configWeb; + +// We need to specify some content-type mapping, so the resources get delivered with the +// right content type and are displayed correctly in the browser +char contentTypes[][2][32] = {{".txt", "text/plain"}, {".html", "text/html"}, + {".js", "text/javascript"}, {".png", "image/png"}, + {".jpg", "image/jpg"}, {".gz", "application/gzip"}, + {".gif", "image/gif"}, {".json", "application/json"}, + {".css", "text/css"}, {".ico", "image/vnd.microsoft.icon"}, + {".svg", "image/svg+xml"}, {".ts", "text/javascript"}, + {".tsx", "text/javascript"}, {"", ""}}; + +#undef str + +volatile bool isWebServerReady; +volatile bool isCertReady; + +HttpAPI webAPI; + +PiWebServerThread *piwebServerThread; + +/** + * Return the filename extension + */ +const char *get_filename_ext(const char *path) +{ + const char *dot = strrchr(path, '.'); + if (!dot || dot == path) + return "*"; + if (strchr(dot, '?') != NULL) { + //*strchr(dot, '?') = '\0'; + const char *empty = "\0"; + return empty; + } + return dot; +} + +/** + * Streaming callback function to ease sending large files + */ +static ssize_t callback_static_file_stream(void *cls, uint64_t pos, char *buf, size_t max) +{ + (void)(pos); + if (cls != NULL) { + return fread(buf, 1, max, (FILE *)cls); + } else { + return U_STREAM_END; + } +} + +/** + * Cleanup FILE* structure when streaming is complete + */ +static void callback_static_file_stream_free(void *cls) +{ + if (cls != NULL) { + fclose((FILE *)cls); + } +} + +/** + * static file callback endpoint that delivers the content for WebServer calls + */ +int callback_static_file(const struct _u_request *request, struct _u_response *response, void *user_data) +{ + size_t length; + FILE *f; + char *file_requested, *file_path, *url_dup_save, *real_path = NULL; + const char *content_type; + + /* + * Comment this if statement if you don't access static files url from root dir, like /app + */ + if (request->callback_position > 0) { + return U_CALLBACK_CONTINUE; + } else if (user_data != NULL && (configWeb.files_path != NULL)) { + file_requested = o_strdup(request->http_url); + url_dup_save = file_requested; + + while (file_requested[0] == '/') { + file_requested++; + } + file_requested += o_strlen(configWeb.url_prefix); + while (file_requested[0] == '/') { + file_requested++; + } + + if (strchr(file_requested, '#') != NULL) { + *strchr(file_requested, '#') = '\0'; + } + + if (strchr(file_requested, '?') != NULL) { + *strchr(file_requested, '?') = '\0'; + } + + if (file_requested == NULL || o_strlen(file_requested) == 0 || 0 == o_strcmp("/", file_requested)) { + o_free(url_dup_save); + url_dup_save = file_requested = o_strdup("index.html"); + } + + file_path = msprintf("%s/%s", configWeb.files_path, file_requested); + real_path = realpath(file_path, NULL); + if (0 == o_strncmp(configWeb.files_path, real_path, o_strlen(configWeb.files_path))) { + if (access(file_path, F_OK) != -1) { + f = fopen(file_path, "rb"); + if (f) { + fseek(f, 0, SEEK_END); + length = ftell(f); + fseek(f, 0, SEEK_SET); + + content_type = u_map_get_case(&configWeb.mime_types, get_filename_ext(file_requested)); + if (content_type == NULL) { + content_type = u_map_get(&configWeb.mime_types, "*"); + LOG_DEBUG("Static File Server - Unknown mime type for extension %s \n", get_filename_ext(file_requested)); + } + u_map_put(response->map_header, "Content-Type", content_type); + u_map_copy_into(response->map_header, &configWeb.map_header); + + if (ulfius_set_stream_response(response, 200, callback_static_file_stream, callback_static_file_stream_free, + length, STATIC_FILE_CHUNK, f) != U_OK) { + LOG_DEBUG("callback_static_file - Error ulfius_set_stream_response\n "); + } + } + } else { + if (configWeb.redirect_on_404 == NULL) { + ulfius_set_string_body_response(response, 404, "File not found"); + } else { + ulfius_add_header_to_response(response, "Location", configWeb.redirect_on_404); + response->status = 302; + } + } + } else { + if (configWeb.redirect_on_404 == NULL) { + ulfius_set_string_body_response(response, 404, "File not found"); + } else { + ulfius_add_header_to_response(response, "Location", configWeb.redirect_on_404); + response->status = 302; + } + } + + o_free(file_path); + o_free(url_dup_save); + free(real_path); // realpath uses malloc + return U_CALLBACK_CONTINUE; + } else { + LOG_DEBUG("Static File Server - Error, user_data is NULL or inconsistent\n"); + return U_CALLBACK_ERROR; + } +} + +static void handleWebResponse() {} + +/* + * Adapt the radioapi to the Webservice handleAPIv1ToRadio + * Trigger : WebGui(SAVE)->WebServcice->phoneApi + */ +int handleAPIv1ToRadio(const struct _u_request *req, struct _u_response *res, void *user_data) +{ + LOG_DEBUG("handleAPIv1ToRadio web -> radio \n"); + + ulfius_add_header_to_response(res, "Content-Type", "application/x-protobuf"); + ulfius_add_header_to_response(res, "Access-Control-Allow-Headers", "Content-Type"); + ulfius_add_header_to_response(res, "Access-Control-Allow-Origin", "*"); + ulfius_add_header_to_response(res, "Access-Control-Allow-Methods", "PUT, OPTIONS"); + ulfius_add_header_to_response(res, "X-Protobuf-Schema", + "https://raw.githubusercontent.com/meshtastic/protobufs/master/mesh.proto"); + + if (req->http_verb == "OPTIONS") { + ulfius_set_response_properties(res, U_OPT_STATUS, 204); + return U_CALLBACK_CONTINUE; + } + + byte buffer[MAX_TO_FROM_RADIO_SIZE]; + size_t s = req->binary_body_length; + + memcpy(buffer, req->binary_body, MAX_TO_FROM_RADIO_SIZE); + + // FIXME* Problem with portdunio loosing mountpoint maybe because of running in a real sep. thread + + portduinoVFS->mountpoint(configWeb.rootPath); + + LOG_DEBUG("Received %d bytes from PUT request\n", s); + webAPI.handleToRadio(buffer, s); + LOG_DEBUG("end web->radio \n"); + return U_CALLBACK_COMPLETE; +} + +/* + * Adapt the radioapi to the Webservice handleAPIv1FromRadio + * Trigger : WebGui(POLL)->handleAPIv1FromRadio->phoneapi->Meshtastic(Radio) events + */ +int handleAPIv1FromRadio(const struct _u_request *req, struct _u_response *res, void *user_data) +{ + + // LOG_DEBUG("handleAPIv1FromRadio radio -> web\n"); + std::string valueAll; + + // Status code is 200 OK by default. + ulfius_add_header_to_response(res, "Content-Type", "application/x-protobuf"); + ulfius_add_header_to_response(res, "Access-Control-Allow-Origin", "*"); + ulfius_add_header_to_response(res, "Access-Control-Allow-Methods", "GET"); + ulfius_add_header_to_response(res, "X-Protobuf-Schema", + "https://raw.githubusercontent.com/meshtastic/protobufs/master/mesh.proto"); + + uint8_t txBuf[MAX_STREAM_BUF_SIZE]; + uint32_t len = 1; + + if (valueAll == "true") { + while (len) { + len = webAPI.getFromRadio(txBuf); + ulfius_set_response_properties(res, U_OPT_STATUS, 200, U_OPT_BINARY_BODY, txBuf, len); + const char *tmpa = (const char *)txBuf; + ulfius_set_string_body_response(res, 200, tmpa); + // LOG_DEBUG("\n----webAPI response all:----\n"); + // LOG_DEBUG(tmpa); + // LOG_DEBUG("\n"); + } + // Otherwise, just return one protobuf + } else { + len = webAPI.getFromRadio(txBuf); + const char *tmpa = (const char *)txBuf; + ulfius_set_binary_body_response(res, 200, tmpa, len); + // LOG_DEBUG("\n----webAPI response:\n"); + // LOG_DEBUG(tmpa); + // LOG_DEBUG("\n"); + } + + // LOG_DEBUG("end radio->web\n", len); + return U_CALLBACK_COMPLETE; +} + +/* +OpenSSL RSA Key Gen +*/ +int generate_rsa_key(EVP_PKEY **pkey) +{ + EVP_PKEY_CTX *pkey_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (!pkey_ctx) + return -1; + if (EVP_PKEY_keygen_init(pkey_ctx) <= 0) + return -1; + if (EVP_PKEY_CTX_set_rsa_keygen_bits(pkey_ctx, 2048) <= 0) + return -1; + if (EVP_PKEY_keygen(pkey_ctx, pkey) <= 0) + return -1; + EVP_PKEY_CTX_free(pkey_ctx); + return 0; // SUCCESS +} + +int generate_self_signed_x509(EVP_PKEY *pkey, X509 **x509) +{ + *x509 = X509_new(); + if (!*x509) + return -1; + if (X509_set_version(*x509, 2) != 1) + return -1; + ASN1_INTEGER_set(X509_get_serialNumber(*x509), 1); + X509_gmtime_adj(X509_get_notBefore(*x509), 0); + X509_gmtime_adj(X509_get_notAfter(*x509), 31536000L); // 1 YEAR ACCESS + + X509_set_pubkey(*x509, pkey); + + // SET Subject Name + X509_NAME *name = X509_get_subject_name(*x509); + X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)"DE", -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *)"Meshtastic", -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"meshtastic.local", -1, -1, 0); + // Selfsigned, Issuer = Subject + X509_set_issuer_name(*x509, name); + + // Certificate signed with our privte key + if (X509_sign(*x509, pkey, EVP_sha256()) <= 0) + return -1; + + return 0; +} + +char *read_file_into_string(const char *filename) +{ + FILE *file = fopen(filename, "rb"); + if (file == NULL) { + LOG_ERROR("Error reading File : %s \n", filename); + return NULL; + } + + // Size of file + fseek(file, 0, SEEK_END); + long filesize = ftell(file); + rewind(file); + + // reserve mem for file + 1 byte + char *buffer = (char *)malloc(filesize + 1); + if (buffer == NULL) { + LOG_ERROR("Malloc of mem failed for file : %s \n", filename); + fclose(file); + return NULL; + } + + // read content + size_t readSize = fread(buffer, 1, filesize, file); + if (readSize != filesize) { + LOG_ERROR("Error reading file into buffer\n"); + free(buffer); + fclose(file); + return NULL; + } + + // add terminator sign at the end + buffer[filesize] = '\0'; + fclose(file); + return buffer; // return pointer +} + +int PiWebServerThread::CheckSSLandLoad() +{ + // read certificate + cert_pem = read_file_into_string("certificate.pem"); + if (cert_pem == NULL) { + LOG_ERROR("ERROR SSL Certificate File can't be loaded or is missing\n"); + return 1; + } + // read private key + key_pem = read_file_into_string("private_key.pem"); + if (key_pem == NULL) { + LOG_ERROR("ERROR file private_key can't be loaded or is missing\n"); + return 2; + } + + return 0; +} + +int PiWebServerThread::CreateSSLCertificate() +{ + + EVP_PKEY *pkey = NULL; + X509 *x509 = NULL; + + if (generate_rsa_key(&pkey) != 0) { + LOG_ERROR("Error generating RSA-Key.\n"); + return 1; + } + + if (generate_self_signed_x509(pkey, &x509) != 0) { + LOG_ERROR("Error generating of X509-Certificat.\n"); + return 2; + } + + // Ope file to write private key file + FILE *pkey_file = fopen("private_key.pem", "wb"); + if (!pkey_file) { + LOG_ERROR("Error opening private key file.\n"); + return 3; + } + // write private key file + PEM_write_PrivateKey(pkey_file, pkey, NULL, NULL, 0, NULL, NULL); + fclose(pkey_file); + + // open Certificate file + FILE *x509_file = fopen("certificate.pem", "wb"); + if (!x509_file) { + LOG_ERROR("Error opening certificate.\n"); + return 4; + } + // write cirtificate + PEM_write_X509(x509_file, x509); + fclose(x509_file); + + EVP_PKEY_free(pkey); + X509_free(x509); + LOG_INFO("Create SSL Certifictate -certificate.pem- succesfull \n"); + return 0; +} + +void initWebServer() {} + +PiWebServerThread::PiWebServerThread() +{ + int ret, retssl, webservport; + + if (CheckSSLandLoad() != 0) { + CreateSSLCertificate(); + if (CheckSSLandLoad() != 0) { + LOG_ERROR("Major Error Gen & Read SSL Certificate\n"); + } + } + + if (settingsMap[webserverport] != 0) { + webservport = settingsMap[webserverport]; + LOG_INFO("Using webserver port from yaml config. %i \n", webservport); + } else { + LOG_INFO("Webserver port in yaml config set to 0, so defaulting to port 443.\n"); + webservport = 443; + } + + // Web Content Service Instance + if (ulfius_init_instance(&instanceWeb, webservport, NULL, DEFAULT_REALM) != U_OK) { + LOG_ERROR("Webserver couldn't be started, abort execution\n"); + } else { + + LOG_INFO("Webserver started ....\n"); + u_map_init(&configWeb.mime_types); + u_map_put(&configWeb.mime_types, "*", "application/octet-stream"); + u_map_put(&configWeb.mime_types, ".html", "text/html"); + u_map_put(&configWeb.mime_types, ".htm", "text/html"); + u_map_put(&configWeb.mime_types, ".tsx", "application/javascript"); + u_map_put(&configWeb.mime_types, ".ts", "application/javascript"); + u_map_put(&configWeb.mime_types, ".css", "text/css"); + u_map_put(&configWeb.mime_types, ".js", "application/javascript"); + u_map_put(&configWeb.mime_types, ".json", "application/json"); + u_map_put(&configWeb.mime_types, ".png", "image/png"); + u_map_put(&configWeb.mime_types, ".gif", "image/gif"); + u_map_put(&configWeb.mime_types, ".jpeg", "image/jpeg"); + u_map_put(&configWeb.mime_types, ".jpg", "image/jpeg"); + u_map_put(&configWeb.mime_types, ".ttf", "font/ttf"); + u_map_put(&configWeb.mime_types, ".woff", "font/woff"); + u_map_put(&configWeb.mime_types, ".ico", "image/x-icon"); + u_map_put(&configWeb.mime_types, ".svg", "image/svg+xml"); + + webrootpath = settingsStrings[webserverrootpath]; + + configWeb.files_path = (char *)webrootpath.c_str(); + configWeb.url_prefix = ""; + configWeb.rootPath = strdup(portduinoVFS->mountpoint()); + + u_map_put(instanceWeb.default_headers, "Access-Control-Allow-Origin", "*"); + // Maximum body size sent by the client is 1 Kb + instanceWeb.max_post_body_size = 1024; + ulfius_add_endpoint_by_val(&instanceWeb, "GET", PREFIX, "/api/v1/fromradio/*", 1, &handleAPIv1FromRadio, NULL); + ulfius_add_endpoint_by_val(&instanceWeb, "PUT", PREFIX, "/api/v1/toradio/*", 1, &handleAPIv1ToRadio, configWeb.rootPath); + + // Add callback function to all endpoints for the Web Server + ulfius_add_endpoint_by_val(&instanceWeb, "GET", NULL, "/*", 2, &callback_static_file, &configWeb); + + // thats for serving without SSL + // retssl = ulfius_start_framework(&instanceWeb); + + // thats for serving with SSL + retssl = ulfius_start_secure_framework(&instanceWeb, key_pem, cert_pem); + + if (retssl == U_OK) { + LOG_INFO("Web Server framework started on port: %i \n", webservport); + LOG_INFO("Web Server root %s\n", (char *)webrootpath.c_str()); + } else { + LOG_ERROR("Error starting Web Server framework, error number: %d\n", retssl); + } + } +} + +PiWebServerThread::~PiWebServerThread() +{ + u_map_clean(&configWeb.mime_types); + + ulfius_stop_framework(&instanceWeb); + ulfius_stop_framework(&instanceWeb); + free(configWeb.rootPath); + ulfius_clean_instance(&instanceService); + ulfius_clean_instance(&instanceService); + free(cert_pem); + LOG_INFO("End framework"); +} + +#endif +#endif \ No newline at end of file diff --git a/src/mesh/raspihttp/PiWebServer.h b/src/mesh/raspihttp/PiWebServer.h new file mode 100644 index 000000000..c4c49e919 --- /dev/null +++ b/src/mesh/raspihttp/PiWebServer.h @@ -0,0 +1,61 @@ +#pragma once +#ifdef PORTDUINO_LINUX_HARDWARE +#if __has_include() +#include "PhoneAPI.h" +#include "ulfius-cfg.h" +#include "ulfius.h" +#include +#include + +#define STATIC_FILE_CHUNK 256 + +void initWebServer(); +void createSSLCert(); +int callback_static_file(const struct _u_request *request, struct _u_response *response, void *user_data); +const char *get_filename_ext(const char *path); + +struct _file_config { + char *files_path; + char *url_prefix; + struct _u_map mime_types; + struct _u_map map_header; + char *redirect_on_404; + char *rootPath; +}; + +class PiWebServerThread +{ + private: + char *key_pem = NULL; + char *cert_pem = NULL; + // struct _u_map mime_types; + std::string webrootpath; + + public: + PiWebServerThread(); + ~PiWebServerThread(); + int CreateSSLCertificate(); + int CheckSSLandLoad(); + uint32_t requestRestart = 0; + struct _u_instance instanceWeb; + struct _u_instance instanceService; +}; + +class HttpAPI : public PhoneAPI +{ + + public: + // Nothing here yet + + private: + // Nothing here yet + + protected: + /// Check the current underlying physical link to see if the client is currently connected + virtual bool checkIsConnected() override { return true; } // FIXME, be smarter about this +}; + +extern PiWebServerThread *piwebServerThread; + +#endif +#endif \ No newline at end of file diff --git a/src/mesh/http/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp similarity index 84% rename from src/mesh/http/WiFiAPClient.cpp rename to src/mesh/wifi/WiFiAPClient.cpp index cc8d4b168..1de4d7669 100644 --- a/src/mesh/http/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -1,17 +1,28 @@ -#include "mesh/http/WiFiAPClient.h" +#include "configuration.h" +#if !MESHTASTIC_EXCLUDE_WIFI #include "NodeDB.h" #include "RTC.h" #include "concurrency/Periodic.h" -#include "configuration.h" +#include "mesh/wifi/WiFiAPClient.h" + #include "main.h" #include "mesh/api/WiFiServerAPI.h" -#include "mesh/http/WebServer.h" +#if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" +#endif #include "target_specific.h" -#include #include #include +#ifdef ARCH_ESP32 +#if !MESHTASTIC_EXCLUDE_WEBSERVER +#if !MESHTASTIC_EXCLUDE_WEBSERVER +#include "mesh/http/WebServer.h" +#endif +#endif +#include #include +static void WiFiEvent(WiFiEvent_t event); +#endif #ifndef DISABLE_NTP #include @@ -19,8 +30,6 @@ using namespace concurrency; -static void WiFiEvent(WiFiEvent_t event); - // NTP WiFiUDP ntpUDP; @@ -37,91 +46,21 @@ bool APStartupComplete = 0; unsigned long lastrun_ntp = 0; -bool needReconnect = true; // If we create our reconnector, run it once at the beginning +bool needReconnect = true; // If we create our reconnector, run it once at the beginning +bool isReconnecting = false; // If we are currently reconnecting WiFiUDP syslogClient; Syslog syslog(syslogClient); Periodic *wifiReconnect; -static int32_t reconnectWiFi() -{ - const char *wifiName = config.network.wifi_ssid; - const char *wifiPsw = config.network.wifi_psk; - - if (config.network.wifi_enabled && needReconnect) { - - if (!*wifiPsw) // Treat empty password as no password - wifiPsw = NULL; - - needReconnect = false; - - // Make sure we clear old connection credentials - WiFi.disconnect(false, true); - LOG_INFO("Reconnecting to WiFi access point %s\n", wifiName); - - delay(5000); - - if (!WiFi.isConnected()) { - WiFi.begin(wifiName, wifiPsw); - } - } - -#ifndef DISABLE_NTP - if (WiFi.isConnected() && (((millis() - lastrun_ntp) > 43200000) || (lastrun_ntp == 0))) { // every 12 hours - LOG_DEBUG("Updating NTP time from %s\n", config.network.ntp_server); - if (timeClient.update()) { - LOG_DEBUG("NTP Request Success - Setting RTCQualityNTP if needed\n"); - - struct timeval tv; - tv.tv_sec = timeClient.getEpochTime(); - tv.tv_usec = 0; - - perhapsSetRTC(RTCQualityNTP, &tv); - lastrun_ntp = millis(); - - } else { - LOG_DEBUG("NTP Update failed\n"); - } - } -#endif - - if (config.network.wifi_enabled && !WiFi.isConnected()) { - return 1000; // check once per second - } else { - return 300000; // every 5 minutes - } -} - -bool isWifiAvailable() -{ - - if (config.network.wifi_enabled && (config.network.wifi_ssid[0])) { - return true; - } else { - return false; - } -} - -// Disable WiFi -void deinitWifi() -{ - LOG_INFO("WiFi deinit\n"); - - if (isWifiAvailable()) { - WiFi.disconnect(true); - WiFi.mode(WIFI_MODE_NULL); - LOG_INFO("WiFi Turned Off\n"); - // WiFi.printDiag(Serial); - } -} - static void onNetworkConnected() { if (!APStartupComplete) { // Start web server LOG_INFO("Starting network services\n"); +#ifdef ARCH_ESP32 // start mdns if (!MDNS.begin("Meshtastic")) { LOG_ERROR("Error setting up MDNS responder!\n"); @@ -131,6 +70,9 @@ static void onNetworkConnected() MDNS.addService("http", "tcp", 80); MDNS.addService("https", "tcp", 443); } +#else // ESP32 handles this in WiFiEvent + LOG_INFO("Obtained IP address: %s\n", WiFi.localIP().toString().c_str()); +#endif #ifndef DISABLE_NTP LOG_INFO("Starting NTP time client\n"); @@ -158,9 +100,10 @@ static void onNetworkConnected() syslog.enable(); } +#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WEBSERVER initWebServer(); +#endif initApiServer(); - APStartupComplete = true; } @@ -169,6 +112,95 @@ static void onNetworkConnected() mqtt->reconnect(); } +static int32_t reconnectWiFi() +{ + const char *wifiName = config.network.wifi_ssid; + const char *wifiPsw = config.network.wifi_psk; + + if (config.network.wifi_enabled && needReconnect) { + + if (!*wifiPsw) // Treat empty password as no password + wifiPsw = NULL; + + needReconnect = false; + isReconnecting = true; + + // Make sure we clear old connection credentials +#ifdef ARCH_ESP32 + WiFi.disconnect(false, true); +#else + WiFi.disconnect(false); +#endif + LOG_INFO("Reconnecting to WiFi access point %s\n", wifiName); + + delay(5000); + + if (!WiFi.isConnected()) { + WiFi.begin(wifiName, wifiPsw); + } + isReconnecting = false; + } + +#ifndef DISABLE_NTP + if (WiFi.isConnected() && (((millis() - lastrun_ntp) > 43200000) || (lastrun_ntp == 0))) { // every 12 hours + LOG_DEBUG("Updating NTP time from %s\n", config.network.ntp_server); + if (timeClient.update()) { + LOG_DEBUG("NTP Request Success - Setting RTCQualityNTP if needed\n"); + + struct timeval tv; + tv.tv_sec = timeClient.getEpochTime(); + tv.tv_usec = 0; + + perhapsSetRTC(RTCQualityNTP, &tv); + lastrun_ntp = millis(); + } else { + LOG_DEBUG("NTP Update failed\n"); + } + } +#endif + + if (config.network.wifi_enabled && !WiFi.isConnected()) { +#ifdef ARCH_RP2040 // (ESP32 handles this in WiFiEvent) + /* If APStartupComplete, but we're not connected, try again. + Shouldn't try again before APStartupComplete. */ + needReconnect = APStartupComplete; +#endif + return 1000; // check once per second + } else { +#ifdef ARCH_RP2040 + onNetworkConnected(); // will only do anything once +#endif + return 300000; // every 5 minutes + } +} + +bool isWifiAvailable() +{ + + if (config.network.wifi_enabled && (config.network.wifi_ssid[0])) { + return true; + } else { + return false; + } +} + +// Disable WiFi +void deinitWifi() +{ + LOG_INFO("WiFi deinit\n"); + + if (isWifiAvailable()) { +#ifdef ARCH_ESP32 + WiFi.disconnect(true, false); +#else + WiFi.disconnect(true); +#endif + WiFi.mode(WIFI_OFF); + LOG_INFO("WiFi Turned Off\n"); + // WiFi.printDiag(Serial); + } +} + // Startup WiFi bool initWifi() { @@ -177,10 +209,12 @@ bool initWifi() const char *wifiName = config.network.wifi_ssid; const char *wifiPsw = config.network.wifi_psk; - createSSLCert(); - +#ifndef ARCH_RP2040 +#if !MESHTASTIC_EXCLUDE_WEBSERVER + createSSLCert(); // For WebServer +#endif esp_wifi_set_storage(WIFI_STORAGE_RAM); // Disable flash storage for WiFi credentials - +#endif if (!*wifiPsw) // Treat empty password as no password wifiPsw = NULL; @@ -189,17 +223,17 @@ bool initWifi() getMacAddr(dmac); snprintf(ourHost, sizeof(ourHost), "Meshtastic-%02x%02x", dmac[4], dmac[5]); - WiFi.mode(WIFI_MODE_STA); + WiFi.mode(WIFI_STA); WiFi.setHostname(ourHost); - WiFi.onEvent(WiFiEvent); - WiFi.setAutoReconnect(true); - WiFi.setSleep(false); if (config.network.address_mode == meshtastic_Config_NetworkConfig_AddressMode_STATIC && config.network.ipv4_config.ip != 0) { WiFi.config(config.network.ipv4_config.ip, config.network.ipv4_config.gateway, config.network.ipv4_config.subnet, - config.network.ipv4_config.dns, - config.network.ipv4_config.dns); // Wifi wants two DNS servers... set both to the same value + config.network.ipv4_config.dns); } +#ifndef ARCH_RP2040 + WiFi.onEvent(WiFiEvent); + WiFi.setAutoReconnect(true); + WiFi.setSleep(false); // This is needed to improve performance. esp_wifi_set_ps(WIFI_PS_NONE); // Disable radio power saving @@ -218,7 +252,7 @@ bool initWifi() wifiDisconnectReason = info.wifi_sta_disconnected.reason; }, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED); - +#endif LOG_DEBUG("JOINING WIFI soon: ssid=%s\n", wifiName); wifiReconnect = new Periodic("WifiConnect", reconnectWiFi); } @@ -229,6 +263,7 @@ bool initWifi() } } +#ifdef ARCH_ESP32 // Called by the Espressif SDK to static void WiFiEvent(WiFiEvent_t event) { @@ -253,27 +288,31 @@ static void WiFiEvent(WiFiEvent_t event) break; case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: LOG_INFO("Disconnected from WiFi access point\n"); - WiFi.disconnect(false, true); - syslog.disable(); - needReconnect = true; - wifiReconnect->setIntervalFromNow(1000); + if (!isReconnecting) { + WiFi.disconnect(false, true); + syslog.disable(); + needReconnect = true; + wifiReconnect->setIntervalFromNow(1000); + } break; case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: LOG_INFO("Authentication mode of access point has changed\n"); break; case ARDUINO_EVENT_WIFI_STA_GOT_IP: - LOG_INFO("Obtained IP address: ", WiFi.localIPv6()); + LOG_INFO("Obtained IP address: %s\n", WiFi.localIP().toString().c_str()); onNetworkConnected(); break; case ARDUINO_EVENT_WIFI_STA_GOT_IP6: - LOG_INFO("Obtained IP6 address: %s", WiFi.localIPv6()); + LOG_INFO("Obtained IP6 address: %s\n", WiFi.localIPv6().toString().c_str()); break; case ARDUINO_EVENT_WIFI_STA_LOST_IP: LOG_INFO("Lost IP address and IP address is reset to 0\n"); - WiFi.disconnect(false, true); - syslog.disable(); - needReconnect = true; - wifiReconnect->setIntervalFromNow(1000); + if (!isReconnecting) { + WiFi.disconnect(false, true); + syslog.disable(); + needReconnect = true; + wifiReconnect->setIntervalFromNow(1000); + } break; case ARDUINO_EVENT_WPS_ER_SUCCESS: LOG_INFO("WiFi Protected Setup (WPS): succeeded in enrollee mode\n"); @@ -369,8 +408,10 @@ static void WiFiEvent(WiFiEvent_t event) break; } } +#endif uint8_t getWifiDisconnectReason() { return wifiDisconnectReason; } +#endif \ No newline at end of file diff --git a/src/mesh/http/WiFiAPClient.h b/src/mesh/wifi/WiFiAPClient.h similarity index 81% rename from src/mesh/http/WiFiAPClient.h rename to src/mesh/wifi/WiFiAPClient.h index 0c08c567b..5f4e2f5c9 100644 --- a/src/mesh/http/WiFiAPClient.h +++ b/src/mesh/wifi/WiFiAPClient.h @@ -5,7 +5,7 @@ #include #include -#ifdef ARCH_ESP32 +#if HAS_WIFI && !defined(ARCH_PORTDUINO) #include #endif @@ -19,4 +19,4 @@ void deinitWifi(); bool isWifiAvailable(); -uint8_t getWifiDisconnectReason(); +uint8_t getWifiDisconnectReason(); \ No newline at end of file diff --git a/src/meshUtils.cpp b/src/meshUtils.cpp index cab05e54b..59d4e6714 100644 --- a/src/meshUtils.cpp +++ b/src/meshUtils.cpp @@ -39,10 +39,11 @@ */ char *strnstr(const char *s, const char *find, size_t slen) { - char c, sc; - size_t len; - + char c; if ((c = *find++) != '\0') { + char sc; + size_t len; + len = strlen(find); do { do { diff --git a/src/meshUtils.h b/src/meshUtils.h index a6436a8d5..e32ef230a 100644 --- a/src/meshUtils.h +++ b/src/meshUtils.h @@ -8,5 +8,6 @@ template constexpr const T &clamp(const T &v, const T &lo, const T &hi #if (defined(ARCH_PORTDUINO) && !defined(STRNSTR)) #define STRNSTR +#include char *strnstr(const char *s, const char *find, size_t slen); #endif \ No newline at end of file diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index dce33ad48..54eb577f7 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -3,7 +3,8 @@ #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" -#ifdef ARCH_ESP32 +#include +#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH #include "BleOta.h" #endif #include "Router.h" @@ -15,10 +16,12 @@ #ifdef ARCH_PORTDUINO #include "unistd.h" #endif +#include "Default.h" +#include "TypeConversions.h" +#if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" - -#define DEFAULT_REBOOT_SECONDS 7 +#endif AdminModule *adminModule; bool hasOpenEditTransaction; @@ -48,7 +51,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta // if handled == false, then let others look at this message also if they want bool handled = false; assert(r); - bool fromOthers = mp.from != 0 && mp.from != nodeDB.getNodeNum(); + bool fromOthers = mp.from != 0 && mp.from != nodeDB->getNodeNum(); switch (r->which_payload_variant) { @@ -119,7 +122,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta } case meshtastic_AdminMessage_reboot_ota_seconds_tag: { int32_t s = r->reboot_ota_seconds; -#ifdef ARCH_ESP32 +#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH if (BleOta::getOtaAppVersion().isEmpty()) { LOG_INFO("No OTA firmware available, scheduling regular reboot in %d seconds\n", s); screen->startRebootScreen(); @@ -148,13 +151,13 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta } case meshtastic_AdminMessage_factory_reset_tag: { LOG_INFO("Initiating factory reset\n"); - nodeDB.factoryReset(); + nodeDB->factoryReset(); reboot(DEFAULT_REBOOT_SECONDS); break; } case meshtastic_AdminMessage_nodedb_reset_tag: { LOG_INFO("Initiating node-db reset\n"); - nodeDB.resetNodes(); + nodeDB->resetNodes(); reboot(DEFAULT_REBOOT_SECONDS); break; } @@ -184,8 +187,64 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta } case meshtastic_AdminMessage_remove_by_nodenum_tag: { LOG_INFO("Client is receiving a remove_nodenum command.\n"); - nodeDB.removeNodeByNum(r->remove_by_nodenum); - reboot(DEFAULT_REBOOT_SECONDS); + nodeDB->removeNodeByNum(r->remove_by_nodenum); + break; + } + case meshtastic_AdminMessage_set_favorite_node_tag: { + LOG_INFO("Client is receiving a set_favorite_node command.\n"); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->set_favorite_node); + if (node != NULL) { + node->is_favorite = true; + } + break; + } + case meshtastic_AdminMessage_remove_favorite_node_tag: { + LOG_INFO("Client is receiving a remove_favorite_node command.\n"); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->remove_favorite_node); + if (node != NULL) { + node->is_favorite = false; + } + break; + } + case meshtastic_AdminMessage_set_fixed_position_tag: { + if (fromOthers) { + LOG_INFO("Ignoring set_fixed_position command from another node.\n"); + } else { + LOG_INFO("Client is receiving a set_fixed_position command.\n"); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); + node->has_position = true; + node->position = TypeConversions::ConvertToPositionLite(r->set_fixed_position); + nodeDB->setLocalPosition(r->set_fixed_position); + config.position.fixed_position = true; + saveChanges(SEGMENT_DEVICESTATE | SEGMENT_CONFIG, false); + } + break; + } + case meshtastic_AdminMessage_remove_fixed_position_tag: { + if (fromOthers) { + LOG_INFO("Ignoring remove_fixed_position command from another node.\n"); + } else { + LOG_INFO("Client is receiving a remove_fixed_position command.\n"); + nodeDB->clearLocalPosition(); + config.position.fixed_position = false; + saveChanges(SEGMENT_DEVICESTATE | SEGMENT_CONFIG, false); + } + break; + } + case meshtastic_AdminMessage_enter_dfu_mode_request_tag: { + LOG_INFO("Client is requesting to enter DFU mode.\n"); +#if defined(ARCH_NRF52) || defined(ARCH_RP2040) + enterDfuMode(); +#endif + break; + } + case meshtastic_AdminMessage_delete_file_request_tag: { + LOG_DEBUG("Client is requesting to delete file: %s\n", r->delete_file_request); + if (FSCom.remove(r->delete_file_request)) { + LOG_DEBUG("Successfully deleted file\n"); + } else { + LOG_DEBUG("Failed to delete file\n"); + } break; } #ifdef ARCH_PORTDUINO @@ -197,7 +256,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta default: meshtastic_AdminMessage res = meshtastic_AdminMessage_init_default; - AdminMessageHandleResult handleResult = MeshModule::handleAdminMessageForAllPlugins(mp, r, &res); + AdminMessageHandleResult handleResult = MeshModule::handleAdminMessageForAllModules(mp, r, &res); if (handleResult == AdminMessageHandleResult::HANDLED_WITH_RESPONSE) { myReply = allocDataProtobuf(res); @@ -274,8 +333,10 @@ void AdminModule::handleSetOwner(const meshtastic_User &o) void AdminModule::handleSetConfig(const meshtastic_Config &c) { + auto changes = SEGMENT_CONFIG; auto existingRole = config.device.role; bool isRegionUnset = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET); + bool requiresReboot = true; switch (c.which_payload_variant) { case meshtastic_Config_device_tag: @@ -284,7 +345,11 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) config.device = c.payload_variant.device; // If we're setting router role for the first time, install its intervals if (existingRole != c.payload_variant.device.role) - nodeDB.installRoleDefaults(c.payload_variant.device.role); + nodeDB->installRoleDefaults(c.payload_variant.device.role); + if (config.device.node_info_broadcast_secs < min_node_info_broadcast_secs) { + LOG_DEBUG("Tried to set node_info_broadcast_secs too low, setting to %d\n", min_node_info_broadcast_secs); + config.device.node_info_broadcast_secs = min_node_info_broadcast_secs; + } break; case meshtastic_Config_position_tag: LOG_INFO("Setting config: Position\n"); @@ -311,9 +376,31 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) case meshtastic_Config_lora_tag: LOG_INFO("Setting config: LoRa\n"); config.has_lora = true; + // If no lora radio parameters change, don't need to reboot + if (config.lora.use_preset == c.payload_variant.lora.use_preset && config.lora.region == c.payload_variant.lora.region && + config.lora.modem_preset == c.payload_variant.lora.modem_preset && + config.lora.bandwidth == c.payload_variant.lora.bandwidth && + config.lora.spread_factor == c.payload_variant.lora.spread_factor && + config.lora.coding_rate == c.payload_variant.lora.coding_rate && + config.lora.tx_power == c.payload_variant.lora.tx_power && + config.lora.frequency_offset == c.payload_variant.lora.frequency_offset && + config.lora.override_frequency == c.payload_variant.lora.override_frequency && + config.lora.channel_num == c.payload_variant.lora.channel_num && + config.lora.sx126x_rx_boosted_gain == c.payload_variant.lora.sx126x_rx_boosted_gain) { + requiresReboot = false; + } config.lora = c.payload_variant.lora; + // If we're setting region for the first time, init the region if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) { config.lora.tx_enabled = true; + initRegion(); + if (myRegion->dutyCycle < 100) { + config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit + } + if (strcmp(moduleConfig.mqtt.root, default_mqtt_root) == 0) { + sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name); + changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG; + } } break; case meshtastic_Config_bluetooth_tag: @@ -323,7 +410,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) break; } - saveChanges(SEGMENT_CONFIG); + saveChanges(changes, requiresReboot); } void AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) @@ -389,6 +476,11 @@ void AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) moduleConfig.has_ambient_lighting = true; moduleConfig.ambient_lighting = c.payload_variant.ambient_lighting; break; + case meshtastic_ModuleConfig_paxcounter_tag: + LOG_INFO("Setting module config: Paxcounter\n"); + moduleConfig.has_paxcounter = true; + moduleConfig.paxcounter = c.payload_variant.paxcounter; + break; } saveChanges(SEGMENT_MODULECONFIG); @@ -539,6 +631,11 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_ambient_lighting_tag; res.get_module_config_response.payload_variant.ambient_lighting = moduleConfig.ambient_lighting; break; + case meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG: + LOG_INFO("Getting module config: Paxcounter\n"); + res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag; + res.get_module_config_response.payload_variant.paxcounter = moduleConfig.paxcounter; + break; } // NOTE: The phone app needs to know the ls_secsvalue so it can properly expect sleep behavior. @@ -568,7 +665,7 @@ void AdminModule::handleGetNodeRemoteHardwarePins(const meshtastic_MeshPacket &r continue; } meshtastic_NodeRemoteHardwarePin nodePin = meshtastic_NodeRemoteHardwarePin_init_default; - nodePin.node_num = nodeDB.getNodeNum(); + nodePin.node_num = nodeDB->getNodeNum(); nodePin.pin = moduleConfig.remote_hardware.available_pins[i]; r.get_node_remote_hardware_pins_response.node_remote_hardware_pins[i + 12] = nodePin; } @@ -612,7 +709,9 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r if (Ethernet.linkStatus() == LinkON) { conn.ethernet.status.is_connected = true; conn.ethernet.status.ip_address = Ethernet.localIP(); +#if !MESHTASTIC_EXCLUDE_MQTT conn.ethernet.status.is_mqtt_connected = mqtt && mqtt->isConnectedDirectly(); +#endif conn.ethernet.status.is_syslog_connected = false; // FIXME wire this up } else { conn.ethernet.status.is_connected = false; @@ -622,12 +721,12 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r #if HAS_BLUETOOTH conn.has_bluetooth = true; conn.bluetooth.pin = config.bluetooth.fixed_pin; -#endif #ifdef ARCH_ESP32 conn.bluetooth.is_connected = nimbleBluetooth->isConnected(); conn.bluetooth.rssi = nimbleBluetooth->getRssi(); #elif defined(ARCH_NRF52) conn.bluetooth.is_connected = nrf52Bluetooth->isConnected(); +#endif #endif conn.has_serial = true; // No serial-less devices conn.serial.is_connected = powerFSM.getState() == &stateSERIAL; diff --git a/src/modules/AdminModule.h b/src/modules/AdminModule.h index eb06e7b83..32b32c253 100644 --- a/src/modules/AdminModule.h +++ b/src/modules/AdminModule.h @@ -1,7 +1,7 @@ #pragma once #include "ProtobufModule.h" -#ifdef ARCH_ESP32 -#include "mesh/http/WiFiAPClient.h" +#if HAS_WIFI && !MESHTASTIC_EXCLUDE_WIFI +#include "mesh/wifi/WiFiAPClient.h" #endif /** @@ -50,4 +50,4 @@ class AdminModule : public ProtobufModule void reboot(int32_t seconds); }; -extern AdminModule *adminModule; +extern AdminModule *adminModule; \ No newline at end of file diff --git a/src/modules/AtakPluginModule.cpp b/src/modules/AtakPluginModule.cpp new file mode 100644 index 000000000..64a85e2bf --- /dev/null +++ b/src/modules/AtakPluginModule.cpp @@ -0,0 +1,146 @@ +#include "AtakPluginModule.h" +#include "Default.h" +#include "MeshService.h" +#include "NodeDB.h" +#include "PowerFSM.h" +#include "configuration.h" +#include "main.h" +#include "meshtastic/atak.pb.h" + +extern "C" { +#include "mesh/compression/unishox2.h" +} + +AtakPluginModule *atakPluginModule; + +AtakPluginModule::AtakPluginModule() + : ProtobufModule("atak", meshtastic_PortNum_ATAK_PLUGIN, &meshtastic_TAKPacket_msg), concurrency::OSThread("AtakPluginModule") +{ + ourPortNum = meshtastic_PortNum_ATAK_PLUGIN; +} + +/* +Encompasses the full construction and sending packet to mesh +Will be used for broadcast. +*/ +int32_t AtakPluginModule::runOnce() +{ + return default_broadcast_interval_secs; +} + +bool AtakPluginModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_TAKPacket *r) +{ + return false; +} + +meshtastic_TAKPacket AtakPluginModule::cloneTAKPacketData(meshtastic_TAKPacket *t) +{ + meshtastic_TAKPacket clone = meshtastic_TAKPacket_init_zero; + if (t->has_group) { + clone.has_group = true; + clone.group = t->group; + } + if (t->has_status) { + clone.has_status = true; + clone.status = t->status; + } + if (t->has_contact) { + clone.has_contact = true; + clone.contact = {0}; + } + + if (t->which_payload_variant == meshtastic_TAKPacket_pli_tag) { + clone.which_payload_variant = meshtastic_TAKPacket_pli_tag; + clone.payload_variant.pli = t->payload_variant.pli; + } else if (t->which_payload_variant == meshtastic_TAKPacket_chat_tag) { + clone.which_payload_variant = meshtastic_TAKPacket_chat_tag; + clone.payload_variant.chat = {0}; + } + + return clone; +} + +void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_TAKPacket *t) +{ + // From Phone (EUD) + if (mp.from == 0) { + LOG_DEBUG("Received uncompressed TAK payload from phone with %d bytes\n", mp.decoded.payload.size); + // Compress for LoRA transport + auto compressed = cloneTAKPacketData(t); + compressed.is_compressed = true; + if (t->has_contact) { + auto length = unishox2_compress_simple(t->contact.callsign, strlen(t->contact.callsign), compressed.contact.callsign); + LOG_DEBUG("Uncompressed callsign '%s' - %d bytes\n", t->contact.callsign, strlen(t->contact.callsign)); + LOG_DEBUG("Compressed callsign '%s' - %d bytes\n", t->contact.callsign, length); + + length = unishox2_compress_simple(t->contact.device_callsign, strlen(t->contact.device_callsign), + compressed.contact.device_callsign); + LOG_DEBUG("Uncompressed device_callsign '%s' - %d bytes\n", t->contact.device_callsign, + strlen(t->contact.device_callsign)); + LOG_DEBUG("Compressed device_callsign '%s' - %d bytes\n", compressed.contact.device_callsign, length); + } + if (t->which_payload_variant == meshtastic_TAKPacket_chat_tag) { + auto length = unishox2_compress_simple(t->payload_variant.chat.message, strlen(t->payload_variant.chat.message), + compressed.payload_variant.chat.message); + LOG_DEBUG("Uncompressed chat message '%s' - %d bytes\n", t->payload_variant.chat.message, + strlen(t->payload_variant.chat.message)); + LOG_DEBUG("Compressed chat message '%s' - %d bytes\n", compressed.payload_variant.chat.message, length); + + if (t->payload_variant.chat.has_to) { + compressed.payload_variant.chat.has_to = true; + length = unishox2_compress_simple(t->payload_variant.chat.to, strlen(t->payload_variant.chat.to), + compressed.payload_variant.chat.to); + LOG_DEBUG("Uncompressed chat to '%s' - %d bytes\n", t->payload_variant.chat.to, + strlen(t->payload_variant.chat.to)); + LOG_DEBUG("Compressed chat to '%s' - %d bytes\n", compressed.payload_variant.chat.to, length); + } + } + mp.decoded.payload.size = pb_encode_to_bytes(mp.decoded.payload.bytes, sizeof(mp.decoded.payload.bytes), + meshtastic_TAKPacket_fields, &compressed); + LOG_DEBUG("Final payload size of %d bytes\n", mp.decoded.payload.size); + } else { + if (!t->is_compressed) { + // Not compressed. Something is wrong + LOG_ERROR("Received uncompressed TAKPacket over radio!\n"); + return; + } + + // Decompress for Phone (EUD) + auto decompressedCopy = packetPool.allocCopy(mp); + auto uncompressed = cloneTAKPacketData(t); + uncompressed.is_compressed = false; + if (t->has_contact) { + auto length = + unishox2_decompress_simple(t->contact.callsign, strlen(t->contact.callsign), uncompressed.contact.callsign); + + LOG_DEBUG("Compressed callsign: %d bytes\n", strlen(t->contact.callsign)); + LOG_DEBUG("Decompressed callsign: '%s' @ %d bytes\n", uncompressed.contact.callsign, length); + + length = unishox2_decompress_simple(t->contact.device_callsign, strlen(t->contact.device_callsign), + uncompressed.contact.device_callsign); + + LOG_DEBUG("Compressed device_callsign: %d bytes\n", strlen(t->contact.device_callsign)); + LOG_DEBUG("Decompressed device_callsign: '%s' @ %d bytes\n", uncompressed.contact.device_callsign, length); + } + if (uncompressed.which_payload_variant == meshtastic_TAKPacket_chat_tag) { + auto length = unishox2_decompress_simple(t->payload_variant.chat.message, strlen(t->payload_variant.chat.message), + uncompressed.payload_variant.chat.message); + LOG_DEBUG("Compressed chat message: %d bytes\n", strlen(t->payload_variant.chat.message)); + LOG_DEBUG("Decompressed chat message: '%s' @ %d bytes\n", uncompressed.payload_variant.chat.message, length); + + if (t->payload_variant.chat.has_to) { + uncompressed.payload_variant.chat.has_to = true; + length = unishox2_decompress_simple(t->payload_variant.chat.to, strlen(t->payload_variant.chat.to), + uncompressed.payload_variant.chat.to); + LOG_DEBUG("Compressed chat to: %d bytes\n", strlen(t->payload_variant.chat.to)); + LOG_DEBUG("Decompressed chat to: '%s' @ %d bytes\n", uncompressed.payload_variant.chat.to, length); + } + } + decompressedCopy->decoded.payload.size = + pb_encode_to_bytes(decompressedCopy->decoded.payload.bytes, sizeof(decompressedCopy->decoded.payload), + meshtastic_TAKPacket_fields, &uncompressed); + + service.sendToPhone(decompressedCopy); + } + return; +} \ No newline at end of file diff --git a/src/modules/AtakPluginModule.h b/src/modules/AtakPluginModule.h new file mode 100644 index 000000000..0470a3b32 --- /dev/null +++ b/src/modules/AtakPluginModule.h @@ -0,0 +1,26 @@ +#pragma once +#include "ProtobufModule.h" +#include "meshtastic/atak.pb.h" + +/** + * Waypoint message handling for meshtastic + */ +class AtakPluginModule : public ProtobufModule, private concurrency::OSThread +{ + public: + /** Constructor + * name is for debugging output + */ + AtakPluginModule(); + + protected: + virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_TAKPacket *t) override; + virtual void alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_TAKPacket *t) override; + /* Does our periodic broadcast */ + int32_t runOnce() override; + + private: + meshtastic_TAKPacket cloneTAKPacketData(meshtastic_TAKPacket *t); +}; + +extern AtakPluginModule *atakPluginModule; \ No newline at end of file diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index ade9d0e5a..cbd6fee72 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -1,6 +1,10 @@ #include "configuration.h" +#if ARCH_PORTDUINO +#include "PortduinoGlue.h" +#endif #if HAS_SCREEN #include "CannedMessageModule.h" +#include "Channels.h" #include "FSCommon.h" #include "MeshService.h" #include "NodeDB.h" @@ -8,46 +12,17 @@ #include "detect/ScanI2C.h" #include "mesh/generated/meshtastic/cannedmessages.pb.h" -#include "main.h" // for cardkb_found +#include "main.h" // for cardkb_found +#include "modules/ExternalNotificationModule.h" // for buzzer control +#if !MESHTASTIC_EXCLUDE_GPS +#include "GPS.h" +#endif #ifndef INPUTBROKER_MATRIX_TYPE #define INPUTBROKER_MATRIX_TYPE 0 #endif -#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(DISPLAY_FORCE_SMALL_FONTS) - -// The screen is bigger so use bigger fonts -#define FONT_SMALL ArialMT_Plain_16 -#define FONT_MEDIUM ArialMT_Plain_24 -#define FONT_LARGE ArialMT_Plain_24 -#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 -#endif -#endif -#define FONT_MEDIUM ArialMT_Plain_16 -#define FONT_LARGE ArialMT_Plain_24 -#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" // Remove Canned message screen if no action is taken for some milliseconds #define INACTIVATE_AFTER_MS 20000 @@ -138,17 +113,23 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) // source at all) return 0; } - + if (this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) { + return 0; // Ignore input while sending + } bool validEvent = false; if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP)) { - // LOG_DEBUG("Canned message event UP\n"); - this->runState = CANNED_MESSAGE_RUN_STATE_ACTION_UP; - validEvent = true; + if (this->messagesCount > 0) { + // LOG_DEBUG("Canned message event UP\n"); + this->runState = CANNED_MESSAGE_RUN_STATE_ACTION_UP; + validEvent = true; + } } if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN)) { - // LOG_DEBUG("Canned message event DOWN\n"); - this->runState = CANNED_MESSAGE_RUN_STATE_ACTION_DOWN; - validEvent = true; + if (this->messagesCount > 0) { + // LOG_DEBUG("Canned message event DOWN\n"); + this->runState = CANNED_MESSAGE_RUN_STATE_ACTION_DOWN; + validEvent = true; + } } if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT)) { LOG_DEBUG("Canned message event Select\n"); @@ -163,9 +144,14 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) } if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL)) { LOG_DEBUG("Canned message event Cancel\n"); - // emulate a timeout. Same result - this->lastTouchMillis = 0; - validEvent = true; + UIFrameEvent e = {false, true}; + e.frameChanged = true; + this->currentMessageIndex = -1; + this->freetext = ""; // clear freetext + this->cursor = 0; + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; + this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + this->notifyObservers(&e); } if ((event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK)) || (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) || @@ -175,10 +161,10 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) if (!event->kbchar) { if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) { this->payload = 0xb4; - this->destSelect = true; + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE; } else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) { this->payload = 0xb7; - this->destSelect = true; + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE; } } else { // pass the pressed key @@ -212,16 +198,21 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) if (validEvent) { // Let runOnce to be called immediately. - setIntervalFromNow(0); + if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_SELECT) { + setIntervalFromNow(0); // on fast keypresses, this isn't fast enough. + } else { + runOnce(); + } } return 0; } -void CannedMessageModule::sendText(NodeNum dest, const char *message, bool wantReplies) +void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const char *message, bool wantReplies) { meshtastic_MeshPacket *p = allocDataPacket(); p->to = dest; + p->channel = channel; p->want_ack = true; p->decoded.payload.size = strlen(message); memcpy(p->decoded.payload.bytes, message, p->decoded.payload.size); @@ -233,7 +224,9 @@ void CannedMessageModule::sendText(NodeNum dest, const char *message, bool wantR LOG_INFO("Sending message id=%d, dest=%x, msg=%.*s\n", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes); - service.sendToMesh(p); + service.sendToMesh( + p, RX_SRC_LOCAL, + true); // send to mesh, cc to phone. Even if there's no phone connected, this stores the message to match ACKs } int32_t CannedMessageModule::runOnce() @@ -244,14 +237,15 @@ int32_t CannedMessageModule::runOnce() } // LOG_DEBUG("Check status\n"); UIFrameEvent e = {false, true}; - if (this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) { + if ((this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) || + (this->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED)) { // TODO: might have some feedback of sendig state this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; e.frameChanged = true; this->currentMessageIndex = -1; this->freetext = ""; // clear freetext this->cursor = 0; - this->destSelect = false; + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; this->notifyObservers(&e); } else if (((this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT)) && ((millis() - this->lastTouchMillis) > INACTIVATE_AFTER_MS)) { @@ -261,13 +255,13 @@ int32_t CannedMessageModule::runOnce() this->currentMessageIndex = -1; this->freetext = ""; // clear freetext this->cursor = 0; - this->destSelect = false; + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; this->notifyObservers(&e); } else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_SELECT) { if (this->payload == CANNED_MESSAGE_RUN_STATE_FREETEXT) { if (this->freetext.length() > 0) { - sendText(this->dest, this->freetext.c_str(), true); + sendText(this->dest, indexChannels[this->channel], this->freetext.c_str(), true); this->runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE; } else { LOG_DEBUG("Reset message is empty.\n"); @@ -279,7 +273,7 @@ int32_t CannedMessageModule::runOnce() powerFSM.trigger(EVENT_PRESS); return INT32_MAX; } else { - sendText(NODENUM_BROADCAST, this->messages[this->currentMessageIndex], true); + sendText(NODENUM_BROADCAST, channels.getPrimaryIndex(), this->messages[this->currentMessageIndex], true); } this->runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE; } else { @@ -291,7 +285,7 @@ int32_t CannedMessageModule::runOnce() this->currentMessageIndex = -1; this->freetext = ""; // clear freetext this->cursor = 0; - this->destSelect = false; + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; this->notifyObservers(&e); return 2000; } else if ((this->runState != CANNED_MESSAGE_RUN_STATE_FREETEXT) && (this->currentMessageIndex == -1)) { @@ -304,7 +298,7 @@ int32_t CannedMessageModule::runOnce() this->currentMessageIndex = getPrevIndex(); this->freetext = ""; // clear freetext this->cursor = 0; - this->destSelect = false; + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; LOG_DEBUG("MOVE UP (%d):%s\n", this->currentMessageIndex, this->getCurrentMessage()); } @@ -313,28 +307,41 @@ int32_t CannedMessageModule::runOnce() this->currentMessageIndex = this->getNextIndex(); this->freetext = ""; // clear freetext this->cursor = 0; - this->destSelect = false; + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; LOG_DEBUG("MOVE DOWN (%d):%s\n", this->currentMessageIndex, this->getCurrentMessage()); } } else if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT || this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) { switch (this->payload) { case 0xb4: // left - if (this->destSelect) { - size_t numMeshNodes = nodeDB.getNumMeshNodes(); + if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) { + size_t numMeshNodes = nodeDB->getNumMeshNodes(); if (this->dest == NODENUM_BROADCAST) { - this->dest = nodeDB.getNodeNum(); + this->dest = nodeDB->getNodeNum(); } for (unsigned int i = 0; i < numMeshNodes; i++) { - if (nodeDB.getMeshNodeByIndex(i)->num == this->dest) { + if (nodeDB->getMeshNodeByIndex(i)->num == this->dest) { this->dest = - (i > 0) ? nodeDB.getMeshNodeByIndex(i - 1)->num : nodeDB.getMeshNodeByIndex(numMeshNodes - 1)->num; + (i > 0) ? nodeDB->getMeshNodeByIndex(i - 1)->num : nodeDB->getMeshNodeByIndex(numMeshNodes - 1)->num; break; } } - if (this->dest == nodeDB.getNodeNum()) { + if (this->dest == nodeDB->getNodeNum()) { this->dest = NODENUM_BROADCAST; } + } else if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL) { + for (unsigned int i = 0; i < channels.getNumChannels(); i++) { + if ((channels.getByIndex(i).role == meshtastic_Channel_Role_SECONDARY) || + (channels.getByIndex(i).role == meshtastic_Channel_Role_PRIMARY)) { + indexChannels[numChannels] = i; + numChannels++; + } + } + if (this->channel == 0) { + this->channel = numChannels - 1; + } else { + this->channel--; + } } else { if (this->cursor > 0) { this->cursor--; @@ -342,21 +349,34 @@ int32_t CannedMessageModule::runOnce() } break; case 0xb7: // right - if (this->destSelect) { - size_t numMeshNodes = nodeDB.getNumMeshNodes(); + if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) { + size_t numMeshNodes = nodeDB->getNumMeshNodes(); if (this->dest == NODENUM_BROADCAST) { - this->dest = nodeDB.getNodeNum(); + this->dest = nodeDB->getNodeNum(); } for (unsigned int i = 0; i < numMeshNodes; i++) { - if (nodeDB.getMeshNodeByIndex(i)->num == this->dest) { + if (nodeDB->getMeshNodeByIndex(i)->num == this->dest) { this->dest = - (i < numMeshNodes - 1) ? nodeDB.getMeshNodeByIndex(i + 1)->num : nodeDB.getMeshNodeByIndex(0)->num; + (i < numMeshNodes - 1) ? nodeDB->getMeshNodeByIndex(i + 1)->num : nodeDB->getMeshNodeByIndex(0)->num; break; } } - if (this->dest == nodeDB.getNodeNum()) { + if (this->dest == nodeDB->getNodeNum()) { this->dest = NODENUM_BROADCAST; } + } else if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL) { + for (unsigned int i = 0; i < channels.getNumChannels(); i++) { + if ((channels.getByIndex(i).role == meshtastic_Channel_Role_SECONDARY) || + (channels.getByIndex(i).role == meshtastic_Channel_Role_PRIMARY)) { + indexChannels[numChannels] = i; + numChannels++; + } + } + if (this->channel == numChannels - 1) { + this->channel = 0; + } else { + this->channel++; + } } else { if (this->cursor < this->freetext.length()) { this->cursor++; @@ -381,16 +401,60 @@ int32_t CannedMessageModule::runOnce() } break; case 0x09: // tab - if (this->destSelect) { - this->destSelect = false; + case 0x91: // alt+t for T-Deck that doesn't have a tab key + if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL) { + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE; + } else if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) { + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL; } else { - this->destSelect = true; + this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE; } break; case 0xb4: // left case 0xb7: // right // already handled above break; + // handle fn+s for shutdown + case 0x9b: + if (screen) + screen->startShutdownScreen(); + shutdownAtMsec = millis() + DEFAULT_SHUTDOWN_SECONDS * 1000; + runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + break; + // and fn+r for reboot + case 0x90: + if (screen) + screen->startRebootScreen(); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + break; + case 0x9e: // toggle GPS like triple press does + if (gps != nullptr) { + gps->toggleGpsMode(); + } + if (screen) + screen->forceDisplay(); + runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + break; + + // mute (switch off/toggle) external notifications on fn+m + case 0xac: + if (moduleConfig.external_notification.enabled == true) { + if (externalNotificationModule->getMute()) { + externalNotificationModule->setMute(false); + runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + } else { + externalNotificationModule->stopNow(); // this will turn off all GPIO and sounds and idle the loop + externalNotificationModule->setMute(true); + runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + } + } + break; + case 0xaf: // fn+space send network ping like double press does + service.refreshLocalMeshNode(); + service.sendNetworkPing(NODENUM_BROADCAST, true); + runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; + break; default: if (this->cursor == this->freetext.length()) { this->freetext += this->payload; @@ -444,7 +508,7 @@ const char *CannedMessageModule::getNodeName(NodeNum node) if (node == NODENUM_BROADCAST) { return "Broadcast"; } else { - meshtastic_NodeInfoLite *info = nodeDB.getMeshNode(node); + meshtastic_NodeInfoLite *info = nodeDB->getMeshNode(node); if (info != NULL) { return info->user.long_name; } else { @@ -483,7 +547,18 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st { char buffer[50]; - if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) { + if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) { + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_MEDIUM); + String displayString; + if (this->ack) { + displayString = "Delivered to\n%s"; + } else { + displayString = "Delivery failed\nto %s"; + } + display->drawStringf(display->getWidth() / 2 + x, 0 + y + 12, buffer, displayString, + cannedMessageModule->getNodeName(this->incoming)); + } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) { display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(FONT_MEDIUM); display->drawString(display->getWidth() / 2 + x, 0 + y + 12, "Sending..."); @@ -494,19 +569,39 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) { display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); - if (this->destSelect) { + if (this->destSelect != CANNED_MESSAGE_DESTINATION_TYPE_NONE) { display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); display->setColor(BLACK); - display->drawStringf(1 + x, 0 + y, buffer, "To: %s", cannedMessageModule->getNodeName(this->dest)); } - display->drawStringf(0 + x, 0 + y, buffer, "To: %s", cannedMessageModule->getNodeName(this->dest)); - // used chars right aligned - uint16_t charsLeft = - meshtastic_Constants_DATA_PAYLOAD_LEN - this->freetext.length() - (moduleConfig.canned_message.send_bell ? 1 : 0); - snprintf(buffer, sizeof(buffer), "%d left", charsLeft); - display->drawString(x + display->getWidth() - display->getStringWidth(buffer), y + 0, buffer); - if (this->destSelect) { - display->drawString(x + display->getWidth() - display->getStringWidth(buffer) - 1, y + 0, buffer); + switch (this->destSelect) { + case CANNED_MESSAGE_DESTINATION_TYPE_NODE: + display->drawStringf(1 + x, 0 + y, buffer, "To: >%s<@%s", cannedMessageModule->getNodeName(this->dest), + channels.getName(indexChannels[this->channel])); + display->drawStringf(0 + x, 0 + y, buffer, "To: >%s<@%s", cannedMessageModule->getNodeName(this->dest), + channels.getName(indexChannels[this->channel])); + break; + case CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL: + display->drawStringf(1 + x, 0 + y, buffer, "To: %s@>%s<", cannedMessageModule->getNodeName(this->dest), + channels.getName(indexChannels[this->channel])); + display->drawStringf(0 + x, 0 + y, buffer, "To: %s@>%s<", cannedMessageModule->getNodeName(this->dest), + channels.getName(indexChannels[this->channel])); + break; + default: + if (display->getWidth() > 128) { + display->drawStringf(0 + x, 0 + y, buffer, "To: %s@%s", cannedMessageModule->getNodeName(this->dest), + channels.getName(indexChannels[this->channel])); + } else { + display->drawStringf(0 + x, 0 + y, buffer, "To: %.5s@%.5s", cannedMessageModule->getNodeName(this->dest), + channels.getName(indexChannels[this->channel])); + } + break; + } + // used chars right aligned, only when not editing the destination + if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NONE) { + uint16_t charsLeft = + meshtastic_Constants_DATA_PAYLOAD_LEN - this->freetext.length() - (moduleConfig.canned_message.send_bell ? 1 : 0); + snprintf(buffer, sizeof(buffer), "%d left", charsLeft); + display->drawString(x + display->getWidth() - display->getStringWidth(buffer), y + 0, buffer); } display->setColor(WHITE); display->drawStringMaxWidth( @@ -546,11 +641,32 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st } } +ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket &mp) +{ + if (mp.decoded.portnum == meshtastic_PortNum_ROUTING_APP) { + // look for a request_id + if (mp.decoded.request_id != 0) { + UIFrameEvent e = {false, true}; + e.frameChanged = true; + this->runState = CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED; + this->incoming = service.getNodenumFromRequestId(mp.decoded.request_id); + meshtastic_Routing decoded = meshtastic_Routing_init_default; + pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_Routing_fields, &decoded); + this->ack = decoded.error_reason == meshtastic_Routing_Error_NONE; + this->notifyObservers(&e); + // run the next time 2 seconds later + setIntervalFromNow(2000); + } + } + + return ProcessMessage::CONTINUE; +} + void CannedMessageModule::loadProtoForModule() { - if (!nodeDB.loadProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, + if (nodeDB->loadProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, sizeof(meshtastic_CannedMessageModuleConfig), &meshtastic_CannedMessageModuleConfig_msg, - &cannedMessageModuleConfig)) { + &cannedMessageModuleConfig) != LoadFileResult::SUCCESS) { installDefaultCannedMessageModuleConfig(); } } @@ -569,8 +685,8 @@ bool CannedMessageModule::saveProtoForModule() FS.mkdir("/prefs"); #endif - okay &= nodeDB.saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, - &meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig); + okay &= nodeDB->saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, + &meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig); return okay; } @@ -650,4 +766,4 @@ String CannedMessageModule::drawWithCursor(String text, int cursor) return result; } -#endif +#endif \ No newline at end of file diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index 98467215e..4802be078 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -9,11 +9,18 @@ enum cannedMessageModuleRunState { CANNED_MESSAGE_RUN_STATE_ACTIVE, CANNED_MESSAGE_RUN_STATE_FREETEXT, CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE, + CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED, CANNED_MESSAGE_RUN_STATE_ACTION_SELECT, CANNED_MESSAGE_RUN_STATE_ACTION_UP, CANNED_MESSAGE_RUN_STATE_ACTION_DOWN, }; +enum cannedMessageDestinationType { + CANNED_MESSAGE_DESTINATION_TYPE_NONE, + CANNED_MESSAGE_DESTINATION_TYPE_NODE, + CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL +}; + #define CANNED_MESSAGE_MODULE_MESSAGE_MAX_COUNT 50 /** * Sum of CannedMessageModuleConfig part sizes. @@ -37,19 +44,33 @@ class CannedMessageModule : public SinglePortModule, public Observabledecoded.portnum) { + case meshtastic_PortNum_TEXT_MESSAGE_APP: + case meshtastic_PortNum_ROUTING_APP: + return true; + default: + return false; + } + } + protected: virtual int32_t runOnce() override; - void sendText(NodeNum dest, const char *message, bool wantReplies); + void sendText(NodeNum dest, ChannelIndex channel, const char *message, bool wantReplies); int splitConfiguredMessages(); int getNextIndex(); @@ -63,6 +84,12 @@ class CannedMessageModule : public SinglePortModule, public Observable= getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs) && + if ((millis() - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs) && hasDetectionEvent()) { sendDetectionMessage(); return DELAYED_INTERVAL; @@ -58,7 +58,8 @@ int32_t DetectionSensorModule::runOnce() // of heartbeat. We only do this if the minimum broadcast interval is greater than zero, otherwise we'll only broadcast state // change detections. else if (moduleConfig.detection_sensor.state_broadcast_secs > 0 && - (millis() - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.detection_sensor.state_broadcast_secs)) { + (millis() - lastSentToMesh) >= + Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.state_broadcast_secs)) { sendCurrentStateMessage(); return DELAYED_INTERVAL; } diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 6a7641b04..a38b231af 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -20,11 +20,10 @@ #include "Router.h" #include "buzz/buzz.h" #include "configuration.h" +#include "main.h" #include "mesh/generated/meshtastic/rtttl.pb.h" #include -#include "main.h" - #ifdef HAS_NCP5623 #include @@ -54,6 +53,8 @@ bool ascending = true; #endif #define EXT_NOTIFICATION_MODULE_OUTPUT_MS 1000 +#define EXT_NOTIFICATION_DEFAULT_THREAD_MS 25 + #define ASCII_BELL 0x07 meshtastic_RTTTLConfig rtttlConfig; @@ -71,11 +72,16 @@ int32_t ExternalNotificationModule::runOnce() if (!moduleConfig.external_notification.enabled) { return INT32_MAX; // we don't need this thread here... } else { - if ((nagCycleCutoff < millis()) && !rtttl::isPlaying()) { + + bool isPlaying = rtttl::isPlaying(); +#ifdef HAS_I2S + isPlaying = rtttl::isPlaying() || audioThread->isPlaying(); +#endif + if ((nagCycleCutoff < millis()) && !isPlaying) { // let the song finish if we reach timeout nagCycleCutoff = UINT32_MAX; LOG_INFO("Turning off external notification: "); - for (int i = 0; i < 2; i++) { + for (int i = 0; i < 3; i++) { setExternalOff(i); externalTurnedOn[i] = 0; LOG_INFO("%d ", i); @@ -132,6 +138,16 @@ int32_t ExternalNotificationModule::runOnce() #endif } + // Play RTTTL over i2s audio interface if enabled as buzzer +#ifdef HAS_I2S + if (moduleConfig.external_notification.use_i2s_as_buzzer) { + if (audioThread->isPlaying()) { + // Continue playing + } else if (isNagging && (nagCycleCutoff >= millis())) { + audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); + } + } +#endif // now let the PWM buzzer play if (moduleConfig.external_notification.use_pwm) { if (rtttl::isPlaying()) { @@ -142,7 +158,7 @@ int32_t ExternalNotificationModule::runOnce() } } - return 25; + return EXT_NOTIFICATION_DEFAULT_THREAD_MS; } } @@ -175,6 +191,7 @@ void ExternalNotificationModule::setExternalOn(uint8_t index) digitalWrite(output, (moduleConfig.external_notification.active ? true : false)); break; } + #ifdef HAS_NCP5623 if (rgb_found.type == ScanI2C::NCP5623) { rgb.setColor(red, green, blue); @@ -226,6 +243,9 @@ bool ExternalNotificationModule::getExternal(uint8_t index) void ExternalNotificationModule::stopNow() { rtttl::stop(); +#ifdef HAS_I2S + audioThread->stop(); +#endif nagCycleCutoff = 1; // small value isNagging = false; setIntervalFromNow(0); @@ -246,6 +266,7 @@ ExternalNotificationModule::ExternalNotificationModule() // moduleConfig.external_notification.alert_message = true; // moduleConfig.external_notification.alert_message_buzzer = true; // moduleConfig.external_notification.alert_message_vibra = true; + // moduleConfig.external_notification.use_i2s_as_buzzer = true; // moduleConfig.external_notification.active = true; // moduleConfig.external_notification.alert_bell = 1; @@ -255,12 +276,19 @@ ExternalNotificationModule::ExternalNotificationModule() // moduleConfig.external_notification.output_vibra = 28; // RAK4631 IO7 // moduleConfig.external_notification.nag_timeout = 300; + // T-Watch / T-Deck i2s audio as buzzer: + // moduleConfig.external_notification.enabled = true; + // moduleConfig.external_notification.nag_timeout = 300; + // moduleConfig.external_notification.output_ms = 1000; + // moduleConfig.external_notification.use_i2s_as_buzzer = true; + // moduleConfig.external_notification.alert_message_buzzer = true; + if (moduleConfig.external_notification.enabled) { - if (!nodeDB.loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig), - &meshtastic_RTTTLConfig_msg, &rtttlConfig)) { + if (nodeDB->loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig), + &meshtastic_RTTTLConfig_msg, &rtttlConfig) != LoadFileResult::SUCCESS) { memset(rtttlConfig.ringtone, 0, sizeof(rtttlConfig.ringtone)); strncpy(rtttlConfig.ringtone, - "a:d=8,o=5,b=125:4d#6,a#,2d#6,16p,g#,4a#,4d#.,p,16g,16a#,d#6,a#,f6,2d#6,16p,c#.6,16c6,16a#,g#.,2a#", + "24:d=32,o=5,b=565:f6,p,f6,4p,p,f6,p,f6,2p,p,b6,p,b6,p,b6,p,b6,p,b,p,b,p,b,p,b,p,b,p,b,p,b,p,b,1p.,2p.,p", sizeof(rtttlConfig.ringtone)); } @@ -308,15 +336,14 @@ ExternalNotificationModule::ExternalNotificationModule() ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshPacket &mp) { - if (moduleConfig.external_notification.enabled) { -#if T_WATCH_S3 + if (moduleConfig.external_notification.enabled && !isMuted) { +#ifdef T_WATCH_S3 drv.setWaveform(0, 75); drv.setWaveform(1, 56); drv.setWaveform(2, 0); drv.go(); #endif - if (getFrom(&mp) != nodeDB.getNodeNum()) { - + if (getFrom(&mp) != nodeDB->getNodeNum()) { // Check if the message contains a bell character. Don't do this loop for every pin, just once. auto &p = mp.decoded; bool containsBell = false; @@ -359,7 +386,11 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP if (!moduleConfig.external_notification.use_pwm) { setExternalOn(2); } else { +#ifdef HAS_I2S + audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); +#else rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); +#endif } if (moduleConfig.external_notification.nag_timeout) { nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; @@ -394,10 +425,16 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP if (moduleConfig.external_notification.alert_message_buzzer) { LOG_INFO("externalNotificationModule - Notification Module (Buzzer)\n"); isNagging = true; - if (!moduleConfig.external_notification.use_pwm) { + if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) { setExternalOn(2); } else { +#ifdef HAS_I2S + if (moduleConfig.external_notification.use_i2s_as_buzzer) { + audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); + } +#else rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); +#endif } if (moduleConfig.external_notification.nag_timeout) { nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; @@ -408,7 +445,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP setIntervalFromNow(0); // run once so we know if we should do something } } else { - LOG_INFO("External Notification Module Disabled\n"); + LOG_INFO("External Notification Module Disabled or muted\n"); } return ProcessMessage::CONTINUE; // Let others look at this message also if they want @@ -469,6 +506,6 @@ void ExternalNotificationModule::handleSetRingtone(const char *from_msg) } if (changed) { - nodeDB.saveProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, &meshtastic_RTTTLConfig_msg, &rtttlConfig); + nodeDB->saveProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, &meshtastic_RTTTLConfig_msg, &rtttlConfig); } } \ No newline at end of file diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h index 3331ec428..08e72c35a 100644 --- a/src/modules/ExternalNotificationModule.h +++ b/src/modules/ExternalNotificationModule.h @@ -38,6 +38,9 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency: void setExternalOff(uint8_t index = 0); bool getExternal(uint8_t index = 0); + void setMute(bool mute) { isMuted = mute; } + bool getMute() { return isMuted; } + void stopNow(); void handleGetRingtone(const meshtastic_MeshPacket &req, meshtastic_AdminMessage *response); @@ -56,6 +59,8 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency: bool isNagging = false; + bool isMuted = false; + virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, meshtastic_AdminMessage *response) override; diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 526a1c7d8..5ac45577e 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -1,40 +1,72 @@ #include "configuration.h" +#if !MESHTASTIC_EXCLUDE_INPUTBROKER #include "input/InputBroker.h" #include "input/RotaryEncoderInterruptImpl1.h" #include "input/TrackballInterruptImpl1.h" #include "input/UpDownInterruptImpl1.h" #include "input/cardKbI2cImpl.h" #include "input/kbMatrixImpl.h" +#endif #include "modules/AdminModule.h" +#if !MESHTASTIC_EXCLUDE_ATAK +#include "modules/AtakPluginModule.h" +#endif +#if !MESHTASTIC_EXCLUDE_CANNEDMESSAGES #include "modules/CannedMessageModule.h" +#endif +#if !MESHTASTIC_EXCLUDE_DETECTIONSENSOR #include "modules/DetectionSensorModule.h" +#endif +#if !MESHTASTIC_EXCLUDE_NEIGHBORINFO #include "modules/NeighborInfoModule.h" +#endif #include "modules/NodeInfoModule.h" +#if !MESHTASTIC_EXCLUDE_GPS #include "modules/PositionModule.h" +#endif +#if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE #include "modules/RemoteHardwareModule.h" -#include "modules/ReplyModule.h" +#endif #include "modules/RoutingModule.h" #include "modules/TextMessageModule.h" +#if !MESHTASTIC_EXCLUDE_TRACEROUTE #include "modules/TraceRouteModule.h" +#endif +#if !MESHTASTIC_EXCLUDE_WAYPOINT #include "modules/WaypointModule.h" +#endif +#if ARCH_PORTDUINO +#include "input/LinuxInputImpl.h" +#endif #if HAS_TELEMETRY #include "modules/Telemetry/DeviceTelemetry.h" #endif -#if HAS_SENSOR +#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "modules/Telemetry/AirQualityTelemetry.h" #include "modules/Telemetry/EnvironmentTelemetry.h" #endif -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) +#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY #include "modules/Telemetry/PowerTelemetry.h" #endif #ifdef ARCH_ESP32 +#if defined(USE_SX1280) && !MESHTASTIC_EXCLUDE_AUDIO #include "modules/esp32/AudioModule.h" +#endif +#if !MESHTASTIC_EXCLUDE_PAXCOUNTER +#include "modules/esp32/PaxcounterModule.h" +#endif +#if !MESHTASTIC_EXCLUDE_STOREFORWARD #include "modules/esp32/StoreForwardModule.h" #endif +#endif #if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) +#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION #include "modules/ExternalNotificationModule.h" +#endif +#if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS #include "modules/RangeTestModule.h" -#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) +#endif +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_SERIAL #include "modules/SerialModule.h" #endif #endif @@ -44,24 +76,39 @@ void setupModules() { if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) { -#if HAS_BUTTON +#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER inputBroker = new InputBroker(); #endif adminModule = new AdminModule(); nodeInfoModule = new NodeInfoModule(); +#if !MESHTASTIC_EXCLUDE_GPS positionModule = new PositionModule(); +#endif +#if !MESHTASTIC_EXCLUDE_WAYPOINT waypointModule = new WaypointModule(); +#endif textMessageModule = new TextMessageModule(); +#if !MESHTASTIC_EXCLUDE_TRACEROUTE traceRouteModule = new TraceRouteModule(); +#endif +#if !MESHTASTIC_EXCLUDE_NEIGHBORINFO neighborInfoModule = new NeighborInfoModule(); +#endif +#if !MESHTASTIC_EXCLUDE_DETECTIONSENSOR detectionSensorModule = new DetectionSensorModule(); - +#endif +#if !MESHTASTIC_EXCLUDE_ATAK + atakPluginModule = new AtakPluginModule(); +#endif // Note: if the rest of meshtastic doesn't need to explicitly use your module, you do not need to assign the instance // to a global variable. +#if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE new RemoteHardwareModule(); - new ReplyModule(); -#if HAS_BUTTON +#endif + // Example: Put your module here + // new ReplyModule(); +#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1(); if (!rotaryEncoderInterruptImpl1->init()) { delete rotaryEncoderInterruptImpl1; @@ -79,45 +126,63 @@ void setupModules() kbMatrixImpl->init(); #endif // INPUTBROKER_MATRIX_TYPE #endif // HAS_BUTTON -#if HAS_TRACKBALL +#if ARCH_PORTDUINO + aLinuxInputImpl = new LinuxInputImpl(); + aLinuxInputImpl->init(); +#endif +#if HAS_TRACKBALL && !MESHTASTIC_EXCLUDE_INPUTBROKER trackballInterruptImpl1 = new TrackballInterruptImpl1(); trackballInterruptImpl1->init(); #endif -#if HAS_SCREEN +#if HAS_SCREEN && !MESHTASTIC_EXCLUDE_CANNEDMESSAGES cannedMessageModule = new CannedMessageModule(); #endif -#if HAS_TELEMETRY +#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) new DeviceTelemetryModule(); #endif -#if HAS_SENSOR +#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR new EnvironmentTelemetryModule(); if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) { new AirQualityTelemetryModule(); } #endif -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) +#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY new PowerTelemetryModule(); #endif #if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ !defined(CONFIG_IDF_TARGET_ESP32C3) +#if !MESHTASTIC_EXCLUDE_SERIAL new SerialModule(); #endif +#endif #ifdef ARCH_ESP32 // Only run on an esp32 based device. +#if defined(USE_SX1280) && !MESHTASTIC_EXCLUDE_AUDIO audioModule = new AudioModule(); - +#endif +#if !MESHTASTIC_EXCLUDE_STOREFORWARD storeForwardModule = new StoreForwardModule(); #endif +#if !MESHTASTIC_EXCLUDE_PAXCOUNTER + paxcounterModule = new PaxcounterModule(); +#endif +#endif #if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) +#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION externalNotificationModule = new ExternalNotificationModule(); +#endif +#if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS new RangeTestModule(); +#endif #endif } else { adminModule = new AdminModule(); #if HAS_TELEMETRY new DeviceTelemetryModule(); #endif +#if !MESHTASTIC_EXCLUDE_TRACEROUTE traceRouteModule = new TraceRouteModule(); +#endif } // NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra // acks diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index 6060e384e..771e88a9a 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -1,13 +1,11 @@ #include "NeighborInfoModule.h" +#include "Default.h" #include "MeshService.h" #include "NodeDB.h" #include "RTC.h" -#define MAX_NUM_NEIGHBORS 10 // also defined in NeighborInfo protobuf options NeighborInfoModule *neighborInfoModule; -static const char *neighborInfoConfigFile = "/prefs/neighbors.proto"; - /* Prints a single neighbor info packet and associated neighbors Uses LOG_DEBUG, which equates to Console.log @@ -16,87 +14,36 @@ NOTE: For debugging only void NeighborInfoModule::printNeighborInfo(const char *header, const meshtastic_NeighborInfo *np) { LOG_DEBUG("%s NEIGHBORINFO PACKET from Node 0x%x to Node 0x%x (last sent by 0x%x)\n", header, np->node_id, - nodeDB.getNodeNum(), np->last_sent_by_id); - LOG_DEBUG("----------------\n"); + nodeDB->getNodeNum(), np->last_sent_by_id); LOG_DEBUG("Packet contains %d neighbors\n", np->neighbors_count); for (int i = 0; i < np->neighbors_count; i++) { LOG_DEBUG("Neighbor %d: node_id=0x%x, snr=%.2f\n", i, np->neighbors[i].node_id, np->neighbors[i].snr); } - LOG_DEBUG("----------------\n"); -} -/* -Prints the nodeDB nodes so we can see whose nodeInfo we have -NOTE: for debugging only -*/ -void NeighborInfoModule::printNodeDBNodes(const char *header) -{ - int num_nodes = nodeDB.getNumMeshNodes(); - LOG_DEBUG("%s NODEDB SELECTION from Node 0x%x:\n", header, nodeDB.getNodeNum()); - LOG_DEBUG("----------------\n"); - LOG_DEBUG("DB contains %d nodes\n", num_nodes); - for (int i = 0; i < num_nodes; i++) { - const meshtastic_NodeInfoLite *dbEntry = nodeDB.getMeshNodeByIndex(i); - LOG_DEBUG(" Node %d: node_id=0x%x, snr=%.2f\n", i, dbEntry->num, dbEntry->snr); - } - LOG_DEBUG("----------------\n"); } /* Prints the nodeDB neighbors NOTE: for debugging only */ -void NeighborInfoModule::printNodeDBNeighbors(const char *header) +void NeighborInfoModule::printNodeDBNeighbors() { - int num_neighbors = getNumNeighbors(); - LOG_DEBUG("%s NODEDB SELECTION from Node 0x%x:\n", header, nodeDB.getNodeNum()); - LOG_DEBUG("----------------\n"); - LOG_DEBUG("DB contains %d neighbors\n", num_neighbors); - for (int i = 0; i < num_neighbors; i++) { - const meshtastic_Neighbor *dbEntry = getNeighborByIndex(i); - LOG_DEBUG(" Node %d: node_id=0x%x, snr=%.2f\n", i, dbEntry->node_id, dbEntry->snr); + LOG_DEBUG("Our NodeDB contains %d neighbors\n", neighbors.size()); + for (size_t i = 0; i < neighbors.size(); i++) { + LOG_DEBUG("Node %d: node_id=0x%x, snr=%.2f\n", i, neighbors[i].node_id, neighbors[i].snr); } - LOG_DEBUG("----------------\n"); -} - -/* -Prints the nodeDB with selectors for the neighbors we've chosen to send (inefficiently) -Uses LOG_DEBUG, which equates to Console.log -NOTE: For debugging only -*/ -void NeighborInfoModule::printNodeDBSelection(const char *header, const meshtastic_NeighborInfo *np) -{ - int num_neighbors = getNumNeighbors(); - LOG_DEBUG("%s NODEDB SELECTION from Node 0x%x:\n", header, nodeDB.getNodeNum()); - LOG_DEBUG("----------------\n"); - LOG_DEBUG("Selected %d neighbors of %d DB neighbors\n", np->neighbors_count, num_neighbors); - for (int i = 0; i < num_neighbors; i++) { - meshtastic_Neighbor *dbEntry = getNeighborByIndex(i); - bool chosen = false; - for (int j = 0; j < np->neighbors_count; j++) { - if (np->neighbors[j].node_id == dbEntry->node_id) { - chosen = true; - } - } - if (!chosen) { - LOG_DEBUG(" Node %d: neighbor=0x%x, snr=%.2f\n", i, dbEntry->node_id, dbEntry->snr); - } else { - LOG_DEBUG("---> Node %d: neighbor=0x%x, snr=%.2f\n", i, dbEntry->node_id, dbEntry->snr); - } - } - LOG_DEBUG("----------------\n"); } /* Send our initial owner announcement 35 seconds after we start (to give network time to setup) */ NeighborInfoModule::NeighborInfoModule() : ProtobufModule("neighborinfo", meshtastic_PortNum_NEIGHBORINFO_APP, &meshtastic_NeighborInfo_msg), - concurrency::OSThread("NeighborInfoModule"), neighbors(neighborState.neighbors), - numNeighbors(&neighborState.neighbors_count) + concurrency::OSThread("NeighborInfoModule") { ourPortNum = meshtastic_PortNum_NEIGHBORINFO_APP; if (moduleConfig.neighbor_info.enabled) { - this->loadProtoForModule(); - setIntervalFromNow(35 * 1000); + isPromiscuous = true; // Update neighbors from all packets + setIntervalFromNow( + Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs)); } else { LOG_DEBUG("NeighborInfoModule is disabled\n"); disable(); @@ -110,76 +57,43 @@ Assumes that the neighborInfo packet has been allocated */ uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighborInfo) { - uint my_node_id = nodeDB.getNodeNum(); + NodeNum my_node_id = nodeDB->getNodeNum(); neighborInfo->node_id = my_node_id; neighborInfo->last_sent_by_id = my_node_id; neighborInfo->node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval; - int num_neighbors = cleanUpNeighbors(); + cleanUpNeighbors(); - for (int i = 0; i < num_neighbors; i++) { - meshtastic_Neighbor *dbEntry = getNeighborByIndex(i); - if ((neighborInfo->neighbors_count < MAX_NUM_NEIGHBORS) && (dbEntry->node_id != my_node_id)) { - neighborInfo->neighbors[neighborInfo->neighbors_count].node_id = dbEntry->node_id; - neighborInfo->neighbors[neighborInfo->neighbors_count].snr = dbEntry->snr; + for (auto nbr : neighbors) { + if ((neighborInfo->neighbors_count < MAX_NUM_NEIGHBORS) && (nbr.node_id != my_node_id)) { + neighborInfo->neighbors[neighborInfo->neighbors_count].node_id = nbr.node_id; + neighborInfo->neighbors[neighborInfo->neighbors_count].snr = nbr.snr; // Note: we don't set the last_rx_time and node_broadcast_intervals_secs here, because we don't want to send this over // the mesh neighborInfo->neighbors_count++; } } - printNodeDBNodes("DBSTATE"); - printNodeDBNeighbors("NEIGHBORS"); - printNodeDBSelection("COLLECTED", neighborInfo); + printNodeDBNeighbors(); return neighborInfo->neighbors_count; } /* -Remove neighbors from the database that we haven't heard from in a while -@returns new number of neighbors + Remove neighbors from the database that we haven't heard from in a while */ -size_t NeighborInfoModule::cleanUpNeighbors() +void NeighborInfoModule::cleanUpNeighbors() { uint32_t now = getTime(); - int num_neighbors = getNumNeighbors(); - NodeNum my_node_id = nodeDB.getNodeNum(); - - // Find neighbors to remove - std::vector indices_to_remove; - for (int i = 0; i < num_neighbors; i++) { - meshtastic_Neighbor *dbEntry = getNeighborByIndex(i); + NodeNum my_node_id = nodeDB->getNodeNum(); + for (auto it = neighbors.rbegin(); it != neighbors.rend();) { // We will remove a neighbor if we haven't heard from them in twice the broadcast interval - if ((now - dbEntry->last_rx_time > dbEntry->node_broadcast_interval_secs * 2) && (dbEntry->node_id != my_node_id)) { - indices_to_remove.push_back(i); + if ((now - it->last_rx_time > it->node_broadcast_interval_secs * 2) && (it->node_id != my_node_id)) { + LOG_DEBUG("Removing neighbor with node ID 0x%x\n", it->node_id); + it = std::vector::reverse_iterator( + neighbors.erase(std::next(it).base())); // Erase the element and update the iterator + } else { + ++it; } } - - // Update the neighbor list - for (uint i = 0; i < indices_to_remove.size(); i++) { - int index = indices_to_remove[i]; - - if (config.lora.next_hop_routing) { - // Clear all next hops of nodes that had this neighbor as next hop - for (unsigned int j = 0; j < nodeDB.getNumMeshNodes(); j++) { - meshtastic_NodeInfoLite *node = nodeDB.getMeshNodeByIndex(j); - if (node->next_hop == nodeDB.getLastByteOfNodeNum(neighbors[index].node_id)) { - node->next_hop = 0; - } - } - } - - LOG_DEBUG("Removing neighbor with node ID 0x%x\n", neighbors[index].node_id); - for (int j = index; j < num_neighbors - 1; j++) { - neighbors[j] = neighbors[j + 1]; - } - (*numNeighbors)--; - } - - // Save the neighbor list if we removed any neighbors - if (indices_to_remove.size() > 0) { - saveProtoForModule(); - } - - return *numNeighbors; } /* Send neighbor info to the mesh */ @@ -203,8 +117,10 @@ Will be used for broadcast. int32_t NeighborInfoModule::runOnce() { bool requestReplies = false; - sendNeighborInfo(NODENUM_BROADCAST, requestReplies); - return getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs); + if (airTime->isTxAllowedChannelUtil(true) && airTime->isTxAllowedAirUtil()) { + sendNeighborInfo(NODENUM_BROADCAST, requestReplies); + } + return Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs); } /* @@ -213,11 +129,12 @@ Pass it to an upper client; do not persist this data on the mesh */ bool NeighborInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *np) { - if (enabled) { + if (np) { printNeighborInfo("RECEIVED", np); updateNeighbors(mp, np); - if (config.lora.next_hop_routing) - updateNextHops(np); + } else if (mp.hop_start != 0 && mp.hop_start == mp.hop_limit) { + // If the hopLimit is the same as hopStart, then it is a neighbor + getOrCreateNeighbor(mp.from, mp.from, 0, mp.rx_snr); // Set the broadcast interval to 0, as we don't know it } // Allow others to handle this packet return false; @@ -226,20 +143,13 @@ bool NeighborInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, /* Copy the content of a current NeighborInfo packet into a new one and update the last_sent_by_id to our NodeNum */ -void NeighborInfoModule::updateLastSentById(meshtastic_MeshPacket *p) +void NeighborInfoModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_NeighborInfo *n) { - auto &incoming = p->decoded; - meshtastic_NeighborInfo scratch; - meshtastic_NeighborInfo *updated = NULL; - memset(&scratch, 0, sizeof(scratch)); - pb_decode_from_bytes(incoming.payload.bytes, incoming.payload.size, &meshtastic_NeighborInfo_msg, &scratch); - updated = &scratch; - - updated->last_sent_by_id = nodeDB.getNodeNum(); + n->last_sent_by_id = nodeDB->getNodeNum(); // Set updated last_sent_by_id to the payload of the to be flooded packet - p->decoded.payload.size = - pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_NeighborInfo_msg, updated); + p.decoded.payload.size = + pb_encode_to_bytes(p.decoded.payload.bytes, sizeof(p.decoded.payload.bytes), &meshtastic_NeighborInfo_msg, n); } /* Update the next hop for nodes in the database. @@ -248,29 +158,29 @@ void NeighborInfoModule::updateLastSentById(meshtastic_MeshPacket *p) void NeighborInfoModule::updateNextHops(meshtastic_NeighborInfo *np) { LOG_DEBUG("Updating next hops based on received NeighborInfo packet\n"); - meshtastic_NodeInfoLite *currentNode = nodeDB.getMeshNode(np->node_id); + meshtastic_NodeInfoLite *currentNode = nodeDB->getMeshNode(np->node_id); // Check if the sender of this neighborInfo packet is a neighbor of ourselves if (currentNode && isANeighbor(np->node_id)) { - currentNode->next_hop = nodeDB.getLastByteOfNodeNum(np->node_id); // Set the next hop to the sender of this packet + currentNode->next_hop = nodeDB->getLastByteOfNodeNum(np->node_id); // Set the next hop to the sender of this packet for (uint8_t i = 0; i < np->neighbors_count; i++) { if (isANeighbor(np->neighbors[i].node_id)) continue; // This node is a neighbor of ourselves - meshtastic_NodeInfoLite *neighborOfCurrentNode = nodeDB.getMeshNode(np->neighbors[i].node_id); + meshtastic_NodeInfoLite *neighborOfCurrentNode = nodeDB->getMeshNode(np->neighbors[i].node_id); // Update next hop of this node to the sender of this packet, because it is the most recent neighbor if (neighborOfCurrentNode) - neighborOfCurrentNode->next_hop = nodeDB.getLastByteOfNodeNum(currentNode->num); + neighborOfCurrentNode->next_hop = nodeDB->getLastByteOfNodeNum(currentNode->num); } } else if (currentNode) { // Sender is not a neighbor // Find common neighbors and use the most recent as next hop to this node - meshtastic_NodeInfoLite *currentNextHop = nodeDB.getMeshNode(currentNode->next_hop); + meshtastic_NodeInfoLite *currentNextHop = nodeDB->getMeshNode(currentNode->next_hop); uint32_t maxLastHeard = currentNextHop ? currentNextHop->last_heard : 0; for (uint8_t i = 0; i < np->neighbors_count; i++) { - meshtastic_NodeInfoLite *neighborOfCurrentNode = nodeDB.getMeshNode(np->neighbors[i].node_id); + meshtastic_NodeInfoLite *neighborOfCurrentNode = nodeDB->getMeshNode(np->neighbors[i].node_id); if (neighborOfCurrentNode && isANeighbor(neighborOfCurrentNode->num)) { // This neighbor was heard more recently than the current next hop if (neighborOfCurrentNode->last_heard > maxLastHeard) { - currentNode->next_hop = nodeDB.getLastByteOfNodeNum(neighborOfCurrentNode->num); + currentNode->next_hop = nodeDB->getLastByteOfNodeNum(neighborOfCurrentNode->num); maxLastHeard = neighborOfCurrentNode->last_heard; LOG_DEBUG("More recent node found, so update next_hop of %x to %x\n", currentNode->num, neighborOfCurrentNode->num); @@ -282,7 +192,7 @@ void NeighborInfoModule::updateNextHops(meshtastic_NeighborInfo *np) bool NeighborInfoModule::isANeighbor(NodeNum node_id) { - for (int i = 0; i < *numNeighbors; i++) { + for (size_t i = 0; i < neighbors.size(); i++) { if (neighbors[i].node_id == node_id) { return true; } @@ -292,10 +202,7 @@ bool NeighborInfoModule::isANeighbor(NodeNum node_id) void NeighborInfoModule::resetNeighbors() { - *numNeighbors = 0; - neighborState.neighbors_count = 0; - memset(neighborState.neighbors, 0, sizeof(neighborState.neighbors)); - saveProtoForModule(); + neighbors.clear(); } void NeighborInfoModule::updateNeighbors(const meshtastic_MeshPacket &mp, const meshtastic_NeighborInfo *np) @@ -312,61 +219,39 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen { // our node and the phone are the same node (not neighbors) if (n == 0) { - n = nodeDB.getNodeNum(); + n = nodeDB->getNodeNum(); } // look for one in the existing list - for (int i = 0; i < (*numNeighbors); i++) { - meshtastic_Neighbor *nbr = &neighbors[i]; - if (nbr->node_id == n) { + for (size_t i = 0; i < neighbors.size(); i++) { + if (neighbors[i].node_id == n) { // if found, update it - nbr->snr = snr; - nbr->last_rx_time = getTime(); + neighbors[i].snr = snr; + neighbors[i].last_rx_time = getTime(); // Only if this is the original sender, the broadcast interval corresponds to it - if (originalSender == n) - nbr->node_broadcast_interval_secs = node_broadcast_interval_secs; - saveProtoForModule(); // Save the updated neighbor - return nbr; + if (originalSender == n && node_broadcast_interval_secs != 0) + neighbors[i].node_broadcast_interval_secs = node_broadcast_interval_secs; + return &neighbors[i]; } } // otherwise, allocate one and assign data to it - // TODO: max memory for the database should take neighbors into account, but currently doesn't - if (*numNeighbors < MAX_NUM_NEIGHBORS) { - (*numNeighbors)++; - } - meshtastic_Neighbor *new_nbr = &neighbors[((*numNeighbors) - 1)]; - new_nbr->node_id = n; - new_nbr->snr = snr; - new_nbr->last_rx_time = getTime(); + + meshtastic_Neighbor new_nbr = meshtastic_Neighbor_init_zero; + new_nbr.node_id = n; + new_nbr.snr = snr; + new_nbr.last_rx_time = getTime(); // Only if this is the original sender, the broadcast interval corresponds to it - if (originalSender == n) - new_nbr->node_broadcast_interval_secs = node_broadcast_interval_secs; - saveProtoForModule(); // Save the new neighbor - return new_nbr; -} + if (originalSender == n && node_broadcast_interval_secs != 0) + new_nbr.node_broadcast_interval_secs = node_broadcast_interval_secs; + else // Assume the same broadcast interval as us for the neighbor if we don't know it + new_nbr.node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval; -void NeighborInfoModule::loadProtoForModule() -{ - if (!nodeDB.loadProto(neighborInfoConfigFile, meshtastic_NeighborInfo_size, sizeof(meshtastic_NeighborInfo), - &meshtastic_NeighborInfo_msg, &neighborState)) { - neighborState = meshtastic_NeighborInfo_init_zero; + if (neighbors.size() < MAX_NUM_NEIGHBORS) { + neighbors.push_back(new_nbr); + } else { + // If we have too many neighbors, replace the oldest one + LOG_WARN("Neighbor DB is full, replacing oldest neighbor\n"); + neighbors.erase(neighbors.begin()); + neighbors.push_back(new_nbr); } -} - -/** - * @brief Save the module config to file. - * - * @return true On success. - * @return false On error. - */ -bool NeighborInfoModule::saveProtoForModule() -{ - bool okay = true; - -#ifdef FS - FS.mkdir("/prefs"); -#endif - - okay &= nodeDB.saveProto(neighborInfoConfigFile, meshtastic_NeighborInfo_size, &meshtastic_NeighborInfo_msg, &neighborState); - - return okay; + return &neighbors.back(); } \ No newline at end of file diff --git a/src/modules/NeighborInfoModule.h b/src/modules/NeighborInfoModule.h index e848f7ee4..c4ceb751a 100644 --- a/src/modules/NeighborInfoModule.h +++ b/src/modules/NeighborInfoModule.h @@ -1,13 +1,13 @@ #pragma once #include "ProtobufModule.h" +#define MAX_NUM_NEIGHBORS 10 // also defined in NeighborInfo protobuf options /* * Neighborinfo module for sending info on each node's 0-hop neighbors to the mesh */ class NeighborInfoModule : public ProtobufModule, private concurrency::OSThread { - meshtastic_Neighbor *neighbors; - pb_size_t *numNeighbors; + std::vector neighbors; public: /* @@ -18,16 +18,7 @@ class NeighborInfoModule : public ProtobufModule, priva /* Reset neighbor info after clearing nodeDB*/ void resetNeighbors(); - bool saveProtoForModule(); - - // Let FloodingRouter/NexthopRouter call updateLastSentById upon rebroadcasting a NeighborInfo packet - friend class FloodingRouter; - friend class NextHopRouter; - protected: - // Note: this holds our local info. - meshtastic_NeighborInfo neighborState; - /* * Called to handle a particular incoming message * @return true if you've guaranteed you've handled this message and no other handlers should be considered for it @@ -41,10 +32,9 @@ class NeighborInfoModule : public ProtobufModule, priva uint32_t collectNeighborInfo(meshtastic_NeighborInfo *neighborInfo); /* - Remove neighbors from the database that we haven't heard from in a while - @returns new number of neighbors + Remove neighbors from the database that we haven't heard from in a while */ - size_t cleanUpNeighbors(); + void cleanUpNeighbors(); /* Allocate a new NeighborInfo packet */ meshtastic_NeighborInfo *allocateNeighborInfoPacket(); @@ -57,35 +47,29 @@ class NeighborInfoModule : public ProtobufModule, priva */ void sendNeighborInfo(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false); - size_t getNumNeighbors() { return *numNeighbors; } - - meshtastic_Neighbor *getNeighborByIndex(size_t x) - { - assert(x < *numNeighbors); - return &neighbors[x]; - } - /* update neighbors with subpacket sniffed from network */ void updateNeighbors(const meshtastic_MeshPacket &mp, const meshtastic_NeighborInfo *np); /* update a NeighborInfo packet with our NodeNum as last_sent_by_id */ - void updateLastSentById(meshtastic_MeshPacket *p); - - /* update the next hop for nodes in the database */ - void updateNextHops(meshtastic_NeighborInfo *np); - - /* check if the given node number is a neighbor of us */ - bool isANeighbor(NodeNum node_id); - - void loadProtoForModule(); + void alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_NeighborInfo *n) override; /* Does our periodic broadcast */ int32_t runOnce() override; + /* Override wantPacket to say we want to see all packets when enabled, not just those for our port number. + Exception is when the packet came via MQTT */ + virtual bool wantPacket(const meshtastic_MeshPacket *p) override { return enabled && !p->via_mqtt; } + + /* Update the next hop for nodes in the database. + * Based on our own neighbors, and the neighbors of our neighbors. + */ + void updateNextHops(meshtastic_NeighborInfo *np); + + /* Return true if the given node is a neighbor of us */ + bool isANeighbor(NodeNum node_id); + /* These are for debugging only */ void printNeighborInfo(const char *header, const meshtastic_NeighborInfo *np); - void printNodeDBNodes(const char *header); - void printNodeDBNeighbors(const char *header); - void printNodeDBSelection(const char *header, const meshtastic_NeighborInfo *np); + void printNodeDBNeighbors(); }; -extern NeighborInfoModule *neighborInfoModule; \ No newline at end of file +extern NeighborInfoModule *neighborInfoModule; diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index 855ba9cde..f77026708 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -1,4 +1,5 @@ #include "NodeInfoModule.h" +#include "Default.h" #include "MeshService.h" #include "NodeDB.h" #include "RTC.h" @@ -12,7 +13,7 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes { auto p = *pptr; - bool hasChanged = nodeDB.updateUser(getFrom(&mp), p); + bool hasChanged = nodeDB->updateUser(getFrom(&mp), p, mp.channel); bool wasBroadcast = mp.to == NODENUM_BROADCAST; @@ -24,7 +25,7 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes } // if user has changed while packet was not for us, inform phone - if (hasChanged && !wasBroadcast && mp.to != nodeDB.getNodeNum()) + if (hasChanged && !wasBroadcast && mp.to != nodeDB->getNodeNum()) service.sendToPhone(packetPool.allocCopy(mp)); // LOG_DEBUG("did handleReceived\n"); @@ -58,8 +59,8 @@ void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t cha meshtastic_MeshPacket *NodeInfoModule::allocReply() { uint32_t now = millis(); - // If we sent our NodeInfo less than 1 min. ago, don't send it again as it may be still underway. - if (lastSentToMesh && (now - lastSentToMesh) < 60 * 1000) { + // If we sent our NodeInfo less than 5 min. ago, don't send it again as it may be still underway. + if (lastSentToMesh && (now - lastSentToMesh) < (5 * 60 * 1000)) { LOG_DEBUG("Sending NodeInfo will be ignored since we just sent it.\n"); ignoreRequest = true; // Mark it as ignored for MeshModule return NULL; @@ -83,16 +84,13 @@ NodeInfoModule::NodeInfoModule() int32_t NodeInfoModule::runOnce() { - static uint32_t currentGeneration; - // If we changed channels, ask everyone else for their latest info bool requestReplies = currentGeneration != radioGeneration; currentGeneration = radioGeneration; - if (airTime->isTxAllowedAirUtil()) { + if (airTime->isTxAllowedAirUtil() && config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) { LOG_INFO("Sending our nodeinfo to mesh (wantReplies=%d)\n", requestReplies); sendOurNodeInfo(NODENUM_BROADCAST, requestReplies); // Send our info (don't request replies) } - - return getConfiguredOrDefaultMs(config.device.node_info_broadcast_secs, default_broadcast_interval_secs); + return Default::getConfiguredOrDefaultMs(config.device.node_info_broadcast_secs, default_node_info_broadcast_secs); } diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 69cd4848e..dcfe03f7f 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -1,4 +1,6 @@ +#if !MESHTASTIC_EXCLUDE_GPS #include "PositionModule.h" +#include "Default.h" #include "GPS.h" #include "MeshService.h" #include "NodeDB.h" @@ -8,36 +10,36 @@ #include "airtime.h" #include "configuration.h" #include "gps/GeoCoord.h" +#include "main.h" +#include "meshtastic/atak.pb.h" #include "sleep.h" #include "target_specific.h" +extern "C" { +#include "mesh/compression/unishox2.h" +} + PositionModule *positionModule; PositionModule::PositionModule() : ProtobufModule("position", meshtastic_PortNum_POSITION_APP, &meshtastic_Position_msg), concurrency::OSThread("PositionModule") { + precision = 0; // safe starting value isPromiscuous = true; // We always want to update our nodedb, even if we are sniffing on others - if (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER) + if (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER && + config.device.role != meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) setIntervalFromNow(60 * 1000); // Power saving trackers should clear their position on startup to avoid waking up and sending a stale position - if (config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER && config.power.is_power_saving) { - clearPosition(); + if ((config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || + config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) && + config.power.is_power_saving) { + LOG_DEBUG("Clearing position on startup for sleepy tracker (ー。ー) zzz\n"); + nodeDB->clearLocalPosition(); } } -void PositionModule::clearPosition() -{ - LOG_DEBUG("Clearing position on startup for sleepy tracker (ー。ー) zzz\n"); - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum()); - node->position.latitude_i = 0; - node->position.longitude_i = 0; - node->position.altitude = 0; - node->position.time = 0; - nodeDB.setLocalPosition(meshtastic_Position_init_default); -} - bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Position *pptr) { auto p = *pptr; @@ -48,10 +50,16 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes // FIXME this can in fact happen with packets sent from EUD (src=RX_SRC_USER) // to set fixed location, EUD-GPS location or just the time (see also issue #900) bool isLocal = false; - if (nodeDB.getNodeNum() == getFrom(&mp)) { - LOG_DEBUG("Incoming update from MYSELF\n"); + if (nodeDB->getNodeNum() == getFrom(&mp)) { isLocal = true; - nodeDB.setLocalPosition(p); + if (config.position.fixed_position) { + LOG_DEBUG("Ignore incoming position update from myself except for time, because position.fixed_position is true\n"); + nodeDB->setLocalPosition(p, true); + return false; + } else { + LOG_DEBUG("Incoming update from MYSELF\n"); + nodeDB->setLocalPosition(p); + } } // Log packet size and data fields @@ -61,7 +69,7 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes p.altitude_geoidal_separation, p.PDOP, p.HDOP, p.VDOP, p.sats_in_view, p.fix_quality, p.fix_type, p.timestamp, p.time); - if (p.time) { + if (p.time && channels.getByIndex(mp.channel).role == meshtastic_Channel_Role_PRIMARY) { struct timeval tv; uint32_t secs = p.time; @@ -72,13 +80,13 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes perhapsSetRTC(isLocal ? RTCQualityNTP : RTCQualityFromNet, &tv); } - nodeDB.updatePosition(getFrom(&mp), p); - - // Only respond to location requests on the channel where we broadcast location. - if (channels.getByIndex(mp.channel).role == meshtastic_Channel_Role_PRIMARY) { - ignoreRequest = false; + nodeDB->updatePosition(getFrom(&mp), p); + if (channels.getByIndex(mp.channel).settings.has_module_settings) { + precision = channels.getByIndex(mp.channel).settings.module_settings.position_precision; + } else if (channels.getByIndex(mp.channel).role == meshtastic_Channel_Role_PRIMARY) { + precision = 32; } else { - ignoreRequest = true; + precision = 0; } return false; // Let others look at this message also if they want @@ -86,7 +94,8 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes meshtastic_MeshPacket *PositionModule::allocReply() { - if (ignoreRequest) { + if (precision == 0) { + LOG_DEBUG("Skipping location send because precision is set to 0!\n"); return nullptr; } @@ -101,13 +110,29 @@ meshtastic_MeshPacket *PositionModule::allocReply() meshtastic_Position p = meshtastic_Position_init_default; // Start with an empty structure // if localPosition is totally empty, put our last saved position (lite) in there if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) { - nodeDB.setLocalPosition(TypeConversions::ConvertToPosition(node->position)); + nodeDB->setLocalPosition(TypeConversions::ConvertToPosition(node->position)); } localPosition.seq_number++; + if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) { + LOG_WARN("Skipping position send because lat/lon are zero!\n"); + return nullptr; + } + // lat/lon are unconditionally included - IF AVAILABLE! - p.latitude_i = localPosition.latitude_i; - p.longitude_i = localPosition.longitude_i; + LOG_DEBUG("Sending location with precision %i\n", precision); + if (precision < 32 && precision > 0) { + p.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - precision)); + p.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - precision)); + + // We want the imprecise position to be the middle of the possible location, not + p.latitude_i += (1 << (31 - precision)); + p.longitude_i += (1 << (31 - precision)); + } else { + p.latitude_i = localPosition.latitude_i; + p.longitude_i = localPosition.longitude_i; + } + p.precision_bits = precision; p.time = localPosition.time; if (pos_flags & meshtastic_Config_PositionConfig_PositionFlags_ALTITUDE) { @@ -150,29 +175,78 @@ meshtastic_MeshPacket *PositionModule::allocReply() LOG_INFO("Stripping time %u from position send\n", p.time); p.time = 0; } else { + p.time = getValidTime(RTCQualityDevice); LOG_INFO("Providing time to mesh %u\n", p.time); } - LOG_INFO("Position reply: time=%i, latI=%i, lonI=-%i\n", p.time, p.latitude_i, p.longitude_i); + LOG_INFO("Position reply: time=%i, latI=%i, lonI=%i\n", p.time, p.latitude_i, p.longitude_i); + + // TAK Tracker devices should send their position in a TAK packet over the ATAK port + if (config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) + return allocAtakPli(); return allocDataProtobuf(p); } +meshtastic_MeshPacket *PositionModule::allocAtakPli() +{ + LOG_INFO("Sending TAK PLI packet\n"); + meshtastic_MeshPacket *mp = allocDataPacket(); + mp->decoded.portnum = meshtastic_PortNum_ATAK_PLUGIN; + + meshtastic_TAKPacket takPacket = {.is_compressed = true, + .has_contact = true, + .contact = {0}, + .has_group = true, + .group = {meshtastic_MemberRole_TeamMember, meshtastic_Team_Cyan}, + .has_status = true, + .status = + { + .battery = powerStatus->getBatteryChargePercent(), + }, + .which_payload_variant = meshtastic_TAKPacket_pli_tag, + {.pli = { + .latitude_i = localPosition.latitude_i, + .longitude_i = localPosition.longitude_i, + .altitude = localPosition.altitude_hae, + .speed = localPosition.ground_speed, + .course = static_cast(localPosition.ground_track), + }}}; + + auto length = unishox2_compress_simple(owner.long_name, strlen(owner.long_name), takPacket.contact.device_callsign); + LOG_DEBUG("Uncompressed device_callsign '%s' - %d bytes\n", owner.long_name, strlen(owner.long_name)); + LOG_DEBUG("Compressed device_callsign '%s' - %d bytes\n", takPacket.contact.device_callsign, length); + length = unishox2_compress_simple(owner.long_name, strlen(owner.long_name), takPacket.contact.callsign); + mp->decoded.payload.size = + pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), &meshtastic_TAKPacket_msg, &takPacket); + return mp; +} + void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t channel) { // cancel any not yet sent (now stale) position packets if (prevPacketId) // if we wrap around to zero, we'll simply fail to cancel in that rare case (no big deal) service.cancelSending(prevPacketId); + // Set's the class precision value for this particular packet + if (channels.getByIndex(channel).settings.has_module_settings) { + precision = channels.getByIndex(channel).settings.module_settings.position_precision; + } else if (channels.getByIndex(channel).role == meshtastic_Channel_Role_PRIMARY) { + precision = 32; + } else { + precision = 0; + } + meshtastic_MeshPacket *p = allocReply(); if (p == nullptr) { - LOG_WARN("allocReply returned a nullptr"); + LOG_DEBUG("allocReply returned a nullptr\n"); return; } p->to = dest; p->decoded.want_response = config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER ? false : wantReplies; - if (config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER) + if (config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || + config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) p->priority = meshtastic_MeshPacket_Priority_RELIABLE; else p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; @@ -183,7 +257,9 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha service.sendToMesh(p, RX_SRC_LOCAL, true); - if (config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER && config.power.is_power_saving) { + if ((config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || + config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) && + config.power.is_power_saving) { LOG_DEBUG("Starting next execution in 5 seconds and then going to sleep.\n"); sleepOnNextExecution = true; setIntervalFromNow(5000); @@ -196,19 +272,23 @@ int32_t PositionModule::runOnce() { if (sleepOnNextExecution == true) { sleepOnNextExecution = false; - uint32_t nightyNightMs = getConfiguredOrDefaultMs(config.position.position_broadcast_secs); + uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(config.position.position_broadcast_secs); LOG_DEBUG("Sleeping for %ims, then awaking to send position again.\n", nightyNightMs); doDeepSleep(nightyNightMs, false); } - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum()); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); + if (node == nullptr) + return RUNONCE_INTERVAL; // We limit our GPS broadcasts to a max rate uint32_t now = millis(); - uint32_t intervalMs = getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs); + uint32_t intervalMs = + Default::getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs); uint32_t msSinceLastSend = now - lastGpsSend; // Only send packets if the channel util. is less than 25% utilized or we're a tracker with less than 40% utilized. - if (!airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER)) { + if (!airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER && + config.device.role != meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)) { return RUNONCE_INTERVAL; } @@ -225,6 +305,9 @@ int32_t PositionModule::runOnce() LOG_INFO("Sending pos@%x:6 to mesh (wantReplies=%d)\n", localPosition.timestamp, requestReplies); sendOurPosition(NODENUM_BROADCAST, requestReplies); + if (config.device.role == meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND) { + sendLostAndFoundText(); + } } } else if (config.position.position_broadcast_smart_enabled) { const meshtastic_NodeInfoLite *node2 = service.refreshLocalMeshNode(); // should guarantee there is now a position @@ -232,7 +315,7 @@ int32_t PositionModule::runOnce() if (hasValidPosition(node2)) { // The minimum time (in seconds) that would pass before we are able to send a new position packet. const uint32_t minimumTimeThreshold = - getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30); + Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30); auto smartPosition = getDistanceTraveledSinceLastSend(node->position); @@ -261,10 +344,26 @@ int32_t PositionModule::runOnce() return RUNONCE_INTERVAL; // to save power only wake for our callback occasionally } +void PositionModule::sendLostAndFoundText() +{ + meshtastic_MeshPacket *p = allocDataPacket(); + p->to = NODENUM_BROADCAST; + char *message = new char[60]; + sprintf(message, "🚨I'm lost! Lat / Lon: %f, %f\a", (lastGpsLatitude * 1e-7), (lastGpsLongitude * 1e-7)); + p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; + p->want_ack = false; + p->decoded.payload.size = strlen(message); + memcpy(p->decoded.payload.bytes, message, p->decoded.payload.size); + + service.sendToMesh(p, RX_SRC_LOCAL, true); + delete[] message; +} + struct SmartPosition PositionModule::getDistanceTraveledSinceLastSend(meshtastic_PositionLite currentPosition) { // The minimum distance to travel before we are able to send a new position packet. - const uint32_t distanceTravelThreshold = getConfiguredOrDefault(config.position.broadcast_smart_minimum_distance, 100); + const uint32_t distanceTravelThreshold = + Default::getConfiguredOrDefault(config.position.broadcast_smart_minimum_distance, 100); // Determine the distance in meters between two points on the globe float distanceTraveledSinceLastSend = GeoCoord::latLongToMeter( @@ -278,7 +377,7 @@ struct SmartPosition PositionModule::getDistanceTraveledSinceLastSend(meshtastic LOG_DEBUG("currentPosition.latitude_i=%i, currentPosition.longitude_i=%i\n", lastGpsLatitude, lastGpsLongitude); LOG_DEBUG("--------SMART POSITION-----------------------------------\n"); - LOG_DEBUG("hasTraveledOverThreshold=%i, distanceTraveled=%d, distanceThreshold=% u\n", + LOG_DEBUG("hasTraveledOverThreshold=%i, distanceTraveled=%f, distanceThreshold=%f\n", abs(distanceTraveledSinceLastSend) >= distanceTravelThreshold, abs(distanceTraveledSinceLastSend), distanceTravelThreshold); @@ -294,7 +393,7 @@ struct SmartPosition PositionModule::getDistanceTraveledSinceLastSend(meshtastic void PositionModule::handleNewPosition() { - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum()); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum()); const meshtastic_NodeInfoLite *node2 = service.refreshLocalMeshNode(); // should guarantee there is now a position // We limit our GPS broadcasts to a max rate uint32_t now = millis(); @@ -320,4 +419,6 @@ void PositionModule::handleNewPosition() lastGpsSend = now; } } -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/src/modules/PositionModule.h b/src/modules/PositionModule.h index 1b7eca800..68171ab0e 100644 --- a/src/modules/PositionModule.h +++ b/src/modules/PositionModule.h @@ -49,9 +49,9 @@ class PositionModule : public ProtobufModule, private concu private: struct SmartPosition getDistanceTraveledSinceLastSend(meshtastic_PositionLite currentPosition); - - /** Only used in power saving trackers for now */ - void clearPosition(); + meshtastic_MeshPacket *allocAtakPli(); + uint32_t precision; + void sendLostAndFoundText(); }; struct SmartPosition { diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index ecf4b70c7..a66a0513e 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -113,8 +113,8 @@ void RangeTestModuleRadio::sendPayload(NodeNum dest, bool wantReplies) meshtastic_MeshPacket *p = allocDataPacket(); p->to = dest; p->decoded.want_response = wantReplies; - - p->want_ack = true; + p->hop_limit = 0; + p->want_ack = false; packetSequence++; @@ -142,14 +142,14 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket LOG_INFO.getNodeNum(), mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes); */ - if (getFrom(&mp) != nodeDB.getNodeNum()) { + if (getFrom(&mp) != nodeDB->getNodeNum()) { if (moduleConfig.range_test.save) { appendFile(mp); } /* - NodeInfoLite *n = nodeDB.getMeshNode(getFrom(&mp)); + NodeInfoLite *n = nodeDB->getMeshNode(getFrom(&mp)); LOG_DEBUG("-----------------------------------------\n"); LOG_DEBUG("p.payload.bytes \"%s\"\n", p.payload.bytes); @@ -188,7 +188,7 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp) #ifdef ARCH_ESP32 auto &p = mp.decoded; - meshtastic_NodeInfoLite *n = nodeDB.getMeshNode(getFrom(&mp)); + meshtastic_NodeInfoLite *n = nodeDB->getMeshNode(getFrom(&mp)); /* LOG_DEBUG("-----------------------------------------\n"); LOG_DEBUG("p.payload.bytes \"%s\"\n", p.payload.bytes); diff --git a/src/modules/RemoteHardwareModule.cpp b/src/modules/RemoteHardwareModule.cpp index 3d4d735b4..8e64b9a9c 100644 --- a/src/modules/RemoteHardwareModule.cpp +++ b/src/modules/RemoteHardwareModule.cpp @@ -56,7 +56,7 @@ bool RemoteHardwareModule::handleReceivedProtobuf(const meshtastic_MeshPacket &r LOG_INFO("Received RemoteHardware type=%d\n", p.type); switch (p.type) { - case meshtastic_HardwareMessage_Type_WRITE_GPIOS: + case meshtastic_HardwareMessage_Type_WRITE_GPIOS: { // Print notification to LCD screen screen->print("Write GPIOs\n"); @@ -69,6 +69,7 @@ bool RemoteHardwareModule::handleReceivedProtobuf(const meshtastic_MeshPacket &r pinModes(p.gpio_mask, OUTPUT); break; + } case meshtastic_HardwareMessage_Type_READ_GPIOS: { // Print notification to LCD screen @@ -92,8 +93,9 @@ bool RemoteHardwareModule::handleReceivedProtobuf(const meshtastic_MeshPacket &r watchGpios = p.gpio_mask; lastWatchMsec = 0; // Force a new publish soon previousWatch = - ~watchGpios; // generate a 'previous' value which is guaranteed to not match (to force an initial publish) - enabled = true; // Let our thread run at least once + ~watchGpios; // generate a 'previous' value which is guaranteed to not match (to force an initial publish) + enabled = true; // Let our thread run at least once + setInterval(2000); // Set a new interval so we'll run soon LOG_INFO("Now watching GPIOs 0x%llx\n", watchGpios); break; } @@ -118,6 +120,7 @@ int32_t RemoteHardwareModule::runOnce() if (now - lastWatchMsec >= WATCH_INTERVAL_MSEC) { uint64_t curVal = digitalReads(watchGpios); + lastWatchMsec = now; if (curVal != previousWatch) { previousWatch = curVal; @@ -136,5 +139,5 @@ int32_t RemoteHardwareModule::runOnce() return disable(); } - return 200; // Poll our GPIOs every 200ms (FIXME, make adjustable via protobuf arg) -} + return 2000; // Poll our GPIOs every 2000ms +} \ No newline at end of file diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp index d81311481..a52328ca4 100644 --- a/src/modules/RoutingModule.cpp +++ b/src/modules/RoutingModule.cpp @@ -14,7 +14,7 @@ bool RoutingModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mesh // FIXME - move this to a non promsicious PhoneAPI module? // Note: we are careful not to send back packets that started with the phone back to the phone - if ((mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum()) && (mp.from != 0)) { + if ((mp.to == NODENUM_BROADCAST || mp.to == nodeDB->getNodeNum()) && (mp.from != 0)) { printPacket("Delivering rx packet", &mp); service.handleFromRadio(&mp); } @@ -36,15 +36,31 @@ meshtastic_MeshPacket *RoutingModule::allocReply() return NULL; } -void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex) +void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopStart, + uint8_t hopLimit) { - auto p = allocAckNak(err, to, idFrom, chIndex); + auto p = allocAckNak(err, to, idFrom, chIndex, hopStart, hopLimit); router->sendLocal(p); // we sometimes send directly to the local node } +uint8_t RoutingModule::getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit) +{ + if (hopStart != 0) { + // Hops used by the request. If somebody in between running modified firmware modified it, ignore it + uint8_t hopsUsed = hopStart < hopLimit ? config.lora.hop_limit : hopStart - hopLimit; + if (hopsUsed > config.lora.hop_limit) { + return hopsUsed; // If the request used more hops than the limit, use the same amount of hops + } else if (hopsUsed + 2 < config.lora.hop_limit) { + return hopsUsed + 2; // Use only the amount of hops needed with some margin as the way back may be different + } + } + return config.lora.hop_limit; // Use the default hop limit +} + RoutingModule::RoutingModule() : ProtobufModule("routing", meshtastic_PortNum_ROUTING_APP, &meshtastic_Routing_msg) { isPromiscuous = true; - encryptedOk = config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_LOCAL_ONLY; -} + encryptedOk = config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_LOCAL_ONLY && + config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY; +} \ No newline at end of file diff --git a/src/modules/RoutingModule.h b/src/modules/RoutingModule.h index 06e76cfb4..f085b307b 100644 --- a/src/modules/RoutingModule.h +++ b/src/modules/RoutingModule.h @@ -13,7 +13,11 @@ class RoutingModule : public ProtobufModule */ RoutingModule(); - void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex); + void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopStart = 0, + uint8_t hopLimit = 0); + + // Given the hopStart and hopLimit upon reception of a request, return the hop limit to use for the response + uint8_t getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit); protected: friend class Router; diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 42b109050..96a99b13e 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -58,7 +58,7 @@ SerialModule *serialModule; SerialModuleRadio *serialModuleRadio; -#ifdef TTGO_T_ECHO +#if defined(TTGO_T_ECHO) || defined(CANARYONE) SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("SerialModule") {} static Print *serialPrint = &Serial; #else @@ -126,8 +126,13 @@ int32_t SerialModule::runOnce() uint32_t baud = getBaudRate(); if (moduleConfig.serial.override_console_serial_port) { +#ifdef RP2040_SLOW_CLOCK + Serial2.flush(); + serialPrint = &Serial2; +#else Serial.flush(); serialPrint = &Serial; +#endif // Give it a chance to flush out 💩 delay(10); } @@ -140,7 +145,7 @@ int32_t SerialModule::runOnce() Serial.begin(baud); Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); } -#elif !defined(TTGO_T_ECHO) +#elif !defined(TTGO_T_ECHO) && !defined(CANARYONE) if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { #ifdef ARCH_RP2040 Serial2.setFIFOSize(RX_BUFFER); @@ -151,8 +156,13 @@ int32_t SerialModule::runOnce() Serial2.begin(baud, SERIAL_8N1); Serial2.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); } else { +#ifdef RP2040_SLOW_CLOCK + Serial2.begin(baud, SERIAL_8N1); + Serial2.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); +#else Serial.begin(baud, SERIAL_8N1); Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); +#endif } #else Serial.begin(baud, SERIAL_8N1); @@ -169,26 +179,26 @@ int32_t SerialModule::runOnce() } else { if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_PROTO) { return runOncePart(); - } else if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA) { + } else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA) && HAS_GPS) { // in NMEA mode send out GGA every 2 seconds, Don't read from Port if (millis() - lastNmeaTime > 2000) { lastNmeaTime = millis(); printGGA(outbuf, sizeof(outbuf), localPosition); serialPrint->printf("%s", outbuf); } - } else if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO) { + } else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO) && HAS_GPS) { if (millis() - lastNmeaTime > 10000) { lastNmeaTime = millis(); uint32_t readIndex = 0; - const meshtastic_NodeInfoLite *tempNodeInfo = nodeDB.readNextMeshNode(readIndex); + const meshtastic_NodeInfoLite *tempNodeInfo = nodeDB->readNextMeshNode(readIndex); while (tempNodeInfo != NULL && tempNodeInfo->has_user && hasValidPosition(tempNodeInfo)) { printWPL(outbuf, sizeof(outbuf), tempNodeInfo->position, tempNodeInfo->user.long_name, true); serialPrint->printf("%s", outbuf); - tempNodeInfo = nodeDB.readNextMeshNode(readIndex); + tempNodeInfo = nodeDB->readNextMeshNode(readIndex); } } } -#if !defined(TTGO_T_ECHO) +#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) else { while (Serial2.available()) { serialPayloadSize = Serial2.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN); @@ -255,9 +265,9 @@ ProcessMessage SerialModuleRadio::handleReceived(const meshtastic_MeshPacket &mp auto &p = mp.decoded; // LOG_DEBUG("Received text msg self=0x%0x, from=0x%0x, to=0x%0x, id=%d, msg=%.*s\n", - // nodeDB.getNodeNum(), mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes); + // nodeDB->getNodeNum(), mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes); - if (getFrom(&mp) == nodeDB.getNodeNum()) { + if (getFrom(&mp) == nodeDB->getNodeNum()) { /* * If moduleConfig.serial.echo is true, then echo the packets that are sent out @@ -280,13 +290,14 @@ ProcessMessage SerialModuleRadio::handleReceived(const meshtastic_MeshPacket &mp moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_SIMPLE) { serialPrint->write(p.payload.bytes, p.payload.size); } else if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG) { - meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(getFrom(&mp)); + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp)); String sender = (node && node->has_user) ? node->user.short_name : "???"; serialPrint->println(); serialPrint->printf("%s: %s", sender, p.payload.bytes); serialPrint->println(); - } else if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA || - moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO) { + } else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA || + moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO) && + HAS_GPS) { // Decode the Payload some more meshtastic_Position scratch; meshtastic_Position *decoded = NULL; @@ -296,7 +307,7 @@ ProcessMessage SerialModuleRadio::handleReceived(const meshtastic_MeshPacket &mp decoded = &scratch; } // send position packet as WPL to the serial port - printWPL(outbuf, sizeof(outbuf), *decoded, nodeDB.getMeshNode(getFrom(&mp))->user.long_name, + printWPL(outbuf, sizeof(outbuf), *decoded, nodeDB->getMeshNode(getFrom(&mp))->user.long_name, moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO); serialPrint->printf("%s", outbuf); } diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index f87ea504b..a51a7cea9 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -1,5 +1,6 @@ #include "AirQualityTelemetry.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "Default.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" @@ -43,7 +44,8 @@ int32_t AirQualityTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval))) && + ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval))) && + airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); lastSentToMesh = now; @@ -112,7 +114,7 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR) p->priority = meshtastic_MeshPacket_Priority_RELIABLE; else - p->priority = meshtastic_MeshPacket_Priority_MIN; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; // release previous packet before occupying a new spot if (lastMeasurementPacket != nullptr) diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index bc6c03c52..3529267cb 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -1,5 +1,6 @@ #include "DeviceTelemetry.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "Default.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" @@ -14,13 +15,14 @@ int32_t DeviceTelemetryModule::runOnce() { - uint32_t now = millis(); + refreshUptime(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.telemetry.device_update_interval))) && - airTime->isTxAllowedChannelUtil() && airTime->isTxAllowedAirUtil() && - config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) { + ((uptimeLastMs - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.device_update_interval))) && + airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && + airTime->isTxAllowedAirUtil() && config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && + config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) { sendTelemetry(); - lastSentToMesh = now; + lastSentToMesh = uptimeLastMs; } else if (service.isToPhoneQueueEmpty()) { // Just send to phone when it's not our time to send to mesh yet // Only send while queue is empty (phone assumed connected) @@ -43,7 +45,7 @@ bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket & t->variant.device_metrics.air_util_tx, t->variant.device_metrics.channel_utilization, t->variant.device_metrics.battery_level, t->variant.device_metrics.voltage); #endif - nodeDB.updateTelemetry(getFrom(&mp), *t, RX_SRC_RADIO); + nodeDB->updateTelemetry(getFrom(&mp), *t, RX_SRC_RADIO); } return false; // Let others look at this message also if they want } @@ -66,16 +68,12 @@ meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry() t.time = getTime(); t.which_variant = meshtastic_Telemetry_device_metrics_tag; - t.variant.device_metrics.air_util_tx = airTime->utilizationTXPercent(); - if (powerStatus->getIsCharging()) { - t.variant.device_metrics.battery_level = MAGIC_USB_BATTERY_LEVEL; - } else { - t.variant.device_metrics.battery_level = powerStatus->getBatteryChargePercent(); - } - + t.variant.device_metrics.battery_level = + powerStatus->getIsCharging() ? MAGIC_USB_BATTERY_LEVEL : powerStatus->getBatteryChargePercent(); t.variant.device_metrics.channel_utilization = airTime->channelUtilizationPercent(); t.variant.device_metrics.voltage = powerStatus->getBatteryVoltageMv() / 1000.0; + t.variant.device_metrics.uptime_seconds = getUptimeSeconds(); return t; } @@ -83,16 +81,17 @@ meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry() bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) { meshtastic_Telemetry telemetry = getDeviceTelemetry(); - LOG_INFO("(Sending): air_util_tx=%f, channel_utilization=%f, battery_level=%i, voltage=%f\n", + LOG_INFO("(Sending): air_util_tx=%f, channel_utilization=%f, battery_level=%i, voltage=%f, uptime=%i\n", telemetry.variant.device_metrics.air_util_tx, telemetry.variant.device_metrics.channel_utilization, - telemetry.variant.device_metrics.battery_level, telemetry.variant.device_metrics.voltage); + telemetry.variant.device_metrics.battery_level, telemetry.variant.device_metrics.voltage, + telemetry.variant.device_metrics.uptime_seconds); meshtastic_MeshPacket *p = allocDataProtobuf(telemetry); p->to = dest; p->decoded.want_response = false; - p->priority = meshtastic_MeshPacket_Priority_MIN; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; - nodeDB.updateTelemetry(nodeDB.getNodeNum(), telemetry, RX_SRC_LOCAL); + nodeDB->updateTelemetry(nodeDB->getNodeNum(), telemetry, RX_SRC_LOCAL); if (phoneOnly) { LOG_INFO("Sending packet to phone\n"); service.sendToPhone(p); diff --git a/src/modules/Telemetry/DeviceTelemetry.h b/src/modules/Telemetry/DeviceTelemetry.h index 81f83ce0a..5f4e761f9 100644 --- a/src/modules/Telemetry/DeviceTelemetry.h +++ b/src/modules/Telemetry/DeviceTelemetry.h @@ -12,6 +12,8 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModu : concurrency::OSThread("DeviceTelemetryModule"), ProtobufModule("DeviceTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg) { + uptimeWrapCount = 0; + uptimeLastMs = millis(); setIntervalFromNow(45 * 1000); // Wait until NodeInfo is sent } virtual bool wantUIFrame() { return false; } @@ -28,8 +30,27 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModu */ bool sendTelemetry(NodeNum dest = NODENUM_BROADCAST, bool phoneOnly = false); + /** + * Get the uptime in seconds + * Loses some accuracy after 49 days, but that's fine + */ + uint32_t getUptimeSeconds() { return (0xFFFFFFFF / 1000) * uptimeWrapCount + (uptimeLastMs / 1000); } + private: meshtastic_Telemetry getDeviceTelemetry(); uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute uint32_t lastSentToMesh = 0; + + void refreshUptime() + { + auto now = millis(); + // If we wrapped around (~49 days), increment the wrap count + if (now < uptimeLastMs) + uptimeWrapCount++; + + uptimeLastMs = now; + } + + uint32_t uptimeWrapCount; + uint32_t uptimeLastMs; }; diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 1047ade1d..189ab7ed0 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -1,5 +1,6 @@ #include "EnvironmentTelemetry.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "Default.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" @@ -16,12 +17,14 @@ // Sensors #include "Sensor/BME280Sensor.h" #include "Sensor/BME680Sensor.h" +#include "Sensor/BMP085Sensor.h" #include "Sensor/BMP280Sensor.h" #include "Sensor/LPS22HBSensor.h" #include "Sensor/MCP9808Sensor.h" #include "Sensor/SHT31Sensor.h" #include "Sensor/SHTC3Sensor.h" +BMP085Sensor bmp085Sensor; BMP280Sensor bmp280Sensor; BME280Sensor bme280Sensor; BME680Sensor bme680Sensor; @@ -33,29 +36,13 @@ SHT31Sensor sht31Sensor; #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true -#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 -#define FONT_MEDIUM ArialMT_Plain_24 -#define FONT_LARGE ArialMT_Plain_24 -#else -#define FONT_SMALL ArialMT_Plain_10 -#define FONT_MEDIUM ArialMT_Plain_16 -#define FONT_LARGE ArialMT_Plain_24 -#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) +#include "graphics/ScreenFonts.h" int32_t EnvironmentTelemetryModule::runOnce() { if (sleepOnNextExecution == true) { sleepOnNextExecution = false; - uint32_t nightyNightMs = getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval); + uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval); LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.\n", nightyNightMs); doDeepSleep(nightyNightMs, true); } @@ -83,6 +70,8 @@ int32_t EnvironmentTelemetryModule::runOnce() LOG_INFO("Environment Telemetry: Initializing\n"); // it's possible to have this module enabled, only for displaying values on the screen. // therefore, we should only enable the sensor loop if measurement is also enabled + if (bmp085Sensor.hasSensor()) + result = bmp085Sensor.runOnce(); if (bmp280Sensor.hasSensor()) result = bmp280Sensor.runOnce(); if (bme280Sensor.hasSensor()) @@ -97,9 +86,9 @@ int32_t EnvironmentTelemetryModule::runOnce() result = lps22hbSensor.runOnce(); if (sht31Sensor.hasSensor()) result = sht31Sensor.runOnce(); - if (ina219Sensor.hasSensor() && !ina219Sensor.isInitialized()) + if (ina219Sensor.hasSensor()) result = ina219Sensor.runOnce(); - if (ina260Sensor.hasSensor() && !ina260Sensor.isInitialized()) + if (ina260Sensor.hasSensor()) result = ina260Sensor.runOnce(); } return result; @@ -114,7 +103,8 @@ int32_t EnvironmentTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval))) && + ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval))) && + airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); lastSentToMesh = now; @@ -191,6 +181,8 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt display->drawString(x, y += fontHeight(FONT_SMALL), "Volt/Cur: " + String(lastMeasurement.variant.environment_metrics.voltage, 0) + "V / " + String(lastMeasurement.variant.environment_metrics.current, 0) + "mA"); + if (lastMeasurement.variant.environment_metrics.iaq != 0) + display->drawString(x, y += fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq)); } bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) @@ -235,6 +227,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) valid = lps22hbSensor.getMetrics(&m); if (shtc3Sensor.hasSensor()) valid = shtc3Sensor.getMetrics(&m); + if (bmp085Sensor.hasSensor()) + valid = bmp085Sensor.getMetrics(&m); if (bmp280Sensor.hasSensor()) valid = bmp280Sensor.getMetrics(&m); if (bme280Sensor.hasSensor()) @@ -263,7 +257,7 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR) p->priority = meshtastic_MeshPacket_Priority_RELIABLE; else - p->priority = meshtastic_MeshPacket_Priority_MIN; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; // release previous packet before occupying a new spot if (lastMeasurementPacket != nullptr) packetPool.release(lastMeasurementPacket); diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 032d7fc27..713f6aacb 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -1,5 +1,6 @@ #include "PowerTelemetry.h" #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "Default.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" @@ -14,29 +15,13 @@ #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true -#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 -#define FONT_MEDIUM ArialMT_Plain_24 -#define FONT_LARGE ArialMT_Plain_24 -#else -#define FONT_SMALL ArialMT_Plain_10 -#define FONT_MEDIUM ArialMT_Plain_16 -#define FONT_LARGE ArialMT_Plain_24 -#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) +#include "graphics/ScreenFonts.h" int32_t PowerTelemetryModule::runOnce() { if (sleepOnNextExecution == true) { sleepOnNextExecution = false; - uint32_t nightyNightMs = getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval); + uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval); LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.\n", nightyNightMs); doDeepSleep(nightyNightMs, true); } @@ -82,7 +67,7 @@ int32_t PowerTelemetryModule::runOnce() uint32_t now = millis(); if (((lastSentToMesh == 0) || - ((now - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval))) && + ((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval))) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); lastSentToMesh = now; @@ -211,7 +196,7 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR) p->priority = meshtastic_MeshPacket_Priority_RELIABLE; else - p->priority = meshtastic_MeshPacket_Priority_MIN; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; // release previous packet before occupying a new spot if (lastMeasurementPacket != nullptr) packetPool.release(lastMeasurementPacket); diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp index 323dce31f..e1222bba4 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp @@ -57,6 +57,7 @@ bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement) measurement->variant.environment_metrics.barometric_pressure = bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal / 100.0F; measurement->variant.environment_metrics.gas_resistance = bme680.getData(BSEC_OUTPUT_RAW_GAS).signal / 1000.0; // Check if we need to save state to filesystem (every STATE_SAVE_PERIOD ms) + measurement->variant.environment_metrics.iaq = bme680.getData(BSEC_OUTPUT_IAQ).signal; updateState(); return true; } @@ -85,17 +86,17 @@ void BME680Sensor::updateState() if (stateUpdateCounter == 0) { /* First state update when IAQ accuracy is >= 3 */ accuracy = bme680.getData(BSEC_OUTPUT_IAQ).accuracy; - if (accuracy >= 3) { - LOG_DEBUG("%s state update IAQ accuracy %u >= 3\n", sensorName, accuracy); + if (accuracy >= 2) { + LOG_DEBUG("%s state update IAQ accuracy %u >= 2\n", sensorName, accuracy); update = true; stateUpdateCounter++; } else { - LOG_DEBUG("%s not updated, IAQ accuracy is %u >= 3\n", sensorName, accuracy); + LOG_DEBUG("%s not updated, IAQ accuracy is %u < 2\n", sensorName, accuracy); } } else { /* Update every STATE_SAVE_PERIOD minutes */ if ((stateUpdateCounter * STATE_SAVE_PERIOD) < millis()) { - LOG_DEBUG("%s state update every %d minutes\n", sensorName, STATE_SAVE_PERIOD); + LOG_DEBUG("%s state update every %d minutes\n", sensorName, STATE_SAVE_PERIOD / 60000); update = true; stateUpdateCounter++; } @@ -103,22 +104,15 @@ void BME680Sensor::updateState() if (update) { bme680.getState(bsecState); - std::string filenameTmp = bsecConfigFileName; - filenameTmp += ".tmp"; + if (FSCom.exists(bsecConfigFileName) && !FSCom.remove(bsecConfigFileName)) { + LOG_WARN("Can't remove old state file\n"); + } auto file = FSCom.open(bsecConfigFileName, FILE_O_WRITE); if (file) { LOG_INFO("%s state write to %s.\n", sensorName, bsecConfigFileName); file.write((uint8_t *)&bsecState, BSEC_MAX_STATE_BLOB_SIZE); file.flush(); file.close(); - // brief window of risk here ;-) - if (FSCom.exists(bsecConfigFileName) && !FSCom.remove(bsecConfigFileName)) { - LOG_WARN("Can't remove old state file\n"); - } - if (!renameFile(filenameTmp.c_str(), bsecConfigFileName)) { - LOG_ERROR("Error: can't rename new state file\n"); - } - } else { LOG_INFO("Can't write %s state (File: %s).\n", sensorName, bsecConfigFileName); } diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.cpp b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp new file mode 100644 index 000000000..b0991749b --- /dev/null +++ b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp @@ -0,0 +1,31 @@ +#include "BMP085Sensor.h" +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include "configuration.h" +#include +#include + +BMP085Sensor::BMP085Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BMP085, "BMP085") {} + +int32_t BMP085Sensor::runOnce() +{ + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + bmp085 = Adafruit_BMP085(); + status = bmp085.begin(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second); + + return initI2CSensor(); +} + +void BMP085Sensor::setup() {} + +bool BMP085Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + LOG_DEBUG("BMP085Sensor::getMetrics\n"); + measurement->variant.environment_metrics.temperature = bmp085.readTemperature(); + measurement->variant.environment_metrics.barometric_pressure = bmp085.readPressure() / 100.0F; + + return true; +} diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.h b/src/modules/Telemetry/Sensor/BMP085Sensor.h new file mode 100644 index 000000000..c4a9479b9 --- /dev/null +++ b/src/modules/Telemetry/Sensor/BMP085Sensor.h @@ -0,0 +1,17 @@ +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +class BMP085Sensor : public TelemetrySensor +{ + private: + Adafruit_BMP085 bmp085; + + protected: + virtual void setup() override; + + public: + BMP085Sensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/INA219Sensor.cpp b/src/modules/Telemetry/Sensor/INA219Sensor.cpp index 5a1faa99f..ecb564368 100644 --- a/src/modules/Telemetry/Sensor/INA219Sensor.cpp +++ b/src/modules/Telemetry/Sensor/INA219Sensor.cpp @@ -4,6 +4,10 @@ #include "configuration.h" #include +#ifndef INA219_MULTIPLIER +#define INA219_MULTIPLIER 1.0f +#endif + INA219Sensor::INA219Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_INA219, "INA219") {} int32_t INA219Sensor::runOnce() @@ -26,7 +30,7 @@ void INA219Sensor::setup() {} bool INA219Sensor::getMetrics(meshtastic_Telemetry *measurement) { measurement->variant.environment_metrics.voltage = ina219.getBusVoltage_V(); - measurement->variant.environment_metrics.current = ina219.getCurrent_mA(); + measurement->variant.environment_metrics.current = ina219.getCurrent_mA() * INA219_MULTIPLIER; return true; } diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp index 7ffb68254..35978d970 100644 --- a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp +++ b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp @@ -12,7 +12,8 @@ int32_t SHT31Sensor::runOnce() if (!hasSensor()) { return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; } - status = sht31.begin(); + sht31 = Adafruit_SHT31(nodeTelemetrySensorsMap[sensorType].second); + status = sht31.begin(nodeTelemetrySensorsMap[sensorType].first); return initI2CSensor(); } diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.h b/src/modules/Telemetry/Sensor/SHT31Sensor.h index 940361325..c6f8f1596 100644 --- a/src/modules/Telemetry/Sensor/SHT31Sensor.h +++ b/src/modules/Telemetry/Sensor/SHT31Sensor.h @@ -5,7 +5,7 @@ class SHT31Sensor : public TelemetrySensor { private: - Adafruit_SHT31 sht31 = Adafruit_SHT31(); + Adafruit_SHT31 sht31; protected: virtual void setup() override; diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp index bf7eaa0cd..aa0b6a1eb 100644 --- a/src/modules/TraceRouteModule.cpp +++ b/src/modules/TraceRouteModule.cpp @@ -1,5 +1,4 @@ #include "TraceRouteModule.h" -#include "FloodingRouter.h" #include "MeshService.h" TraceRouteModule *traceRouteModule; @@ -14,23 +13,17 @@ bool TraceRouteModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, m return false; // let it be handled by RoutingModule } -void TraceRouteModule::updateRoute(meshtastic_MeshPacket *p) +void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) { - auto &incoming = p->decoded; - // Only append an ID for the request (one way) - if (!incoming.request_id) { - meshtastic_RouteDiscovery scratch; - meshtastic_RouteDiscovery *updated = NULL; - memset(&scratch, 0, sizeof(scratch)); - pb_decode_from_bytes(incoming.payload.bytes, incoming.payload.size, &meshtastic_RouteDiscovery_msg, &scratch); - updated = &scratch; - - appendMyID(updated); - printRoute(updated, p->from, NODENUM_BROADCAST); + auto &incoming = p.decoded; + // Only append an ID for the request (one way) and if we are not the destination (the reply will have our NodeNum already) + if (!incoming.request_id && p.to != nodeDB->getNodeNum()) { + appendMyID(r); + printRoute(r, p.from, NODENUM_BROADCAST); // Set updated route to the payload of the to be flooded packet - p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), - &meshtastic_RouteDiscovery_msg, updated); + p.decoded.payload.size = + pb_encode_to_bytes(p.decoded.payload.bytes, sizeof(p.decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, r); } } @@ -85,4 +78,5 @@ TraceRouteModule::TraceRouteModule() : ProtobufModule("traceroute", meshtastic_PortNum_TRACEROUTE_APP, &meshtastic_RouteDiscovery_msg) { ourPortNum = meshtastic_PortNum_TRACEROUTE_APP; -} + isPromiscuous = true; // We need to update the route even if it is not destined to us +} \ No newline at end of file diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h index 8e8b64561..15e01debd 100644 --- a/src/modules/TraceRouteModule.h +++ b/src/modules/TraceRouteModule.h @@ -9,18 +9,14 @@ class TraceRouteModule : public ProtobufModule public: TraceRouteModule(); - // Let FloodingRouter/NextHopRouter call updateRoute upon rebroadcasting a TraceRoute request - friend class FloodingRouter; - friend class NextHopRouter; - protected: bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_RouteDiscovery *r) override; virtual meshtastic_MeshPacket *allocReply() override; - /* Call before rebroadcasting a RouteDiscovery payload in order to update + /* Called before rebroadcasting a RouteDiscovery payload in order to update the route array containing the IDs of nodes this packet went through */ - void updateRoute(meshtastic_MeshPacket *p); + void alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) override; private: // Call to add your ID to the route array of a RouteDiscovery message diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index a2e43347a..4a7b1c2c6 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -1,6 +1,5 @@ - #include "configuration.h" -#if defined(ARCH_ESP32) +#if defined(ARCH_ESP32) && defined(USE_SX1280) #include "AudioModule.h" #include "FSCommon.h" #include "MeshService.h" @@ -8,14 +7,6 @@ #include "RTC.h" #include "Router.h" -#ifdef OLED_RU -#include "graphics/fonts/OLEDDisplayFontsRU.h" -#endif - -#ifdef OLED_UA -#include "graphics/fonts/OLEDDisplayFontsUA.h" -#endif - /* AudioModule A interface to send raw codec2 audio data over the mesh network. Based on the example code from the ESP32_codec2 project. @@ -48,32 +39,7 @@ AudioModule *audioModule; #define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x) #endif -#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 -#define FONT_MEDIUM ArialMT_Plain_24 -#define FONT_LARGE ArialMT_Plain_24 -#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 -#endif -#endif -#define FONT_MEDIUM ArialMT_Plain_16 -#define FONT_LARGE ArialMT_Plain_24 -#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" void run_codec2(void *parameter) { @@ -145,7 +111,7 @@ AudioModule::AudioModule() : SinglePortModule("AudioModule", meshtastic_PortNum_ encode_frame_num = (meshtastic_Constants_DATA_PAYLOAD_LEN - sizeof(tx_header)) / encode_codec_size; encode_frame_size = encode_frame_num * encode_codec_size; // max 233 bytes + 4 header bytes adc_buffer_size = codec2_samples_per_frame(codec2); - LOG_INFO(" using %d frames of %d bytes for a total payload length of %d bytes\n", encode_frame_num, encode_codec_size, + LOG_INFO("using %d frames of %d bytes for a total payload length of %d bytes\n", encode_frame_num, encode_codec_size, encode_frame_size); xTaskCreate(&run_codec2, "codec2_task", 30000, NULL, 5, &codec2HandlerTask); } else { @@ -307,7 +273,7 @@ ProcessMessage AudioModule::handleReceived(const meshtastic_MeshPacket &mp) { if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { auto &p = mp.decoded; - if (getFrom(&mp) != nodeDB.getNodeNum()) { + if (getFrom(&mp) != nodeDB->getNodeNum()) { memcpy(rx_encode_frame, p.payload.bytes, p.payload.size); radio_state = RadioState::rx; rx_encode_frame_index = p.payload.size; diff --git a/src/modules/esp32/AudioModule.h b/src/modules/esp32/AudioModule.h index 8bf8c3f55..b6762706a 100644 --- a/src/modules/esp32/AudioModule.h +++ b/src/modules/esp32/AudioModule.h @@ -3,7 +3,7 @@ #include "SinglePortModule.h" #include "concurrency/NotifiedWorkerThread.h" #include "configuration.h" -#if defined(ARCH_ESP32) +#if defined(ARCH_ESP32) && defined(USE_SX1280) #include "NodeDB.h" #include #include diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp new file mode 100644 index 000000000..aad7b5d63 --- /dev/null +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -0,0 +1,134 @@ +#include "configuration.h" +#if defined(ARCH_ESP32) +#include "Default.h" +#include "MeshService.h" +#include "PaxcounterModule.h" +#include + +PaxcounterModule *paxcounterModule; + +/** + * Callback function for libpax. + * We only clear our sent flag here, since this function is called from another thread, so we + * cannot send to the mesh directly. + */ +void PaxcounterModule::handlePaxCounterReportRequest() +{ + // The libpax library already updated our data structure, just before invoking this callback. + LOG_INFO("PaxcounterModule: libpax reported new data: wifi=%d; ble=%d; uptime=%lu\n", + paxcounterModule->count_from_libpax.wifi_count, paxcounterModule->count_from_libpax.ble_count, millis() / 1000); + paxcounterModule->reportedDataSent = false; + paxcounterModule->setIntervalFromNow(0); +} + +PaxcounterModule::PaxcounterModule() + : concurrency::OSThread("PaxcounterModule"), + ProtobufModule("paxcounter", meshtastic_PortNum_PAXCOUNTER_APP, &meshtastic_Paxcount_msg) +{ +} + +/** + * Send the Pax information to the mesh if we got new data from libpax. + * This is called periodically from our runOnce() method and will actually send the data to the mesh + * if libpax updated it since the last transmission through the callback. + * @param dest - destination node (usually NODENUM_BROADCAST) + * @return false if sending is unnecessary, true if information was sent + */ +bool PaxcounterModule::sendInfo(NodeNum dest) +{ + if (paxcounterModule->reportedDataSent) + return false; + + LOG_INFO("PaxcounterModule: sending pax info wifi=%d; ble=%d; uptime=%lu\n", count_from_libpax.wifi_count, + count_from_libpax.ble_count, millis() / 1000); + + meshtastic_Paxcount pl = meshtastic_Paxcount_init_default; + pl.wifi = count_from_libpax.wifi_count; + pl.ble = count_from_libpax.ble_count; + pl.uptime = millis() / 1000; + + meshtastic_MeshPacket *p = allocDataProtobuf(pl); + p->to = dest; + p->decoded.want_response = false; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; + + service.sendToMesh(p, RX_SRC_LOCAL, true); + + paxcounterModule->reportedDataSent = true; + + return true; +} + +bool PaxcounterModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Paxcount *p) +{ + return false; // Let others look at this message also if they want. We don't do anything with received packets. +} + +meshtastic_MeshPacket *PaxcounterModule::allocReply() +{ + if (ignoreRequest) { + return NULL; + } + + meshtastic_Paxcount pl = meshtastic_Paxcount_init_default; + pl.wifi = count_from_libpax.wifi_count; + pl.ble = count_from_libpax.ble_count; + pl.uptime = millis() / 1000; + return allocDataProtobuf(pl); +} + +int32_t PaxcounterModule::runOnce() +{ + if (isActive()) { + if (firstTime) { + firstTime = false; + LOG_DEBUG("Paxcounter starting up with interval of %d seconds\n", + Default::getConfiguredOrDefault(moduleConfig.paxcounter.paxcounter_update_interval, + default_broadcast_interval_secs)); + struct libpax_config_t configuration; + libpax_default_config(&configuration); + + configuration.blecounter = 1; + configuration.blescantime = 0; // infinite + configuration.wificounter = 1; + configuration.wifi_channel_map = WIFI_CHANNEL_ALL; + configuration.wifi_channel_switch_interval = 50; + configuration.wifi_rssi_threshold = -80; + configuration.ble_rssi_threshold = -80; + libpax_update_config(&configuration); + + // internal processing initialization + libpax_counter_init(handlePaxCounterReportRequest, &count_from_libpax, + moduleConfig.paxcounter.paxcounter_update_interval, 0); + libpax_counter_start(); + } else { + sendInfo(NODENUM_BROADCAST); + } + return Default::getConfiguredOrDefaultMs(moduleConfig.paxcounter.paxcounter_update_interval, + default_broadcast_interval_secs); + } else { + return disable(); + } +} + +#if HAS_SCREEN + +#include "graphics/ScreenFonts.h" + +void PaxcounterModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + char buffer[50]; + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + display->drawString(x + 0, y + 0, "PAX"); + + libpax_counter_count(&count_from_libpax); + + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_SMALL); + display->drawStringf(display->getWidth() / 2 + x, 0 + y + 12, buffer, "WiFi: %d\nBLE: %d\nuptime: %ds", + count_from_libpax.wifi_count, count_from_libpax.ble_count, millis() / 1000); +} +#endif // HAS_SCREEN + +#endif diff --git a/src/modules/esp32/PaxcounterModule.h b/src/modules/esp32/PaxcounterModule.h new file mode 100644 index 000000000..ebd6e7191 --- /dev/null +++ b/src/modules/esp32/PaxcounterModule.h @@ -0,0 +1,38 @@ +#pragma once + +#include "ProtobufModule.h" +#include "configuration.h" +#if defined(ARCH_ESP32) +#include "../mesh/generated/meshtastic/paxcount.pb.h" +#include "NodeDB.h" +#include + +/** + * Wrapper module for the estimate passenger (PAX) count library (https://github.com/dbinfrago/libpax) which + * implements the core functionality of the ESP32 Paxcounter project (https://github.com/cyberman54/ESP32-Paxcounter) + */ +class PaxcounterModule : private concurrency::OSThread, public ProtobufModule +{ + bool firstTime = true; + bool reportedDataSent = true; + + static void handlePaxCounterReportRequest(); + + public: + PaxcounterModule(); + + protected: + struct count_payload_t count_from_libpax = {0, 0, 0}; + virtual int32_t runOnce() override; + bool sendInfo(NodeNum dest = NODENUM_BROADCAST); + virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Paxcount *p) override; + virtual meshtastic_MeshPacket *allocReply() override; + bool isActive() { return moduleConfig.paxcounter.enabled && !config.bluetooth.enabled && !config.network.wifi_enabled; } +#if HAS_SCREEN + virtual bool wantUIFrame() override { return isActive(); } + virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override; +#endif +}; + +extern PaxcounterModule *paxcounterModule; +#endif \ No newline at end of file diff --git a/src/modules/esp32/StoreForwardModule.cpp b/src/modules/esp32/StoreForwardModule.cpp index 6b2c079cc..12cddc520 100644 --- a/src/modules/esp32/StoreForwardModule.cpp +++ b/src/modules/esp32/StoreForwardModule.cpp @@ -38,25 +38,21 @@ int32_t StoreForwardModule::runOnce() // Only send packets if the channel is less than 25% utilized. if (airTime->isTxAllowedChannelUtil(true)) { storeForwardModule->sendPayload(this->busyTo, this->packetHistoryTXQueue_index); - if (this->packetHistoryTXQueue_index == packetHistoryTXQueue_size) { - // Tell the client we're done sending - meshtastic_StoreAndForward sf = meshtastic_StoreAndForward_init_zero; - sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_PING; - storeForwardModule->sendMessage(this->busyTo, sf); - LOG_INFO("*** S&F - Done. (ROUTER_PING)\n"); + if (this->packetHistoryTXQueue_index < packetHistoryTXQueue_size - 1) { + this->packetHistoryTXQueue_index++; + } else { this->packetHistoryTXQueue_index = 0; this->busy = false; - } else { - this->packetHistoryTXQueue_index++; } } - } else if ((millis() - lastHeartbeat > (heartbeatInterval * 1000)) && airTime->isTxAllowedChannelUtil(true)) { + } else if (this->heartbeat && (millis() - lastHeartbeat > (heartbeatInterval * 1000)) && + airTime->isTxAllowedChannelUtil(true)) { lastHeartbeat = millis(); LOG_INFO("*** Sending heartbeat\n"); meshtastic_StoreAndForward sf = meshtastic_StoreAndForward_init_zero; sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_HEARTBEAT; sf.which_variant = meshtastic_StoreAndForward_heartbeat_tag; - sf.variant.heartbeat.period = 300; + sf.variant.heartbeat.period = heartbeatInterval; sf.variant.heartbeat.secondary = 0; // TODO we always have one primary router for now storeForwardModule->sendMessage(NODENUM_BROADCAST, sf); } @@ -104,7 +100,8 @@ void StoreForwardModule::populatePSRAM() */ void StoreForwardModule::historySend(uint32_t msAgo, uint32_t to) { - uint32_t queueSize = storeForwardModule->historyQueueCreate(msAgo, to); + uint32_t lastIndex = lastRequest.find(to) != lastRequest.end() ? lastRequest[to] : 0; + uint32_t queueSize = storeForwardModule->historyQueueCreate(msAgo, to, &lastIndex); if (queueSize) { LOG_INFO("*** S&F - Sending %u message(s)\n", queueSize); @@ -118,6 +115,8 @@ void StoreForwardModule::historySend(uint32_t msAgo, uint32_t to) sf.which_variant = meshtastic_StoreAndForward_history_tag; sf.variant.history.history_messages = queueSize; sf.variant.history.window = msAgo; + sf.variant.history.last_request = lastIndex; + lastRequest[to] = lastIndex; storeForwardModule->sendMessage(to, sf); } @@ -125,44 +124,49 @@ void StoreForwardModule::historySend(uint32_t msAgo, uint32_t to) * Creates a new history queue with messages that were received within the specified time frame. * * @param msAgo The number of milliseconds ago to start the history queue. - * @param to The maximum number of messages to include in the history queue. + * @param to The NodeNum of the recipient. + * @param last_request_index The index in the packet history of the last request from this node. * @return The ID of the newly created history queue. */ -uint32_t StoreForwardModule::historyQueueCreate(uint32_t msAgo, uint32_t to) +uint32_t StoreForwardModule::historyQueueCreate(uint32_t msAgo, uint32_t to, uint32_t *last_request_index) { this->packetHistoryTXQueue_size = 0; + // If our history was cleared, ignore the last request index + uint32_t last_index = *last_request_index > this->packetHistoryCurrent ? 0 : *last_request_index; - for (int i = 0; i < this->packetHistoryCurrent; i++) { + for (uint32_t i = last_index; i < this->packetHistoryCurrent; i++) { /* LOG_DEBUG("SF historyQueueCreate\n"); LOG_DEBUG("SF historyQueueCreate - time %d\n", this->packetHistory[i].time); LOG_DEBUG("SF historyQueueCreate - millis %d\n", millis()); LOG_DEBUG("SF historyQueueCreate - math %d\n", (millis() - msAgo)); */ - if (this->packetHistory[i].time && (this->packetHistory[i].time < (millis() - msAgo))) { - LOG_DEBUG("*** SF historyQueueCreate - Time matches - ok\n"); - /* - Copy the messages that were received by the router in the last msAgo - to the packetHistoryTXQueue structure. + if (this->packetHistoryTXQueue_size < this->historyReturnMax) { + if (this->packetHistory[i].time && (this->packetHistory[i].time < (millis() - msAgo))) { + /* Copy the messages that were received by the router in the last msAgo + to the packetHistoryTXQueue structure. + Client not interested in packets from itself and only in broadcast packets or packets towards it. */ + if (this->packetHistory[i].from != to && + (this->packetHistory[i].to == NODENUM_BROADCAST || this->packetHistory[i].to == to)) { + this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].time = this->packetHistory[i].time; + this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].to = this->packetHistory[i].to; + this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].from = this->packetHistory[i].from; + this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].channel = this->packetHistory[i].channel; + this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload_size = + this->packetHistory[i].payload_size; + memcpy(this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload, this->packetHistory[i].payload, + meshtastic_Constants_DATA_PAYLOAD_LEN); + this->packetHistoryTXQueue_size++; + *last_request_index = i + 1; // Set to one higher such that we don't send the same message again - TODO: The condition (this->packetHistory[i].to & NODENUM_BROADCAST) == to) is not tested since - I don't have an easy way to target a specific user. Will need to do this soon. - */ - if ((this->packetHistory[i].to & NODENUM_BROADCAST) == NODENUM_BROADCAST || - ((this->packetHistory[i].to & NODENUM_BROADCAST) == to)) { - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].time = this->packetHistory[i].time; - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].to = this->packetHistory[i].to; - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].from = this->packetHistory[i].from; - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].channel = this->packetHistory[i].channel; - this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload_size = this->packetHistory[i].payload_size; - memcpy(this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload, this->packetHistory[i].payload, - meshtastic_Constants_DATA_PAYLOAD_LEN); - this->packetHistoryTXQueue_size++; - - LOG_DEBUG("*** PacketHistoryStruct time=%d\n", this->packetHistory[i].time); - LOG_DEBUG("*** PacketHistoryStruct msg=%s\n", this->packetHistory[i].payload); + LOG_DEBUG("*** PacketHistoryStruct time=%d, msg=%s\n", this->packetHistory[i].time, + this->packetHistory[i].payload); + } } + } else { + LOG_WARN("*** S&F - Maximum history return reached.\n"); + return this->packetHistoryTXQueue_size; } } return this->packetHistoryTXQueue_size; @@ -177,6 +181,15 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp) { const auto &p = mp.decoded; + if (this->packetHistoryCurrent == this->records) { + LOG_WARN("*** S&F - PSRAM Full. Starting overwrite now.\n"); + this->packetHistoryCurrent = 0; + this->packetHistoryMax = 0; + for (auto &i : lastRequest) { + i.second = 0; // Clear the last request index for each client device + } + } + this->packetHistory[this->packetHistoryCurrent].time = millis(); this->packetHistory[this->packetHistoryCurrent].to = mp.to; this->packetHistory[this->packetHistoryCurrent].channel = mp.channel; @@ -213,10 +226,19 @@ void StoreForwardModule::sendPayload(NodeNum dest, uint32_t packetHistory_index) // TODO: Make this configurable. p->want_ack = false; - p->decoded.payload.size = - this->packetHistoryTXQueue[packetHistory_index].payload_size; // You must specify how many bytes are in the reply - memcpy(p->decoded.payload.bytes, this->packetHistoryTXQueue[packetHistory_index].payload, + meshtastic_StoreAndForward sf = meshtastic_StoreAndForward_init_zero; + sf.which_variant = meshtastic_StoreAndForward_text_tag; + sf.variant.text.size = this->packetHistoryTXQueue[packetHistory_index].payload_size; + memcpy(sf.variant.text.bytes, this->packetHistoryTXQueue[packetHistory_index].payload, this->packetHistoryTXQueue[packetHistory_index].payload_size); + if (this->packetHistoryTXQueue[packetHistory_index].to == NODENUM_BROADCAST) { + sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_TEXT_BROADCAST; + } else { + sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_TEXT_DIRECT; + } + + p->decoded.payload.size = + pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_StoreAndForward_msg, &sf); service.sendToMesh(p); } @@ -233,7 +255,7 @@ void StoreForwardModule::sendMessage(NodeNum dest, const meshtastic_StoreAndForw p->to = dest; - p->priority = meshtastic_MeshPacket_Priority_MIN; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; // FIXME - Determine if the delayed packet is broadcast or delayed. For now, assume // everything is broadcast. @@ -298,11 +320,12 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m if (moduleConfig.store_forward.enabled) { // The router node should not be sending messages as a client. Unless he is a ROUTER_CLIENT - if ((getFrom(&mp) != nodeDB.getNodeNum()) || (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT)) { + if ((getFrom(&mp) != nodeDB->getNodeNum()) || (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT)) { if ((mp.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) && is_server) { auto &p = mp.decoded; - if ((p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && (p.payload.bytes[2] == 0x00)) { + if (mp.to == nodeDB->getNodeNum() && (p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && + (p.payload.bytes[2] == 0x00)) { LOG_DEBUG("*** Legacy Request to send\n"); // Send the last 60 minutes of messages. @@ -311,7 +334,7 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m LOG_INFO("*** S&F - Busy. Try again shortly.\n"); meshtastic_MeshPacket *pr = allocReply(); pr->to = getFrom(&mp); - pr->priority = meshtastic_MeshPacket_Priority_MIN; + pr->priority = meshtastic_MeshPacket_Priority_BACKGROUND; pr->want_ack = false; pr->decoded.want_response = false; pr->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; @@ -387,7 +410,8 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, LOG_INFO("*** S&F - Busy. Try again shortly.\n"); } else { if ((p->which_variant == meshtastic_StoreAndForward_history_tag) && (p->variant.history.window > 0)) { - storeForwardModule->historySend(p->variant.history.window * 60000, getFrom(&mp)); // window is in minutes + // window is in minutes + storeForwardModule->historySend(p->variant.history.window * 60000, getFrom(&mp)); } else { storeForwardModule->historySend(historyReturnWindow * 60000, getFrom(&mp)); // defaults to 4 hours } @@ -406,8 +430,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, case meshtastic_StoreAndForward_RequestResponse_CLIENT_PONG: if (is_server) { LOG_INFO("*** StoreAndForward_RequestResponse_CLIENT_PONG\n"); - // The Client is alive, update NodeDB - nodeDB.updateFrom(mp); + // NodeDB is already updated } break; @@ -483,7 +506,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, break; default: - assert(0); // unexpected state + break; // no need to do anything } return true; // There's no need for others to look at this message. } @@ -532,6 +555,8 @@ StoreForwardModule::StoreForwardModule() // send heartbeat advertising? if (moduleConfig.store_forward.heartbeat) this->heartbeat = moduleConfig.store_forward.heartbeat; + else + this->heartbeat = false; // Popupate PSRAM with our data structures. this->populatePSRAM(); @@ -546,9 +571,7 @@ StoreForwardModule::StoreForwardModule() } // Client - } - if ((config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT) || - (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT)) { + } else { is_client = true; LOG_INFO("*** Initializing Store & Forward Module in Client mode\n"); } diff --git a/src/modules/esp32/StoreForwardModule.h b/src/modules/esp32/StoreForwardModule.h index 806f0a836..0d2fb9fce 100644 --- a/src/modules/esp32/StoreForwardModule.h +++ b/src/modules/esp32/StoreForwardModule.h @@ -7,13 +7,13 @@ #include "configuration.h" #include #include +#include struct PacketHistoryStruct { uint32_t time; uint32_t to; uint32_t from; uint8_t channel; - bool ack; uint8_t payload[meshtastic_Constants_DATA_PAYLOAD_LEN]; pb_size_t payload_size; }; @@ -32,16 +32,19 @@ class StoreForwardModule : private concurrency::OSThread, public ProtobufModule< uint32_t packetHistoryTXQueue_size = 0; uint32_t packetHistoryTXQueue_index = 0; - uint32_t packetTimeMax = 5000; + uint32_t packetTimeMax = 5000; // Interval between sending history packets as a server. bool is_client = false; bool is_server = false; + // Unordered_map stores the last request for each nodeNum (`to` field) + std::unordered_map lastRequest; + public: StoreForwardModule(); unsigned long lastHeartbeat = 0; - uint32_t heartbeatInterval = 300; + uint32_t heartbeatInterval = 900; /** Update our local reference of when we last saw that node. @@ -51,7 +54,7 @@ class StoreForwardModule : private concurrency::OSThread, public ProtobufModule< void statsSend(uint32_t to); void historySend(uint32_t msAgo, uint32_t to); - uint32_t historyQueueCreate(uint32_t msAgo, uint32_t to); + uint32_t historyQueueCreate(uint32_t msAgo, uint32_t to, uint32_t *last_request_index); /** * Send our payload into the mesh @@ -79,16 +82,16 @@ class StoreForwardModule : private concurrency::OSThread, public ProtobufModule< void populatePSRAM(); // S&F Defaults - uint32_t historyReturnMax = 250; // 250 records - uint32_t historyReturnWindow = 240; // 4 hours + uint32_t historyReturnMax = 25; // Return maximum of 25 records by default. + uint32_t historyReturnWindow = 240; // Return history of last 4 hours by default. uint32_t records = 0; // Calculated bool heartbeat = false; // No heartbeat. // stats - uint32_t requests = 0; - uint32_t requests_history = 0; + uint32_t requests = 0; // Number of times any client sent a request to the S&F. + uint32_t requests_history = 0; // Number of times the history was requested. - uint32_t retry_delay = 0; + uint32_t retry_delay = 0; // If server is busy, retry after this delay (in ms). protected: virtual int32_t runOnce() override; @@ -102,4 +105,4 @@ class StoreForwardModule : private concurrency::OSThread, public ProtobufModule< virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_StoreAndForward *p); }; -extern StoreForwardModule *storeForwardModule; +extern StoreForwardModule *storeForwardModule; \ No newline at end of file diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 29a634922..da1c204b8 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -2,17 +2,23 @@ #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" +#include "configuration.h" #include "main.h" #include "mesh/Channels.h" #include "mesh/Router.h" #include "mesh/generated/meshtastic/mqtt.pb.h" #include "mesh/generated/meshtastic/telemetry.pb.h" -#include "mesh/http/WiFiAPClient.h" +#include "modules/RoutingModule.h" +#if defined(ARCH_ESP32) +#include "../mesh/generated/meshtastic/paxcount.pb.h" +#endif +#include "mesh/generated/meshtastic/remote_hardware.pb.h" #include "sleep.h" -#if HAS_WIFI +#if HAS_WIFI && !MESHTASTIC_EXCLUDE_WIFI +#include "mesh/wifi/WiFiAPClient.h" #include #endif -#include "mqtt/JSON.h" +#include "Default.h" #include const int reconnectMax = 5; @@ -44,107 +50,109 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) payloadStr[length] = 0; // null terminated string JSONValue *json_value = JSON::Parse(payloadStr); if (json_value != NULL) { - LOG_INFO("JSON Received on MQTT, parsing..\n"); - // check if it is a valid envelope JSONObject json; json = json_value->AsObject(); - // parse the channel name from the topic string - char *ptr = strtok(topic, "/"); - for (int i = 0; i < 3; i++) { - ptr = strtok(NULL, "/"); - } + // parse the channel name from the topic string by looking for "json/" + const char *jsonSlash = "json/"; + char *ptr = strstr(topic, jsonSlash) + sizeof(jsonSlash) + 1; // set pointer to after "json/" + ptr = strtok(ptr, "/") ? strtok(ptr, "/") : ptr; // if another "/" was added, parse string up to that character meshtastic_Channel sendChannel = channels.getByName(ptr); - LOG_DEBUG("Found Channel name: %s (Index %d)\n", channels.getGlobalId(sendChannel.index), sendChannel.index); + // We allow downlink JSON packets only on a channel named "mqtt" + if (strncasecmp(channels.getGlobalId(sendChannel.index), Channels::mqttChannel, strlen(Channels::mqttChannel)) == 0 && + sendChannel.settings.downlink_enabled) { + if (isValidJsonEnvelope(json)) { + // this is a valid envelope + if (json["type"]->AsString().compare("sendtext") == 0 && json["payload"]->IsString()) { + std::string jsonPayloadStr = json["payload"]->AsString(); + LOG_INFO("JSON payload %s, length %u\n", jsonPayloadStr.c_str(), jsonPayloadStr.length()); - if ((json.find("sender") != json.end()) && (json.find("payload") != json.end()) && - (json.find("type") != json.end()) && json["type"]->IsString() && - (json["type"]->AsString().compare("sendtext") == 0)) { - // this is a valid envelope - if (json["payload"]->IsString() && json["type"]->IsString() && - (json["sender"]->AsString().compare(owner.id) != 0)) { - std::string jsonPayloadStr = json["payload"]->AsString(); - LOG_INFO("JSON payload %s, length %u\n", jsonPayloadStr.c_str(), jsonPayloadStr.length()); - - // construct protobuf data packet using TEXT_MESSAGE, send it to the mesh - meshtastic_MeshPacket *p = router->allocForSending(); - p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; - p->channel = sendChannel.index; - if (sendChannel.settings.downlink_enabled) { + // construct protobuf data packet using TEXT_MESSAGE, send it to the mesh + meshtastic_MeshPacket *p = router->allocForSending(); + p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; + if (json.find("channel") != json.end() && json["channel"]->IsNumber() && + (json["channel"]->AsNumber() < channels.getNumChannels())) + p->channel = json["channel"]->AsNumber(); + if (json.find("to") != json.end() && json["to"]->IsNumber()) + p->to = json["to"]->AsNumber(); if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) { memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length()); p->decoded.payload.size = jsonPayloadStr.length(); - meshtastic_MeshPacket *packet = packetPool.allocCopy(*p); - service.sendToMesh(packet, RX_SRC_LOCAL); + service.sendToMesh(p, RX_SRC_LOCAL); } else { LOG_WARN("Received MQTT json payload too long, dropping\n"); } - } else { - LOG_WARN("Received MQTT json payload on channel %s, but downlink is disabled, dropping\n", - sendChannel.settings.name); - } - } else { - LOG_DEBUG("JSON Ignoring downlink message we originally sent.\n"); - } - } else if ((json.find("sender") != json.end()) && (json.find("payload") != json.end()) && - (json.find("type") != json.end()) && json["type"]->IsString() && - (json["type"]->AsString().compare("sendposition") == 0)) { - // invent the "sendposition" type for a valid envelope - if (json["payload"]->IsObject() && json["type"]->IsString() && - (json["sender"]->AsString().compare(owner.id) != 0)) { - JSONObject posit; - posit = json["payload"]->AsObject(); // get nested JSON Position - meshtastic_Position pos = meshtastic_Position_init_default; - pos.latitude_i = posit["latitude_i"]->AsNumber(); - pos.longitude_i = posit["longitude_i"]->AsNumber(); - pos.altitude = posit["altitude"]->AsNumber(); - pos.time = posit["time"]->AsNumber(); + } else if (json["type"]->AsString().compare("sendposition") == 0 && json["payload"]->IsObject()) { + // invent the "sendposition" type for a valid envelope + JSONObject posit; + posit = json["payload"]->AsObject(); // get nested JSON Position + meshtastic_Position pos = meshtastic_Position_init_default; + if (posit.find("latitude_i") != posit.end() && posit["latitude_i"]->IsNumber()) + pos.latitude_i = posit["latitude_i"]->AsNumber(); + if (posit.find("longitude_i") != posit.end() && posit["longitude_i"]->IsNumber()) + pos.longitude_i = posit["longitude_i"]->AsNumber(); + if (posit.find("altitude") != posit.end() && posit["altitude"]->IsNumber()) + pos.altitude = posit["altitude"]->AsNumber(); + if (posit.find("time") != posit.end() && posit["time"]->IsNumber()) + pos.time = posit["time"]->AsNumber(); - // construct protobuf data packet using POSITION, send it to the mesh - meshtastic_MeshPacket *p = router->allocForSending(); - p->decoded.portnum = meshtastic_PortNum_POSITION_APP; - p->channel = sendChannel.index; - if (sendChannel.settings.downlink_enabled) { + // construct protobuf data packet using POSITION, send it to the mesh + meshtastic_MeshPacket *p = router->allocForSending(); + p->decoded.portnum = meshtastic_PortNum_POSITION_APP; + if (json.find("channel") != json.end() && json["channel"]->IsNumber() && + (json["channel"]->AsNumber() < channels.getNumChannels())) + p->channel = json["channel"]->AsNumber(); + if (json.find("to") != json.end() && json["to"]->IsNumber()) + p->to = json["to"]->AsNumber(); p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_Position_msg, &pos); // make the Data protobuf from position service.sendToMesh(p, RX_SRC_LOCAL); } else { - LOG_WARN("Received MQTT json payload on channel %s, but downlink is disabled, dropping\n", - sendChannel.settings.name); + LOG_DEBUG("JSON Ignoring downlink message with unsupported type.\n"); } } else { - LOG_DEBUG("JSON Ignoring downlink message we originally sent.\n"); + LOG_ERROR("JSON Received payload on MQTT but not a valid envelope.\n"); } } else { - LOG_ERROR("JSON Received payload on MQTT but not a valid envelope\n"); + LOG_WARN("JSON downlink received on channel not called 'mqtt' or without downlink enabled.\n"); } } else { // no json, this is an invalid payload - LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); + LOG_ERROR("JSON Received payload on MQTT but not a valid JSON\n"); } delete json_value; } else { - if (!pb_decode_from_bytes(payload, length, &meshtastic_ServiceEnvelope_msg, &e)) { + if (length == 0) { + LOG_WARN("Empty MQTT payload received, topic %s!\n", topic); + return; + } else if (!pb_decode_from_bytes(payload, length, &meshtastic_ServiceEnvelope_msg, &e)) { LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length); return; } else { - if (strcmp(e.gateway_id, owner.id) == 0) - LOG_INFO("Ignoring downlink message we originally sent.\n"); - else { + meshtastic_Channel ch = channels.getByName(e.channel_id); + if (strcmp(e.gateway_id, owner.id) == 0) { + // Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message. + // We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node + // receives it when we get our own packet back. Then we'll stop our retransmissions. + if (e.packet && getFrom(e.packet) == nodeDB->getNodeNum()) + routingModule->sendAckNak(meshtastic_Routing_Error_NONE, getFrom(e.packet), e.packet->id, ch.index); + else + LOG_INFO("Ignoring downlink message we originally sent.\n"); + } else { // Find channel by channel_id and check downlink_enabled - meshtastic_Channel ch = channels.getByName(e.channel_id); if (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && e.packet && ch.settings.downlink_enabled) { LOG_INFO("Received MQTT topic %s, len=%u\n", topic, length); meshtastic_MeshPacket *p = packetPool.allocCopy(*e.packet); + p->via_mqtt = true; // Mark that the packet was received via MQTT if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { p->channel = ch.index; } - // ignore messages sent by us or if we don't have the channel key - if (router && p->from != nodeDB.getNodeNum() && perhapsDecode(p)) + // ignore messages if we don't have the channel key + if (router && perhapsDecode(p)) router->enqueueReceivedMessage(p); else packetPool.release(p); @@ -179,10 +187,19 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) statusTopic = moduleConfig.mqtt.root + statusTopic; cryptTopic = moduleConfig.mqtt.root + cryptTopic; jsonTopic = moduleConfig.mqtt.root + jsonTopic; + mapTopic = moduleConfig.mqtt.root + mapTopic; } else { statusTopic = "msh" + statusTopic; cryptTopic = "msh" + cryptTopic; jsonTopic = "msh" + jsonTopic; + mapTopic = "msh" + mapTopic; + } + + if (moduleConfig.mqtt.map_reporting_enabled && moduleConfig.mqtt.has_map_report_settings) { + map_position_precision = Default::getConfiguredOrDefault(moduleConfig.mqtt.map_report_settings.position_precision, + default_map_position_precision); + map_publish_interval_msecs = Default::getConfiguredOrDefaultMs( + moduleConfig.mqtt.map_report_settings.publish_interval_secs, default_map_publish_interval_secs); } #ifdef HAS_NETWORKING @@ -344,11 +361,13 @@ void MQTT::sendSubscriptions() std::string topic = cryptTopic + channels.getGlobalId(i) + "/#"; LOG_INFO("Subscribing to %s\n", topic.c_str()); pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right? +#ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 if (moduleConfig.mqtt.json_enabled == true) { std::string topicDecoded = jsonTopic + channels.getGlobalId(i) + "/#"; LOG_INFO("Subscribing to %s\n", topicDecoded.c_str()); pubSub.subscribe(topicDecoded.c_str(), 1); // FIXME, is QOS 1 right? } +#endif // ARCH_NRF52 } } #endif @@ -356,38 +375,30 @@ void MQTT::sendSubscriptions() bool MQTT::wantsLink() const { - bool hasChannel = false; + bool hasChannelorMapReport = + moduleConfig.mqtt.enabled && (moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled()); - if (moduleConfig.mqtt.enabled) { - // No need for link if no channel needed it - size_t numChan = channels.getNumChannels(); - for (size_t i = 0; i < numChan; i++) { - const auto &ch = channels.getByIndex(i); - if (ch.settings.uplink_enabled || ch.settings.downlink_enabled) { - hasChannel = true; - break; - } - } - } - if (hasChannel && moduleConfig.mqtt.proxy_to_client_enabled) + if (hasChannelorMapReport && moduleConfig.mqtt.proxy_to_client_enabled) return true; #if HAS_WIFI - return hasChannel && WiFi.isConnected(); + return hasChannelorMapReport && WiFi.isConnected(); #endif #if HAS_ETHERNET - return hasChannel && Ethernet.linkStatus() == LinkON; + return hasChannelorMapReport && Ethernet.linkStatus() == LinkON; #endif return false; } int32_t MQTT::runOnce() { - if (!moduleConfig.mqtt.enabled) + if (!moduleConfig.mqtt.enabled || !(moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled())) return disable(); bool wantConnection = wantsLink(); + perhapsReportToMap(); + // If connected poll rapidly, otherwise only occasionally check for a wifi connection change and ability to contact server if (moduleConfig.mqtt.proxy_to_client_enabled) { publishQueuedMessages(); @@ -443,6 +454,7 @@ void MQTT::publishQueuedMessages() publish(topic.c_str(), bytes, numBytes, false); +#ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 if (moduleConfig.mqtt.json_enabled) { // handle json topic auto jsonString = this->meshPacketToJson(env->packet); @@ -453,17 +465,21 @@ void MQTT::publishQueuedMessages() publish(topicJson.c_str(), jsonString.c_str(), false); } } +#endif // ARCH_NRF52 mqttPool.release(env); } } -void MQTT::onSend(const meshtastic_MeshPacket &mp, ChannelIndex chIndex) +void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket &mp_decoded, ChannelIndex chIndex) { + if (mp.via_mqtt) + return; // Don't send messages that came from MQTT back into MQTT + auto &ch = channels.getByIndex(chIndex); - if (&mp.decoded && strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && - (mp.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP || - mp.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) { + if (&mp_decoded.decoded && strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0 && + (mp_decoded.decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP || + mp_decoded.decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP)) { LOG_DEBUG("MQTT onSend - Ignoring range test or detection sensor message on public mqtt\n"); return; } @@ -474,8 +490,15 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, ChannelIndex chIndex) meshtastic_ServiceEnvelope *env = mqttPool.allocZeroed(); env->channel_id = (char *)channelId; env->gateway_id = owner.id; - env->packet = (meshtastic_MeshPacket *)∓ - LOG_DEBUG("MQTT onSend - Publishing portnum %i message\n", env->packet->decoded.portnum); + + LOG_DEBUG("MQTT onSend - Publishing "); + if (moduleConfig.mqtt.encryption_enabled) { + env->packet = (meshtastic_MeshPacket *)∓ + LOG_DEBUG("encrypted message\n"); + } else { + env->packet = (meshtastic_MeshPacket *)&mp_decoded; + LOG_DEBUG("portnum %i message\n", env->packet->decoded.portnum); + } if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) { // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets @@ -487,9 +510,10 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, ChannelIndex chIndex) publish(topic.c_str(), bytes, numBytes, false); +#ifndef ARCH_NRF52 // JSON is not supported on nRF52, see issue #2804 if (moduleConfig.mqtt.json_enabled) { // handle json topic - auto jsonString = this->meshPacketToJson((meshtastic_MeshPacket *)&mp); + auto jsonString = this->meshPacketToJson((meshtastic_MeshPacket *)&mp_decoded); if (jsonString.length() != 0) { std::string topicJson = jsonTopic + channelId + "/" + owner.id; LOG_INFO("JSON publish message to %s, %u bytes: %s\n", topicJson.c_str(), jsonString.length(), @@ -497,6 +521,7 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, ChannelIndex chIndex) publish(topicJson.c_str(), jsonString.c_str(), false); } } +#endif // ARCH_NRF52 } else { LOG_INFO("MQTT not connected, queueing packet\n"); if (mqttQueue.numFree() == 0) { @@ -513,16 +538,89 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, ChannelIndex chIndex) } } +void MQTT::perhapsReportToMap() +{ + if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly())) + return; + + if (millis() - last_report_to_map < map_publish_interval_msecs) { + return; + } else { + if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) { + last_report_to_map = millis(); + LOG_WARN("MQTT Map reporting is enabled, but precision is 0 or no position available.\n"); + return; + } + + // Allocate ServiceEnvelope and fill it + meshtastic_ServiceEnvelope *se = mqttPool.allocZeroed(); + se->channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()); // Use primary channel as the channel_id + se->gateway_id = owner.id; + + // Allocate MeshPacket and fill it + meshtastic_MeshPacket *mp = packetPool.allocZeroed(); + mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag; + mp->from = nodeDB->getNodeNum(); + mp->to = NODENUM_BROADCAST; + mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP; + + // Fill MapReport message + meshtastic_MapReport mapReport = meshtastic_MapReport_init_default; + memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name)); + memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name)); + mapReport.role = config.device.role; + mapReport.hw_model = owner.hw_model; + strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version)); + mapReport.region = config.lora.region; + mapReport.modem_preset = config.lora.modem_preset; + mapReport.has_default_channel = channels.hasDefaultChannel(); + + // Set position with precision (same as in PositionModule) + if (map_position_precision < 32 && map_position_precision > 0) { + mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision)); + mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision)); + mapReport.latitude_i += (1 << (31 - map_position_precision)); + mapReport.longitude_i += (1 << (31 - map_position_precision)); + } else { + mapReport.latitude_i = localPosition.latitude_i; + mapReport.longitude_i = localPosition.longitude_i; + } + mapReport.altitude = localPosition.altitude; + mapReport.position_precision = map_position_precision; + + mapReport.num_online_local_nodes = nodeDB->getNumOnlineMeshNodes(true); + + // Encode MapReport message and set it to MeshPacket in ServiceEnvelope + mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), + &meshtastic_MapReport_msg, &mapReport); + se->packet = mp; + + // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets + static uint8_t bytes[meshtastic_MeshPacket_size + 64]; + size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se); + + LOG_INFO("MQTT Publish map report to %s\n", mapTopic.c_str()); + publish(mapTopic.c_str(), bytes, numBytes, false); + + // Release the allocated memory for ServiceEnvelope and MeshPacket + mqttPool.release(se); + packetPool.release(mp); + + // Update the last report time + last_report_to_map = millis(); + } +} + // converts a downstream packet into a json message std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) { // the created jsonObj is immutable after creation, so // we need to do the heavy lifting before assembling it. std::string msgType; - JSONObject msgPayload; JSONObject jsonObj; if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + JSONObject msgPayload; switch (mp->decoded.portnum) { case meshtastic_PortNum_TEXT_MESSAGE_APP: { msgType = "text"; @@ -558,6 +656,7 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) msgPayload["voltage"] = new JSONValue(decoded->variant.device_metrics.voltage); msgPayload["channel_utilization"] = new JSONValue(decoded->variant.device_metrics.channel_utilization); msgPayload["air_util_tx"] = new JSONValue(decoded->variant.device_metrics.air_util_tx); + msgPayload["uptime_seconds"] = new JSONValue((uint)decoded->variant.device_metrics.uptime_seconds); } else if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) { msgPayload["temperature"] = new JSONValue(decoded->variant.environment_metrics.temperature); msgPayload["relative_humidity"] = new JSONValue(decoded->variant.environment_metrics.relative_humidity); @@ -632,6 +731,9 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) if ((int)decoded->VDOP) { msgPayload["VDOP"] = new JSONValue((int)decoded->VDOP); } + if ((int)decoded->precision_bits) { + msgPayload["precision_bits"] = new JSONValue((int)decoded->precision_bits); + } jsonObj["payload"] = new JSONValue(msgPayload); } else { LOG_ERROR("Error decoding protobuf for position message!\n"); @@ -684,6 +786,88 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) } break; } + case meshtastic_PortNum_TRACEROUTE_APP: { + if (mp->decoded.request_id) { // Only report the traceroute response + msgType = "traceroute"; + meshtastic_RouteDiscovery scratch; + meshtastic_RouteDiscovery *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_RouteDiscovery_msg, + &scratch)) { + decoded = &scratch; + JSONArray route; // Route this message took + // Lambda function for adding a long name to the route + auto addToRoute = [](JSONArray *route, NodeNum num) { + char long_name[40] = "Unknown"; + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num); + bool name_known = node ? node->has_user : false; + if (name_known) + memcpy(long_name, node->user.long_name, sizeof(long_name)); + route->push_back(new JSONValue(long_name)); + }; + addToRoute(&route, mp->to); // Started at the original transmitter (destination of response) + for (uint8_t i = 0; i < decoded->route_count; i++) { + addToRoute(&route, decoded->route[i]); + } + addToRoute(&route, mp->from); // Ended at the original destination (source of response) + + msgPayload["route"] = new JSONValue(route); + jsonObj["payload"] = new JSONValue(msgPayload); + } else { + LOG_ERROR("Error decoding protobuf for traceroute message!\n"); + } + } + break; + } + case meshtastic_PortNum_DETECTION_SENSOR_APP: { + msgType = "detection"; + char payloadStr[(mp->decoded.payload.size) + 1]; + memcpy(payloadStr, mp->decoded.payload.bytes, mp->decoded.payload.size); + payloadStr[mp->decoded.payload.size] = 0; // null terminated string + msgPayload["text"] = new JSONValue(payloadStr); + jsonObj["payload"] = new JSONValue(msgPayload); + break; + } +#ifdef ARCH_ESP32 + case meshtastic_PortNum_PAXCOUNTER_APP: { + msgType = "paxcounter"; + meshtastic_Paxcount scratch; + meshtastic_Paxcount *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Paxcount_msg, &scratch)) { + decoded = &scratch; + msgPayload["wifi_count"] = new JSONValue((uint)decoded->wifi); + msgPayload["ble_count"] = new JSONValue((uint)decoded->ble); + msgPayload["uptime"] = new JSONValue((uint)decoded->uptime); + jsonObj["payload"] = new JSONValue(msgPayload); + } else { + LOG_ERROR("Error decoding protobuf for Paxcount message!\n"); + } + break; + } +#endif + case meshtastic_PortNum_REMOTE_HARDWARE_APP: { + meshtastic_HardwareMessage scratch; + meshtastic_HardwareMessage *decoded = NULL; + memset(&scratch, 0, sizeof(scratch)); + if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_HardwareMessage_msg, + &scratch)) { + decoded = &scratch; + if (decoded->type == meshtastic_HardwareMessage_Type_GPIOS_CHANGED) { + msgType = "gpios_changed"; + msgPayload["gpio_value"] = new JSONValue((uint)decoded->gpio_value); + jsonObj["payload"] = new JSONValue(msgPayload); + } else if (decoded->type == meshtastic_HardwareMessage_Type_READ_GPIOS_REPLY) { + msgType = "gpios_read_reply"; + msgPayload["gpio_value"] = new JSONValue((uint)decoded->gpio_value); + msgPayload["gpio_mask"] = new JSONValue((uint)decoded->gpio_mask); + jsonObj["payload"] = new JSONValue(msgPayload); + } + } else { + LOG_ERROR("Error decoding protobuf for RemoteHardware message!\n"); + } + break; + } // add more packet types here if needed default: break; @@ -703,6 +887,8 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) jsonObj["rssi"] = new JSONValue((int)mp->rx_rssi); if (mp->rx_snr != 0) jsonObj["snr"] = new JSONValue((float)mp->rx_snr); + if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) + jsonObj["hops_away"] = new JSONValue((uint)(mp->hop_start - mp->hop_limit)); // serialize and write it to the stream JSONValue *value = new JSONValue(jsonObj); @@ -712,4 +898,14 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) delete value; return jsonStr; +} + +bool MQTT::isValidJsonEnvelope(JSONObject &json) +{ + // if "sender" is provided, avoid processing packets we uplinked + return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) && + (json.find("from") != json.end()) && json["from"]->IsNumber() && + (json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us + (json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type + (json.find("payload") != json.end()); // should have a payload } \ No newline at end of file diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index 565f46ecf..f2eb6b120 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -5,6 +5,7 @@ #include "concurrency/OSThread.h" #include "mesh/Channels.h" #include "mesh/generated/meshtastic/mqtt.pb.h" +#include "mqtt/JSON.h" #if HAS_WIFI #include #define HAS_NETWORKING 1 @@ -48,14 +49,15 @@ class MQTT : private concurrency::OSThread MQTT(); /** - * Publish a packet on the glboal MQTT server. - * This hook must be called **after** the packet is encrypted (including the channel being changed to a hash). + * Publish a packet on the global MQTT server. + * @param mp the encrypted packet to publish + * @param mp_decoded the decrypted packet to publish * @param chIndex the index of the channel for this message * * Note: for messages we are forwarding on the mesh that we can't find the channel for (because we don't have the keys), we * can not forward those messages to the cloud - because no way to find a global channel ID. */ - void onSend(const meshtastic_MeshPacket &mp, ChannelIndex chIndex); + void onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket &mp_decoded, ChannelIndex chIndex); /** Attempt to connect to server if necessary */ @@ -69,6 +71,10 @@ class MQTT : private concurrency::OSThread void onClientProxyReceive(meshtastic_MqttClientProxyMessage msg); + bool isEnabled() { return this->enabled; }; + + void start() { setIntervalFromNow(0); }; + protected: PointerQueue mqttQueue; @@ -77,10 +83,19 @@ class MQTT : private concurrency::OSThread virtual int32_t runOnce() override; private: - std::string statusTopic = "/2/stat/"; - std::string cryptTopic = "/2/c/"; // msh/2/c/CHANNELID/NODEID - std::string jsonTopic = "/2/json/"; // msh/2/json/CHANNELID/NODEID - /** return true if we have a channel that wants uplink/downlink + std::string statusTopic = "/2/stat/"; // For "online"/"offline" message + std::string cryptTopic = "/2/e/"; // msh/2/e/CHANNELID/NODEID + std::string jsonTopic = "/2/json/"; // msh/2/json/CHANNELID/NODEID + std::string mapTopic = "/2/map/"; // For protobuf-encoded MapReport messages + + // For map reporting (only applies when enabled) + const uint32_t default_map_position_precision = 14; // defaults to max. offset of ~1459m + const uint32_t default_map_publish_interval_secs = 60 * 15; // defaults to 15 minutes + uint32_t last_report_to_map = 0; + uint32_t map_position_precision = default_map_position_precision; + uint32_t map_publish_interval_msecs = default_map_publish_interval_secs * 1000; + + /** return true if we have a channel that wants uplink/downlink or map reporting is enabled */ bool wantsLink() const; @@ -100,6 +115,12 @@ class MQTT : private concurrency::OSThread void publishStatus(); void publishQueuedMessages(); + // Check if we should report unencrypted information about our node for consumption by a map + void perhapsReportToMap(); + + // returns true if this is a valid JSON envelope which we accept on downlink + bool isValidJsonEnvelope(JSONObject &json); + /// Return 0 if sleep is okay, veto sleep if we are connected to pubsub server // int preflightSleepCb(void *unused = NULL) { return pubSub.connected() ? 1 : 0; } }; diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 3175e0f09..8f7e00461 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -1,7 +1,9 @@ -#include "NimbleBluetooth.h" -#include "BluetoothCommon.h" -#include "PowerFSM.h" #include "configuration.h" +#if !MESHTASTIC_EXCLUDE_BLUETOOTH +#include "BluetoothCommon.h" +#include "NimbleBluetooth.h" +#include "PowerFSM.h" + #include "main.h" #include "mesh/PhoneAPI.h" #include "mesh/mesh-pb-constants.h" @@ -104,14 +106,26 @@ static NimbleBluetoothFromRadioCallback *fromRadioCallbacks; void NimbleBluetooth::shutdown() { + // No measurable power saving for ESP32 during light-sleep(?) +#ifndef ARCH_ESP32 // Shutdown bluetooth for minimum power draw LOG_INFO("Disable bluetooth\n"); - // Bluefruit.Advertising.stop(); NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); pAdvertising->reset(); pAdvertising->stop(); +#endif } +// Proper shutdown for ESP32. Needs reboot to reverse. +void NimbleBluetooth::deinit() +{ +#ifdef ARCH_ESP32 + LOG_INFO("Disable bluetooth until reboot\n"); + NimBLEDevice::deinit(); +#endif +} + +// Has initial setup been completed bool NimbleBluetooth::isActive() { return bleServer; @@ -186,7 +200,7 @@ void NimbleBluetooth::setupService() // Setup the battery service NimBLEService *batteryService = bleServer->createService(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service BatteryCharacteristic = batteryService->createCharacteristic( // 0x2A19 is the Battery Level characteristic) - (uint16_t)0x2a19, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY); + (uint16_t)0x2a19, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY, 1); NimBLE2904 *batteryLevelDescriptor = (NimBLE2904 *)BatteryCharacteristic->createDescriptor((uint16_t)0x2904); batteryLevelDescriptor->setFormat(NimBLE2904::FORMAT_UINT8); @@ -208,8 +222,10 @@ void NimbleBluetooth::startAdvertising() /// Given a level between 0-100, update the BLE attribute void updateBatteryLevel(uint8_t level) { - BatteryCharacteristic->setValue(&level, 1); - BatteryCharacteristic->notify(); + if ((config.bluetooth.enabled == true) && bleServer && nimbleBluetooth->isConnected()) { + BatteryCharacteristic->setValue(&level, 1); + BatteryCharacteristic->notify(); + } } void NimbleBluetooth::clearBonds() @@ -225,3 +241,4 @@ void clearNVS() ESP.restart(); #endif } +#endif \ No newline at end of file diff --git a/src/nimble/NimbleBluetooth.h b/src/nimble/NimbleBluetooth.h index 4080a7cbc..d1e347830 100644 --- a/src/nimble/NimbleBluetooth.h +++ b/src/nimble/NimbleBluetooth.h @@ -6,6 +6,7 @@ class NimbleBluetooth : BluetoothApi public: void setup(); void shutdown(); + void deinit(); void clearBonds(); bool isActive(); bool isConnected(); @@ -16,5 +17,5 @@ class NimbleBluetooth : BluetoothApi void startAdvertising(); }; -void setBluetoothEnable(bool on); -void clearNVS(); +void setBluetoothEnable(bool enable); +void clearNVS(); \ No newline at end of file diff --git a/src/platform/esp32/SimpleAllocator.cpp b/src/platform/esp32/SimpleAllocator.cpp index ed44722c5..63f3b02de 100644 --- a/src/platform/esp32/SimpleAllocator.cpp +++ b/src/platform/esp32/SimpleAllocator.cpp @@ -58,7 +58,7 @@ void *operator new(size_t sz) throw(std::bad_alloc) void operator delete(void *ptr) throw() { if (activeAllocator) - LOG_DEBUG("Warning: leaking an active allocator object\n"); // We don't properly handle this yet + LOG_WARN("Leaking an active allocator object\n"); // We don't properly handle this yet else free(ptr); } diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 163cc8b84..15e437bb5 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -106,11 +106,19 @@ #elif defined(HELTEC_WSL_V3) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_WSL_V3 #elif defined(HELTEC_WIRELESS_TRACKER) +#ifdef HELTEC_TRACKER_V1_0 +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V1_0 +#else #define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER +#endif +#elif defined(HELTEC_WIRELESS_PAPER_V1_0) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_PAPER_V1_0 #elif defined(HELTEC_WIRELESS_PAPER) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_PAPER #elif defined(TLORA_T3S3_V1) #define HW_VENDOR meshtastic_HardwareModel_TLORA_T3_S3 +#elif defined(CDEBYTE_ELORA_S3) +#define HW_VENDOR meshtastic_HardwareModel_CDEBYTE_ELORA_S3 #elif defined(BETAFPV_2400_TX) #define HW_VENDOR meshtastic_HardwareModel_BETAFPV_2400_TX #elif defined(NANO_G1_EXPLORER) @@ -119,20 +127,38 @@ #define HW_VENDOR meshtastic_HardwareModel_BETAFPV_900_NANO_TX #elif defined(PICOMPUTER_S3) #define HW_VENDOR meshtastic_HardwareModel_PICOMPUTER_S3 +#elif defined(ESP32_S3_PICO) +#define HW_VENDOR meshtastic_HardwareModel_ESP32_S3_PICO +#elif defined(SENSELORA_S3) +#define HW_VENDOR meshtastic_HardwareModel_SENSELORA_S3 #elif defined(HELTEC_HT62) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_HT62 +#elif defined(CHATTER_2) +#define HW_VENDOR meshtastic_HardwareModel_CHATTER_2 +#elif defined(STATION_G2) +#define HW_VENDOR meshtastic_HardwareModel_STATION_G2 +#elif defined(UNPHONE) +#define HW_VENDOR meshtastic_HardwareModel_UNPHONE #endif // ----------------------------------------------------------------------------- // LoRa SPI // ----------------------------------------------------------------------------- -// NRF52 boards will define this in variant.h -#ifndef RF95_SCK -#define RF95_SCK 5 -#define RF95_MISO 19 -#define RF95_MOSI 27 -#define RF95_NSS 18 +// If an SPI-related pin used by the LoRa module isn't defined, use the conventional pin number for it. +// FIXME: these pins should really be defined in each variant.h file to prevent breakages if the defaults change, currently many +// ESP32 variants don't define these pins in their variant.h file. +#ifndef LORA_SCK +#define LORA_SCK 5 +#endif +#ifndef LORA_MISO +#define LORA_MISO 19 +#endif +#ifndef LORA_MOSI +#define LORA_MOSI 27 +#endif +#ifndef LORA_CS +#define LORA_CS 18 #endif -#define SERIAL0_RX_GPIO 3 // Always GPIO3 on ESP32 \ No newline at end of file +#define SERIAL0_RX_GPIO 3 // Always GPIO3 on ESP32 // FIXME: may be different on ESP32-S3, etc. diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 833e058d8..2894a49fc 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -3,11 +3,14 @@ #include "esp_task_wdt.h" #include "main.h" -#if !defined(CONFIG_IDF_TARGET_ESP32S2) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH +#include "BleOta.h" #include "nimble/NimbleBluetooth.h" #endif -#include "BleOta.h" -#include "mesh/http/WiFiAPClient.h" + +#if !MESHTASTIC_EXCLUDE_WIFI +#include "mesh/wifi/WiFiAPClient.h" +#endif #include "meshUtils.h" #include "sleep.h" @@ -18,23 +21,23 @@ #include #include -#if !defined(CONFIG_IDF_TARGET_ESP32S2) - -void setBluetoothEnable(bool on) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH +void setBluetoothEnable(bool enable) { if (!isWifiAvailable() && config.bluetooth.enabled == true) { if (!nimbleBluetooth) { nimbleBluetooth = new NimbleBluetooth(); } - if (on && !nimbleBluetooth->isActive()) { + if (enable && !nimbleBluetooth->isActive()) { nimbleBluetooth->setup(); - } else if (!on) { - nimbleBluetooth->shutdown(); } + // For ESP32, no way to recover from bluetooth shutdown without reboot + // BLE advertising automatically stops when MCU enters light-sleep(?) + // For deep-sleep, shutdown hardware with nimbleBluetooth->deinit(). Requires reboot to reverse } } #else -void setBluetoothEnable(bool on) {} +void setBluetoothEnable(bool enable) {} void updateBatteryLevel(uint8_t level) {} #endif @@ -108,12 +111,16 @@ void esp32Setup() preferences.putUInt("rebootCounter", rebootCounter); preferences.end(); LOG_DEBUG("Number of Device Reboots: %d\n", rebootCounter); +#if !MESHTASTIC_EXCLUDE_BLUETOOTH String BLEOTA = BleOta::getOtaAppVersion(); if (BLEOTA.isEmpty()) { LOG_DEBUG("No OTA firmware available\n"); } else { LOG_DEBUG("OTA firmware version %s\n", BLEOTA.c_str()); } +#else + LOG_DEBUG("No OTA firmware available\n"); +#endif // enableModemSleep(); @@ -175,7 +182,8 @@ void cpuDeepSleep(uint32_t msecToWake) some current will flow through these external and internal resistors, increasing deep sleep current above the minimal possible value. - Note: we don't isolate pins that are used for the LORA, LED, i2c, spi or the wake button + Note: we don't isolate pins that are used for the LORA, LED, i2c, or ST7735 Display for the Chatter2, spi or the wake + button(s), maybe we should not include any other GPIOs... */ #if SOC_RTCIO_HOLD_SUPPORTED static const uint8_t rtcGpios[] = {/* 0, */ 2, @@ -184,9 +192,9 @@ void cpuDeepSleep(uint32_t msecToWake) 13, /* 14, */ /* 15, */ #endif - /* 25, */ 26, /* 27, */ - 32, 33, 34, 35, - 36, 37 + /* 25, */ /* 26, */ /* 27, */ + /* 32, */ /* 33, */ 34, 35, + /* 36, */ 37 /* 38, 39 */}; for (int i = 0; i < sizeof(rtcGpios); i++) diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index c29739542..759cbb404 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -1,5 +1,6 @@ #include "NRF52Bluetooth.h" #include "BluetoothCommon.h" +#include "PowerFSM.h" #include "configuration.h" #include "main.h" #include "mesh/PhoneAPI.h" @@ -202,14 +203,15 @@ void setupMeshService(void) toRadio.begin(); } -// FIXME, turn off soft device access for debugging -static bool isSoftDeviceAllowed = true; static uint32_t configuredPasskey; void NRF52Bluetooth::shutdown() { // Shutdown bluetooth for minimum power draw LOG_INFO("Disable NRF52 bluetooth\n"); + if (connectionHandle != 0) { + Bluefruit.disconnect(connectionHandle); + } Bluefruit.Advertising.stop(); } @@ -278,14 +280,19 @@ void NRF52Bluetooth::setup() LOG_INFO("Configuring the Mesh bluetooth service\n"); setupMeshService(); - // Supposedly debugging works with soft device if you disable advertising - if (isSoftDeviceAllowed) { - // Setup the advertising packet(s) - LOG_INFO("Setting up the advertising payload(s)\n"); - startAdv(); + // Setup the advertising packet(s) + LOG_INFO("Setting up the advertising payload(s)\n"); + startAdv(); - LOG_INFO("Advertising\n"); - } + LOG_INFO("Advertising\n"); +} + +void NRF52Bluetooth::resumeAdverising() +{ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); } /// Given a level between 0-100, update the BLE attribute @@ -312,6 +319,7 @@ void NRF52Bluetooth::onConnectionSecured(uint16_t conn_handle) bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request) { LOG_INFO("BLE pairing process started with passkey %.3s %.3s\n", passkey, passkey + 3); + powerFSM.trigger(EVENT_BLUETOOTH_PAIR); screen->startBluetoothPinScreen(configuredPasskey); if (match_request) { @@ -333,4 +341,4 @@ void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_statu LOG_INFO("BLE pairing failed\n"); screen->stopBluetoothPinScreen(); -} +} \ No newline at end of file diff --git a/src/platform/nrf52/NRF52Bluetooth.h b/src/platform/nrf52/NRF52Bluetooth.h index 193e86cf8..fd27bbf04 100644 --- a/src/platform/nrf52/NRF52Bluetooth.h +++ b/src/platform/nrf52/NRF52Bluetooth.h @@ -8,6 +8,7 @@ class NRF52Bluetooth : BluetoothApi public: void setup(); void shutdown(); + void resumeAdverising(); void clearBonds(); bool isConnected(); int getRssi(); @@ -17,4 +18,4 @@ class NRF52Bluetooth : BluetoothApi void convertToUint8(uint8_t target[4], uint32_t source); static bool onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request); static void onPairingCompleted(uint16_t conn_handle, uint8_t auth_status); -}; +}; \ No newline at end of file diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index e6eebc45b..35cd4fd84 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -48,6 +48,8 @@ #define HW_VENDOR meshtastic_HardwareModel_T_ECHO #elif defined(NANO_G2_ULTRA) #define HW_VENDOR meshtastic_HardwareModel_NANO_G2_ULTRA +#elif defined(CANARYONE) +#define HW_VENDOR meshtastic_HardwareModel_CANARYONE #elif defined(NORDIC_PCA10059) #define HW_VENDOR meshtastic_HardwareModel_NRF52840_PCA10059 #elif defined(PRIVATE_HW) || defined(FEATHER_DIY) diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 65b45f1e9..ecffb745d 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -1,4 +1,5 @@ #include "configuration.h" +#include #include #include #include @@ -51,7 +52,7 @@ void getMacAddr(uint8_t *dmac) static void initBrownout() { - auto vccthresh = POWER_POFCON_THRESHOLD_V17; + auto vccthresh = POWER_POFCON_THRESHOLD_V24; auto err_code = sd_power_pof_enable(POWER_POFCON_POF_Enabled); assert(err_code == NRF_SUCCESS); @@ -62,31 +63,35 @@ static void initBrownout() // We don't bother with setting up brownout if soft device is disabled - because during production we always use softdevice } -static bool bleOn = false; static const bool useSoftDevice = true; // Set to false for easier debugging -void setBluetoothEnable(bool on) +#if !MESHTASTIC_EXCLUDE_BLUETOOTH +void setBluetoothEnable(bool enable) { - if (on != bleOn && config.bluetooth.enabled == true) { - if (on) { + if (enable && config.bluetooth.enabled) { + if (!useSoftDevice) { + LOG_INFO("DISABLING NRF52 BLUETOOTH WHILE DEBUGGING\n"); + } else { if (!nrf52Bluetooth) { - if (!useSoftDevice) - LOG_INFO("DISABLING NRF52 BLUETOOTH WHILE DEBUGGING\n"); - else { - nrf52Bluetooth = new NRF52Bluetooth(); - nrf52Bluetooth->setup(); + LOG_DEBUG("Initializing NRF52 Bluetooth\n"); + nrf52Bluetooth = new NRF52Bluetooth(); + nrf52Bluetooth->setup(); - // We delay brownout init until after BLE because BLE starts soft device - initBrownout(); - } + // We delay brownout init until after BLE because BLE starts soft device + initBrownout(); + } else { + nrf52Bluetooth->resumeAdverising(); } - } else if (nrf52Bluetooth) { + } + } else { + if (nrf52Bluetooth) { nrf52Bluetooth->shutdown(); } - bleOn = on; } } - +#else +void setBluetoothEnable(bool enable) {} +#endif /** * Override printf to use the SEGGER output library (note - this does not effect the printf method on the debug console) */ @@ -175,7 +180,7 @@ void cpuDeepSleep(uint32_t msecToWake) #ifdef PIN_3V3_EN digitalWrite(PIN_3V3_EN, LOW); #endif -#ifndef USE_EINK +#ifdef AQ_SET_PIN // RAK-12039 set pin for Air quality sensor digitalWrite(AQ_SET_PIN, LOW); #endif @@ -184,6 +189,7 @@ void cpuDeepSleep(uint32_t msecToWake) // Don't enter this if we're sleeping portMAX_DELAY, since that's a shutdown event if (msecToWake != portMAX_DELAY && (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) && config.power.is_power_saving == true) { sd_power_mode_set(NRF_POWER_MODE_LOWPWR); @@ -214,4 +220,9 @@ void clearBonds() nrf52Bluetooth->setup(); } nrf52Bluetooth->clearBonds(); +} + +void enterDfuMode() +{ + enterUf2Dfu(); } \ No newline at end of file diff --git a/src/platform/portduino/PiHal.h b/src/platform/portduino/PiHal.h deleted file mode 100644 index f10040583..000000000 --- a/src/platform/portduino/PiHal.h +++ /dev/null @@ -1,155 +0,0 @@ -#ifndef PI_HAL_H -#define PI_HAL_H - -// include RadioLib -#include - -// include the library for Raspberry GPIO pins -#include "pigpio.h" - -// create a new Raspberry Pi hardware abstraction layer -// using the pigpio library -// the HAL must inherit from the base RadioLibHal class -// and implement all of its virtual methods -class PiHal : public RadioLibHal -{ - public: - // default constructor - initializes the base HAL and any needed private members - PiHal(uint8_t spiChannel, uint32_t spiSpeed = 2000000) - : RadioLibHal(PI_INPUT, PI_OUTPUT, PI_LOW, PI_HIGH, RISING_EDGE, FALLING_EDGE), _spiChannel(spiChannel), - _spiSpeed(spiSpeed) - { - } - - void init() override - { - // first initialise pigpio library - gpioInitialise(); - - // now the SPI - spiBegin(); - - // Waveshare LoRaWAN Hat also needs pin 18 to be pulled high to enable the radio - // gpioSetMode(18, PI_OUTPUT); - // gpioWrite(18, PI_HIGH); - } - - void term() override - { - // stop the SPI - spiEnd(); - - // pull the enable pin low - // gpioSetMode(18, PI_OUTPUT); - // gpioWrite(18, PI_LOW); - - // finally, stop the pigpio library - gpioTerminate(); - } - - // GPIO-related methods (pinMode, digitalWrite etc.) should check - // RADIOLIB_NC as an alias for non-connected pins - void pinMode(uint32_t pin, uint32_t mode) override - { - if (pin == RADIOLIB_NC) { - return; - } - - gpioSetMode(pin, mode); - } - - void digitalWrite(uint32_t pin, uint32_t value) override - { - if (pin == RADIOLIB_NC) { - return; - } - - gpioWrite(pin, value); - } - - uint32_t digitalRead(uint32_t pin) override - { - if (pin == RADIOLIB_NC) { - return (0); - } - - return (gpioRead(pin)); - } - - void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override - { - if (interruptNum == RADIOLIB_NC) { - return; - } - if (gpioRead(interruptNum) == 1) { - interruptCb(); - } else { - gpioSetAlertFunc(interruptNum, (gpioISRFunc_t)interruptCb); - } - } - - void detachInterrupt(uint32_t interruptNum) override - { - if (interruptNum == RADIOLIB_NC) { - return; - } - - gpioSetAlertFunc(interruptNum, NULL); - } - - void delay(unsigned long ms) override { gpioDelay(ms * 1000); } - - void delayMicroseconds(unsigned long us) override { gpioDelay(us); } - - unsigned long millis() override { return (gpioTick() / 1000); } - - unsigned long micros() override { return (gpioTick()); } - - long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override - { - if (pin == RADIOLIB_NC) { - return (0); - } - - this->pinMode(pin, PI_INPUT); - uint32_t start = this->micros(); - uint32_t curtick = this->micros(); - - while (this->digitalRead(pin) == state) { - if ((this->micros() - curtick) > timeout) { - return (0); - } - } - - return (this->micros() - start); - } - - void spiBegin() - { - if (_spiHandle < 0) { - _spiHandle = spiOpen(_spiChannel, _spiSpeed, 0); - } - } - - void spiBeginTransaction() {} - - void spiTransfer(uint8_t *out, size_t len, uint8_t *in) { spiXfer(_spiHandle, (char *)out, (char *)in, len); } - - void spiEndTransaction() {} - - void spiEnd() - { - if (_spiHandle >= 0) { - spiClose(_spiHandle); - _spiHandle = -1; - } - } - - private: - // the HAL can contain any additional private members - const unsigned int _spiSpeed; - const uint8_t _spiChannel; - int _spiHandle = -1; -}; - -#endif \ No newline at end of file diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 2e402c0a0..a04c9c12c 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -8,22 +8,21 @@ #include #include -#ifdef ARCH_RASPBERRY_PI #include "PortduinoGlue.h" -#include "pigpio.h" +#include "linux/gpio/LinuxGPIOPin.h" #include "yaml-cpp/yaml.h" #include #include #include -std::map settingsMap; - -#else -#include -#endif +HardwareSPI *DisplaySPI; +HardwareSPI *LoraSPI; +std::map settingsMap; +std::map settingsStrings; +char *configPath = nullptr; // FIXME - move setBluetoothEnable into a HALPlatform class -void setBluetoothEnable(bool on) +void setBluetoothEnable(bool enable) { // not needed } @@ -34,34 +33,7 @@ void cpuDeepSleep(uint32_t msecs) } void updateBatteryLevel(uint8_t level) NOT_IMPLEMENTED("updateBatteryLevel"); -#ifndef ARCH_RASPBERRY_PI -/** a simulated pin for busted IRQ hardware - * Porduino helper class to do this i2c based polling: - */ -class PolledIrqPin : public GPIOPin -{ - public: - PolledIrqPin() : GPIOPin(LORA_DIO1, "loraIRQ") {} - /// Read the low level hardware for this pin - virtual PinStatus readPinHardware() - { - if (isrPinStatus < 0) - return LOW; // No interrupt handler attached, don't bother polling i2c right now - else { - extern RadioInterface *rIf; // FIXME, temporary hack until we know if we need to keep this - - assert(rIf); - RadioLibInterface *rIf95 = static_cast(rIf); - bool p = rIf95->isIRQPending(); - log(SysGPIO, LogDebug, "PolledIrqPin::readPinHardware(%s, %d, %d)", getName(), getPinNum(), p); - return p ? HIGH : LOW; - } - } -}; - -static GPIOPin *loraIrq; -#endif int TCPPort = 4403; static error_t parse_opt(int key, char *arg, struct argp_state *state) @@ -71,7 +43,10 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) if (sscanf(arg, "%d", &TCPPort) < 1) return ARGP_ERR_UNKNOWN; else - printf("Using TCP port %d\n", TCPPort); + printf("Using config file %d\n", TCPPort); + break; + case 'c': + configPath = arg; break; case ARGP_KEY_ARG: return 0; @@ -83,7 +58,9 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) void portduinoCustomInit() { - static struct argp_option options[] = {{"port", 'p', "PORT", 0, "The TCP port to use."}, {0}}; + static struct argp_option options[] = {{"port", 'p', "PORT", 0, "The TCP port to use."}, + {"config", 'c', "CONFIG_PATH", 0, "Full path of the .yaml config file to use."}, + {0}}; static void *childArguments; static char doc[] = "Meshtastic native build."; static char args_doc[] = "..."; @@ -99,11 +76,27 @@ void portduinoCustomInit() void portduinoSetup() { printf("Setting up Meshtastic on Portduino...\n"); + gpioInit(); + + std::string gpioChipName = "gpiochip"; + settingsStrings[i2cdev] = ""; + settingsStrings[keyboardDevice] = ""; + settingsStrings[webserverrootpath] = ""; + settingsStrings[spidev] = ""; + settingsStrings[displayspidev] = ""; -#ifdef ARCH_RASPBERRY_PI YAML::Node yamlConfig; - if (access("config.yaml", R_OK) == 0) { + if (configPath != nullptr) { + std::cout << "Using " << configPath << " as config file" << std::endl; + try { + yamlConfig = YAML::LoadFile(configPath); + } catch (YAML::Exception e) { + std::cout << "Could not open " << configPath << " because of error: " << e.what() << std::endl; + exit(EXIT_FAILURE); + } + } else if (access("config.yaml", R_OK) == 0) { + std::cout << "Using local config.yaml as config file" << std::endl; try { yamlConfig = YAML::LoadFile("config.yaml"); } catch (YAML::Exception e) { @@ -111,6 +104,7 @@ void portduinoSetup() exit(EXIT_FAILURE); } } else if (access("/etc/meshtasticd/config.yaml", R_OK) == 0) { + std::cout << "Using /etc/meshtasticd/config.yaml as config file" << std::endl; try { yamlConfig = YAML::LoadFile("/etc/meshtasticd/config.yaml"); } catch (YAML::Exception e) { @@ -118,80 +112,224 @@ void portduinoSetup() exit(EXIT_FAILURE); } } else { - std::cout << "No 'config.yaml' found, exiting." << std::endl; - exit(EXIT_FAILURE); + std::cout << "No 'config.yaml' found, running simulated." << std::endl; + settingsMap[maxnodes] = 200; // Default to 200 nodes + settingsMap[logoutputlevel] = level_debug; // Default to debug + // Set the random seed equal to TCPPort to have a different seed per instance + randomSeed(TCPPort); + return; } try { + if (yamlConfig["Logging"]) { + if (yamlConfig["Logging"]["LogLevel"].as("info") == "debug") { + settingsMap[logoutputlevel] = level_debug; + } else if (yamlConfig["Logging"]["LogLevel"].as("info") == "info") { + settingsMap[logoutputlevel] = level_info; + } else if (yamlConfig["Logging"]["LogLevel"].as("info") == "warn") { + settingsMap[logoutputlevel] = level_warn; + } else if (yamlConfig["Logging"]["LogLevel"].as("info") == "error") { + settingsMap[logoutputlevel] = level_error; + } + } if (yamlConfig["Lora"]) { settingsMap[use_sx1262] = false; settingsMap[use_rf95] = false; + settingsMap[use_sx1280] = false; if (yamlConfig["Lora"]["Module"] && yamlConfig["Lora"]["Module"].as("") == "sx1262") { settingsMap[use_sx1262] = true; } else if (yamlConfig["Lora"]["Module"] && yamlConfig["Lora"]["Module"].as("") == "RF95") { settingsMap[use_rf95] = true; + } else if (yamlConfig["Lora"]["Module"] && yamlConfig["Lora"]["Module"].as("") == "sx1280") { + settingsMap[use_sx1280] = true; } settingsMap[dio2_as_rf_switch] = yamlConfig["Lora"]["DIO2_AS_RF_SWITCH"].as(false); + settingsMap[dio3_tcxo_voltage] = yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as(false); settingsMap[cs] = yamlConfig["Lora"]["CS"].as(RADIOLIB_NC); settingsMap[irq] = yamlConfig["Lora"]["IRQ"].as(RADIOLIB_NC); settingsMap[busy] = yamlConfig["Lora"]["Busy"].as(RADIOLIB_NC); settingsMap[reset] = yamlConfig["Lora"]["Reset"].as(RADIOLIB_NC); + settingsMap[txen] = yamlConfig["Lora"]["TXen"].as(RADIOLIB_NC); + settingsMap[rxen] = yamlConfig["Lora"]["RXen"].as(RADIOLIB_NC); + settingsMap[gpiochip] = yamlConfig["Lora"]["gpiochip"].as(0); + gpioChipName += std::to_string(settingsMap[gpiochip]); + + settingsStrings[spidev] = "/dev/" + yamlConfig["Lora"]["spidev"].as("spidev0.0"); } + if (yamlConfig["GPIO"]) { + settingsMap[user] = yamlConfig["GPIO"]["User"].as(RADIOLIB_NC); + } + if (yamlConfig["GPS"]) { + std::string serialPath = yamlConfig["GPS"]["SerialPath"].as(""); + if (serialPath != "") { + Serial1.setPath(serialPath); + settingsMap[has_gps] = 1; + } + } + if (yamlConfig["I2C"]) { + settingsStrings[i2cdev] = yamlConfig["I2C"]["I2CDevice"].as(""); + } + settingsMap[displayPanel] = no_screen; + if (yamlConfig["Display"]) { + if (yamlConfig["Display"]["Panel"].as("") == "ST7789") + settingsMap[displayPanel] = st7789; + else if (yamlConfig["Display"]["Panel"].as("") == "ST7735") + settingsMap[displayPanel] = st7735; + else if (yamlConfig["Display"]["Panel"].as("") == "ST7735S") + settingsMap[displayPanel] = st7735s; + else if (yamlConfig["Display"]["Panel"].as("") == "ST7796") + settingsMap[displayPanel] = st7796; + else if (yamlConfig["Display"]["Panel"].as("") == "ILI9341") + settingsMap[displayPanel] = ili9341; + else if (yamlConfig["Display"]["Panel"].as("") == "ILI9488") + settingsMap[displayPanel] = ili9488; + else if (yamlConfig["Display"]["Panel"].as("") == "HX8357D") + settingsMap[displayPanel] = hx8357d; + else if (yamlConfig["Display"]["Panel"].as("") == "X11") + settingsMap[displayPanel] = x11; + settingsMap[displayHeight] = yamlConfig["Display"]["Height"].as(0); + settingsMap[displayWidth] = yamlConfig["Display"]["Width"].as(0); + settingsMap[displayDC] = yamlConfig["Display"]["DC"].as(-1); + settingsMap[displayCS] = yamlConfig["Display"]["CS"].as(-1); + settingsMap[displayRGBOrder] = yamlConfig["Display"]["RGBOrder"].as(false); + settingsMap[displayBacklight] = yamlConfig["Display"]["Backlight"].as(-1); + settingsMap[displayBacklightInvert] = yamlConfig["Display"]["BacklightInvert"].as(false); + settingsMap[displayBacklightPWMChannel] = yamlConfig["Display"]["BacklightPWMChannel"].as(-1); + settingsMap[displayReset] = yamlConfig["Display"]["Reset"].as(-1); + settingsMap[displayOffsetX] = yamlConfig["Display"]["OffsetX"].as(0); + settingsMap[displayOffsetY] = yamlConfig["Display"]["OffsetY"].as(0); + settingsMap[displayRotate] = yamlConfig["Display"]["Rotate"].as(false); + settingsMap[displayOffsetRotate] = yamlConfig["Display"]["OffsetRotate"].as(1); + settingsMap[displayInvert] = yamlConfig["Display"]["Invert"].as(false); + settingsMap[displayBusFrequency] = yamlConfig["Display"]["BusFrequency"].as(40000000); + if (yamlConfig["Display"]["spidev"]) { + settingsStrings[displayspidev] = "/dev/" + yamlConfig["Display"]["spidev"].as("spidev0.1"); + } + } + settingsMap[touchscreenModule] = no_touchscreen; + if (yamlConfig["Touchscreen"]) { + if (yamlConfig["Touchscreen"]["Module"].as("") == "XPT2046") + settingsMap[touchscreenModule] = xpt2046; + else if (yamlConfig["Touchscreen"]["Module"].as("") == "STMPE610") + settingsMap[touchscreenModule] = stmpe610; + else if (yamlConfig["Touchscreen"]["Module"].as("") == "GT911") + settingsMap[touchscreenModule] = gt911; + else if (yamlConfig["Touchscreen"]["Module"].as("") == "FT5x06") + settingsMap[touchscreenModule] = ft5x06; + settingsMap[touchscreenCS] = yamlConfig["Touchscreen"]["CS"].as(-1); + settingsMap[touchscreenIRQ] = yamlConfig["Touchscreen"]["IRQ"].as(-1); + settingsMap[touchscreenBusFrequency] = yamlConfig["Touchscreen"]["BusFrequency"].as(1000000); + settingsMap[touchscreenRotate] = yamlConfig["Touchscreen"]["Rotate"].as(-1); + if (yamlConfig["Touchscreen"]["spidev"]) { + settingsStrings[touchscreenspidev] = "/dev/" + yamlConfig["Touchscreen"]["spidev"].as(""); + } + } + if (yamlConfig["Input"]) { + settingsStrings[keyboardDevice] = (yamlConfig["Input"]["KeyboardDevice"]).as(""); + } + + if (yamlConfig["Webserver"]) { + settingsMap[webserverport] = (yamlConfig["Webserver"]["Port"]).as(-1); + settingsStrings[webserverrootpath] = (yamlConfig["Webserver"]["RootPath"]).as(""); + } + + settingsMap[maxnodes] = (yamlConfig["General"]["MaxNodes"]).as(200); } catch (YAML::Exception e) { std::cout << "*** Exception " << e.what() << std::endl; exit(EXIT_FAILURE); } - if (access("/sys/kernel/debug/bluetooth/hci0/identity", R_OK) != 0) { - std::cout << "Cannot read Bluetooth MAC Address. Please run as root" << std::endl; - exit(EXIT_FAILURE); + + // Need to bind all the configured GPIO pins so they're not simulated + if (settingsMap.count(cs) > 0 && settingsMap[cs] != RADIOLIB_NC) { + if (initGPIOPin(settingsMap[cs], gpioChipName) != ERRNO_OK) { + settingsMap[cs] = RADIOLIB_NC; + } } + if (settingsMap.count(irq) > 0 && settingsMap[irq] != RADIOLIB_NC) { + if (initGPIOPin(settingsMap[irq], gpioChipName) != ERRNO_OK) { + settingsMap[irq] = RADIOLIB_NC; + } + } + if (settingsMap.count(busy) > 0 && settingsMap[busy] != RADIOLIB_NC) { + if (initGPIOPin(settingsMap[busy], gpioChipName) != ERRNO_OK) { + settingsMap[busy] = RADIOLIB_NC; + } + } + if (settingsMap.count(reset) > 0 && settingsMap[reset] != RADIOLIB_NC) { + if (initGPIOPin(settingsMap[reset], gpioChipName) != ERRNO_OK) { + settingsMap[reset] = RADIOLIB_NC; + } + } + if (settingsMap.count(user) > 0 && settingsMap[user] != RADIOLIB_NC) { + if (initGPIOPin(settingsMap[user], gpioChipName) != ERRNO_OK) { + settingsMap[user] = RADIOLIB_NC; + } + } + if (settingsMap.count(rxen) > 0 && settingsMap[rxen] != RADIOLIB_NC) { + if (initGPIOPin(settingsMap[rxen], gpioChipName) != ERRNO_OK) { + settingsMap[rxen] = RADIOLIB_NC; + } + } + if (settingsMap.count(txen) > 0 && settingsMap[txen] != RADIOLIB_NC) { + if (initGPIOPin(settingsMap[txen], gpioChipName) != ERRNO_OK) { + settingsMap[txen] = RADIOLIB_NC; + } + } + + if (settingsMap[displayPanel] != no_screen) { + if (settingsMap[displayCS] > 0) + initGPIOPin(settingsMap[displayCS], gpioChipName); + if (settingsMap[displayDC] > 0) + initGPIOPin(settingsMap[displayDC], gpioChipName); + if (settingsMap[displayBacklight] > 0) + initGPIOPin(settingsMap[displayBacklight], gpioChipName); + if (settingsMap[displayReset] > 0) + initGPIOPin(settingsMap[displayReset], gpioChipName); + } + if (settingsMap[touchscreenModule] != no_touchscreen) { + if (settingsMap[touchscreenCS] > 0) + initGPIOPin(settingsMap[touchscreenCS], gpioChipName); + if (settingsMap[touchscreenIRQ] > 0) + initGPIOPin(settingsMap[touchscreenIRQ], gpioChipName); + } + + // if we specify a touchscreen dev, that is SPI. + // else if we specify a screen dev, that is SPI + // else if we specify a LoRa dev, that is SPI. + if (settingsStrings[touchscreenspidev] != "") { + SPI.begin(settingsStrings[touchscreenspidev].c_str()); + DisplaySPI = new HardwareSPI; + DisplaySPI->begin(settingsStrings[displayspidev].c_str()); + LoraSPI = new HardwareSPI; + LoraSPI->begin(settingsStrings[spidev].c_str()); + } else if (settingsStrings[displayspidev] != "") { + SPI.begin(settingsStrings[displayspidev].c_str()); + DisplaySPI = &SPI; + LoraSPI = new HardwareSPI; + LoraSPI->begin(settingsStrings[spidev].c_str()); + } else { + SPI.begin(settingsStrings[spidev].c_str()); + LoraSPI = &SPI; + DisplaySPI = &SPI; + } + return; -#endif +} -#ifdef defined(PORTDUINO_LINUX_HARDWARE) - SPI.begin(); // We need to create SPI - bool usePineLora = !spiChip->isSimulated(); - if (usePineLora) { - printf("Connecting to PineLora board...\n"); - - // FIXME: remove this hack once interrupts are confirmed to work on new pine64 board - // loraIrq = new PolledIrqPin(); - loraIrq = new LinuxGPIOPin(LORA_DIO1, "ch341", "int", "loraIrq"); // or "err"? - loraIrq->setSilent(); - gpioBind(loraIrq); - - // BUSY hw was busted on current board - just use the simulated pin (which will read low) - auto busy = new LinuxGPIOPin(SX126X_BUSY, "ch341", "slct", "loraBusy"); - busy->setSilent(); - gpioBind(busy); - - gpioBind(new LinuxGPIOPin(SX126X_RESET, "ch341", "ini", "loraReset")); - - auto loraCs = new LinuxGPIOPin(SX126X_CS, "ch341", "cs0", "loraCs"); - loraCs->setSilent(); - gpioBind(loraCs); - } else -#endif -#ifndef ARCH_RASPBERRY_PI - { - // Set the random seed equal to TCPPort to have a different seed per instance - randomSeed(TCPPort); - - auto fakeBusy = new SimGPIOPin(SX126X_BUSY, "fakeBusy"); - fakeBusy->writePin(LOW); - fakeBusy->setSilent(true); - gpioBind(fakeBusy); - - auto cs = new SimGPIOPin(SX126X_CS, "fakeLoraCS"); - cs->setSilent(true); - gpioBind(cs); - - gpioBind(new SimGPIOPin(SX126X_RESET, "fakeLoraReset")); - gpioBind(new SimGPIOPin(LORA_DIO1, "fakeLoraIrq")); +int initGPIOPin(int pinNum, std::string gpioChipName) +{ + std::string gpio_name = "GPIO" + std::to_string(pinNum); + try { + GPIOPin *csPin; + csPin = new LinuxGPIOPin(pinNum, gpioChipName.c_str(), pinNum, gpio_name.c_str()); + csPin->setSilent(); + gpioBind(csPin); + return ERRNO_OK; + } catch (...) { + std::exception_ptr p = std::current_exception(); + std::cout << "Warning, cannot claim pin " << gpio_name << (p ? p.__cxa_exception_type()->name() : "null") << std::endl; + return ERRNO_DISABLED; } - // gpioBind((new SimGPIOPin(LORA_RESET, "LORA_RESET"))); - // gpioBind((new SimGPIOPin(RF95_NSS, "RF95_NSS"))->setSilent()); -#endif -} \ No newline at end of file +} diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 7dc563038..ed2954eef 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -1,9 +1,59 @@ #pragma once -#ifdef ARCH_RASPBERRY_PI #include -extern std::map settingsMap; +enum configNames { + use_sx1262, + cs, + irq, + busy, + reset, + txen, + rxen, + dio2_as_rf_switch, + dio3_tcxo_voltage, + use_rf95, + use_sx1280, + user, + gpiochip, + spidev, + i2cdev, + has_gps, + touchscreenModule, + touchscreenCS, + touchscreenIRQ, + touchscreenBusFrequency, + touchscreenRotate, + touchscreenspidev, + displayspidev, + displayBusFrequency, + displayPanel, + displayWidth, + displayHeight, + displayCS, + displayDC, + displayRGBOrder, + displayBacklight, + displayBacklightPWMChannel, + displayBacklightInvert, + displayReset, + displayRotate, + displayOffsetRotate, + displayOffsetX, + displayOffsetY, + displayInvert, + keyboardDevice, + logoutputlevel, + webserver, + webserverport, + webserverrootpath, + maxnodes +}; +enum { no_screen, x11, st7789, st7735, st7735s, st7796, ili9341, ili9488, hx8357d }; +enum { no_touchscreen, xpt2046, stmpe610, gt911, ft5x06 }; +enum { level_error, level_warn, level_info, level_debug }; -enum { use_sx1262, cs, irq, busy, reset, dio2_as_rf_switch, use_rf95 }; - -#endif \ No newline at end of file +extern std::map settingsMap; +extern std::map settingsStrings; +int initGPIOPin(int pinNum, std::string gpioChipname); +extern HardwareSPI *DisplaySPI; +extern HardwareSPI *LoraSPI; \ No newline at end of file diff --git a/src/platform/portduino/SimRadio.cpp b/src/platform/portduino/SimRadio.cpp index e3d56554a..d0072cf35 100644 --- a/src/platform/portduino/SimRadio.cpp +++ b/src/platform/portduino/SimRadio.cpp @@ -198,6 +198,8 @@ void SimRadio::startSend(meshtastic_MeshPacket *txp) p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_Compressed_msg, &c); p->decoded.portnum = meshtastic_PortNum_SIMULATOR_APP; + + service.sendQueueStatusToPhone(router->getQueueStatus(), 0, p->id); service.sendToPhone(p); // Sending back to simulator } @@ -263,4 +265,4 @@ int16_t SimRadio::readData(uint8_t *data, size_t len) } return state; -} +} \ No newline at end of file diff --git a/src/platform/portduino/architecture.h b/src/platform/portduino/architecture.h index 9408ad19c..321949226 100644 --- a/src/platform/portduino/architecture.h +++ b/src/platform/portduino/architecture.h @@ -1,6 +1,6 @@ #pragma once -#define ARCH_PORTDUINO +#define ARCH_PORTDUINO 1 // // set HW_VENDOR @@ -16,4 +16,4 @@ #endif #ifndef HAS_TELEMETRY #define HAS_TELEMETRY 1 -#endif +#endif \ No newline at end of file diff --git a/src/platform/rp2040/architecture.h b/src/platform/rp2040/architecture.h index 762a2dc83..d7d7214c0 100644 --- a/src/platform/rp2040/architecture.h +++ b/src/platform/rp2040/architecture.h @@ -25,4 +25,8 @@ #define HW_VENDOR meshtastic_HardwareModel_RPI_PICO #elif defined(RAK11310) #define HW_VENDOR meshtastic_HardwareModel_RAK11310 +#elif defined(SENSELORA_RP2040) +#define HW_VENDOR meshtastic_HardwareModel_SENSELORA_RP2040 +#elif defined(RP2040_LORA) +#define HW_VENDOR meshtastic_HardwareModel_RP2040_LORA #endif \ No newline at end of file diff --git a/src/platform/rp2040/main-rp2040.cpp b/src/platform/rp2040/main-rp2040.cpp index 1d7f8fe70..af3aeadc3 100644 --- a/src/platform/rp2040/main-rp2040.cpp +++ b/src/platform/rp2040/main-rp2040.cpp @@ -1,8 +1,11 @@ #include "configuration.h" +#include +#include +#include #include #include -void setBluetoothEnable(bool on) +void setBluetoothEnable(bool enable) { // not needed } @@ -35,4 +38,56 @@ void rp2040Setup() Taken from CPU cycle counter and ROSC oscillator, so should be pretty random. */ randomSeed(rp2040.hwrand32()); -} \ No newline at end of file + +#ifdef RP2040_SLOW_CLOCK + uint f_pll_sys = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_PLL_SYS_CLKSRC_PRIMARY); + uint f_pll_usb = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_PLL_USB_CLKSRC_PRIMARY); + uint f_rosc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC); + uint f_clk_sys = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_SYS); + uint f_clk_peri = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_PERI); + uint f_clk_usb = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_USB); + uint f_clk_adc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_ADC); + uint f_clk_rtc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_RTC); + + LOG_INFO("Clock speed:\n"); + LOG_INFO("pll_sys = %dkHz\n", f_pll_sys); + LOG_INFO("pll_usb = %dkHz\n", f_pll_usb); + LOG_INFO("rosc = %dkHz\n", f_rosc); + LOG_INFO("clk_sys = %dkHz\n", f_clk_sys); + LOG_INFO("clk_peri = %dkHz\n", f_clk_peri); + LOG_INFO("clk_usb = %dkHz\n", f_clk_usb); + LOG_INFO("clk_adc = %dkHz\n", f_clk_adc); + LOG_INFO("clk_rtc = %dkHz\n", f_clk_rtc); +#endif +} + +void enterDfuMode() +{ + reset_usb_boot(0, 0); +} + +/* Init in early boot state. */ +#ifdef RP2040_SLOW_CLOCK +void initVariant() +{ + /* Set the system frequency to 18 MHz. */ + set_sys_clock_khz(18 * KHZ, false); + /* The previous line automatically detached clk_peri from clk_sys, and + attached it to pll_usb. We need to attach clk_peri back to system PLL to keep SPI + working at this low speed. + For details see https://github.com/jgromes/RadioLib/discussions/938 + */ + clock_configure(clk_peri, + 0, // No glitchless mux + CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS, // System PLL on AUX mux + 18 * MHZ, // Input frequency + 18 * MHZ // Output (must be same as no divider) + ); + /* Run also ADC on lower clk_sys. */ + clock_configure(clk_adc, 0, CLOCKS_CLK_ADC_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS, 18 * MHZ, 18 * MHZ); + /* Run RTC from XOSC since USB clock is off */ + clock_configure(clk_rtc, 0, CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC, 12 * MHZ, 47 * KHZ); + /* Turn off USB PLL */ + pll_deinit(pll_usb); +} +#endif \ No newline at end of file diff --git a/src/platform/stm32wl/main-stm32wl.cpp b/src/platform/stm32wl/main-stm32wl.cpp index f57928c60..60c3cce10 100644 --- a/src/platform/stm32wl/main-stm32wl.cpp +++ b/src/platform/stm32wl/main-stm32wl.cpp @@ -3,7 +3,7 @@ #include #include -void setBluetoothEnable(bool on) {} +void setBluetoothEnable(bool enable) {} void playStartMelody() {} @@ -33,4 +33,4 @@ int _gettimeofday(struct timeval *tv, void *tzvp) { return -1; } -} +} \ No newline at end of file diff --git a/src/power.h b/src/power.h index 54d98e715..b94ce8f98 100644 --- a/src/power.h +++ b/src/power.h @@ -5,18 +5,32 @@ #include #include #endif -/** - * Per @spattinson - * MIN_BAT_MILLIVOLTS seems high. Typical 18650 are different chemistry to LiPo, even for LiPos that chart seems a bit off, other - * charts put 3690mV at about 30% for a lipo, for 18650 i think 10% remaining iis in the region of 3.2-3.3V. Reference 1st graph - * in [this test report](https://lygte-info.dk/review/batteries2012/Samsung%20INR18650-30Q%203000mAh%20%28Pink%29%20UK.html) - * looking at the red line - discharge at 0.2A - he gets a capacity of 2900mah, 90% of 2900 = 2610, that point in the graph looks - * to be a shade above 3.2V - */ -#define MIN_BAT_MILLIVOLTS 3250 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/ -#define BAT_MILLIVOLTS_FULL 4100 -#define BAT_MILLIVOLTS_EMPTY 3500 +#ifndef NUM_OCV_POINTS +#define NUM_OCV_POINTS 11 +#endif + +#ifndef OCV_ARRAY +#ifdef CELL_TYPE_LIFEPO4 +#define OCV_ARRAY 3400, 3350, 3320, 3290, 3270, 3260, 3250, 3230, 3200, 3120, 3000 +#elif defined(CELL_TYPE_LEADACID) +#define OCV_ARRAY 2120, 2090, 2070, 2050, 2030, 2010, 1990, 1980, 1970, 1960, 1950 +#elif defined(CELL_TYPE_ALKALINE) +#define OCV_ARRAY 1580, 1400, 1350, 1300, 1280, 1250, 1230, 1190, 1150, 1100, 1000 +#elif defined(CELL_TYPE_NIMH) +#define OCV_ARRAY 1400, 1300, 1280, 1270, 1260, 1250, 1240, 1230, 1210, 1150, 1000 +#elif defined(CELL_TYPE_LTO) +#define OCV_ARRAY 2700, 2560, 2540, 2520, 2500, 2460, 2420, 2400, 2380, 2320, 1500 +#else // LiIon +#define OCV_ARRAY 4190, 4050, 3990, 3890, 3800, 3720, 3630, 3530, 3420, 3300, 3100 +#endif +#endif + +/*Note: 12V lead acid is 6 cells, most board accept only 1 cell LiIon/LiPo*/ +#ifndef NUM_CELLS +#define NUM_CELLS 1 +#endif + #ifdef BAT_MEASURE_ADC_UNIT extern RTC_NOINIT_ATTR uint64_t RTC_reg_b; #include "soc/sens_reg.h" // needed for adc pin reset @@ -44,6 +58,7 @@ class Power : private concurrency::OSThread virtual bool setup(); virtual int32_t runOnce() override; void setStatusHandler(meshtastic::PowerStatus *handler) { statusHandler = handler; } + const uint16_t OCV[11] = {OCV_ARRAY}; protected: meshtastic::PowerStatus *statusHandler; @@ -54,6 +69,7 @@ class Power : private concurrency::OSThread bool analogInit(); private: + // open circuit voltage lookup table uint8_t low_voltage_counter; #ifdef DEBUG_HEAP uint32_t lastheap; diff --git a/src/shutdown.h b/src/shutdown.h index f36a7f8dd..21abba07e 100644 --- a/src/shutdown.h +++ b/src/shutdown.h @@ -3,6 +3,11 @@ #include "graphics/Screen.h" #include "main.h" #include "power.h" +#if defined(ARCH_PORTDUINO) +#include "api/WiFiServerAPI.h" +#include "input/LinuxInputImpl.h" + +#endif void powerCommandsCheck() { @@ -14,8 +19,16 @@ void powerCommandsCheck() NVIC_SystemReset(); #elif defined(ARCH_RP2040) rp2040.reboot(); -#elif defined(ARCH_RASPBERRY_PI) - exit(EXIT_SUCCESS); +#elif defined(ARCH_PORTDUINO) + deInitApiServer(); + if (aLinuxInputImpl) + aLinuxInputImpl->deInit(); + SPI.end(); + Wire.end(); + Serial1.end(); + if (screen) + delete screen; + reboot(); #else rebootAtMsec = -1; LOG_WARN("FIXME implement reboot for this platform. Note that some settings require a restart to be applied.\n"); @@ -33,6 +46,8 @@ void powerCommandsCheck() #if defined(ARCH_NRF52) || defined(ARCH_ESP32) playShutdownMelody(); power->shutdown(); +#elif defined(ARCH_PORTDUINO) + exit(EXIT_SUCCESS); #else LOG_WARN("FIXME implement shutdown for this platform"); #endif diff --git a/src/sleep.cpp b/src/sleep.cpp index b0f4aec88..a2a221d79 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -1,17 +1,25 @@ -#include "sleep.h" +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_GPS #include "GPS.h" +#endif + +#include "ButtonThread.h" #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" -#include "configuration.h" +#include "detect/LoRaRadioType.h" #include "error.h" #include "main.h" +#include "sleep.h" #include "target_specific.h" #ifdef ARCH_ESP32 #include "esp32/pm.h" #include "esp_pm.h" -#include "mesh/http/WiFiAPClient.h" +#if !MESHTASTIC_EXCLUDE_WIFI +#include "mesh/wifi/WiFiAPClient.h" +#endif #include "rom/rtc.h" #include #include @@ -48,7 +56,7 @@ RTC_DATA_ATTR int bootCount = 0; */ void setCPUFast(bool on) { -#ifdef ARCH_ESP32 +#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI if (isWifiAvailable()) { /* @@ -141,6 +149,18 @@ void initDeepSleep() LOG_INFO("Booted, wake cause %d (boot count %d), reset_reason=%s\n", wakeCause, bootCount, reason); #endif + +#if SOC_RTCIO_HOLD_SUPPORTED + // If waking from sleep, release any and all RTC GPIOs + if (wakeCause != ESP_SLEEP_WAKEUP_UNDEFINED) { + LOG_DEBUG("Disabling any holds on RTC IO pads\n"); + for (uint8_t i = 0; i <= GPIO_NUM_MAX; i++) { + if (rtc_gpio_is_valid_gpio((gpio_num_t)i)) + rtc_gpio_hold_dis((gpio_num_t)i); + } + } +#endif + #endif } @@ -186,16 +206,37 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) // not using wifi yet, but once we are this is needed to shutoff the radio hw // esp_wifi_stop(); waitEnterSleep(skipPreflight); + +#ifdef ARCH_ESP32 + // Full shutdown of bluetooth hardware + nimbleBluetooth->deinit(); +#endif + +#ifdef ARCH_ESP32 + if (shouldLoraWake(msecToWake)) { + notifySleep.notifyObservers(NULL); + } else { + notifyDeepSleep.notifyObservers(NULL); + } +#else notifyDeepSleep.notifyObservers(NULL); +#endif screen->doDeepSleep(); // datasheet says this will draw only 10ua - nodeDB.saveToDisk(); + nodeDB->saveToDisk(); +#ifdef TTGO_T_ECHO +#ifdef PIN_POWER_EN + pinMode(PIN_POWER_EN, INPUT); // power off peripherals + // pinMode(PIN_POWER_EN1, INPUT_PULLDOWN); +#endif +#endif +#if HAS_GPS // Kill GPS power completely (even if previously we just had it in sleep mode) if (gps) gps->setGPSPower(false, false, 0); - +#endif setLed(false); #ifdef RESET_OLED @@ -203,15 +244,30 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) #endif #if defined(VEXT_ENABLE_V03) - if (heltec_version == 3) { - digitalWrite(VEXT_ENABLE_V03, 1); // turn off the display power - } else { - digitalWrite(VEXT_ENABLE_V05, 0); // turn off the display power - } + digitalWrite(VEXT_ENABLE_V03, 1); // turn off the display power +#elif defined(VEXT_ENABLE_V05) + digitalWrite(VEXT_ENABLE_V05, 0); // turn off the lora amplifier power + digitalWrite(ST7735_BL_V05, 0); // turn off the display power #elif defined(VEXT_ENABLE) digitalWrite(VEXT_ENABLE, 1); // turn off the display power #endif +#ifdef ARCH_ESP32 + if (shouldLoraWake(msecToWake)) { + enableLoraInterrupt(); + } +#ifdef BUTTON_PIN + // Avoid leakage through button pin + pinMode(BUTTON_PIN, INPUT); + gpio_hold_en((gpio_num_t)BUTTON_PIN); +#endif + + // LoRa CS (RADIO_NSS) needs to stay HIGH, even during deep sleep + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, HIGH); + gpio_hold_en((gpio_num_t)LORA_CS); +#endif + #ifdef HAS_PMU if (pmu_found && PMU) { // Obsolete comment: from back when we we used to receive lora packets while CPU was in deep sleep. @@ -224,6 +280,7 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) // If we want to leave the radio receiving in would be 11.5mA current draw, but most of the time it is just waiting // in its sequencer (true?) so the average power draw should be much lower even if we were listinging for packets // all the time. + PMU->setChargingLedMode(XPOWERS_CHG_LED_OFF); uint8_t model = PMU->getChipModel(); if (model == XPOWERS_AXP2101) { @@ -238,9 +295,15 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false) // t-beam v1.1 radio power channel PMU->disablePowerOutput(XPOWERS_LDO2); // lora radio power channel } + if (msecToWake == portMAX_DELAY) { + LOG_INFO("PMU shutdown.\n"); + console->flush(); + PMU->shutdown(); + } } #endif + console->flush(); cpuDeepSleep(msecToWake); } @@ -287,20 +350,19 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r // assert(esp_sleep_enable_uart_wakeup(0) == ESP_OK); #endif #ifdef BUTTON_PIN -#if SOC_PM_SUPPORT_EXT_WAKEUP - esp_sleep_enable_ext0_wakeup((gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN), - LOW); // when user presses, this button goes low -#else + // The enableLoraInterrupt() method is using ext0_wakeup, so we are forced to use GPIO wakeup + gpio_num_t pin = (gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN); + + // Have to *fully* detach the normal button-interrupts first + buttonThread->detachButtonInterrupts(); + + gpio_wakeup_enable(pin, GPIO_INTR_LOW_LEVEL); esp_sleep_enable_gpio_wakeup(); - gpio_wakeup_enable((gpio_num_t)BUTTON_PIN, GPIO_INTR_LOW_LEVEL); #endif +#ifdef T_WATCH_S3 + gpio_wakeup_enable((gpio_num_t)SCREEN_TOUCH_INT, GPIO_INTR_LOW_LEVEL); #endif -#if defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) - gpio_wakeup_enable((gpio_num_t)LORA_DIO1, GPIO_INTR_HIGH_LEVEL); // SX126x/SX128x interrupt, active high -#endif -#ifdef RF95_IRQ - gpio_wakeup_enable((gpio_num_t)RF95_IRQ, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high -#endif + enableLoraInterrupt(); #ifdef PMU_IRQ // wake due to PMU can happen repeatedly if there is no battery installed or the battery fills if (pmu_found) @@ -308,27 +370,55 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r #endif auto res = esp_sleep_enable_gpio_wakeup(); if (res != ESP_OK) { - LOG_DEBUG("esp_sleep_enable_gpio_wakeup result %d\n", res); + LOG_ERROR("esp_sleep_enable_gpio_wakeup result %d\n", res); } assert(res == ESP_OK); res = esp_sleep_enable_timer_wakeup(sleepUsec); if (res != ESP_OK) { - LOG_DEBUG("esp_sleep_enable_timer_wakeup result %d\n", res); + LOG_ERROR("esp_sleep_enable_timer_wakeup result %d\n", res); } assert(res == ESP_OK); + + console->flush(); res = esp_light_sleep_start(); if (res != ESP_OK) { - LOG_DEBUG("esp_light_sleep_start result %d\n", res); + LOG_ERROR("esp_light_sleep_start result %d\n", res); } - assert(res == ESP_OK); + // commented out because it's not that crucial; + // if it sporadically happens the node will go into light sleep during the next round + // assert(res == ESP_OK); + +#ifdef BUTTON_PIN + // Disable wake-on-button interrupt. Re-attach normal button-interrupts + gpio_wakeup_disable(pin); + buttonThread->attachButtonInterrupts(); +#endif + +#ifdef T_WATCH_S3 + gpio_wakeup_disable((gpio_num_t)SCREEN_TOUCH_INT); +#endif + +#if !defined(SOC_PM_SUPPORT_EXT_WAKEUP) && defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) + if (radioType != RF95_RADIO) { + gpio_wakeup_disable((gpio_num_t)LORA_DIO1); + } +#endif +#if defined(RF95_IRQ) && (RF95_IRQ != RADIOLIB_NC) + if (radioType == RF95_RADIO) { + gpio_wakeup_disable((gpio_num_t)RF95_IRQ); + } +#endif esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); #ifdef BUTTON_PIN if (cause == ESP_SLEEP_WAKEUP_GPIO) { LOG_INFO("Exit light sleep gpio: btn=%d\n", !digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); - } + } else #endif + { + LOG_INFO("Exit light sleep cause: %d\n", cause); + } return cause; } @@ -360,4 +450,44 @@ void enableModemSleep() int rv = esp_pm_configure(&esp32_config); LOG_DEBUG("Sleep request result %x\n", rv); } -#endif \ No newline at end of file + +bool shouldLoraWake(uint32_t msecToWake) +{ + return msecToWake < portMAX_DELAY && (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || + config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER); +} + +void enableLoraInterrupt() +{ +#if SOC_PM_SUPPORT_EXT_WAKEUP && defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) + gpio_pulldown_en((gpio_num_t)LORA_DIO1); +#if defined(LORA_RESET) && (LORA_RESET != RADIOLIB_NC) + gpio_pullup_en((gpio_num_t)LORA_RESET); +#endif +#if defined(LORA_CS) && (LORA_CS != RADIOLIB_NC) + gpio_pullup_en((gpio_num_t)LORA_CS); +#endif + + if (rtc_gpio_is_valid_gpio((gpio_num_t)LORA_DIO1)) { + // Setup light/deep sleep with wakeup by external source + LOG_INFO("setup LORA_DIO1 (GPIO%02d) with wakeup by external source\n", LORA_DIO1); + esp_sleep_enable_ext0_wakeup((gpio_num_t)LORA_DIO1, HIGH); + } else { + LOG_INFO("setup LORA_DIO1 (GPIO%02d) with wakeup by gpio interrupt\n", LORA_DIO1); + gpio_wakeup_enable((gpio_num_t)LORA_DIO1, GPIO_INTR_HIGH_LEVEL); + } + +#elif defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) + if (radioType != RF95_RADIO) { + LOG_INFO("setup LORA_DIO1 (GPIO%02d) with wakeup by gpio interrupt\n", LORA_DIO1); + gpio_wakeup_enable((gpio_num_t)LORA_DIO1, GPIO_INTR_HIGH_LEVEL); // SX126x/SX128x interrupt, active high + } +#endif +#if defined(RF95_IRQ) && (RF95_IRQ != RADIOLIB_NC) + if (radioType == RF95_RADIO) { + LOG_INFO("setup RF95_IRQ (GPIO%02d) with wakeup by gpio interrupt\n", RF95_IRQ); + gpio_wakeup_enable((gpio_num_t)RF95_IRQ, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high + } +#endif +} +#endif diff --git a/src/sleep.h b/src/sleep.h index a592ad2c1..8d5b9a94f 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -43,4 +43,8 @@ extern Observable notifyDeepSleep; /// Called to tell GPS thread to enter deep sleep independently of LoRa/MCU sleep, prior to full poweroff. Must return 0 extern Observable notifyGPSSleep; -void enableModemSleep(); \ No newline at end of file +void enableModemSleep(); +#ifdef ARCH_ESP32 +void enableLoraInterrupt(); +bool shouldLoraWake(uint32_t msecToWake); +#endif \ No newline at end of file diff --git a/src/target_specific.h b/src/target_specific.h index 1e79df510..7404a3720 100644 --- a/src/target_specific.h +++ b/src/target_specific.h @@ -5,6 +5,6 @@ // Functions that are unique to particular target types (esp32, bare, nrf52 etc...) // Enable/disable bluetooth. -void setBluetoothEnable(bool on); +void setBluetoothEnable(bool enable); void getMacAddr(uint8_t *dmac); \ No newline at end of file diff --git a/suppressions.txt b/suppressions.txt index e65afc0bf..04937523d 100644 --- a/suppressions.txt +++ b/suppressions.txt @@ -49,4 +49,8 @@ virtualCallInConstructor passedByValue:*/RedirectablePrint.h -internalAstError:*/CrossPlatformCryptoEngine.cpp \ No newline at end of file +internalAstError:*/CrossPlatformCryptoEngine.cpp +uninitMemberVar:*/AudioThread.h +// False positive +constVariableReference:*/Channels.cpp +constParameterPointer:*/unishox2.c \ No newline at end of file diff --git a/variants/CDEBYTE_EoRa-S3/pins_arduino.h b/variants/CDEBYTE_EoRa-S3/pins_arduino.h new file mode 100644 index 000000000..38a9103f0 --- /dev/null +++ b/variants/CDEBYTE_EoRa-S3/pins_arduino.h @@ -0,0 +1,37 @@ +// Need this file for ESP32-S3 +// No need to modify this file, changes to pins imported from variant.h +// Most is similar to https://github.com/espressif/arduino-esp32/blob/master/variants/esp32s3/pins_arduino.h + +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +#define EXTERNAL_NUM_INTERRUPTS 46 +#define NUM_DIGITAL_PINS 48 +#define NUM_ANALOG_INPUTS 20 + +#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) +#define digitalPinToInterrupt(p) \ + (((p) < 48) ? (p) : -1) // Maybe it should be <= 48 but this is from a trustworthy source so it is likely correct +#define digitalPinHasPWM(p) (p < 46) + +// Serial +static const uint8_t TX = UART_TX; +static const uint8_t RX = UART_RX; + +// Default SPI will be mapped to Radio +static const uint8_t SS = LORA_CS; +static const uint8_t SCK = LORA_SCK; +static const uint8_t MOSI = LORA_MOSI; +static const uint8_t MISO = LORA_MISO; + +// The default Wire will be mapped to PMU and RTC +static const uint8_t SCL = I2C_SCL; +static const uint8_t SDA = I2C_SDA; + +#endif /* Pins_Arduino_h */ diff --git a/variants/CDEBYTE_EoRa-S3/platformio.ini b/variants/CDEBYTE_EoRa-S3/platformio.ini new file mode 100644 index 000000000..1ff54de88 --- /dev/null +++ b/variants/CDEBYTE_EoRa-S3/platformio.ini @@ -0,0 +1,8 @@ +[env:CDEBYTE_EoRa-S3] +extends = esp32s3_base +board = CDEBYTE_EoRa-S3 +build_flags = + ${esp32s3_base.build_flags} + -D CDEBYTE_EORA_S3 + -I variants/CDEBYTE_EoRa-S3 + -D GPS_POWER_TOGGLE diff --git a/variants/CDEBYTE_EoRa-S3/variant.h b/variants/CDEBYTE_EoRa-S3/variant.h new file mode 100644 index 000000000..5da99667b --- /dev/null +++ b/variants/CDEBYTE_EoRa-S3/variant.h @@ -0,0 +1,63 @@ +// LED - status indication +#define LED_PIN 37 + +// Button - user interface +#define BUTTON_PIN 0 // This is the BOOT button, and it has its own pull-up resistor + +// SD card - TODO: test, currently untested, copied from T3S3 variant +#define HAS_SDCARD +#define SDCARD_USE_SPI1 +// TODO: rename this to make this SD-card specific +#define SPI_CS 13 +#define SPI_SCK 14 +#define SPI_MOSI 11 +#define SPI_MISO 2 +// FIXME: there are two other SPI pins that are not defined here +// Compatibility +#define SDCARD_CS SPI_CS + +// Battery voltage monitoring - TODO: test, currently untested, copied from T3S3 variant +#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_MULTIPLIER \ + 2.11 // ratio of voltage divider = 2.0 (R10=1M, R13=1M), plus some undervoltage correction - TODO: this was carried over from + // the T3S3, test to see if the undervoltage correction is needed. + +// Display - OLED connected via I2C by the default hardware configuration +#define HAS_SCREEN 1 +#define USE_SSD1306 +#define I2C_SCL 17 +#define I2C_SDA 18 + +// UART - The 1mm JST SH connector closest to the USB-C port +#define UART_TX 43 +#define UART_RX 44 + +// Peripheral I2C - The 1mm JST SH connector furthest from the USB-C port which follows Adafruit connection standard. There are no +// pull-up resistors on these lines, the downstream device needs to include them. TODO: test, currently untested +#define I2C_SCL1 21 +#define I2C_SDA1 10 + +// Radio +#define USE_SX1262 // CDEBYTE EoRa-S3-900TB <- CDEBYTE E22-900MM22S <- Semtech SX1262 +#define USE_SX1268 // CDEBYTE EoRa-S3-400TB <- CDEBYTE E22-400MM22S <- Semtech SX1268 + +#define SX126X_CS 7 +#define LORA_SCK 5 +#define LORA_MOSI 6 +#define LORA_MISO 3 +#define SX126X_RESET 8 +#define SX126X_BUSY 34 +#define SX126X_DIO1 33 + +#define SX126X_DIO2_AS_RF_SWITCH // All switching is performed with DIO2, it is automatically inverted using circuitry. +// CDEBYTE EoRa-S3 uses an XTAL, thus we do not need DIO3 as TCXO voltage reference. Don't define SX126X_DIO3_TCXO_VOLTAGE for +// simplicity rather than defining it as 0. +#define SX126X_MAX_POWER \ + 22 // E22-900MM22S and E22-400MM22S have a raw SX1262 or SX1268 respsectively, they are rated to output up and including 22 + // dBm out of their SX126x IC. + +// Compatibility with old variant.h file structure - FIXME: this should be done in the respective radio interface modules to clean +// up all variants. +#define LORA_CS SX126X_CS +#define LORA_DIO1 SX126X_DIO1 \ No newline at end of file diff --git a/variants/Dongle_nRF52840-pca10059-v1/platformio.ini b/variants/Dongle_nRF52840-pca10059-v1/platformio.ini index a937bfa98..b1608770e 100644 --- a/variants/Dongle_nRF52840-pca10059-v1/platformio.ini +++ b/variants/Dongle_nRF52840-pca10059-v1/platformio.ini @@ -4,6 +4,9 @@ board = nordic_pca10059 board_level = extra build_flags = ${nrf52840_base.build_flags} -Ivariants/Dongle_nRF52840-pca10059-v1 -D NORDIC_PCA10059 -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -DEINK_DISPLAY_MODEL=GxEPD2_420_M01 + -DEINK_WIDTH=300 + -DEINK_HEIGHT=400 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/Dongle_nRF52840-pca10059-v1> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/Dongle_nRF52840-pca10059-v1/variant.h b/variants/Dongle_nRF52840-pca10059-v1/variant.h index 81e2ad995..533367a30 100644 --- a/variants/Dongle_nRF52840-pca10059-v1/variant.h +++ b/variants/Dongle_nRF52840-pca10059-v1/variant.h @@ -136,7 +136,7 @@ static const uint8_t SCK = PIN_SPI_SCK; #define USE_SX1262 #define SX126X_CS (0 + 31) // LORA_CS P0.31 #define SX126X_DIO1 (0 + 29) // DIO1 P0.29 -#define SX126X_BUSY (0 + 2) // LORA_BUSY P0.02 +#define SX126X_BUSY (0 + 2) // LORA_BUSY P0.02 #define SX126X_RESET (32 + 15) // LORA_RESET P1.15 #define SX126X_TXEN (32 + 13) // TXEN P1.13 NiceRF 868 dont use #define SX126X_RXEN (32 + 10) // RXEN P1.10 NiceRF 868 dont use @@ -155,19 +155,10 @@ static const uint8_t SCK = PIN_SPI_SCK; // and has 12 bit resolution #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) -#define VBAT_DIVIDER (0.4F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (1.73) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +#define ADC_MULTIPLIER (1.73F) #ifdef __cplusplus } @@ -177,4 +168,4 @@ static const uint8_t SCK = PIN_SPI_SCK; * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif +#endif \ No newline at end of file diff --git a/variants/MakePython_nRF52840_eink/platformio.ini b/variants/MakePython_nRF52840_eink/platformio.ini index 3ac18bcf0..f421466ec 100644 --- a/variants/MakePython_nRF52840_eink/platformio.ini +++ b/variants/MakePython_nRF52840_eink/platformio.ini @@ -10,5 +10,8 @@ lib_deps = ${nrf52840_base.lib_deps} https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f zinggjm/GxEPD2@^1.4.9 + -DEINK_DISPLAY_MODEL=GxEPD2_290_T5D + -DEINK_WIDTH=296 + -DEINK_HEIGHT=128 debug_tool = jlink ;upload_port = /dev/ttyACM4 diff --git a/variants/MakePython_nRF52840_eink/variant.h b/variants/MakePython_nRF52840_eink/variant.h index 2ff9c76fd..16f803dd2 100644 --- a/variants/MakePython_nRF52840_eink/variant.h +++ b/variants/MakePython_nRF52840_eink/variant.h @@ -134,19 +134,10 @@ static const uint8_t SCK = PIN_SPI_SCK; // and has 12 bit resolution #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) -#define VBAT_DIVIDER (0.4F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (1.73) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +#define ADC_MULTIPLIER (1.73F) #ifdef __cplusplus } @@ -156,4 +147,4 @@ static const uint8_t SCK = PIN_SPI_SCK; * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif +#endif \ No newline at end of file diff --git a/variants/MakePython_nRF52840_oled/variant.h b/variants/MakePython_nRF52840_oled/variant.h index e7375a610..a2a5fa80a 100644 --- a/variants/MakePython_nRF52840_oled/variant.h +++ b/variants/MakePython_nRF52840_oled/variant.h @@ -112,19 +112,10 @@ static const uint8_t SCK = PIN_SPI_SCK; // and has 12 bit resolution #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) -#define VBAT_DIVIDER (0.4F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (1.73) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +#define ADC_MULTIPLIER (1.73F) #ifdef __cplusplus } @@ -134,4 +125,4 @@ static const uint8_t SCK = PIN_SPI_SCK; * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif +#endif \ No newline at end of file diff --git a/variants/ai-c3/variant.h b/variants/ai-c3/variant.h index 254f5fd36..6c4f4d38a 100644 --- a/variants/ai-c3/variant.h +++ b/variants/ai-c3/variant.h @@ -7,10 +7,10 @@ #define LED_PIN 30 // RGB LED #define USE_RF95 -#define RF95_SCK 4 -#define RF95_MISO 5 -#define RF95_MOSI 6 -#define RF95_NSS 7 +#define LORA_SCK 4 +#define LORA_MISO 5 +#define LORA_MOSI 6 +#define LORA_CS 7 #define LORA_DIO0 10 #define LORA_DIO1 3 @@ -19,7 +19,7 @@ // WaveShare Core1262-868M // https://www.waveshare.com/wiki/Core1262-868M #define USE_SX1262 -#define SX126X_CS RF95_NSS +#define SX126X_CS LORA_CS #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY 10 #define SX126X_RESET LORA_RESET diff --git a/variants/betafpv_2400_tx_micro/variant.h b/variants/betafpv_2400_tx_micro/variant.h index 2a8b2f40c..8c615d168 100644 --- a/variants/betafpv_2400_tx_micro/variant.h +++ b/variants/betafpv_2400_tx_micro/variant.h @@ -9,10 +9,10 @@ #undef GPS_RX_PIN #undef GPS_TX_PIN -#define RF95_SCK 18 -#define RF95_MISO 19 -#define RF95_MOSI 23 -#define RF95_NSS 5 +#define LORA_SCK 18 +#define LORA_MISO 19 +#define LORA_MOSI 23 +#define LORA_CS 5 #define RF95_FAN_EN 17 #define LED_PIN 16 // This is a LED_WS2812 not a standard LED diff --git a/variants/betafpv_900_tx_nano/variant.h b/variants/betafpv_900_tx_nano/variant.h index 01961d92d..7a4ae9190 100644 --- a/variants/betafpv_900_tx_nano/variant.h +++ b/variants/betafpv_900_tx_nano/variant.h @@ -9,10 +9,10 @@ #define USE_RF95 -#define RF95_SCK 18 -#define RF95_MISO 19 -#define RF95_MOSI 23 -#define RF95_NSS 5 +#define LORA_SCK 18 +#define LORA_MISO 19 +#define LORA_MOSI 23 +#define LORA_CS 5 #define LORA_DIO0 4 #define LORA_RESET 14 diff --git a/variants/bpi_picow_esp32_s3/variant.h b/variants/bpi_picow_esp32_s3/variant.h index 8114b9ea3..d8d9413d7 100644 --- a/variants/bpi_picow_esp32_s3/variant.h +++ b/variants/bpi_picow_esp32_s3/variant.h @@ -22,24 +22,24 @@ // #define USE_RF95 // RFM95/SX127x -#undef RF95_SCK -#undef RF95_MISO -#undef RF95_MOSI -#undef RF95_NSS +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS // WaveShare Core1262-868M OK // https://www.waveshare.com/wiki/Core1262-868M #define USE_SX1262 #ifdef USE_SX1262 -#define RF95_MISO 39 -#define RF95_SCK 21 -#define RF95_MOSI 38 -#define RF95_NSS 17 +#define LORA_MISO 39 +#define LORA_SCK 21 +#define LORA_MOSI 38 +#define LORA_CS 17 #define LORA_RESET 42 #define LORA_DIO1 5 #define LORA_BUSY 47 -#define SX126X_CS RF95_NSS +#define SX126X_CS LORA_CS #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY LORA_BUSY #define SX126X_RESET LORA_RESET @@ -49,14 +49,14 @@ // #define USE_SX1280 #ifdef USE_SX1280 -#define RF95_MISO 1 -#define RF95_SCK 3 -#define RF95_MOSI 4 -#define RF95_NSS 2 +#define LORA_MISO 1 +#define LORA_SCK 3 +#define LORA_MOSI 4 +#define LORA_CS 2 #define LORA_RESET 17 #define LORA_DIO1 12 #define LORA_BUSY 47 -#define SX128X_CS RF95_NSS +#define SX128X_CS LORA_CS #define SX128X_DIO1 LORA_DIO1 #define SX128X_BUSY LORA_BUSY #define SX128X_RESET LORA_RESET diff --git a/variants/canaryone/platformio.ini b/variants/canaryone/platformio.ini new file mode 100644 index 000000000..4917f52c7 --- /dev/null +++ b/variants/canaryone/platformio.ini @@ -0,0 +1,14 @@ +; Public Beta oled/nrf52840/sx1262 device +[env:canaryone] +extends = nrf52840_base +board = canaryone +debug_tool = jlink + +# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. +build_flags = ${nrf52840_base.build_flags} -Ivariants/canaryone + -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/canaryone> +lib_deps = + ${nrf52840_base.lib_deps} + lewisxhe/PCF8563_Library@^1.0.1 +;upload_protocol = fs diff --git a/variants/canaryone/variant.cpp b/variants/canaryone/variant.cpp new file mode 100644 index 000000000..5967a2a96 --- /dev/null +++ b/variants/canaryone/variant.cpp @@ -0,0 +1,56 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 - pins 0 and 1 are hardwired for xtal and should never be enabled + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // LEDs + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); + + pinMode(PIN_LED3, OUTPUT); + ledOff(PIN_LED3); + + // Turn on power to the GPS and LoRa + pinMode(PIN_PWR_EN, OUTPUT); + digitalWrite(PIN_PWR_EN, HIGH); + + // Pull the GPS out of reset + pinMode(GPS_RESET_PIN, OUTPUT); + digitalWrite(GPS_RESET_PIN, HIGH); + + // Pull the LoRa out of reset + pinMode(LORA_RF_PWR, OUTPUT); + digitalWrite(LORA_RF_PWR, HIGH); +} diff --git a/variants/canaryone/variant.h b/variants/canaryone/variant.h new file mode 100644 index 000000000..d283f08f6 --- /dev/null +++ b/variants/canaryone/variant.h @@ -0,0 +1,182 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_CANARYONE +#define _VARIANT_CANARYONE + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#define CANARYONE + +#define GPIO_PORT0 0 +#define GPIO_PORT1 32 + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (GPIO_PORT1 + 1) // blue P1.01 +#define PIN_LED2 (GPIO_PORT0 + 14) // yellow P0.14 +#define PIN_LED3 (GPIO_PORT1 + 3) // green P1.03 + +#define LED_BLUE PIN_LED1 + +#define LED_BUILTIN PIN_LED1 +#define LED_CONN PIN_LED3 + +#define LED_STATE_ON 0 // State when LED is lit +#define LED_INVERTED 1 + +/* + * Buttons + */ +#define PIN_BUTTON1 (GPIO_PORT0 + 15) // BTN0 on schematic +#define PIN_BUTTON2 (GPIO_PORT0 + 16) // BTN1 on schematic + +/* + * Analog pins + */ +#define PIN_A0 (4) // Battery ADC P0.04 + +#define BATTERY_PIN PIN_A0 + +static const uint8_t A0 = PIN_A0; + +#define ADC_RESOLUTION 14 + +/** + * Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (GPIO_PORT0 + 26) +// #define I2C_SDA (GPIO_PORT0 + 26) +#define PIN_WIRE_SCL (GPIO_PORT0 + 27) +// #define I2C_SCL (GPIO_PORT0 + 27) + +#define PIN_LCD_RESET (GPIO_PORT0 + 2) + +/* + * External serial flash WP25R1635FZUIL0 + */ + +// QSPI Pins +#define PIN_QSPI_SCK (GPIO_PORT1 + 14) +#define PIN_QSPI_CS (GPIO_PORT1 + 15) +#define PIN_QSPI_IO0 (GPIO_PORT1 + 12) // MOSI if using two bit interface +#define PIN_QSPI_IO1 (GPIO_PORT1 + 13) // MISO if using two bit interface +#define PIN_QSPI_IO2 (GPIO_PORT0 + 7) // WP if using two bit interface (i.e. not used) +#define PIN_QSPI_IO3 (GPIO_PORT0 + 5) // HOLD if using two bit interface (i.e. not used) + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES MX25R1635F +#define EXTERNAL_FLASH_USE_QSPI + +// Add a delay on startup to allow LoRa and GPS to power up +#define PIN_PWR_DELAY_MS 100 + +/* + * Lora radio + */ +#define RADIOLIB_DEBUG 1 +#define USE_SX1262 +#define SX126X_CS (GPIO_PORT0 + 24) +#define SX126X_DIO1 (GPIO_PORT1 + 11) +// #define SX126X_DIO3 (GPIO_PORT0 + 21) +// #define SX126X_DIO2 () // LORA_BUSY // LoRa RX/TX +#define SX126X_BUSY (GPIO_PORT0 + 17) +#define SX126X_RESET (GPIO_PORT0 + 25) +#define LORA_RF_PWR (GPIO_PORT0 + 28) // LORA_RF_SWITCH + +/* + * GPS pins + */ +#define HAS_GPS 1 +#define GPS_UBLOX +#define GPS_BAUDRATE 38400 + +// #define PIN_GPS_WAKE (GPIO_PORT1 + 2) // An output to wake GPS, low means allow sleep, high means force wake +// Seems to be missing on this new board +#define PIN_GPS_PPS (GPIO_PORT1 + 4) // Pulse per second input from the GPS +#define GPS_TX_PIN (GPIO_PORT1 + 9) // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN (GPIO_PORT1 + 8) // This is for bits going TOWARDS the GPS + +#define GPS_THREAD_INTERVAL 50 + +#define PIN_SERIAL1_RX GPS_TX_PIN +#define PIN_SERIAL1_TX GPS_RX_PIN + +#define GPS_RESET_PIN (GPIO_PORT1 + 5) // GPS reset pin + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 1 + +// For LORA, spi 0 +#define PIN_SPI_MISO (GPIO_PORT0 + 23) +#define PIN_SPI_MOSI (GPIO_PORT0 + 22) +#define PIN_SPI_SCK (GPIO_PORT0 + 19) + +// #define PIN_SPI1_MISO (GPIO_PORT1 + 6) // FIXME not really needed, but for now the SPI code requires something to be defined, +// pick an used GPIO #define PIN_SPI1_MOSI (GPIO_PORT1 + 8) #define PIN_SPI1_SCK (GPIO_PORT1 + 9) + +#define PIN_PWR_EN (GPIO_PORT0 + 12) + +// To debug via the segger JLINK console rather than the CDC-ACM serial device +#define USE_SEGGER 1 + +// #define LORA_DISABLE_SENDING 1 +#define SX126X_DIO2_AS_RF_SWITCH 1 + +// Battery +// The battery sense is hooked to pin A0 (4) +// it is defined in the anlaolgue pin section of this file +// and has 12 bit resolution +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER (2.0F) + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif \ No newline at end of file diff --git a/variants/chatter2/platformio.ini b/variants/chatter2/platformio.ini new file mode 100644 index 000000000..0856debfc --- /dev/null +++ b/variants/chatter2/platformio.ini @@ -0,0 +1,13 @@ +; CircuitMess Chatter 2 based on ESP32-WROOM-32 (38 pins) devkit & DeeamLNK DL-LLCC68 or Heltec HT RA62 SX1262/SX1268 module +[env:chatter2] +extends = esp32_base +board = esp32doit-devkit-v1 +board_level = extra +build_flags = + ${esp32_base.build_flags} + -D CHATTER_2 + -I variants/chatter2 + +lib_deps = + ${esp32_base.lib_deps} + lovyan03/LovyanGFX@^1.1.8 \ No newline at end of file diff --git a/variants/chatter2/variant.h b/variants/chatter2/variant.h new file mode 100644 index 000000000..90faa1d7b --- /dev/null +++ b/variants/chatter2/variant.h @@ -0,0 +1,117 @@ +////////////////////////////////////////////////////////////////////////////////// +// // +// Have custom connections or functionality? Configure them in this section // +// // +////////////////////////////////////////////////////////////////////////////////// + +// Debugging +// #define GPS_DEBUG +// #define GPS_EXTRAVERBOSE + +// Lora +#define USE_LLCC68 // Original Chatter2 with LLCC68 module +#define USE_SX1262 // Added for when Lora module is swapped for HT-RA62 + +#define SX126X_CS 14 // module's NSS pin +#define LORA_SCK 16 // module's SCK pin +#define LORA_MOSI 5 // module's MOSI pin +#define LORA_MISO 17 // module's MISO pin +#define SX126X_RESET RADIOLIB_NC // module's NRST pin +#define SX126X_BUSY 4 // module's BUSY pin works for both LLCC68 and RA-62 with cut & jumper +#define SX126X_DIO1 18 // module's DIO1 pin +#define SX126X_DIO2_AS_RF_SWITCH // module's DIO2 pin +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 // module's DIO pin +#define SX126X_TXEN RADIOLIB_NC +#define SX126X_RXEN RADIOLIB_NC + +// Status +// #define LED_PIN 1 +// External notification +// FIXME: Check if EXT_NOTIFY_OUT actualy has any effect and removes the need for setting the external notication pin in the +// app/preferences +// #define EXT_NOTIFY_OUT 2 // The GPIO pin that acts as the external notification output (here we connect an LED to it) + +// Buzzer +#define PIN_BUZZER 19 +// Buttons +#define BUTTON_PIN 36 // Use the WAKE button as the user button +// I2C +// #define I2C_SCL 27 +// #define I2C_SDA 26 + +#define SX126X_MAX_POWER 22 // SX126xInterface.cpp defaults to 22 if not defined, but here we define it for good practice + +// Display + +#define HAS_SCREEN 1 // Assume no screen present by default to prevent crash... + +// ST7735S TFT LCD +#define ST7735S 1 // there are different (sub-)versions of ST7735 +#define ST7735_CS -1 +#define ST7735_RS 33 // DC +#define ST7735_SDA 26 // MOSI +#define ST7735_SCK 27 +#define ST7735_RESET 15 +#define ST7735_MISO -1 +#define ST7735_BUSY -1 +#define ST7735_BL 32 +#define ST7735_SPI_HOST HSPI_HOST // SPI2_HOST for S3, auto may work too +#define SPI_FREQUENCY 40000000 +#define SPI_READ_FREQUENCY 16000000 +#define TFT_HEIGHT 160 +#define TFT_WIDTH 128 +#define TFT_OFFSET_X 0 +#define TFT_OFFSET_Y 0 +#define TFT_INVERT false +#define SCREEN_ROTATE +#define SCREEN_TRANSITION_FRAMERATE 5 // fps +#define DISPLAY_FORCE_SMALL_FONTS + +// Battery + +#define BATTERY_PIN 34 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC1_GPIO34_CHANNEL +#define ADC_ATTENUATION \ + ADC_ATTEN_DB_2_5 // 2_5-> 100mv-1250mv, 11-> 150mv-3100mv for ESP32 + // ESP32-S2/C3/S3 are different + // lower dB for lower voltage rnage +#define ADC_MULTIPLIER 5.0 // VBATT---10k--pin34---2.5K---GND +// Chatter2 uses 3 AAA cells +#define CELL_TYPE_ALKALINE +#define NUM_CELLS 3 +#undef EXT_PWR_DETECT + +// GPS +// FIXME: unsure what to define HAS_GPS as if GPS isn't always present +#define HAS_GPS 1 // Don't need to set this to 0 to prevent a crash as it doesn't crash if GPS not found, will probe by default +// #define PIN_GPS_EN 15 +// #define GPS_EN_ACTIVE 1 +#undef GPS_TX_PIN +#undef GPS_RX_PIN +#define GPS_TX_PIN 13 +#define GPS_RX_PIN 2 + +///////////////////////////////////////////////////////////////////////////////// +// // +// You should have no need to modify the code below, nor in pins_arduino.h // +// // +///////////////////////////////////////////////////////////////////////////////// + +#define LORA_CS SX126X_CS // FIXME: for some reason both are used in /src + +// Many of the below values would only be used if USE_RF95 was defined, but it's not as we aren't actually using an RF95, just +// that the 4 pins above are named like it If they aren't used they don't need to be defined and doing so cause confusion to those +// adapting this file LORA_RESET value is never used in src (as we are not using RF95), so no need to define LORA_DIO0 is not used +// in src (as we are not using RF95) as SX1262 does not have it per SX1262 datasheet, so no need to define +// FIXME: confirm that the linked lines below are actually only called when using the SX126x or SX128x and no other modules +// then use SX126X_DIO1 and SX128X_DIO1 respectively for that purpose, removing the need for RF95-style LORA_* definitions when +// the RF95 isn't used +#define LORA_DIO1 \ + SX126X_DIO1 // The old name is used in + // https://github.com/meshtastic/firmware/blob/7eff5e7bcb2084499b723c5e3846c15ee089e36d/src/sleep.cpp#L298, so + // must also define the old name +// LORA_DIO2 value is never used in src (as we are not using RF95), so no need to define, and if DIO2_AS_RF_SWITCH is set then it +// cannot serve any extra function even if requested to LORA_DIO3 value is never used in src (as we are not using RF95), so no +// need to define, and DIO3_AS_TCXO_AT_1V8 is set so it cannot serve any extra function even if requested to (from 13.3.2.1 +// DioxMask in SX1262 datasheet: Note that if DIO2 or DIO3 are used to control the RF Switch or the TCXO, the IRQ will not be +// generated even if it is mapped to the pins.) \ No newline at end of file diff --git a/variants/diy/dr-dev/variant.h b/variants/diy/dr-dev/variant.h index 08d57eec9..35b18ee74 100644 --- a/variants/diy/dr-dev/variant.h +++ b/variants/diy/dr-dev/variant.h @@ -22,12 +22,12 @@ // In receiving, set RXEN as high communication level, TXEN is lowlevel; // Before powering off, set TXEN、RXEN as low level. -#undef RF95_SCK -#define RF95_SCK 18 -#undef RF95_MISO -#define RF95_MISO 19 -#undef RF95_MOSI -#define RF95_MOSI 23 +#undef LORA_SCK +#define LORA_SCK 18 +#undef LORA_MISO +#define LORA_MISO 19 +#undef LORA_MOSI +#define LORA_MOSI 23 // PINS FOR THE 900M22S @@ -38,8 +38,8 @@ // E22_TXEN_CONNECTED_TO_DIO2 wasn't defined, so RXEN wasn't controlled. Commented it out to maintain behavior, but shouldn't be. // Need to comment out defining SX126X_RXEN as LORA_RXEN too // #define LORA_RXEN 17 // Input - RF switch RX control, connecting external MCU IO, valid in high level -#undef RF95_NSS -#define RF95_NSS 16 +#undef LORA_CS +#define LORA_CS 16 #define SX126X_BUSY 22 #define SX126X_CS 16 @@ -49,8 +49,8 @@ #define LORA_DIO2 35 // BUSY for SX1262/SX1268 #define LORA_TXEN NOT_A_PIN // Input - RF switch TX control, connecting external MCU IO or DIO2, valid in high level #define LORA_RXEN 21 // Input - RF switch RX control, connecting external MCU IO, valid in high level -#undef RF95_NSS -#define RF95_NSS 33 +#undef LORA_CS +#define LORA_CS 33 #define SX126X_BUSY 35 #define SX126X_CS 33 */ diff --git a/variants/diy/hydra/variant.h b/variants/diy/hydra/variant.h index 64bdd73f7..60bb60beb 100644 --- a/variants/diy/hydra/variant.h +++ b/variants/diy/hydra/variant.h @@ -18,6 +18,7 @@ // Radio #define USE_SX1262 // E22-900M30S uses SX1262 +#define USE_SX1268 // E22-400M30S uses SX1268 #define SX126X_MAX_POWER \ 22 // Outputting 22dBm from SX1262 results in ~30dBm E22-900M30S output (module only uses last stage of the YP2233W PA) #define SX126X_DIO3_TCXO_VOLTAGE 1.8 // E22 series TCXO reference voltage is 1.8V @@ -33,8 +34,8 @@ #define SX126X_TXEN 13 // Schematic connects EBYTE module's TXEN pin to MCU #define SX126X_RXEN 14 // Schematic connects EBYTE module's RXEN pin to MCU -#define RF95_NSS SX126X_CS // Compatibility with variant file configuration structure -#define RF95_SCK SX126X_SCK // Compatibility with variant file configuration structure -#define RF95_MOSI SX126X_MOSI // Compatibility with variant file configuration structure -#define RF95_MISO SX126X_MISO // Compatibility with variant file configuration structure +#define LORA_CS SX126X_CS // Compatibility with variant file configuration structure +#define LORA_SCK SX126X_SCK // Compatibility with variant file configuration structure +#define LORA_MOSI SX126X_MOSI // Compatibility with variant file configuration structure +#define LORA_MISO SX126X_MISO // Compatibility with variant file configuration structure #define LORA_DIO1 SX126X_DIO1 // Compatibility with variant file configuration structure diff --git a/variants/diy/v1/variant.h b/variants/diy/v1/variant.h index 48906515b..4802dbe89 100644 --- a/variants/diy/v1/variant.h +++ b/variants/diy/v1/variant.h @@ -23,10 +23,10 @@ #define LORA_DIO2 32 // BUSY for SX1262/SX1268 #define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262/SX1268, if DIO3 is high the TXCO is enabled -#define RF95_SCK 5 -#define RF95_MISO 19 -#define RF95_MOSI 27 -#define RF95_NSS 18 +#define LORA_SCK 5 +#define LORA_MISO 19 +#define LORA_MOSI 27 +#define LORA_CS 18 // supported modules list #define USE_RF95 // RFM95/SX127x diff --git a/variants/diy/v1_1/variant.h b/variants/diy/v1_1/variant.h index fd5276ced..8a006d0d2 100644 --- a/variants/diy/v1_1/variant.h +++ b/variants/diy/v1_1/variant.h @@ -22,14 +22,14 @@ #define LORA_RXEN 14 // Input - RF switch RX control, connecting external MCU IO, valid in high level #define LORA_TXEN 13 // Input - RF switch TX control, connecting external MCU IO or DIO2, valid in high level -#undef RF95_SCK -#define RF95_SCK 18 -#undef RF95_MISO -#define RF95_MISO 19 -#undef RF95_MOSI -#define RF95_MOSI 23 -#undef RF95_NSS -#define RF95_NSS 5 +#undef LORA_SCK +#define LORA_SCK 18 +#undef LORA_MISO +#define LORA_MISO 19 +#undef LORA_MOSI +#define LORA_MOSI 23 +#undef LORA_CS +#define LORA_CS 5 // RX/TX for RFM95/SX127x #define RF95_RXEN LORA_RXEN diff --git a/variants/esp32-s3-pico/pins_arduino.h b/variants/esp32-s3-pico/pins_arduino.h new file mode 100644 index 000000000..d24d98a2e --- /dev/null +++ b/variants/esp32-s3-pico/pins_arduino.h @@ -0,0 +1,36 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +#define EXTERNAL_NUM_INTERRUPTS 46 +#define NUM_DIGITAL_PINS 48 +#define NUM_ANALOG_INPUTS 20 + +#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) +#define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) +#define digitalPinHasPWM(p) (p < 46) + +// The default Wire will be mapped to PMU and RTC +static const uint8_t SDA = 15; +static const uint8_t SCL = 16; + +// Default SPI will be mapped to Radio +static const uint8_t MISO = 37; +static const uint8_t SCK = 35; +static const uint8_t MOSI = 36; +static const uint8_t SS = 14; + +static const uint8_t BAT_ADC_PIN = 26; + +// #define SPI_MOSI (11) +// #define SPI_SCK (14) +// #define SPI_MISO (2) +// #define SPI_CS (13) + +// #define SDCARD_CS SPI_CS + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/esp32-s3-pico/platformio.ini b/variants/esp32-s3-pico/platformio.ini new file mode 100644 index 000000000..ef737d98a --- /dev/null +++ b/variants/esp32-s3-pico/platformio.ini @@ -0,0 +1,25 @@ +[env:ESP32-S3-Pico] + +board_level = extra +extends = esp32s3_base +upload_protocol = esptool +board = esp32-s3-pico + +board_upload.use_1200bps_touch = yes +board_upload.wait_for_upload_port = yes +board_upload.require_upload_port = yes + +;upload_port = /dev/ttyACM0 + +build_flags = ${esp32_base.build_flags} + -DESP32_S3_PICO + ;-DPRIVATE_HW + -Ivariants/esp32-s3-pico + -DBOARD_HAS_PSRAM + -DEINK_DISPLAY_MODEL=GxEPD2_290_T94_V2 + -DEINK_WIDTH=296 + -DEINK_HEIGHT=128 + +lib_deps = ${esp32s3_base.lib_deps} + zinggjm/GxEPD2@^1.5.3 + ;adafruit/Adafruit NeoPixel@^1.10.7 diff --git a/variants/esp32-s3-pico/variant.h b/variants/esp32-s3-pico/variant.h new file mode 100644 index 000000000..87378d378 --- /dev/null +++ b/variants/esp32-s3-pico/variant.h @@ -0,0 +1,77 @@ +/* + +*/ +#define HAS_GPS 0 +#undef GPS_RX_PIN +#undef GPS_TX_PIN + +#define EXT_NOTIFY_OUT 22 +#define BUTTON_PIN 0 // 17 + +// #define LED_PIN PIN_LED +// Board has RGB LED 21 + +// The usbPower state is revered ? +// DEBUG | ??:??:?? 365 [Power] Battery: usbPower=0, isCharging=0, batMv=4116, batPct=90 +// DEBUG | ??:??:?? 385 [Power] Battery: usbPower=1, isCharging=1, batMv=4243, batPct=0 + +// https://www.waveshare.com/img/devkit/ESP32-S3-Pico/ESP32-S3-Pico-details-inter-1.jpg +// digram is incorrect labeled as battery pin is getting readings on GPIO7_CH1? +#define BATTERY_PIN 7 +#define ADC_CHANNEL ADC1_GPIO7_CHANNEL +// #define ADC_CHANNEL ADC1_GPIO6_CHANNEL +// ratio of voltage divider = 3.0 (R17=200k, R18=100k) +#define ADC_MULTIPLIER 3.1 // 3.0 + a bit for being optimistic + +#define I2C_SDA 15 +#define I2C_SCL 16 + +// Enable secondary bus for external periherals +// https://www.waveshare.com/wiki/Pico-OLED-1.3 +// #define USE_SH1107_128_64 +// Not working +#define I2C_SDA1 17 +#define I2C_SCL1 18 + +#define BUTTON_PIN 0 // This is the BOOT button +#define BUTTON_NEED_PULLUP + +// #define USE_RF95 // RFM95/SX127x +#define USE_SX1262 +// #define USE_SX1280 + +#define LORA_MISO 37 +#define LORA_SCK 35 +#define LORA_MOSI 36 +#define LORA_CS 14 + +#define LORA_RESET 40 +#define LORA_DIO1 4 +#define LORA_DIO2 13 + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#endif + +#ifdef USE_SX1280 +#define SX128X_CS LORA_CS +#define SX128X_DIO1 LORA_DIO1 +#define SX128X_BUSY 9 +#define SX128X_RESET LORA_RESET +#endif + +#define USE_EINK +/* + * eink display pins + */ +#define PIN_EINK_CS 34 +#define PIN_EINK_BUSY 38 +#define PIN_EINK_DC 33 +#define PIN_EINK_RES 42 // 37 //(-1) // cant be MISO Waveshare ??) +#define PIN_EINK_SCLK 35 +#define PIN_EINK_MOSI 36 \ No newline at end of file diff --git a/variants/feather_diy/variant.h b/variants/feather_diy/variant.h index 5e889b04e..1c0979f82 100644 --- a/variants/feather_diy/variant.h +++ b/variants/feather_diy/variant.h @@ -81,10 +81,10 @@ extern "C" { #define LORA_DIO2 (0 + 8) // P0.08 12 // BUSY for SX1262/SX1268 #define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262/SX1268, if DIO3 is high the TXCO is enabled -#define RF95_SCK SCK -#define RF95_MISO MI -#define RF95_MOSI MO -#define RF95_NSS SS +#define LORA_SCK SCK +#define LORA_MISO MI +#define LORA_MOSI MO +#define LORA_CS SS // enables 3.3V periphery like GPS or IO Module #define PIN_3V3_EN (-1) @@ -95,7 +95,7 @@ extern "C" { #define USE_SX1262 // common pinouts for SX126X modules -#define SX126X_CS RF95_NSS // NSS for SX126X +#define SX126X_CS LORA_CS // NSS for SX126X #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY LORA_DIO2 #define SX126X_RESET LORA_RESET diff --git a/variants/heltec_esp32c3/variant.h b/variants/heltec_esp32c3/variant.h index 7d113720d..6641f9d21 100644 --- a/variants/heltec_esp32c3/variant.h +++ b/variants/heltec_esp32c3/variant.h @@ -9,27 +9,27 @@ #define LED_PIN 18 // LED #define LED_INVERTED 1 -#define HAS_SCREEN 0 +#define HAS_SCREEN 1 #define HAS_GPS 0 #undef GPS_RX_PIN #undef GPS_TX_PIN -#undef RF95_SCK -#undef RF95_MISO -#undef RF95_MOSI -#undef RF95_NSS +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS #define USE_SX1262 -#define RF95_SCK 10 -#define RF95_MISO 6 -#define RF95_MOSI 7 -#define RF95_NSS 8 +#define LORA_SCK 10 +#define LORA_MISO 6 +#define LORA_MOSI 7 +#define LORA_CS 8 #define LORA_DIO0 RADIOLIB_NC #define LORA_RESET 5 #define LORA_DIO1 3 #define LORA_DIO2 RADIOLIB_NC #define LORA_BUSY 4 -#define SX126X_CS RF95_NSS +#define SX126X_CS LORA_CS #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY LORA_BUSY #define SX126X_RESET LORA_RESET diff --git a/variants/heltec_v2.1/variant.h b/variants/heltec_v2.1/variant.h index ed123efec..8ebccc54f 100644 --- a/variants/heltec_v2.1/variant.h +++ b/variants/heltec_v2.1/variant.h @@ -29,7 +29,8 @@ #define LORA_DIO1 35 // https://www.thethingsnetwork.org/forum/t/big-esp32-sx127x-topic-part-3/18436 #define LORA_DIO2 34 // Not really used -#define ADC_MULTIPLIER 3.8 +#define ADC_MULTIPLIER 3.2 // 220k + 100k (320k/100k=3.2) +// #define ADC_WIDTH ADC_WIDTH_BIT_10 #define BATTERY_PIN 37 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #define ADC_CHANNEL ADC1_GPIO37_CHANNEL diff --git a/variants/heltec_v3/variant.h b/variants/heltec_v3/variant.h index 4ce47996b..70b122f58 100644 --- a/variants/heltec_v3/variant.h +++ b/variants/heltec_v3/variant.h @@ -11,6 +11,8 @@ #define VEXT_ENABLE Vext // active low, powers the oled display and the lora antenna boost #define BUTTON_PIN 0 +#define ADC_CTRL 37 +#define ADC_CTRL_ENABLED LOW #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #define ADC_CHANNEL ADC1_GPIO1_CHANNEL #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider @@ -24,12 +26,12 @@ #define LORA_DIO2 13 // SX1262 BUSY #define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled -#define RF95_SCK 9 -#define RF95_MISO 11 -#define RF95_MOSI 10 -#define RF95_NSS 8 +#define LORA_SCK 9 +#define LORA_MISO 11 +#define LORA_MOSI 10 +#define LORA_CS 8 -#define SX126X_CS RF95_NSS +#define SX126X_CS LORA_CS #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY LORA_DIO2 #define SX126X_RESET LORA_RESET diff --git a/variants/heltec_wireless_paper/pins_arduino.h b/variants/heltec_wireless_paper/pins_arduino.h new file mode 100644 index 000000000..66d091691 --- /dev/null +++ b/variants/heltec_wireless_paper/pins_arduino.h @@ -0,0 +1,78 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define WIFI_Kit_32 true +#define DISPLAY_HEIGHT 64 +#define DISPLAY_WIDTH 128 + +#define EXTERNAL_NUM_INTERRUPTS 16 +#define NUM_DIGITAL_PINS 40 +#define NUM_ANALOG_INPUTS 16 + +#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) +#define digitalPinToInterrupt(p) (((p) < 40) ? (p) : -1) +#define digitalPinHasPWM(p) (p < 34) + +static const uint8_t LED_BUILTIN = 35; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN + +static const uint8_t KEY_BUILTIN = 0; + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 41; +static const uint8_t SCL = 42; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t Vext = 45; +static const uint8_t LED = 18; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO0 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 0bc7f14d1..d7aac5e22 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -2,7 +2,22 @@ extends = esp32s3_base board = heltec_wifi_lora_32_V3 build_flags = - ${esp32s3_base.build_flags} -D HELTEC_WIRELESS_PAPER -I variants/heltec_wireless_paper + ${esp32s3_base.build_flags} + -I variants/heltec_wireless_paper + -D HELTEC_WIRELESS_PAPER + -D EINK_DISPLAY_MODEL=GxEPD2_213_FC1 + -D EINK_WIDTH=250 + -D EINK_HEIGHT=122 + -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted + -D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates + -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates + -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated + -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. + -D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" + -D EINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight lib_deps = ${esp32s3_base.lib_deps} - zinggjm/GxEPD2@^1.5.2 + https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a + lewisxhe/PCF8563_Library@^1.0.1 +upload_speed = 115200 \ No newline at end of file diff --git a/variants/heltec_wireless_paper/variant.h b/variants/heltec_wireless_paper/variant.h index b73596f50..29b8bbbd1 100644 --- a/variants/heltec_wireless_paper/variant.h +++ b/variants/heltec_wireless_paper/variant.h @@ -5,6 +5,7 @@ #define I2C_SCL SCL #define USE_EINK + /* * eink display pins */ @@ -15,13 +16,24 @@ #define PIN_EINK_SCLK 3 #define PIN_EINK_MOSI 2 -#define VEXT_ENABLE Vext // active low, powers the oled display and the lora antenna boost +/* + * SPI interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +#define PIN_SPI_MISO 10 // MISO P0.17 +#define PIN_SPI_MOSI 11 // MOSI P0.15 +#define PIN_SPI_SCK 9 // SCK P0.13 + +#define VEXT_ENABLE 45 // active low, powers the oled display and the lora antenna boost #define BUTTON_PIN 0 -#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL -#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider -#define ADC_MULTIPLIER 4.9 +#define ADC_CTRL 19 +#define BATTERY_PIN 20 +#define ADC_CHANNEL ADC2_GPIO20_CHANNEL +#define ADC_MULTIPLIER 2 // Voltage divider is roughly 1:1 +#define BAT_MEASURE_ADC_UNIT 2 // Use ADC2 +#define ADC_ATTENUATION ADC_ATTEN_DB_11 // Voltage divider output is quite high #define USE_SX1262 @@ -31,12 +43,12 @@ #define LORA_DIO2 13 // SX1262 BUSY #define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled -#define RF95_SCK 9 -#define RF95_MISO 11 -#define RF95_MOSI 10 -#define RF95_NSS 8 +#define LORA_SCK 9 +#define LORA_MISO 11 +#define LORA_MOSI 10 +#define LORA_CS 8 -#define SX126X_CS RF95_NSS +#define SX126X_CS LORA_CS #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY LORA_DIO2 #define SX126X_RESET LORA_RESET diff --git a/variants/heltec_wireless_paper_v1/pins_arduino.h b/variants/heltec_wireless_paper_v1/pins_arduino.h new file mode 100644 index 000000000..66d091691 --- /dev/null +++ b/variants/heltec_wireless_paper_v1/pins_arduino.h @@ -0,0 +1,78 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define WIFI_Kit_32 true +#define DISPLAY_HEIGHT 64 +#define DISPLAY_WIDTH 128 + +#define EXTERNAL_NUM_INTERRUPTS 16 +#define NUM_DIGITAL_PINS 40 +#define NUM_ANALOG_INPUTS 16 + +#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) +#define digitalPinToInterrupt(p) (((p) < 40) ? (p) : -1) +#define digitalPinHasPWM(p) (p < 34) + +static const uint8_t LED_BUILTIN = 35; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN + +static const uint8_t KEY_BUILTIN = 0; + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 41; +static const uint8_t SCL = 42; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t Vext = 45; +static const uint8_t LED = 18; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO0 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_wireless_paper_v1/platformio.ini b/variants/heltec_wireless_paper_v1/platformio.ini new file mode 100644 index 000000000..999f1586a --- /dev/null +++ b/variants/heltec_wireless_paper_v1/platformio.ini @@ -0,0 +1,22 @@ +[env:heltec-wireless-paper-v1_0] +extends = esp32s3_base +board = heltec_wifi_lora_32_V3 +build_flags = + ${esp32s3_base.build_flags} + -I variants/heltec_wireless_paper_v1 + -D HELTEC_WIRELESS_PAPER_V1_0 + -D EINK_DISPLAY_MODEL=GxEPD2_213_BN + -D EINK_WIDTH=250 + -D EINK_HEIGHT=122 + -D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -D EINK_LIMIT_FASTREFRESH=5 ; How many consecutive fast-refreshes are permitted + -D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates + -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates + -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated + ;-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. + -D EINK_HASQUIRK_VICIOUSFASTREFRESH ; Identify that pixels drawn by fast-refresh are harder to clear +lib_deps = + ${esp32s3_base.lib_deps} + https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a + lewisxhe/PCF8563_Library@^1.0.1 +upload_speed = 115200 \ No newline at end of file diff --git a/variants/heltec_wireless_paper_v1/variant.h b/variants/heltec_wireless_paper_v1/variant.h new file mode 100644 index 000000000..29b8bbbd1 --- /dev/null +++ b/variants/heltec_wireless_paper_v1/variant.h @@ -0,0 +1,57 @@ +#define LED_PIN 18 + +// Enable bus for external periherals +#define I2C_SDA SDA +#define I2C_SCL SCL + +#define USE_EINK + +/* + * eink display pins + */ +#define PIN_EINK_CS 4 +#define PIN_EINK_BUSY 7 +#define PIN_EINK_DC 5 +#define PIN_EINK_RES 6 +#define PIN_EINK_SCLK 3 +#define PIN_EINK_MOSI 2 + +/* + * SPI interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +#define PIN_SPI_MISO 10 // MISO P0.17 +#define PIN_SPI_MOSI 11 // MOSI P0.15 +#define PIN_SPI_SCK 9 // SCK P0.13 + +#define VEXT_ENABLE 45 // active low, powers the oled display and the lora antenna boost +#define BUTTON_PIN 0 + +#define ADC_CTRL 19 +#define BATTERY_PIN 20 +#define ADC_CHANNEL ADC2_GPIO20_CHANNEL +#define ADC_MULTIPLIER 2 // Voltage divider is roughly 1:1 +#define BAT_MEASURE_ADC_UNIT 2 // Use ADC2 +#define ADC_ATTENUATION ADC_ATTEN_DB_11 // Voltage divider output is quite high + +#define USE_SX1262 + +#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 12 +#define LORA_DIO1 14 // SX1262 IRQ +#define LORA_DIO2 13 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled + +#define LORA_SCK 9 +#define LORA_MISO 11 +#define LORA_MOSI 10 +#define LORA_CS 8 + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET + +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 diff --git a/variants/heltec_wireless_tracker/pins_arduino.h b/variants/heltec_wireless_tracker/pins_arduino.h index e4d2631e7..5c0b529b0 100644 --- a/variants/heltec_wireless_tracker/pins_arduino.h +++ b/variants/heltec_wireless_tracker/pins_arduino.h @@ -5,8 +5,8 @@ #include #define WIFI_LoRa_32_V3 true -#define DISPLAY_HEIGHT 64 -#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 80 +#define DISPLAY_WIDTH 160 #define USB_VID 0x303a #define USB_PID 0x1001 @@ -26,8 +26,8 @@ static const uint8_t LED_BUILTIN = 18; static const uint8_t TX = 43; static const uint8_t RX = 44; -static const uint8_t SDA = 41; -static const uint8_t SCL = 42; +static const uint8_t SDA = 45; +static const uint8_t SCL = 46; static const uint8_t SS = 8; static const uint8_t MOSI = 10; diff --git a/variants/heltec_wireless_tracker/platformio.ini b/variants/heltec_wireless_tracker/platformio.ini index fa79eeb6a..3259d563c 100644 --- a/variants/heltec_wireless_tracker/platformio.ini +++ b/variants/heltec_wireless_tracker/platformio.ini @@ -5,7 +5,9 @@ upload_protocol = esp-builtin build_flags = ${esp32s3_base.build_flags} -I variants/heltec_wireless_tracker - -DGPS_POWER_TOGGLE + -D HELTEC_TRACKER_V1_1 + -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output lib_deps = ${esp32s3_base.lib_deps} diff --git a/variants/heltec_wireless_tracker/variant.h b/variants/heltec_wireless_tracker/variant.h index 4a1b61038..167345e1a 100644 --- a/variants/heltec_wireless_tracker/variant.h +++ b/variants/heltec_wireless_tracker/variant.h @@ -1,5 +1,11 @@ #define LED_PIN 18 +#define HELTEC_TRACKER_V1_X + +// I2C +#define I2C_SDA SDA +#define I2C_SCL SCL + // ST7735S TFT LCD #define ST7735S 1 // there are different (sub-)versions of ST7735 #define ST7735_CS 38 @@ -9,25 +15,19 @@ #define ST7735_RESET 39 #define ST7735_MISO -1 #define ST7735_BUSY -1 -#define ST7735_BL_V03 45 #define ST7735_BL_V05 21 /* V1.1 PCB marking */ #define ST7735_SPI_HOST SPI3_HOST -#define ST7735_BACKLIGHT_EN_V03 45 -#define ST7735_BACKLIGHT_EN_V05 21 #define SPI_FREQUENCY 40000000 #define SPI_READ_FREQUENCY 16000000 #define SCREEN_ROTATE -#define TFT_HEIGHT 160 -#define TFT_WIDTH 80 +#define TFT_HEIGHT DISPLAY_WIDTH +#define TFT_WIDTH DISPLAY_HEIGHT #define TFT_OFFSET_X 26 -#define TFT_OFFSET_Y 0 -#define VTFT_CTRL_V03 46 // Heltec Tracker needs this pulled low for TFT -#define VTFT_CTRL_V05 -1 -#define SCREEN_TRANSITION_FRAMERATE 1 // fps +#define TFT_OFFSET_Y -1 +#define SCREEN_TRANSITION_FRAMERATE 3 // fps #define DISPLAY_FORCE_SMALL_FONTS -#define VEXT_ENABLE_V03 Vext // active low, powers the oled display and the lora antenna boost -#define VEXT_ENABLE_V05 3 // active HIGH, powers the oled display +#define VEXT_ENABLE_V05 3 // active HIGH, powers the lora antenna boost #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage @@ -35,6 +35,7 @@ #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 #define ADC_CTRL 2 // active HIGH, powers the voltage divider. Only on 1.1 +#define ADC_CTRL_ENABLED HIGH #undef GPS_RX_PIN #undef GPS_TX_PIN @@ -43,11 +44,6 @@ #define PIN_GPS_RESET 35 #define PIN_GPS_PPS 36 -#define VGNSS_CTRL_V03 37 // Heltec Tracker needs this pulled low for GPS -#define VGNSS_CTRL_V05 -1 // Heltec Tracker needs this pulled low for GPS -#define PIN_GPS_EN VGNSS_CTRL_V03 -#define GPS_EN_ACTIVE LOW - #define GPS_RESET_MODE LOW #define GPS_UC6580 @@ -58,12 +54,12 @@ #define LORA_DIO2 13 // SX1262 BUSY #define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled -#define RF95_SCK 9 -#define RF95_MISO 11 -#define RF95_MOSI 10 -#define RF95_NSS 8 +#define LORA_SCK 9 +#define LORA_MISO 11 +#define LORA_MOSI 10 +#define LORA_CS 8 -#define SX126X_CS RF95_NSS +#define SX126X_CS LORA_CS #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY LORA_DIO2 #define SX126X_RESET LORA_RESET diff --git a/variants/heltec_wireless_tracker_V1_0/pins_arduino.h b/variants/heltec_wireless_tracker_V1_0/pins_arduino.h new file mode 100644 index 000000000..f72c7661a --- /dev/null +++ b/variants/heltec_wireless_tracker_V1_0/pins_arduino.h @@ -0,0 +1,80 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include "soc/soc_caps.h" +#include + +#define WIFI_LoRa_32_V3 true +#define DISPLAY_HEIGHT 80 +#define DISPLAY_WIDTH 160 + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +#define EXTERNAL_NUM_INTERRUPTS 46 +#define NUM_DIGITAL_PINS 48 +#define NUM_ANALOG_INPUTS 20 + +static const uint8_t LED_BUILTIN = 18; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN + +#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) +#define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1) +#define digitalPinHasPWM(p) (p < 46) + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 5; +static const uint8_t SCL = 6; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t Vext = 36; +static const uint8_t LED = 18; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO0 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_wireless_tracker_V1_0/platformio.ini b/variants/heltec_wireless_tracker_V1_0/platformio.ini new file mode 100644 index 000000000..303e27dbf --- /dev/null +++ b/variants/heltec_wireless_tracker_V1_0/platformio.ini @@ -0,0 +1,14 @@ +[env:heltec-wireless-tracker-V1-0] +extends = esp32s3_base +board = heltec_wireless_tracker +upload_protocol = esptool + +build_flags = + ${esp32s3_base.build_flags} -I variants/heltec_wireless_tracker_V1_0 + -D HELTEC_TRACKER_V1_0 + -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output + +lib_deps = + ${esp32s3_base.lib_deps} + lovyan03/LovyanGFX@^1.1.8 \ No newline at end of file diff --git a/variants/heltec_wireless_tracker_V1_0/variant.h b/variants/heltec_wireless_tracker_V1_0/variant.h new file mode 100644 index 000000000..84e77a6b9 --- /dev/null +++ b/variants/heltec_wireless_tracker_V1_0/variant.h @@ -0,0 +1,71 @@ +#define LED_PIN 18 + +#define HELTEC_TRACKER_V1_X + +// I2C +#define I2C_SDA SDA +#define I2C_SCL SCL + +// ST7735S TFT LCD +#define ST7735S 1 // there are different (sub-)versions of ST7735 +#define ST7735_CS 38 +#define ST7735_RS 40 // DC +#define ST7735_SDA 42 // MOSI +#define ST7735_SCK 41 +#define ST7735_RESET 39 +#define ST7735_MISO -1 +#define ST7735_BUSY -1 +#define ST7735_BL_V03 45 +#define ST7735_SPI_HOST SPI3_HOST +#define SPI_FREQUENCY 40000000 +#define SPI_READ_FREQUENCY 16000000 +#define SCREEN_ROTATE +#define TFT_HEIGHT DISPLAY_WIDTH +#define TFT_WIDTH DISPLAY_HEIGHT +#define TFT_OFFSET_X 26 +#define TFT_OFFSET_Y -1 +#define VTFT_CTRL_V03 46 // Heltec Tracker needs this pulled low for TFT +#define SCREEN_TRANSITION_FRAMERATE 3 // fps +#define DISPLAY_FORCE_SMALL_FONTS + +#define VEXT_ENABLE_V03 Vext // active low, powers the oled display and the lora antenna boost +#define BUTTON_PIN 0 + +#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider +#define ADC_MULTIPLIER 4.9 + +#undef GPS_RX_PIN +#undef GPS_TX_PIN +#define GPS_RX_PIN 33 +#define GPS_TX_PIN 34 +#define PIN_GPS_RESET 35 +#define PIN_GPS_PPS 36 + +#define VGNSS_CTRL_V03 37 // Heltec Tracker needs this pulled low for GPS +#define PIN_GPS_EN VGNSS_CTRL_V03 +#define GPS_EN_ACTIVE LOW + +#define GPS_RESET_MODE LOW +#define GPS_UC6580 + +#define USE_SX1262 +#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 12 +#define LORA_DIO1 14 // SX1262 IRQ +#define LORA_DIO2 13 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled + +#define LORA_SCK 9 +#define LORA_MISO 11 +#define LORA_MOSI 10 +#define LORA_CS 8 + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET + +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 diff --git a/variants/heltec_wsl_v3/variant.h b/variants/heltec_wsl_v3/variant.h index 417abf34d..d3a009ade 100644 --- a/variants/heltec_wsl_v3/variant.h +++ b/variants/heltec_wsl_v3/variant.h @@ -8,6 +8,8 @@ #define VEXT_ENABLE Vext // active low, powers the oled display and the lora antenna boost #define BUTTON_PIN 0 +#define ADC_CTRL 37 +#define ADC_CTRL_ENABLED LOW #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #define ADC_CHANNEL ADC1_GPIO1_CHANNEL #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider @@ -21,12 +23,12 @@ #define LORA_DIO2 13 // SX1262 BUSY #define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled -#define RF95_SCK 9 -#define RF95_MISO 11 -#define RF95_MOSI 10 -#define RF95_NSS 8 +#define LORA_SCK 9 +#define LORA_MISO 11 +#define LORA_MOSI 10 +#define LORA_CS 8 -#define SX126X_CS RF95_NSS +#define SX126X_CS LORA_CS #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY LORA_DIO2 #define SX126X_RESET LORA_RESET diff --git a/variants/lora_relay_v1/variant.h b/variants/lora_relay_v1/variant.h index b310223d7..9cfb69337 100644 --- a/variants/lora_relay_v1/variant.h +++ b/variants/lora_relay_v1/variant.h @@ -80,6 +80,7 @@ static const uint8_t A5 = PIN_A5; // Other pins #define PIN_AREF PIN_A5 #define PIN_VBAT PIN_A4 +#define BATTERY_PIN PIN_VBAT #define PIN_NFC1 (33) #define PIN_NFC2 (2) #define PIN_PIEZO (37) diff --git a/variants/lora_relay_v2/variant.h b/variants/lora_relay_v2/variant.h index 172da17f7..3afe8620e 100644 --- a/variants/lora_relay_v2/variant.h +++ b/variants/lora_relay_v2/variant.h @@ -100,6 +100,7 @@ static const uint8_t A5 = PIN_A5; // Other pins #define PIN_AREF PIN_A5 #define PIN_VBAT PIN_A4 +#define BATTERY_PIN PIN_VBAT #define PIN_NFC1 (33) #define PIN_NFC2 (2) #define PIN_PIEZO (37) diff --git a/variants/m5stack-stamp-c3/variant.h b/variants/m5stack-stamp-c3/variant.h index 87adbc226..8242ef499 100644 --- a/variants/m5stack-stamp-c3/variant.h +++ b/variants/m5stack-stamp-c3/variant.h @@ -9,18 +9,18 @@ #undef GPS_RX_PIN #undef GPS_TX_PIN -#undef RF95_SCK -#undef RF95_MISO -#undef RF95_MOSI -#undef RF95_NSS +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS // Adafruit RFM95W OK // https://www.adafruit.com/product/3072 #define USE_RF95 -#define RF95_SCK 4 -#define RF95_MISO 5 -#define RF95_MOSI 6 -#define RF95_NSS 7 +#define LORA_SCK 4 +#define LORA_MISO 5 +#define LORA_MOSI 6 +#define LORA_CS 7 #define LORA_DIO0 10 #define LORA_RESET 8 #define LORA_DIO1 RADIOLIB_NC @@ -29,16 +29,16 @@ // WaveShare Core1262-868M OK // https://www.waveshare.com/wiki/Core1262-868M // #define USE_SX1262 -// #define RF95_SCK 4 -// #define RF95_MISO 5 -// #define RF95_MOSI 6 -// #define RF95_NSS 7 +// #define LORA_SCK 4 +// #define LORA_MISO 5 +// #define LORA_MOSI 6 +// #define LORA_CS 7 // #define LORA_DIO0 RADIOLIB_NC // #define LORA_RESET 8 // #define LORA_DIO1 10 // #define LORA_DIO2 RADIOLIB_NC // #define LORA_BUSY 18 -// #define SX126X_CS RF95_NSS +// #define SX126X_CS LORA_CS // #define SX126X_DIO1 LORA_DIO1 // #define SX126X_BUSY LORA_BUSY // #define SX126X_RESET LORA_RESET @@ -47,16 +47,16 @@ // SX128X 2.4 Ghz LoRa module Not OK - RadioLib issue ? still to confirm // #define USE_SX1280 -// #define RF95_SCK 4 -// #define RF95_MISO 5 -// #define RF95_MOSI 6 -// #define RF95_NSS 7 +// #define LORA_SCK 4 +// #define LORA_MISO 5 +// #define LORA_MOSI 6 +// #define LORA_CS 7 // #define LORA_DIO0 -1 // #define LORA_DIO1 10 // #define LORA_DIO2 21 // #define LORA_RESET 8 // #define LORA_BUSY 1 -// #define SX128X_CS RF95_NSS +// #define SX128X_CS LORA_CS // #define SX128X_DIO1 LORA_DIO1 // #define SX128X_BUSY LORA_BUSY // #define SX128X_RESET LORA_RESET diff --git a/variants/m5stack_core/variant.h b/variants/m5stack_core/variant.h index c671d77fa..72aeb160e 100644 --- a/variants/m5stack_core/variant.h +++ b/variants/m5stack_core/variant.h @@ -12,15 +12,15 @@ #define PIN_BUZZER 25 -#undef RF95_SCK -#undef RF95_MISO -#undef RF95_MOSI -#undef RF95_NSS +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS -#define RF95_SCK 18 -#define RF95_MISO 19 -#define RF95_MOSI 23 -#define RF95_NSS 5 +#define LORA_SCK 18 +#define LORA_MISO 19 +#define LORA_MOSI 23 +#define LORA_CS 5 #define USE_RF95 #define LORA_DIO0 36 // a No connect on the SX1262 module diff --git a/variants/m5stack_coreink/platformio.ini b/variants/m5stack_coreink/platformio.ini index e6aef380c..dfb078a0a 100644 --- a/variants/m5stack_coreink/platformio.ini +++ b/variants/m5stack_coreink/platformio.ini @@ -9,18 +9,19 @@ build_flags = ;-D RADIOLIB_VERBOSE -Ofast -D__MCUXPRESSO - -DEPD_HEIGHT=200 - -DEPD_WIDTH=200 + -DEINK_DISPLAY_MODEL=GxEPD2_154_M09 + -DEINK_WIDTH=200 + -DEINK_HEIGHT=200 -DUSER_SETUP_LOADED -DM5_COREINK -DM5STACK lib_deps = ${esp32_base.lib_deps} - zinggjm/GxEPD2@^1.4.9 + zinggjm/GxEPD2@^1.5.3 lewisxhe/PCF8563_Library@^1.0.1 lib_ignore = m5stack-coreink monitor_filters = esp32_exception_decoder board_build.f_cpu = 240000000L upload_protocol = esptool -;upload_port = /dev/ttyACM0 +upload_port = /dev/ttyACM0 diff --git a/variants/m5stack_coreink/variant.h b/variants/m5stack_coreink/variant.h index 90ce41334..f19da2696 100644 --- a/variants/m5stack_coreink/variant.h +++ b/variants/m5stack_coreink/variant.h @@ -2,16 +2,12 @@ #define I2C_SDA 21 #define I2C_SCL 22 -// 7-07-2023 Or enable Secondary I2C Bus -// #define I2C_SDA1 32 -// #define I2C_SCL1 33 - #define HAS_GPS 1 #undef GPS_RX_PIN #undef GPS_TX_PIN // Use Secondary I2C Bus as GPS Serial #define GPS_RX_PIN 33 -#define GPS_TX_PIN 32 +// #define GPS_TX_PIN 32 (now used by SX1262 BUSY as GPS works with just RX) // Green LED #define LED_INVERTED 0 @@ -34,33 +30,52 @@ // BUZZER #define PIN_BUZZER 2 -#undef RF95_SCK -#undef RF95_MISO -#undef RF95_MOSI -#undef RF95_NSS +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS + #define USE_RF95 +// #define USE_SX1262 // #define USE_SX1280 #ifdef USE_RF95 -#define RF95_SCK 18 -#define RF95_MISO 34 -#define RF95_MOSI 23 -#define RF95_NSS 14 +#define LORA_SCK 18 +#define LORA_MISO 34 +#define LORA_MOSI 23 +#define LORA_CS 14 #define LORA_DIO0 25 #define LORA_RESET 26 #define LORA_DIO1 RADIOLIB_NC #define LORA_DIO2 RADIOLIB_NC #endif +// https://www.waveshare.com/core1262-868m.htm +#ifdef USE_SX1262 +#define LORA_SCK 18 +#define LORA_MISO 34 +#define LORA_MOSI 23 +#define LORA_CS 14 +#define LORA_RESET 26 +#define LORA_DIO1 25 +#define LORA_DIO2 32 // 33 // (13 not working) //BUSY pin on SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#endif + #ifdef USE_SX1280 -#define RF95_SCK 18 -#define RF95_MISO 34 -#define RF95_MOSI 23 -#define RF95_NSS 14 +#define LORA_SCK 18 +#define LORA_MISO 34 +#define LORA_MOSI 23 +#define LORA_CS 14 #define LORA_RESET 26 #define LORA_DIO1 25 #define LORA_DIO2 13 -#define SX128X_CS RF95_NSS +#define SX128X_CS LORA_CS #define SX128X_DIO1 LORA_DIO1 #define SX128X_BUSY LORA_DIO2 #define SX128X_RESET LORA_RESET @@ -90,5 +105,5 @@ // | // GND // https://github.com/m5stack/M5Core-Ink/blob/master/examples/Basics/FactoryTest/FactoryTest.ino#L58 -#define ADC_MULTIPLIER 5 // Just a guess for now... more detailed getBatVoltage above +#define ADC_MULTIPLIER 5 // https://embeddedexplorer.com/esp32-adc-esp-idf-tutorial/ diff --git a/variants/monteops_hw1/variant.h b/variants/monteops_hw1/variant.h index f7df0688b..97536b169 100644 --- a/variants/monteops_hw1/variant.h +++ b/variants/monteops_hw1/variant.h @@ -208,19 +208,10 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG // and has 12 bit resolution #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) -#define VBAT_DIVIDER (0.4F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (1.73) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +#define ADC_MULTIPLIER (1.73F) // #define HAS_RTC 1 @@ -239,4 +230,4 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif +#endif \ No newline at end of file diff --git a/variants/my_esp32s3_diy_eink/platformio.ini b/variants/my_esp32s3_diy_eink/platformio.ini index d3c55afda..966bc580e 100644 --- a/variants/my_esp32s3_diy_eink/platformio.ini +++ b/variants/my_esp32s3_diy_eink/platformio.ini @@ -19,9 +19,9 @@ build_flags = ;${esp32_base.build_flags} -D MY_ESP32S3_DIY -I variants/my_esp32s3_diy_eink ${esp32_base.build_flags} -D PRIVATE_HW -I variants/my_esp32s3_diy_eink -Dmy - -DTECHO_DISPLAY_MODEL=GxEPD2_290_T5D - -DEPD_HEIGHT=128 - -DEPD_WIDTH=296 + -DEINK_DISPLAY_MODEL=GxEPD2_290_T5D + -DEINK_WIDTH=296 + -DEINK_HEIGHT=128 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DARDUINO_USB_MODE=0 diff --git a/variants/my_esp32s3_diy_eink/variant.h b/variants/my_esp32s3_diy_eink/variant.h index 7e4fe2756..516fa7f34 100644 --- a/variants/my_esp32s3_diy_eink/variant.h +++ b/variants/my_esp32s3_diy_eink/variant.h @@ -20,16 +20,16 @@ // #define USE_SX1262 #define USE_SX1280 -#define RF95_MISO 3 -#define RF95_SCK 5 -#define RF95_MOSI 6 -#define RF95_NSS 7 +#define LORA_MISO 3 +#define LORA_SCK 5 +#define LORA_MOSI 6 +#define LORA_CS 7 #define LORA_RESET 8 #define LORA_DIO1 16 #ifdef USE_SX1262 -#define SX126X_CS RF95_NSS // FIXME - we really should define LORA_CS instead +#define SX126X_CS LORA_CS // FIXME - we really should define LORA_CS instead #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY 15 #define SX126X_RESET LORA_RESET @@ -38,7 +38,7 @@ #endif #ifdef USE_SX1280 -#define SX128X_CS RF95_NSS +#define SX128X_CS LORA_CS #define SX128X_DIO1 LORA_DIO1 #define SX128X_BUSY 15 #define SX128X_RESET LORA_RESET @@ -53,4 +53,4 @@ #define PIN_EINK_DC 1 #define PIN_EINK_RES (-1) #define PIN_EINK_SCLK 5 -#define PIN_EINK_MOSI 6 +#define PIN_EINK_MOSI 6 \ No newline at end of file diff --git a/variants/my_esp32s3_diy_oled/variant.h b/variants/my_esp32s3_diy_oled/variant.h index bb9657c5b..6dd18c236 100644 --- a/variants/my_esp32s3_diy_oled/variant.h +++ b/variants/my_esp32s3_diy_oled/variant.h @@ -20,16 +20,16 @@ // #define USE_SX1262 #define USE_SX1280 -#define RF95_MISO 3 -#define RF95_SCK 5 -#define RF95_MOSI 6 -#define RF95_NSS 7 +#define LORA_MISO 3 +#define LORA_SCK 5 +#define LORA_MOSI 6 +#define LORA_CS 7 #define LORA_RESET 8 #define LORA_DIO1 16 #ifdef USE_SX1262 -#define SX126X_CS RF95_NSS // FIXME - we really should define LORA_CS instead +#define SX126X_CS LORA_CS // FIXME - we really should define LORA_CS instead #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY 15 #define SX126X_RESET LORA_RESET @@ -38,7 +38,7 @@ #endif #ifdef USE_SX1280 -#define SX128X_CS RF95_NSS +#define SX128X_CS LORA_CS #define SX128X_DIO1 LORA_DIO1 #define SX128X_BUSY 15 #define SX128X_RESET LORA_RESET diff --git a/variants/nano-g1-explorer/variant.h b/variants/nano-g1-explorer/variant.h index 71dd49f05..3d5d71acc 100644 --- a/variants/nano-g1-explorer/variant.h +++ b/variants/nano-g1-explorer/variant.h @@ -23,7 +23,7 @@ #define LORA_DIO3 // Not connected on PCB #ifdef USE_SX1262 -#define SX126X_CS RF95_NSS // FIXME - we really should define LORA_CS instead +#define SX126X_CS LORA_CS // FIXME - we really should define LORA_CS instead #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY LORA_DIO2 #define SX126X_RESET LORA_RESET diff --git a/variants/nano-g1/variant.h b/variants/nano-g1/variant.h index 5ceb96f0c..dd8355492 100644 --- a/variants/nano-g1/variant.h +++ b/variants/nano-g1/variant.h @@ -23,7 +23,7 @@ #define LORA_DIO3 // Not connected on PCB #ifdef USE_SX1262 -#define SX126X_CS RF95_NSS // FIXME - we really should define LORA_CS instead +#define SX126X_CS LORA_CS // FIXME - we really should define LORA_CS instead #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY LORA_DIO2 #define SX126X_RESET LORA_RESET diff --git a/variants/nano-g2-ultra/platformio.ini b/variants/nano-g2-ultra/platformio.ini index d5e5a6137..2b011e032 100644 --- a/variants/nano-g2-ultra/platformio.ini +++ b/variants/nano-g2-ultra/platformio.ini @@ -9,6 +9,5 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/nano-g2-ultra -D NANO_G2_U build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nano-g2-ultra> lib_deps = ${nrf52840_base.lib_deps} - adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 ;upload_protocol = fs diff --git a/variants/nano-g2-ultra/variant.h b/variants/nano-g2-ultra/variant.h index d69235fd4..4d8aa5784 100644 --- a/variants/nano-g2-ultra/variant.h +++ b/variants/nano-g2-ultra/variant.h @@ -165,19 +165,10 @@ External serial flash W25Q16JV_IQ // and has 12 bit resolution #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 100K + 100K voltage divider on VBAT = (100K / (100K + 100K)) -#define VBAT_DIVIDER (0.5F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (2.0) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +#define ADC_MULTIPLIER (2.0F) #define HAS_RTC 1 @@ -195,4 +186,4 @@ External serial flash W25Q16JV_IQ * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif +#endif \ No newline at end of file diff --git a/variants/picomputer-s3/variant.h b/variants/picomputer-s3/variant.h index 732be4bcf..fc746c599 100644 --- a/variants/picomputer-s3/variant.h +++ b/variants/picomputer-s3/variant.h @@ -15,10 +15,10 @@ #define USE_RF95 // RFM95/SX127x -#define RF95_SCK SCK // 21 -#define RF95_MISO MISO // 39 -#define RF95_MOSI MOSI // 38 -#define RF95_NSS SS // 40 +#define LORA_SCK SCK // 21 +#define LORA_MISO MISO // 39 +#define LORA_MOSI MOSI // 38 +#define LORA_CS SS // 40 #define LORA_RESET RADIOLIB_NC // per SX1276_Receive_Interrupt/utilities.h diff --git a/variants/portduino/platformio.ini b/variants/portduino/platformio.ini index 5e9428d4e..46417e388 100644 --- a/variants/portduino/platformio.ini +++ b/variants/portduino/platformio.ini @@ -1,22 +1,10 @@ [env:native] extends = portduino_base -build_flags = ${portduino_base.build_flags} -O0 -I variants/portduino +; The pkg-config commands below optionally add link flags. +; the || : is just a "or run the null command" to avoid returning an error code +build_flags = ${portduino_base.build_flags} -O0 -I variants/portduino -I /usr/include + !pkg-config --libs libulfius --silence-errors || : + !pkg-config --libs openssl --silence-errors || : board = cross_platform lib_deps = ${portduino_base.lib_deps} build_src_filter = ${portduino_base.build_src_filter} - -; The Portduino based sim environment on top of a linux OS and touching linux hardware devices -[env:linux] -extends = portduino_base -build_flags = ${portduino_base.build_flags} -O0 -lgpiod -I variants/portduino -board = linux_hardware -lib_deps = ${portduino_base.lib_deps} -build_src_filter = ${portduino_base.build_src_filter} - -; The Raspberry Pi actually has accessible SPI and GPIO, so we can support real hardware there. -[env:raspbian] -extends = portduino_base -build_flags = ${portduino_base.build_flags} -O0 -lgpiod -I variants/portduino -DARCH_RASPBERRY_PI -lpigpio -lyaml-cpp -board = linux_arm -lib_deps = ${portduino_base.lib_deps} -build_src_filter = ${portduino_base.build_src_filter} \ No newline at end of file diff --git a/variants/portduino/variant.h b/variants/portduino/variant.h index 46f7d1f0c..414a3fa56 100644 --- a/variants/portduino/variant.h +++ b/variants/portduino/variant.h @@ -1,31 +1,5 @@ -#if defined(ARCH_RASPBERRY_PI) -#define NO_SCREEN - -#else // Pine64 mode. - -// Pine64 uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if -// not found then probe for SX1262. Currently the RF95 code is disabled because I think the RF95 module won't need to ship. -// #define USE_RF95 -#define USE_SX1262 - -// Fake SPI device selections -#define RF95_SCK 5 -#define RF95_MISO 19 -#define RF95_MOSI 27 -#define RF95_NSS RADIOLIB_NC // the ch341f spi controller does CS for us - -#define LORA_DIO0 26 // a No connect on the SX1262 module -#define LORA_RESET 14 -#define LORA_DIO1 33 // SX1262 IRQ, called DIO0 on pinelora schematic, pin 7 on ch341f "ack" - FIXME, enable hwints in linux -#define LORA_DIO2 32 // SX1262 BUSY, actually connected to "DIO5" on pinelora schematic, pin 8 on ch341f "slct" -#define LORA_DIO3 RADIOLIB_NC // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled - -#ifdef USE_SX1262 -#define SX126X_CS 20 // CS0 on pinelora schematic, hooked to gpio D0 on ch341f -#define SX126X_DIO1 LORA_DIO1 -#define SX126X_BUSY LORA_DIO2 -#define SX126X_RESET LORA_RESET -#define SX126X_DIO2_AS_RF_SWITCH -#endif - -#endif \ No newline at end of file +#define HAS_SCREEN 1 +#define CANNED_MESSAGE_MODULE_ENABLE 1 +#define HAS_GPS 1 +#define MAX_NUM_NODES settingsMap[maxnodes] +#define RADIOLIB_GODMODE 1 diff --git a/variants/rak10701/platformio.ini b/variants/rak10701/platformio.ini index 736329eb8..37f785e84 100644 --- a/variants/rak10701/platformio.ini +++ b/variants/rak10701/platformio.ini @@ -5,6 +5,9 @@ board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} -Ivariants/rak10701 -D RAK_4631 -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + -DEINK_DISPLAY_MODEL=GxEPD2_213_BN + -DEINK_WIDTH=250 + -DEINK_HEIGHT=122 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak10701> + + + lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/rak10701/variant.h b/variants/rak10701/variant.h index 5ff12a7de..076504c16 100644 --- a/variants/rak10701/variant.h +++ b/variants/rak10701/variant.h @@ -133,10 +133,6 @@ static const uint8_t SCK = PIN_SPI_SCK; #define PIN_EINK_SCLK (0 + 3) #define PIN_EINK_MOSI (0 + 30) // also called SDI -// Controls power for the eink display - Board power is enabled either by VBUS from USB or the CPU asserting PWR_ON -// FIXME - I think this is actually just the board power enable - it enables power to the CPU also -// #define PIN_EINK_PWR_ON (-1) - // #define USE_EINK // RAKRGB @@ -202,13 +198,13 @@ static const uint8_t WB_SPI_MOSI = 30; // IO_SLOT /* Setup of the SX1262 LoRa module ( https://docs.rakwireless.com/Product-Categories/WisBlock/RAK4631/Datasheet/ ) -P1.10 NSS SPI NSS (Arduino GPIO number 42) -P1.11 SCK SPI CLK (Arduino GPIO number 43) -P1.12 MOSI SPI MOSI (Arduino GPIO number 44) -P1.13 MISO SPI MISO (Arduino GPIO number 45) -P1.14 BUSY BUSY signal (Arduino GPIO number 46) -P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) -P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) +P1.10 NSS SPI NSS (Arduino GPIO number 42) +P1.11 SCK SPI CLK (Arduino GPIO number 43) +P1.12 MOSI SPI MOSI (Arduino GPIO number 44) +P1.13 MISO SPI MISO (Arduino GPIO number 45) +P1.14 BUSY BUSY signal (Arduino GPIO number 46) +P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) +P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) Important for successful SX1262 initialization: @@ -268,19 +264,10 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG // and has 12 bit resolution #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) -#define VBAT_DIVIDER (0.4F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (1.73) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +#define ADC_MULTIPLIER (1.73F) #define HAS_RTC 1 @@ -329,4 +316,4 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif +#endif \ No newline at end of file diff --git a/variants/rak11200/variant.h b/variants/rak11200/variant.h index b6d9c4229..3399594e5 100644 --- a/variants/rak11200/variant.h +++ b/variants/rak11200/variant.h @@ -56,6 +56,8 @@ static const uint8_t SCK = 33; #define LED_PIN LED_BLUE #define PIN_VBAT WB_A0 +#define BATTERY_PIN PIN_VBAT +#define ADC_CHANNEL ADC1_GPIO36_CHANNEL // https://docs.rakwireless.com/Product-Categories/WisBlock/RAK13300/ @@ -66,14 +68,14 @@ static const uint8_t SCK = 33; #define LORA_DIO3 \ RADIOLIB_NC // Not connected on PCB, but internally on the TTGO SX1262/SX1268, if DIO3 is high the TXCO is enabled -#undef RF95_SCK -#define RF95_SCK SCK -#undef RF95_MISO -#define RF95_MISO MISO -#undef RF95_MOSI -#define RF95_MOSI MOSI -#undef RF95_NSS -#define RF95_NSS SS +#undef LORA_SCK +#define LORA_SCK SCK +#undef LORA_MISO +#define LORA_MISO MISO +#undef LORA_MOSI +#define LORA_MOSI MOSI +#undef LORA_CS +#define LORA_CS SS #define USE_SX1262 #define SX126X_CS SS // NSS for SX126X diff --git a/variants/rak11310/platformio.ini b/variants/rak11310/platformio.ini index a69b18c1a..6495278bf 100644 --- a/variants/rak11310/platformio.ini +++ b/variants/rak11310/platformio.ini @@ -10,4 +10,6 @@ build_flags = ${rp2040_base.build_flags} -DDEBUG_RP2040_PORT=Serial -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m0plus" lib_deps = - ${rp2040_base.lib_deps} \ No newline at end of file + ${rp2040_base.lib_deps} +debug_build_flags = ${rp2040_base.build_flags} +debug_tool = cmsis-dap ; for e.g. Picotool \ No newline at end of file diff --git a/variants/rak11310/variant.h b/variants/rak11310/variant.h index acc21ce99..ba3d4fed7 100644 --- a/variants/rak11310/variant.h +++ b/variants/rak11310/variant.h @@ -12,6 +12,7 @@ // #define EXT_NOTIFY_OUT 4 #define BATTERY_PIN 26 +#define BATTERY_SENSE_RESOLUTION_BITS ADC_RESOLUTION // ratio of voltage divider = 3.0 (R17=200k, R18=100k) #define ADC_MULTIPLIER 3.1 // 3.0 + a bit for being optimistic @@ -19,17 +20,17 @@ #define USE_SX1262 -#undef RF95_SCK -#undef RF95_MISO -#undef RF95_MOSI -#undef RF95_NSS +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS // RAK BSP somehow uses SPI1 instead of SPI0 #define HW_SPI1_DEVICE -#define RF95_SCK PIN_SPI0_SCK -#define RF95_MOSI PIN_SPI0_MOSI -#define RF95_MISO PIN_SPI0_MISO -#define RF95_NSS PIN_SPI0_SS +#define LORA_SCK PIN_SPI0_SCK +#define LORA_MOSI PIN_SPI0_MOSI +#define LORA_MISO PIN_SPI0_MISO +#define LORA_CS PIN_SPI0_SS #define LORA_DIO0 RADIOLIB_NC #define LORA_RESET 14 @@ -38,7 +39,7 @@ #define LORA_DIO3 RADIOLIB_NC #ifdef USE_SX1262 -#define SX126X_CS RF95_NSS +#define SX126X_CS LORA_CS #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY LORA_DIO2 #define SX126X_RESET LORA_RESET diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index b9789166f..b1bc2d9b5 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -5,6 +5,9 @@ board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631 -D RAK_4631 -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + -DEINK_DISPLAY_MODEL=GxEPD2_213_BN + -DEINK_WIDTH=250 + -DEINK_HEIGHT=122 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631> + + + lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/rak4631/variant.h b/variants/rak4631/variant.h index 956bcd772..bc5541336 100644 --- a/variants/rak4631/variant.h +++ b/variants/rak4631/variant.h @@ -133,10 +133,6 @@ static const uint8_t SCK = PIN_SPI_SCK; #define PIN_EINK_SCLK (0 + 3) #define PIN_EINK_MOSI (0 + 30) // also called SDI -// Controls power for the eink display - Board power is enabled either by VBUS from USB or the CPU asserting PWR_ON -// FIXME - I think this is actually just the board power enable - it enables power to the CPU also -// #define PIN_EINK_PWR_ON (-1) - // #define USE_EINK // RAKRGB @@ -181,13 +177,13 @@ static const uint8_t SCK = PIN_SPI_SCK; /* Setup of the SX1262 LoRa module ( https://docs.rakwireless.com/Product-Categories/WisBlock/RAK4631/Datasheet/ ) -P1.10 NSS SPI NSS (Arduino GPIO number 42) -P1.11 SCK SPI CLK (Arduino GPIO number 43) -P1.12 MOSI SPI MOSI (Arduino GPIO number 44) -P1.13 MISO SPI MISO (Arduino GPIO number 45) -P1.14 BUSY BUSY signal (Arduino GPIO number 46) -P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) -P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) +P1.10 NSS SPI NSS (Arduino GPIO number 42) +P1.11 SCK SPI CLK (Arduino GPIO number 43) +P1.12 MOSI SPI MOSI (Arduino GPIO number 44) +P1.13 MISO SPI MISO (Arduino GPIO number 45) +P1.14 BUSY BUSY signal (Arduino GPIO number 46) +P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) +P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) Important for successful SX1262 initialization: @@ -219,6 +215,7 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define NRF_APM // enables 3.3V periphery like GPS or IO Module +// Do not toggle this for GPS power savings #define PIN_3V3_EN (34) // RAK1910 GPS module @@ -249,19 +246,10 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG // and has 12 bit resolution #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) -#define VBAT_DIVIDER (0.4F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (1.73) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +#define ADC_MULTIPLIER 1.73 #define HAS_RTC 1 diff --git a/variants/rak4631_epaper/platformio.ini b/variants/rak4631_epaper/platformio.ini index 0871eee45..ced732d94 100644 --- a/variants/rak4631_epaper/platformio.ini +++ b/variants/rak4631_epaper/platformio.ini @@ -4,6 +4,9 @@ extends = nrf52840_base board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_epaper -D RAK_4631 -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -DEINK_DISPLAY_MODEL=GxEPD2_213_BN + -DEINK_WIDTH=250 + -DEINK_HEIGHT=122 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_epaper> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/rak4631_epaper/variant.h b/variants/rak4631_epaper/variant.h index bc2eddfee..0bb97498c 100644 --- a/variants/rak4631_epaper/variant.h +++ b/variants/rak4631_epaper/variant.h @@ -133,10 +133,6 @@ static const uint8_t SCK = PIN_SPI_SCK; #define PIN_EINK_SCLK (0 + 3) #define PIN_EINK_MOSI (0 + 30) // also called SDI -// Controls power for the eink display - Board power is enabled either by VBUS from USB or the CPU asserting PWR_ON -// FIXME - I think this is actually just the board power enable - it enables power to the CPU also -// #define PIN_EINK_PWR_ON (-1) - #define USE_EINK // RAKRGB @@ -218,19 +214,10 @@ static const uint8_t SCK = PIN_SPI_SCK; // and has 12 bit resolution #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) -#define VBAT_DIVIDER (0.4F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (1.73) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +#define ADC_MULTIPLIER 1.73 #define HAS_RTC 1 diff --git a/variants/rak4631_epaper_onrxtx/platformio.ini b/variants/rak4631_epaper_onrxtx/platformio.ini index 0a4f05344..c4a13ec82 100644 --- a/variants/rak4631_epaper_onrxtx/platformio.ini +++ b/variants/rak4631_epaper_onrxtx/platformio.ini @@ -6,6 +6,9 @@ board = wiscore_rak4631 build_flags = ${nrf52840_base.build_flags} -Ivariants/rak4631_epaper -D RAK_4631 -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" -D PIN_EINK_EN=34 + -D EINK_DISPLAY_MODEL=GxEPD2_213_BN + -D EINK_WIDTH=250 + -D EINK_HEIGHT=122 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631_epaper_onrxtx> lib_deps = ${nrf52840_base.lib_deps} diff --git a/variants/rak4631_epaper_onrxtx/variant.h b/variants/rak4631_epaper_onrxtx/variant.h index 411e3eb17..5888cff33 100644 --- a/variants/rak4631_epaper_onrxtx/variant.h +++ b/variants/rak4631_epaper_onrxtx/variant.h @@ -119,10 +119,6 @@ static const uint8_t SCK = PIN_SPI_SCK; #define PIN_EINK_SCLK (0 + 14) // SCL #define PIN_EINK_MOSI (0 + 13) // SDA -// Controls power for the eink display - Board power is enabled either by VBUS from USB or the CPU asserting PWR_ON -// FIXME - I think this is actually just the board power enable - it enables power to the CPU also -// #define PIN_EINK_PWR_ON (-1) - // RAKRGB #define HAS_NCP5623 @@ -191,19 +187,10 @@ static const uint8_t SCK = PIN_SPI_SCK; // and has 12 bit resolution // #define BATTERY_SENSE_RESOLUTION_BITS 12 // #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -// #define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) -// #define VBAT_DIVIDER (0.4F) -// Compensation factor for the VBAT divider -// #define VBAT_DIVIDER_COMP (1.73) -// Fixed calculation of milliVolt from compensation value -// #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) // #undef AREF_VOLTAGE // #define AREF_VOLTAGE 3.0 // #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -// #define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB -// #define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +// #define ADC_MULTIPLIER 1.73 // #define HAS_RTC 1 diff --git a/variants/rp2040-lora/platformio.ini b/variants/rp2040-lora/platformio.ini new file mode 100644 index 000000000..a1d6bea9d --- /dev/null +++ b/variants/rp2040-lora/platformio.ini @@ -0,0 +1,16 @@ +[env:rp2040-lora] +extends = rp2040_base +board = rpipico +upload_protocol = picotool + +# add our variants files to the include and src paths +build_flags = ${rp2040_base.build_flags} + -DRP2040_LORA + -Ivariants/rp2040-lora + -DDEBUG_RP2040_PORT=Serial + -DHW_SPI1_DEVICE + -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m0plus" +lib_deps = + ${rp2040_base.lib_deps} +debug_build_flags = ${rp2040_base.build_flags} +debug_tool = cmsis-dap ; for e.g. Picotool \ No newline at end of file diff --git a/variants/rp2040-lora/variant.h b/variants/rp2040-lora/variant.h new file mode 100644 index 000000000..1f42c4db7 --- /dev/null +++ b/variants/rp2040-lora/variant.h @@ -0,0 +1,54 @@ +// #define RADIOLIB_CUSTOM_ARDUINO 1 +// #define RADIOLIB_TONE_UNSUPPORTED 1 +// #define RADIOLIB_SOFTWARE_SERIAL_UNSUPPORTED 1 + +#define ARDUINO_ARCH_AVR + +// #define USE_SH1106 1 + +// default I2C pins: +// SDA = 4 +// SCL = 5 + +// Recommended pins for SerialModule: +// txd = 8 +// rxd = 9 + +#define EXT_NOTIFY_OUT 22 +#define BUTTON_PIN 17 + +#define LED_PIN PIN_LED + +// #define BATTERY_PIN 26 +// ratio of voltage divider = 3.0 (R17=200k, R18=100k) +// #define ADC_MULTIPLIER 3.1 // 3.0 + a bit for being optimistic + +#define USE_SX1262 + +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS + +// https://www.waveshare.com/rp2040-lora.htm +// https://www.waveshare.com/img/devkit/RP2040-LoRa-HF/RP2040-LoRa-HF-details-11.jpg +#define LORA_SCK 14 // 10 +#define LORA_MISO 24 // 12 +#define LORA_MOSI 15 // 11 +#define LORA_CS 13 // 3 + +#define LORA_DIO0 RADIOLIB_NC +#define LORA_RESET 23 // 15 +#define LORA_DIO1 16 // 20 +#define LORA_DIO2 18 // 2 +#define LORA_DIO3 RADIOLIB_NC +#define LORA_DIO4 17 + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +// #define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#endif \ No newline at end of file diff --git a/variants/rpipico-slowclock/platformio.ini b/variants/rpipico-slowclock/platformio.ini new file mode 100644 index 000000000..eec76ca0f --- /dev/null +++ b/variants/rpipico-slowclock/platformio.ini @@ -0,0 +1,28 @@ +[env:pico_slowclock] +extends = rp2040_base +board = rpipico +upload_protocol = jlink +# debug settings for external openocd with RP2040 support (custom build) +debug_tool = custom +debug_init_cmds = + target extended-remote localhost:3333 + $INIT_BREAK + monitor reset halt + $LOAD_CMDS + monitor init + monitor reset halt + +# add our variants files to the include and src paths +build_flags = ${rp2040_base.build_flags} + -DRPI_PICO + -Ivariants/rpipico-slowclock + -DDEBUG_RP2040_PORT=Serial2 + -DHW_SPI1_DEVICE + -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m0plus" + -g + -DNO_USB +lib_deps = + ${rp2040_base.lib_deps} +debug_build_flags = ${rp2040_base.build_flags} + -g + -DNO_USB diff --git a/variants/rpipico-slowclock/variant.h b/variants/rpipico-slowclock/variant.h new file mode 100644 index 000000000..fb97ec0fb --- /dev/null +++ b/variants/rpipico-slowclock/variant.h @@ -0,0 +1,87 @@ +#define ARDUINO_ARCH_AVR + +// Build with slow system clock enabled to reduce power consumption. +#define RP2040_SLOW_CLOCK + +#ifdef RP2040_SLOW_CLOCK +// Redefine UART1 serial log output to avoid collision with UART0 for GPS. +#define SERIAL2_TX 4 +#define SERIAL2_RX 5 +// Reroute log output in SensorLib when USB is not available +#define log_e(...) Serial2.printf(__VA_ARGS__) +#define log_i(...) Serial2.printf(__VA_ARGS__) +#define log_d(...) Serial2.printf(__VA_ARGS__) +#endif + +// Expecting the Waveshare Pico GPS hat +#define HAS_GPS 1 + +// Enable OLED Screen +#define HAS_SCREEN 1 +#define USE_SH1106 1 +#define RESET_OLED 13 + +// Redefine I2C0 pins to avoid collision with UART1/Serial2. +#define I2C_SDA 8 +#define I2C_SCL 9 + +// Redefine Waveshare UPS-A/B I2C_1 pins: +#define I2C_SDA1 6 +#define I2C_SCL1 7 +// Waveshare UPS-A/B uses a 0.01 Ohm shunt for the INA219 sensor +#define INA219_MULTIPLIER 10.0f + +// Waveshare Pico GPS L76B pins: +#define GPS_RX_PIN 1 +#define GPS_TX_PIN 0 + +// Wakeup from backup mode +// #define PIN_GPS_FORCE_ON 14 +// No GPS reset available +#undef PIN_GPS_RESET +/* + * For PPS output the resistor R20 needs to be populated with 0 Ohm + * on the Waveshare Pico GPS board. + */ +#define PIN_GPS_PPS 16 +/* + * For standby mode switching the resistor R18 needs to be populated + * with 0 Ohm on the Waveshare Pico GPS board. + */ +#define PIN_GPS_STANDBY 17 + +#define BUTTON_PIN 18 +#define EXT_NOTIFY_OUT 22 +#define LED_PIN PIN_LED + +#define BATTERY_PIN 26 +// ratio of voltage divider = 3.0 (R17=200k, R18=100k) +#define ADC_MULTIPLIER 3.1 // 3.0 + a bit for being optimistic +#define BATTERY_SENSE_RESOLUTION_BITS ADC_RESOLUTION + +#define USE_SX1262 + +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS + +#define LORA_SCK 10 +#define LORA_MISO 12 +#define LORA_MOSI 11 +#define LORA_CS 3 + +#define LORA_DIO0 RADIOLIB_NC +#define LORA_RESET 15 +#define LORA_DIO1 20 +#define LORA_DIO2 2 +#define LORA_DIO3 RADIOLIB_NC + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#endif \ No newline at end of file diff --git a/variants/rpipico/platformio.ini b/variants/rpipico/platformio.ini index 727a1cab6..9537694ec 100644 --- a/variants/rpipico/platformio.ini +++ b/variants/rpipico/platformio.ini @@ -11,4 +11,6 @@ build_flags = ${rp2040_base.build_flags} -DHW_SPI1_DEVICE -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m0plus" lib_deps = - ${rp2040_base.lib_deps} \ No newline at end of file + ${rp2040_base.lib_deps} +debug_build_flags = ${rp2040_base.build_flags} +debug_tool = cmsis-dap ; for e.g. Picotool \ No newline at end of file diff --git a/variants/rpipico/variant.h b/variants/rpipico/variant.h index be26099de..ad6d0b211 100644 --- a/variants/rpipico/variant.h +++ b/variants/rpipico/variant.h @@ -22,18 +22,19 @@ #define BATTERY_PIN 26 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) #define ADC_MULTIPLIER 3.1 // 3.0 + a bit for being optimistic +#define BATTERY_SENSE_RESOLUTION_BITS ADC_RESOLUTION #define USE_SX1262 -#undef RF95_SCK -#undef RF95_MISO -#undef RF95_MOSI -#undef RF95_NSS +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS -#define RF95_SCK 10 -#define RF95_MISO 12 -#define RF95_MOSI 11 -#define RF95_NSS 3 +#define LORA_SCK 10 +#define LORA_MISO 12 +#define LORA_MOSI 11 +#define LORA_CS 3 #define LORA_DIO0 RADIOLIB_NC #define LORA_RESET 15 @@ -42,7 +43,7 @@ #define LORA_DIO3 RADIOLIB_NC #ifdef USE_SX1262 -#define SX126X_CS RF95_NSS +#define SX126X_CS LORA_CS #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY LORA_DIO2 #define SX126X_RESET LORA_RESET diff --git a/variants/rpipicow/platformio.ini b/variants/rpipicow/platformio.ini index 3228e4c24..29b5c8bcb 100644 --- a/variants/rpipicow/platformio.ini +++ b/variants/rpipicow/platformio.ini @@ -8,8 +8,12 @@ upload_protocol = picotool build_flags = ${rp2040_base.build_flags} -DRPI_PICO -Ivariants/rpipicow - -DDEBUG_RP2040_PORT=Serial -DHW_SPI1_DEVICE -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m0plus" + -fexceptions # for exception handling in MQTT +build_src_filter = ${rp2040_base.build_src_filter} + lib_deps = - ${rp2040_base.lib_deps} \ No newline at end of file + ${rp2040_base.lib_deps} + ${networking_base.lib_deps} +debug_build_flags = ${rp2040_base.build_flags} +debug_tool = cmsis-dap ; for e.g. Picotool \ No newline at end of file diff --git a/variants/rpipicow/variant.h b/variants/rpipicow/variant.h index 77cb1ffd6..a17f05ee0 100644 --- a/variants/rpipicow/variant.h +++ b/variants/rpipicow/variant.h @@ -4,6 +4,10 @@ #define ARDUINO_ARCH_AVR +#ifndef HAS_WIFI +#define HAS_WIFI 1 +#endif + #define USE_SH1106 1 // default I2C pins: @@ -17,21 +21,24 @@ #define EXT_NOTIFY_OUT 22 #define BUTTON_PIN 17 +#define LED_PIN LED_BUILTIN + #define BATTERY_PIN 26 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) #define ADC_MULTIPLIER 3.1 // 3.0 + a bit for being optimistic +#define BATTERY_SENSE_RESOLUTION_BITS ADC_RESOLUTION #define USE_SX1262 -#undef RF95_SCK -#undef RF95_MISO -#undef RF95_MOSI -#undef RF95_NSS +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS -#define RF95_SCK 10 -#define RF95_MISO 12 -#define RF95_MOSI 11 -#define RF95_NSS 3 +#define LORA_SCK 10 +#define LORA_MISO 12 +#define LORA_MOSI 11 +#define LORA_CS 3 #define LORA_DIO0 RADIOLIB_NC #define LORA_RESET 15 @@ -40,7 +47,7 @@ #define LORA_DIO3 RADIOLIB_NC #ifdef USE_SX1262 -#define SX126X_CS RF95_NSS +#define SX126X_CS LORA_CS #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY LORA_DIO2 #define SX126X_RESET LORA_RESET diff --git a/variants/senselora_rp2040/pins_arduino.h b/variants/senselora_rp2040/pins_arduino.h new file mode 100644 index 000000000..bb0ee637e --- /dev/null +++ b/variants/senselora_rp2040/pins_arduino.h @@ -0,0 +1,50 @@ +#pragma once + +#define PIN_A0 (26u) +#define PIN_A1 (27u) +#define PIN_A2 (28u) +#define PIN_A3 (29u) + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; + +// LEDs +#define PIN_LED (23u) +#define PIN_LED1 PIN_LED +#define LED_BUILTIN PIN_LED + +#define ADC_RESOLUTION 12 + +// Serial +#define PIN_SERIAL1_TX (0ul) +#define PIN_SERIAL1_RX (1ul) + +#define PIN_SERIAL2_TX (4ul) +#define PIN_SERIAL2_RX (5ul) + +// SPI +#define PIN_SPI0_MISO (16u) +#define PIN_SPI0_MOSI (19u) +#define PIN_SPI0_SCK (18u) +#define PIN_SPI0_SS (17u) + +// Wire +#define PIN_WIRE0_SDA (6u) +#define PIN_WIRE0_SCL (7u) + +#define PIN_WIRE1_SDA (-1) +#define PIN_WIRE1_SCL (-1) + +#define SERIAL_HOWMANY (3u) +#define SPI_HOWMANY (2u) +#define WIRE_HOWMANY (1u) + +static const uint8_t SS = PIN_SPI0_SS; +static const uint8_t MOSI = PIN_SPI0_MOSI; +static const uint8_t MISO = PIN_SPI0_MISO; +static const uint8_t SCK = PIN_SPI0_SCK; + +static const uint8_t SDA = PIN_WIRE0_SDA; +static const uint8_t SCL = PIN_WIRE0_SCL; \ No newline at end of file diff --git a/variants/senselora_rp2040/platformio.ini b/variants/senselora_rp2040/platformio.ini new file mode 100644 index 000000000..3b3253ee8 --- /dev/null +++ b/variants/senselora_rp2040/platformio.ini @@ -0,0 +1,13 @@ +[env:senselora_rp2040] +extends = rp2040_base +board = rpipico +upload_protocol = picotool + +# add our variants files to the include and src paths +build_flags = ${rp2040_base.build_flags} + -DSENSELORA_RP2040 + -Ivariants/senselora_rp2040 + -DDEBUG_RP2040_PORT=Serial + -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m0plus" +lib_deps = + ${rp2040_base.lib_deps} \ No newline at end of file diff --git a/variants/senselora_rp2040/variant.h b/variants/senselora_rp2040/variant.h new file mode 100644 index 000000000..2f68cf034 --- /dev/null +++ b/variants/senselora_rp2040/variant.h @@ -0,0 +1,27 @@ +#define ARDUINO_ARCH_AVR + +#define USE_SSD1306 + +#define BUTTON_PIN 2 +#define BUTTON_NEED_PULLUP + +#define LED_PIN PIN_LED + +#undef BATTERY_PIN +#define BATTERY_SENSE_RESOLUTION_BITS ADC_RESOLUTION + +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS + +#define USE_RF95 +#define LORA_SCK PIN_SPI0_SCK +#define LORA_MISO PIN_SPI0_MISO +#define LORA_MOSI PIN_SPI0_MOSI +#define LORA_CS PIN_SPI0_SS + +#define LORA_DIO0 21 +#define LORA_DIO1 22 +#define LORA_DIO2 RADIOLIB_NC +#define LORA_RESET 20 \ No newline at end of file diff --git a/variants/station-g1/variant.h b/variants/station-g1/variant.h index 79a49fc96..9a3c37b73 100644 --- a/variants/station-g1/variant.h +++ b/variants/station-g1/variant.h @@ -23,7 +23,7 @@ #define LORA_DIO3 // Not connected on PCB #ifdef USE_SX1262 -#define SX126X_CS RF95_NSS // FIXME - we really should define LORA_CS instead +#define SX126X_CS LORA_CS // FIXME - we really should define LORA_CS instead #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY LORA_DIO2 #define SX126X_RESET LORA_RESET @@ -37,10 +37,8 @@ #define ADC_CHANNEL ADC1_GPIO35_CHANNEL #define BATTERY_SENSE_SAMPLES 30 // Set the number of samples, It has an effect of increasing sensitivity. #define ADC_MULTIPLIER 6.45 -#define BAT_FULLVOLT 12600 -#define BAT_EMPTYVOLT 8200 -#define BAT_CHARGINGVOLT 12600 -#define BAT_NOBATVOLT 6690 +#define CELL_TYPE_LION // same curve for liion/lipo +#define NUM_CELLS 3 // different screen #define USE_SH1106 diff --git a/variants/station-g2/pins_arduino.h b/variants/station-g2/pins_arduino.h new file mode 100755 index 000000000..98cbd46d3 --- /dev/null +++ b/variants/station-g2/pins_arduino.h @@ -0,0 +1,29 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +#define EXTERNAL_NUM_INTERRUPTS 46 +#define NUM_DIGITAL_PINS 48 +#define NUM_ANALOG_INPUTS 20 + +#define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1) +#define digitalPinToInterrupt(p) (((p) <= 48) ? (p) : -1) +#define digitalPinHasPWM(p) (p < 46) + +// GPIO48 Reference: https://github.com/espressif/arduino-esp32/pull/8600 + +// The default Wire will be mapped to Screen and Sensors +static const uint8_t SDA = 5; +static const uint8_t SCL = 6; + +// Default SPI will be mapped to Radio +static const uint8_t MISO = 14; +static const uint8_t SCK = 12; +static const uint8_t MOSI = 13; +static const uint8_t SS = 11; + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/station-g2/platformio.ini b/variants/station-g2/platformio.ini new file mode 100755 index 000000000..b39136684 --- /dev/null +++ b/variants/station-g2/platformio.ini @@ -0,0 +1,15 @@ +[env:station-g2] +extends = esp32s3_base +board = station-g2 +board_build.mcu = esp32s3 +upload_protocol = esptool +;upload_port = /dev/ttyACM0 +upload_speed = 921600 +lib_deps = + ${esp32s3_base.lib_deps} +build_unflags = -DARDUINO_USB_MODE=1 +build_flags = + ${esp32s3_base.build_flags} -D STATION_G2 -I variants/station-g2 + -DBOARD_HAS_PSRAM + -DSTATION_G2 + -DARDUINO_USB_MODE=0 \ No newline at end of file diff --git a/variants/station-g2/variant.h b/variants/station-g2/variant.h new file mode 100755 index 000000000..f781ceb24 --- /dev/null +++ b/variants/station-g2/variant.h @@ -0,0 +1,52 @@ +/* +Board Information: https://wiki.uniteng.com/en/meshtastic/station-g2 +*/ + +// Station G2 may not have GPS installed, but it has a GROVE GPS Socket for Optional GPS Module +#define GPS_RX_PIN 7 +#define GPS_TX_PIN 15 + +// Station G2 has 1.3 inch OLED Screen +#define USE_SH1107_128_64 + +#define I2C_SDA 5 // I2C pins for this board +#define I2C_SCL 6 + +#define BUTTON_PIN 38 // This is the Program Button +#define BUTTON_NEED_PULLUP + +#define USE_SX1262 + +#define LORA_MISO 14 +#define LORA_SCK 12 +#define LORA_MOSI 13 +#define LORA_CS 11 + +#define LORA_RESET 21 +#define LORA_DIO1 48 + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS // FIXME - we really should define LORA_CS instead +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY 47 +#define SX126X_RESET LORA_RESET + +// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// Ensure the PA does not exceed the saturation output power. More +// Info:https://wiki.uniteng.com/en/meshtastic/station-g2#summary-for-lora-power-amplifier-conduction-test +#define SX126X_MAX_POWER 19 +#endif + +#define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC1_GPIO4_CHANNEL +#define ADC_MULTIPLIER 4 +#define BATTERY_SENSE_SAMPLES 15 // Set the number of samples, It has an effect of increasing sensitivity. +#define BAT_FULLVOLT 8400 +#define BAT_EMPTYVOLT 5000 +#define BAT_CHARGINGVOLT 8400 +#define BAT_NOBATVOLT 4460 +#define CELL_TYPE_LION // same curve for liion/lipo +#define NUM_CELLS 2 diff --git a/variants/t-deck/platformio.ini b/variants/t-deck/platformio.ini index 38e334a30..593fdae5e 100644 --- a/variants/t-deck/platformio.ini +++ b/variants/t-deck/platformio.ini @@ -2,14 +2,17 @@ [env:t-deck] extends = esp32s3_base board = t-deck -upload_protocol = esp-builtin -debug_tool = esp-builtin +upload_protocol = esptool +#upload_port = COM29 build_flags = ${esp32_base.build_flags} -DT_DECK -DBOARD_HAS_PSRAM + -DMAX_THREADS=40 -DGPS_POWER_TOGGLE -Ivariants/t-deck lib_deps = ${esp32s3_base.lib_deps} - lovyan03/LovyanGFX@^1.1.9 \ No newline at end of file + lovyan03/LovyanGFX@^1.1.9 + earlephilhower/ESP8266Audio@^1.9.7 + earlephilhower/ESP8266SAM@^1.0.1 \ No newline at end of file diff --git a/variants/t-deck/variant.h b/variants/t-deck/variant.h index b1673d338..09db198ec 100644 --- a/variants/t-deck/variant.h +++ b/variants/t-deck/variant.h @@ -24,6 +24,8 @@ #define TOUCH_I2C_PORT 0 #define TOUCH_SLAVE_ADDRESS 0x5D // GT911 +#define SLEEP_TIME 120 + #define BUTTON_PIN 0 // #define BUTTON_NEED_PULLUP @@ -65,14 +67,20 @@ #define ES7210_LRCK 21 #define ES7210_MCLK 48 +// dac / amp +#define HAS_I2S +#define DAC_I2S_BCK 7 +#define DAC_I2S_WS 5 +#define DAC_I2S_DOUT 6 + // LoRa #define USE_SX1262 #define USE_SX1268 -#define RF95_SCK 40 -#define RF95_MISO 38 -#define RF95_MOSI 41 -#define RF95_NSS 9 +#define LORA_SCK 40 +#define LORA_MISO 38 +#define LORA_MOSI 41 +#define LORA_CS 9 #define LORA_DIO0 -1 // a No connect on the SX1262 module #define LORA_RESET 17 @@ -80,7 +88,7 @@ #define LORA_DIO2 13 // SX1262 BUSY #define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled -#define SX126X_CS RF95_NSS // FIXME - we really should define LORA_CS instead +#define SX126X_CS LORA_CS // FIXME - we really should define LORA_CS instead #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY LORA_DIO2 #define SX126X_RESET LORA_RESET diff --git a/variants/t-echo/platformio.ini b/variants/t-echo/platformio.ini index 2555032df..9ff60be3f 100644 --- a/variants/t-echo/platformio.ini +++ b/variants/t-echo/platformio.ini @@ -6,11 +6,20 @@ debug_tool = jlink # add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo + -DGPS_POWER_TOGGLE -L "${platformio.libdeps_dir}/${this.__env__}/BSEC2 Software Library/src/cortex-m4/fpv4-sp-d16-hard" + -DEINK_DISPLAY_MODEL=GxEPD2_154_D67 + -DEINK_WIDTH=200 + -DEINK_HEIGHT=200 + -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -DEINK_LIMIT_FASTREFRESH=20 ; How many consecutive fast-refreshes are permitted + -DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates + -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates + -DEINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated + -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo> lib_deps = ${nrf52840_base.lib_deps} - https://github.com/meshtastic/GxEPD2#afce87a97dda1ac31d8a28dc8fa7c6f55dc96a61 - adafruit/Adafruit BusIO@^1.13.2 + https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a lewisxhe/PCF8563_Library@^1.0.1 ;upload_protocol = fs diff --git a/variants/t-echo/variant.h b/variants/t-echo/variant.h index 6bd5091dd..6a5146dc0 100644 --- a/variants/t-echo/variant.h +++ b/variants/t-echo/variant.h @@ -44,6 +44,7 @@ extern "C" { // LEDs #define PIN_LED1 (0 + 14) // 13 red (confirmed on 1.0 board) +// Unused(by firmware) LEDs: #define PIN_LED2 (0 + 15) // 14 blue #define PIN_LED3 (0 + 13) // 15 green @@ -155,9 +156,9 @@ External serial flash WP25R1635FZUIL0 #define PIN_EINK_SCLK (0 + 31) #define PIN_EINK_MOSI (0 + 29) // also called SDI -// Controls power for the eink display - Board power is enabled either by VBUS from USB or the CPU asserting PWR_ON -// FIXME - I think this is actually just the board power enable - it enables power to the CPU also -#define PIN_EINK_PWR_ON (0 + 12) +// Controls power for all peripherals (eink + GPS + LoRa + Sensor) +#define PIN_POWER_EN (0 + 12) +// #define PIN_POWER_EN1 (0 + 13) #define USE_EINK @@ -208,19 +209,10 @@ External serial flash WP25R1635FZUIL0 // and has 12 bit resolution #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 100K + 100K voltage divider on VBAT = (100K / (100K + 100K)) -#define VBAT_DIVIDER (0.5F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (2.0) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) +#define ADC_MULTIPLIER (2.0F) #define HAS_RTC 1 @@ -232,4 +224,4 @@ External serial flash WP25R1635FZUIL0 * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif +#endif \ No newline at end of file diff --git a/variants/t-watch-s3/platformio.ini b/variants/t-watch-s3/platformio.ini index 162384bfd..5d5904b30 100644 --- a/variants/t-watch-s3/platformio.ini +++ b/variants/t-watch-s3/platformio.ini @@ -12,4 +12,6 @@ build_flags = ${esp32_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} lovyan03/LovyanGFX@^1.1.9 lewisxhe/PCF8563_Library@1.0.1 - adafruit/Adafruit DRV2605 Library@^1.2.2 \ No newline at end of file + adafruit/Adafruit DRV2605 Library@^1.2.2 + earlephilhower/ESP8266Audio@^1.9.7 + earlephilhower/ESP8266SAM@^1.0.1 \ No newline at end of file diff --git a/variants/t-watch-s3/variant.h b/variants/t-watch-s3/variant.h index c87845afa..ad7e6b56b 100644 --- a/variants/t-watch-s3/variant.h +++ b/variants/t-watch-s3/variant.h @@ -25,11 +25,18 @@ #define TOUCH_I2C_PORT 1 #define TOUCH_SLAVE_ADDRESS 0x38 +#define SLEEP_TIME 180 + #define I2C_SDA1 39 // Used for capacitive touch #define I2C_SCL1 40 // Used for capacitive touch #define TFT_BL ST7789_BACKLIGHT_EN +#define HAS_I2S +#define DAC_I2S_BCK 48 +#define DAC_I2S_WS 15 +#define DAC_I2S_DOUT 46 + #define HAS_AXP2101 #define HAS_RTC 1 @@ -37,8 +44,6 @@ #define I2C_SDA 10 // For QMC6310 sensors and screens #define I2C_SCL 11 // For QMC6310 sensors and screens -#define BUTTON_PIN 0 - #define BMA4XX_INT 14 // Interrupt for BMA_423 axis sensor #define HAS_GPS 0 @@ -48,10 +53,10 @@ #define USE_SX1262 #define USE_SX1268 -#define RF95_SCK 3 -#define RF95_MISO 4 -#define RF95_MOSI 1 -#define RF95_NSS 5 +#define LORA_SCK 3 +#define LORA_MISO 4 +#define LORA_MOSI 1 +#define LORA_CS 5 #define LORA_DIO0 -1 // a No connect on the SX1262 module #define LORA_RESET 8 @@ -59,7 +64,7 @@ #define LORA_DIO2 7 // SX1262 BUSY #define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled -#define SX126X_CS RF95_NSS // FIXME - we really should define LORA_CS instead +#define SX126X_CS LORA_CS // FIXME - we really should define LORA_CS instead #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY LORA_DIO2 #define SX126X_RESET LORA_RESET diff --git a/variants/tbeam-s3-core/variant.h b/variants/tbeam-s3-core/variant.h index 5e9894cc6..0fa9f8fe8 100644 --- a/variants/tbeam-s3-core/variant.h +++ b/variants/tbeam-s3-core/variant.h @@ -47,10 +47,10 @@ #define PMU_USE_WIRE1 #define RTC_USE_WIRE1 -#define RF95_SCK 12 -#define RF95_MISO 13 -#define RF95_MOSI 11 -#define RF95_NSS 10 +#define LORA_SCK 12 +#define LORA_MISO 13 +#define LORA_MOSI 11 +#define LORA_CS 10 #define GPS_RX_PIN 9 #define GPS_TX_PIN 8 diff --git a/variants/tbeam/variant.h b/variants/tbeam/variant.h index a0ba70bfd..d237f542b 100644 --- a/variants/tbeam/variant.h +++ b/variants/tbeam/variant.h @@ -24,7 +24,7 @@ #define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled #ifdef USE_SX1262 -#define SX126X_CS RF95_NSS // FIXME - we really should define LORA_CS instead +#define SX126X_CS LORA_CS // FIXME - we really should define LORA_CS instead #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY LORA_DIO2 #define SX126X_RESET LORA_RESET diff --git a/variants/tlora_t3s3_v1/variant.h b/variants/tlora_t3s3_v1/variant.h index 6e1d1d0eb..8a1af1ec2 100644 --- a/variants/tlora_t3s3_v1/variant.h +++ b/variants/tlora_t3s3_v1/variant.h @@ -25,10 +25,10 @@ #define USE_SX1262 #define USE_SX1280 -#define RF95_SCK 5 -#define RF95_MISO 3 -#define RF95_MOSI 6 -#define RF95_NSS 7 +#define LORA_SCK 5 +#define LORA_MISO 3 +#define LORA_MOSI 6 +#define LORA_CS 7 #define LORA_RESET 8 // per SX1276_Receive_Interrupt/utilities.h @@ -40,7 +40,7 @@ // per SX1262_Receive_Interrupt/utilities.h #ifdef USE_SX1262 -#define SX126X_CS RF95_NSS +#define SX126X_CS LORA_CS #define SX126X_DIO1 33 #define SX126X_BUSY 34 #define SX126X_RESET LORA_RESET @@ -50,7 +50,7 @@ // per SX128x_Receive_Interrupt/utilities.h #ifdef USE_SX1280 -#define SX128X_CS RF95_NSS +#define SX128X_CS LORA_CS #define SX128X_DIO1 9 #define SX128X_DIO2 33 #define SX128X_DIO3 34 diff --git a/variants/tlora_v2_1_16/variant.h b/variants/tlora_v2_1_16/variant.h index b8c43e557..8bb5ce3b1 100644 --- a/variants/tlora_v2_1_16/variant.h +++ b/variants/tlora_v2_1_16/variant.h @@ -16,5 +16,13 @@ #define USE_RF95 #define LORA_DIO0 26 // a No connect on the SX1262 module #define LORA_RESET 23 + +// In the T3 V1.6.1 TXCO version, GPIO 33 is connected to Radio’s +// internal temperature-compensated crystal oscillator enable +#ifdef LORA_TCXO_GPIO +#define LORA_DIO1 RADIOLIB_NC // no-connect on sx127x module +#else #define LORA_DIO1 33 // https://www.thethingsnetwork.org/forum/t/big-esp32-sx127x-topic-part-3/18436 +#endif + #define LORA_DIO2 32 // Not really used \ No newline at end of file diff --git a/variants/tlora_v2_1_16_tcxo/platformio.ini b/variants/tlora_v2_1_16_tcxo/platformio.ini new file mode 100644 index 000000000..e54c1a920 --- /dev/null +++ b/variants/tlora_v2_1_16_tcxo/platformio.ini @@ -0,0 +1,9 @@ +[env:tlora-v2-1-1_6-tcxo] +extends = esp32_base +board = ttgo-lora32-v21 +build_flags = + ${esp32_base.build_flags} + -D TLORA_V2_1_16 + -I variants/tlora_v2_1_16 + -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + -D LORA_TCXO_GPIO=33 \ No newline at end of file diff --git a/variants/trackerd/variant.h b/variants/trackerd/variant.h index b3fca367f..bd8017d8c 100644 --- a/variants/trackerd/variant.h +++ b/variants/trackerd/variant.h @@ -10,7 +10,7 @@ #define LED_PIN 13 // 13 red, 2 blue, 15 red -//#define HAS_BUTTON 0 +// #define HAS_BUTTON 0 #define BUTTON_PIN 0 #define BUTTON_NEED_PULLUP @@ -26,21 +26,10 @@ // The battery sense is hooked to pin A0 (4) // it is defined in the anlaolgue pin section of this file // and has 12 bit resolution +// #define BATTERY_SENSE_SAMPLES 15 // Set the number of samples, It has an effect of increasing sensitivity. #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 100K + 100K voltage divider on VBAT = (100K / (100K + 100K)) -#define VBAT_DIVIDER (0.5F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (2.0) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) - -//#define BATTERY_SENSE_SAMPLES 15 // Set the number of samples, It has an effect of increasing sensitivity. -//#define ADC_MULTIPLIER 3.3 \ No newline at end of file +#define ADC_MULTIPLIER (2.0F) \ No newline at end of file diff --git a/variants/unphone/platformio.ini b/variants/unphone/platformio.ini new file mode 100644 index 000000000..dad9a7177 --- /dev/null +++ b/variants/unphone/platformio.ini @@ -0,0 +1,30 @@ +; platformio.ini for unphone meshtastic + +[env:unphone] + +extends = esp32s3_base +board_level = extra +board = unphone9 +upload_speed = 921600 +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder + +build_unflags = + -D ARDUINO_USB_MODE + +build_flags = ${esp32_base.build_flags} + -D UNPHONE + -I variants/unphone + -D ARDUINO_USB_MODE=0 + -D UNPHONE_ACCEL=0 + -D UNPHONE_TOUCHS=0 + -D UNPHONE_SDCARD=0 + -D UNPHONE_UI0=0 + -D UNPHONE_LORA=0 + -D UNPHONE_FACTORY_MODE=0 + +build_src_filter = ${esp32_base.build_src_filter} +<../variants/unphone> + +lib_deps = ${esp32s3_base.lib_deps} + lovyan03/LovyanGFX @ ^1.1.8 + https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic @ ^9.0.0 \ No newline at end of file diff --git a/variants/unphone/variant.cpp b/variants/unphone/variant.cpp new file mode 100644 index 000000000..3f6d1c54d --- /dev/null +++ b/variants/unphone/variant.cpp @@ -0,0 +1,20 @@ +// meshtastic/firmware/variants/unphone/variant.cpp + +#include "unPhone.h" +unPhone unphone = unPhone("meshtastic_unphone"); + +void initVariant() +{ + unphone.begin(); // initialise hardware etc. + unphone.store(unphone.buildTime); + unphone.printWakeupReason(); // what woke us up? (stored, not printed :|) + unphone.checkPowerSwitch(); // if power switch is off, shutdown + unphone.backlight(false); // setup backlight and make sure its off + + for (int i = 0; i < 3; i++) { // buzz a bit + unphone.vibe(true); + delay(150); + unphone.vibe(false); + delay(150); + } +} \ No newline at end of file diff --git a/variants/unphone/variant.h b/variants/unphone/variant.h new file mode 100644 index 000000000..180fdfe2c --- /dev/null +++ b/variants/unphone/variant.h @@ -0,0 +1,71 @@ +// meshtastic/firmware/variants/unphone/variant.h + +#pragma once + +#define SPI_SCK 39 +#define SPI_MOSI 40 +#define SPI_MISO 41 + +// We use the RFM95W LoRa module +#define USE_RF95 +#define LORA_SCK SPI_SCK +#define LORA_MOSI SPI_MOSI +#define LORA_MISO SPI_MISO +#define LORA_CS 44 +#define LORA_DIO0 10 // AKA LORA_IRQ +#define LORA_RESET 42 +#define LORA_DIO1 11 +#define LORA_DIO2 RADIOLIB_NC // Not really used + +// HX8357 TFT LCD +#define HX8357_CS 48 +#define HX8357_RS 47 // AKA DC +#define HX8357_RESET 46 +#define HX8357_SCK SPI_SCK +#define HX8357_MOSI SPI_MOSI +#define HX8357_MISO SPI_MISO +#define HX8357_BUSY -1 +#define HX8357_SPI_HOST SPI2_HOST +#define SPI_FREQUENCY 40000000 +#define SPI_READ_FREQUENCY 16000000 +#define TFT_HEIGHT 480 +#define TFT_WIDTH 320 +#define TFT_OFFSET_X 0 +#define TFT_OFFSET_Y 0 +#define TFT_OFFSET_ROTATION 6 // unPhone's screen wired unusually, 0 typical +#define TFT_INVERT false +#define SCREEN_ROTATE true +#define SCREEN_TRANSITION_FRAMERATE 5 + +#define HAS_TOUCHSCREEN 1 +#define USE_XPT2046 1 +#define TOUCH_CS 38 + +#define HAS_GPS \ + 0 // the unphone doesn't have a gps module by default (though + // GPS featherwing -- https://www.adafruit.com/product/3133 + // -- can be added) +#undef GPS_RX_PIN +#undef GPS_TX_PIN + +#define HAS_SDCARD 1 +#define SDCARD_CS 43 + +#define LED_PIN 13 // the red part of the RGB LED +#define LED_INVERTED 1 + +#define BUTTON_PIN 21 // Button 3 - square - top button in landscape mode +#define BUTTON_NEED_PULLUP // we do need a helping hand up +#define BUTTON_PIN_ALT 45 // Button 1 - triangle - bottom button in landscape mode + +#define I2C_SDA 3 // I2C pins for this board +#define I2C_SCL 4 + +#define LSM6DS3_WAKE_THRESH 5 // higher values reduce the sensitivity of the wake threshold + +// ratio of voltage divider = 3.20 (R1=100k, R2=220k) +// #define ADC_MULTIPLIER 3.2 + +// #define BATTERY_PIN 13 // battery V measurement pin; vbat divider is here +// #define ADC_CHANNEL ADC2_GPIO13_CHANNEL +// #define BAT_MEASURE_ADC_UNIT 2 \ No newline at end of file diff --git a/version.properties b/version.properties index 972a6f6de..485f55130 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 -minor = 2 -build = 14 +minor = 3 +build = 7