diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 000000000..e44b86089
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,11 @@
+# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.162.0/containers/ubuntu/.devcontainer/base.Dockerfile
+
+# [Choice] Ubuntu version: bionic, focal
+ARG VARIANT="focal"
+FROM mcr.microsoft.com/vscode/devcontainers/base:0-${VARIANT}
+
+# [Optional] Uncomment this section to install additional OS packages.
+RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
+ && apt-get -y install python3-distutils python3-pip
+RUN pip3 install platformio meshtastic adafruit-nrfutil
+RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v3.15.8/protoc-3.15.8-linux-x86_64.zip -O /tmp/protoc.zip && cd /tmp && unzip protoc.zip && chmod a+x bin/protoc && cp bin/protoc /usr/local/bin
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 000000000..e0a284e88
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,32 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
+// https://github.com/microsoft/vscode-dev-containers/tree/v0.162.0/containers/ubuntu
+{
+ "name": "Ubuntu",
+ "build": {
+ "dockerfile": "Dockerfile",
+ // Update 'VARIANT' to pick an Ubuntu version: focal, bionic
+ "args": { "VARIANT": "focal" }
+ },
+
+ // Set *default* container specific settings.json values on container create.
+ "settings": {
+ "terminal.integrated.shell.linux": "/bin/bash"
+ },
+
+ // Add the IDs of extensions you want installed when the container is created.
+ "extensions": [
+ "platformio.platformio-ide",
+ "xaver.clang-format"
+ ],
+
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
+ // "forwardPorts": [],
+
+ // Use 'postCreateCommand' to run commands after the container is created.
+ // "postCreateCommand": "uname -a",
+
+ // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
+ "remoteUser": "vscode",
+
+ "postCreateCommand": "git submodule update --init"
+}
\ No newline at end of file
diff --git a/.github/actions/initbuild/action.yml b/.github/actions/initbuild/action.yml
index db49b31ad..b852d0a6c 100644
--- a/.github/actions/initbuild/action.yml
+++ b/.github/actions/initbuild/action.yml
@@ -24,12 +24,13 @@ runs:
python -m pip install --upgrade pip
pip install -U platformio meshtastic adafruit-nrfutil
- - name: Cache platformio
- uses: actions/cache@v1
- id: cache-platformio # needed in if test
- with:
- path: ~/.platformio
- key: ${{ runner.os }}-platformio
+ # Don't cache for now because I want to be lazy with portuino updates githashes
+ # - name: Cache platformio
+ # uses: actions/cache@v1
+ # id: cache-platformio # needed in if test
+ # with:
+ # path: ~/.platformio
+ # key: ${{ runner.os }}-platformio
- name: Upgrade platformio
run: |
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 1087daacf..0425f13fa 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -36,6 +36,10 @@ jobs:
path: ~/.cache/pip
key: ${{ runner.os }}-pip
+ #- name: Install linux apt packages
+ # run: |
+ # sudo apt-get install -y libgpiod-dev
+
- name: Upgrade python tools
# We actually want to run this every time
# if: steps.cache-pip.outputs.cache-hit != 'true'
@@ -43,17 +47,30 @@ jobs:
python -m pip install --upgrade pip
pip install -U platformio meshtastic adafruit-nrfutil
- - name: Cache platformio
- uses: actions/cache@v1
- id: cache-platformio # needed in if test
- with:
- path: ~/.platformio
- key: ${{ runner.os }}-platformio
+ # - name: Cache platformio
+ # uses: actions/cache@v1
+ # id: cache-platformio # needed in if test
+ # with:
+ # path: ~/.platformio
+ # key: ${{ runner.os }}-platformio
- name: Upgrade platformio
run: |
pio upgrade
+ - name: Pull web ui
+ uses: dsaltares/fetch-gh-release-asset@master
+ with:
+ repo: "meshtastic/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
+
# We now run integration test before other build steps (to quickly see runtime failures)
- name: Build for native
run: platformio run -e native
@@ -74,9 +91,16 @@ jobs:
- name: Build everything
run: bin/build-all.sh
- - name: Store release zip as an artifact
+ - name: Store binaries as an artifact
uses: actions/upload-artifact@v2
with:
name: built
path: release/archive/firmware-*.zip
retention-days: 30
+
+ - name: Store debugging elf files as an artifact
+ uses: actions/upload-artifact@v2
+ with:
+ name: debug-elfs
+ path: release/archive/elfs-*.zip
+ retention-days: 7
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 7b801d583..7e4e259a6 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -9,7 +9,7 @@ on:
branches:
- "!*"
tags:
- - "v*"
+ - "v1*"
jobs:
release-build:
@@ -47,9 +47,22 @@ jobs:
run: |
pio upgrade
+ - name: Pull web ui
+ uses: dsaltares/fetch-gh-release-asset@master
+ with:
+ repo: "meshtastic/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
+
# Will be available in steps.version.outputs.version
- name: Get version string
- run: echo "::set-output name=version::$(./bin/buildinfo.py)"
+ run: echo "::set-output name=version::$(./bin/buildinfo.py long)"
id: version
- name: Build everything
@@ -69,7 +82,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ github.token }}
- - name: Add artifact to release
+ - name: Add bins to release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ github.token }}
@@ -77,4 +90,14 @@ jobs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: release/archive/firmware-${{ steps.version.outputs.version }}.zip
asset_name: firmware-${{ steps.version.outputs.version }}.zip
- asset_content_type: application/zip
\ No newline at end of file
+ asset_content_type: application/zip
+
+ - name: Add debug elfs to release
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ with:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ asset_path: release/archive/elfs-${{ steps.version.outputs.version }}.zip
+ asset_name: debug-elfs-${{ steps.version.outputs.version }}.zip
+ asset_content_type: application/zip
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 67688a0b4..eeb1bcb09 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,8 @@ main/credentials.h
!.vscode/extensions.json
*.code-workspace
+.idea/workspace.xml
+
.DS_Store
Thumbs.db
.autotools
@@ -19,4 +21,5 @@ Thumbs.db
nanopb*
flash.uf2
cmake-build*
-__pycache__
\ No newline at end of file
+__pycache__
+
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
deleted file mode 100644
index 826eaac58..000000000
--- a/.idea/workspace.xml
+++ /dev/null
@@ -1,162 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1615788661896
-
-
- 1615788661896
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- file://$PROJECT_DIR$/src/mesh/wifi/WiFiServerAPI.cpp
- 53
-
-
-
- file://$PROJECT_DIR$/src/mesh/wifi/WiFiServerAPI.cpp
- 37
-
-
-
- file://$PROJECT_DIR$/src/mqtt/MQTT.cpp
- 166
-
-
-
- file://$PROJECT_DIR$/.pio/libdeps/native/PubSubClient/src/PubSubClient.cpp
- 468
-
-
-
- file://$PROJECT_DIR$/src/mesh/mesh-pb-constants.cpp
- 20
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 5ce21fa35..455d58ffb 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -77,6 +77,5 @@
"--java_out=/tmp",
"-I=/home/kevinh/development/meshtastic/meshtastic-esp32/proto"
]
- },
- "editor.formatOnSave": true
+ }
}
\ No newline at end of file
diff --git a/README.md b/README.md
index 7bbed4283..a5561bad6 100644
--- a/README.md
+++ b/README.md
@@ -1,213 +1,5 @@
# Meshtastic-device
+[](https://open.vscode.dev/meshtastic/Meshtastic-device)
+## This repository contains the device firmware used in the [Meshtastic](https://meshtastic.org) project.
-This is the device side code for the [meshtastic.org](https://www.meshtastic.org) project.
-
-
-
-Meshtastic® is a project that lets you use
-inexpensive GPS mesh radios as an extensible, super long battery life mesh GPS communicator. These radios are great for hiking, skiing, paragliding -
-essentially any hobby where you don't have reliable internet access. Each member of your private mesh can always see the location and distance of all other
-members and any text messages sent to your group chat.
-
-The radios automatically create a mesh to forward packets as needed, so everyone in the group can receive messages from even the furthest member. The radios
-will optionally work with your phone, but no phone is required.
-
-Typical time between recharging the radios should be about eight days.
-
-This project is is currently in beta-testing - if you have questions please [join our discussion forum](https://meshtastic.discourse.group/).
-
-This software is 100% open source and developed by a group of hobbyist experimenters. No warranty is provided, if you'd like to improve it - we'd love your help. Please post in the chat.
-
-## Supported hardware
-
-We currently support three models of radios.
-
-- TTGO T-Beam (usually the recommended choice)
- - [T-Beam V1.1 w/ NEO-6M - special Meshtastic version](https://www.aliexpress.com/item/4001178678568.html) (Includes built-in OLED display and they have **preinstalled** the meshtastic software)
- - [T-Beam V1.1 w/ NEO-M8N](https://www.aliexpress.com/item/33047631119.html) (slightly better GPS)
- - [T-Beam V1.1 w/ NEO-M8N /w SX1262](https://www.aliexpress.com/item/4001287221970.html) (slightly better GPS + LoRa)
- - board labels "TTGO T22_V1.1 20191212"
- - [T-Beam V0.7 w/ NEO-6M](https://www.aliexpress.com/item/4000574335430.html) (will work but **you must use the tbeam0.7 firmware ** - but the T-Beam V1.0 or later are better!)
- - board labels "TTGO T22_V07 20180711"
- - 3D printable cases
- - [T-Beam V0](https://www.thingiverse.com/thing:3773717) (GPS and LoRa antenna misaligned if GPS placed as pictured)
- - [T-Beam V1 (SMA-antenna)](https://www.thingiverse.com/thing:3830711)
- - [T-Beam V1 (SMA-antenna)](https://www.thingiverse.com/thing:4677388) (Mounting option for larger GPS antenna but LoRa antenna enclosed)
- - [T-Beam V1 (IPEX-antenna)](https://www.thingiverse.com/thing:4587297) (GPS and LoRa antenna misaligned if GPS placed as pictured)
- - [T-Beam V1 (IPEX-antenna)](https://www.thingiverse.com/thing:4589651)
- - [T-Beam V1 (IPEX-antenna)](https://www.thingiverse.com/thing:4619981) (GPS and LoRa antenna misaligned if GPS placed as pictured)
- - Laser-cut cases
- - [T-Beam V1 (SMA-antenna)](https://www.thingiverse.com/thing:4552771)
-
-- [TTGO LORA32](https://www.aliexpress.com/item/4000211331316.html) - No GPS
- - version 2.1
- - board labels "TTGO T3_V1.6 20180606"
- - 3D printable case
- - [TTGO LORA32 v1](https://www.thingiverse.com/thing:3385109)
-
-- [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/) - No GPS
- - [Official Heltec case](https://www.aliexpress.com/item/4001050707951.html)
- - [3D Printable case](https://www.thingiverse.com/thing:3125854)
-
-Note: The GPS and LoRa stock antennas should be placed in a way, that the GPS antenna faces the sky and the LoRa antenna radiates 360 degrees horizontally. For better GPS reception you might want to [upgrade the GPS antenna](https://meshtastic.discourse.group/t/the-importance-of-gps-antennas-and-request-to-3d-case-documentation-people/1505) and to properly align the antennas you might want to upgrade to a LoRa antenna that can be adjusted to radiate into the right directions.
-
-**Make sure to get the frequency for your country**
-
-- US/JP/AU/NZ/CA - 915MHz
-- CN - 470MHz
-- EU - 868MHz, 433MHz
-- full list of LoRa frequencies per region is available [here](https://www.thethingsnetwork.org/docs/lorawan/frequencies-by-country.html)
-
-Getting a version that includes a screen is optional, but highly recommended.
-
-## Firmware Installation
-
-Prebuilt binaries for the supported radios are available in our [releases](https://github.com/meshtastic/Meshtastic-esp32/releases). Your initial installation has to happen over USB from your Mac, Windows or Linux PC. Once our software is installed, all future software updates happen over bluetooth from your phone.
-
-Be **very careful** to install the correct load for your board. In particular the popular 'T-BEAM' radio from TTGO is not called 'TTGO-Lora' (that is a different board). So don't install the 'TTGO-Lora' build on a TBEAM, it won't work correctly.
-
-Please post comments on our [group chat](https://meshtastic.discourse.group/) if you have problems or successes.
-
-### Installing from a GUI - Windows and Mac
-
-1. Download and unzip the latest Meshtastic firmware [release](https://github.com/meshtastic/Meshtastic-esp32/releases).
-2. Download [ESPHome Flasher](https://github.com/esphome/esphome-flasher/releases) (either x86-32bit Windows or x64-64 bit Windows).
-3. Connect your radio to your USB port and open ESPHome Flasher.
-4. If your board is not showing under Serial Port then you likely need to install the drivers for the CP210X serial chip. In Windows you can check by searching “Device Manager” and ensuring the device is shown under “Ports”.
-5. If there is an error, download the drivers [here](https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers), then unzip and run the Installer application.
-6. In ESPHome Flasher, refresh the serial ports and select your board.
-7. Browse to the previously downloaded firmware and select the correct firmware based on the board type, country and frequency.
-8. Select Flash ESP.
-9. Once complete, “Done! Flashing is complete!” will be shown.
-10. Debug messages sent from the Meshtastic device can be viewed with a terminal program such as [PuTTY](https://www.putty.org/) (Windows only). Within PuTTY, click “Serial”, enter the “Serial line” com port (can be found at step 4), enter “Speed” as 921600, then click “Open”.
-
-### Installing from a commandline
-
-These instructions currently require a few commmand lines, but it should be pretty straightforward.
-
-1. Install "pip". Pip is the python package manager we use to get the esptool installer app. Instructions [here](https://www.makeuseof.com/tag/install-pip-for-python/). If you are using OS-X, see these [special instructions](docs/software/install-OSX.md).
-2. Run "pip install --upgrade esptool" to get esptool installed on your machine.
-3. Connect your radio to your USB port.
-4. Confirm that your device is talking to your PC by running "esptool.py chip_id". The Heltec build also works on the TTGO LORA32 radio. You should see something like:
-
-```
-mydir$ esptool.py chip_id
-esptool.py v2.6
-Found 2 serial ports
-Serial port /dev/ttyUSB0
-Connecting....
-Detecting chip type... ESP32
-Chip is ESP32D0WDQ6 (revision 1)
-Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
-MAC: 24:6f:28:b5:36:71
-Uploading stub...
-Running stub...
-Stub running...
-Warning: ESP32 has no Chip ID. Reading MAC instead.
-MAC: 24:6f:28:b5:36:71
-Hard resetting via RTS pin...
-```
-
-5. cd into the directory where the release zip file was expanded.
-6. Install the correct firmware for your board with `device-install.sh -f firmware-_board_-_country_.bin`.
- - Example: `./device-install.sh -f firmware-HELTEC-US-0.0.3.bin`.
-7. To update run `device-update.sh -f firmware-_board_-_country_.bin`
- - Example: `./device-update.sh -f firmware-HELTEC-US-0.0.3.bin`.
-
-Note: If you have previously installed meshtastic, you don't need to run this full script instead just run `esptool.py --baud 921600 write_flash 0x10000 firmware-_board_-_country_-_version_.bin`. This will be faster, also all of your current preferences will be preserved.
-
-You should see something like this:
-
-```
-kevinh@kevin-server:~/development/meshtastic/meshtastic-esp32/release/latest$ ./device-install.sh firmware-TBEAM-US-0.1.8.bin
-Trying to flash firmware-TBEAM-US-0.1.8.bin, but first erasing and writing system information
-esptool.py v2.6
-Found 2 serial ports
-Serial port /dev/ttyUSB0
-Connecting........____
-Detecting chip type... ESP32
-Chip is ESP32D0WDQ6 (revision 1)
-Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
-MAC: 24:6f:28:b2:01:6c
-Uploading stub...
-Running stub...
-Stub running...
-Changing baud rate to 921600
-Changed.
-Erasing flash (this may take a while)...
-Chip erase completed successfully in 6.1s
-Hard resetting via RTS pin...
-esptool.py v2.6
-Found 2 serial ports
-Serial port /dev/ttyUSB0
-Connecting.......
-Detecting chip type... ESP32
-Chip is ESP32D0WDQ6 (revision 1)
-Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
-MAC: 24:6f:28:b2:01:6c
-Uploading stub...
-Running stub...
-Stub running...
-Changing baud rate to 921600
-Changed.
-Configuring flash size...
-Auto-detected Flash size: 4MB
-Flash params set to 0x0220
-Compressed 61440 bytes to 11950...
-Wrote 61440 bytes (11950 compressed) at 0x00001000 in 0.2 seconds (effective 3092.4 kbit/s)...
-Hash of data verified.
-
-Leaving...
-Hard resetting via RTS pin...
-esptool.py v2.6
-Found 2 serial ports
-Serial port /dev/ttyUSB0
-Connecting.....
-Detecting chip type... ESP32
-Chip is ESP32D0WDQ6 (revision 1)
-Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
-MAC: 24:6f:28:b2:01:6c
-Uploading stub...
-Running stub...
-Stub running...
-Changing baud rate to 921600
-Changed.
-Configuring flash size...
-Auto-detected Flash size: 4MB
-Compressed 1223568 bytes to 678412...
-Wrote 1223568 bytes (678412 compressed) at 0x00010000 in 10.7 seconds (effective 912.0 kbit/s)...
-Hash of data verified.
-
-Leaving...
-Hard resetting via RTS pin...
-```
-
-7. The board will boot and show the Meshtastic logo.
-8. Please post a comment on our chat so we know if these instructions worked for you ;-). If you find bugs/have-questions post there also - we will be rapidly iterating over the next few weeks.
-
-# Meshtastic Android app
-
-The companion (optional) Meshtastic Android app is [here](https://play.google.com/store/apps/details?id=com.geeksville.mesh&referrer=utm_source%3Dgithub-dev-readme). You can also download it on Google Play.
-
-# Python API
-
-We offer a [python API](https://github.com/meshtastic/Meshtastic-python) that makes it easy to use these devices to provide mesh networking for your custom projects.
-
-# Development
-
-We'd love to have you join us on this merry little project. Please see our [development documents](./docs/software/sw-design.md) and [join us in our discussion forum](https://meshtastic.discourse.group/).
-
-# Credits
-
-This project is run by volunteers. We are a friendly group and welcome any contribution (code fixes, documentation, features, bug reports etc...). We try to be good about listing contributor names in release notes, but it has become unwieldy for the main-devs to keep updating the list below and we've neglected it too long. If you'd like your name included in this list please send a pull request to edit this README and simply add your line yourself. Thank you very much for your help!
-
-- @astro-arphid: Added support for 433MHz radios in europe.
-- @claesg: Various documentation fixes and 3D print enclosures
-- @girtsf: Lots of improvements
-- @spattinson: Fixed interrupt handling for the AXP192 part
-
-# IMPORTANT DISCLAIMERS AND FAQ
-
-For a listing of currently missing features and a FAQ click [here](docs/faq.md).
-
-Copyright 2019 Geeksville Industries, LLC. GPL V3 Licensed.
+For developer information and specific building instructions, please see the [developer doccumentation](https://meshtastic.org/docs/developers)
diff --git a/bin/build-all.sh b/bin/build-all.sh
index 110b3c915..b176df5fc 100755
--- a/bin/build-all.sh
+++ b/bin/build-all.sh
@@ -2,13 +2,15 @@
set -e
-VERSION=`bin/buildinfo.py`
+VERSION=`bin/buildinfo.py long`
+SHORT_VERSION=`bin/buildinfo.py short`
-BOARDS_ESP32="tlora-v2 tlora-v1 tlora_v1_3 tlora-v2-1-1.6 tbeam heltec tbeam0.7"
+BOARDS_ESP32="tlora-v2 tlora-v1 tlora_v1_3 tlora-v2-1-1.6 tbeam heltec-v2.0 heltec-v2.1 tbeam0.7"
#BOARDS_ESP32=tbeam
# FIXME note nrf52840dk build is for some reason only generating a BIN file but not a HEX file nrf52840dk-geeksville is fine
-BOARDS_NRF52="rak4631"
+BOARDS_NRF52="rak4631 t-echo"
+#BOARDS_NRF52=""
OUTDIR=release/latest
@@ -70,6 +72,9 @@ platformio lib update
do_boards "$BOARDS_ESP32" "false"
do_boards "$BOARDS_NRF52" "true"
+pio run --environment native
+cp .pio/build/native/program $OUTDIR/bins/universal/meshtasticd_linux_amd64
+
echo "Building SPIFFS for ESP32 targets"
pio run --environment tbeam -t buildfs
cp .pio/build/tbeam/spiffs.bin $OUTDIR/bins/universal/spiffs-$VERSION.bin
@@ -91,11 +96,15 @@ Generated by bin/buildall.sh -->
$VERSION
+ $SHORT_VERSION
XML
echo Generating $ARCHIVEDIR/firmware-$VERSION.zip
rm -f $ARCHIVEDIR/firmware-$VERSION.zip
-zip --junk-paths $ARCHIVEDIR/firmware-$VERSION.zip $ARCHIVEDIR/spiffs-$VERSION.bin $OUTDIR/bins/universal/firmware-*-$VERSION.* images/system-info.bin bin/device-install.sh bin/device-update.sh
+zip --junk-paths $ARCHIVEDIR/firmware-$VERSION.zip $ARCHIVEDIR/spiffs-$VERSION.bin $OUTDIR/bins/universal/firmware-*-$VERSION.* $OUTDIR/bins/universal/meshtasticd* images/system-info.bin bin/device-install.* bin/device-update.*
+echo Generating $ARCHIVEDIR/elfs-$VERSION.zip
+rm -f $ARCHIVEDIR/elfs-$VERSION.zip
+zip --junk-paths $ARCHIVEDIR/elfs-$VERSION.zip $OUTDIR/elfs/universal/firmware-*-$VERSION.*
echo BUILT ALL
diff --git a/bin/buildinfo.py b/bin/buildinfo.py
index 36f88f43e..6b123c9cf 100755
--- a/bin/buildinfo.py
+++ b/bin/buildinfo.py
@@ -1,7 +1,9 @@
#!/usr/bin/env python3
import configparser
+import sys
from readprops import readProps
-verStr = readProps('version.properties')
-print(f"{verStr}")
+verObj = readProps('version.properties')
+propName = sys.argv[1]
+print(f"{verObj[propName]}")
diff --git a/bin/device-install.bat b/bin/device-install.bat
new file mode 100644
index 000000000..4837d61b1
--- /dev/null
+++ b/bin/device-install.bat
@@ -0,0 +1,42 @@
+@ECHO OFF
+
+set PYTHON=python
+
+goto GETOPTS
+:HELP
+echo Usage: %~nx0 [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME^|FILENAME]
+echo Flash image file to device, but first erasing and writing system information
+echo.
+echo -h Display this help and exit
+echo -p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerrous).
+echo -P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: %PYTHON%)
+echo -f FILENAME The .bin file to flash. Custom to your device type and region.
+goto EOF
+
+:GETOPTS
+if /I "%1"=="-h" goto HELP
+if /I "%1"=="--help" goto HELP
+if /I "%1"=="-F" set "FILENAME=%2" & SHIFT
+if /I "%1"=="-p" set ESPTOOL_PORT=%2 & SHIFT
+if /I "%1"=="-P" set PYTHON=%2 & SHIFT
+SHIFT
+IF NOT "__%1__"=="____" goto GETOPTS
+
+IF "__%FILENAME%__" == "____" (
+ echo "Missing FILENAME"
+ goto HELP
+)
+IF EXIST %FILENAME% (
+ echo Trying to flash update %FILENAME%, but first erasing and writing system information"
+ %PYTHON% -m esptool --baud 921600 erase_flash
+ %PYTHON% -m esptool --baud 921600 write_flash 0x1000 system-info.bin
+ for %%f in (spiffs-*.bin) do (
+ %PYTHON% -m esptool --baud 921600 write_flash 0x00390000 %%f
+ )
+ %PYTHON% -m esptool --baud 921600 write_flash 0x10000 %FILENAME%
+) else (
+ echo "Invalid file: %FILENAME%"
+ goto HELP
+)
+
+:EOF
\ No newline at end of file
diff --git a/bin/device-update.bat b/bin/device-update.bat
new file mode 100644
index 000000000..4cede3db9
--- /dev/null
+++ b/bin/device-update.bat
@@ -0,0 +1,39 @@
+@ECHO OFF
+
+set PYTHON=python
+
+goto GETOPTS
+:HELP
+echo Usage: %~nx0 [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME^|FILENAME]
+echo Flash image file to device, leave existing system intact.
+echo.
+echo -h Display this help and exit
+echo -p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerrous).
+echo -P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: %PYTHON%)
+echo -f FILENAME The .bin file to flash. Custom to your device type and region.
+goto EOF
+
+:GETOPTS
+if /I "%1"=="-h" goto HELP
+if /I "%1"=="--help" goto HELP
+if /I "%1"=="-F" set "FILENAME=%2" & SHIFT
+if /I "%1"=="-p" set ESPTOOL_PORT=%2 & SHIFT
+if /I "%1"=="-P" set PYTHON=%2 & SHIFT
+SHIFT
+IF NOT "__%1__"=="____" goto GETOPTS
+
+IF "__%FILENAME%__" == "____" (
+ echo "Missing FILENAME"
+ goto HELP
+)
+IF EXIST %FILENAME% (
+ echo Trying to flash update %FILENAME%
+ %PYTHON% -m esptool --baud 921600 write_flash 0x10000 %FILENAME%
+ echo Erasing the otadata partition, which will turn off flash flippy-flop and force the first image to be used
+ %PYTHON% -m esptool --baud 921600 erase_region 0xe000 0x2000
+) else (
+ echo "Invalid file: %FILENAME%"
+ goto HELP
+)
+
+:EOF
\ No newline at end of file
diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py
index ce811c9cb..d299f954f 100644
--- a/bin/platformio-custom.py
+++ b/bin/platformio-custom.py
@@ -9,11 +9,12 @@ from readprops import readProps
Import("projenv")
prefsLoc = projenv["PROJECT_DIR"] + "/version.properties"
-verStr = readProps(prefsLoc)
-print("Using meshtastic platform-custom.py, firmare version " + verStr)
+verObj = readProps(prefsLoc)
+print("Using meshtastic platform-custom.py, firmare version " + verObj['long'])
# print("path is" + ','.join(sys.path))
# General options that are passed to the C and C++ compilers
projenv.Append(CCFLAGS=[
- "-DAPP_VERSION=" + verStr
+ "-DAPP_VERSION=" + verObj['long'],
+ "-DAPP_VERSION_SHORT=" + verObj['short']
])
diff --git a/bin/promote-release.sh b/bin/promote-release.sh
index 98e3d6748..b4828dd68 100755
--- a/bin/promote-release.sh
+++ b/bin/promote-release.sh
@@ -1,6 +1,8 @@
+set -e
+
echo "This script is only for developers who are publishing new builds on github. Most users don't need it"
-VERSION=`bin/buildinfo.py`
+VERSION=`bin/buildinfo.py long`
# Must have a V prefix to trigger github
git tag "v${VERSION}"
diff --git a/bin/readprops.py b/bin/readprops.py
index 7fc471774..25eba4852 100644
--- a/bin/readprops.py
+++ b/bin/readprops.py
@@ -12,6 +12,8 @@ def readProps(prefsLoc):
config = configparser.RawConfigParser()
config.read(prefsLoc)
version = dict(config.items('VERSION'))
+ verObj = dict(short = "{}.{}.{}".format(version["major"], version["minor"], version["build"]),
+ long = "unset")
# Try to find current build SHA if if the workspace is clean. This could fail if git is not installed
try:
@@ -23,14 +25,13 @@ def readProps(prefsLoc):
if isDirty:
# short for 'dirty', we want to keep our verstrings source for protobuf reasons
suffix = sha + "-d"
- verStr = "{}.{}.{}.{}".format(
+ verObj['long'] = "{}.{}.{}.{}".format(
version["major"], version["minor"], version["build"], suffix)
except:
# print("Unexpected error:", sys.exc_info()[0])
# traceback.print_exc()
- verStr = "{}.{}.{}".format(
- version["major"], version["minor"], version["build"])
+ verObj['long'] = verObj['short']
# print("firmare version " + verStr)
- return verStr
+ return verObj
# print("path is" + ','.join(sys.path))
diff --git a/bin/regen-protos.sh b/bin/regen-protos.sh
index a3335e535..2d96f2b7b 100755
--- a/bin/regen-protos.sh
+++ b/bin/regen-protos.sh
@@ -10,6 +10,6 @@ echo "prebuilt binaries for your computer into nanopb-0.4.4"
cd proto
../nanopb-0.4.4/generator-bin/protoc --nanopb_out=-v:../src/mesh/generated -I=../proto *.proto
-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
\ No newline at end of file
+#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
\ No newline at end of file
diff --git a/bin/test-simulator.sh b/bin/test-simulator.sh
new file mode 100755
index 000000000..46158dc56
--- /dev/null
+++ b/bin/test-simulator.sh
@@ -0,0 +1,9 @@
+set -e
+
+echo "Starting simulator"
+.pio/build/native/program &
+sleep 20 # 5 seconds was not enough
+
+echo "Simulator started, launching python test..."
+python3 -c 'from meshtastic.test import testSimulator; testSimulator()'
+
diff --git a/bin/upload-to-bootloader.sh b/bin/upload-to-bootloader.sh
index 95ade3e37..5ee008057 100755
--- a/bin/upload-to-bootloader.sh
+++ b/bin/upload-to-bootloader.sh
@@ -1,5 +1,8 @@
set -e
-echo "Converting to uf2 for NRF52 Adafruit bootloader"
-bin/uf2conv.py .pio/build/rak4631/firmware.hex -f 0xADA52840
+echo "building for t-echo"
+pio run --environment t-echo
+
+echo "Converting to uf2 for NRF52 Adafruit bootloader - double tap on the reset button to force bootloader entry"
+bin/uf2conv.py .pio/build/t-echo/firmware.hex -f 0xADA52840
cp flash.uf2 /media/kevinh/FTH*BOOT/
diff --git a/bin/upload-to-rak4631.sh b/bin/upload-to-rak4631.sh
new file mode 100755
index 000000000..95ade3e37
--- /dev/null
+++ b/bin/upload-to-rak4631.sh
@@ -0,0 +1,5 @@
+set -e
+
+echo "Converting to uf2 for NRF52 Adafruit bootloader"
+bin/uf2conv.py .pio/build/rak4631/firmware.hex -f 0xADA52840
+cp flash.uf2 /media/kevinh/FTH*BOOT/
diff --git a/boards/nrf52840_dk_modified.json b/boards/nrf52840_dk_modified.json
index cbd246480..0462c55f8 100644
--- a/boards/nrf52840_dk_modified.json
+++ b/boards/nrf52840_dk_modified.json
@@ -16,9 +16,9 @@
"name": "adafruit"
},
"softdevice": {
- "sd_flags": "-DS113",
- "sd_name": "s113",
- "sd_version": "7.2.0",
+ "sd_flags": "-DS140",
+ "sd_name": "s140",
+ "sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
diff --git a/boards/eink.json b/boards/t-echo.json
similarity index 71%
rename from boards/eink.json
rename to boards/t-echo.json
index e2414b23f..da67ef05e 100644
--- a/boards/eink.json
+++ b/boards/t-echo.json
@@ -15,7 +15,7 @@
],
"usb_product": "TTGO_eink",
"mcu": "nrf52840",
- "variant": "eink",
+ "variant": "t-echo",
"variants_dir": "variants",
"bsp": {
"name": "adafruit"
@@ -44,17 +44,20 @@
"arduino"
],
"name": "TTGO eink (Adafruit BSP)",
- "upload": {
- "maximum_ram_size": 248832,
- "maximum_size": 815104,
- "require_upload_port": true,
- "speed": 115200,
- "protocol": "jlink",
- "protocols": [
- "jlink",
- "nrfjprog",
- "stlink"
- ]
+ "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": "FIXME",
"vendor": "TTGO"
diff --git a/data/static/.gitkeep b/data/static/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/data/static/index.html b/data/static/index.html
deleted file mode 100644
index 9297288c7..000000000
--- a/data/static/index.html
+++ /dev/null
@@ -1 +0,0 @@
-not yet supported - soon will be included in build
diff --git a/geeksville-private/TODO.md b/geeksville-private/TODO.md
index 6fefdd616..732627108 100644
--- a/geeksville-private/TODO.md
+++ b/geeksville-private/TODO.md
@@ -2,22 +2,21 @@
You probably don't care about this section - skip to the next one.
-## before next release
-
-* @havealoha fixedposition not working
-* pine64 lora module
-* merge https://meshtastic.discourse.group/t/spanish-translation-update/2986/5
-* nrf52 USB is unreliable while sleeping?
-* @luxonn reports that after a while the android app stops showing new messages
-* nrf52 shows as "sleeping" in android app? (but led is blinking)
+* usb lora dongle from pine64, add end user instructions
+* measure rak4630 power draw and turn off power for GPS most of the time. We should be able to run on the small solar panel.
+* turn on watchdog reset if app hangs on nrf52 or esp32
+* pine64 solar boards
+* for the matrix gateway? recommended by @sam-uk https://github.com/matrix-org/coap-proxy
+* figure our wss for mqtt.meshtastic - use cloudflare? 2052 ws, 2053 crypt
* ask for vercel access
-* fix heltec battery scaling
-* check android 1.2.20 usage, possibly release to general
-* release android APK
-
-* add rak4600 support (with rf95 radio and limited ram)
-
-* Switch to use https://github.com/adafruit/Adafruit_nRF52_Arduino.git when available (see arduino code for examples)
+* finish plan for riot.im
+* turn on setTx(timeout) and state = setDioIrqParams(SX126X_IRQ_TX_DONE | SX126X_IRQ_TIMEOUT, SX126X_IRQ_TX_DONE | SX126X_IRQ_TIMEOUT); in sx1262 code
+* NO add rak4600 support (with rf95 radio and limited ram)
+* store esp32 crashes to flash (and 64KB coredump partition) - https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/core_dump.html
+* If more nodes appear than the nodedb can hold, delete oldest entries from DB
+* send debug info 'in-band'
+* DONE @luxonn reports that after a while the android app stops showing new messages
+* DONE release android APK - fix recent 1.2.28 crash report
* DONE remote admin busted?
* DONE check android code - @havealoha comments about odd sleep behavior
* ABANDONED test github actions locally on linux
@@ -28,7 +27,6 @@ You probably don't care about this section - skip to the next one.
* DONE tcp stream problem in python+pordtuino, server thinks client dropped when client DID NOT DROP
* DONE TCP mode for android, localhost is at 10.0.2.2
* DONE make sure USB still works in android
-* add portduino builds to zip
* add license to portduino and make announcement
* DONE naks are being dropped (though enqueuedLocal) sometimes before phone/PC gets them
* DONE have android fill in if local GPS has poor signal
diff --git a/geeksville-private/bl602.md b/geeksville-private/bl602.md
new file mode 100644
index 000000000..6f42f4c66
--- /dev/null
+++ b/geeksville-private/bl602.md
@@ -0,0 +1,25 @@
+
+* nutcracker https://www.pine64.org/2020/10/28/nutcracker-challenge-blob-free-wifi-ble/
+* https://github.com/pine64/bl_iot_sdk
+* https://github.com/pine64/bl602-docs / https://pine64.github.io/bl602-docs/
+* https://github.com/pine64/ArduinoCore-bouffalo
+
+
+cd ~/packages
+git clone --recursive https://github.com/pine64/bl_iot_sdk
+
+https://github.com/spacemeowx2/blflash/releases
+
+
+# FIXME or BL604
+cd bl_iot_sdk
+export BL60X_SDK_PATH=/home/kevinh/packages/bl_iot_sdk
+export CONFIG_CHIP_NAME=BL602
+cd customer_app/bl602_boot2
+make
+
+* todo run hello world on hardware (check for bl604 vs bl602 first)
+* build/run in the crummy arduino environment
+* build in platformio
+
+https://lupyuen.github.io/articles/lorawan2
diff --git a/geeksville-private/pine64.md b/geeksville-private/pine64.md
new file mode 100644
index 000000000..86691277a
--- /dev/null
+++ b/geeksville-private/pine64.md
@@ -0,0 +1,51 @@
+# Notes on the pine64 lora board
+
+like before but sx1262 based?
+
+Since both DIO3 and DIO2 not apply to PINE64 LoRa situation, I will wire SX1262 INT [DIO1] pin, contact to CS341F pin 7 INT# and pin 5.
+
+FIX ch341 GPIO access from linux
+RF95 packet RX seems busted FIX FIRST
+
+USE ch341 devboard if needed
+
+SX1262 BUSY seems to come out on pin 15 of the RFM90 HOPE module. The 'footprint' seems rotated on the pine64 board schematic and that becomes pin 7 on U4 which is "DIO5" on that schematic. Which goes to pin 8 on the CH341F, which that datasheet calls "IN3"
+
+FIXME - see if possible to read BUSY from "IN3"?
+
+on a ch341a
+ * - Pin 15 (D0/CS0 ) as input/output/CS (CH341_PIN_MODE_IN/CH341_PIN_MODE_OUT/CH341_PIN_MODE_CS) (confirm hooked to CS)
+ * - Pin 16 (D1/CS1 ) as input/output/CS (CH341_PIN_MODE_IN/CH341_PIN_MODE_OUT/CH341_PIN_MODE_CS)
+ * - Pin 17 (D2/CS2 ) as input/output/CS (CH341_PIN_MODE_IN/CH341_PIN_MODE_OUT/CH341_PIN_MODE_CS)
+ * - Pin 19 (D4/DOUT2) as input/output (CH341_PIN_MODE_IN/CH341_PIN_MODE_OUT) / gpio4 in linux driver / (FIXME: confirm hooked to IRQ also?)
+ * - Pin 21 (D6/DIN2 ) as input (CH341_PIN_MODE_IN) / called RTS when in UART mode
+
+## ch341-driver
+
+driver busted in 5.11 kernels (rf95 init fails). 5.8.0 is okay, 5.8.18 is okay. fails on 5.10.31, 5.9.16 is okay. Therefore breakage happened in 5.10 kernels! Possibly not really breakage, possibly just sloppy caching or something that is more easily caught with modern threading.
+
+cs_change is not being set on the way into the driver!
+
+## gpio
+
+the new GPIO interface https://embeddedbits.org/new-linux-kernel-gpio-user-space-interface/
+
+~/development/meshtastic/meshtastic-esp32$ gpiodetect
+gpiochip0 [INT34BB:00] (312 lines)
+gpiochip1 [ch341] (2 lines)
+~/development/meshtastic/meshtastic-esp32$ gpioinfo 1
+gpiochip1 - 2 lines:
+ line 0: "gpio4" unused input active-high
+ line 1: "gpio5" unused input active-high
+gpiofind gpio4
+gpiochip1 0
+
+DO NOT "apt install libgpiod-dev" It doesn't work with kernels newer than about 5.8. Instead build and install from source: https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/
+
+## Send in patch
+Fix drivers/spi/spi.c transfer_once
+
+read about spi: https://elinux.org/images/2/20/Whats_going_on_with_SPI--mark_brown.pdf
+
+https://www.kernel.org/doc/html/v4.17/process/submitting-patches.html
+git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
\ No newline at end of file
diff --git a/geeksville-private/windows-build-instructions.md b/geeksville-private/windows-build-instructions.md
new file mode 100644
index 000000000..9a7ec19e8
--- /dev/null
+++ b/geeksville-private/windows-build-instructions.md
@@ -0,0 +1,7 @@
+
+
+* install python
+* install git (including git-bash)
+* install platformio
+* install vscode
+* install https://sourceforge.net/projects/mingw-w64/ (for windows gcc/g++) - you'll need to add the bin directory to your PATH
diff --git a/platformio.ini b/platformio.ini
index 889ea5bae..0081ffbd2 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -9,18 +9,19 @@
; https://docs.platformio.org/page/projectconf.html
[platformio]
-default_envs = tbeam
+;default_envs = tbeam
;default_envs = tbeam0.7
-;default_envs = heltec
+;default_envs = heltec-v2.0
;default_envs = tlora-v1
;default_envs = tlora_v1_3
;default_envs = tlora-v2
;default_envs = lora-relay-v1 # nrf board
-;default_envs = eink
+;default_envs = t-echo
;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 = rak4631
;default_envs = rak4630
+default_envs = meshtastic-diy-v1
[common]
; common is not currently used
@@ -70,9 +71,9 @@ lib_deps =
https://github.com/meshtastic/esp8266-oled-ssd1306.git#35d796226b853b0c0ff818b2f1aa3d35e7296a96 ; ESP8266_SSD1306
https://github.com/geeksville/OneButton.git ; OneButton library for non-blocking button debounce
1202 ; CRC32, explicitly needed because dependency is missing in the ble ota update lib
- https://github.com/meshtastic/arduino-fsm.git#829e967b8a95c094f73c60ef8dacfe66eae38940
+ https://github.com/meshtastic/arduino-fsm.git
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git#31015a55e630a2df77d9d714669c621a5bf355ad
- https://github.com/meshtastic/RadioLib.git#07de964e929238949035fb0d5887026a3058df1a
+ https://github.com/meshtastic/RadioLib.git#5582ac30578ff3f53f20630a00b2a8a4b8f92c74
https://github.com/meshtastic/TinyGPSPlus.git#f0f47067ef2f67c856475933188251c1ef615e79
https://github.com/meshtastic/AXP202X_Library.git#8404abb6d4b486748636bc6ad72d2a47baaf5460
Wire ; explicitly needed here because the AXP202 library forgets to add it
@@ -88,7 +89,8 @@ framework = arduino
lib_deps =
${env.lib_deps}
-build_flags = ${env.build_flags} -Os
+build_flags = ${env.build_flags} -Os
+# -DRADIOLIB_GODMODE
src_filter = ${env.src_filter} -
@@ -109,6 +111,8 @@ lib_deps =
https://github.com/meshtastic/esp32_https_server.git
adafruit/DHT sensor library@^1.4.1
adafruit/Adafruit Unified Sensor@^1.1.4
+ paulstoffregen/OneWire@^2.3.5
+ robtillaart/DS18B20@^0.1.11
# Hmm - this doesn't work yet
# board_build.ldscript = linker/esp32.extram.bss.ld
lib_ignore = segger_rtt
@@ -150,10 +154,19 @@ board = ttgo-t-beam
build_flags =
${esp32_base.build_flags} -D TBEAM_V07
-[env:heltec]
+[env:heltec-v2.0]
;build_type = debug ; to make it possible to step through our jtag debugger
extends = esp32_base
board = heltec_wifi_lora_32_V2
+build_flags =
+ ${esp32_base.build_flags} -D HELTEC_V2_0
+
+[env:heltec-v2.1]
+;build_type = debug ; to make it possible to step through our jtag debugger
+extends = esp32_base
+board = heltec_wifi_lora_32_V2
+build_flags =
+ ${esp32_base.build_flags} -D HELTEC_V2_1
[env:tlora-v1]
extends = esp32_base
@@ -180,16 +193,25 @@ board = ttgo-lora32-v1
build_flags =
${esp32_base.build_flags} -D TLORA_V2_1_16
+; Meshtastic DIY v1 by Nano VHF Schematic based on ESP32-WROOM-32 (38 pins) devkit & EBYTE E22 SX1262/SX1268 module
+[env:meshtastic-diy-v1]
+extends = esp32_base
+board = esp32doit-devkit-v1
+build_flags =
+ ${esp32_base.build_flags}
+ -D DIY_V1
+ -D EBYTE_E22
+
; The Heltec Cubecell plus
; IMPORTANT NOTE: This target doesn't yet work and probably won't ever work. I'm keeping it around for now.
; For more details see my post in the forum.
-[env:cubecellplus]
-platform = https://github.com/HelTecAutomation/platform-asrmicro650x.git ; we use top-of-tree because stable version has too many bugs - asrmicro650x
-board = cubecell_board_plus
+;[env:cubecellplus]
+;platform = https://github.com/HelTecAutomation/platform-asrmicro650x.git ; we use top-of-tree because stable version has too many bugs - asrmicro650x
+;board = cubecell_board_plus
; FIXME, bug in cubecell arduino - they are supposed to set ARDUINO
-build_flags = ${arduino_base.build_flags} -DARDUINO=100 -Isrc/cubecell
-src_filter =
- ${arduino_base.src_filter} - -
+;build_flags = ${arduino_base.build_flags} -DARDUINO=100 -Isrc/cubecell
+;src_filter =
+; ${arduino_base.src_filter} - -
; Common settings for NRF52 based targets
[nrf52_base]
@@ -208,10 +230,10 @@ src_filter =
${arduino_base.src_filter} - - - - - -
lib_ignore =
BluetoothOTA
-monitor_port = /dev/ttyACM1
+; monitor_port = /dev/ttyACM1
# we pass in options to jlink so it can understand freertos (note: we don't use "jlink" as the tool)
-debug_tool = jlink
+;debug_tool = jlink
debug_port = :2331
# Note: the ARGUMENTS MUST BE on multiple lines. Otherwise platformio/commands/debug/helpers.py misparses everything into the "executable"
# attribute and leaves "arguments" empty
@@ -245,9 +267,12 @@ debug_init_break =
[nrf52840_base]
; Common base class for all nrf52840 based targets
extends = nrf52_base
+; was -DTINY_USB
+build_flags = ${nrf52_base.build_flags}
lib_deps =
${arduino_base.lib_deps}
Adafruit nRFCrypto
+ # Adafruit TinyUSB Arduino
# add Adafruit nRFCrypto platform IO automated scan is broken
[env:lora_isp4520]
@@ -305,12 +330,13 @@ extends = nrf52840_base
board = wiscore_rak4631
# add our variants files to the include and src paths
# define build flags for the TFT_eSPI library
-build_flags = ${nrf52_base.build_flags} -Ivariants/WisCore_RAK4631_Board
+build_flags = ${nrf52840_base.build_flags} -Ivariants/WisCore_RAK4631_Board
src_filter = ${nrf52_base.src_filter} +<../variants/WisCore_RAK4631_Board>
debug_tool = jlink
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
;upload_protocol = jlink
+; Note, this board is not yet supported! It will not work without futher development.
; THIS IS UNTESTED (I don't have this board), but other developers can use it as a starting point
[env:rak4600]
extends = nrf52_base
@@ -340,32 +366,36 @@ lib_deps =
${arduino_base.lib_deps}
; First prototype eink/nrf52840/sx1262 device
-[env:eink]
+[env:t-echo]
extends = nrf52840_base
-board = eink
+board = t-echo
+debug_tool = jlink
+upload_protocol = jlink
# add our variants files to the include and src paths
-# define build flags for the TFT_eSPI library
-build_flags = ${nrf52_base.build_flags} -Ivariants/eink
- -DBUSY_PIN=3 -DRST_PIN=2 -DDC_PIN=28 -DCS_PIN=30
-src_filter = ${nrf52_base.src_filter} +<../variants/eink>
+# define build flags for the TFT_eSPI library - NOTE: WE NOT LONGER USE TFT_eSPI, it was for an earlier version of the TTGO eink screens
+# -DBUSY_PIN=3 -DRST_PIN=2 -DDC_PIN=28 -DCS_PIN=30
+# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling.
+build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo
+src_filter = ${nrf52_base.src_filter} +<../variants/t-echo>
lib_deps =
${nrf52840_base.lib_deps}
- https://github.com/geeksville/EPD_Libraries.git
- TFT_eSPI
+ https://github.com/geeksville/GxEPD2.git
+ adafruit/Adafruit BusIO
+;upload_protocol = fs
-; First prototype eink/nrf52840/sx1262 device
-[env:eink0.1]
-extends = nrf52840_base
-board = eink0.1
-# add our variants files to the include and src paths
-# define build flags for the TFT_eSPI library
-build_flags = ${nrf52_base.build_flags} -Ivariants/eink0.1
- -DBUSY_PIN=3 -DRST_PIN=2 -DDC_PIN=28 -DCS_PIN=30
-src_filter = ${nrf52_base.src_filter} +<../variants/eink0.1>
-lib_deps =
- ${nrf52840_base.lib_deps}
- https://github.com/geeksville/EPD_Libraries.git
- TFT_eSPI
+; First prototype eink/nrf52840/sx1262 device (removed from build because didn't ship in quantity)
+;[env:eink0.1]
+;extends = nrf52840_base
+;board = eink0.1
+;# add our variants files to the include and src paths
+;# define build flags for the TFT_eSPI library
+;build_flags = ${nrf52_base.build_flags} -Ivariants/eink0.1
+; -DBUSY_PIN=3 -DRST_PIN=2 -DDC_PIN=28 -DCS_PIN=30
+;src_filter = ${nrf52_base.src_filter} +<../variants/eink0.1>
+;lib_deps =
+; ${nrf52840_base.lib_deps}
+; https://github.com/geeksville/EPD_Libraries.git
+; TFT_eSPI
; The https://github.com/BigCorvus/SX1262-LoRa-BLE-Relay board by @BigCorvus
[env:lora-relay-v1]
@@ -373,7 +403,7 @@ extends = nrf52840_base
board = lora-relay-v1
# add our variants files to the include and src paths
# define build flags for the TFT_eSPI library
-build_flags = ${nrf52_base.build_flags} -Ivariants/lora_relay_v1
+build_flags = ${nrf52840_base.build_flags} -Ivariants/lora_relay_v1
-DUSER_SETUP_LOADED
-DTFT_WIDTH=80
-DTFT_HEIGHT=160
@@ -395,7 +425,7 @@ extends = nrf52840_base
board = lora-relay-v2
# add our variants files to the include and src paths
# define build flags for the TFT_eSPI library
-build_flags = ${nrf52_base.build_flags} -Ivariants/lora_relay_v2
+build_flags = ${nrf52840_base.build_flags} -Ivariants/lora_relay_v2
-DUSER_SETUP_LOADED
-DTFT_WIDTH=80
-DTFT_HEIGHT=160
@@ -413,13 +443,24 @@ lib_deps =
SparkFun BQ27441 LiPo Fuel Gauge Arduino Library
TFT_eSPI
-; The Portduino based sim environment on top of linux
+; The Portduino based sim environment on top of any host OS, all hardware will be simulated
[env:native]
platform = https://github.com/geeksville/platform-native.git
src_filter = ${env.src_filter} - - - - -
-build_flags = ${arduino_base.build_flags} -O0
+build_flags = ${arduino_base.build_flags} -O0
framework = arduino
-board = native
+board = cross_platform
+lib_deps =
+ ${arduino_base.lib_deps}
+ rweather/Crypto
+
+; The Portduino based sim environment on top of a linux OS and touching linux hardware devices
+[env:linux]
+platform = https://github.com/geeksville/platform-native.git
+src_filter = ${env.src_filter} - - - - -
+build_flags = ${arduino_base.build_flags} -O0 -lgpiod
+framework = arduino
+board = linux_hardware
lib_deps =
${arduino_base.lib_deps}
rweather/Crypto
diff --git a/src/BluetoothCommon.cpp b/src/BluetoothCommon.cpp
index a12a52cf9..0eda2080e 100644
--- a/src/BluetoothCommon.cpp
+++ b/src/BluetoothCommon.cpp
@@ -1,3 +1,4 @@
+#include "configuration.h"
#include "BluetoothCommon.h"
// NRF52 wants these constants as byte arrays
diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp
index 2ce7f0bbe..ac87ba05a 100644
--- a/src/FSCommon.cpp
+++ b/src/FSCommon.cpp
@@ -1,3 +1,4 @@
+#include "configuration.h"
#include "FSCommon.h"
void fsInit()
diff --git a/src/GPSStatus.h b/src/GPSStatus.h
index 237964eb0..7399d7e20 100644
--- a/src/GPSStatus.h
+++ b/src/GPSStatus.h
@@ -1,8 +1,11 @@
#pragma once
#include "Status.h"
#include "configuration.h"
+#include "NodeDB.h"
#include
+extern NodeDB nodeDB;
+
namespace meshtastic
{
@@ -16,28 +19,39 @@ class GPSStatus : public Status
bool hasLock = false; // default to false, until we complete our first read
bool isConnected = false; // Do we have a GPS we are talking to
- int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double
- int32_t altitude = 0;
- uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs
- // scaling before use)
- uint32_t heading = 0;
- uint32_t numSatellites = 0;
+
+ Position p = Position_init_default;
public:
GPSStatus() { statusType = STATUS_TYPE_GPS; }
+
+ // proposed for deprecation
GPSStatus(bool hasLock, bool isConnected, int32_t latitude, int32_t longitude, int32_t altitude, uint32_t dop,
uint32_t heading, uint32_t numSatellites)
: Status()
{
this->hasLock = hasLock;
this->isConnected = isConnected;
- this->latitude = latitude;
- this->longitude = longitude;
- this->altitude = altitude;
- this->dop = dop;
- this->heading = heading;
- this->numSatellites = numSatellites;
+
+ this->p.latitude_i = latitude;
+ this->p.longitude_i = longitude;
+ this->p.altitude = altitude;
+ this->p.PDOP = dop;
+ this->p.ground_track = heading;
+ this->p.sats_in_view = numSatellites;
}
+
+ // preferred method
+ GPSStatus(bool hasLock, bool isConnected, Position pos)
+ : Status()
+ {
+ this->hasLock = hasLock;
+ this->isConnected = isConnected;
+
+ // all-in-one struct copy
+ this->p = pos;
+ }
+
GPSStatus(const GPSStatus &);
GPSStatus &operator=(const GPSStatus &);
@@ -47,45 +61,85 @@ class GPSStatus : public Status
bool getIsConnected() const { return isConnected; }
- int32_t getLatitude() const { return latitude; }
+ int32_t getLatitude() const {
+ if (radioConfig.preferences.fixed_position){
+ DEBUG_MSG("WARNING: Using fixed latitude\n");
+ NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
+ return node->position.latitude_i;
+ } else {
+ return p.latitude_i;
+ }
+ }
- int32_t getLongitude() const { return longitude; }
+ int32_t getLongitude() const {
+ if (radioConfig.preferences.fixed_position){
+ DEBUG_MSG("WARNING: Using fixed longitude\n");
+ NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
+ return node->position.longitude_i;
+ } else {
+ return p.longitude_i;
+ }
+ }
- int32_t getAltitude() const { return altitude; }
+ int32_t getAltitude() const {
+ if (radioConfig.preferences.fixed_position){
+ DEBUG_MSG("WARNING: Using fixed altitude\n");
+ NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
+ return node->position.altitude;
+ } else {
+ return p.altitude;
+ }
+ }
- uint32_t getDOP() const { return dop; }
+ uint32_t getDOP() const { return p.PDOP; }
- uint32_t getHeading() const { return heading; }
+ uint32_t getHeading() const { return p.ground_track; }
- uint32_t getNumSatellites() const { return numSatellites; }
+ uint32_t getNumSatellites() const { return p.sats_in_view; }
bool matches(const GPSStatus *newStatus) const
{
- return (newStatus->hasLock != hasLock || newStatus->isConnected != isConnected || newStatus->latitude != latitude ||
- newStatus->longitude != longitude || newStatus->altitude != altitude || newStatus->dop != dop ||
- newStatus->heading != heading || newStatus->numSatellites != numSatellites);
+#if GPS_EXTRAVERBOSE
+ DEBUG_MSG("GPSStatus.match() new pos@%x to old pos@%x\n",
+ newStatus->p.pos_timestamp, p.pos_timestamp);
+#endif
+ return (newStatus->hasLock != hasLock ||
+ newStatus->isConnected != isConnected ||
+ newStatus->p.latitude_i != p.latitude_i ||
+ newStatus->p.longitude_i != p.longitude_i ||
+ newStatus->p.altitude != p.altitude ||
+ newStatus->p.altitude_hae != p.altitude_hae ||
+ newStatus->p.PDOP != p.PDOP ||
+ newStatus->p.ground_track != p.ground_track ||
+ newStatus->p.sats_in_view != p.sats_in_view);
}
+
int updateStatus(const GPSStatus *newStatus)
{
// Only update the status if values have actually changed
- bool isDirty;
- {
- isDirty = matches(newStatus);
- initialized = true;
- hasLock = newStatus->hasLock;
- isConnected = newStatus->isConnected;
- latitude = newStatus->latitude;
- longitude = newStatus->longitude;
- altitude = newStatus->altitude;
- dop = newStatus->dop;
- heading = newStatus->heading;
- numSatellites = newStatus->numSatellites;
+ bool isDirty = matches(newStatus);
+
+ if (isDirty && p.pos_timestamp &&
+ (newStatus->p.pos_timestamp == p.pos_timestamp)) {
+ // We can NEVER be in two locations at the same time! (also PR #886)
+ DEBUG_MSG("BUG!! positional timestamp unchanged from prev solution\n");
}
+
+ initialized = true;
+ hasLock = newStatus->hasLock;
+ isConnected = newStatus->isConnected;
+
+ p = newStatus->p;
+
if (isDirty) {
- if (hasLock)
- DEBUG_MSG("New GPS pos lat=%f, lon=%f, alt=%d, pdop=%f, heading=%f, sats=%d\n", latitude * 1e-7, longitude * 1e-7,
- altitude, dop * 1e-2, heading * 1e-5, numSatellites);
- else
+ if (hasLock) {
+ // In debug logs, identify position by @timestamp:stage (stage 3 = notify)
+ DEBUG_MSG("New GPS pos@%x:3 lat=%f, lon=%f, alt=%d, pdop=%.2f, track=%.2f, sats=%d\n",
+ p.pos_timestamp,
+ p.latitude_i * 1e-7, p.longitude_i * 1e-7,
+ p.altitude, p.PDOP * 1e-2, p.ground_track * 1e-5,
+ p.sats_in_view);
+ } else
DEBUG_MSG("No GPS lock\n");
onNewStatus.notifyObservers(this);
}
diff --git a/src/OSTimer.cpp b/src/OSTimer.cpp
index 9be4bd3db..f2b38f17c 100644
--- a/src/OSTimer.cpp
+++ b/src/OSTimer.cpp
@@ -1,5 +1,5 @@
-#include "OSTimer.h"
#include "configuration.h"
+#include "OSTimer.h"
/**
* Schedule a callback to run. The callback must _not_ block, though it is called from regular thread level (not ISR)
diff --git a/src/Observer.cpp b/src/Observer.cpp
index 1025f8bc0..6d1124684 100644
--- a/src/Observer.cpp
+++ b/src/Observer.cpp
@@ -1,2 +1,3 @@
+#include "configuration.h"
#include "Observer.h"
diff --git a/src/Power.cpp b/src/Power.cpp
index 61533887c..4d2eca29d 100644
--- a/src/Power.cpp
+++ b/src/Power.cpp
@@ -1,3 +1,4 @@
+#include "configuration.h"
#include "power.h"
#include "NodeDB.h"
#include "PowerFSM.h"
@@ -42,6 +43,7 @@ Power *power;
using namespace meshtastic;
+#ifndef AREF_VOLTAGE
#if defined(NRF52_SERIES)
/*
* Internal Reference is +/-0.6V, with an adjustable gain of 1/6, 1/5, 1/4,
@@ -56,6 +58,7 @@ using namespace meshtastic;
#else
#define AREF_VOLTAGE 3.3
#endif
+#endif
/**
* If this board has a battery level sensor, set this to a valid implementation
@@ -79,10 +82,13 @@ class AnalogBatteryLevel : public HasBatteryLevel
if (v < noBatVolt)
return -1; // If voltage is super low assume no battery installed
+#ifndef NRF52_SERIES
+ // 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 100 * (v - emptyVolt) / (fullVolt - emptyVolt);
+ return clamp((int)(100 * (v - emptyVolt) / (fullVolt - emptyVolt)), 0, 100);
}
/**
@@ -101,8 +107,13 @@ class AnalogBatteryLevel : public HasBatteryLevel
if (millis() - last_read_time_ms > min_read_interval) {
last_read_time_ms = millis();
uint32_t raw = analogRead(BATTERY_PIN);
- float scaled = 1000.0 * ADC_MULTIPLIER * (AREF_VOLTAGE / 1024.0) * raw;
- // DEBUG_MSG("raw val=%u scaled=%u\n", raw, (uint32_t)(scaled));
+ float scaled;
+ #ifndef VBAT_RAW_TO_SCALED
+ scaled = 1000.0 * ADC_MULTIPLIER * (AREF_VOLTAGE / 1024.0) * raw;
+ #else
+ scaled = VBAT_RAW_TO_SCALED(raw); //defined in variant.h
+ #endif
+ // DEBUG_MSG("battery gpio %d raw val=%u scaled=%u\n", BATTERY_PIN, raw, (uint32_t)(scaled));
last_read_value = scaled;
return scaled;
} else {
@@ -120,7 +131,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
/// If we see a battery voltage higher than physics allows - assume charger is pumping
/// in power
- virtual bool isVBUSPlug() { return getBattVoltage() > 1000 * chargingVolt; }
+ virtual bool isVBUSPlug() { return getBattVoltage() > chargingVolt; }
/// Assume charging if we have a battery and external power is connected.
/// we can't be smart enough to say 'full'?
@@ -129,17 +140,21 @@ class AnalogBatteryLevel : public HasBatteryLevel
private:
/// If we see a battery voltage higher than physics allows - assume charger is pumping
/// in power
- const float fullVolt = 4200, emptyVolt = 3270, chargingVolt = 4210, noBatVolt = 2100;
+
+ /// For heltecs with no battery connected, the measured voltage is 2204, so raising to 2230 from 2100
+ const float fullVolt = 4200, emptyVolt = 3270, chargingVolt = 4210, noBatVolt = 2230;
float last_read_value = 0.0;
uint32_t last_read_time_ms = 0;
-} analogLevel;
+};
+
+AnalogBatteryLevel analogLevel;
Power::Power() : OSThread("Power") {}
bool Power::analogInit()
{
#ifdef BATTERY_PIN
- DEBUG_MSG("Using analog input for battery level\n");
+ DEBUG_MSG("Using analog input %d for battery level\n", BATTERY_PIN);
// disable any internal pullups
pinMode(BATTERY_PIN, INPUT);
@@ -149,11 +164,19 @@ bool Power::analogInit()
adcAttachPin(BATTERY_PIN);
#endif
#ifdef NRF52_SERIES
+#ifdef VBAT_AR_INTERNAL
+ analogReference(VBAT_AR_INTERNAL);
+#else
analogReference(AR_INTERNAL); // 3.6V
#endif
+#endif
+#ifndef BATTERY_SENSE_RESOLUTION_BITS
+#define BATTERY_SENSE_RESOLUTION_BITS 10
+#endif
+
// adcStart(BATTERY_PIN);
- analogReadResolution(10); // Default of 12 is not very linear. Recommended to use 10 or 11 depending on needed resolution.
+ analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS); // Default of 12 is not very linear. Recommended to use 10 or 11 depending on needed resolution.
batteryLevel = &analogLevel;
return true;
#else
@@ -169,6 +192,7 @@ bool Power::setup()
found = analogInit();
}
enabled = found;
+ low_voltage_counter = 0;
return found;
}
@@ -215,9 +239,24 @@ void Power::readPowerStatus()
powerStatus.getIsCharging(), powerStatus.getBatteryVoltageMv(), powerStatus.getBatteryChargePercent());
newStatus.notifyObservers(&powerStatus);
+
+ // If we have a battery at all and it is less than 10% full, force deep sleep if we have more than 3 low readings in a row
+ // Supect fluctuating voltage on the RAK4631 to force it to deep sleep even if battery is at 85% after only a few days
+ #ifdef NRF52_SERIES
+ if (powerStatus.getHasBattery() && !powerStatus.getHasUSB()){
+ if (batteryLevel->getBattVoltage() < MIN_BAT_MILLIVOLTS){
+ low_voltage_counter++;
+ if (low_voltage_counter>3)
+ powerFSM.trigger(EVENT_LOW_BATTERY);
+ } else {
+ low_voltage_counter = 0;
+ }
+ }
+ #else
// If we have a battery at all and it is less than 10% full, force deep sleep
if (powerStatus.getHasBattery() && !powerStatus.getHasUSB() && batteryLevel->getBattVoltage() < MIN_BAT_MILLIVOLTS)
powerFSM.trigger(EVENT_LOW_BATTERY);
+ #endif
} else {
// No power sensing on this board - tell everyone else we have no idea what is happening
const PowerStatus powerStatus = PowerStatus(OptUnknown, OptUnknown, OptUnknown, -1, -1);
diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp
index e101c494b..91248c4e3 100644
--- a/src/PowerFSM.cpp
+++ b/src/PowerFSM.cpp
@@ -1,9 +1,8 @@
-
+#include "configuration.h"
#include "PowerFSM.h"
#include "GPS.h"
#include "MeshService.h"
#include "NodeDB.h"
-#include "configuration.h"
#include "graphics/Screen.h"
#include "main.h"
#include "sleep.h"
@@ -28,6 +27,7 @@ static bool isPowered()
static void sdsEnter()
{
+ DEBUG_MSG("Enter state: SDS\n");
// FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw
doDeepSleep(getPref_sds_secs() * 1000LL);
}
@@ -42,7 +42,7 @@ static void lsEnter()
screen->setOn(false);
secsSlept = 0; // How long have we been sleeping this time
- DEBUG_MSG("lsEnter end\n");
+ // DEBUG_MSG("lsEnter end\n");
}
static void lsIdle()
@@ -113,6 +113,7 @@ static void lsIdle()
static void lsExit()
{
+ DEBUG_MSG("Exit state: LS\n");
// setGPSPower(true); // restore GPS power
if (gps)
gps->forceWake(true);
@@ -120,6 +121,7 @@ static void lsExit()
static void nbEnter()
{
+ DEBUG_MSG("Enter state: NB\n");
screen->setOn(false);
setBluetoothEnable(false);
@@ -134,6 +136,7 @@ static void darkEnter()
static void serialEnter()
{
+ DEBUG_MSG("Enter state: SERIAL\n");
setBluetoothEnable(false);
screen->setOn(true);
screen->print("Serial connected\n");
@@ -146,6 +149,7 @@ static void serialExit()
static void powerEnter()
{
+ DEBUG_MSG("Enter state: POWER\n");
if (!isPowered()) {
// If we got here, we are in the wrong state - we should be in powered, let that state ahndle things
DEBUG_MSG("Loss of power in Powered\n");
@@ -175,6 +179,7 @@ static void powerExit()
static void onEnter()
{
+ DEBUG_MSG("Enter state: ON\n");
screen->setOn(true);
setBluetoothEnable(true);
@@ -203,7 +208,9 @@ static void screenPress()
screen->onPress();
}
-static void bootEnter() {}
+static void bootEnter() {
+ DEBUG_MSG("Enter state: BOOT\n");
+}
State stateSDS(sdsEnter, NULL, NULL, "SDS");
State stateLS(lsEnter, lsIdle, lsExit, "LS");
@@ -227,10 +234,9 @@ void PowerFSM_setup()
// if we are a router node, we go to NB (no need for bluetooth) otherwise we go to DARK (so we can send message to phone)
powerFSM.add_transition(&stateLS, isRouter ? &stateNB : &stateDARK, EVENT_WAKE_TIMER, NULL, "Wake timer");
- // Note we don't really use this transition, because when we wake from light sleep we _always_ transition to NB or dark and
- // then it handles things powerFSM.add_transition(&stateLS, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet");
-
- powerFSM.add_transition(&stateNB, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet, resetting win wake");
+ // We need this transition, because we might not transition if we were waiting to enter light-sleep, because when we wake from light sleep we _always_ transition to NB or dark and
+ powerFSM.add_transition(&stateLS, isRouter ? &stateNB : &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Received packet, exiting light sleep");
+ powerFSM.add_transition(&stateNB, &stateNB, EVENT_PACKET_FOR_PHONE, NULL, "Received packet, resetting win wake");
// Handle press events - note: we ignore button presses when in API mode
powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, NULL, "Press");
@@ -254,6 +260,9 @@ void PowerFSM_setup()
// if we are a router we don't turn the screen on for these things
if (!isRouter) {
+ // if any packet destined for phone arrives, turn on bluetooth at least
+ powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone");
+
// show the latest node when we get a new node db update
powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
@@ -293,13 +302,13 @@ void PowerFSM_setup()
powerFSM.add_transition(&stateDARK, &stateON, EVENT_FIRMWARE_UPDATE, NULL, "Got firmware update");
powerFSM.add_transition(&stateON, &stateON, EVENT_FIRMWARE_UPDATE, NULL, "Got firmware update");
- powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone");
-
powerFSM.add_timed_transition(&stateON, &stateDARK, getPref_screen_on_secs() * 1000, NULL, "Screen-on timeout");
// On most boards we use light-sleep to be our main state, but on NRF52 we just stay in DARK
State *lowPowerState = &stateLS;
+ uint32_t meshSds = 0;
+
#ifndef NRF52_SERIES
// We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally)
@@ -308,11 +317,12 @@ void PowerFSM_setup()
powerFSM.add_timed_transition(&stateNB, &stateLS, getPref_min_wake_secs() * 1000, NULL, "Min wake timeout");
powerFSM.add_timed_transition(&stateDARK, &stateLS, getPref_wait_bluetooth_secs() * 1000, NULL, "Bluetooth timeout");
+ meshSds = getPref_mesh_sds_timeout_secs();
#else
lowPowerState = &stateDARK;
+ meshSds = UINT32_MAX; //Workaround for now: Don't go into deep sleep on the RAK4631
#endif
- auto meshSds = getPref_mesh_sds_timeout_secs();
if (meshSds != UINT32_MAX)
powerFSM.add_timed_transition(lowPowerState, &stateSDS, meshSds * 1000, NULL, "mesh timeout");
// removing for now, because some users don't even have phones
diff --git a/src/PowerFSM.h b/src/PowerFSM.h
index e48b55d87..5541385a3 100644
--- a/src/PowerFSM.h
+++ b/src/PowerFSM.h
@@ -6,7 +6,7 @@
#define EVENT_PRESS 1
#define EVENT_WAKE_TIMER 2
-#define EVENT_RECEIVED_PACKET 3
+// #define EVENT_RECEIVED_PACKET 3
#define EVENT_PACKET_FOR_PHONE 4
#define EVENT_RECEIVED_TEXT_MSG 5
// #define EVENT_BOOT 6 // now done with a timed transition
diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp
index d88fbe4ba..dc0f3b958 100644
--- a/src/RedirectablePrint.cpp
+++ b/src/RedirectablePrint.cpp
@@ -1,10 +1,12 @@
+#include "configuration.h"
#include "RedirectablePrint.h"
#include "RTC.h"
#include "concurrency/OSThread.h"
-#include "configuration.h"
+// #include "wifi/WiFiServerAPI.h"
#include
#include
#include
+#include
/**
* A printer that doesn't go anywhere
@@ -24,6 +26,10 @@ size_t RedirectablePrint::write(uint8_t c)
SEGGER_RTT_PutChar(SEGGER_STDOUT_CH, c);
#endif
+ // FIXME - clean this up, the whole relationship of this class to SerialConsole to TCP/bluetooth debug log output is kinda messed up. But for now, just have this hack to
+ // optionally send chars to TCP also
+ //WiFiServerPort::debugOut(c);
+
dest->write(c);
return 1; // We always claim one was written, rather than trusting what the
// serial port said (which could be zero)
diff --git a/src/SPILock.cpp b/src/SPILock.cpp
index 43d841fc8..06163f67a 100644
--- a/src/SPILock.cpp
+++ b/src/SPILock.cpp
@@ -1,3 +1,4 @@
+#include "configuration.h"
#include "SPILock.h"
#include
#include
diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp
index 137fbcf0d..8f652e3fd 100644
--- a/src/SerialConsole.cpp
+++ b/src/SerialConsole.cpp
@@ -1,8 +1,7 @@
+#include "configuration.h"
#include "SerialConsole.h"
#include "NodeDB.h"
#include "PowerFSM.h"
-#include "configuration.h"
-#include
#define Port Serial
@@ -32,6 +31,13 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port)
emitRebooted();
}
+// For the serial port we can't really detect if any client is on the other side, so instead just look for recent messages
+bool SerialConsole::checkIsConnected()
+{
+ uint32_t now = millis();
+ return (now - lastContactMsec) < getPref_phone_timeout_secs() * 1000UL;
+}
+
/**
* we override this to notice when we've received a protobuf over the serial
* stream. Then we shunt off debug serial output.
@@ -46,14 +52,3 @@ bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len)
return StreamAPI::handleToRadio(buf, len);
}
-/// Hookable to find out when connection changes
-void SerialConsole::onConnectionChanged(bool connected)
-{
- if (connected) { // To prevent user confusion, turn off bluetooth while using the serial port api
- powerFSM.trigger(EVENT_SERIAL_CONNECTED);
- } else {
- // FIXME, we get no notice of serial going away, we should instead automatically generate this event if we haven't
- // received a packet in a while
- powerFSM.trigger(EVENT_SERIAL_DISCONNECTED);
- }
-}
\ No newline at end of file
diff --git a/src/SerialConsole.h b/src/SerialConsole.h
index b057c1931..cc3ec4ec3 100644
--- a/src/SerialConsole.h
+++ b/src/SerialConsole.h
@@ -25,8 +25,9 @@ class SerialConsole : public StreamAPI, public RedirectablePrint
}
protected:
- /// Hookable to find out when connection changes
- virtual void onConnectionChanged(bool connected);
+
+ /// Check the current underlying physical link to see if the client is currently connected
+ virtual bool checkIsConnected();
};
// A simple wrapper to allow non class aware code write to the console
diff --git a/src/airtime.cpp b/src/airtime.cpp
index 8d1800d52..221ba1193 100644
--- a/src/airtime.cpp
+++ b/src/airtime.cpp
@@ -1,5 +1,5 @@
+#include "configuration.h"
#include "airtime.h"
-#include
#define periodsToLog 48
diff --git a/src/concurrency/BinarySemaphoreFreeRTOS.cpp b/src/concurrency/BinarySemaphoreFreeRTOS.cpp
index 3d8097455..96c647756 100644
--- a/src/concurrency/BinarySemaphoreFreeRTOS.cpp
+++ b/src/concurrency/BinarySemaphoreFreeRTOS.cpp
@@ -1,5 +1,5 @@
-#include "concurrency/BinarySemaphoreFreeRTOS.h"
#include "configuration.h"
+#include "concurrency/BinarySemaphoreFreeRTOS.h"
#include
#ifdef HAS_FREE_RTOS
diff --git a/src/concurrency/BinarySemaphorePosix.cpp b/src/concurrency/BinarySemaphorePosix.cpp
index 44cd741f1..0de52b322 100644
--- a/src/concurrency/BinarySemaphorePosix.cpp
+++ b/src/concurrency/BinarySemaphorePosix.cpp
@@ -1,5 +1,5 @@
-#include "concurrency/BinarySemaphorePosix.h"
#include "configuration.h"
+#include "concurrency/BinarySemaphorePosix.h"
#ifndef HAS_FREE_RTOS
diff --git a/src/concurrency/InterruptableDelay.cpp b/src/concurrency/InterruptableDelay.cpp
index 80743cc22..fa664e7bb 100644
--- a/src/concurrency/InterruptableDelay.cpp
+++ b/src/concurrency/InterruptableDelay.cpp
@@ -1,5 +1,5 @@
-#include "concurrency/InterruptableDelay.h"
#include "configuration.h"
+#include "concurrency/InterruptableDelay.h"
namespace concurrency
{
diff --git a/src/concurrency/Lock.cpp b/src/concurrency/Lock.cpp
index ffe997f8d..bcf92496e 100644
--- a/src/concurrency/Lock.cpp
+++ b/src/concurrency/Lock.cpp
@@ -1,3 +1,4 @@
+#include "configuration.h"
#include "Lock.h"
#include
diff --git a/src/concurrency/LockGuard.cpp b/src/concurrency/LockGuard.cpp
index 56e8ac877..1c4d9e2e6 100644
--- a/src/concurrency/LockGuard.cpp
+++ b/src/concurrency/LockGuard.cpp
@@ -1,3 +1,4 @@
+#include "configuration.h"
#include "LockGuard.h"
namespace concurrency {
diff --git a/src/concurrency/NotifiedWorkerThread.cpp b/src/concurrency/NotifiedWorkerThread.cpp
index 78c15a6d0..9be2bc90d 100644
--- a/src/concurrency/NotifiedWorkerThread.cpp
+++ b/src/concurrency/NotifiedWorkerThread.cpp
@@ -1,5 +1,5 @@
-#include "NotifiedWorkerThread.h"
#include "configuration.h"
+#include "NotifiedWorkerThread.h"
#include "main.h"
#include
@@ -72,14 +72,21 @@ bool NotifiedWorkerThread::notifyLater(uint32_t delay, uint32_t v, bool overwrit
return didIt;
}
-int32_t NotifiedWorkerThread::runOnce()
+void NotifiedWorkerThread::checkNotification()
{
auto n = notification;
- enabled = false; // Only run once per notification
notification = 0; // clear notification
if (n) {
onNotify(n);
}
+}
+
+
+
+int32_t NotifiedWorkerThread::runOnce()
+{
+ enabled = false; // Only run once per notification
+ checkNotification();
return RUN_SAME;
}
diff --git a/src/concurrency/NotifiedWorkerThread.h b/src/concurrency/NotifiedWorkerThread.h
index cdb37c35b..628277850 100644
--- a/src/concurrency/NotifiedWorkerThread.h
+++ b/src/concurrency/NotifiedWorkerThread.h
@@ -38,8 +38,14 @@ class NotifiedWorkerThread : public OSThread
protected:
virtual void onNotify(uint32_t notification) = 0;
+ /// just calls checkNotification()
virtual int32_t runOnce();
+ /// Sometimes we might want to check notifications independently of when our thread was getting woken up (i.e. if we are about to change
+ /// radio transmit/receive modes we want to handle any pending interrupts first). You can call this method and if any notifications are currently
+ /// pending they will be handled immediately.
+ void checkNotification();
+
private:
/**
* Notify this thread so it can run
diff --git a/src/concurrency/OSThread.cpp b/src/concurrency/OSThread.cpp
index 4bde72b07..88c21d1c5 100644
--- a/src/concurrency/OSThread.cpp
+++ b/src/concurrency/OSThread.cpp
@@ -1,5 +1,5 @@
-#include "OSThread.h"
#include "configuration.h"
+#include "OSThread.h"
#include
namespace concurrency
diff --git a/src/configuration.h b/src/configuration.h
index 1e58b7001..8231d5551 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -188,11 +188,11 @@ along with this program. If not, see .
#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 SX1262_CS RF95_NSS // FIXME - we really should define LORA_CS instead
-#define SX1262_DIO1 LORA_DIO1
-#define SX1262_BUSY LORA_DIO2
-#define SX1262_RESET LORA_RESET
-#define SX1262_E22 // Not really an E22 but TTGO seems to be trying to clone that
+#define SX126X_CS RF95_NSS // FIXME - we really should define LORA_CS instead
+#define SX126X_DIO1 LORA_DIO1
+#define SX126X_BUSY LORA_DIO2
+#define SX126X_RESET LORA_RESET
+#define SX126X_E22 // Not really an E22 but TTGO seems to be trying to clone that
// Internally the TTGO module hooks the SX1262-DIO2 in to control the TX/RX switch (which is the default for the sx1262interface
// code)
#endif
@@ -226,9 +226,52 @@ along with this program. If not, see .
#define GPS_RX_PIN 12
#define GPS_TX_PIN 15
-#elif defined(ARDUINO_HELTEC_WIFI_LORA_32_V2)
+#elif defined(DIY_V1)
// This string must exactly match the case used in release file names or the android updater won't work
-#define HW_VENDOR HardwareModel_HELTEC
+#define HW_VENDOR HardwareModel_DIY_V1
+
+// For OLED LCD
+#define I2C_SDA 21
+#define I2C_SCL 22
+
+// GPS
+#undef GPS_RX_PIN
+#define GPS_RX_PIN 15
+//#undef GPS_TX_PIN
+//#define GPS_TX_PIN 12 // not connected
+
+#define BUTTON_PIN 39 // The middle button GPIO on the T-Beam
+
+#define LORA_DIO0 26 // a No connect on the SX1262/SX1268 module
+#define LORA_RESET 23 // RST for SX1276, and for SX1262/SX1268
+#define LORA_DIO1 33 // IRQ for SX1262/SX1268
+#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
+
+// supported modules list
+#define USE_SX1262
+#define USE_SX1268
+
+// common pinouts for SX126X modules
+#define SX126X_CS 18 // NSS for SX126X
+#define SX126X_DIO1 LORA_DIO1
+#define SX126X_BUSY LORA_DIO2
+#define SX126X_RESET LORA_RESET
+#define SX126X_RXEN 14
+#define SX126X_TXEN 13
+
+#ifdef EBYTE_E22
+// Internally the TTGO module hooks the SX126x-DIO2 in to control the TX/RX switch
+// (which is the default for the sx1262interface code)
+#define SX126X_E22
+#endif
+
+#elif defined(ARDUINO_HELTEC_WIFI_LORA_32_V2)
// the default ESP32 Pin of 15 is the Oled SCL, set to 36 and 37 and works fine.
// Tested on Neo6m module.
@@ -256,6 +299,25 @@ along with this program. If not, see .
#define LORA_DIO1 35 // Not really used
#define LORA_DIO2 34 // Not really used
+// ratio of voltage divider = 3.20 (R1=100k, R2=220k)
+#define ADC_MULTIPLIER 3.2
+
+#ifdef HELTEC_V2_0
+// This string must exactly match the case used in release file names or the android updater won't work
+#define HW_VENDOR HardwareModel_HELTEC_V2_0
+
+#define BATTERY_PIN 13 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
+
+#endif
+
+#ifdef HELTEC_V2_1
+// This string must exactly match the case used in release file names or the android updater won't work
+#define HW_VENDOR HardwareModel_HELTEC_V2_1
+
+#define BATTERY_PIN 37 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
+
+#endif
+
#elif defined(TLORA_V1)
// This string must exactly match the case used in release file names or the android updater won't work
#define HW_VENDOR HardwareModel_TLORA_V1
@@ -413,6 +475,10 @@ along with this program. If not, see .
#define HW_VENDOR HardwareModel_RAK4631
+#elif defined(TTGO_T_ECHO)
+
+#define HW_VENDOR HardwareModel_T_ECHO
+
#elif NRF52_SERIES
#define HW_VENDOR HardwareModel_NRF52_UNKNOWN
@@ -423,11 +489,10 @@ along with this program. If not, see .
#define USE_SIM_RADIO
-#define USE_RF95
-#define LORA_DIO0 26 // a No connect on the SX1262 module
-#define LORA_RESET RADIOLIB_NC
-#define LORA_DIO1 33 // Not really used
-#define LORA_DIO2 32 // Not really used
+// 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
@@ -435,6 +500,20 @@ along with this program. If not, see .
#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 // 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
+// HOPE RFM90 does not have a TCXO therefore not SX126X_E22
+#endif
+
#endif
// DEBUG LED
diff --git a/src/error.h b/src/error.h
index 12cb5a002..0893f02f8 100644
--- a/src/error.h
+++ b/src/error.h
@@ -4,5 +4,8 @@
#include "mesh/generated/mesh.pb.h" // For CriticalErrorCode
+/// A macro that include filename and line
+#define RECORD_CRITICALERROR(code) recordCriticalError(code, __LINE__, __FILE__)
+
/// Record an error that should be reported via analytics
-void recordCriticalError(CriticalErrorCode code = CriticalErrorCode_Unspecified, uint32_t address = 0);
+void recordCriticalError(CriticalErrorCode code = CriticalErrorCode_Unspecified, uint32_t address = 0, const char *filename = NULL);
diff --git a/src/gps/Air530GPS.cpp b/src/gps/Air530GPS.cpp
index 857118963..46070eb30 100644
--- a/src/gps/Air530GPS.cpp
+++ b/src/gps/Air530GPS.cpp
@@ -1,3 +1,4 @@
+#include "configuration.h"
#include "Air530GPS.h"
#include
diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp
index 080dd0eea..c1e480347 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -1,8 +1,7 @@
-
+#include "configuration.h"
#include "GPS.h"
#include "NodeDB.h"
#include "RTC.h"
-#include "configuration.h"
#include "sleep.h"
#include
@@ -81,6 +80,8 @@ GPS::~GPS()
notifyDeepSleepObserver.unobserve();
}
+bool GPS::hasLock() { return hasValidLocation; }
+
// Allow defining the polarity of the WAKE output. default is active high
#ifndef GPS_WAKE_ACTIVE
#define GPS_WAKE_ACTIVE 1
@@ -201,11 +202,13 @@ void GPS::publishUpdate()
if (shouldPublish) {
shouldPublish = false;
- DEBUG_MSG("publishing GPS lock=%d\n", hasLock());
+ // In debug logs, identify position by @timestamp:stage (stage 2 = publish)
+ DEBUG_MSG("publishing pos@%x:2, hasVal=%d, GPSlock=%d\n",
+ p.pos_timestamp, hasValidLocation, hasLock());
// Notify any status instances that are observing us
const meshtastic::GPSStatus status =
- meshtastic::GPSStatus(hasLock(), isConnected(), latitude, longitude, altitude, dop, heading, numSatellites);
+ meshtastic::GPSStatus(hasValidLocation, isConnected(), p);
newStatus.notifyObservers(&status);
}
}
@@ -243,6 +246,7 @@ int32_t GPS::runOnce()
bool gotLoc = lookForLocation();
if (gotLoc && !hasValidLocation) { // declare that we have location ASAP
+ DEBUG_MSG("hasValidLocation RISING EDGE\n");
hasValidLocation = true;
shouldPublish = true;
}
@@ -259,6 +263,10 @@ int32_t GPS::runOnce()
if (tooLong) {
// we didn't get a location during this ack window, therefore declare loss of lock
+ if (hasValidLocation) {
+ DEBUG_MSG("hasValidLocation FALLING EDGE (last read: %d)\n", gotLoc);
+ }
+ p = Position_init_default;
hasValidLocation = false;
}
@@ -326,6 +334,11 @@ GPS *createGps()
#ifdef NO_GPS
return nullptr;
#else
+#ifdef GPS_ALTITUDE_HAE
+ DEBUG_MSG("Using HAE altitude model\n");
+#else
+ DEBUG_MSG("Using MSL altitude model\n");
+#endif
// If we don't have bidirectional comms, we can't even try talking to UBLOX
#ifdef GPS_TX_PIN
// Init GPS - first try ublox
diff --git a/src/gps/GPS.h b/src/gps/GPS.h
index 6de72f29c..895d5607f 100644
--- a/src/gps/GPS.h
+++ b/src/gps/GPS.h
@@ -17,6 +17,10 @@ class GPS : private concurrency::OSThread
private:
uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0, lastWhileActiveMsec = 0;
+ /**
+ * hasValidLocation - indicates that the position variables contain a complete
+ * GPS location, valid and fresh (< gps_update_interval + gps_attempt_time)
+ */
bool hasValidLocation = false; // default to false, until we complete our first read
bool isAwake = false; // true if we want a location right now
@@ -39,11 +43,7 @@ class GPS : private concurrency::OSThread
/** If !0 we will attempt to connect to the GPS over I2C */
static uint8_t i2cAddress;
- int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double
- int32_t altitude = 0;
- uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs
- // scaling before use)
- uint32_t heading = 0; // Heading of motion, in degrees * 10^-5
+ Position p = Position_init_default;
GPS() : concurrency::OSThread("GPS") {}
@@ -57,8 +57,8 @@ class GPS : private concurrency::OSThread
*/
virtual bool setup();
- /// Returns ture if we have acquired GPS lock.
- bool hasLock() const { return hasValidLocation; }
+ /// Returns true if we have acquired GPS lock.
+ virtual bool hasLock();
/// Return true if we are connected to a GPS
bool isConnected() const { return hasGPS; }
diff --git a/src/gps/GeoCoord.cpp b/src/gps/GeoCoord.cpp
new file mode 100644
index 000000000..d09b1addb
--- /dev/null
+++ b/src/gps/GeoCoord.cpp
@@ -0,0 +1,450 @@
+#include "GeoCoord.h"
+
+GeoCoord::GeoCoord() {
+ _dirty = true;
+}
+
+GeoCoord::GeoCoord (int32_t lat, int32_t lon, int32_t alt) : _latitude(lat), _longitude(lon), _altitude(alt) {
+ GeoCoord::setCoords();
+}
+
+GeoCoord::GeoCoord (float lat, float lon, int32_t alt) : _altitude(alt) {
+ // Change decimial reprsentation to int32_t. I.e., 12.345 becomes 123450000
+ _latitude = int32_t(lat * 1e+7);
+ _longitude = int32_t(lon * 1e+7);
+ GeoCoord::setCoords();
+}
+
+GeoCoord::GeoCoord(double lat, double lon, int32_t alt): _altitude(alt) {
+ // Change decimial reprsentation to int32_t. I.e., 12.345 becomes 123450000
+ _latitude = int32_t(lat * 1e+7);
+ _longitude = int32_t(lon * 1e+7);
+ GeoCoord::setCoords();
+}
+
+// Initialize all the coordinate systems
+void GeoCoord::setCoords() {
+ double lat = _latitude * 1e-7;
+ double lon = _longitude * 1e-7;
+ GeoCoord::latLongToDMS(lat, lon, _dms);
+ GeoCoord::latLongToUTM(lat, lon, _utm);
+ GeoCoord::latLongToMGRS(lat, lon, _mgrs);
+ GeoCoord::latLongToOSGR(lat, lon, _osgr);
+ GeoCoord::latLongToOLC(lat, lon, _olc);
+ _dirty = false;
+}
+
+void GeoCoord::updateCoords(int32_t lat, int32_t lon, int32_t alt) {
+ // If marked dirty or new coordiantes
+ if(_dirty || _latitude != lat || _longitude != lon || _altitude != alt) {
+ _dirty = true;
+ _latitude = lat;
+ _longitude = lon;
+ _altitude = alt;
+ setCoords();
+ }
+}
+
+void GeoCoord::updateCoords(const double lat, const double lon, const int32_t alt) {
+ int32_t iLat = lat * 1e+7;
+ int32_t iLon = lon * 1e+7;
+ // If marked dirty or new coordiantes
+ if(_dirty || _latitude != iLat || _longitude != iLon || _altitude != alt) {
+ _dirty = true;
+ _latitude = iLat;
+ _longitude = iLon;
+ _altitude = alt;
+ setCoords();
+ }
+
+}
+
+void GeoCoord::updateCoords(const float lat, const float lon, const int32_t alt) {
+ int32_t iLat = lat * 1e+7;
+ int32_t iLon = lon * 1e+7;
+ // If marked dirty or new coordiantes
+ if(_dirty || _latitude != iLat || _longitude != iLon || _altitude != alt) {
+ _dirty = true;
+ _latitude = iLat;
+ _longitude = iLon;
+ _altitude = alt;
+ setCoords();
+ }
+}
+
+/**
+ * Converts lat long coordinates from decimal degrees to degrees minutes seconds format.
+ * DD°MM'SS"C DDD°MM'SS"C
+ */
+void GeoCoord::latLongToDMS(const double lat, const double lon, DMS &dms) {
+ if (lat < 0) dms.latCP = 'S';
+ else dms.latCP = 'N';
+
+ double latDeg = lat;
+
+ if (lat < 0)
+ latDeg = latDeg * -1;
+
+ dms.latDeg = floor(latDeg);
+ double latMin = (latDeg - dms.latDeg) * 60;
+ dms.latMin = floor(latMin);
+ dms.latSec = (latMin - dms.latMin) * 60;
+
+ if (lon < 0) dms.lonCP = 'W';
+ else dms.lonCP = 'E';
+
+ double lonDeg = lon;
+
+ if (lon < 0)
+ lonDeg = lonDeg * -1;
+
+ dms.lonDeg = floor(lonDeg);
+ double lonMin = (lonDeg - dms.lonDeg) * 60;
+ dms.lonMin = floor(lonMin);
+ dms.lonSec = (lonMin - dms.lonMin) * 60;
+}
+
+/**
+ * Converts lat long coordinates to UTM.
+ * based on this: https://github.com/walvok/LatLonToUTM/blob/master/latlon_utm.ino
+ */
+void GeoCoord::latLongToUTM(const double lat, const double lon, UTM &utm) {
+
+ const std::string latBands = "CDEFGHJKLMNPQRSTUVWXX";
+ utm.zone = int((lon + 180)/6 + 1);
+ utm.band = latBands[int(lat/8 + 10)];
+ double a = 6378137; // WGS84 - equatorial radius
+ double k0 = 0.9996; // UTM point scale on the central meridian
+ double eccSquared = 0.00669438; // eccentricity squared
+ double lonTemp = (lon + 180) - int((lon + 180)/360) * 360 - 180; //Make sure the longitude is between -180.00 .. 179.9
+ double latRad = toRadians(lat);
+ double lonRad = toRadians(lonTemp);
+
+ // Special Zones for Norway and Svalbard
+ if( lat >= 56.0 && lat < 64.0 && lonTemp >= 3.0 && lonTemp < 12.0 ) // Norway
+ utm.zone = 32;
+ if( lat >= 72.0 && lat < 84.0 ) { // Svalbard
+ if ( lonTemp >= 0.0 && lonTemp < 9.0 ) utm.zone = 31;
+ else if( lonTemp >= 9.0 && lonTemp < 21.0 ) utm.zone = 33;
+ else if( lonTemp >= 21.0 && lonTemp < 33.0 ) utm.zone = 35;
+ else if( lonTemp >= 33.0 && lonTemp < 42.0 ) utm.zone = 37;
+ }
+
+ double lonOrigin = (utm.zone - 1)*6 - 180 + 3; // puts origin in middle of zone
+ double lonOriginRad = toRadians(lonOrigin);
+ double eccPrimeSquared = (eccSquared)/(1 - eccSquared);
+ double N = a/sqrt(1 - eccSquared*sin(latRad)*sin(latRad));
+ double T = tan(latRad)*tan(latRad);
+ double C = eccPrimeSquared*cos(latRad)*cos(latRad);
+ double A = cos(latRad)*(lonRad - lonOriginRad);
+ double M = a*((1 - eccSquared/4 - 3*eccSquared*eccSquared/64 - 5*eccSquared*eccSquared*eccSquared/256)*latRad
+ - (3*eccSquared/8 + 3*eccSquared*eccSquared/32 + 45*eccSquared*eccSquared*eccSquared/1024)*sin(2*latRad)
+ + (15*eccSquared*eccSquared/256 + 45*eccSquared*eccSquared*eccSquared/1024)*sin(4*latRad)
+ - (35*eccSquared*eccSquared*eccSquared/3072)*sin(6*latRad));
+ utm.easting = (double)(k0*N*(A+(1-T+C)*pow(A, 3)/6 + (5-18*T+T*T+72*C-58*eccPrimeSquared)*A*A*A*A*A/120)
+ + 500000.0);
+ utm.northing = (double)(k0*(M+N*tan(latRad)*(A*A/2+(5-T+9*C+4*C*C)*A*A*A*A/24
+ + (61-58*T+T*T+600*C-330*eccPrimeSquared)*A*A*A*A*A*A/720)));
+
+ if(lat < 0)
+ utm.northing += 10000000.0; //10000000 meter offset for southern hemisphere
+}
+
+// Converts lat long coordinates to an MGRS.
+void GeoCoord::latLongToMGRS(const double lat, const double lon, MGRS &mgrs) {
+ const std::string e100kLetters[3] = { "ABCDEFGH", "JKLMNPQR", "STUVWXYZ" };
+ const std::string n100kLetters[2] = { "ABCDEFGHJKLMNPQRSTUV", "FGHJKLMNPQRSTUVABCDE" };
+ UTM utm;
+ latLongToUTM(lat, lon, utm);
+ mgrs.zone = utm.zone;
+ mgrs.band = utm.band;
+ double col = floor(utm.easting / 100000);
+ mgrs.east100k = e100kLetters[(mgrs.zone - 1) % 3][col - 1];
+ double row = (int32_t)floor(utm.northing / 100000.0) % 20;
+ mgrs.north100k = n100kLetters[(mgrs.zone-1)%2][row];
+ mgrs.easting = (int32_t)utm.easting % 100000;
+ mgrs.northing = (int32_t)utm.northing % 100000;
+}
+
+/**
+ * Converts lat long coordinates to Ordnance Survey Grid Reference (UK National Grid Ref).
+ * Based on: https://www.movable-type.co.uk/scripts/latlong-os-gridref.html
+ */
+void GeoCoord::latLongToOSGR(const double lat, const double lon, OSGR &osgr) {
+ char letter[] = "ABCDEFGHJKLMNOPQRSTUVWXYZ"; // No 'I' in OSGR
+ double a = 6377563.396; // Airy 1830 semi-major axis
+ double b = 6356256.909; // Airy 1830 semi-minor axis
+ double f0 = 0.9996012717; // National Grid point scale factor on the central meridian
+ double phi0 = toRadians(49);
+ double lambda0 = toRadians(-2);
+ double n0 = -100000;
+ double e0 = 400000;
+ double e2 = 1 - (b*b)/(a*a); // eccentricity squared
+ double n = (a - b)/(a + b);
+
+ double osgb_Latitude;
+ double osgb_Longitude;
+ convertWGS84ToOSGB36(lat, lon, osgb_Latitude, osgb_Longitude);
+ double phi = osgb_Latitude; // already in radians
+ double lambda = osgb_Longitude; // already in radians
+ double v = a * f0 / sqrt(1 - e2 * sin(phi) * sin(phi));
+ double rho = a * f0 * (1 - e2) / pow(1 - e2 * sin(phi) * sin(phi), 1.5);
+ double eta2 = v / rho - 1;
+ double mA = (1 + n + (5/4)*n*n + (5/4)*n*n*n) * (phi - phi0);
+ double mB = (3*n + 3*n*n + (21/8)*n*n*n) * sin(phi - phi0) * cos(phi + phi0);
+ // loss of precision in mC & mD due to floating point rounding can cause innaccuracy of northing by a few meters
+ double mC = (15/8*n*n + 15/8*n*n*n) * sin(2*(phi - phi0)) * cos(2*(phi + phi0));
+ double mD = (35/24)*n*n*n * sin(3*(phi - phi0)) * cos(3*(phi + phi0));
+ double m = b*f0*(mA - mB + mC - mD);
+
+ double cos3Phi = cos(phi)*cos(phi)*cos(phi);
+ double cos5Phi = cos3Phi*cos(phi)*cos(phi);
+ double tan2Phi = tan(phi)*tan(phi);
+ double tan4Phi = tan2Phi*tan2Phi;
+ double I = m + n0;
+ double II = (v/2)*sin(phi)*cos(phi);
+ double III = (v/24)*sin(phi)*cos3Phi*(5 - tan2Phi + 9*eta2);
+ double IIIA = (v/720)*sin(phi)*cos5Phi*(61 - 58*tan2Phi + tan4Phi);
+ double IV = v*cos(phi);
+ double V = (v/6)*cos3Phi*(v/rho - tan2Phi);
+ double VI = (v/120)*cos5Phi*(5 - 18*tan2Phi + tan4Phi + 14*eta2 - 58*tan2Phi*eta2);
+
+ double deltaLambda = lambda - lambda0;
+ double deltaLambda2 = deltaLambda*deltaLambda;
+ double northing = I + II*deltaLambda2 + III*deltaLambda2*deltaLambda2 + IIIA*deltaLambda2*deltaLambda2*deltaLambda2;
+ double easting = e0 + IV*deltaLambda + V*deltaLambda2*deltaLambda + VI*deltaLambda2*deltaLambda2*deltaLambda;
+
+ if (easting < 0 || easting > 700000 || northing < 0 || northing > 1300000) // Check if out of boundaries
+ osgr = { 'I', 'I', 0, 0 };
+ else {
+ uint32_t e100k = floor(easting / 100000);
+ uint32_t n100k = floor(northing / 100000);
+ int8_t l1 = (19 - n100k) - (19 - n100k) % 5 + floor((e100k + 10) / 5);
+ int8_t l2 = (19 - n100k) * 5 % 25 + e100k % 5;
+ osgr.e100k = letter[l1];
+ osgr.n100k = letter[l2];
+ osgr.easting = floor((int)easting % 100000);
+ osgr.northing = floor((int)northing % 100000);
+ }
+}
+
+/**
+ * Converts lat long coordinates to Open Location Code.
+ * Based on: https://github.com/google/open-location-code/blob/main/c/src/olc.c
+ */
+void GeoCoord::latLongToOLC(double lat, double lon, OLC &olc) {
+ char tempCode[] = "1234567890abc";
+ const char kAlphabet[] = "23456789CFGHJMPQRVWX";
+ double latitude;
+ double longitude = lon;
+ double latitude_degrees = std::min(90.0, std::max(-90.0, lat));
+
+ if (latitude_degrees < 90) // Check latitude less than lat max
+ latitude = latitude_degrees;
+ else {
+ double precision;
+ if (OLC_CODE_LEN <= 10)
+ precision = pow_neg(20, floor((OLC_CODE_LEN / -2) + 2));
+ else
+ precision = pow_neg(20, -3) / pow(5, OLC_CODE_LEN - 10);
+ latitude = latitude_degrees - precision / 2;
+ }
+ while (longitude < -180) // Normalize longitude
+ longitude += 360;
+ while (longitude >= 180)
+ longitude -= 360;
+ int64_t lat_val = 90 * 2.5e7;
+ int64_t lng_val = 180 * 8.192e6;
+ lat_val += latitude * 2.5e7;
+ lng_val += longitude * 8.192e6;
+ size_t pos = OLC_CODE_LEN;
+
+ if (OLC_CODE_LEN > 10) { // Compute grid part of code if needed
+ for (size_t i = 0; i < 5; i++) {
+ int lat_digit = lat_val % 5;
+ int lng_digit = lng_val % 4;
+ int ndx = lat_digit * 4 + lng_digit;
+ tempCode[pos--] = kAlphabet[ndx];
+ lat_val /= 5;
+ lng_val /= 4;
+ }
+ } else {
+ lat_val /= pow(5, 5);
+ lng_val /= pow(4, 5);
+ }
+
+ pos = 10;
+
+ for (size_t i = 0; i < 5; i++) { // Compute pair section of code
+ int lat_ndx = lat_val % 20;
+ int lng_ndx = lng_val % 20;
+ tempCode[pos--] = kAlphabet[lng_ndx];
+ tempCode[pos--] = kAlphabet[lat_ndx];
+ lat_val /= 20;
+ lng_val /= 20;
+
+ if (i == 0)
+ tempCode[pos--] = '+';
+ }
+
+ if (OLC_CODE_LEN < 9) { // Add padding if needed
+ for (size_t i = OLC_CODE_LEN; i < 9; i++)
+ tempCode[i] = '0';
+ tempCode[9] = '+';
+ }
+
+ size_t char_count = OLC_CODE_LEN;
+ if (10 > char_count) {
+ char_count = 10;
+ }
+ for (size_t i = 0; i < char_count; i++) {
+ olc.code[i] = tempCode[i];
+ }
+ olc.code[char_count] = '\0';
+}
+
+// Converts the coordinate in WGS84 datum to the OSGB36 datum.
+void GeoCoord::convertWGS84ToOSGB36(const double lat, const double lon, double &osgb_Latitude, double &osgb_Longitude) {
+ // Convert lat long to cartesian
+ double phi = toRadians(lat);
+ double lambda = toRadians(lon);
+ double h = 0.0; // No OSTN height data used, some loss of accuracy (up to 5m)
+ double wgsA = 6378137; // WGS84 datum semi major axis
+ double wgsF = 1 / 298.257223563; // WGS84 datum flattening
+ double ecc = 2*wgsF - wgsF*wgsF;
+ double vee = wgsA / sqrt(1 - ecc * pow(sin(phi), 2));
+ double wgsX = (vee + h) * cos(phi) * cos(lambda);
+ double wgsY = (vee + h) * cos(phi) * sin(lambda);
+ double wgsZ = ((1 - ecc) * vee + h) * sin(phi);
+
+ // 7-parameter Helmert transform
+ double tx = -446.448; // x shift in meters
+ double ty = 125.157; // y shift in meters
+ double tz = -542.060; // z shift in meters
+ double s = 20.4894/1e6 + 1; // scale normalized parts per million to (s + 1)
+ double rx = toRadians(-0.1502/3600); // x rotation normalize arcseconds to radians
+ double ry = toRadians(-0.2470/3600); // y rotation normalize arcseconds to radians
+ double rz = toRadians(-0.8421/3600); // z rotation normalize arcseconds to radians
+ double osgbX = tx + wgsX*s - wgsY*rz + wgsZ*ry;
+ double osgbY = ty + wgsX*rz + wgsY*s - wgsZ*rx;
+ double osgbZ = tz - wgsX*ry + wgsY*rx + wgsZ*s;
+
+ // Convert cartesian to lat long
+ double airyA = 6377563.396; // Airy1830 datum semi major axis
+ double airyB = 6356256.909; // Airy1830 datum semi minor axis
+ double airyF = 1/ 299.3249646; // Airy1830 datum flattening
+ double airyEcc = 2*airyF - airyF*airyF;
+ double airyEcc2 = airyEcc / (1 - airyEcc);
+ double p = sqrt(osgbX*osgbX + osgbY*osgbY);
+ double R = sqrt(p*p + osgbZ*osgbZ);
+ double tanBeta = (airyB*osgbZ) / (airyA*p) * (1 + airyEcc2*airyB/R);
+ double sinBeta = tanBeta / sqrt(1 + tanBeta*tanBeta);
+ double cosBeta = sinBeta / tanBeta;
+ osgb_Latitude = atan2(osgbZ + airyEcc2*airyB*sinBeta*sinBeta*sinBeta, p - airyEcc*airyA*cosBeta*cosBeta*cosBeta); // leave in radians
+ osgb_Longitude = atan2(osgbY, osgbX); // leave in radians
+ //osgb height = p*cos(osgb.latitude) + osgbZ*sin(osgb.latitude) -
+ //(airyA*airyA/(airyA / sqrt(1 - airyEcc*sin(osgb.latitude)*sin(osgb.latitude)))); // Not used, no OSTN data
+}
+
+/// Ported from my old java code, returns distance in meters along the globe
+/// surface (by magic?)
+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;
+ double cos_b1 = cos(b1);
+ double cos_a1 = cos(a1);
+ double t1 = cos_a1 * cos(a2) * cos_b1 * cos(b2);
+ double t2 = cos_a1 * sin(a2) * cos_b1 * sin(b2);
+ double t3 = sin(a1) * sin(b1);
+ double tt = acos(t1 + t2 + t3);
+ if (std::isnan(tt))
+ tt = 0.0; // Must have been the same point?
+
+ return (float)(6366000 * tt);
+}
+
+/**
+ * Computes the bearing in degrees between two points on Earth. Ported from my
+ * old Gaggle android app.
+ *
+ * @param lat1
+ * Latitude of the first point
+ * @param lon1
+ * Longitude of the first point
+ * @param lat2
+ * Latitude of the second point
+ * @param lon2
+ * Longitude of the second point
+ * @return Bearing between the two points in radians. A value of 0 means due
+ * north.
+ */
+float GeoCoord::bearing(double lat1, double lon1, double lat2, double lon2)
+{
+ double lat1Rad = toRadians(lat1);
+ double lat2Rad = toRadians(lat2);
+ double deltaLonRad = toRadians(lon2 - lon1);
+ double y = sin(deltaLonRad) * cos(lat2Rad);
+ double x = cos(lat1Rad) * sin(lat2Rad) - (sin(lat1Rad) * cos(lat2Rad) * cos(deltaLonRad));
+ return atan2(y, x);
+}
+
+/**
+ * Ported from http://www.edwilliams.org/avform147.htm#Intro
+ * @brief Convert from meters to range in radians on a great circle
+ * @param range_meters
+ * The range in meters
+ * @return range in radians on a great circle
+ */
+float GeoCoord::rangeMetersToRadians(double range_meters) {
+ // 1 nm is 1852 meters
+ double distance_nm = range_meters * 1852;
+ return (PI / (180 * 60)) *distance_nm;
+}
+
+/**
+ * Ported from http://www.edwilliams.org/avform147.htm#Intro
+ * @brief Convert from radians to range in meters on a great circle
+ * @param range_radians
+ * The range in radians
+ * @return Range in meters on a great circle
+ */
+float GeoCoord::rangeRadiansToMeters(double range_radians) {
+ double distance_nm = ((180 * 60) / PI) * range_radians;
+ // 1 meter is 0.000539957 nm
+ return distance_nm * 0.000539957;
+}
+
+// Find distance from point to passed in point
+int32_t GeoCoord::distanceTo(GeoCoord pointB) {
+ return latLongToMeter(this->getLatitude() * 1e-7, this->getLongitude() * 1e-7, pointB.getLatitude() * 1e-7, pointB.getLongitude() * 1e-7);
+}
+
+// Find bearing from point to passed in point
+int32_t GeoCoord::bearingTo(GeoCoord pointB) {
+ return bearing(this->getLatitude() * 1e-7, this->getLongitude() * 1e-7, pointB.getLatitude() * 1e-7, pointB.getLongitude() * 1e-7);
+}
+
+/**
+ * Create a new point bassed on the passed in poin
+ * Ported from http://www.edwilliams.org/avform147.htm#LL
+ * @param bearing
+ * The bearing in raidans
+ * @param range_meters
+ * range in meters
+ * @return GeoCoord object of point at bearing and range from initial point
+*/
+std::shared_ptr GeoCoord::pointAtDistance(double bearing, double range_meters) {
+ double range_radians = rangeMetersToRadians(range_meters);
+ double lat1 = this->getLatitude() * 1e-7;
+ double lon1 = this->getLongitude() * 1e-7;
+ double lat = asin(sin(lat1) * cos(range_radians) + cos(lat1) * sin(range_radians) * cos(bearing));
+ double dlon = atan2(sin(bearing) * sin(range_radians) * cos(lat1), cos(range_radians) - sin(lat1) * sin(lat));
+ double lon = fmod(lon1 - dlon + PI, 2 * PI) - PI;
+
+ return std::make_shared(double(lat), double(lon), this->getAltitude());
+
+}
\ No newline at end of file
diff --git a/src/gps/GeoCoord.h b/src/gps/GeoCoord.h
new file mode 100644
index 000000000..b3d218700
--- /dev/null
+++ b/src/gps/GeoCoord.h
@@ -0,0 +1,164 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define PI 3.1415926535897932384626433832795
+#define OLC_CODE_LEN 11
+
+// Helper functions
+// Raises a number to an exponent, handling negative exponents.
+static inline double pow_neg(double base, double exponent) {
+ if (exponent == 0) {
+ return 1;
+ } else if (exponent > 0) {
+ return pow(base, exponent);
+ }
+ return 1 / pow(base, -exponent);
+}
+
+static inline double toRadians(double deg)
+{
+ return deg * PI / 180;
+}
+
+static inline double toDegrees(double r)
+{
+ return r * 180 / PI;
+}
+
+// GeoCoord structs/classes
+// A struct to hold the data for a DMS coordinate.
+struct DMS
+{
+ uint8_t latDeg;
+ uint8_t latMin;
+ uint32_t latSec;
+ char latCP;
+ uint8_t lonDeg;
+ uint8_t lonMin;
+ uint32_t lonSec;
+ char lonCP;
+};
+
+// A struct to hold the data for a UTM coordinate, this is also used when creating an MGRS coordinate.
+struct UTM
+{
+ uint8_t zone;
+ char band;
+ uint32_t easting;
+ uint32_t northing;
+};
+
+// A struct to hold the data for a MGRS coordinate.
+struct MGRS
+{
+ uint8_t zone;
+ char band;
+ char east100k;
+ char north100k;
+ uint32_t easting;
+ uint32_t northing;
+};
+
+// A struct to hold the data for a OSGR coordiante
+struct OSGR {
+ char e100k;
+ char n100k;
+ uint32_t easting;
+ uint32_t northing;
+};
+
+// A struct to hold the data for a OLC coordinate
+struct OLC {
+ char code[OLC_CODE_LEN + 1]; // +1 for null termination
+};
+
+class GeoCoord {
+ private:
+ int32_t _latitude = 0;
+ int32_t _longitude = 0;
+ int32_t _altitude = 0;
+
+ DMS _dms;
+ UTM _utm;
+ MGRS _mgrs;
+ OSGR _osgr;
+ OLC _olc;
+
+ bool _dirty = true;
+
+ void setCoords();
+
+ public:
+ GeoCoord();
+ GeoCoord(int32_t lat, int32_t lon, int32_t alt);
+ GeoCoord(double lat, double lon, int32_t alt);
+ GeoCoord(float lat, float lon, int32_t alt);
+
+ void updateCoords(const int32_t lat, const int32_t lon, const int32_t alt);
+ void updateCoords(const double lat, const double lon, const int32_t alt);
+ void updateCoords(const float lat, const float lon, const int32_t alt);
+
+ // Conversions
+ static void latLongToDMS(const double lat, const double lon, DMS &dms);
+ static void latLongToUTM(const double lat, const double lon, UTM &utm);
+ static void latLongToMGRS(const double lat, const double lon, MGRS &mgrs);
+ static void latLongToOSGR(const double lat, const double lon, OSGR &osgr);
+ static void latLongToOLC(const double lat, const double lon, OLC &olc);
+ static void convertWGS84ToOSGB36(const double lat, const double lon, double &osgb_Latitude, double &osgb_Longitude);
+ static float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b);
+ static float bearing(double lat1, double lon1, double lat2, double lon2);
+ static float rangeRadiansToMeters(double range_radians);
+ static float rangeMetersToRadians(double range_meters);
+
+ // Point to point conversions
+ int32_t distanceTo(GeoCoord pointB);
+ int32_t bearingTo(GeoCoord pointB);
+ std::shared_ptr pointAtDistance(double bearing, double range);
+
+ // Lat lon alt getters
+ int32_t getLatitude() const { return _latitude; }
+ int32_t getLongitude() const { return _longitude; }
+ int32_t getAltitude() const { return _altitude; }
+
+ // DMS getters
+ uint8_t getDMSLatDeg() const { return _dms.latDeg; }
+ uint8_t getDMSLatMin() const { return _dms.latMin; }
+ uint32_t getDMSLatSec() const { return _dms.latSec; }
+ char getDMSLatCP() const { return _dms.latCP; }
+ uint8_t getDMSLonDeg() const { return _dms.lonDeg; }
+ uint8_t getDMSLonMin() const { return _dms.lonMin; }
+ uint32_t getDMSLonSec() const { return _dms.lonSec; }
+ char getDMSLonCP() const { return _dms.lonCP; }
+
+ // UTM getters
+ uint8_t getUTMZone() const { return _utm.zone; }
+ char getUTMBand() const { return _utm.band; }
+ uint32_t getUTMEasting() const { return _utm.easting; }
+ uint32_t getUTMNorthing() const { return _utm.northing; }
+
+ // MGRS getters
+ uint8_t getMGRSZone() const { return _mgrs.zone; }
+ char getMGRSBand() const { return _mgrs.band; }
+ char getMGRSEast100k() const { return _mgrs.east100k; }
+ char getMGRSNorth100k() const { return _mgrs.north100k; }
+ uint32_t getMGRSEasting() const { return _mgrs.easting; }
+ uint32_t getMGRSNorthing() const { return _mgrs.northing; }
+
+ // OSGR getters
+ char getOSGRE100k() const { return _osgr.e100k; }
+ char getOSGRN100k() const { return _osgr.n100k; }
+ uint32_t getOSGREasting() const { return _osgr.easting; }
+ uint32_t getOSGRNorthing() const { return _osgr.northing; }
+
+ // OLC getter
+ void getOLCCode(char* code) { strncpy(code, _olc.code, OLC_CODE_LEN + 1); } // +1 for null termination
+};
+
diff --git a/src/gps/NMEAGPS.cpp b/src/gps/NMEAGPS.cpp
index bd58b5254..d0d5e46e2 100644
--- a/src/gps/NMEAGPS.cpp
+++ b/src/gps/NMEAGPS.cpp
@@ -1,6 +1,12 @@
+#include "configuration.h"
#include "NMEAGPS.h"
#include "RTC.h"
-#include "configuration.h"
+
+#include
+
+// GPS solutions older than this will be rejected - see TinyGPSDatum::age()
+#define GPS_SOL_EXPIRY_MS 300 // in millis
+#define NMEA_MSG_GXGSA "GNGSA" // GSA message (GPGSA, GNGSA etc)
static int32_t toDegInt(RawDegrees d)
{
@@ -21,6 +27,17 @@ bool NMEAGPS::setupGPS()
pinMode(PIN_GPS_PPS, INPUT);
#endif
+// Currently disabled per issue #525 (TinyGPS++ crash bug)
+// when fixed upstream, can be un-disabled to enable 3D FixType and PDOP
+#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
+ // see NMEAGPS.h
+ gsafixtype.begin(reader, NMEA_MSG_GXGSA, 2);
+ gsapdop.begin(reader, NMEA_MSG_GXGSA, 15);
+ DEBUG_MSG("Using " NMEA_MSG_GXGSA " for 3DFIX and PDOP\n");
+#else
+ DEBUG_MSG("GxGSA NOT available\n");
+#endif
+
return true;
}
@@ -48,7 +65,7 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
t.tm_year = d.year() - 1900;
t.tm_isdst = false;
DEBUG_MSG("NMEA GPS time %d\n", t.tm_sec);
-
+
perhapsSetRTC(RTCQualityGPS, t);
return true;
@@ -64,47 +81,140 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
*/
bool NMEAGPS::lookForLocation()
{
- bool foundLocation = false;
+ // By default, TinyGPS++ does not parse GPGSA lines, which give us
+ // the 2D/3D fixType (see NMEAGPS.h)
+ // At a minimum, use the fixQuality indicator in GPGGA (FIXME?)
+ fixQual = reader.fixQuality();
- // uint8_t fixtype = reader.fixQuality();
- // hasValidLocation = ((fixtype >= 1) && (fixtype <= 5));
+#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
+ fixType = atoi(gsafixtype.value()); // will set to zero if no data
+ DEBUG_MSG("FIX QUAL=%d, TYPE=%d\n", fixQual, fixType);
+#endif
+ // check if GPS has an acceptable lock
+ if (! hasLock())
+ return false;
+
+#ifdef GPS_EXTRAVERBOSE
+ DEBUG_MSG("AGE: LOC=%d FIX=%d DATE=%d TIME=%d\n",
+ reader.location.age(),
+#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
+ gsafixtype.age(),
+#else
+ 0,
+#endif
+ reader.date.age(), reader.time.age());
+#endif // GPS_EXTRAVERBOSE
+
+ // check if a complete GPS solution set is available for reading
+ // tinyGPSDatum::age() also includes isValid() test
+ // FIXME
+ if (! ((reader.location.age() < GPS_SOL_EXPIRY_MS) &&
+#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
+ (gsafixtype.age() < GPS_SOL_EXPIRY_MS) &&
+#endif
+ (reader.time.age() < GPS_SOL_EXPIRY_MS) &&
+ (reader.date.age() < GPS_SOL_EXPIRY_MS)))
+ {
+ DEBUG_MSG("SOME data is TOO OLD\n");
+ 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();
+
+ // Some GPSes (Air530) seem to send a zero longitude when the current fix is bogus
+ // Bail out EARLY to avoid overwriting previous good data (like #857)
+ if(toDegInt(loc.lat) == 0) {
+ DEBUG_MSG("Ignoring bogus NMEA position\n");
+ return false;
+ }
+
+ p.location_source = Position_LocSource_LOCSRC_GPS_INTERNAL;
+
+ // Dilution of precision (an accuracy metric) is reported in 10^2 units, so we need to scale down when we use it
+#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
+ p.HDOP = reader.hdop.value();
+ p.PDOP = TinyGPSPlus::parseDecimal(gsapdop.value());
+ DEBUG_MSG("PDOP=%d, HDOP=%d\n", dop, reader.hdop.value());
+#else
+ // FIXME! naive PDOP emulation (assumes VDOP==HDOP)
+ // correct formula is PDOP = SQRT(HDOP^2 + VDOP^2)
+ p.HDOP = reader.hdop.value();
+ p.PDOP = 1.41 * reader.hdop.value();
+#endif
+
+ // Discard incomplete or erroneous readings
+ if (reader.hdop.value() == 0)
+ return false;
+
+ p.latitude_i = toDegInt(loc.lat);
+ p.longitude_i = toDegInt(loc.lng);
+
+ p.alt_geoid_sep = reader.geoidHeight.meters();
+ p.altitude_hae = reader.altitude.meters() + p.alt_geoid_sep;
+ p.altitude = reader.altitude.meters();
+
+ p.fix_quality = fixQual;
+#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
+ p.fix_type = fixType;
+#endif
+
+ // positional timestamp
+ struct tm t;
+ t.tm_sec = reader.time.second();
+ t.tm_min = reader.time.minute();
+ t.tm_hour = reader.time.hour();
+ t.tm_mday = reader.date.day();
+ t.tm_mon = reader.date.month() - 1;
+ t.tm_year = reader.date.year() - 1900;
+ t.tm_isdst = false;
+ p.pos_timestamp = mktime(&t);
+
+ // Nice to have, if available
if (reader.satellites.isUpdated()) {
- setNumSatellites(reader.satellites.value());
+ p.sats_in_view = reader.satellites.value();
}
- // Diminution of precision (an accuracy metric) is reported in 10^2 units, so we need to scale down when we use it
- if (reader.hdop.isUpdated()) {
- dop = reader.hdop.value();
- }
- if (reader.course.isUpdated()) {
- heading = reader.course.value() * 1e3; // Scale the heading (in degrees * 10^-2) to match the expected degrees * 10^-5
- }
-
- if (reader.altitude.isUpdated())
- altitude = reader.altitude.meters();
-
- if (reader.location.isUpdated()) {
-
- auto loc = reader.location.value();
- latitude = toDegInt(loc.lat);
- longitude = toDegInt(loc.lng);
-
- // Some GPSes (Air530) seem to send a zero longitude when the current fix is bogus
- if(longitude == 0)
- DEBUG_MSG("Ignoring bogus NMEA position\n");
- else {
- foundLocation = true;
-
- // expect gps pos lat=37.520825, lon=-122.309162, alt=158
- DEBUG_MSG("new NMEA GPS pos lat=%f, lon=%f, alt=%d, hdop=%g, heading=%f\n", latitude * 1e-7, longitude * 1e-7, altitude,
- dop * 1e-2, heading * 1e-5);
+ if (reader.course.isUpdated() && reader.course.isValid()) {
+ if (reader.course.value() < 36000) { // sanity check
+ p.ground_track = reader.course.value() * 1e3; // Scale the heading (in degrees * 10^-2) to match the expected degrees * 10^-5
+ } else {
+ DEBUG_MSG("BOGUS course.value() REJECTED: %d\n",
+ reader.course.value());
}
}
- return foundLocation;
+/*
+ // REDUNDANT?
+ // expect gps pos lat=37.520825, lon=-122.309162, alt=158
+ DEBUG_MSG("new NMEA GPS pos lat=%f, lon=%f, alt=%d, dop=%g, heading=%f\n",
+ latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2,
+ heading * 1e-5);
+*/
+ return true;
}
+
+bool NMEAGPS::hasLock()
+{
+ // Using GPGGA fix quality indicator
+ if (fixQual >= 1 && fixQual <= 5) {
+#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
+ // Use GPGSA fix type 2D/3D (better) if available
+ if (fixType == 3 || fixType == 0) // zero means "no data received"
+#endif
+ return true;
+ }
+
+ return false;
+}
+
+
bool NMEAGPS::whileIdle()
{
bool isValid = false;
diff --git a/src/gps/NMEAGPS.h b/src/gps/NMEAGPS.h
index 2fd29d408..bc5582508 100644
--- a/src/gps/NMEAGPS.h
+++ b/src/gps/NMEAGPS.h
@@ -12,6 +12,15 @@
class NMEAGPS : public GPS
{
TinyGPSPlus reader;
+ uint8_t fixQual = 0; // fix quality from GPGGA
+
+#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
+ // (20210908) TinyGps++ can only read the GPGSA "FIX TYPE" field
+ // via optional feature "custom fields", currently disabled (bug #525)
+ TinyGPSCustom gsafixtype; // custom extract fix type from GPGSA
+ TinyGPSCustom gsapdop; // custom extract PDOP from GPGSA
+ uint8_t fixType = 0; // fix type from GPGSA
+#endif
public:
virtual bool setupGPS();
@@ -38,4 +47,6 @@ class NMEAGPS : public GPS
* @return true if we've acquired a new location
*/
virtual bool lookForLocation();
+
+ virtual bool hasLock();
};
diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp
index 5f6a54c5c..bc7e816cb 100644
--- a/src/gps/RTC.cpp
+++ b/src/gps/RTC.cpp
@@ -48,14 +48,22 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
shouldSet = false;
if (shouldSet) {
- lastSetMsec = now;
-#ifndef NO_ESP32
- settimeofday(tv, NULL);
- readFromRTC();
-#else
+ lastSetMsec = now;
+
+ // This delta value works on all platforms
timeStartMsec = now;
zeroOffsetSecs = tv->tv_sec;
+
+ // If this platform has a setable RTC, set it
+#ifndef NO_ESP32
+ settimeofday(tv, NULL);
#endif
+
+ // nrf52 doesn't have a readable RTC (yet - software not written)
+#if defined(PORTDUINO) || !defined(NO_ESP32)
+ readFromRTC();
+#endif
+
return true;
} else {
return false;
@@ -84,7 +92,7 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t)
uint32_t getTime()
{
- return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
+ return (((uint32_t) millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
}
uint32_t getValidTime(RTCQuality minQuality)
diff --git a/src/gps/UBloxGPS.cpp b/src/gps/UBloxGPS.cpp
index 8fda6bc43..30b6a5795 100644
--- a/src/gps/UBloxGPS.cpp
+++ b/src/gps/UBloxGPS.cpp
@@ -1,9 +1,19 @@
+#include "configuration.h"
#include "UBloxGPS.h"
#include "RTC.h"
#include "error.h"
#include "sleep.h"
#include
+// if gps_update_interval below this value, do not powercycle the GPS
+#define UBLOX_POWEROFF_THRESHOLD 90
+
+#define PDOP_INVALID 9999
+
+// #define UBX_MODE_NMEA
+
+extern RadioConfig radioConfig;
+
UBloxGPS::UBloxGPS() {}
bool UBloxGPS::tryConnect()
@@ -40,12 +50,22 @@ bool UBloxGPS::setupGPS()
delay(500);
if (isConnected()) {
+#ifdef UBX_MODE_NMEA
+ DEBUG_MSG("Connected to UBLOX GPS, downgrading to NMEA mode\n");
+ DEBUG_MSG("- GPS errors below are related and safe to ignore\n");
+#else
DEBUG_MSG("Connected to UBLOX GPS successfully\n");
+#endif
if (!setUBXMode())
- recordCriticalError(CriticalErrorCode_UBloxInitFailed); // Don't halt the boot if saving the config fails, but do report the bug
+ RECORD_CRITICALERROR(CriticalErrorCode_UBloxInitFailed); // Don't halt the boot if saving the config fails, but do report the bug
+#ifdef UBX_MODE_NMEA
+ return false;
+#else
return true;
+#endif
+
} else {
return false;
}
@@ -53,6 +73,17 @@ bool UBloxGPS::setupGPS()
bool UBloxGPS::setUBXMode()
{
+#ifdef UBX_MODE_NMEA
+ if (_serial_gps) {
+ ublox.setUART1Output(COM_TYPE_NMEA, 1000);
+ }
+ if (i2cAddress) {
+ ublox.setI2COutput(COM_TYPE_NMEA, 1000);
+ }
+
+ return false; // pretend initialization failed to force NMEA mode
+#endif
+
if (_serial_gps) {
if (!ublox.setUART1Output(COM_TYPE_UBX, 1000)) // Use native API
return false;
@@ -110,19 +141,17 @@ bool UBloxGPS::factoryReset()
/** Idle processing while GPS is looking for lock */
void UBloxGPS::whileActive()
{
+ ublox.flushPVT(); // reset ALL freshness flags first
ublox.getT(maxWait()); // ask for new time data - hopefully ready when we come back
// Ask for a new position fix - hopefully it will have results ready by next time
// the order here is important, because we only check for has latitude when reading
- ublox.getSIV(maxWait());
- ublox.getPDOP(maxWait());
- ublox.getP(maxWait());
- // Update fixtype
- if (ublox.moduleQueried.fixType) {
- fixType = ublox.getFixType(0);
- // DEBUG_MSG("GPS fix type %d, numSats %d\n", fixType, numSatellites);
- }
+ //ublox.getSIV(maxWait()); // redundant with getPDOP below
+ ublox.getPDOP(maxWait()); // will trigger getSOL on NEO6, getP on others
+ ublox.getP(maxWait()); // will trigger getPosLLH on NEO6, getP on others
+
+ // the fixType flag will be checked and updated in lookForLocation()
}
/**
@@ -163,33 +192,94 @@ bool UBloxGPS::lookForLocation()
{
bool foundLocation = false;
- if (ublox.moduleQueried.SIV)
- setNumSatellites(ublox.getSIV(0));
-
- if (ublox.moduleQueried.pDOP)
- dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it
-
- // we only notify if position has changed due to a new fix
- if ((fixType >= 3 && fixType <= 4)) {
- if (ublox.moduleQueried.latitude) // rd fixes only
- {
- latitude = ublox.getLatitude(0);
- longitude = ublox.getLongitude(0);
- altitude = ublox.getAltitudeMSL(0) / 1000; // in mm convert to meters
-
- // Note: heading is only currently implmented in the ublox for the 8m chipset - therefore
- // don't read it here - it will generate an ignored getPVT command on the 6ms
- // heading = ublox.getHeading(0);
-
- // bogus lat lon is reported as 0 or 0 (can be bogus just for one)
- // Also: apparently when the GPS is initially reporting lock it can output a bogus latitude > 90 deg!
- foundLocation = (latitude != 0) && (longitude != 0) && (latitude <= 900000000 && latitude >= -900000000);
- }
+ // check if a complete GPS solution set is available for reading
+ // (some of these, like lat/lon are redundant and can be removed)
+ if ( ! (ublox.moduleQueried.fixType &&
+ ublox.moduleQueried.latitude &&
+ ublox.moduleQueried.longitude &&
+ ublox.moduleQueried.altitude &&
+ ublox.moduleQueried.pDOP &&
+ ublox.moduleQueried.SIV &&
+ ublox.moduleQueried.gpsDay))
+ {
+ // Not ready? No problem! We'll try again later.
+ return false;
}
+ fixType = ublox.getFixType();
+#ifdef UBLOX_EXTRAVERBOSE
+ DEBUG_MSG("FixType=%d\n", fixType);
+#endif
+
+
+ // check if GPS has an acceptable lock
+ if (! hasLock()) {
+ ublox.flushPVT(); // reset ALL freshness flags
+ return false;
+ }
+
+ // read lat/lon/alt/dop data into temporary variables to avoid
+ // overwriting global variables with potentially invalid data
+ int32_t tmp_dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it
+ int32_t tmp_lat = ublox.getLatitude(0);
+ int32_t tmp_lon = ublox.getLongitude(0);
+ int32_t tmp_alt_msl = ublox.getAltitudeMSL(0);
+ int32_t tmp_alt_hae = ublox.getAltitude(0);
+ // Note: heading is only currently implmented in the ublox for the 8m chipset - therefore
+ // don't read it here - it will generate an ignored getPVT command on the 6ms
+ // heading = ublox.getHeading(0);
+
+ // read positional timestamp
+ struct tm t;
+ t.tm_sec = ublox.getSecond(0);
+ t.tm_min = ublox.getMinute(0);
+ t.tm_hour = ublox.getHour(0);
+ t.tm_mday = ublox.getDay(0);
+ t.tm_mon = ublox.getMonth(0) - 1;
+ t.tm_year = ublox.getYear(0) - 1900;
+ t.tm_isdst = false;
+
+ time_t tmp_ts = mktime(&t);
+
+ // FIXME - can opportunistically attempt to set RTC from GPS timestamp?
+
+ // bogus lat lon is reported as 0 or 0 (can be bogus just for one)
+ // Also: apparently when the GPS is initially reporting lock it can output a bogus latitude > 90 deg!
+ // FIXME - NULL ISLAND is a real location on Earth!
+ foundLocation = (tmp_lat != 0) && (tmp_lon != 0) &&
+ (tmp_lat <= 900000000) && (tmp_lat >= -900000000) &&
+ (tmp_dop < PDOP_INVALID);
+
+ // only if entire dataset is valid, update globals from temp vars
+ if (foundLocation) {
+ p.location_source = Position_LocSource_LOCSRC_GPS_INTERNAL;
+ p.longitude_i = tmp_lon;
+ p.latitude_i = tmp_lat;
+ p.altitude = tmp_alt_msl / 1000;
+ p.altitude_hae = tmp_alt_hae / 1000;
+ p.alt_geoid_sep = (tmp_alt_hae - tmp_alt_msl) / 1000;
+ p.pos_timestamp = tmp_ts;
+ p.PDOP = tmp_dop;
+ p.fix_type = fixType;
+ p.sats_in_view = ublox.getSIV(0);
+ // In debug logs, identify position by @timestamp:stage (stage 1 = birth)
+ DEBUG_MSG("lookForLocation() new pos@%x:1\n", tmp_ts);
+ } else {
+ // INVALID solution - should never happen
+ DEBUG_MSG("Invalid location lat/lon/hae/dop %d/%d/%d/%d - discarded\n",
+ tmp_lat, tmp_lon, tmp_alt_hae, tmp_dop);
+ }
+
+ ublox.flushPVT(); // reset ALL freshness flags at the end
+
return foundLocation;
}
+bool UBloxGPS::hasLock()
+{
+ return (fixType >= 3 && fixType <= 4);
+}
+
bool UBloxGPS::whileIdle()
{
// if using i2c or serial look too see if any chars are ready
@@ -200,15 +290,20 @@ bool UBloxGPS::whileIdle()
/// Note: ublox doesn't need a wake method, because as soon as we send chars to the GPS it will wake up
void UBloxGPS::sleep()
{
- // Tell GPS to power down until we send it characters on serial port (we leave vcc connected)
- ublox.powerOff();
- // setGPSPower(false);
+ if (radioConfig.preferences.gps_update_interval > UBLOX_POWEROFF_THRESHOLD) {
+ // Tell GPS to power down until we send it characters on serial port (we leave vcc connected)
+ ublox.powerOff();
+ // setGPSPower(false);
+ }
}
void UBloxGPS::wake()
{
- fixType = 0; // assume we hace no fix yet
+ if (radioConfig.preferences.gps_update_interval > UBLOX_POWEROFF_THRESHOLD) {
+ fixType = 0; // assume we have no fix yet
+ }
+ // this is idempotent
setGPSPower(true);
// Note: no delay needed because now we leave gps power on always and instead use ublox.powerOff()
diff --git a/src/gps/UBloxGPS.h b/src/gps/UBloxGPS.h
index 9fda4bd0e..009744a52 100644
--- a/src/gps/UBloxGPS.h
+++ b/src/gps/UBloxGPS.h
@@ -54,6 +54,7 @@ class UBloxGPS : public GPS
* @return true if we've acquired a new location
*/
virtual bool lookForLocation();
+ virtual bool hasLock();
/// If possible force the GPS into sleep/low power mode
virtual void sleep();
diff --git a/src/graphics/EInkDisplay.cpp b/src/graphics/EInkDisplay.cpp
deleted file mode 100644
index 44d38f22d..000000000
--- a/src/graphics/EInkDisplay.cpp
+++ /dev/null
@@ -1,152 +0,0 @@
-#include "configuration.h"
-
-#ifdef HAS_EINK
-#include "EInkDisplay.h"
-#include "SPILock.h"
-#include "epd1in54.h" // Screen specific library
-#include
-#include // Graphics library and Sprite class
-
-Epd ePaper; // Create an instance ePaper
-
-TFT_eSPI glc = TFT_eSPI(); // Invoke the graphics library class
-TFT_eSprite frame = TFT_eSprite(&glc); // Invoke the Sprite class for the image frame buffer
-uint8_t *framePtr; // Pointer for the black frame buffer
-
-#define COLORED 0
-#define UNCOLORED 1
-
-#define INK COLORED // Black ink
-#define PAPER UNCOLORED // 'paper' background colour
-
-//------------------------------------------------------------------------------------
-// Update display - different displays have different function names in the default
-// Waveshare libraries :-(
-//------------------------------------------------------------------------------------
-#if defined(EPD1IN54B_H) || defined(EPD1IN54C_H) || defined(EPD2IN13B_H) || defined(EPD2IN7B_H) || defined(EPD2IN9B_H) || \
- defined(EPD4IN2_H)
-void updateDisplay(uint8_t *blackFrame = blackFramePtr, uint8_t *redFrame = redFramePtr)
-{
- ePaper.DisplayFrame(blackFrame, redFrame); // Update 3 colour display
-#else
-void updateDisplay(uint8_t *blackFrame = framePtr)
-{
-#if defined(EPD2IN7_H) || defined(EPD4IN2_H)
- ePaper.DisplayFrame(blackFrame); // Update 2 color display
-
-#elif defined(EPD1IN54_H) || defined(EPD2IN13_H) || defined(EPD2IN9_H)
- ePaper.SetFrameMemory(blackFrame); // Update 2 colour display
- ePaper.DisplayFrame();
-#else
-#error "Selected ePaper library is not supported"
-#endif
-#endif
-}
-
-EInkDisplay::EInkDisplay(uint8_t address, int sda, int scl)
-{
- setGeometry(GEOMETRY_RAWMODE, EPD_WIDTH, EPD_HEIGHT);
- // 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
-}
-
-// 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
- */
-bool EInkDisplay::forceDisplay(uint32_t msecLimit)
-{
- // No need to grab this lock because we are on our own SPI bus
- // concurrency::LockGuard g(spiLock);
-
- uint32_t now = millis();
- uint32_t sinceLast = now - lastDrawMsec;
-
- if (framePtr && (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 (uint8_t y = 0; y < displayHeight; y++) {
- for (uint8_t x = 0; x < displayWidth; x++) {
-
- // get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent
- auto b = buffer[x + (y / 8) * displayWidth];
- auto isset = b & (1 << (y & 7));
- frame.drawPixel(x, y, isset ? INK : PAPER);
- }
- }
-
- ePaper.Reset(); // wake the screen from sleep
-
- DEBUG_MSG("Updating eink... ");
- updateDisplay(); // Send image to display and refresh
- DEBUG_MSG("done\n");
-
- // Put screen to sleep to save power
- ePaper.Sleep();
- return true;
- } else {
- // DEBUG_MSG("Skipping eink display\n");
- return false;
- }
-}
-
-// Write the buffer to the display memory
-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)
- forceDisplay(slowUpdateMsec); // Show the first screen a few seconds after boot, then slower
-}
-
-// Send a command to the display (low level function)
-void EInkDisplay::sendCommand(uint8_t com)
-{
- (void)com;
- // Drop all commands to device (we just update the buffer)
-}
-
-// Connect to the display
-bool EInkDisplay::connect()
-{
- DEBUG_MSG("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);
-#endif
-
- // Initialise the ePaper library
- // FIXME - figure out how to use lut_partial_update
- if (ePaper.Init(lut_full_update) != 0) {
- DEBUG_MSG("ePaper init failed\n");
- return false;
- } else {
- frame.setColorDepth(1); // Must set the bits per pixel to 1 for ePaper displays
- // Set bit depth BEFORE creating Sprite, default is 16!
-
- // Create a frame buffer in RAM of defined size and save the pointer to it
- // RAM needed is about (EPD_WIDTH * EPD_HEIGHT)/8 , ~5000 bytes for 200 x 200 pixels
- // Note: always create the Sprite before setting the Sprite rotation
- framePtr = (uint8_t *)frame.createSprite(EPD_WIDTH, EPD_HEIGHT);
-
- frame.fillSprite(PAPER); // Fill frame with white
- /* frame.drawLine(0, 0, frame.width() - 1, frame.height() - 1, INK);
- frame.drawLine(0, frame.height() - 1, frame.width() - 1, 0, INK);
- updateDisplay(); */
- return true;
- }
-}
-
-#endif
diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp
new file mode 100644
index 000000000..f93453dd4
--- /dev/null
+++ b/src/graphics/EInkDisplay2.cpp
@@ -0,0 +1,116 @@
+#include "configuration.h"
+
+#ifdef HAS_EINK
+#include "EInkDisplay2.h"
+#include "SPILock.h"
+#include
+#include "GxEPD2_BW.h"
+
+#define COLORED GxEPD_BLACK
+#define UNCOLORED GxEPD_WHITE
+
+
+#define TECHO_DISPLAY_MODEL GxEPD2_154_D67
+
+GxEPD2_BW *adafruitDisplay;
+
+EInkDisplay::EInkDisplay(uint8_t address, int sda, int scl)
+{
+ setGeometry(GEOMETRY_RAWMODE, TECHO_DISPLAY_MODEL::WIDTH, TECHO_DISPLAY_MODEL::HEIGHT);
+ // 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
+}
+
+// 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
+ */
+bool EInkDisplay::forceDisplay(uint32_t msecLimit)
+{
+ // No need to grab this lock because we are on our own SPI bus
+ // concurrency::LockGuard g(spiLock);
+
+ uint32_t now = millis();
+ uint32_t sinceLast = now - lastDrawMsec;
+
+ 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 (uint8_t y = 0; y < displayHeight; y++) {
+ for (uint8_t x = 0; x < displayWidth; x++) {
+
+ // get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent
+ auto b = buffer[x + (y / 8) * displayWidth];
+ auto isset = b & (1 << (y & 7));
+ adafruitDisplay->drawPixel(x, y, isset ? COLORED : UNCOLORED);
+ }
+ }
+
+ DEBUG_MSG("Updating eink... ");
+ // ePaper.Reset(); // wake the screen from sleep
+ adafruitDisplay->display(false); // FIXME, use partial update mode
+ // Put screen to sleep to save power (possibly not necessary because we already did poweroff inside of display)
+ adafruitDisplay->hibernate();
+ DEBUG_MSG("done\n");
+
+ return true;
+ } else {
+ // DEBUG_MSG("Skipping eink display\n");
+ return false;
+ }
+}
+
+// Write the buffer to the display memory
+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)
+ forceDisplay(slowUpdateMsec); // Show the first screen a few seconds after boot, then slower
+}
+
+// Send a command to the display (low level function)
+void EInkDisplay::sendCommand(uint8_t com)
+{
+ (void)com;
+ // Drop all commands to device (we just update the buffer)
+}
+
+// Connect to the display
+bool EInkDisplay::connect()
+{
+ DEBUG_MSG("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);
+#endif
+
+ auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS,
+ PIN_EINK_DC,
+ PIN_EINK_RES,
+ PIN_EINK_BUSY, SPI1);
+
+ adafruitDisplay = new GxEPD2_BW(*lowLevel);
+ adafruitDisplay->init();
+ adafruitDisplay->setRotation(3);
+ //adafruitDisplay->setFullWindow();
+ //adafruitDisplay->fillScreen(UNCOLORED);
+ //adafruitDisplay->drawCircle(100, 100, 20, COLORED);
+ //adafruitDisplay->display(false);
+
+ return true;
+}
+
+#endif
diff --git a/src/graphics/EInkDisplay.h b/src/graphics/EInkDisplay2.h
similarity index 100%
rename from src/graphics/EInkDisplay.h
rename to src/graphics/EInkDisplay2.h
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index c7580f02d..d197f7765 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -19,14 +19,13 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
-
+#include "configuration.h"
#include
#include "GPS.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "Screen.h"
-#include "configuration.h"
#include "fonts.h"
#include "gps/RTC.h"
#include "graphics/images.h"
@@ -36,6 +35,7 @@ along with this program. If not, see .
#include "plugins/TextMessagePlugin.h"
#include "target_specific.h"
#include "utils.h"
+#include "gps/GeoCoord.h"
#ifndef NO_ESP32
#include "mesh/http/WiFiAPClient.h"
@@ -73,6 +73,9 @@ std::vector pluginFrames;
// Stores the last 4 of our hardware ID, to make finding the device for pairing easier
static char ourId[5];
+// GeoCoord object for the screen
+GeoCoord geoCoord;
+
#ifdef SHOW_REDRAWS
static bool heartbeat = false;
#endif
@@ -130,7 +133,7 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl
// Draw version in upper right
char buf[16];
snprintf(buf, sizeof(buf), "%s",
- xstr(APP_VERSION)); // Note: we don't bother printing region or now, it makes the string too long
+ xstr(APP_VERSION_SHORT)); // Note: we don't bother printing region or now, it makes the string too long
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf);
screen->forceDisplay();
@@ -334,6 +337,11 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, NodeStatus *no
// Draw GPS status summary
static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
{
+ if (radioConfig.preferences.fixed_position) {
+ // GPS coordinates are currently fixed
+ display->drawString(x - 1, y - 2, "Fixed GPS");
+ return;
+ }
if (!gps->getIsConnected()) {
display->drawString(x, y - 2, "No GPS");
return;
@@ -368,15 +376,15 @@ static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus
static void drawGPSAltitude(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
{
String displayLine = "";
- if (!gps->getIsConnected()) {
+ if (!gps->getIsConnected() && !radioConfig.preferences.fixed_position) {
// displayLine = "No GPS Module";
// display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
- } else if (!gps->getHasLock()) {
+ } else if (!gps->getHasLock() && !radioConfig.preferences.fixed_position) {
// displayLine = "No GPS Lock";
// display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
} else {
-
- displayLine = "Altitude: " + String(gps->getAltitude()) + "m";
+ geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude()));
+ displayLine = "Altitude: " + String(geoCoord.getAltitude()) + "m";
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
}
}
@@ -384,76 +392,51 @@ static void drawGPSAltitude(OLEDDisplay *display, int16_t x, int16_t y, const GP
// Draw GPS status coordinates
static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
{
+ auto gpsFormat = radioConfig.preferences.gps_format;
String displayLine = "";
- if (!gps->getIsConnected()) {
+
+ if (!gps->getIsConnected() && !radioConfig.preferences.fixed_position) {
displayLine = "No GPS Module";
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
- } else if (!gps->getHasLock()) {
+ } else if (!gps->getHasLock() && !radioConfig.preferences.fixed_position) {
displayLine = "No GPS Lock";
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
} else {
- char coordinateLine[22];
- sprintf(coordinateLine, "%f %f", gps->getLatitude() * 1e-7, gps->getLongitude() * 1e-7);
- display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(coordinateLine))) / 2, y, coordinateLine);
+ if (gpsFormat != GpsCoordinateFormat_GpsFormatDMS) {
+ char coordinateLine[22];
+ geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude()));
+ if (gpsFormat == GpsCoordinateFormat_GpsFormatDec) { // Decimal Degrees
+ sprintf(coordinateLine, "%f %f", geoCoord.getLatitude() * 1e-7, geoCoord.getLongitude() * 1e-7);
+ } else if (gpsFormat == GpsCoordinateFormat_GpsFormatUTM) { // Universal Transverse Mercator
+ sprintf(coordinateLine, "%2i%1c %06i %07i", geoCoord.getUTMZone(), geoCoord.getUTMBand(),
+ geoCoord.getUTMEasting(), geoCoord.getUTMNorthing());
+ } else if (gpsFormat == GpsCoordinateFormat_GpsFormatMGRS) { // Military Grid Reference System
+ sprintf(coordinateLine, "%2i%1c %1c%1c %05i %05i", geoCoord.getMGRSZone(), geoCoord.getMGRSBand(), geoCoord.getMGRSEast100k(),
+ geoCoord.getMGRSNorth100k(), geoCoord.getMGRSEasting(), geoCoord.getMGRSNorthing());
+ } else if (gpsFormat == GpsCoordinateFormat_GpsFormatOLC) { // Open Location Code
+ geoCoord.getOLCCode(coordinateLine);
+ } else if (gpsFormat == GpsCoordinateFormat_GpsFormatOSGR) { // Ordnance Survey Grid Reference
+ if (geoCoord.getOSGRE100k() == 'I' || geoCoord.getOSGRN100k() == 'I') // OSGR is only valid around the UK region
+ sprintf(coordinateLine, "%s", "Out of Boundary");
+ else
+ sprintf(coordinateLine, "%1c%1c %05i %05i", geoCoord.getOSGRE100k(),geoCoord.getOSGRN100k(),
+ geoCoord.getOSGREasting(), geoCoord.getOSGRNorthing());
+ }
+
+ display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(coordinateLine))) / 2, y, coordinateLine);
+ } else {
+ char latLine[22];
+ char lonLine[22];
+ sprintf(latLine, "%2i° %2i' %2.4f\" %1c", geoCoord.getDMSLatDeg(), geoCoord.getDMSLatMin(), geoCoord.getDMSLatSec(),
+ geoCoord.getDMSLatCP());
+ sprintf(lonLine, "%3i° %2i' %2.4f\" %1c", geoCoord.getDMSLonDeg(), geoCoord.getDMSLonMin(), geoCoord.getDMSLonSec(),
+ geoCoord.getDMSLonCP());
+ display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(latLine))) / 2, y - FONT_HEIGHT_SMALL * 1, latLine);
+ display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(lonLine))) / 2, y, lonLine);
+ }
}
}
-/// Ported from my old java code, returns distance in meters along the globe
-/// surface (by magic?)
-static float 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;
- double cos_b1 = cos(b1);
- double cos_a1 = cos(a1);
- double t1 = cos_a1 * cos(a2) * cos_b1 * cos(b2);
- double t2 = cos_a1 * sin(a2) * cos_b1 * sin(b2);
- double t3 = sin(a1) * sin(b1);
- double tt = acos(t1 + t2 + t3);
- if (isnan(tt))
- tt = 0.0; // Must have been the same point?
-
- return (float)(6366000 * tt);
-}
-
-static inline double toRadians(double deg)
-{
- return deg * PI / 180;
-}
-
-static inline double toDegrees(double r)
-{
- return r * 180 / PI;
-}
-
-/**
- * Computes the bearing in degrees between two points on Earth. Ported from my
- * old Gaggle android app.
- *
- * @param lat1
- * Latitude of the first point
- * @param lon1
- * Longitude of the first point
- * @param lat2
- * Latitude of the second point
- * @param lon2
- * Longitude of the second point
- * @return Bearing between the two points in radians. A value of 0 means due
- * north.
- */
-static float bearing(double lat1, double lon1, double lat2, double lon2)
-{
- double lat1Rad = toRadians(lat1);
- double lat2Rad = toRadians(lat2);
- double deltaLonRad = toRadians(lon2 - lon1);
- double y = sin(deltaLonRad) * cos(lat2Rad);
- double x = cos(lat1Rad) * sin(lat2Rad) - (sin(lat1Rad) * cos(lat2Rad) * cos(deltaLonRad));
- return atan2(y, x);
-}
-
namespace
{
@@ -514,11 +497,11 @@ static float estimatedHeading(double lat, double lon)
return b;
}
- float d = latLongToMeter(oldLat, oldLon, lat, lon);
+ float d = GeoCoord::latLongToMeter(oldLat, oldLon, lat, lon);
if (d < 10) // haven't moved enough, just keep current bearing
return b;
- b = bearing(oldLat, oldLon, lat, lon);
+ b = GeoCoord::bearing(oldLat, oldLon, lat, lon);
oldLat = lat;
oldLon = lon;
@@ -638,7 +621,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
// display direction toward node
hasNodeHeading = true;
Position &p = node->position;
- float d = latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
+ float d = GeoCoord::latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
if (d < 2000)
snprintf(distStr, sizeof(distStr), "%.0f m", d);
else
@@ -646,7 +629,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
// FIXME, also keep the guess at the operators heading and add/substract
// it. currently we don't do this and instead draw north up only.
- float bearingToOther = bearing(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
+ float bearingToOther = GeoCoord::bearing(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
headingRadian = bearingToOther - myHeading;
drawNodeHeading(display, compassX, compassY, headingRadian);
}
@@ -947,7 +930,9 @@ void Screen::setFrames()
normalFrames[numframes++] = drawTextMessageFrame;
// then all the nodes
- for (size_t i = 0; i < numnodes; i++)
+ // We only show a few nodes in our scrolling list - because meshes with many nodes would have too many screens
+ size_t numToShow = min(numnodes, 4U);
+ for (size_t i = 0; i < numToShow; i++)
normalFrames[numframes++] = drawNodeInfo;
// then the debug info
@@ -1329,7 +1314,8 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
#endif
// Line 3
- drawGPSAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
+ if (radioConfig.preferences.gps_format != GpsCoordinateFormat_GpsFormatDMS) // if DMS then don't draw altitude
+ drawGPSAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
// Line 4
drawGPScoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus);
diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h
index 65e7b0a12..1c543dbf0 100644
--- a/src/graphics/Screen.h
+++ b/src/graphics/Screen.h
@@ -22,6 +22,8 @@ class Screen
#include
+#include "../configuration.h"
+
#ifdef USE_SH1106
#include
#elif defined(USE_ST7567)
@@ -30,7 +32,7 @@ class Screen
#include
#endif
-#include "EInkDisplay.h"
+#include "EInkDisplay2.h"
#include "TFTDisplay.h"
#include "TypedQueue.h"
#include "commands.h"
diff --git a/src/main.cpp b/src/main.cpp
index c6bebe36f..0171631e7 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,4 +1,4 @@
-
+#include "configuration.h"
#include "GPS.h"
#include "MeshRadio.h"
#include "MeshService.h"
@@ -6,7 +6,6 @@
#include "PowerFSM.h"
#include "airtime.h"
#include "buzz.h"
-#include "configuration.h"
#include "error.h"
#include "power.h"
// #include "rom/rtc.h"
@@ -26,9 +25,10 @@
#include
// #include
+#include "mesh/http/WiFiAPClient.h"
+
#ifndef NO_ESP32
#include "mesh/http/WebServer.h"
-#include "mesh/http/WiFiAPClient.h"
#include "nimble/BluetoothUtil.h"
#endif
@@ -39,6 +39,7 @@
#include "RF95Interface.h"
#include "SX1262Interface.h"
+#include "SX1268Interface.h"
#ifdef NRF52_SERIES
#include "variant.h"
@@ -299,6 +300,13 @@ uint32_t ButtonThread::longPressTime = 0;
RadioInterface *rIf = NULL;
+/**
+ * Some platforms (nrf52) might provide an alterate version that supresses calling delay from sleep.
+ */
+__attribute__ ((weak, noinline)) bool loopCanSleep() {
+ return true;
+}
+
void setup()
{
concurrency::hasBeenSetup = true;
@@ -331,9 +339,10 @@ void setup()
digitalWrite(RESET_OLED, 1);
#endif
+ bool forceSoftAP = 0;
+
#ifdef BUTTON_PIN
#ifndef NO_ESP32
- bool forceSoftAP = 0;
// If the button is connected to GPIO 12, don't enable the ability to use
// meshtasticAdmin on the device.
@@ -459,7 +468,7 @@ void setup()
// Do this after service.init (because that clears error_code)
#ifdef AXP192_SLAVE_ADDRESS
if (!axp192_found)
- recordCriticalError(CriticalErrorCode_NoAXP192); // Record a hardware fault for missing hardware
+ RECORD_CRITICALERROR(CriticalErrorCode_NoAXP192); // Record a hardware fault for missing hardware
#endif
// Don't call screen setup until after nodedb is setup (because we need
@@ -483,10 +492,10 @@ void setup()
}
}
-#ifdef SX1262_ANT_SW
- // make analog PA vs not PA switch on SX1262 eval board work properly
- pinMode(SX1262_ANT_SW, OUTPUT);
- digitalWrite(SX1262_ANT_SW, 1);
+#ifdef SX126X_ANT_SW
+ // make analog PA vs not PA switch on SX126x eval board work properly
+ pinMode(SX126X_ANT_SW, OUTPUT);
+ digitalWrite(SX126X_ANT_SW, 1);
#endif
// radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init)
@@ -499,20 +508,33 @@ void setup()
delete rIf;
rIf = NULL;
} else {
- DEBUG_MSG("Radio init succeeded, using RF95 radio\n");
+ DEBUG_MSG("RF95 Radio init succeeded, using RF95 radio\n");
}
}
#endif
-#if defined(SX1262_CS)
+#if defined(USE_SX1262)
if (!rIf) {
- rIf = new SX1262Interface(SX1262_CS, SX1262_DIO1, SX1262_RESET, SX1262_BUSY, SPI);
+ rIf = new SX1262Interface(SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY, SPI);
if (!rIf->init()) {
DEBUG_MSG("Warning: Failed to find SX1262 radio\n");
delete rIf;
rIf = NULL;
} else {
- DEBUG_MSG("Radio init succeeded, using SX1262 radio\n");
+ DEBUG_MSG("SX1262 Radio init succeeded, using SX1262 radio\n");
+ }
+ }
+#endif
+
+#if defined(USE_SX1268)
+ if (!rIf) {
+ rIf = new SX1268Interface(SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY, SPI);
+ if (!rIf->init()) {
+ DEBUG_MSG("Warning: Failed to find SX1268 radio\n");
+ delete rIf;
+ rIf = NULL;
+ } else {
+ DEBUG_MSG("SX1268 Radio init succeeded, using SX1268 radio\n");
}
}
#endif
@@ -530,10 +552,14 @@ void setup()
}
#endif
-#ifndef NO_ESP32
+#if defined(PORTDUINO) || defined(HAS_WIFI)
+ mqttInit();
+#endif
+
// Initialize Wifi
initWifi(forceSoftAP);
+#ifndef NO_ESP32
// Start web server thread.
webServerThread = new WebServerThread();
#endif
@@ -542,15 +568,11 @@ void setup()
initApiServer();
#endif
-#if defined(PORTDUINO) || defined(HAS_WIFI)
- mqttInit();
-#endif
-
// Start airtime logger thread.
airTime = new AirTime();
if (!rIf)
- recordCriticalError(CriticalErrorCode_NoRadio);
+ RECORD_CRITICALERROR(CriticalErrorCode_NoRadio);
else
router->addInterface(rIf);
@@ -640,7 +662,9 @@ void loop()
mainController.nextThread->tillRun(millis())); */
// We want to sleep as long as possible here - because it saves power
- if (!runASAP)
+ if (!runASAP && loopCanSleep()) {
+ // if(delayMsec > 100) DEBUG_MSG("sleeping %ld\n", delayMsec);
mainDelay.delay(delayMsec);
+ }
// if (didWake) DEBUG_MSG("wake!\n");
}
diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp
index 1e49d9319..652fe99a2 100644
--- a/src/mesh/Channels.cpp
+++ b/src/mesh/Channels.cpp
@@ -1,3 +1,4 @@
+#include "configuration.h"
#include "Channels.h"
#include "CryptoEngine.h"
#include "NodeDB.h"
diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp
index 59cb7ad8f..bce9bb02f 100644
--- a/src/mesh/CryptoEngine.cpp
+++ b/src/mesh/CryptoEngine.cpp
@@ -1,5 +1,5 @@
-#include "CryptoEngine.h"
#include "configuration.h"
+#include "CryptoEngine.h"
void CryptoEngine::setKey(const CryptoKey &k)
{
@@ -32,6 +32,10 @@ void CryptoEngine::decrypt(uint32_t fromNode, uint64_t packetNum, size_t numByte
void CryptoEngine::initNonce(uint32_t fromNode, uint64_t packetNum)
{
memset(nonce, 0, sizeof(nonce));
- *((uint64_t *)&nonce[0]) = packetNum;
- *((uint32_t *)&nonce[8]) = fromNode;
+
+ // use memcpy to avoid breaking strict-aliasing
+ memcpy(nonce, &packetNum, sizeof(uint64_t));
+ memcpy(nonce + sizeof(uint64_t), &fromNode, sizeof(uint32_t));
+ //*((uint64_t *)&nonce[0]) = packetNum;
+ //*((uint32_t *)&nonce[8]) = fromNode;
}
\ No newline at end of file
diff --git a/src/mesh/DSRRouter.cpp b/src/mesh/DSRRouter.cpp
index f7d8588e4..c2a170ec7 100644
--- a/src/mesh/DSRRouter.cpp
+++ b/src/mesh/DSRRouter.cpp
@@ -1,5 +1,5 @@
-#include "DSRRouter.h"
#include "configuration.h"
+#include "DSRRouter.h"
/* when we receive any packet
diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index 491501e91..fa37aa504 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -1,5 +1,5 @@
-#include "FloodingRouter.h"
#include "configuration.h"
+#include "FloodingRouter.h"
#include "mesh-pb-constants.h"
FloodingRouter::FloodingRouter() {}
diff --git a/src/mesh/InterfacesTemplates.cpp b/src/mesh/InterfacesTemplates.cpp
new file mode 100644
index 000000000..a50fbd6b8
--- /dev/null
+++ b/src/mesh/InterfacesTemplates.cpp
@@ -0,0 +1,6 @@
+#include "SX126xInterface.h"
+#include "SX126xInterface.cpp"
+
+// We need this declaration for proper linking in derived classes
+template class SX126xInterface;
+template class SX126xInterface;
\ No newline at end of file
diff --git a/src/mesh/MeshPacketQueue.cpp b/src/mesh/MeshPacketQueue.cpp
index 6c964e509..87347f309 100644
--- a/src/mesh/MeshPacketQueue.cpp
+++ b/src/mesh/MeshPacketQueue.cpp
@@ -1,16 +1,17 @@
+#include "configuration.h"
#include "MeshPacketQueue.h"
#include
/// @return the priority of the specified packet
-inline uint32_t getPriority(MeshPacket *p)
+inline uint32_t getPriority(const MeshPacket *p)
{
auto pri = p->priority;
return pri;
}
/// @return "true" if "p1" is ordered before "p2"
-bool CompareMeshPacket::operator()(MeshPacket *p1, MeshPacket *p2)
+bool CompareMeshPacketFunc(const MeshPacket *p1, const MeshPacket *p2)
{
assert(p1 && p2);
auto p1p = getPriority(p1), p2p = getPriority(p2);
@@ -24,7 +25,12 @@ bool CompareMeshPacket::operator()(MeshPacket *p1, MeshPacket *p2)
MeshPacketQueue::MeshPacketQueue(size_t _maxLen) : maxLen(_maxLen) {}
-/** Some clients might not properly set priority, therefore we fix it here.
+bool MeshPacketQueue::empty() {
+ return queue.empty();
+}
+
+/**
+ * Some clients might not properly set priority, therefore we fix it here.
*/
void fixPriority(MeshPacket *p)
{
@@ -33,7 +39,7 @@ void fixPriority(MeshPacket *p)
if (p->priority == MeshPacket_Priority_UNSET) {
// if acks give high priority
// if a reliable message give a bit higher default priority
- p->priority = (p->decoded.portnum == PortNum_ROUTING_APP) ? MeshPacket_Priority_ACK :
+ p->priority = (p->decoded.portnum == PortNum_ROUTING_APP) ? MeshPacket_Priority_ACK :
(p->want_ack ? MeshPacket_Priority_RELIABLE : MeshPacket_Priority_DEFAULT);
}
}
@@ -41,51 +47,70 @@ void fixPriority(MeshPacket *p)
/** enqueue a packet, return false if full */
bool MeshPacketQueue::enqueue(MeshPacket *p)
{
-
fixPriority(p);
- // fixme if there is something lower priority in the queue that can be deleted to make space, delete that instead
- if (size() >= maxLen)
- return false;
- else {
- push(p);
- return true;
+ // no space - try to replace a lower priority packet in the queue
+ if (queue.size() >= maxLen) {
+ return replaceLowerPriorityPacket(p);
}
+
+ queue.push_back(p);
+ std::push_heap(queue.begin(), queue.end(), &CompareMeshPacketFunc);
+ return true;
}
MeshPacket *MeshPacketQueue::dequeue()
{
- if (empty())
+ if (empty()) {
return NULL;
- else {
- auto p = top();
- pop(); // remove the first item
- return p;
}
+
+ auto *p = queue.front();
+ std::pop_heap(queue.begin(), queue.end(), &CompareMeshPacketFunc);
+ queue.pop_back();
+
+ return p;
}
-// this is kinda yucky, but I'm not sure if all arduino c++ compilers support closuers. And we only have one
-// thread that can run at a time - so safe
-static NodeNum findFrom;
-static PacketId findId;
-
-static bool isMyPacket(MeshPacket *p)
-{
- return p->id == findId && getFrom(p) == findFrom;
-}
-
-/** Attempt to find and remove a packet from this queue. Returns true the packet which was removed from the queue */
+/** Attempt to find and remove a packet from this queue. Returns a pointer to the removed packet, or NULL if not found */
MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id)
{
- findFrom = from;
- findId = id;
- auto it = std::find_if(this->c.begin(), this->c.end(), isMyPacket);
- if (it != this->c.end()) {
- auto p = *it;
- this->c.erase(it);
- std::make_heap(this->c.begin(), this->c.end(), this->comp);
- return p;
- } else {
- return NULL;
+ for (auto it = queue.begin(); it != queue.end(); it++) {
+ auto p = (*it);
+ if (getFrom(p) == from && p->id == id) {
+ queue.erase(it);
+ std::make_heap(queue.begin(), queue.end(), &CompareMeshPacketFunc);
+ return p;
+ }
}
+
+ return NULL;
+}
+
+/** Attempt to find and remove a packet from this queue. Returns the packet which was removed from the queue */
+bool MeshPacketQueue::replaceLowerPriorityPacket(MeshPacket *p) {
+ std::sort_heap(queue.begin(), queue.end(), &CompareMeshPacketFunc); // sort ascending based on priority (0 -> 127)
+
+ // find first packet which does not compare less (in priority) than parameter packet
+ auto low = std::lower_bound(queue.begin(), queue.end(), p, &CompareMeshPacketFunc);
+
+ if (low == queue.begin()) { // if already at start, there are no packets with lower priority
+ return false;
+ }
+
+ if (low == queue.end()) {
+ // all priorities in the vector are smaller than the incoming packet. Replace the lowest priority (first) element
+ low = queue.begin();
+ } else {
+ // 'low' iterator points to first packet which does not compare less than parameter
+ --low; // iterate to lower priority packet
+ }
+
+ if (getPriority(p) > getPriority(*low)) {
+ packetPool.release(*low); // deallocate and drop the packet we're replacing
+ *low = p; // replace low-pri packet at this position with incoming packet with higher priority
+ }
+
+ std::make_heap(queue.begin(), queue.end(), &CompareMeshPacketFunc);
+ return true;
}
diff --git a/src/mesh/MeshPacketQueue.h b/src/mesh/MeshPacketQueue.h
index f04649cb5..2c869db43 100644
--- a/src/mesh/MeshPacketQueue.h
+++ b/src/mesh/MeshPacketQueue.h
@@ -5,29 +5,29 @@
#include
#include
-// this is an strucure which implements the
-// operator overloading
-struct CompareMeshPacket {
- bool operator()(MeshPacket *p1, MeshPacket *p2);
-};
/**
- * A priority queue of packets.
- *
+ * A priority queue of packets
*/
-class MeshPacketQueue : public std::priority_queue, CompareMeshPacket>
+class MeshPacketQueue
{
size_t maxLen;
+ std::vector queue;
+
+ /** Replace a lower priority package in the queue with 'mp' (provided there are lower pri packages). Return true if replaced. */
+ bool replaceLowerPriorityPacket(MeshPacket *mp);
+
public:
MeshPacketQueue(size_t _maxLen);
/** enqueue a packet, return false if full */
bool enqueue(MeshPacket *p);
- // bool isEmpty();
+ /** return true if the queue is empty */
+ bool empty();
MeshPacket *dequeue();
- /** Attempt to find and remove a packet from this queue. Returns true the packet which was removed from the queue */
+ /** Attempt to find and remove a packet from this queue. Returns the packet which was removed from the queue */
MeshPacket *remove(NodeNum from, PacketId id);
};
\ No newline at end of file
diff --git a/src/mesh/MeshPlugin.cpp b/src/mesh/MeshPlugin.cpp
index 1c14001f2..f801682d1 100644
--- a/src/mesh/MeshPlugin.cpp
+++ b/src/mesh/MeshPlugin.cpp
@@ -1,3 +1,4 @@
+#include "configuration.h"
#include "MeshPlugin.h"
#include "Channels.h"
#include "MeshService.h"
@@ -65,7 +66,7 @@ MeshPacket *MeshPlugin::allocErrorResponse(Routing_Error err, const MeshPacket *
return r;
}
-void MeshPlugin::callPlugins(const MeshPacket &mp)
+void MeshPlugin::callPlugins(const MeshPacket &mp, RxSource src)
{
// DEBUG_MSG("In call plugins\n");
bool pluginFound = false;
@@ -78,6 +79,7 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
// Was this message directed to us specifically? Will be false if we are sniffing someone elses packets
auto ourNodeNum = nodeDB.getNodeNum();
bool toUs = mp.to == NODENUM_BROADCAST || mp.to == ourNodeNum;
+
for (auto i = plugins->begin(); i != plugins->end(); ++i) {
auto &pi = **i;
@@ -86,10 +88,16 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
/// We only call plugins that are interested in the packet (and the message is destined to us or we are promiscious)
bool wantsPacket = (isDecoded || pi.encryptedOk) && (pi.isPromiscuous || toUs) && pi.wantPacket(&mp);
- DEBUG_MSG("Plugin %s wantsPacket=%d\n", pi.name, wantsPacket);
+ if ((src == RX_SRC_LOCAL) && !(pi.loopbackOk)) {
+ // new case, monitor separately for now, then FIXME merge above
+ wantsPacket = false;
+ }
+
assert(!pi.myReply); // If it is !null it means we have a bug, because it should have been sent the previous time
if (wantsPacket) {
+ DEBUG_MSG("Plugin %s wantsPacket=%d\n", pi.name, wantsPacket);
+
pluginFound = true;
/// received channel (or NULL if not decoded)
@@ -97,20 +105,24 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
/// Is the channel this packet arrived on acceptable? (security check)
/// Note: we can't know channel names for encrypted packets, so those are NEVER sent to boundChannel plugins
- bool rxChannelOk = !pi.boundChannel || (ch && ((mp.from == 0) || (strcmp(ch->settings.name, pi.boundChannel) == 0)));
+
+ /// Also: if a packet comes in on the local PC interface, we don't check for bound channels, because it is TRUSTED and it needs to
+ /// to be able to fetch the initial admin packets without yet knowing any channels.
+
+ bool rxChannelOk = !pi.boundChannel || (mp.from == 0) || (ch && (strcmp(ch->settings.name, pi.boundChannel) == 0));
if (!rxChannelOk) {
// no one should have already replied!
assert(!currentReply);
if (mp.decoded.want_response) {
- DEBUG_MSG("packet on wrong channel, returning error\n");
+ printPacket("packet on wrong channel, returning error", &mp);
currentReply = pi.allocErrorResponse(Routing_Error_NOT_AUTHORIZED, &mp);
} else
- DEBUG_MSG("packet on wrong channel, but client didn't want response\n");
+ printPacket("packet on wrong channel, but can't respond", &mp);
} else {
- bool handled = pi.handleReceived(mp);
+ ProcessMessage handled = pi.handleReceived(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 plugin send a reply, once that happens, remaining plugins are not
@@ -134,7 +146,7 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
pi.myReply = NULL;
}
- if (handled) {
+ if (handled == ProcessMessage::STOP) {
DEBUG_MSG("Plugin %s handled and skipped other processing\n", pi.name);
break;
}
@@ -149,7 +161,9 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
printPacket("Sending response", currentReply);
service.sendToMesh(currentReply);
currentReply = NULL;
- } else {
+ } else if(mp.from != ourNodeNum) {
+ // Note: if the message started with the local node we don't want to send a no response reply
+
// No one wanted to reply to this requst, tell the requster that happened
DEBUG_MSG("No one responded, send a nak\n");
@@ -161,7 +175,9 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
}
if (!pluginFound)
- DEBUG_MSG("No plugins interested in portnum=%d\n", mp.decoded.portnum);
+ DEBUG_MSG("No plugins interested in portnum=%d, src=%s\n",
+ mp.decoded.portnum,
+ (src == RX_SRC_LOCAL) ? "LOCAL":"REMOTE");
}
MeshPacket *MeshPlugin::allocReply()
diff --git a/src/mesh/MeshPlugin.h b/src/mesh/MeshPlugin.h
index 937c37dfe..0143814a7 100644
--- a/src/mesh/MeshPlugin.h
+++ b/src/mesh/MeshPlugin.h
@@ -9,6 +9,18 @@
#include
#endif
+/** handleReceived return enumeration
+ *
+ * Use ProcessMessage::CONTINUE to allows other modules to process a message.
+ *
+ * Use ProcessMessage::STOP to stop further message processing.
+ */
+enum class ProcessMessage
+{
+ CONTINUE = 0,
+ STOP = 1,
+};
+
/** A baseclass for any mesh "plugin".
*
* A plugin allows you to add new features to meshtastic device code, without needing to know messaging details.
@@ -33,7 +45,7 @@ class MeshPlugin
/** For use only by MeshService
*/
- static void callPlugins(const MeshPacket &mp);
+ static void callPlugins(const MeshPacket &mp, RxSource src = RX_SRC_RADIO);
static std::vector GetMeshPluginsWithUIFrames();
#ifndef NO_SCREEN
@@ -48,6 +60,10 @@ class MeshPlugin
*/
bool isPromiscuous = false;
+ /** Also receive a copy of LOCALLY GENERATED messages - most plugins should leave
+ * this setting disabled - see issue #877 */
+ bool loopbackOk = false;
+
/** Most plugins only understand decrypted packets. For plugins that also want to see encrypted packets, they should set this
* flag */
bool encryptedOk = false;
@@ -87,9 +103,9 @@ class MeshPlugin
/** 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
+ @return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for it
*/
- virtual bool handleReceived(const MeshPacket &mp) { return false; }
+ virtual ProcessMessage handleReceived(const MeshPacket &mp) { return ProcessMessage::CONTINUE; }
/** 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.
diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp
index 4a42f7716..acfb5f970 100644
--- a/src/mesh/MeshService.cpp
+++ b/src/mesh/MeshService.cpp
@@ -1,5 +1,4 @@
-
-#include
+#include "configuration.h"
#include
#include
@@ -67,7 +66,7 @@ void MeshService::init()
int MeshService::handleFromRadio(const MeshPacket *mp)
{
- powerFSM.trigger(EVENT_RECEIVED_PACKET); // Possibly keep the node from sleeping
+ powerFSM.trigger(EVENT_PACKET_FOR_PHONE); // Possibly keep the node from sleeping
printPacket("Forwarding to phone", mp);
nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
@@ -141,7 +140,7 @@ void MeshService::handleToRadio(MeshPacket &p)
// Send the packet into the mesh
- sendToMesh(packetPool.allocCopy(p));
+ sendToMesh(packetPool.allocCopy(p), RX_SRC_USER);
bool loopback = false; // if true send any packet the phone sends back itself (for testing)
if (loopback) {
@@ -158,12 +157,12 @@ bool MeshService::cancelSending(PacketId id)
return router->cancelSending(nodeDB.getNodeNum(), id);
}
-void MeshService::sendToMesh(MeshPacket *p)
+void MeshService::sendToMesh(MeshPacket *p, RxSource src)
{
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
- router->sendLocal(p);
+ router->sendLocal(p, src);
}
void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
@@ -210,34 +209,39 @@ NodeInfo *MeshService::refreshMyNodeInfo()
return node;
}
-int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused)
+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)
NodeInfo *node = refreshMyNodeInfo();
- Position pos = node->position;
+ Position pos = Position_init_default;
- if (gps->hasLock()) {
- if (gps->altitude != 0)
- pos.altitude = gps->altitude;
- pos.latitude_i = gps->latitude;
- pos.longitude_i = gps->longitude;
+ if (newStatus->getHasLock()) {
+ // load data from GPS object, will add timestamp + battery further down
+ pos = gps->p;
} else {
// The GPS has lost lock, if we are fixed position we should just keep using
// the old position
+#if GPS_EXTRAVERBOSE
+ DEBUG_MSG("onGPSchanged() - lost validLocation\n");
+#endif
if (radioConfig.preferences.fixed_position) {
DEBUG_MSG("WARNING: Using fixed position\n");
- } else {
- // throw away old position
- pos.latitude_i = 0;
- pos.longitude_i = 0;
- pos.altitude = 0;
+ pos = node->position;
}
}
- DEBUG_MSG("got gps notify time=%u, lat=%d, bat=%d\n", pos.time, pos.latitude_i, pos.battery_level);
+ // Finally add a fresh timestamp and battery level reading
+ // I KNOW this is redundant with refreshMyNodeInfo() above, but these are
+ // inexpensive nonblocking calls and can be refactored in due course
+ pos.time = getValidTime(RTCQualityGPS);
+ pos.battery_level = powerStatus->getBatteryChargePercent();
+
+ // In debug logs, identify position by @timestamp:stage (stage 4 = nodeDB)
+ DEBUG_MSG("onGPSChanged() pos@%x:4, time=%u, lat=%d, bat=%d\n",
+ pos.pos_timestamp, pos.time, pos.latitude_i, pos.battery_level);
// Update our current position in the local DB
- nodeDB.updatePosition(nodeDB.getNodeNum(), pos);
+ nodeDB.updatePosition(nodeDB.getNodeNum(), pos, RX_SRC_LOCAL);
return 0;
}
diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h
index 7d22a3004..eecfbdf64 100644
--- a/src/mesh/MeshService.h
+++ b/src/mesh/MeshService.h
@@ -75,7 +75,7 @@ class MeshService
/// Send a packet into the mesh - note p must have been allocated from packetPool. We will return it to that pool after
/// sending. This is the ONLY function you should use for sending messages into the mesh, because it also updates the nodedb
/// cache
- void sendToMesh(MeshPacket *p);
+ void sendToMesh(MeshPacket *p, RxSource src = RX_SRC_LOCAL);
/** Attempt to cancel a previously sent packet from this _local_ node. Returns true if a packet was found we could cancel */
bool cancelSending(PacketId id);
diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h
index 34d4f02c9..c08637385 100644
--- a/src/mesh/MeshTypes.h
+++ b/src/mesh/MeshTypes.h
@@ -15,6 +15,15 @@ typedef uint32_t PacketId; // A packet sequence number
#define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER
#define ERRNO_DISABLED 34 // the itnerface is disabled
+/*
+ * Source of a received message
+ */
+enum RxSource {
+ RX_SRC_LOCAL, // message was generated locally
+ RX_SRC_RADIO, // message was received from radio mesh
+ RX_SRC_USER // message was received from end-user device
+};
+
/**
* the max number of hops a message can pass through, used as the default max for hop_limit in MeshPacket.
*
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index a6ce8e4c8..e5594b212 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -1,5 +1,4 @@
-
-#include
+#include "configuration.h"
#include
#include "FS.h"
@@ -14,7 +13,6 @@
#include "PowerFSM.h"
#include "RTC.h"
#include "Router.h"
-#include "configuration.h"
#include "error.h"
#include "main.h"
#include "mesh-pb-constants.h"
@@ -127,6 +125,11 @@ void NodeDB::installDefaultRadioConfig()
memset(&radioConfig, 0, sizeof(radioConfig));
radioConfig.has_preferences = true;
resetRadioConfig();
+
+ // for backward compat, default position flags are BAT+ALT+MSL (0x23 = 35)
+ radioConfig.preferences.position_flags = (PositionFlags_POS_BATTERY |
+ PositionFlags_POS_ALTITUDE | PositionFlags_POS_ALT_MSL);
+
}
void NodeDB::installDefaultChannels()
@@ -444,25 +447,33 @@ size_t NodeDB::getNumOnlineNodes()
/** Update position info for this node based on received position data
*/
-void NodeDB::updatePosition(uint32_t nodeId, const Position &p)
+void NodeDB::updatePosition(uint32_t nodeId, const Position &p, RxSource src)
{
NodeInfo *info = getOrCreateNode(nodeId);
- DEBUG_MSG("DB update position node=0x%x time=%u, latI=%d, lonI=%d\n", nodeId, p.time, p.latitude_i, p.longitude_i);
+ if (src == RX_SRC_LOCAL) {
+ // Local packet, fully authoritative
+ DEBUG_MSG("updatePosition LOCAL pos@%x:5, time=%u, latI=%d, lonI=%d\n",
+ p.pos_timestamp, p.time, p.latitude_i, p.longitude_i);
+ info->position = p;
- // Be careful to only update fields that have been set by the sender
- // A lot of position reports don't have time populated. In that case, be careful to not blow away the time we
- // recorded based on the packet rxTime
- if (p.time)
- info->position.time = p.time;
- if (p.battery_level)
- info->position.battery_level = p.battery_level;
- if (p.latitude_i || p.longitude_i) {
- info->position.latitude_i = p.latitude_i;
- info->position.longitude_i = p.longitude_i;
+ } else {
+ // Be careful to only update fields that have been set by the REMOTE sender
+ // A lot of position reports don't have time populated. In that case, be careful to not blow away the time we
+ // recorded based on the packet rxTime
+ DEBUG_MSG("updatePosition REMOTE node=0x%x time=%u, latI=%d, lonI=%d\n",
+ nodeId, p.time, p.latitude_i, p.longitude_i);
+
+ // First, back up fields that we want to protect from overwrite
+ uint32_t tmp_time = info->position.time;
+
+ // Next, update atomically
+ info->position = p;
+
+ // Last, restore any fields that may have been overwritten
+ if (! info->position.time)
+ info->position.time = tmp_time;
}
- if (p.altitude)
- info->position.altitude = p.altitude;
info->has_position = true;
updateGUIforNode = info;
notifyObservers(true); // Force an update whether or not our node counts have changed
@@ -541,15 +552,24 @@ NodeInfo *NodeDB::getOrCreateNode(NodeNum n)
}
/// Record an error that should be reported via analytics
-void recordCriticalError(CriticalErrorCode code, uint32_t address)
+void recordCriticalError(CriticalErrorCode code, uint32_t address, const char *filename)
{
// Print error to screen and serial port
String lcd = String("Critical error ") + code + "!\n";
screen->print(lcd.c_str());
- DEBUG_MSG("NOTE! Recording critical error %d, address=%lx\n", code, address);
+ if(filename)
+ DEBUG_MSG("NOTE! Recording critical error %d at %s:%lx\n", code, filename, address);
+ else
+ DEBUG_MSG("NOTE! Recording critical error %d, address=%lx\n", code, address);
// Record error to DB
myNodeInfo.error_code = code;
myNodeInfo.error_address = address;
myNodeInfo.error_count++;
+
+ // Currently portuino is mostly used for simulation. Make sue the user notices something really bad happend
+#ifdef PORTDUINO
+ DEBUG_MSG("A critical failure occurred, portduino is exiting...");
+ exit(2);
+#endif
}
diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h
index bd7346455..86ab874e8 100644
--- a/src/mesh/NodeDB.h
+++ b/src/mesh/NodeDB.h
@@ -59,7 +59,7 @@ class NodeDB
/** Update position info for this node based on received position data
*/
- void updatePosition(uint32_t nodeId, const Position &p);
+ void updatePosition(uint32_t nodeId, const Position &p, RxSource src = RX_SRC_RADIO);
/** Update user info for this node based on received user data
*/
diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp
index 19ce41bc8..c8008689d 100644
--- a/src/mesh/PacketHistory.cpp
+++ b/src/mesh/PacketHistory.cpp
@@ -1,5 +1,5 @@
-#include "PacketHistory.h"
#include "configuration.h"
+#include "PacketHistory.h"
#include "mesh-pb-constants.h"
PacketHistory::PacketHistory()
@@ -19,35 +19,57 @@ bool PacketHistory::wasSeenRecently(const MeshPacket *p, bool withUpdate)
}
uint32_t now = millis();
- for (size_t i = 0; i < recentPackets.size();) {
- PacketRecord &r = recentPackets[i];
- if ((now - r.rxTimeMsec) >= FLOOD_EXPIRE_TIME) {
- // DEBUG_MSG("Deleting old broadcast record %d\n", i);
- recentPackets.erase(recentPackets.begin() + i); // delete old record
+ PacketRecord r;
+ r.id = p->id;
+ r.sender = getFrom(p);
+ r.rxTimeMsec = now;
+
+ auto found = recentPackets.find(r);
+ bool seenRecently = (found != recentPackets.end()); // found not equal to .end() means packet was seen recently
+
+ if (seenRecently && (now - found->rxTimeMsec) >= FLOOD_EXPIRE_TIME) { // Check whether found packet has already expired
+ recentPackets.erase(found); // Erase and pretend packet has not been seen recently
+ found = recentPackets.end();
+ seenRecently = false;
+ }
+
+ if (seenRecently) {
+ DEBUG_MSG("Found existing packet record for fr=0x%x,to=0x%x,id=0x%x\n", p->from, p->to, p->id);
+ }
+
+ if (withUpdate) {
+ if (found != recentPackets.end()) { // delete existing to updated timestamp (re-insert)
+ recentPackets.erase(found); // as unsorted_set::iterator is const (can't update timestamp - so re-insert..)
+ }
+ recentPackets.insert(r);
+ printPacket("Add packet record", p);
+ }
+
+ // Capacity is reerved, so only purge expired packets if recentPackets fills past 90% capacity
+ // Expiry is normally dealt with after having searched/found a packet (above)
+ if (recentPackets.size() > (MAX_NUM_NODES * 0.9)) {
+ clearExpiredRecentPackets();
+ }
+
+ return seenRecently;
+}
+
+/**
+ * Iterate through all recent packets, and remove all older than FLOOD_EXPIRE_TIME
+ */
+void PacketHistory::clearExpiredRecentPackets() {
+ uint32_t now = millis();
+
+ DEBUG_MSG("recentPackets size=%ld\n", recentPackets.size());
+
+ for (auto it = recentPackets.begin(); it != recentPackets.end(); ) {
+ if ((now - it->rxTimeMsec) >= FLOOD_EXPIRE_TIME) {
+ it = recentPackets.erase(it); // erase returns iterator pointing to element immediately following the one erased
} else {
- if (r.id == p->id && r.sender == getFrom(p)) {
- DEBUG_MSG("Found existing packet record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
-
- // Update the time on this record to now
- if (withUpdate)
- r.rxTimeMsec = now;
- return true;
- }
-
- i++;
+ ++it;
}
}
- // Didn't find an existing record, make one
- if (withUpdate) {
- PacketRecord r;
- r.id = p->id;
- r.sender = getFrom(p);
- r.rxTimeMsec = now;
- recentPackets.push_back(r);
- printPacket("Adding packet record", p);
- }
-
- return false;
+ DEBUG_MSG("recentPackets size=%ld (after clearing expired packets)\n", recentPackets.size());
}
\ No newline at end of file
diff --git a/src/mesh/PacketHistory.h b/src/mesh/PacketHistory.h
index d390915a2..d44a3d369 100644
--- a/src/mesh/PacketHistory.h
+++ b/src/mesh/PacketHistory.h
@@ -1,11 +1,8 @@
#pragma once
#include "Router.h"
-#include
#include
-using namespace std;
-
/// We clear our old flood record five minute after we see the last of it
#define FLOOD_EXPIRE_TIME (5 * 60 * 1000L)
@@ -23,26 +20,7 @@ struct PacketRecord {
class PacketRecordHashFunction
{
public:
- size_t operator()(const PacketRecord &p) const { return (hash()(p.sender)) ^ (hash()(p.id)); }
-};
-
-/// Order packet records by arrival time, we want the oldest packets to be in the front of our heap
-class PacketRecordOrderFunction
-{
- public:
- size_t operator()(const PacketRecord &p1, const PacketRecord &p2) const
- {
- // If the timer ticks have rolled over the difference between times will be _enormous_. Handle that case specially
- uint32_t t1 = p1.rxTimeMsec, t2 = p2.rxTimeMsec;
-
- if (t1 - t2 > UINT32_MAX / 2) {
- // time must have rolled over, swap them because the new little number is 'bigger' than the old big number
- t1 = t2;
- t2 = p1.rxTimeMsec;
- }
-
- return t1 > t2;
- }
+ size_t operator()(const PacketRecord &p) const { return (std::hash()(p.sender)) ^ (std::hash()(p.id)); }
};
/**
@@ -51,12 +29,9 @@ class PacketRecordOrderFunction
class PacketHistory
{
private:
- /** FIXME: really should be a std::unordered_set with the key being sender,id.
- * This would make checking packets in wasSeenRecently faster.
- */
- vector recentPackets;
- // priority_queue, PacketRecordOrderFunction> arrivalTimes;
- // unordered_set recentPackets;
+ std::unordered_set recentPackets;
+
+ void clearExpiredRecentPackets(); // clear all recentPackets older than FLOOD_EXPIRE_TIME
public:
PacketHistory();
diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index 018ab6d3c..4f434fa1f 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -1,3 +1,4 @@
+#include "configuration.h"
#include "PhoneAPI.h"
#include "Channels.h"
#include "GPS.h"
@@ -27,6 +28,7 @@ PhoneAPI::~PhoneAPI()
void PhoneAPI::handleStartConfig()
{
+ // Must be before setting state (because state is how we know !connected)
if (!isConnected()) {
onConnectionChanged(true);
observe(&service.fromNumChanged);
@@ -35,7 +37,7 @@ void PhoneAPI::handleStartConfig()
// even if we were already connected - restart our state machine
state = STATE_SEND_MY_INFO;
- DEBUG_MSG("Reset nodeinfo read pointer\n");
+ DEBUG_MSG("Starting API client config\n");
nodeInfoForPhone = NULL; // Don't keep returning old nodeinfos
nodeDB.resetReadPointer(); // FIXME, this read pointer should be moved out of nodeDB and into this class - because
// this will break once we have multiple instances of PhoneAPI running independently
@@ -56,10 +58,9 @@ void PhoneAPI::close()
void PhoneAPI::checkConnectionTimeout()
{
if (isConnected()) {
- uint32_t now = millis();
- bool newContact = (now - lastContactMsec) < getPref_phone_timeout_secs() * 1000UL;
+ bool newContact = checkIsConnected();
if (!newContact) {
- DEBUG_MSG("Timed out on phone contact, dropping phone connection\n");
+ DEBUG_MSG("Lost phone connection\n");
close();
}
}
@@ -93,7 +94,8 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
break;
default:
- DEBUG_MSG("Error: unexpected ToRadio variant\n");
+ // Ignore nop messages
+ // DEBUG_MSG("Error: unexpected ToRadio variant\n");
break;
}
} else {
diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h
index 02165aae3..e818bba56 100644
--- a/src/mesh/PhoneAPI.h
+++ b/src/mesh/PhoneAPI.h
@@ -50,9 +50,6 @@ class PhoneAPI
/// Use to ensure that clients don't get confused about old messages from the radio
uint32_t config_nonce = 0;
- /** the last msec we heard from the client on the other side of this link */
- uint32_t lastContactMsec = 0;
-
public:
PhoneAPI();
@@ -84,16 +81,25 @@ class PhoneAPI
bool isConnected() { return state != STATE_SEND_NOTHING; }
+ /// emit a debugging log character, FIXME - implement
+ void debugOut(char c) { }
+
protected:
/// Our fromradio packet while it is being assembled
FromRadio fromRadioScratch;
+ /** the last msec we heard from the client on the other side of this link */
+ uint32_t lastContactMsec = 0;
+
/// 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();
+ /// Check the current underlying physical link to see if the client is currently connected
+ virtual bool checkIsConnected() = 0;
+
/**
* Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies)
*/
diff --git a/src/mesh/ProtobufPlugin.cpp b/src/mesh/ProtobufPlugin.cpp
index 36728c3cd..9cb4b9c2d 100644
--- a/src/mesh/ProtobufPlugin.cpp
+++ b/src/mesh/ProtobufPlugin.cpp
@@ -1,3 +1,4 @@
+#include "configuration.h"
#include "ProtobufPlugin.h"
diff --git a/src/mesh/ProtobufPlugin.h b/src/mesh/ProtobufPlugin.h
index 6984cf626..72e3b902e 100644
--- a/src/mesh/ProtobufPlugin.h
+++ b/src/mesh/ProtobufPlugin.h
@@ -28,7 +28,7 @@ template class ProtobufPlugin : protected SinglePortPlugin
* In general decoded will always be !NULL. But in some special applications (where you have handling packets
* for multiple port numbers, decoding will ONLY be attempted for packets where the portnum matches our expected ourPortNum.
*/
- virtual bool handleReceivedProtobuf(const MeshPacket &mp, const T *decoded) = 0;
+ virtual bool handleReceivedProtobuf(const MeshPacket &mp, T *decoded) = 0;
/**
* Return a mesh packet which has been preinited with a particular protobuf data payload and port number.
@@ -49,9 +49,9 @@ template class ProtobufPlugin : protected SinglePortPlugin
private:
/** 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
+ @return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for it
*/
- virtual bool handleReceived(const MeshPacket &mp)
+ virtual ProcessMessage handleReceived(const MeshPacket &mp)
{
// FIXME - we currently update position data in the DB only if the message was a broadcast or destined to us
// it would be better to update even if the message was destined to others.
@@ -70,6 +70,6 @@ template class ProtobufPlugin : protected SinglePortPlugin
DEBUG_MSG("Error decoding protobuf plugin!\n");
}
- return handleReceivedProtobuf(mp, decoded);
+ return handleReceivedProtobuf(mp, decoded) ? ProcessMessage::STOP : ProcessMessage::CONTINUE;
}
};
diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp
index e99ec2e36..5a1cce32a 100644
--- a/src/mesh/RF95Interface.cpp
+++ b/src/mesh/RF95Interface.cpp
@@ -1,8 +1,8 @@
+#include "configuration.h"
#include "RF95Interface.h"
#include "MeshRadio.h" // kinda yucky, but we need to know which region we are in
#include "RadioLibRF95.h"
#include "error.h"
-#include
#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
@@ -67,9 +67,15 @@ bool RF95Interface::init()
#endif
setTransmitEnable(false);
- int res = lora->begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength);
+ int res = lora->begin(getFreq(), bw, sf, cr, syncWord, power, currentLimit, preambleLength);
DEBUG_MSG("RF95 init result %d\n", res);
+ // current limit was removed from module' ctor
+ // override default value (60 mA)
+ res = lora->setCurrentLimit(currentLimit);
+ DEBUG_MSG("Current limit set to %f\n", currentLimit);
+ DEBUG_MSG("Current limit set result %d\n", res);
+
if (res == ERR_NONE)
res = lora->setCRC(SX126X_LORA_CRC_ON);
@@ -94,15 +100,15 @@ bool RF95Interface::reconfigure()
// configure publicly accessible settings
int err = lora->setSpreadingFactor(sf);
if (err != ERR_NONE)
- recordCriticalError(CriticalErrorCode_InvalidRadioSetting);
+ RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
err = lora->setBandwidth(bw);
if (err != ERR_NONE)
- recordCriticalError(CriticalErrorCode_InvalidRadioSetting);
+ RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
err = lora->setCodingRate(cr);
if (err != ERR_NONE)
- recordCriticalError(CriticalErrorCode_InvalidRadioSetting);
+ RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
err = lora->setSyncWord(syncWord);
assert(err == ERR_NONE);
@@ -113,15 +119,15 @@ bool RF95Interface::reconfigure()
err = lora->setPreambleLength(preambleLength);
assert(err == ERR_NONE);
- err = lora->setFrequency(freq);
+ err = lora->setFrequency(getFreq());
if (err != ERR_NONE)
- recordCriticalError(CriticalErrorCode_InvalidRadioSetting);
+ RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
if (power > MAX_POWER) // This chip has lower power limits than some
power = MAX_POWER;
err = lora->setOutputPower(power);
if (err != ERR_NONE)
- recordCriticalError(CriticalErrorCode_InvalidRadioSetting);
+ RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
startReceive(); // restart receiving
diff --git a/src/mesh/RF95Interface.h b/src/mesh/RF95Interface.h
index ebebe3c79..59930adbd 100644
--- a/src/mesh/RF95Interface.h
+++ b/src/mesh/RF95Interface.h
@@ -14,7 +14,6 @@ class RF95Interface : public RadioLibInterface
public:
RF95Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, SPIClass &spi);
- /// Some boards (Pinetab Lora module) have broken IRQ wires, so we need to poll via i2c registers
bool isIRQPending() { return lora->getPendingIRQ(); }
/// Initialise the Driver transport hardware and software.
diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index 5e63352c9..cf86ae01c 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -1,4 +1,4 @@
-
+#include "configuration.h"
#include "RadioInterface.h"
#include "Channels.h"
#include "MeshRadio.h"
@@ -6,7 +6,6 @@
#include "NodeDB.h"
#include "Router.h"
#include "assert.h"
-#include "configuration.h"
#include "sleep.h"
#include
#include
@@ -319,18 +318,18 @@ void RadioInterface::applyModemConfig()
// If user has manually specified a channel num, then use that, otherwise generate one by hashing the name
const char *channelName = channels.getName(channels.getPrimaryIndex());
int channel_num = channelSettings.channel_num ? channelSettings.channel_num - 1 : hash(channelName) % myRegion->numChannels;
- freq = myRegion->freq + radioConfig.preferences.frequency_offset + myRegion->spacing * channel_num;
+ float freq = myRegion->freq + radioConfig.preferences.frequency_offset + myRegion->spacing * channel_num;
+
+ saveChannelNum(channel_num);
+ saveFreq(freq);
DEBUG_MSG("Set radio: name=%s, config=%u, ch=%d, power=%d\n", channelName, channelSettings.modem_config, channel_num, power);
DEBUG_MSG("Radio myRegion->freq: %f\n", myRegion->freq);
DEBUG_MSG("Radio myRegion->spacing: %f\n", myRegion->spacing);
DEBUG_MSG("Radio myRegion->numChannels: %d\n", myRegion->numChannels);
DEBUG_MSG("Radio channel_num: %d\n", channel_num);
- DEBUG_MSG("Radio frequency: %f\n", freq);
+ DEBUG_MSG("Radio frequency: %f\n", getFreq()); // the frequency could be overridden in RadioInterface::getFreq() for some modules
DEBUG_MSG("Short packet time: %u msec\n", shortPacketMsec);
-
- saveChannelNum(channel_num);
- saveFreq(freq);
}
/**
diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h
index d263e012d..542b575da 100644
--- a/src/mesh/RadioInterface.h
+++ b/src/mesh/RadioInterface.h
@@ -78,8 +78,6 @@ class RadioInterface
void deliverToReceiver(MeshPacket *p);
public:
- float freq = 915.0;
-
/** pool is the pool we will alloc our rx packets from
*/
RadioInterface();
@@ -149,7 +147,10 @@ class RadioInterface
/**
* Get the frequency we saved.
*/
- float getFreq();
+ virtual float getFreq();
+
+ /// Some boards (1st gen Pinetab Lora module) have broken IRQ wires, so we need to poll via i2c registers
+ virtual bool isIRQPending() { return false; }
protected:
int8_t power = 17; // Set by applyModemConfig()
diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp
index f7a2bc415..cb77d9ec3 100644
--- a/src/mesh/RadioLibInterface.cpp
+++ b/src/mesh/RadioLibInterface.cpp
@@ -1,10 +1,10 @@
+#include "configuration.h"
#include "RadioLibInterface.h"
#include "MeshTypes.h"
#include "NodeDB.h"
#include "SPILock.h"
#include "error.h"
#include "mesh-pb-constants.h"
-#include
#include
#include
@@ -75,7 +75,7 @@ bool RadioLibInterface::canSendImmediately()
// TX IRQ from the radio, the radio is probably broken.
if (busyTx && (millis() - lastTxStart > 60000)) {
DEBUG_MSG("Hardware Failure! busyTx for more than 60s\n");
- recordCriticalError(CriticalErrorCode_TransmitFailed);
+ RECORD_CRITICALERROR(CriticalErrorCode_TransmitFailed);
#ifndef NO_ESP32
if (busyTx && (millis() - lastTxStart > 65000)) // After 5s more, reboot
ESP.restart();
@@ -312,7 +312,13 @@ void RadioLibInterface::startSend(MeshPacket *txp)
size_t numbytes = beginSending(txp);
int res = iface->startTransmit(radiobuf, numbytes);
- assert(res == ERR_NONE);
+ if(res != ERR_NONE) {
+ RECORD_CRITICALERROR(CriticalErrorCode_RadioSpiBug);
+
+ // This send failed, but make sure to 'complete' it properly
+ completeSending();
+ startReceive(); // Restart receive mode (because startTransmit failed to put us in xmit mode)
+ }
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
enableInterrupt(isrTxLevel0);
diff --git a/src/mesh/RadioLibRF95.cpp b/src/mesh/RadioLibRF95.cpp
index 0551e7377..dcb987d1b 100644
--- a/src/mesh/RadioLibRF95.cpp
+++ b/src/mesh/RadioLibRF95.cpp
@@ -1,3 +1,4 @@
+#include "configuration.h"
#include "RadioLibRF95.h"
#define RF95_CHIP_VERSION 0x12
@@ -9,15 +10,21 @@
RadioLibRF95::RadioLibRF95(Module *mod) : SX1278(mod) {}
-int16_t RadioLibRF95::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power, uint8_t currentLimit,
+int16_t RadioLibRF95::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power,
uint16_t preambleLength, uint8_t gain)
{
// execute common part
- int16_t state = SX127x::begin(RF95_CHIP_VERSION, syncWord, currentLimit, preambleLength);
+ int16_t state = SX127x::begin(RF95_CHIP_VERSION, syncWord, preambleLength);
if (state != ERR_NONE)
- state = SX127x::begin(RF95_ALT_VERSION, syncWord, currentLimit, preambleLength);
+ state = SX127x::begin(RF95_ALT_VERSION, syncWord, preambleLength);
RADIOLIB_ASSERT(state);
+ // current limit was removed from module' ctor
+ // override default value (60 mA)
+ state = setCurrentLimit(currentLimit);
+ DEBUG_MSG("Current limit set to %f\n", currentLimit);
+ DEBUG_MSG("Current limit set result %d\n", state);
+
// configure settings not accessible by API
state = config();
RADIOLIB_ASSERT(state);
diff --git a/src/mesh/RadioLibRF95.h b/src/mesh/RadioLibRF95.h
index 3c45a8f44..d6aeb541a 100644
--- a/src/mesh/RadioLibRF95.h
+++ b/src/mesh/RadioLibRF95.h
@@ -35,9 +35,6 @@ class RadioLibRF95: public SX1278 {
\param power Transmission output power in dBm. Allowed values range from 2 to 17 dBm.
- \param currentLimit Trim value for OCP (over current protection) in mA. Can be set to multiplies of 5 in range 45 to 120 mA and to multiples of 10 in range 120 to 240 mA.
- Set to 0 to disable OCP (not recommended).
-
\param preambleLength Length of %LoRa transmission preamble in symbols. The actual preamble length is 4.25 symbols longer than the set number.
Allowed values range from 6 to 65535.
@@ -46,7 +43,7 @@ class RadioLibRF95: public SX1278 {
\returns \ref status_codes
*/
- int16_t begin(float freq = 915.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = SX127X_SYNC_WORD, int8_t power = 17, uint8_t currentLimit = 100, uint16_t preambleLength = 8, uint8_t gain = 0);
+ int16_t begin(float freq = 915.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = SX127X_SYNC_WORD, int8_t power = 17, uint16_t preambleLength = 8, uint8_t gain = 0);
// configuration methods
@@ -65,6 +62,11 @@ class RadioLibRF95: public SX1278 {
/// For debugging
uint8_t readReg(uint8_t addr);
+ protected:
+ // since default current limit for SX126x/127x in updated RadioLib is 60mA
+ // use the previous value
+ float currentLimit = 100;
+
#ifndef RADIOLIB_GODMODE
private:
#endif
diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp
index 131d5bb97..c7f77e5d7 100644
--- a/src/mesh/ReliableRouter.cpp
+++ b/src/mesh/ReliableRouter.cpp
@@ -1,7 +1,7 @@
+#include "configuration.h"
#include "ReliableRouter.h"
#include "MeshPlugin.h"
#include "MeshTypes.h"
-#include "configuration.h"
#include "mesh-pb-constants.h"
// ReliableRouter::ReliableRouter() {}
@@ -49,6 +49,9 @@ bool ReliableRouter::shouldFilterReceived(const MeshPacket *p)
stopRetransmission(key);
}
+ else {
+ DEBUG_MSG("didn't find pending packet\n");
+ }
}
return FloodingRouter::shouldFilterReceived(p);
diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h
index 7299d1c18..e0890ff28 100644
--- a/src/mesh/ReliableRouter.h
+++ b/src/mesh/ReliableRouter.h
@@ -51,7 +51,7 @@ struct PendingPacket {
class GlobalPacketIdHashFunction
{
public:
- size_t operator()(const GlobalPacketId &p) const { return (hash()(p.node)) ^ (hash()(p.id)); }
+ size_t operator()(const GlobalPacketId &p) const { return (std::hash()(p.node)) ^ (std::hash()(p.id)); }
};
/**
@@ -60,7 +60,7 @@ class GlobalPacketIdHashFunction
class ReliableRouter : public FloodingRouter
{
private:
- unordered_map pending;
+ std::unordered_map pending;
public:
/**
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index f58ff6a45..68b08161a 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -1,9 +1,9 @@
+#include "configuration.h"
#include "Router.h"
#include "Channels.h"
#include "CryptoEngine.h"
#include "NodeDB.h"
#include "RTC.h"
-#include "configuration.h"
#include "main.h"
#include "mesh-pb-constants.h"
#include "plugins/RoutingPlugin.h"
@@ -145,7 +145,7 @@ void Router::setReceivedMessage()
runASAP = true;
}
-ErrorCode Router::sendLocal(MeshPacket *p)
+ErrorCode Router::sendLocal(MeshPacket *p, RxSource src)
{
// No need to deliver externally if the destination is the local node
if (p->to == nodeDB.getNodeNum()) {
@@ -161,7 +161,7 @@ ErrorCode Router::sendLocal(MeshPacket *p)
// If we are sending a broadcast, we also treat it as if we just received it ourself
// this allows local apps (and PCs) to see broadcasts sourced locally
if (p->to == NODENUM_BROADCAST) {
- handleReceived(p);
+ handleReceived(p, src);
}
return send(p);
@@ -324,7 +324,7 @@ NodeNum Router::getNodeNum()
* Handle any packet that is received by an interface on this node.
* Note: some packets may merely being passed through this node and will be forwarded elsewhere.
*/
-void Router::handleReceived(MeshPacket *p)
+void Router::handleReceived(MeshPacket *p, RxSource src)
{
// 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
@@ -333,13 +333,16 @@ void Router::handleReceived(MeshPacket *p)
bool decoded = perhapsDecode(p);
if (decoded) {
// parsing was successful, queue for our recipient
- printPacket("handleReceived", p);
+ if (src == RX_SRC_LOCAL)
+ printPacket("handleReceived(local)", p);
+ else
+ printPacket("handleReceived(remote)", p);
} else {
printPacket("packet decoding failed (no PSK?)", p);
}
// call plugins here
- MeshPlugin::callPlugins(*p);
+ MeshPlugin::callPlugins(*p, src);
}
void Router::perhapsHandleReceived(MeshPacket *p)
diff --git a/src/mesh/Router.h b/src/mesh/Router.h
index 9d7358af5..a2d75d35f 100644
--- a/src/mesh/Router.h
+++ b/src/mesh/Router.h
@@ -45,7 +45,7 @@ class Router : protected concurrency::OSThread
*
* NOTE: This method will free the provided packet (even if we return an error code)
*/
- ErrorCode sendLocal(MeshPacket *p);
+ ErrorCode sendLocal(MeshPacket *p, RxSource src = RX_SRC_RADIO);
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
bool cancelSending(NodeNum from, PacketId id);
@@ -122,7 +122,7 @@ class Router : protected concurrency::OSThread
* Note: this packet will never be called for messages sent/generated by this node.
* Note: this method will free the provided packet.
*/
- void handleReceived(MeshPacket *p);
+ void handleReceived(MeshPacket *p, RxSource src = RX_SRC_RADIO);
/** Frees the provided packet, and generates a NAK indicating the speicifed error while sending */
void abortSendAndNak(Routing_Error err, MeshPacket *p);
diff --git a/src/mesh/SX1262Interface.cpp b/src/mesh/SX1262Interface.cpp
index 2a68bca30..d01372802 100644
--- a/src/mesh/SX1262Interface.cpp
+++ b/src/mesh/SX1262Interface.cpp
@@ -1,225 +1,9 @@
+#include "configuration.h"
#include "SX1262Interface.h"
#include "error.h"
-#include
-
-// Particular boards might define a different max power based on what their hardware can do
-#ifndef SX1262_MAX_POWER
-#define SX1262_MAX_POWER 22
-#endif
SX1262Interface::SX1262Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
SPIClass &spi)
- : RadioLibInterface(cs, irq, rst, busy, spi, &lora), lora(&module)
+ : SX126xInterface(cs, irq, rst, busy, spi)
{
-}
-
-/// Initialise the Driver transport hardware and software.
-/// Make sure the Driver is properly configured before calling init().
-/// \return true if initialisation succeeded.
-bool SX1262Interface::init()
-{
-#ifdef SX1262_POWER_EN
- digitalWrite(SX1262_POWER_EN, HIGH);
- pinMode(SX1262_POWER_EN, OUTPUT);
-#endif
-
-#ifdef SX1262_RXEN // set not rx or tx mode
- digitalWrite(SX1262_RXEN, LOW); // Set low before becoming an output
- pinMode(SX1262_RXEN, OUTPUT);
-#endif
-#ifdef SX1262_TXEN
- digitalWrite(SX1262_TXEN, LOW);
- pinMode(SX1262_TXEN, OUTPUT);
-#endif
-
-#ifndef SX1262_E22
- float tcxoVoltage = 0; // None - we use an XTAL
-#else
- // Use DIO3 to power tcxo per https://github.com/jgromes/RadioLib/issues/12#issuecomment-520695575
- float tcxoVoltage = 1.8;
-#endif
- bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC?
-
- RadioLibInterface::init();
-
- if (power == 0)
- power = SX1262_MAX_POWER;
-
- if (power > SX1262_MAX_POWER) // This chip has lower power limits than some
- power = SX1262_MAX_POWER;
-
- limitPower();
-
- int res = lora.begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO);
- DEBUG_MSG("SX1262 init result %d\n", res);
-
-#ifdef SX1262_TXEN
- // lora.begin sets Dio2 as RF switch control, which is not true if we are manually controlling RX and TX
- if (res == ERR_NONE)
- res = lora.setDio2AsRfSwitch(false);
-#endif
-
- if (res == ERR_NONE)
- res = lora.setCRC(SX126X_LORA_CRC_ON);
-
- if (res == ERR_NONE)
- startReceive(); // start receiving
-
- return res == ERR_NONE;
-}
-
-bool SX1262Interface::reconfigure()
-{
- RadioLibInterface::reconfigure();
-
- // set mode to standby
- setStandby();
-
- // configure publicly accessible settings
- int err = lora.setSpreadingFactor(sf);
- if (err != ERR_NONE)
- recordCriticalError(CriticalErrorCode_InvalidRadioSetting);
-
- err = lora.setBandwidth(bw);
- if (err != ERR_NONE)
- recordCriticalError(CriticalErrorCode_InvalidRadioSetting);
-
- err = lora.setCodingRate(cr);
- if (err != ERR_NONE)
- recordCriticalError(CriticalErrorCode_InvalidRadioSetting);
-
- // Hmm - seems to lower SNR when the signal levels are high. Leaving off for now...
- err = lora.setRxGain(true);
- assert(err == ERR_NONE);
-
- err = lora.setSyncWord(syncWord);
- assert(err == ERR_NONE);
-
- err = lora.setCurrentLimit(currentLimit);
- assert(err == ERR_NONE);
-
- err = lora.setPreambleLength(preambleLength);
- assert(err == ERR_NONE);
-
- err = lora.setFrequency(freq);
- if (err != ERR_NONE)
- recordCriticalError(CriticalErrorCode_InvalidRadioSetting);
-
- if (power > 22) // This chip has lower power limits than some
- power = 22;
- err = lora.setOutputPower(power);
- assert(err == ERR_NONE);
-
- startReceive(); // restart receiving
-
- return ERR_NONE;
-}
-
-void INTERRUPT_ATTR SX1262Interface::disableInterrupt()
-{
- lora.clearDio1Action();
-}
-
-void SX1262Interface::setStandby()
-{
- int err = lora.standby();
- assert(err == ERR_NONE);
-
-#ifdef SX1262_RXEN // we have RXEN/TXEN control - turn off RX and TX power
- digitalWrite(SX1262_RXEN, LOW);
-#endif
-#ifdef SX1262_TXEN
- digitalWrite(SX1262_TXEN, LOW);
-#endif
-
- isReceiving = false; // If we were receiving, not any more
- disableInterrupt();
- completeSending(); // If we were sending, not anymore
-}
-
-/**
- * Add SNR data to received messages
- */
-void SX1262Interface::addReceiveMetadata(MeshPacket *mp)
-{
- // DEBUG_MSG("PacketStatus %x\n", lora.getPacketStatus());
- mp->rx_snr = lora.getSNR();
- mp->rx_rssi = lround(lora.getRSSI());
-}
-
-/** We override to turn on transmitter power as needed.
- */
-void SX1262Interface::configHardwareForSend()
-{
-#ifdef SX1262_TXEN // we have RXEN/TXEN control - turn on TX power / off RX power
- digitalWrite(SX1262_TXEN, HIGH);
-#endif
-
- RadioLibInterface::configHardwareForSend();
-}
-
-// For power draw measurements, helpful to force radio to stay sleeping
-// #define SLEEP_ONLY
-
-void SX1262Interface::startReceive()
-{
-#ifdef SLEEP_ONLY
- sleep();
-#else
-
- setStandby();
-
-#ifdef SX1262_RXEN // we have RXEN/TXEN control - turn on RX power / off TX power
- digitalWrite(SX1262_RXEN, HIGH);
-#endif
-
- // int err = lora.startReceive();
- int err = lora.startReceiveDutyCycleAuto(); // We use a 32 bit preamble so this should save some power by letting radio sit in
- // standby mostly.
- assert(err == ERR_NONE);
-
- isReceiving = true;
-
- // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
- enableInterrupt(isrRxLevel0);
-#endif
-}
-
-/** Could we send right now (i.e. either not actively receving or transmitting)? */
-bool SX1262Interface::isActivelyReceiving()
-{
- // The IRQ status will be cleared when we start our read operation. Check if we've started a header, but haven't yet
- // received and handled the interrupt for reading the packet/handling errors.
- // FIXME: it would be better to check for preamble, but we currently have our ISR not set to fire for packets that
- // never even get a valid header, so we don't want preamble to get set and stay set due to noise on the network.
-
- uint16_t irq = lora.getIrqStatus();
- bool hasPreamble = (irq & SX126X_IRQ_HEADER_VALID);
-
- // this is not correct - often always true - need to add an extra conditional
- // size_t bytesPending = lora.getPacketLength();
-
- // if (hasPreamble) DEBUG_MSG("rx hasPreamble\n");
- return hasPreamble;
-}
-
-bool SX1262Interface::sleep()
-{
- // Not keeping config is busted - next time nrf52 board boots lora sending fails tcxo related? - see datasheet
- DEBUG_MSG("sx1262 entering sleep mode (FIXME, don't keep config)\n");
- setStandby(); // Stop any pending operations
-
- // turn off TCXO if it was powered
- // FIXME - this isn't correct
- // lora.setTCXO(0);
-
- // put chipset into sleep mode (we've already disabled interrupts by now)
- bool keepConfig = true;
- lora.sleep(keepConfig); // Note: we do not keep the config, full reinit will be needed
-
-#ifdef SX1262_POWER_EN
- digitalWrite(SX1262_POWER_EN, LOW);
-#endif
-
- return true;
}
\ No newline at end of file
diff --git a/src/mesh/SX1262Interface.h b/src/mesh/SX1262Interface.h
index 30167bc2d..0f029ada9 100644
--- a/src/mesh/SX1262Interface.h
+++ b/src/mesh/SX1262Interface.h
@@ -1,60 +1,12 @@
#pragma once
-#include "RadioLibInterface.h"
+#include "SX126xInterface.h"
/**
* Our adapter for SX1262 radios
*/
-class SX1262Interface : public RadioLibInterface
+class SX1262Interface : public SX126xInterface
{
- SX1262 lora;
-
public:
SX1262Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi);
-
- /// Initialise the Driver transport hardware and software.
- /// Make sure the Driver is properly configured before calling init().
- /// \return true if initialisation succeeded.
- virtual bool init();
-
- /// Apply any radio provisioning changes
- /// Make sure the Driver is properly configured before calling init().
- /// \return true if initialisation succeeded.
- virtual bool reconfigure();
-
- /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep.
- virtual bool sleep();
-
- protected:
- /**
- * Glue functions called from ISR land
- */
- virtual void disableInterrupt();
-
- /**
- * Enable a particular ISR callback glue function
- */
- virtual void enableInterrupt(void (*callback)()) { lora.setDio1Action(callback); }
-
- /** are we actively receiving a packet (only called during receiving state) */
- virtual bool isActivelyReceiving();
-
- /**
- * Start waiting to receive a message
- */
- virtual void startReceive();
-
- /**
- * We override to turn on transmitter power as needed.
- */
- virtual void configHardwareForSend();
-
- /**
- * Add SNR data to received messages
- */
- virtual void addReceiveMetadata(MeshPacket *mp);
-
- virtual void setStandby();
-
- private:
};
\ No newline at end of file
diff --git a/src/mesh/SX1268Interface.cpp b/src/mesh/SX1268Interface.cpp
new file mode 100644
index 000000000..668943124
--- /dev/null
+++ b/src/mesh/SX1268Interface.cpp
@@ -0,0 +1,9 @@
+#include "configuration.h"
+#include "SX1268Interface.h"
+#include "error.h"
+
+SX1268Interface::SX1268Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
+ SPIClass &spi)
+ : SX126xInterface(cs, irq, rst, busy, spi)
+{
+}
\ No newline at end of file
diff --git a/src/mesh/SX1268Interface.h b/src/mesh/SX1268Interface.h
new file mode 100644
index 000000000..c5b56171b
--- /dev/null
+++ b/src/mesh/SX1268Interface.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "SX126xInterface.h"
+
+/**
+ * Our adapter for SX1268 radios
+ */
+class SX1268Interface : public SX126xInterface
+{
+ public:
+ /// override frequency of the SX1268 module regardless of the region (use EU433 value)
+ virtual float getFreq() { return 433.175f; }
+
+ SX1268Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi);
+};
\ No newline at end of file
diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp
new file mode 100644
index 000000000..b0c48d55a
--- /dev/null
+++ b/src/mesh/SX126xInterface.cpp
@@ -0,0 +1,269 @@
+#include "configuration.h"
+#include "SX126xInterface.h"
+#include "error.h"
+
+// Particular boards might define a different max power based on what their hardware can do
+#ifndef SX126X_MAX_POWER
+#define SX126X_MAX_POWER 22
+#endif
+
+template
+SX126xInterface::SX126xInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
+ SPIClass &spi)
+ : RadioLibInterface(cs, irq, rst, busy, spi, &lora), lora(&module)
+{
+}
+
+/// Initialise the Driver transport hardware and software.
+/// Make sure the Driver is properly configured before calling init().
+/// \return true if initialisation succeeded.
+template
+bool SX126xInterface::init()
+{
+#ifdef SX126X_POWER_EN
+ digitalWrite(SX126X_POWER_EN, HIGH);
+ pinMode(SX126X_POWER_EN, OUTPUT);
+#endif
+
+#ifdef SX126X_RXEN // set not rx or tx mode
+ digitalWrite(SX126X_RXEN, LOW); // Set low before becoming an output
+ pinMode(SX126X_RXEN, OUTPUT);
+#endif
+#ifdef SX126X_TXEN
+ digitalWrite(SX126X_TXEN, LOW);
+ pinMode(SX126X_TXEN, OUTPUT);
+#endif
+
+#ifndef SX126X_E22
+ float tcxoVoltage = 0; // None - we use an XTAL
+#else
+ // Use DIO3 to power tcxo per https://github.com/jgromes/RadioLib/issues/12#issuecomment-520695575
+ float tcxoVoltage = 1.8;
+#endif
+ bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC?
+
+ RadioLibInterface::init();
+
+ if (power == 0)
+ power = SX126X_MAX_POWER;
+
+ if (power > SX126X_MAX_POWER) // This chip has lower power limits than some
+ power = SX126X_MAX_POWER;
+
+ limitPower();
+
+ int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO);
+ // \todo Display actual typename of the adapter, not just `SX126x`
+ DEBUG_MSG("SX126x init result %d\n", res);
+
+ // current limit was removed from module' ctor
+ // override default value (60 mA)
+ res = lora.setCurrentLimit(currentLimit);
+ DEBUG_MSG("Current limit set to %f\n", currentLimit);
+ DEBUG_MSG("Current limit set result %d\n", res);
+
+#ifdef SX126X_TXEN
+ // lora.begin sets Dio2 as RF switch control, which is not true if we are manually controlling RX and TX
+ if (res == ERR_NONE)
+ res = lora.setDio2AsRfSwitch(false);
+#endif
+
+#if 0
+ // Read/write a register we are not using (only used for FSK mode) to test SPI comms
+ uint8_t crcLSB = 0;
+ int err = lora.readRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1);
+ if(err != ERR_NONE)
+ RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
+
+ //if(crcLSB != 0x0f)
+ // RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
+
+ crcLSB = 0x5a;
+ err = lora.writeRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1);
+ if(err != ERR_NONE)
+ RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
+
+ err = lora.readRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1);
+ if(err != ERR_NONE)
+ RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
+
+ if(crcLSB != 0x5a)
+ RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
+ // If we got this far register accesses (and therefore SPI comms) are good
+#endif
+
+ if (res == ERR_NONE)
+ res = lora.setCRC(SX126X_LORA_CRC_ON);
+
+ if (res == ERR_NONE)
+ startReceive(); // start receiving
+
+ return res == ERR_NONE;
+}
+
+template
+bool SX126xInterface::reconfigure()
+{
+ RadioLibInterface::reconfigure();
+
+ // set mode to standby
+ setStandby();
+
+ // configure publicly accessible settings
+ int err = lora.setSpreadingFactor(sf);
+ if (err != ERR_NONE)
+ RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
+
+ err = lora.setBandwidth(bw);
+ if (err != ERR_NONE)
+ RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
+
+ err = lora.setCodingRate(cr);
+ if (err != ERR_NONE)
+ RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
+
+ // Hmm - seems to lower SNR when the signal levels are high. Leaving off for now...
+ err = lora.setRxGain(true);
+ assert(err == ERR_NONE);
+
+ err = lora.setSyncWord(syncWord);
+ assert(err == ERR_NONE);
+
+ err = lora.setCurrentLimit(currentLimit);
+ assert(err == ERR_NONE);
+
+ err = lora.setPreambleLength(preambleLength);
+ assert(err == ERR_NONE);
+
+ err = lora.setFrequency(getFreq());
+ if (err != ERR_NONE)
+ RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
+
+ if (power > 22) // This chip has lower power limits than some
+ power = 22;
+ err = lora.setOutputPower(power);
+ assert(err == ERR_NONE);
+
+ startReceive(); // restart receiving
+
+ return ERR_NONE;
+}
+
+template
+void INTERRUPT_ATTR SX126xInterface::disableInterrupt()
+{
+ lora.clearDio1Action();
+}
+
+template
+void SX126xInterface::setStandby()
+{
+ checkNotification(); // handle any pending interrupts before we force standby
+
+ int err = lora.standby();
+ assert(err == ERR_NONE);
+
+#ifdef SX126X_RXEN // we have RXEN/TXEN control - turn off RX and TX power
+ digitalWrite(SX126X_RXEN, LOW);
+#endif
+#ifdef SX126X_TXEN
+ digitalWrite(SX126X_TXEN, LOW);
+#endif
+
+ isReceiving = false; // If we were receiving, not any more
+ disableInterrupt();
+ completeSending(); // If we were sending, not anymore
+}
+
+/**
+ * Add SNR data to received messages
+ */
+template
+void SX126xInterface::addReceiveMetadata(MeshPacket *mp)
+{
+ // DEBUG_MSG("PacketStatus %x\n", lora.getPacketStatus());
+ mp->rx_snr = lora.getSNR();
+ mp->rx_rssi = lround(lora.getRSSI());
+}
+
+/** We override to turn on transmitter power as needed.
+ */
+template
+void SX126xInterface::configHardwareForSend()
+{
+#ifdef SX126X_TXEN // we have RXEN/TXEN control - turn on TX power / off RX power
+ digitalWrite(SX126X_TXEN, HIGH);
+#endif
+
+ RadioLibInterface::configHardwareForSend();
+}
+
+// For power draw measurements, helpful to force radio to stay sleeping
+// #define SLEEP_ONLY
+
+template
+void SX126xInterface::startReceive()
+{
+#ifdef SLEEP_ONLY
+ sleep();
+#else
+
+ setStandby();
+
+#ifdef SX126X_RXEN // we have RXEN/TXEN control - turn on RX power / off TX power
+ digitalWrite(SX126X_RXEN, HIGH);
+#endif
+
+ // int err = lora.startReceive();
+ int err = lora.startReceiveDutyCycleAuto(); // We use a 32 bit preamble so this should save some power by letting radio sit in
+ // standby mostly.
+ assert(err == ERR_NONE);
+
+ isReceiving = true;
+
+ // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
+ enableInterrupt(isrRxLevel0);
+#endif
+}
+
+/** Could we send right now (i.e. either not actively receving or transmitting)? */
+template
+bool SX126xInterface::isActivelyReceiving()
+{
+ // The IRQ status will be cleared when we start our read operation. Check if we've started a header, but haven't yet
+ // received and handled the interrupt for reading the packet/handling errors.
+ // FIXME: it would be better to check for preamble, but we currently have our ISR not set to fire for packets that
+ // never even get a valid header, so we don't want preamble to get set and stay set due to noise on the network.
+
+ uint16_t irq = lora.getIrqStatus();
+ bool hasPreamble = (irq & SX126X_IRQ_HEADER_VALID);
+
+ // this is not correct - often always true - need to add an extra conditional
+ // size_t bytesPending = lora.getPacketLength();
+
+ // if (hasPreamble) DEBUG_MSG("rx hasPreamble\n");
+ return hasPreamble;
+}
+
+template
+bool SX126xInterface::sleep()
+{
+ // Not keeping config is busted - next time nrf52 board boots lora sending fails tcxo related? - see datasheet
+ // \todo Display actual typename of the adapter, not just `SX126x`
+ DEBUG_MSG("sx126x entering sleep mode (FIXME, don't keep config)\n");
+ setStandby(); // Stop any pending operations
+
+ // turn off TCXO if it was powered
+ // FIXME - this isn't correct
+ // lora.setTCXO(0);
+
+ // put chipset into sleep mode (we've already disabled interrupts by now)
+ bool keepConfig = true;
+ lora.sleep(keepConfig); // Note: we do not keep the config, full reinit will be needed
+
+#ifdef SX126X_POWER_EN
+ digitalWrite(SX126X_POWER_EN, LOW);
+#endif
+
+ return true;
+}
\ No newline at end of file
diff --git a/src/mesh/SX126xInterface.h b/src/mesh/SX126xInterface.h
new file mode 100644
index 000000000..1b4a10048
--- /dev/null
+++ b/src/mesh/SX126xInterface.h
@@ -0,0 +1,68 @@
+#pragma once
+
+#include "RadioLibInterface.h"
+
+/**
+ * \brief Adapter for SX126x radio family. Implements common logic for child classes.
+ * \tparam T RadioLib module type for SX126x: SX1262, SX1268.
+ */
+template
+class SX126xInterface : public RadioLibInterface
+{
+ public:
+ SX126xInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi);
+
+ /// Initialise the Driver transport hardware and software.
+ /// Make sure the Driver is properly configured before calling init().
+ /// \return true if initialisation succeeded.
+ virtual bool init();
+
+ /// Apply any radio provisioning changes
+ /// Make sure the Driver is properly configured before calling init().
+ /// \return true if initialisation succeeded.
+ virtual bool reconfigure();
+
+ /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep.
+ virtual bool sleep();
+
+ bool isIRQPending() { return lora.getIrqStatus() != 0; }
+
+ protected:
+
+ /**
+ * Specific module instance
+ */
+ T lora;
+
+ /**
+ * Glue functions called from ISR land
+ */
+ virtual void disableInterrupt();
+
+ /**
+ * Enable a particular ISR callback glue function
+ */
+ virtual void enableInterrupt(void (*callback)()) { lora.setDio1Action(callback); }
+
+ /** are we actively receiving a packet (only called during receiving state) */
+ virtual bool isActivelyReceiving();
+
+ /**
+ * Start waiting to receive a message
+ */
+ virtual void startReceive();
+
+ /**
+ * We override to turn on transmitter power as needed.
+ */
+ virtual void configHardwareForSend();
+
+ /**
+ * Add SNR data to received messages
+ */
+ virtual void addReceiveMetadata(MeshPacket *mp);
+
+ virtual void setStandby();
+
+ private:
+};
\ No newline at end of file
diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp
index 2bc82b084..e930f4f77 100644
--- a/src/mesh/StreamAPI.cpp
+++ b/src/mesh/StreamAPI.cpp
@@ -1,4 +1,5 @@
#include "StreamAPI.h"
+#include "PowerFSM.h"
#include "configuration.h"
#define START1 0x94
@@ -25,40 +26,49 @@ int32_t StreamAPI::readStream()
return recentRx ? 5 : 250;
} else {
while (stream->available()) { // Currently we never want to block
- uint8_t c = stream->read();
+ int cInt = stream->read();
+ if(cInt < 0)
+ break; // We ran out of characters (even though available said otherwise) - this can happen on rf52 adafruit arduino
+
+ uint8_t c = (uint8_t) cInt;
// Use the read pointer for a little state machine, first look for framing, then length bytes, then payload
- size_t ptr = rxPtr++; // assume we will probably advance the rxPtr
+ size_t ptr = rxPtr;
+ rxPtr++; // assume we will probably advance the rxPtr
rxBuf[ptr] = c; // store all bytes (including framing)
+ // console->printf("rxPtr %d ptr=%d c=0x%x\n", rxPtr, ptr, c);
+
if (ptr == 0) { // looking for START1
if (c != START1)
rxPtr = 0; // failed to find framing
} else if (ptr == 1) { // looking for START2
if (c != START2)
rxPtr = 0; // failed to find framing
- } else if (ptr >= HEADER_LEN) { // we have at least read our 4 byte framing
+ } else if (ptr >= HEADER_LEN - 1) { // we have at least read our 4 byte framing
uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing
- if (ptr == HEADER_LEN) {
+ // console->printf("len %d\n", len);
+
+ if (ptr == HEADER_LEN - 1) {
// we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid
// protobuf also)
if (len > MAX_TO_FROM_RADIO_SIZE)
rxPtr = 0; // length is bogus, restart search for framing
}
- if (rxPtr != 0 && ptr + 1 == len + HEADER_LEN) {
- rxPtr = 0; // start over again on the next packet
+ if (rxPtr != 0) // Is packet still considered 'good'?
+ if (ptr + 1 >= len + HEADER_LEN) { // have we received all of the payload?
+ rxPtr = 0; // start over again on the next packet
- // If we didn't just fail the packet and we now have the right # of bytes, parse it
- if (handleToRadio(rxBuf + HEADER_LEN, len))
- return 0; // we want to be called again ASAP because we still have more work to do
- }
+ // If we didn't just fail the packet and we now have the right # of bytes, parse it
+ handleToRadio(rxBuf + HEADER_LEN, len);
+ }
}
}
- // we had packets available this time, so assume we might have them next time also
+ // we had bytes available this time, so assume we might have them next time also
lastRxMsec = now;
return 0;
}
@@ -109,4 +119,18 @@ void StreamAPI::emitRebooted()
// DEBUG_MSG("Emitting reboot packet for serial shell\n");
emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, FromRadio_size, FromRadio_fields, &fromRadioScratch));
+}
+
+/// Hookable to find out when connection changes
+void StreamAPI::onConnectionChanged(bool connected)
+{
+ // FIXME do reference counting instead
+
+ if (connected) { // To prevent user confusion, turn off bluetooth while using the serial port api
+ powerFSM.trigger(EVENT_SERIAL_CONNECTED);
+ } else {
+ // FIXME, we get no notice of serial going away, we should instead automatically generate this event if we haven't
+ // received a packet in a while
+ powerFSM.trigger(EVENT_SERIAL_DISCONNECTED);
+ }
}
\ No newline at end of file
diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h
index 6180a95d8..58af95b34 100644
--- a/src/mesh/StreamAPI.h
+++ b/src/mesh/StreamAPI.h
@@ -67,6 +67,11 @@ class StreamAPI : public PhoneAPI, protected concurrency::OSThread
*/
void emitRebooted();
+ virtual void onConnectionChanged(bool connected);
+
+ /// Check the current underlying physical link to see if the client is currently connected
+ virtual bool checkIsConnected() = 0;
+
/**
* Send the current txBuffer over our stream
*/
diff --git a/src/mesh/generated/admin.pb.h b/src/mesh/generated/admin.pb.h
index 5ac5bcc16..c6c309691 100644
--- a/src/mesh/generated/admin.pb.h
+++ b/src/mesh/generated/admin.pb.h
@@ -79,7 +79,7 @@ extern const pb_msgdesc_t AdminMessage_msg;
#define AdminMessage_fields &AdminMessage_msg
/* Maximum encoded size of messages (where known) */
-#define AdminMessage_size 397
+#define AdminMessage_size 407
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/mesh/generated/deviceonly.pb.c b/src/mesh/generated/deviceonly.pb.c
index dd2bb751f..764c54fdb 100644
--- a/src/mesh/generated/deviceonly.pb.c
+++ b/src/mesh/generated/deviceonly.pb.c
@@ -12,7 +12,7 @@ PB_BIND(LegacyRadioConfig, LegacyRadioConfig, AUTO)
PB_BIND(LegacyRadioConfig_LegacyPreferences, LegacyRadioConfig_LegacyPreferences, AUTO)
-PB_BIND(DeviceState, DeviceState, 2)
+PB_BIND(DeviceState, DeviceState, 4)
PB_BIND(ChannelFile, ChannelFile, 2)
diff --git a/src/mesh/generated/deviceonly.pb.h b/src/mesh/generated/deviceonly.pb.h
index 76ab8d6aa..6c97720be 100644
--- a/src/mesh/generated/deviceonly.pb.h
+++ b/src/mesh/generated/deviceonly.pb.h
@@ -125,7 +125,7 @@ extern const pb_msgdesc_t ChannelFile_msg;
/* Maximum encoded size of messages (where known) */
#define LegacyRadioConfig_size 4
#define LegacyRadioConfig_LegacyPreferences_size 2
-#define DeviceState_size 5190
+#define DeviceState_size 10054
#define ChannelFile_size 832
#ifdef __cplusplus
diff --git a/src/mesh/generated/mesh.pb.c b/src/mesh/generated/mesh.pb.c
index 8a4a1d0a1..1e9000396 100644
--- a/src/mesh/generated/mesh.pb.c
+++ b/src/mesh/generated/mesh.pb.c
@@ -49,3 +49,5 @@ PB_BIND(ToRadio_PeerInfo, ToRadio_PeerInfo, AUTO)
+
+
diff --git a/src/mesh/generated/mesh.pb.h b/src/mesh/generated/mesh.pb.h
index faa915dbd..e464cf616 100644
--- a/src/mesh/generated/mesh.pb.h
+++ b/src/mesh/generated/mesh.pb.h
@@ -17,23 +17,25 @@ typedef enum _HardwareModel {
HardwareModel_TLORA_V1 = 2,
HardwareModel_TLORA_V2_1_1p6 = 3,
HardwareModel_TBEAM = 4,
- HardwareModel_HELTEC = 5,
+ HardwareModel_HELTEC_V2_0 = 5,
HardwareModel_TBEAM0p7 = 6,
HardwareModel_T_ECHO = 7,
HardwareModel_TLORA_V1_1p3 = 8,
HardwareModel_RAK4631 = 9,
+ HardwareModel_HELTEC_V2_1 = 10,
HardwareModel_LORA_RELAY_V1 = 32,
HardwareModel_NRF52840DK = 33,
HardwareModel_PPR = 34,
HardwareModel_GENIEBLOCKS = 35,
HardwareModel_NRF52_UNKNOWN = 36,
HardwareModel_PORTDUINO = 37,
- HardwareModel_ANDROID_SIM = 38
+ HardwareModel_ANDROID_SIM = 38,
+ HardwareModel_DIY_V1 = 39
} HardwareModel;
typedef enum _Constants {
Constants_Unused = 0,
- Constants_DATA_PAYLOAD_LEN = 240
+ Constants_DATA_PAYLOAD_LEN = 237
} Constants;
typedef enum _CriticalErrorCode {
@@ -46,9 +48,26 @@ typedef enum _CriticalErrorCode {
CriticalErrorCode_NoAXP192 = 6,
CriticalErrorCode_InvalidRadioSetting = 7,
CriticalErrorCode_TransmitFailed = 8,
- CriticalErrorCode_Brownout = 9
+ CriticalErrorCode_Brownout = 9,
+ CriticalErrorCode_SX1262Failure = 10,
+ CriticalErrorCode_RadioSpiBug = 11
} CriticalErrorCode;
+typedef enum _Position_LocSource {
+ Position_LocSource_LOCSRC_UNSPECIFIED = 0,
+ Position_LocSource_LOCSRC_MANUAL_ENTRY = 1,
+ Position_LocSource_LOCSRC_GPS_INTERNAL = 2,
+ Position_LocSource_LOCSRC_GPS_EXTERNAL = 3
+} Position_LocSource;
+
+typedef enum _Position_AltSource {
+ Position_AltSource_ALTSRC_UNSPECIFIED = 0,
+ Position_AltSource_ALTSRC_MANUAL_ENTRY = 1,
+ Position_AltSource_ALTSRC_GPS_INTERNAL = 2,
+ Position_AltSource_ALTSRC_GPS_EXTERNAL = 3,
+ Position_AltSource_ALTSRC_BAROMETRIC = 4
+} Position_AltSource;
+
typedef enum _Routing_Error {
Routing_Error_NONE = 0,
Routing_Error_NO_ROUTE = 1,
@@ -123,6 +142,29 @@ typedef struct _Position {
int32_t altitude;
int32_t battery_level;
uint32_t time;
+ Position_LocSource location_source;
+ Position_AltSource altitude_source;
+ uint32_t pos_timestamp;
+ int32_t pos_time_millis;
+ int32_t altitude_hae;
+ int32_t alt_geoid_sep;
+ uint32_t PDOP;
+ uint32_t HDOP;
+ uint32_t VDOP;
+ uint32_t gps_accuracy;
+ uint32_t ground_speed;
+ uint32_t ground_track;
+ uint32_t fix_quality;
+ uint32_t fix_type;
+ uint32_t sats_in_view;
+ uint32_t sensor_id;
+ uint32_t heading;
+ int32_t roll;
+ int32_t pitch;
+ uint32_t air_speed;
+ uint32_t ground_distance_cm;
+ uint32_t pos_next_update;
+ uint32_t pos_seq_number;
} Position;
typedef struct _RouteDiscovery {
@@ -208,16 +250,24 @@ typedef struct _ToRadio {
/* Helper constants for enums */
#define _HardwareModel_MIN HardwareModel_UNSET
-#define _HardwareModel_MAX HardwareModel_ANDROID_SIM
-#define _HardwareModel_ARRAYSIZE ((HardwareModel)(HardwareModel_ANDROID_SIM+1))
+#define _HardwareModel_MAX HardwareModel_DIY_V1
+#define _HardwareModel_ARRAYSIZE ((HardwareModel)(HardwareModel_DIY_V1+1))
#define _Constants_MIN Constants_Unused
#define _Constants_MAX Constants_DATA_PAYLOAD_LEN
#define _Constants_ARRAYSIZE ((Constants)(Constants_DATA_PAYLOAD_LEN+1))
#define _CriticalErrorCode_MIN CriticalErrorCode_None
-#define _CriticalErrorCode_MAX CriticalErrorCode_Brownout
-#define _CriticalErrorCode_ARRAYSIZE ((CriticalErrorCode)(CriticalErrorCode_Brownout+1))
+#define _CriticalErrorCode_MAX CriticalErrorCode_RadioSpiBug
+#define _CriticalErrorCode_ARRAYSIZE ((CriticalErrorCode)(CriticalErrorCode_RadioSpiBug+1))
+
+#define _Position_LocSource_MIN Position_LocSource_LOCSRC_UNSPECIFIED
+#define _Position_LocSource_MAX Position_LocSource_LOCSRC_GPS_EXTERNAL
+#define _Position_LocSource_ARRAYSIZE ((Position_LocSource)(Position_LocSource_LOCSRC_GPS_EXTERNAL+1))
+
+#define _Position_AltSource_MIN Position_AltSource_ALTSRC_UNSPECIFIED
+#define _Position_AltSource_MAX Position_AltSource_ALTSRC_BAROMETRIC
+#define _Position_AltSource_ARRAYSIZE ((Position_AltSource)(Position_AltSource_ALTSRC_BAROMETRIC+1))
#define _Routing_Error_MIN Routing_Error_NONE
#define _Routing_Error_MAX Routing_Error_NOT_AUTHORIZED
@@ -237,7 +287,7 @@ extern "C" {
#endif
/* Initializer values for message structs */
-#define Position_init_default {0, 0, 0, 0, 0}
+#define Position_init_default {0, 0, 0, 0, 0, _Position_LocSource_MIN, _Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define User_init_default {"", "", "", {0}, _HardwareModel_MIN, 0}
#define RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}}
#define Routing_init_default {0, {RouteDiscovery_init_default}}
@@ -249,7 +299,7 @@ extern "C" {
#define FromRadio_init_default {0, 0, {MyNodeInfo_init_default}}
#define ToRadio_init_default {0, {MeshPacket_init_default}}
#define ToRadio_PeerInfo_init_default {0, 0}
-#define Position_init_zero {0, 0, 0, 0, 0}
+#define Position_init_zero {0, 0, 0, 0, 0, _Position_LocSource_MIN, _Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define User_init_zero {"", "", "", {0}, _HardwareModel_MIN, 0}
#define RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}}
#define Routing_init_zero {0, {RouteDiscovery_init_zero}}
@@ -291,6 +341,29 @@ extern "C" {
#define Position_altitude_tag 3
#define Position_battery_level_tag 4
#define Position_time_tag 9
+#define Position_location_source_tag 10
+#define Position_altitude_source_tag 11
+#define Position_pos_timestamp_tag 12
+#define Position_pos_time_millis_tag 13
+#define Position_altitude_hae_tag 14
+#define Position_alt_geoid_sep_tag 15
+#define Position_PDOP_tag 16
+#define Position_HDOP_tag 17
+#define Position_VDOP_tag 18
+#define Position_gps_accuracy_tag 19
+#define Position_ground_speed_tag 20
+#define Position_ground_track_tag 21
+#define Position_fix_quality_tag 22
+#define Position_fix_type_tag 23
+#define Position_sats_in_view_tag 24
+#define Position_sensor_id_tag 25
+#define Position_heading_tag 30
+#define Position_roll_tag 31
+#define Position_pitch_tag 32
+#define Position_air_speed_tag 33
+#define Position_ground_distance_cm_tag 34
+#define Position_pos_next_update_tag 40
+#define Position_pos_seq_number_tag 41
#define RouteDiscovery_route_tag 2
#define ToRadio_PeerInfo_app_version_tag 1
#define ToRadio_PeerInfo_mqtt_gateway_tag 2
@@ -338,7 +411,30 @@ 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, INT32, battery_level, 4) \
-X(a, STATIC, SINGULAR, FIXED32, time, 9)
+X(a, STATIC, SINGULAR, FIXED32, time, 9) \
+X(a, STATIC, SINGULAR, UENUM, location_source, 10) \
+X(a, STATIC, SINGULAR, UENUM, altitude_source, 11) \
+X(a, STATIC, SINGULAR, FIXED32, pos_timestamp, 12) \
+X(a, STATIC, SINGULAR, INT32, pos_time_millis, 13) \
+X(a, STATIC, SINGULAR, SINT32, altitude_hae, 14) \
+X(a, STATIC, SINGULAR, SINT32, alt_geoid_sep, 15) \
+X(a, STATIC, SINGULAR, UINT32, PDOP, 16) \
+X(a, STATIC, SINGULAR, UINT32, HDOP, 17) \
+X(a, STATIC, SINGULAR, UINT32, VDOP, 18) \
+X(a, STATIC, SINGULAR, UINT32, gps_accuracy, 19) \
+X(a, STATIC, SINGULAR, UINT32, ground_speed, 20) \
+X(a, STATIC, SINGULAR, UINT32, ground_track, 21) \
+X(a, STATIC, SINGULAR, UINT32, fix_quality, 22) \
+X(a, STATIC, SINGULAR, UINT32, fix_type, 23) \
+X(a, STATIC, SINGULAR, UINT32, sats_in_view, 24) \
+X(a, STATIC, SINGULAR, UINT32, sensor_id, 25) \
+X(a, STATIC, SINGULAR, UINT32, heading, 30) \
+X(a, STATIC, SINGULAR, SINT32, roll, 31) \
+X(a, STATIC, SINGULAR, SINT32, pitch, 32) \
+X(a, STATIC, SINGULAR, UINT32, air_speed, 33) \
+X(a, STATIC, SINGULAR, UINT32, ground_distance_cm, 34) \
+X(a, STATIC, SINGULAR, UINT32, pos_next_update, 40) \
+X(a, STATIC, SINGULAR, UINT32, pos_seq_number, 41)
#define Position_CALLBACK NULL
#define Position_DEFAULT NULL
@@ -488,13 +584,13 @@ extern const pb_msgdesc_t ToRadio_PeerInfo_msg;
#define ToRadio_PeerInfo_fields &ToRadio_PeerInfo_msg
/* Maximum encoded size of messages (where known) */
-#define Position_size 37
+#define Position_size 188
#define User_size 76
#define RouteDiscovery_size 40
#define Routing_size 42
#define Data_size 260
#define MeshPacket_size 309
-#define NodeInfo_size 133
+#define NodeInfo_size 285
#define MyNodeInfo_size 101
#define LogRecord_size 81
#define FromRadio_size 318
diff --git a/src/mesh/generated/radioconfig.pb.c b/src/mesh/generated/radioconfig.pb.c
index 716b804e8..26930ae3c 100644
--- a/src/mesh/generated/radioconfig.pb.c
+++ b/src/mesh/generated/radioconfig.pb.c
@@ -18,3 +18,5 @@ PB_BIND(RadioConfig_UserPreferences, RadioConfig_UserPreferences, 2)
+
+
diff --git a/src/mesh/generated/radioconfig.pb.h b/src/mesh/generated/radioconfig.pb.h
index afccd937a..ee7b2aeb1 100644
--- a/src/mesh/generated/radioconfig.pb.h
+++ b/src/mesh/generated/radioconfig.pb.h
@@ -51,14 +51,37 @@ typedef enum _GpsOperation {
GpsOperation_GpsOpDisabled = 4
} GpsOperation;
+typedef enum _GpsCoordinateFormat {
+ GpsCoordinateFormat_GpsFormatDec = 0,
+ GpsCoordinateFormat_GpsFormatDMS = 1,
+ GpsCoordinateFormat_GpsFormatUTM = 2,
+ GpsCoordinateFormat_GpsFormatMGRS = 3,
+ GpsCoordinateFormat_GpsFormatOLC = 4,
+ GpsCoordinateFormat_GpsFormatOSGR = 5
+} GpsCoordinateFormat;
+
typedef enum _LocationSharing {
LocationSharing_LocUnset = 0,
LocationSharing_LocEnabled = 1,
LocationSharing_LocDisabled = 2
} LocationSharing;
+typedef enum _PositionFlags {
+ PositionFlags_POS_UNDEFINED = 0,
+ PositionFlags_POS_ALTITUDE = 1,
+ PositionFlags_POS_ALT_MSL = 2,
+ PositionFlags_POS_GEO_SEP = 4,
+ PositionFlags_POS_DOP = 8,
+ PositionFlags_POS_HVDOP = 16,
+ PositionFlags_POS_BATTERY = 32,
+ PositionFlags_POS_SATINVIEW = 64,
+ PositionFlags_POS_SEQ_NOS = 128,
+ PositionFlags_POS_TIMESTAMP = 256
+} PositionFlags;
+
typedef enum _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType {
- RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11 = 0
+ RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11 = 0,
+ RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DS18B20 = 1
} RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType;
/* Struct definitions */
@@ -89,6 +112,7 @@ typedef struct _RadioConfig_UserPreferences {
float frequency_offset;
char mqtt_server[32];
bool mqtt_disabled;
+ GpsCoordinateFormat gps_format;
bool factory_reset;
bool debug_log_enabled;
pb_size_t ignore_incoming_count;
@@ -118,6 +142,7 @@ typedef struct _RadioConfig_UserPreferences {
RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType environmental_measurement_plugin_sensor_type;
uint32_t environmental_measurement_plugin_sensor_pin;
bool store_forward_plugin_enabled;
+ uint32_t position_flags;
} RadioConfig_UserPreferences;
typedef struct _RadioConfig {
@@ -139,10 +164,18 @@ typedef struct _RadioConfig {
#define _GpsOperation_MAX GpsOperation_GpsOpDisabled
#define _GpsOperation_ARRAYSIZE ((GpsOperation)(GpsOperation_GpsOpDisabled+1))
+#define _GpsCoordinateFormat_MIN GpsCoordinateFormat_GpsFormatDec
+#define _GpsCoordinateFormat_MAX GpsCoordinateFormat_GpsFormatOSGR
+#define _GpsCoordinateFormat_ARRAYSIZE ((GpsCoordinateFormat)(GpsCoordinateFormat_GpsFormatOSGR+1))
+
#define _LocationSharing_MIN LocationSharing_LocUnset
#define _LocationSharing_MAX LocationSharing_LocDisabled
#define _LocationSharing_ARRAYSIZE ((LocationSharing)(LocationSharing_LocDisabled+1))
+#define _PositionFlags_MIN PositionFlags_POS_UNDEFINED
+#define _PositionFlags_MAX PositionFlags_POS_TIMESTAMP
+#define _PositionFlags_ARRAYSIZE ((PositionFlags)(PositionFlags_POS_TIMESTAMP+1))
+
#define _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11
#define _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MAX RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11
#define _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_ARRAYSIZE ((RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType)(RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11+1))
@@ -154,9 +187,9 @@ extern "C" {
/* Initializer values for message structs */
#define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default}
-#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, "", 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0, 0}
+#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, "", 0, _GpsCoordinateFormat_MIN, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0, 0, 0}
#define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero}
-#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, "", 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0, 0}
+#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, "", 0, _GpsCoordinateFormat_MIN, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0, 0, 0}
/* Field tags (for use in manual encoding/decoding) */
#define RadioConfig_UserPreferences_position_broadcast_secs_tag 1
@@ -185,6 +218,7 @@ extern "C" {
#define RadioConfig_UserPreferences_frequency_offset_tag 41
#define RadioConfig_UserPreferences_mqtt_server_tag 42
#define RadioConfig_UserPreferences_mqtt_disabled_tag 43
+#define RadioConfig_UserPreferences_gps_format_tag 44
#define RadioConfig_UserPreferences_factory_reset_tag 100
#define RadioConfig_UserPreferences_debug_log_enabled_tag 101
#define RadioConfig_UserPreferences_ignore_incoming_tag 103
@@ -213,6 +247,7 @@ extern "C" {
#define RadioConfig_UserPreferences_environmental_measurement_plugin_sensor_type_tag 146
#define RadioConfig_UserPreferences_environmental_measurement_plugin_sensor_pin_tag 147
#define RadioConfig_UserPreferences_store_forward_plugin_enabled_tag 148
+#define RadioConfig_UserPreferences_position_flags_tag 150
#define RadioConfig_preferences_tag 1
/* Struct field encoding specification for nanopb */
@@ -249,6 +284,7 @@ X(a, STATIC, SINGULAR, BOOL, serial_disabled, 40) \
X(a, STATIC, SINGULAR, FLOAT, frequency_offset, 41) \
X(a, STATIC, SINGULAR, STRING, mqtt_server, 42) \
X(a, STATIC, SINGULAR, BOOL, mqtt_disabled, 43) \
+X(a, STATIC, SINGULAR, UENUM, gps_format, 44) \
X(a, STATIC, SINGULAR, BOOL, factory_reset, 100) \
X(a, STATIC, SINGULAR, BOOL, debug_log_enabled, 101) \
X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103) \
@@ -276,7 +312,8 @@ X(a, STATIC, SINGULAR, UINT32, environmental_measurement_plugin_recovery_int
X(a, STATIC, SINGULAR, BOOL, environmental_measurement_plugin_display_farenheit, 145) \
X(a, STATIC, SINGULAR, UENUM, environmental_measurement_plugin_sensor_type, 146) \
X(a, STATIC, SINGULAR, UINT32, environmental_measurement_plugin_sensor_pin, 147) \
-X(a, STATIC, SINGULAR, BOOL, store_forward_plugin_enabled, 148)
+X(a, STATIC, SINGULAR, BOOL, store_forward_plugin_enabled, 148) \
+X(a, STATIC, SINGULAR, UINT32, position_flags, 150)
#define RadioConfig_UserPreferences_CALLBACK NULL
#define RadioConfig_UserPreferences_DEFAULT NULL
@@ -288,8 +325,8 @@ extern const pb_msgdesc_t RadioConfig_UserPreferences_msg;
#define RadioConfig_UserPreferences_fields &RadioConfig_UserPreferences_msg
/* Maximum encoded size of messages (where known) */
-#define RadioConfig_size 394
-#define RadioConfig_UserPreferences_size 391
+#define RadioConfig_size 404
+#define RadioConfig_UserPreferences_size 401
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp
index 851f06abe..1004e7555 100644
--- a/src/mesh/http/ContentHandler.cpp
+++ b/src/mesh/http/ContentHandler.cpp
@@ -4,7 +4,6 @@
#include "airtime.h"
#include "main.h"
#include "mesh/http/ContentHelper.h"
-#include "mesh/http/ContentStatic.h"
#include "mesh/http/WiFiAPClient.h"
#include "power.h"
#include "sleep.h"
@@ -79,19 +78,17 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
ResourceNode *nodeHotspotApple = new ResourceNode("/hotspot-detect.html", "GET", &handleHotspot);
ResourceNode *nodeHotspotAndroid = new ResourceNode("/generate_204", "GET", &handleHotspot);
- ResourceNode *nodeFavicon = new ResourceNode("/favicon.ico", "GET", &handleFavicon);
- ResourceNode *nodeRoot = new ResourceNode("/", "GET", &handleRoot);
- ResourceNode *nodeStaticBrowse = new ResourceNode("/static", "GET", &handleStaticBrowse);
- ResourceNode *nodeStaticPOST = new ResourceNode("/static", "POST", &handleStaticPost);
- ResourceNode *nodeStatic = new ResourceNode("/static/*", "GET", &handleStatic);
+
ResourceNode *nodeRestart = new ResourceNode("/restart", "POST", &handleRestart);
- ResourceNode *node404 = new ResourceNode("", "GET", &handle404);
ResourceNode *nodeFormUpload = new ResourceNode("/upload", "POST", &handleFormUpload);
+
ResourceNode *nodeJsonScanNetworks = new ResourceNode("/json/scanNetworks", "GET", &handleScanNetworks);
ResourceNode *nodeJsonBlinkLED = new ResourceNode("/json/blink", "POST", &handleBlinkLED);
ResourceNode *nodeJsonReport = new ResourceNode("/json/report", "GET", &handleReport);
ResourceNode *nodeJsonSpiffsBrowseStatic = new ResourceNode("/json/spiffs/browse/static", "GET", &handleSpiffsBrowseStatic);
ResourceNode *nodeJsonDelete = new ResourceNode("/json/spiffs/delete/static", "DELETE", &handleSpiffsDeleteStatic);
+
+ ResourceNode *nodeRoot = new ResourceNode("/*", "GET", &handleStatic);
// Secure nodes
secureServer->registerNode(nodeAPIv1ToRadioOptions);
@@ -99,11 +96,6 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
secureServer->registerNode(nodeAPIv1FromRadio);
secureServer->registerNode(nodeHotspotApple);
secureServer->registerNode(nodeHotspotAndroid);
- secureServer->registerNode(nodeFavicon);
- secureServer->registerNode(nodeRoot);
- secureServer->registerNode(nodeStaticBrowse);
- secureServer->registerNode(nodeStaticPOST);
- secureServer->registerNode(nodeStatic);
secureServer->registerNode(nodeRestart);
secureServer->registerNode(nodeFormUpload);
secureServer->registerNode(nodeJsonScanNetworks);
@@ -111,7 +103,7 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
secureServer->registerNode(nodeJsonSpiffsBrowseStatic);
secureServer->registerNode(nodeJsonDelete);
secureServer->registerNode(nodeJsonReport);
- secureServer->setDefaultNode(node404);
+ secureServer->registerNode(nodeRoot);
secureServer->addMiddleware(&middlewareSpeedUp240);
@@ -121,11 +113,6 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
insecureServer->registerNode(nodeAPIv1FromRadio);
insecureServer->registerNode(nodeHotspotApple);
insecureServer->registerNode(nodeHotspotAndroid);
- insecureServer->registerNode(nodeFavicon);
- insecureServer->registerNode(nodeRoot);
- insecureServer->registerNode(nodeStaticBrowse);
- insecureServer->registerNode(nodeStaticPOST);
- insecureServer->registerNode(nodeStatic);
insecureServer->registerNode(nodeRestart);
insecureServer->registerNode(nodeFormUpload);
insecureServer->registerNode(nodeJsonScanNetworks);
@@ -133,7 +120,7 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
insecureServer->registerNode(nodeJsonSpiffsBrowseStatic);
insecureServer->registerNode(nodeJsonDelete);
insecureServer->registerNode(nodeJsonReport);
- insecureServer->setDefaultNode(node404);
+ insecureServer->registerNode(nodeRoot);
insecureServer->addMiddleware(&middlewareSpeedUp160);
}
@@ -180,11 +167,8 @@ void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res)
/*
For documentation, see:
- https://github.com/meshtastic/Meshtastic-device/wiki/HTTP-REST-API-discussion
- https://github.com/meshtastic/Meshtastic-device/blob/master/docs/software/device-api.md
-
- Example:
- http://10.10.30.198/api/v1/fromradio
+ https://meshtastic.org/docs/developers/device/http-api
+ https://meshtastic.org/docs/developers/device/device-api
*/
// Get access to the parameters
@@ -233,24 +217,20 @@ void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res)
/*
For documentation, see:
- https://github.com/meshtastic/Meshtastic-device/wiki/HTTP-REST-API-discussion
- https://github.com/meshtastic/Meshtastic-device/blob/master/docs/software/device-api.md
-
- Example:
- http://10.10.30.198/api/v1/toradio
+ https://meshtastic.org/docs/developers/device/http-api
+ https://meshtastic.org/docs/developers/device/device-api
*/
- // Status code is 200 OK by default.
-
res->setHeader("Content-Type", "application/x-protobuf");
res->setHeader("Access-Control-Allow-Headers", "Content-Type");
res->setHeader("Access-Control-Allow-Origin", "*");
res->setHeader("Access-Control-Allow-Methods", "PUT, OPTIONS");
res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/Meshtastic-protobufs/master/mesh.proto");
+
if (req->getMethod() == "OPTIONS") {
res->setStatusCode(204); // Success with no content
- res->print("");
+ // res->print(""); @todo remove
return;
}
@@ -264,86 +244,8 @@ void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res)
DEBUG_MSG("--------------- webAPI handleAPIv1ToRadio\n");
}
-void handleFavicon(HTTPRequest *req, HTTPResponse *res)
-{
- // Set Content-Type
- res->setHeader("Content-Type", "image/vnd.microsoft.icon");
- // Write data from header file
- res->write(FAVICON_DATA, FAVICON_LENGTH);
-}
-
-void handleStaticPost(HTTPRequest *req, HTTPResponse *res)
-{
- // Assume POST request. Contains submitted data.
- res->println("File Edited
File Edited
");
-
- // The form is submitted with the x-www-form-urlencoded content type, so we need the
- // HTTPURLEncodedBodyParser to read the fields.
- // Note that the content of the file's content comes from a