diff --git a/.gitignore b/.gitignore index 171f0f3c4..b26acad66 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,5 @@ main/credentials.h .vscode/* !.vscode/settings.json !.vscode/tasks.json -!.vscode/launch.json !.vscode/extensions.json *.code-workspace \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 914831d68..000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,32 +0,0 @@ -// AUTOMATICALLY GENERATED FILE. PLEASE DO NOT MODIFY IT MANUALLY - -// PIO Unified Debugger -// -// Documentation: https://docs.platformio.org/page/plus/debugging.html -// Configuration: https://docs.platformio.org/page/projectconf/section_env_debug.html - -{ - "version": "0.2.0", - "configurations": [ - { - "type": "platformio-debug", - "request": "launch", - "name": "PIO Debug", - "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf", - "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin", - "preLaunchTask": { - "type": "PlatformIO", - "task": "Pre-Debug" - }, - "internalConsoleOptions": "openOnSessionStart" - }, - { - "type": "platformio-debug", - "request": "launch", - "name": "PIO Debug (skip Pre-Debug)", - "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf", - "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin", - "internalConsoleOptions": "openOnSessionStart" - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 6622b9aeb..ebed64343 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -42,10 +42,18 @@ "typeinfo": "cpp", "string": "cpp", "*.xbm": "cpp", - "list": "cpp" + "list": "cpp", + "atomic": "cpp", + "memory_resource": "cpp", + "optional": "cpp", + "string_view": "cpp", + "cassert": "cpp" }, "cSpell.words": [ + "Blox", "Meshtastic", + "NEMAGPS", + "Ublox", "descs", "protobufs" ] diff --git a/README.md b/README.md index 365786ac7..5fa2c871d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Meshtastic-esp32 +# Meshtastic-device This is the device side code for the [meshtastic.org](https://www.meshtastic.org) project. @@ -20,9 +20,25 @@ This software is 100% open source and developed by a group of hobbyist experimen ## Supported hardware -We currently support three models of radios. The [TTGO T-Beam](https://www.aliexpress.com/item/4000119152086.html), [TTGO LORA32](https://www.banggood.com/LILYGO-TTGO-LORA32-868Mhz-SX1276-ESP32-Oled-Display-bluetooth-WIFI-Lora-Development-Module-Board-p-1248652.html?cur_warehouse=UK) and the [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/). Most users should buy the T-Beam and an 18650 battery (total cost less than \$35). Make sure to buy the frequency range which is legal for your country (915MHz for US/JP/AU/NZ, 470MHz for CN, 433MHz and 870MHz for EU). Getting a version that includes a screen is optional, but highly recommended. +We currently support three models of radios. +- TTGO T-Beam + - [T-Beam V1.0 w/ NEO-M8N](https://www.aliexpress.com/item/33047631119.html) (Recommended) + - [T-Beam V1.0 w/ NEO-6M](https://www.aliexpress.com/item/33050391850.html) + - 3D printable cases + - [T-Beam V0](https://www.thingiverse.com/thing:3773717) + - [T-Beam V1](https://www.thingiverse.com/thing:3830711) -See (meshtastic.org) for 3D printable cases. +- [TTGO LORA32](https://www.aliexpress.com/item/4000211331316.html) - No GPS + +- [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/) - No GPS + - [3D Printable case](https://www.thingiverse.com/thing:3125854) + +**Make sure to get the frequency for your country** + - US/JP/AU/NZ - 915MHz + - CN - 470MHz + - EU - 870MHz + +Getting a version that includes a screen is optional, but highly recommended. ## Firmware Installation @@ -35,12 +51,13 @@ Please post comments on our [group chat](https://meshtastic.discourse.group/) if 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. +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 finished, the terminal should start displaying debug messages including the Bluetooth passphrase when you try connect from your phone (handy if you don’t have a screen). +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 @@ -70,7 +87,10 @@ 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 firmware-_board_-_country_.bin". For instance "./device-install.sh firmware-HELTEC-US-0.0.3.bin". +6. Install the correct firmware for your board with `device-install.sh firmware-_board_-_country_.bin`. + - Example: `./device-install.sh firmware-HELTEC-US-0.0.3.bin`. +7. To update run `device-update.sh firmware-_board_-_country_.bin` + - Example: `./device-update.sh 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_.bin". This will be faster, also all of your current preferences will be preserved. @@ -143,15 +163,19 @@ 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 +# Meshtastic Android app The source code for the (optional) Meshtastic Android app is [here](https://github.com/meshtastic/Meshtastic-Android). -Alpha test builds are current available by opting into our alpha test group. See (www.meshtastic.org) for instructions. +Alpha test builds available by opting into our alpha test group. See (www.meshtastic.org) for instructions. -After our rate of change slows a bit, we will make beta builds available here (without needing to join the alphatest group): +If you don't want to live on the 'bleeding edge' you can opt-in to the beta-test or use the released version: [![Download at https://play.google.com/store/apps/details?id=com.geeksville.mesh](https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png)](https://play.google.com/store/apps/details?id=com.geeksville.mesh&referrer=utm_source%3Dgithub%26utm_medium%3Desp32-readme%26utm_campaign%3Dmeshtastic-esp32%2520readme%26anid%3Dadmob&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1) +# 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/). diff --git a/bin/build-all.sh b/bin/build-all.sh index 29a6af3a6..55d72fa87 100755 --- a/bin/build-all.sh +++ b/bin/build-all.sh @@ -6,6 +6,10 @@ source bin/version.sh COUNTRIES="US EU433 EU865 CN JP" #COUNTRIES=US +#COUNTRIES=CN + +BOARDS="ttgo-lora32-v2 ttgo-lora32-v1 tbeam heltec" +#BOARDS=tbeam OUTDIR=release/latest @@ -24,23 +28,27 @@ function do_build { SRCBIN=.pio/build/$ENV_NAME/firmware.bin SRCELF=.pio/build/$ENV_NAME/firmware.elf rm -f $SRCBIN - pio run --environment $ENV_NAME # -v + + # The shell vars the build tool expects to find + export HW_VERSION="1.0-$COUNTRY" + export APP_VERSION=$VERSION + export COUNTRY + + pio run --jobs 4 --environment $ENV_NAME # -v cp $SRCBIN $OUTDIR/bins/firmware-$ENV_NAME-$COUNTRY-$VERSION.bin cp $SRCELF $OUTDIR/elfs/firmware-$ENV_NAME-$COUNTRY-$VERSION.elf } +# Make sure our submodules are current +git submodule update + +# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale +platformio lib update + for COUNTRY in $COUNTRIES; do - - HWVERSTR="1.0-$COUNTRY" - COMMONOPTS="-DAPP_VERSION=$VERSION -DHW_VERSION_$COUNTRY -DHW_VERSION=$HWVERSTR -Wall -Wextra -Wno-missing-field-initializers -Isrc -Os -DAXP_DEBUG_PORT=Serial" - - export PLATFORMIO_BUILD_FLAGS="$COMMONOPTS" - - do_build "tbeam0.7" - do_build "ttgo-lora32-v2" - do_build "ttgo-lora32-v1" - do_build "tbeam" - do_build "heltec" + for BOARD in $BOARDS; do + do_build $BOARD + done done # keep the bins in archive also @@ -59,6 +67,6 @@ Generated by bin/buildall.sh --> XML rm -f $ARCHIVEDIR/firmware-$VERSION.zip -zip --junk-paths $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/bins/firmware-*-$VERSION.* images/system-info.bin bin/device-install.sh +zip --junk-paths $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/bins/firmware-*-$VERSION.* images/system-info.bin bin/device-install.sh bin/device-update.sh -echo BUILT ALL \ No newline at end of file +echo BUILT ALL diff --git a/bin/device-update.sh b/bin/device-update.sh new file mode 100755 index 000000000..b8a8dcf9b --- /dev/null +++ b/bin/device-update.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +set -e + +FILENAME=$1 + +echo "Trying to update $FILENAME" +esptool.py --baud 921600 writeflash 0x10000 $FILENAME diff --git a/bin/nrf52-console.sh b/bin/nrf52-console.sh new file mode 100755 index 000000000..c7ad903c1 --- /dev/null +++ b/bin/nrf52-console.sh @@ -0,0 +1 @@ +JLinkRTTViewer diff --git a/bin/program-release-heltec.sh b/bin/program-release-heltec.sh index d16f6c360..90506473b 100755 --- a/bin/program-release-heltec.sh +++ b/bin/program-release-heltec.sh @@ -3,4 +3,4 @@ set -e source bin/version.sh -esptool.py --baud 921600 write_flash 0x10000 release/latest/firmware-HELTEC-US-$VERSION.bin +esptool.py --baud 921600 write_flash 0x10000 release/latest/bins/firmware-heltec-US-$VERSION.bin diff --git a/bin/program-release-tbeam.sh b/bin/program-release-tbeam.sh index 2c5f030c6..d83b35622 100755 --- a/bin/program-release-tbeam.sh +++ b/bin/program-release-tbeam.sh @@ -3,4 +3,4 @@ set -e source bin/version.sh -esptool.py --baud 921600 write_flash 0x10000 release/latest/firmware-TBEAM-US-$VERSION.bin +esptool.py --baud 921600 write_flash 0x10000 release/latest/bins/firmware-tbeam-US-$VERSION.bin diff --git a/bin/regen-protos.sh b/bin/regen-protos.sh index bc6bc9b72..0f1ae43c6 100755 --- a/bin/regen-protos.sh +++ b/bin/regen-protos.sh @@ -3,4 +3,4 @@ echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.1" # the nanopb tool seems to require that the .options file be in the current directory! cd proto -../../nanopb-0.4.1-linux-x86/generator-bin/protoc --nanopb_out=-v:../src -I=../proto mesh.proto +../../nanopb-0.4.1-linux-x86/generator-bin/protoc --nanopb_out=-v:../src/mesh -I=../proto mesh.proto diff --git a/bin/run-1-monitor.sh b/bin/run-1-monitor.sh new file mode 100755 index 000000000..a25d99489 --- /dev/null +++ b/bin/run-1-monitor.sh @@ -0,0 +1 @@ +pio run --upload-port /dev/ttyUSB1 -t upload -t monitor diff --git a/bin/start-terminal0.sh b/bin/start-terminal0.sh index 895fb94b5..3ad4e7822 100755 --- a/bin/start-terminal0.sh +++ b/bin/start-terminal0.sh @@ -1 +1 @@ -pio device monitor -b 115200 +pio device monitor -b 921600 diff --git a/bin/start-terminal1.sh b/bin/start-terminal1.sh index c5236bbe3..3433501ea 100755 --- a/bin/start-terminal1.sh +++ b/bin/start-terminal1.sh @@ -1 +1 @@ -pio device monitor -p /dev/ttyUSB1 -b 115200 +pio device monitor -p /dev/ttyUSB1 -b 921600 diff --git a/bin/version.sh b/bin/version.sh index 429d819cf..fc9ebf0f0 100644 --- a/bin/version.sh +++ b/bin/version.sh @@ -1,3 +1,3 @@ -export VERSION=0.2.3 \ No newline at end of file +export VERSION=0.6.3 \ No newline at end of file diff --git a/boards/nrf52840_dk_modified.json b/boards/nrf52840_dk_modified.json new file mode 100644 index 000000000..ce86e09f5 --- /dev/null +++ b/boards/nrf52840_dk_modified.json @@ -0,0 +1,46 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_PCA10056 -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [["0x239A", "0x4404"]], + "usb_product": "SimPPR", + "mcu": "nrf52840", + "variant": "pca10056-rc-clock", + "variants_dir": "variants", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": ["jlink"], + "svd_path": "nrf52840.svd" + }, + "frameworks": ["arduino"], + "name": "A modified NRF52840-DK devboard (Adafruit BSP)", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "require_upload_port": true, + "speed": 115200, + "protocol": "jlink", + "protocols": ["jlink", "nrfjprog", "stlink"] + }, + "url": "https://meshtastic.org/", + "vendor": "Nordic Semi" +} diff --git a/boards/ppr.json b/boards/ppr.json new file mode 100644 index 000000000..5283fdc4e --- /dev/null +++ b/boards/ppr.json @@ -0,0 +1,46 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_PPR -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [["0x239A", "0x4403"]], + "usb_product": "PPR", + "mcu": "nrf52840", + "variant": "ppr", + "variants_dir": "variants", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": ["jlink"], + "svd_path": "nrf52840.svd" + }, + "frameworks": ["arduino"], + "name": "Meshtastic PPR (Adafruit BSP)", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "require_upload_port": true, + "speed": 115200, + "protocol": "jlink", + "protocols": ["jlink", "nrfjprog", "stlink"] + }, + "url": "https://meshtastic.org/", + "vendor": "Othernet" +} diff --git a/docs/.well-known/assetlinks.json b/docs/.well-known/assetlinks.json index ac10fce86..85d22f4a6 100644 --- a/docs/.well-known/assetlinks.json +++ b/docs/.well-known/assetlinks.json @@ -4,6 +4,6 @@ "namespace": "android_app", "package_name": "com.geeksville.mesh", "sha256_cert_fingerprints": - ["D0:05:E7:8B:D2:1B:FA:94:56:1D:6B:90:EB:53:07:1A:74:4F:D9:C2:6F:13:87:6A:D9:17:4F:C2:59:48:02:9D"] + ["D0:05:E7:8B:D2:1B:FA:94:56:1D:6B:90:EB:53:07:1A:74:4F:D9:C2:6F:13:87:6A:D9:17:4F:C2:59:48:02:9D", "42:17:52:DC:57:40:38:B5:6B:86:61:1C:2F:47:DB:2B:0F:A2:EA:59:E1:18:9C:AA:90:8D:37:D6:CD:40:0E:BB", "A9:3B:45:65:68:C1:75:DB:08:00:A0:9F:06:77:7F:89:2D:81:24:32:AD:B8:A3:DF:73:BC:3E:7F:06:C8:0C:6D"] } -}] \ No newline at end of file +}] diff --git a/docs/README.md b/docs/README.md index 65d616bce..47281dbeb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,7 +1,7 @@ # What is Meshtastic? Meshtastic is a project that lets you use -inexpensive (\$30 ish) GPS 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. +inexpensive (\$30 ish) GPS radios as an extensible, long battery life, secure, 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. @@ -13,6 +13,7 @@ Note: Questions after reading this? See our new [forum](https://meshtastic.disco - Applications where closed source GPS communicators just won't cut it (it is easy to add features for glider pilots etc...) - Secure long-range communication within groups without depending on cellular providers - Finding your lost kids ;-) +- Through our [python API](https://pypi.org/project/meshtastic/) use these inexpensive radios to easily add mesh networking to your own projects. [![Youtube video demo](desk-video-screenshot.png)](https://www.youtube.com/watch?v=WlNbMbVZlHI "Meshtastic early demo") @@ -36,8 +37,10 @@ This software is 100% open source and developed by a group of hobbyist experimen # Updates -Note: Updates are happening almost daily, only major updates are listed below. For more details see our chat, github releases or the Android alpha tester emails. +Note: Updates are happening almost daily, only major updates are listed below. For more details see our forum. +- 04/28/2020 - 0.6.0 [Python API](https://pypi.org/project/meshtastic/) released. Makes it easy to use meshtastic devices as "zero config / just works" mesh transport adapters for other projects. +- 04/20/2020 - 0.4.3 Pretty solid now both for the android app and the device code. Many people have donated translations and code. Probably going to call it a beta soon. - 03/03/2020 - 0.0.9 of the Android app and device code is released. Still an alpha but fairly functional. - 02/25/2020 - 0.0.4 of the Android app is released. This is a very early alpha, see below to join the alpha-testers group. - 02/23/2020 - 0.0.4 release. Still very bleeding edge but much closer to the final power management, a charged T-BEAM should run for many days with this load. If you'd like to try it, we'd love your feedback. Click [here](https://github.com/meshtastic/Meshtastic-esp32/blob/master/README.md) for instructions. @@ -45,11 +48,11 @@ Note: Updates are happening almost daily, only major updates are listed below. F ## Meshtastic Android app -Once out of alpha the companion Android application will be released here: +Our Android application is available here: [![Download at https://play.google.com/store/apps/details?id=com.geeksville.mesh](https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png)](https://play.google.com/store/apps/details?id=com.geeksville.mesh&referrer=utm_source%3Dhomepage%26anid%3Dadmob) -But if you want the bleeding edge app now, we'd love to have your help testing. Three steps to opt-in to the alpha- test: +The link above will return older more stable releases. We would prefer if you join our alpha-test group, because the application is rapidly improving. Three steps to opt-in to the alpha- test: 1. Join [this Google group](https://groups.google.com/forum/#!forum/meshtastic-alpha-testers) with the account you use in Google Play. 2. Go to this [URL](https://play.google.com/apps/testing/com.geeksville.mesh) to opt-in to the alpha test. diff --git a/docs/_config.yml b/docs/_config.yml index 5bf84d80b..9c1ecd436 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -3,3 +3,6 @@ theme: jekyll-theme-cayman title: Meshtastic description: An opensource hiking, pilot, skiing, Signal-App-extending GPS mesh communicator google_analytics: G-DRZ5H5EXHV + +include: [".well-known"] + diff --git a/docs/faq.md b/docs/faq.md index c8ceae960..a015f9fdc 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,27 +1,25 @@ # Disclaimers -This project is still pretty young but moving at a pretty good pace. Not all features are fully implemented in the current alpha builds. +This project is still pretty young but moving at a pretty good pace. Not all features are fully implemented in the current alpha builds. Most of these problems should be solved by the beta release (within three months): -* We don't make these devices and they haven't been tested by UL or the FCC. If you use them you are experimenting and we can't promise they won't burn your house down ;-) -* Encryption is turned off for now -* A number of (straightforward) software work items have to be completed before battery life matches our measurements, currently battery life is about three days. Join us on chat if you want the spreadsheet of power measurements/calculations. -* The current Android GUI is slightly ugly still -* The Android API needs to be documented better -* The mesh protocol is turned off for now, currently we only send packets one hop distant. The mesh feature will be turned on again [soonish](https://github.com/meshtastic/Meshtastic-esp32/issues/3). -* No one has written an iOS app yet. But some good souls [are talking about it](https://github.com/meshtastic/Meshtastic-esp32/issues/14) ;-) +- We don't make these devices and they haven't been tested by UL or the FCC. If you use them you are experimenting and we can't promise they won't burn your house down ;-) +- The encryption [implementation](software/crypto.md) has not been reviewed by an expert. (Are you an expert? Please help us) +- A number of (straightforward) software work items have to be completed before battery life matches our measurements, currently battery life is about three days. Join us on chat if you want the spreadsheet of power measurements/calculations. +- The Android API needs to be documented better +- No one has written an iOS app yet. But some good souls [are talking about it](https://github.com/meshtastic/Meshtastic-esp32/issues/14) ;-) For more details see the [device software TODO](https://github.com/meshtastic/Meshtastic-esp32/blob/master/docs/software/TODO.md) or the [Android app TODO](https://github.com/meshtastic/Meshtastic-Android/blob/master/TODO.md). # FAQ -If you have a question missing from this faq, please [ask in our discussion forum](https://meshtastic.discourse.group/). And if you are feeling extra generous send in a pull-request for this faq.md with whatever we answered ;-). +If you have a question missing from this faq, please [ask in our discussion forum](https://meshtastic.discourse.group/). And if you are feeling extra generous send in a pull-request for this faq.md with whatever we answered ;-). ## Q: Which of the various supported radios should I buy? -Basically you just need the radio + (optional but recommended) battery. The TBEAM is usually better because it has gps and huge battery socket. The Heltec is basically the same hardware but without the GPS (the phone provides position data to the radio in that case, so the behavior is similar - but it does burn some battery in the phone). Also the battery for the Heltec can be smaller. +Basically you just need the radio + (optional but recommended) battery. The TBEAM is usually better because it has gps and huge battery socket. The Heltec is basically the same hardware but without the GPS (the phone provides position data to the radio in that case, so the behavior is similar - but it does burn some battery in the phone). Also the battery for the Heltec can be smaller. -In addition to Aliexpress, (banggood.com) usually has stock and faster shipping, or Amazon. If buying a TBEAM, make sure to buy a version that includes the OLED screen - this project doesn't absolutely require the screen, but we use it if is installed. +In addition to Aliexpress, (banggood.com) usually has stock and faster shipping, or Amazon. If buying a TBEAM, make sure to buy a version that includes the OLED screen - this project doesn't absolutely require the screen, but we use it if is installed. @claesg has added links to various 3D printable cases, you can see them at (www.meshtastic.org). @@ -29,8 +27,8 @@ In addition to Aliexpress, (banggood.com) usually has stock and faster shipping, Nope. though if some other person/group wanted to use this software and a more customized device we think that would be awesome (as long as they obey the GPL license). -## Q: Does this project use patented algorithms? +## Q: Does this project use patented algorithms? (Kindly borrowed from the geeks at [ffmpeg](http://ffmpeg.org/legal.html)) -We do not know, we are not lawyers so we are not qualified to answer this. Also we have never read patents to implement any part of this, so even if we were qualified we could not answer it as we do not know what is patented. Furthermore the sheer number of software patents makes it impossible to read them all so no one (lawyer or not) could answer such a question with a definite no. We are merely geeks experimenting on a fun and free project. +We do not know, we are not lawyers so we are not qualified to answer this. Also we have never read patents to implement any part of this, so even if we were qualified we could not answer it as we do not know what is patented. Furthermore the sheer number of software patents makes it impossible to read them all so no one (lawyer or not) could answer such a question with a definite no. We are merely geeks experimenting on a fun and free project. diff --git a/docs/mesh-proto.md b/docs/mesh-proto.md deleted file mode 100644 index aa5a84df9..000000000 --- a/docs/mesh-proto.md +++ /dev/null @@ -1,20 +0,0 @@ -TODO: -* reread the radiohead mesh implementation -* read about general mesh flooding solutions -* reread the disaster radio protocol docs - -good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept - -interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf -It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728 -https://en.wikipedia.org/wiki/Dynamic_Source_Routing - -broadcast solution: -Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent) -Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set - -compare to db sync algorithm? - -what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies. - -If Y were to die, at least the neighbor nodes of Y would have their last known position of Y. diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 2d80da3c1..93ceb20d2 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -5,21 +5,16 @@ Items to complete soon (next couple of alpha releases). - lower wait_bluetooth_secs to 30 seconds once we have the GPS power on (but GPS in sleep mode) across light sleep. For the time being I have it set at 2 minutes to ensure enough time for a GPS lock from scratch. -- remeasure wake time power draws now that we run CPU down at 80MHz - -# AXP192 tasks - -- figure out why this fixme is needed: "FIXME, disable wake due to PMU because it seems to fire all the time?" -- "AXP192 interrupt is not firing, remove this temporary polling of battery state" -- make debug info screen show real data (including battery level & charging) - close corresponding github issue - # Medium priority Items to complete before the first beta release. -- Don't store position packets in the to phone fifo if we are disconnected. The phone will get that info for 'free' when it -fetches the fresh nodedb. -- Use the RFM95 sequencer to stay in idle mode most of the time, then automatically go to receive mode and automatically go from transmit to receive mode. See 4.2.8.2 of manual. +- Use 32 bits for message IDs +- Use fixed32 for node IDs +- Remove the "want node" node number arbitration process +- Don't store position packets in the to phone fifo if we are disconnected. The phone will get that info for 'free' when it + fetches the fresh nodedb. +- Use the RFM95 sequencer to stay in idle mode most of the time, then automatically go to receive mode and automatically go from transmit to receive mode. See 4.2.8.2 of manual. - possibly switch to https://github.com/SlashDevin/NeoGPS for gps comms - good source of battery/signal/gps icons https://materialdesignicons.com/ - research and implement better mesh algorithm - investigate changing routing to https://github.com/sudomesh/LoRaLayer2 ? @@ -29,7 +24,6 @@ fetches the fresh nodedb. - rx signal measurements -3 marginal, -9 bad, 10 great, -10 means almost unusable. So scale this into % signal strength. preferably as a graph, with an X indicating loss of comms. - assign every "channel" a random shared 8 bit sync word (per 4.2.13.6 of datasheet) - use that word to filter packets before even checking CRC. This will ensure our CPU will only wake for packets on our "channel" - Note: we do not do address filtering at the chip level, because we might need to route for the mesh -- add basic crypto - https://github.com/chegewara/esp32-mbedtls-aes-test/blob/master/main/main.c https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation - use ECB at first (though it is shit) because it doesn't require us to send 16 bytes of IV with each packet. Then OFB per example. Possibly do this crypto at the data payload level only, so that all of the packet routing metadata is in cleartext (so that nodes will route for other radios that are cryptoed with a key we don't know) - add frequency hopping, dependent on the gps time, make the switch moment far from the time anyone is going to be transmitting - share channel settings over Signal (or qr code) by embedding an an URL which is handled by the MeshUtil app. @@ -205,3 +199,7 @@ Items after the first final candidate release. - enable fast lock and low power inside the gps chip - Make a FAQ - add a SF12 transmit option for _super_ long range +- figure out why this fixme is needed: "FIXME, disable wake due to PMU because it seems to fire all the time?" +- "AXP192 interrupt is not firing, remove this temporary polling of battery state" +- make debug info screen show real data (including battery level & charging) - close corresponding github issue +- remeasure wake time power draws now that we run CPU down at 80MHz diff --git a/docs/software/bluetooth-api.md b/docs/software/bluetooth-api.md index 69f808616..cc0df5a1d 100644 --- a/docs/software/bluetooth-api.md +++ b/docs/software/bluetooth-api.md @@ -1,30 +1,30 @@ # Bluetooth API -The Bluetooth API is design to have only a few characteristics and most polymorphism comes from the flexible set of Google Protocol Buffers which are sent over the wire. We use protocol buffers extensively both for the bluetooth API and for packets inside the mesh or when providing packets to other applications on the phone. +The Bluetooth API is design to have only a few characteristics and most polymorphism comes from the flexible set of Google Protocol Buffers which are sent over the wire. We use protocol buffers extensively both for the bluetooth API and for packets inside the mesh or when providing packets to other applications on the phone. ## A note on MTU sizes -This device will work with any MTU size, but it is highly recommended that you call your phone's "setMTU function to increase MTU to 512 bytes" as soon as you connect to a service. This will dramatically improve performance when reading/writing packets. +This device will work with any MTU size, but it is highly recommended that you call your phone's "setMTU function to increase MTU to 512 bytes" as soon as you connect to a service. This will dramatically improve performance when reading/writing packets. -## MeshBluetoothService +## MeshBluetoothService -This is the main bluetooth service for the device and provides the API your app should use to get information about the mesh, send packets or provision the radio. +This is the main bluetooth service for the device and provides the API your app should use to get information about the mesh, send packets or provision the radio. -For a reference implementation of a client that uses this service see [RadioInterfaceService](https://github.com/meshtastic/Meshtastic-Android/blob/master/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt). Typical flow when +For a reference implementation of a client that uses this service see [RadioInterfaceService](https://github.com/meshtastic/Meshtastic-Android/blob/master/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt). Typical flow when a phone connects to the device should be the following: -* SetMTU size to 512 -* Read a RadioConfig from "radio" - used to get the channel and radio settings -* Read (and write if incorrect) a User from "user" - to get the username for this node -* Read a MyNodeInfo from "mynode" to get information about this local device -* Write an empty record to "nodeinfo" to restart the nodeinfo reading state machine -* Read from "nodeinfo" until it returns empty to build the phone's copy of the current NodeDB for the mesh -* Read from "fromradio" until it returns empty to get any messages that arrived for this node while the phone was away -* Subscribe to notify on "fromnum" to get notified whenever the device has a new received packet -* Read that new packet from "fromradio" -* Whenever the phone has a packet to send write to "toradio" +- SetMTU size to 512 +- Read a RadioConfig from "radio" - used to get the channel and radio settings +- Read (and write if incorrect) a User from "user" - to get the username for this node +- Read a MyNodeInfo from "mynode" to get information about this local device +- Write an empty record to "nodeinfo" to restart the nodeinfo reading state machine +- Read from "nodeinfo" until it returns empty to build the phone's copy of the current NodeDB for the mesh +- Read from "fromradio" until it returns empty to get any messages that arrived for this node while the phone was away +- Subscribe to notify on "fromnum" to get notified whenever the device has a new received packet +- Read that new packet from "fromradio" +- Whenever the phone has a packet to send write to "toradio" -For definitions (and documentation) on FromRadio, ToRadio, MyNodeInfo, NodeInfo and User protocol buffers see [mesh.proto](https://github.com/meshtastic/Meshtastic-protobufs/blob/master/mesh.proto) +For definitions (and documentation) on FromRadio, ToRadio, MyNodeInfo, NodeInfo and User protocol buffers see [mesh.proto](https://github.com/meshtastic/Meshtastic-protobufs/blob/master/mesh.proto) UUID for the service: 6ba1b218-15a8-461f-9fa8-5dcae273eafd @@ -37,7 +37,7 @@ Description (including human readable name) 8ba2bcc2-ee02-4a55-a531-c525c5e454d5 read fromradio - contains a newly received FromRadio packet destined towards the phone (up to MAXPACKET bytes per packet). -After reading the esp32 will put the next packet in this mailbox. If the FIFO is empty it will put an empty packet in this +After reading the esp32 will put the next packet in this mailbox. If the FIFO is empty it will put an empty packet in this mailbox. f75c76d2-129e-4dad-a1dd-7866124401e7 @@ -49,34 +49,22 @@ read,notify,write fromnum - the current packet # in the message waiting inside fromradio, if the phone sees this notify it should read messages until it catches up with this number. -The phone can write to this register to go backwards up to FIXME packets, to handle the rare case of a fromradio packet was dropped after the esp32 callback was called, but before it arrives at the phone. If the phone writes to this register the esp32 will discard older packets and put the next packet >= fromnum in fromradio. +The phone can write to this register to go backwards up to FIXME packets, to handle the rare case of a fromradio packet was dropped after the esp32 callback was called, but before it arrives at the phone. If the phone writes to this register the esp32 will discard older packets and put the next packet >= fromnum in fromradio. When the esp32 advances fromnum, it will delay doing the notify by 100ms, in the hopes that the notify will never actally need to be sent if the phone is already pulling from fromradio. Note: that if the phone ever sees this number decrease, it means the esp32 has rebooted. -ea9f3f82-8dc4-4733-9452-1f6da28892a2 -read -mynode - read this to access a MyNodeInfo protobuf - -d31e02e0-c8ab-4d3f-9cc9-0b8466bdabe8 -read, write -nodeinfo - read this to get a series of NodeInfos (ending with a null empty record), write to this to restart the read statemachine that returns all the node infos - -b56786c8-839a-44a1-b98e-a1724c4a0262 -read,write -radio - read/write this to access a RadioConfig protobuf - -6ff1d8b6-e2de-41e3-8c0b-8fa384f64eb6 -read,write -owner - read/write this to access a User protobuf - Re: queue management Not all messages are kept in the fromradio queue (filtered based on SubPacket): -* only the most recent Position and User messages for a particular node are kept -* all Data SubPackets are kept -* No WantNodeNum / DenyNodeNum messages are kept -A variable keepAllPackets, if set to true will suppress this behavior and instead keep everything for forwarding to the phone (for debugging) +- only the most recent Position and User messages for a particular node are kept +- all Data SubPackets are kept +- No WantNodeNum / DenyNodeNum messages are kept + A variable keepAllPackets, if set to true will suppress this behavior and instead keep everything for forwarding to the phone (for debugging) + +## Protobuf API + +On connect, you should send a want_config_id protobuf to the device. This will cause the device to send its node DB and radio config via the fromradio endpoint. After sending the full DB, the radio will send a want_config_id to indicate it is done sending the configuration. ## Other bluetooth services @@ -85,21 +73,21 @@ provided by the device. ### BluetoothSoftwareUpdate -The software update service. For a sample function that performs a software update using this API see [startUpdate](https://github.com/meshtastic/Meshtastic-Android/blob/master/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt). +The software update service. For a sample function that performs a software update using this API see [startUpdate](https://github.com/meshtastic/Meshtastic-Android/blob/master/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt). SoftwareUpdateService UUID cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30 Characteristics -| UUID | properties | description| -|--------------------------------------|------------------|------------| -| e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e | write,read | total image size, 32 bit, write this first, then read read back to see if it was acceptable (0 mean not accepted) | -| e272ebac-d463-4b98-bc84-5cc1a39ee517 | write | data, variable sized, recommended 512 bytes, write one for each block of file | -| 4826129c-c22a-43a3-b066-ce8f0d5bacc6 | write | crc32, write last - writing this will complete the OTA operation, now you can read result | -| 5e134862-7411-4424-ac4a-210937432c77 | read,notify | result code, readable but will notify when the OTA operation completes | -| GATT_UUID_SW_VERSION_STR/0x2a28 | read | We also implement these standard GATT entries because SW update probably needs them: | -| GATT_UUID_MANU_NAME/0x2a29 | read | | -| GATT_UUID_HW_VERSION_STR/0x2a27 | read | | +| UUID | properties | description | +| ------------------------------------ | ----------- | ----------------------------------------------------------------------------------------------------------------- | +| e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e | write,read | total image size, 32 bit, write this first, then read read back to see if it was acceptable (0 mean not accepted) | +| e272ebac-d463-4b98-bc84-5cc1a39ee517 | write | data, variable sized, recommended 512 bytes, write one for each block of file | +| 4826129c-c22a-43a3-b066-ce8f0d5bacc6 | write | crc32, write last - writing this will complete the OTA operation, now you can read result | +| 5e134862-7411-4424-ac4a-210937432c77 | read,notify | result code, readable but will notify when the OTA operation completes | +| GATT_UUID_SW_VERSION_STR/0x2a28 | read | We also implement these standard GATT entries because SW update probably needs them: | +| GATT_UUID_MANU_NAME/0x2a29 | read | | +| GATT_UUID_HW_VERSION_STR/0x2a27 | read | | ### DeviceInformationService @@ -107,4 +95,4 @@ Implements the standard BLE contract for this service (has software version, har ### BatteryLevelService -Implements the standard BLE contract service, provides battery level in a way that most client devices should automatically understand (i.e. it should show in the bluetooth devices screen automatically) \ No newline at end of file +Implements the standard BLE contract service, provides battery level in a way that most client devices should automatically understand (i.e. it should show in the bluetooth devices screen automatically) diff --git a/docs/software/crypto.md b/docs/software/crypto.md new file mode 100644 index 000000000..7f5709c48 --- /dev/null +++ b/docs/software/crypto.md @@ -0,0 +1,40 @@ +# Encryption in Meshtastic + +Cryptography is tricky, so we've tried to 'simply' apply standard crypto solutions to our implementation. However, +the project developers are not cryptography experts. Therefore we ask two things: + +- If you are a cryptography expert, please review these notes and our questions below. Can you help us by reviewing our + notes below and offering advice? We will happily give as much or as little credit as you wish ;-). +- Consider our existing solution 'alpha' and probably fairly secure against a not particularly aggressive adversary. But until + it is reviewed by someone smarter than us, assume it might have flaws. + +## Notes on implementation + +- We do all crypto at the SubPacket (payload) level only, so that all meshtastic nodes will route for others - even those channels which are encrypted with a different key. +- Mostly based on reading [Wikipedia]() and using the modes the ESP32 provides support for in hardware. +- We use AES256-CTR as a stream cypher (with zero padding on the last BLOCK) because it is well supported with hardware acceleration. + +Parameters for our CTR implementation: + +- Our AES key is 128 or 256 bits, shared as part of the 'Channel' specification. +- Each SubPacket will be sent as a series of 16 byte BLOCKS. +- The node number concatenated with the packet number is used as the NONCE. This counter will be stored in flash in the device and should essentially never repeat. If the user makes a new 'Channel' (i.e. picking a new random 256 bit key), the packet number will start at zero. The packet number is sent + in cleartext with each packet. The node number can be derived from the "from" field of each packet. +- Each BLOCK for a packet has an incrementing COUNTER. COUNTER starts at zero for the first block of each packet. +- The IV for each block is constructed by concatenating the NONCE as the upper 96 bits of the IV and the COUNTER as the bottom 32 bits. Note: since our packets are small counter will really never be higher than 32 (five bits). + +``` +You can encrypt separate messages by dividing the nonce_counter buffer in two areas: the first one used for a per-message nonce, handled by yourself, and the second one updated by this function internally. +For example, you might reserve the first 12 bytes for the per-message nonce, and the last 4 bytes for internal use. In that case, before calling this function on a new message you need to set the first 12 bytes of nonce_counter to your chosen nonce value, the last 4 to 0, and nc_off to 0 (which will cause stream_block to be ignored). That way, you can encrypt at most 2**96 messages of up to 2**32 blocks each with the same key. + +The per-message nonce (or information sufficient to reconstruct it) needs to be communicated with the ciphertext and must be unique. The recommended way to ensure uniqueness is to use a message counter. An alternative is to generate random nonces, but this limits the number of messages that can be securely encrypted: for example, with 96-bit random nonces, you should not encrypt more than 2**32 messages with the same key. + +Note that for both stategies, sizes are measured in blocks and that an AES block is 16 bytes. +``` + +## Remaining todo + +- Make the packet numbers 32 bit +- Confirm the packet #s are stored in flash across deep sleep (and otherwise in in RAM) +- Have the app change the crypto key when the user generates a new channel +- Implement for NRF52 [NRF52](https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.0.0/lib_crypto_aes.html#sub_aes_ctr) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index d0935d34f..d4ae71215 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -1,72 +1,162 @@ # Mesh broadcast algorithm -FIXME - instead look for standard solutions. this approach seems really suboptimal, because too many nodes will try to rebroast. If -all else fails could always use the stock Radiohead solution - though super inefficient. - great source of papers and class notes: http://www.cs.jhu.edu/~cs647/ -TODO: -* DONE reread the radiohead mesh implementation - hop to hop acknoledgement seems VERY expensive but otherwise it seems like DSR -* DONE read about mesh routing solutions (DSR and AODV) -* DONE read about general mesh flooding solutions (naive, MPR, geo assisted) -* DONE reread the disaster radio protocol docs - seems based on Babel (which is AODVish) -* possibly dash7? https://www.slideshare.net/MaartenWeyn1/dash7-alliance-protocol-technical-presentation https://github.com/MOSAIC-LoPoW/dash7-ap-open-source-stack - does the opensource stack implement multihop routing? flooding? their discussion mailing list looks dead-dead -* update duty cycle spreadsheet for our typical usecase -* generalize naive flooding on top of radiohead or disaster.radio? (and fix radiohead to use my new driver) +flood routing improvements +- DONE if we don't see anyone rebroadcast our want_ack=true broadcasts, retry as needed. + +reliable messaging tasks (stage one for DSR): + +- DONE generalize naive flooding +- DONE add a max hops parameter, use it for broadcast as well (0 means adjacent only, 1 is one forward etc...). Store as three bits in the header. +- DONE add a 'snoopReceived' hook for all messages that pass through our node. +- DONE use the same 'recentmessages' array used for broadcast msgs to detect duplicate retransmitted messages. +- DONE in the router receive path?, send an ack packet if want_ack was set and we are the final destination. FIXME, for now don't handle multihop or merging of data replies with these acks. +- DONE keep a list of packets waiting for acks +- DONE for each message keep a count of # retries (max of three). Local to the node, only for the most immediate hop, ignorant of multihop routing. +- DONE delay some random time for each retry (large enough to allow for acks to come in) +- DONE once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender +- DONE after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service) +- DONE test one hop ack/nak with the python framework +- Do stress test with acks + +dsr tasks + +- Don't use broadcasts for the network pings (close open github issue) +- add ignoreSenders to radioconfig to allow testing different mesh topologies by refusing to see certain senders +- test multihop delivery with the python framework + +optimizations / low priority: + +- low priority: think more careful about reliable retransmit intervals +- make ReliableRouter.pending threadsafe +- bump up PacketPool size for all the new ack/nak/routing packets +- handle 51 day rollover in doRetransmissions +- use a priority queue for the messages waiting to send. Send acks first, then routing messages, then data messages, then broadcasts? + +when we send a packet + +- do "hop by hop" routing +- when sending, if destnodeinfo.next_hop is zero (and no message is already waiting for an arp for that node), startRouteDiscovery() for that node. Queue the message in the 'waiting for arp queue' so we can send it later when then the arp completes. +- otherwise, use next_hop and start sending a message (with ack request) towards that node (starting with next_hop). + +when we receive any packet + +- sniff and update tables (especially useful to find adjacent nodes). Update user, network and position info. +- if we need to route() that packet, resend it to the next_hop based on our nodedb. +- if it is broadcast or destined for our node, deliver locally +- handle routereply/routeerror/routediscovery messages as described below +- then free it + +routeDiscovery + +- if we've already passed through us (or is from us), then it ignore it +- use the nodes already mentioned in the request to update our routing table +- if they were looking for us, send back a routereply +- NOT DOING FOR NOW -if max_hops is zero and they weren't looking for us, drop (FIXME, send back error - I think not though?) +- if we receive a discovery packet, and we don't have next_hop set in our nodedb, we use it to populate next_hop (if needed) towards the requester (after decrementing max_hops) +- if we receive a discovery packet, and we have a next_hop in our nodedb for that destination we send a (reliable) we send a route reply towards the requester + +when sending any reliable packet + +- if timeout doing retries, send a routeError (nak) message back towards the original requester. all nodes eavesdrop on that packet and update their route caches. + +when we receive a routereply packet + +- update next_hop on the node, if the new reply needs fewer hops than the existing one (we prefer shorter paths). fixme, someday use a better heuristic + +when we receive a routeError packet + +- delete the route for that failed recipient, restartRouteDiscovery() +- if we receive routeerror in response to a discovery, +- fixme, eventually keep caches of possible other routes. + +TODO: + +- optimize our generalized flooding with heuristics, possibly have particular nodes self mark as 'router' nodes. + +- DONE reread the radiohead mesh implementation - hop to hop acknowledgement seems VERY expensive but otherwise it seems like DSR +- DONE read about mesh routing solutions (DSR and AODV) +- DONE read about general mesh flooding solutions (naive, MPR, geo assisted) +- DONE reread the disaster radio protocol docs - seems based on Babel (which is AODVish) +- REJECTED - seems dying - possibly dash7? https://www.slideshare.net/MaartenWeyn1/dash7-alliance-protocol-technical-presentation https://github.com/MOSAIC-LoPoW/dash7-ap-open-source-stack - does the opensource stack implement multihop routing? flooding? their discussion mailing list looks dead-dead +- update duty cycle spreadsheet for our typical usecase + +a description of DSR: https://tools.ietf.org/html/rfc4728 good slides here: https://www.slideshare.net/ashrafmath/dynamic-source-routing good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf -It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728 +It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728 https://en.wikipedia.org/wiki/Dynamic_Source_Routing broadcast solution: -Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent) -Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set +Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent) +Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set compare to db sync algorithm? -what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies. +what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies. If Y were to die, at least the neighbor nodes of Y would have their last known position of Y. ## approach 1 -* send all broadcasts with a TTL -* periodically(?) do a survey to find the max TTL that is needed to fully cover the current network. -* to do a study first send a broadcast (maybe our current initial user announcement?) with TTL set to one (so therefore no one will rebroadcast our request) -* survey replies are sent unicast back to us (and intervening nodes will need to keep the route table that they have built up based on past packets) -* count the number of replies to this TTL 1 attempt. That is the number of nodes we can reach without any rebroadcasts -* repeat the study with a TTL of 2 and then 3. stop once the # of replies stops going up. -* it is important for any node to do listen before talk to prevent stomping on other rebroadcasters... -* For these little networks I bet a max TTL would never be higher than 3? +- send all broadcasts with a TTL +- periodically(?) do a survey to find the max TTL that is needed to fully cover the current network. +- to do a study first send a broadcast (maybe our current initial user announcement?) with TTL set to one (so therefore no one will rebroadcast our request) +- survey replies are sent unicast back to us (and intervening nodes will need to keep the route table that they have built up based on past packets) +- count the number of replies to this TTL 1 attempt. That is the number of nodes we can reach without any rebroadcasts +- repeat the study with a TTL of 2 and then 3. stop once the # of replies stops going up. +- it is important for any node to do listen before talk to prevent stomping on other rebroadcasters... +- For these little networks I bet a max TTL would never be higher than 3? ## approach 2 -* send a TTL1 broadcast, the replies let us build a list of the nodes (stored as a bitvector?) that we can see (and their rssis) -* we then broadcast out that bitvector (also TTL1) asking "can any of ya'll (even indirectly) see anyone else?" -* if a node can see someone I missed (and they are the best person to see that node), they reply (unidirectionally) with the missing nodes and their rssis (other nodes might sniff (and update their db) based on this reply but they don't have to) -* given that the max number of nodes in this mesh will be like 20 (for normal cases), I bet globally updating this db of "nodenums and who has the best rssi for packets from that node" would be useful -* once the global DB is shared, when a node wants to broadcast, it just sends out its broadcast . the first level receivers then make a decision "am I the best to rebroadcast to someone who likely missed this packet?" if so, rebroadcast +- send a TTL1 broadcast, the replies let us build a list of the nodes (stored as a bitvector?) that we can see (and their rssis) +- we then broadcast out that bitvector (also TTL1) asking "can any of ya'll (even indirectly) see anyone else?" +- if a node can see someone I missed (and they are the best person to see that node), they reply (unidirectionally) with the missing nodes and their rssis (other nodes might sniff (and update their db) based on this reply but they don't have to) +- given that the max number of nodes in this mesh will be like 20 (for normal cases), I bet globally updating this db of "nodenums and who has the best rssi for packets from that node" would be useful +- once the global DB is shared, when a node wants to broadcast, it just sends out its broadcast . the first level receivers then make a decision "am I the best to rebroadcast to someone who likely missed this packet?" if so, rebroadcast ## approach 3 -* when a node X wants to know other nodes positions, it broadcasts its position with want_replies=true. Then each of the nodes that received that request broadcast their replies (possibly by using special timeslots?) -* all nodes constantly update their local db based on replies they witnessed. -* after 10s (or whatever) if node Y notices that it didn't hear a reply from node Z (that Y has heard from recently ) to that initial request, that means Z never heard the request from X. Node Y will reply to X on Z's behalf. -* could this work for more than one hop? Is more than one hop needed? Could it work for sending messages (i.e. for a msg sent to Z with want-reply set). +- when a node X wants to know other nodes positions, it broadcasts its position with want_replies=true. Then each of the nodes that received that request broadcast their replies (possibly by using special timeslots?) +- all nodes constantly update their local db based on replies they witnessed. +- after 10s (or whatever) if node Y notices that it didn't hear a reply from node Z (that Y has heard from recently ) to that initial request, that means Z never heard the request from X. Node Y will reply to X on Z's behalf. +- could this work for more than one hop? Is more than one hop needed? Could it work for sending messages (i.e. for a msg sent to Z with want-reply set). ## approach 4 look into the literature for this idea specifically. -* don't view it as a mesh protocol as much as a "distributed db unification problem". When nodes talk to nearby nodes they work together -to update their nodedbs. Each nodedb would have a last change date and any new changes that only one node has would get passed to the -other node. This would nicely allow distant nodes to propogate their position to all other nodes (eventually). -* handle group messages the same way, there would be a table of messages and time of creation. -* when a node has a new position or message to send out, it does a broadcast. All the adjacent nodes update their db instantly (this handles 90% of messages I'll bet). -* Occasionally a node might broadcast saying "anyone have anything newer than time X?" If someone does, they send the diffs since that date. -* essentially everything in this variant becomes broadcasts of "request db updates for >time X - for _all_ or for a particular nodenum" and nodes sending (either due to request or because they changed state) "here's a set of db updates". Every node is constantly trying to -build the most recent version of reality, and if some nodes are too far, then nodes closer in will eventually forward their changes to the distributed db. -* construct non ambigious rules for who broadcasts to request db updates. ideally the algorithm should nicely realize node X can see most other nodes, so they should just listen to all those nodes and minimize the # of broadcasts. the distributed picture of nodes rssi could be useful here? -* possibly view the BLE protocol to the radio the same way - just a process of reconverging the node/msgdb database. +- don't view it as a mesh protocol as much as a "distributed db unification problem". When nodes talk to nearby nodes they work together + to update their nodedbs. Each nodedb would have a last change date and any new changes that only one node has would get passed to the + other node. This would nicely allow distant nodes to propogate their position to all other nodes (eventually). +- handle group messages the same way, there would be a table of messages and time of creation. +- when a node has a new position or message to send out, it does a broadcast. All the adjacent nodes update their db instantly (this handles 90% of messages I'll bet). +- Occasionally a node might broadcast saying "anyone have anything newer than time X?" If someone does, they send the diffs since that date. +- essentially everything in this variant becomes broadcasts of "request db updates for >time X - for _all_ or for a particular nodenum" and nodes sending (either due to request or because they changed state) "here's a set of db updates". Every node is constantly trying to + build the most recent version of reality, and if some nodes are too far, then nodes closer in will eventually forward their changes to the distributed db. +- construct non ambigious rules for who broadcasts to request db updates. ideally the algorithm should nicely realize node X can see most other nodes, so they should just listen to all those nodes and minimize the # of broadcasts. the distributed picture of nodes rssi could be useful here? +- possibly view the BLE protocol to the radio the same way - just a process of reconverging the node/msgdb database. + +# Old notes + +FIXME, merge into the above: + +good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept + +interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf +It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728 +https://en.wikipedia.org/wiki/Dynamic_Source_Routing + +broadcast solution: +Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent) +Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set + +compare to db sync algorithm? + +what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies. + +If Y were to die, at least the neighbor nodes of Y would have their last known position of Y. diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md new file mode 100644 index 000000000..ae89750dd --- /dev/null +++ b/docs/software/nrf52-TODO.md @@ -0,0 +1,171 @@ +# NRF52 TODO + +## Misc work items + +## Initial work items + +Minimum items needed to make sure hardware is good. + +- test my hackedup bootloader on the real hardware +- add a hard fault handler +- Use the PMU driver on real hardware +- Use new radio driver on real hardware +- Use UC1701 LCD driver on real hardware. Still need to create at startup and probe on SPI +- test the LEDs +- test the buttons + +## Secondary work items + +Needed to be fully functional at least at the same level of the ESP32 boards. At this point users would probably want them. + +- stop polling for GPS characters, instead stay blocked on read in a thread +- increase preamble length? - will break other clients? so all devices must update +- enable BLE DFU somehow +- set appversion/hwversion +- report appversion/hwversion in BLE +- use new LCD driver from screen.cpp. Still need to hook it to a subclass of (poorly named) OLEDDisplay, and override display() to stream bytes out to the screen. +- get full BLE api working +- we need to enable the external xtal for the sx1262 (on dio3) +- figure out which regulator mode the sx1262 is operating in +- turn on security for BLE, make pairing work +- make power management/sleep work properly +- make a settimeofday implementation +- make a file system implementation (preferably one that can see the files the bootloader also sees) - use https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.3.0/lib_fds_usage.html?cp=7_5_0_3_55_3 +- make ble endpoints not require "start config", just have them start in config mode +- measure power management and confirm battery life +- use new PMU to provide battery voltage/% full to app (both bluetooth and screen) +- do initial power measurements + +## Items to be 'feature complete' + +- use SX126x::startReceiveDutyCycleAuto to save power by sleeping and briefly waking to check for preamble bits. Change xmit rules to have more preamble bits. +- turn back on in-radio destaddr checking for RF95 +- figure out what the correct current limit should be for the sx1262, currently we just use the default 100 +- put sx1262 in sleepmode when processor gets shutdown (or rebooted), ideally even for critical faults (to keep power draw low). repurpose deepsleep state for this. +- good power management tips: https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/optimizing-power-on-nrf52-designs +- call PMU set_ADC_CONV(0) during sleep, to stop reading PMU adcs and decrease current draw +- do final power measurements +- backport the common PMU API between AXP192 and PmuBQ25703A +- use the new buttons in the UX +- currently using soft device SD140, is that ideal? +- turn on the watchdog timer, require servicing from key application threads +- install a hardfault handler for null ptrs (if one isn't already installed) +- nrf52setup should call randomSeed(tbd) + +## Things to do 'someday' + +Nice ideas worth considering someday... + +- Use flego to me an iOS/linux app? https://felgo.com/doc/qt/qtbluetooth-index/ or +- Use flutter to make an iOS/linux app? https://github.com/Polidea/FlutterBleLib +- make a Mfg Controller and device under test classes as examples of custom app code for third party devs. Make a post about this. Use a custom payload type code. Have device under test send a broadcast with max hopcount of 0 for the 'mfgcontroller' payload type. mfg controller will read SNR and reply. DOT will declare failure/success and switch to the regular app screen. +- Hook Segger RTT to the nordic logging framework. https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/debugging-with-real-time-terminal +- Use nordic logging for DEBUG_MSG +- use the Jumper simulator to run meshes of simulated hardware: https://docs.jumper.io/docs/install.html +- make/find a multithread safe debug logging class (include remote logging and timestamps and levels). make each log event atomic. +- turn on freertos stack size checking +- Currently we use Nordic's vendor ID, which is apparently okay: https://devzone.nordicsemi.com/f/nordic-q-a/44014/using-nordic-vid-and-pid-for-nrf52840 and I just picked a PID of 0x4403 +- Use NRF logger module (includes flash logging etc...) instead of DEBUG_MSG +- Use "LED softblink" library on NRF52 to do nice pretty "breathing" LEDs. Don't whack LED from main thread anymore. +- decrease BLE xmit power "At 0dBm with the DC/DC on, the nRF52832 transmitter draws 5.3mA. Increasing the TX power to +4dBm adds only 2.2mA. Decreasing it to -40 dBm saves only 2.6mA." +- in addition to the main CPU watchdog, use the PMU watchdog as a really big emergency hammer +- turn on 'shipping mode' in the PMU when device is 'off' - to cut battery draw to essentially zero +- make Lorro_BQ25703A read/write operations atomic, current version could let other threads sneak in (once we start using threads) +- make the segger logbuffer larger, move it to RAM that is preserved across reboots and support reading it out at runtime (to allow full log messages to be included in crash reports). Share this code with ESP32 (use gcc noinit attribute) +- convert hardfaults/panics/asserts/wd exceptions into fault codes sent to phone +- stop enumerating all i2c devices at boot, it wastes power & time +- consider using "SYSTEMOFF" deep sleep mode, without RAM retension. Only useful for 'truly off - wake only by button press' only saves 1.5uA vs SYSTEMON. (SYSTEMON only costs 1.5uA). Possibly put PMU into shipping mode? +- change the BLE protocol to be more symmetric. Have the phone _also_ host a GATT service which receives writes to + 'fromradio'. This would allow removing the 'fromnum' mailbox/notify scheme of the current approach and decrease the number of packet handoffs when a packet is received. +- Using the preceeding, make a generalized 'nrf52/esp32 ble to internet' bridge service. To let nrf52 apps do MQTT/UDP/HTTP POST/HTTP GET operations to web services. +- lower advertise interval to save power, lower ble transmit power to save power + +## Old unorganized notes + +## Notes on PCA10059 Dongle + +- docs: https://infocenter.nordicsemi.com/pdf/nRF52840_Dongle_User_Guide_v1.0.pdf + +- Currently using Nordic PCA10059 Dongle hardware +- https://community.platformio.org/t/same-bootloader-same-softdevice-different-board-different-pins/11411/9 + +## Done + +- DONE add "DFU trigger library" to application load +- DONE: using this: Possibly use this bootloader? https://github.com/adafruit/Adafruit_nRF52_Bootloader +- DONE select and install a bootloader (adafruit) +- DONE get old radio driver working on NRF52 +- DONE basic test of BLE +- DONE get a debug 'serial' console working via the ICE passthrough feature +- DONE switch to RadioLab? test it with current radio. https://github.com/jgromes/RadioLib +- DONE change rx95 to radiolib +- DONE track rxbad, rxgood, txgood +- DONE neg 7 error code from receive +- DONE remove unused sx1262 lib from github +- at boot we are starting our message IDs at 1, rather we should start them at a random number. also, seed random based on timer. this could be the cause of our first message not seen bug. +- add a NEMA based GPS driver to test GPS +- DONE use "variants" to get all gpio bindings +- DONE plug in correct variants for the real board +- turn on DFU assistance in the appload using the nordic DFU helper lib call +- make a new boarddef with a variant.h file. Fix pins in that file. In particular (at least): + #define PIN_SPI_MISO (46) + #define PIN_SPI_MOSI (45) + #define PIN_SPI_SCK (47) + #define PIN_WIRE_SDA (26) + #define PIN_WIRE_SCL (27) +- customize the bootloader to use proper button bindings +- remove the MeshRadio wrapper - we don't need it anymore, just do everything in RadioInterface subclasses. + +``` + +/* +per +https://docs.platformio.org/en/latest/tutorials/nordicnrf52/arduino_debugging_unit_testing.html + +ardunino github is here https://github.com/sandeepmistry/arduino-nRF5 +devboard hw docs here: +https://infocenter.nordicsemi.com/topic/ug_nrf52840_dk/UG/nrf52840_DK/hw_buttons_leds.html?cp=4_0_4_7_6 + +https://docs.platformio.org/en/latest/boards/nordicnrf52/nrf52840_dk_adafruit.html + +must install adafruit bootloader first! +https://learn.adafruit.com/circuitpython-on-the-nrf52/nrf52840-bootloader +see link above and turn off jlink filesystem if we see unreliable serial comms +over USBCDC + +adafruit bootloader install commands (from their readme) +kevinh@kevin-server:~/.platformio/packages/framework-arduinoadafruitnrf52/bootloader$ +nrfjprog -e -f nrf52 Erasing user available code and UICR flash areas. Applying +system reset. +kevinh@kevin-server:~/.platformio/packages/framework-arduinoadafruitnrf52/bootloader$ +nrfjprog --program pca10056/pca10056_bootloader-0.3.2_s140_6.1.1.hex -f nrf52 +Parsing hex file. +Reading flash area to program to guarantee it is erased. +Checking that the area to write is not protected. +Programming device. +kevinh@kevin-server:~/.platformio/packages/framework-arduinoadafruitnrf52/bootloader$ +nrfjprog --reset -f nrf52 Applying system reset. Run. + +install jlink tools from here: +https://www.segger.com/downloads/jlink#J-LinkSoftwareAndDocumentationPack + +install nrf tools from here: +https://www.nordicsemi.com/Software-and-tools/Development-Tools/nRF-Command-Line-Tools/Download#infotabs + +examples of turning off the loop call to save power: +https://learn.adafruit.com/bluefruit-nrf52-feather-learning-guide/advertising-beacon + +example of a more complex BLE service: +https://learn.adafruit.com/bluefruit-nrf52-feather-learning-guide/custom-hrm +*/ + +// See g_ADigitalPinMap to see how arduino maps to the real gpio#s - and all in +// P0 +#define LED1 14 +#define LED2 13 + +/* +good led ble demo: +https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/libraries/Bluefruit52Lib/examples/Peripheral/nrf_blinky/nrf_blinky.ino +*/ +``` diff --git a/docs/software/power.md b/docs/software/power.md index 0367e2f66..84c397519 100644 --- a/docs/software/power.md +++ b/docs/software/power.md @@ -4,34 +4,35 @@ i.e. sleep behavior ## Power measurements -Since one of the main goals of this project is long battery life, it is important to consider that in our software/protocol design. Based on initial measurements it seems that the current code should run about three days between charging, and with a bit more software work (see the [TODO list](TODO.md)) a battery life of eight days should be quite doable. Our current power measurements/model is in [this spreadsheet](https://docs.google.com/spreadsheets/d/1ft1bS3iXqFKU8SApU8ZLTq9r7QQEGESYnVgdtvdT67k/edit?usp=sharing). +Since one of the main goals of this project is long battery life, it is important to consider that in our software/protocol design. Based on initial measurements it seems that the current code should run about three days between charging, and with a bit more software work (see the [TODO list](TODO.md)) a battery life of eight days should be quite doable. Our current power measurements/model is in [this spreadsheet](https://docs.google.com/spreadsheets/d/1ft1bS3iXqFKU8SApU8ZLTq9r7QQEGESYnVgdtvdT67k/edit?usp=sharing). ## States From lower to higher power consumption. -* Super-deep-sleep (SDS) - everything is off, CPU, radio, bluetooth, GPS. Only wakes due to timer or button press. We enter this mode only after no radio comms for a few hours, used to put the device into what is effectively "off" mode. +- Super-deep-sleep (SDS) - everything is off, CPU, radio, bluetooth, GPS. Only wakes due to timer or button press. We enter this mode only after no radio comms for a few hours, used to put the device into what is effectively "off" mode. onEntry: setBluetoothOn(false), call doDeepSleep onExit: (standard bootup code, starts in DARK) -* deep-sleep (DS) - CPU is off, radio is on, bluetooth and GPS is off. Note: This mode is never used currently, because it only saves 1.5mA vs light-sleep +- deep-sleep (DS) - CPU is off, radio is on, bluetooth and GPS is off. Note: This mode is never used currently, because it only saves 1.5mA vs light-sleep (Not currently used) -* light-sleep (LS) - CPU is suspended (RAM stays alive), radio is on, bluetooth is off, GPS is off. Note: currently GPS is not turned -off during light sleep, but there is a TODO item to fix this. - onEntry: setBluetoothOn(false), setGPSPower(false), doLightSleep() +- light-sleep (LS) - CPU is suspended (RAM stays alive), radio is on, bluetooth is off, GPS is off. Note: currently GPS is not turned + off during light sleep, but there is a TODO item to fix this. + NOTE: On NRF52 platforms (because CPU current draw is so low), light-sleep state is never used. + onEntry: setBluetoothOn(false), setGPSPower(false), doLightSleep() onIdle: (if we wake because our led blink timer has expired) blink the led then go back to sleep until we sleep for ls_secs onExit: setGPSPower(true), start trying to get gps lock: gps.startLock(), once lock arrives service.sendPosition(BROADCAST) -* No bluetooth (NB) - CPU is running, radio is on, GPS is on but bluetooth is off, screen is off. +- No bluetooth (NB) - CPU is running, radio is on, GPS is on but bluetooth is off, screen is off. onEntry: setBluetoothOn(false) - onExit: + onExit: -* running dark (DARK) - Everything is on except screen +- running dark (DARK) - Everything is on except screen onEntry: setBluetoothOn(true) - onExit: + onExit: -* full on (ON) - Everything is on +- full on (ON) - Everything is on onEntry: setBluetoothOn(true), screen.setOn(true) onExit: screen.setOn(false) @@ -39,47 +40,47 @@ off during light sleep, but there is a TODO item to fix this. ### events that increase CPU activity -* At cold boot: The initial state (after setup() has run) is DARK -* While in DARK: if we receive EVENT_BOOT, transition to ON (and show the bootscreen). This event will be sent if we detect we woke due to reset (as opposed to deep sleep) -* While in LS: Once every position_broadcast_secs (default 15 mins) - the unit will wake into DARK mode and broadcast a "networkPing" (our position) and stay alive for wait_bluetooth_secs (default 30 seconds). This allows other nodes to have a record of our last known position if we go away and allows a paired phone to hear from us and download messages. -* While in LS: Every send_owner_interval (defaults to 4, i.e. one hour), when we wake to send our position we _also_ broadcast our owner. This lets new nodes on the network find out about us or correct duplicate node number assignments. -* While in LS/NB/DARK: If the user presses a button (EVENT_PRESS) we go to full ON mode for screen_on_secs (default 30 seconds). Multiple presses keeps resetting this timeout -* While in LS/NB/DARK: If we receive new text messages (EVENT_RECEIVED_TEXT_MSG), we go to full ON mode for screen_on_secs (same as if user pressed a button) -* While in LS: while we receive packets on the radio (EVENT_RECEIVED_PACKET) we will wake and handle them and stay awake in NB mode for min_wake_secs (default 10 seconds) -* While in NB: If we do have packets the phone (EVENT_PACKETS_FOR_PHONE) would want we transition to DARK mode for wait_bluetooth secs. -* While in DARK/ON: If we receive EVENT_BLUETOOTH_PAIR we transition to ON and start our screen_on_secs timeout -* While in NB/DARK/ON: If we receive EVENT_NODEDB_UPDATED we transition to ON (so the new screen can be shown) -* While in DARK: While the phone talks to us over BLE (EVENT_CONTACT_FROM_PHONE) reset any sleep timers and stay in DARK (needed for bluetooth sw update and nice user experience if the user is reading/replying to texts) +- At cold boot: The initial state (after setup() has run) is DARK +- While in DARK: if we receive EVENT_BOOT, transition to ON (and show the bootscreen). This event will be sent if we detect we woke due to reset (as opposed to deep sleep) +- While in LS: Once every position_broadcast_secs (default 15 mins) - the unit will wake into DARK mode and broadcast a "networkPing" (our position) and stay alive for wait_bluetooth_secs (default 30 seconds). This allows other nodes to have a record of our last known position if we go away and allows a paired phone to hear from us and download messages. +- While in LS: Every send*owner_interval (defaults to 4, i.e. one hour), when we wake to send our position we \_also* broadcast our owner. This lets new nodes on the network find out about us or correct duplicate node number assignments. +- While in LS/NB/DARK: If the user presses a button (EVENT_PRESS) we go to full ON mode for screen_on_secs (default 30 seconds). Multiple presses keeps resetting this timeout +- While in LS/NB/DARK: If we receive new text messages (EVENT_RECEIVED_TEXT_MSG), we go to full ON mode for screen_on_secs (same as if user pressed a button) +- While in LS: while we receive packets on the radio (EVENT_RECEIVED_PACKET) we will wake and handle them and stay awake in NB mode for min_wake_secs (default 10 seconds) +- While in NB: If we do have packets the phone (EVENT_PACKETS_FOR_PHONE) would want we transition to DARK mode for wait_bluetooth secs. +- While in DARK/ON: If we receive EVENT_BLUETOOTH_PAIR we transition to ON and start our screen_on_secs timeout +- While in NB/DARK/ON: If we receive EVENT_NODEDB_UPDATED we transition to ON (so the new screen can be shown) +- While in DARK: While the phone talks to us over BLE (EVENT_CONTACT_FROM_PHONE) reset any sleep timers and stay in DARK (needed for bluetooth sw update and nice user experience if the user is reading/replying to texts) ### events that decrease cpu activity -* While in ON: If PRESS event occurs, reset screen_on_secs timer and tell the screen to handle the pess -* While in ON: If it has been more than screen_on_secs since a press, lower to DARK -* While in DARK: If time since last contact by our phone exceeds phone_timeout_secs (15 minutes), we transition down into NB mode -* While in DARK or NB: If nothing above is forcing us to stay in a higher mode (wait_bluetooth_secs, min_wake_secs) we will lower down to LS state -* While in LS: If either phone_sds_timeout_secs (default 2 hr) or mesh_sds_timeout_secs (default 2 hr) are exceeded we will lower into SDS mode for sds_secs (default 1 yr) (or a button press). (Note: phone_sds_timeout_secs is currently disabled for now, because most users -are using without a phone) -* Any time we enter LS mode: We stay in that until an interrupt, button press or other state transition. Every ls_secs (default 1 hr) and let the arduino loop() run one iteration (FIXME, not sure if we need this at all), and then immediately reenter lightsleep mode on the CPU. +- While in ON: If PRESS event occurs, reset screen_on_secs timer and tell the screen to handle the pess +- While in ON: If it has been more than screen_on_secs since a press, lower to DARK +- While in DARK: If time since last contact by our phone exceeds phone_timeout_secs (15 minutes), we transition down into NB mode +- While in DARK or NB: If nothing above is forcing us to stay in a higher mode (wait_bluetooth_secs, min_wake_secs) we will lower down to LS state +- While in LS: If either phone_sds_timeout_secs (default 2 hr) or mesh_sds_timeout_secs (default 2 hr) are exceeded we will lower into SDS mode for sds_secs (default 1 yr) (or a button press). (Note: phone_sds_timeout_secs is currently disabled for now, because most users + are using without a phone) +- Any time we enter LS mode: We stay in that until an interrupt, button press or other state transition. Every ls_secs (default 1 hr) and let the arduino loop() run one iteration (FIXME, not sure if we need this at all), and then immediately reenter lightsleep mode on the CPU. TODO: Eventually these scheduled intervals should be synchronized to the GPS clock, so that we can consider leaving the lora receiver off to save even more power. TODO: In NB mode we should put cpu into light sleep any time we really aren't that busy (without declaring LS state) - i.e. we should leave GPS on etc... # Low power consumption tasks -General ideas to hit the power draws our spreadsheet predicts. Do the easy ones before beta, the last 15% can be done after 1.0. +General ideas to hit the power draws our spreadsheet predicts. Do the easy ones before beta, the last 15% can be done after 1.0. -* don't even power on the gps until someone else wants our position, just stay in lora deep sleep until press or rxpacket (except for once an hour updates) -* (possibly bad idea - better to have lora radio always listen - check spreadsheet) have every node wake at the same tick and do their position syncs then go back to deep sleep -* lower BT announce interval to save battery -* change to use RXcontinuous mode and config to drop packets with bad CRC (see section 6.4 of datasheet) - I think this is already the case -* have mesh service run in a thread that stays blocked until a packet arrives from the RF95 -* platformio sdkconfig CONFIG_PM and turn on modem sleep mode -* keep cpu 100% in deepsleep until irq from radio wakes it. Then stay awake for 30 secs to attempt delivery to phone. -* use https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/ association sleep pattern to save power - but see https://github.com/espressif/esp-idf/issues/2070 and https://esp32.com/viewtopic.php?f=13&t=12182 it seems with BLE on the 'easy' draw people are getting is 80mA -* stop using loop() instead use a job queue and let cpu sleep -* measure power consumption and calculate battery life assuming no deep sleep -* do lowest sleep level possible where BT still works during normal sleeping, make sure cpu stays in that mode unless lora rx packet happens, bt rx packet happens or button press happens -* optionally do lora messaging only during special scheduled intervals (unless nodes are told to go to low latency mode), then deep sleep except during those intervals - before implementing calculate what battery life would be with this feature -* see section 7.3 of https://cdn.sparkfun.com/assets/learn_tutorials/8/0/4/RFM95_96_97_98W.pdf and have hope radio wake only when a valid packet is received. Possibly even wake the ESP32 from deep sleep via GPIO. -* never enter deep sleep while connected to USB power (but still go to other low power modes) -* when main cpu is idle (in loop), turn cpu clock rate down and/or activate special sleep modes. We want almost everything shutdown until it gets an interrupt. +- don't even power on the gps until someone else wants our position, just stay in lora deep sleep until press or rxpacket (except for once an hour updates) +- (possibly bad idea - better to have lora radio always listen - check spreadsheet) have every node wake at the same tick and do their position syncs then go back to deep sleep +- lower BT announce interval to save battery +- change to use RXcontinuous mode and config to drop packets with bad CRC (see section 6.4 of datasheet) - I think this is already the case +- have mesh service run in a thread that stays blocked until a packet arrives from the RF95 +- platformio sdkconfig CONFIG_PM and turn on modem sleep mode +- keep cpu 100% in deepsleep until irq from radio wakes it. Then stay awake for 30 secs to attempt delivery to phone. +- use https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/ association sleep pattern to save power - but see https://github.com/espressif/esp-idf/issues/2070 and https://esp32.com/viewtopic.php?f=13&t=12182 it seems with BLE on the 'easy' draw people are getting is 80mA +- stop using loop() instead use a job queue and let cpu sleep +- measure power consumption and calculate battery life assuming no deep sleep +- do lowest sleep level possible where BT still works during normal sleeping, make sure cpu stays in that mode unless lora rx packet happens, bt rx packet happens or button press happens +- optionally do lora messaging only during special scheduled intervals (unless nodes are told to go to low latency mode), then deep sleep except during those intervals - before implementing calculate what battery life would be with this feature +- see section 7.3 of https://cdn.sparkfun.com/assets/learn_tutorials/8/0/4/RFM95_96_97_98W.pdf and have hope radio wake only when a valid packet is received. Possibly even wake the ESP32 from deep sleep via GPIO. +- never enter deep sleep while connected to USB power (but still go to other low power modes) +- when main cpu is idle (in loop), turn cpu clock rate down and/or activate special sleep modes. We want almost everything shutdown until it gets an interrupt. diff --git a/gdbinit b/gdbinit new file mode 100644 index 000000000..2af350b3a --- /dev/null +++ b/gdbinit @@ -0,0 +1,6 @@ +# the jlink debugger seems to want a pause after reset before we tell it to start running +define restart + monitor reset + shell sleep 1 + cont +end diff --git a/lib/BluetoothOTA/src/BluetoothUtil.cpp b/lib/BluetoothOTA/src/BluetoothUtil.cpp deleted file mode 100644 index 682db0b9e..000000000 --- a/lib/BluetoothOTA/src/BluetoothUtil.cpp +++ /dev/null @@ -1,312 +0,0 @@ -#include "BluetoothUtil.h" -#include "BluetoothSoftwareUpdate.h" -#include -#include -#include -#include -#include "configuration.h" - -SimpleAllocator btPool; - -/** - * Create standard device info service - **/ -BLEService *createDeviceInfomationService(BLEServer *server, std::string hwVendor, std::string swVersion, std::string hwVersion = "") -{ - BLEService *deviceInfoService = server->createService(BLEUUID((uint16_t)ESP_GATT_UUID_DEVICE_INFO_SVC)); - - BLECharacteristic *swC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ); - BLECharacteristic *mfC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_MANU_NAME), BLECharacteristic::PROPERTY_READ); - // BLECharacteristic SerialNumberCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_SERIAL_NUMBER_STR), BLECharacteristic::PROPERTY_READ); - - /* - * Mandatory characteristic for device info service? - - BLECharacteristic *m_pnpCharacteristic = m_deviceInfoService->createCharacteristic(ESP_GATT_UUID_PNP_ID, BLECharacteristic::PROPERTY_READ); - - uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version; - uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >> 8), (uint8_t) version }; - m_pnpCharacteristic->setValue(pnp, sizeof(pnp)); - */ - swC->setValue(swVersion); - deviceInfoService->addCharacteristic(addBLECharacteristic(swC)); - mfC->setValue(hwVendor); - deviceInfoService->addCharacteristic(addBLECharacteristic(mfC)); - if (!hwVersion.empty()) - { - BLECharacteristic *hwvC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ); - hwvC->setValue(hwVersion); - deviceInfoService->addCharacteristic(addBLECharacteristic(hwvC)); - } - //SerialNumberCharacteristic.setValue("FIXME"); - //deviceInfoService->addCharacteristic(&SerialNumberCharacteristic); - - // m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, BLECharacteristic::PROPERTY_READ); - // m_manufacturerCharacteristic->setValue(name); - - /* add these later? - ESP_GATT_UUID_SYSTEM_ID - */ - - // caller must call service->start(); - return deviceInfoService; -} - -bool _BLEClientConnected = false; - -class MyServerCallbacks : public BLEServerCallbacks -{ - void onConnect(BLEServer *pServer) - { - _BLEClientConnected = true; - }; - - void onDisconnect(BLEServer *pServer) - { - _BLEClientConnected = false; - } -}; - -#define MAX_DESCRIPTORS 32 -#define MAX_CHARACTERISTICS 32 - -static BLECharacteristic *chars[MAX_CHARACTERISTICS]; -static size_t numChars; -static BLEDescriptor *descs[MAX_DESCRIPTORS]; -static size_t numDescs; - -/// Add a characteristic that we will delete when we restart -BLECharacteristic *addBLECharacteristic(BLECharacteristic *c) -{ - assert(numChars < MAX_CHARACTERISTICS); - chars[numChars++] = c; - return c; -} - -/// Add a characteristic that we will delete when we restart -BLEDescriptor *addBLEDescriptor(BLEDescriptor *c) -{ - assert(numDescs < MAX_DESCRIPTORS); - descs[numDescs++] = c; - - return c; -} - -// Help routine to add a description to any BLECharacteristic and add it to the service -// We default to require an encrypted BOND for all these these characterstics -void addWithDesc(BLEService *service, BLECharacteristic *c, const char *description) -{ - c->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); - - BLEDescriptor *desc = new BLEDescriptor(BLEUUID((uint16_t)ESP_GATT_UUID_CHAR_DESCRIPTION), strlen(description) + 1); - assert(desc); - desc->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); - desc->setValue(description); - c->addDescriptor(desc); - service->addCharacteristic(c); - addBLECharacteristic(c); - addBLEDescriptor(desc); -} - -static BLECharacteristic *batteryLevelC; - -/** - * Create a battery level service - */ -BLEService *createBatteryService(BLEServer *server) -{ - // Create the BLE Service - BLEService *pBattery = server->createService(BLEUUID((uint16_t)0x180F)); - - batteryLevelC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_BATTERY_LEVEL), BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); - - addWithDesc(pBattery, batteryLevelC, "Percentage 0 - 100"); - batteryLevelC->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification - - // I don't think we need to advertise this - // server->getAdvertising()->addServiceUUID(pBattery->getUUID()); - pBattery->start(); - - return pBattery; -} - -/** - * Update the battery level we are currently telling clients. - * level should be a pct between 0 and 100 - */ -void updateBatteryLevel(uint8_t level) -{ - // Pretend to update battery levels - fixme do elsewhere - if (batteryLevelC) - { - batteryLevelC->setValue(&level, 1); - batteryLevelC->notify(); - } -} - -void dumpCharacteristic(BLECharacteristic *c) -{ - std::string value = c->getValue(); - - if (value.length() > 0) - { - DEBUG_MSG("New value: "); - for (int i = 0; i < value.length(); i++) - DEBUG_MSG("%c", value[i]); - - DEBUG_MSG("\n"); - } -} - -/** converting endianness pull out a 32 bit value */ -uint32_t getValue32(BLECharacteristic *c, uint32_t defaultValue) -{ - std::string value = c->getValue(); - uint32_t r = defaultValue; - - if (value.length() == 4) - r = value[0] | (value[1] << 8UL) | (value[2] << 16UL) | (value[3] << 24UL); - - return r; -} - -class MySecurity : public BLESecurityCallbacks -{ - protected: - bool onConfirmPIN(uint32_t pin) - { - Serial.printf("onConfirmPIN %u\n", pin); - return false; - } - - uint32_t onPassKeyRequest() - { - Serial.println("onPassKeyRequest"); - return 123511; // not used - } - - void onPassKeyNotify(uint32_t pass_key) - { - Serial.printf("onPassKeyNotify %u\n", pass_key); - startCb(pass_key); - } - - bool onSecurityRequest() - { - Serial.println("onSecurityRequest"); - return true; - } - - void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) - { - if (cmpl.success) - { - uint16_t length; - esp_ble_gap_get_whitelist_size(&length); - Serial.printf(" onAuthenticationComplete -> success size: %d\n", length); - } - else - { - Serial.printf("onAuthenticationComplete -> fail %d\n", cmpl.fail_reason); - } - - // Remove our custom PIN request screen. - stopCb(); - } - - public: - StartBluetoothPinScreenCallback startCb; - StopBluetoothPinScreenCallback stopCb; -}; - -BLEServer *pServer; - -BLEService *pDevInfo, *pUpdate; - -void deinitBLE() -{ - assert(pServer); - - pServer->getAdvertising()->stop(); - - destroyUpdateService(); - - pUpdate->stop(); - pDevInfo->stop(); - pUpdate->stop(); // we delete them below - - // First shutdown bluetooth - BLEDevice::deinit(false); - - // do not delete this - it is dynamically allocated, but only once - statically in BLEDevice - // delete pServer->getAdvertising(); - - delete pUpdate; - delete pDevInfo; - delete pServer; - - batteryLevelC = NULL; // Don't let anyone generate bogus notifies - - for (int i = 0; i < numChars; i++) - delete chars[i]; - numChars = 0; - - for (int i = 0; i < numDescs; i++) - delete descs[i]; - numDescs = 0; - - btPool.reset(); -} - -BLEServer *initBLE( - StartBluetoothPinScreenCallback startBtPinScreen, - StopBluetoothPinScreenCallback stopBtPinScreen, - std::string deviceName, std::string hwVendor, std::string swVersion, std::string hwVersion) -{ - BLEDevice::init(deviceName); - BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); - - /* - * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation - */ - static MySecurity mySecurity; - mySecurity.startCb = startBtPinScreen; - mySecurity.stopCb = stopBtPinScreen; - BLEDevice::setSecurityCallbacks(&mySecurity); - - // Create the BLE Server - pServer = BLEDevice::createServer(); - static MyServerCallbacks myCallbacks; - pServer->setCallbacks(&myCallbacks); - - pDevInfo = createDeviceInfomationService(pServer, hwVendor, swVersion, hwVersion); - - // We now let users create the battery service only if they really want (not all devices have a battery) - // BLEService *pBattery = createBatteryService(pServer); - - pUpdate = createUpdateService(pServer, hwVendor, swVersion, hwVersion); // We need to advertise this so our android ble scan operation can see it - - // It seems only one service can be advertised - so for now don't advertise our updater - // pServer->getAdvertising()->addServiceUUID(pUpdate->getUUID()); - - // start all our services (do this after creating all of them) - pDevInfo->start(); - pUpdate->start(); - - // FIXME turn on this restriction only after the device is paired with a phone - // advert->setScanFilter(false, true); // We let anyone scan for us (FIXME, perhaps only allow that until we are paired with a phone and configured) but only let whitelist phones connect - - static BLESecurity security; // static to avoid allocs - BLESecurity *pSecurity = &security; - pSecurity->setCapability(ESP_IO_CAP_OUT); - pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND); - pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); - - return pServer; -} - -// Called from loop -void loopBLE() -{ - bluetoothRebootCheck(); -} diff --git a/lib/nanopb/src/pb.h b/lib/nanopb/include/pb.h similarity index 100% rename from lib/nanopb/src/pb.h rename to lib/nanopb/include/pb.h diff --git a/lib/nanopb/src/pb_common.h b/lib/nanopb/include/pb_common.h similarity index 100% rename from lib/nanopb/src/pb_common.h rename to lib/nanopb/include/pb_common.h diff --git a/lib/nanopb/src/pb_decode.h b/lib/nanopb/include/pb_decode.h similarity index 100% rename from lib/nanopb/src/pb_decode.h rename to lib/nanopb/include/pb_decode.h diff --git a/lib/nanopb/src/pb_encode.h b/lib/nanopb/include/pb_encode.h similarity index 100% rename from lib/nanopb/src/pb_encode.h rename to lib/nanopb/include/pb_encode.h diff --git a/lib/segger_rtt/License.txt b/lib/segger_rtt/License.txt new file mode 100644 index 000000000..bc83e48a1 --- /dev/null +++ b/lib/segger_rtt/License.txt @@ -0,0 +1,34 @@ +Important - Read carefully: + +SEGGER RTT - Real Time Transfer for embedded targets + +All rights reserved. + +SEGGER strongly recommends to not make any changes +to or modify the source code of this software in order to stay +compatible with the RTT protocol and J-Link. + +Redistribution and use in source and binary forms, with or +without modification, are permitted provided that the following +condition is met: + +o Redistributions of source code must retain the above copyright + notice, this condition and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + + +(c) 2014 - 2016 SEGGER Microcontroller GmbH +www.segger.com diff --git a/lib/segger_rtt/README.txt b/lib/segger_rtt/README.txt new file mode 100644 index 000000000..4dd23eba2 --- /dev/null +++ b/lib/segger_rtt/README.txt @@ -0,0 +1,20 @@ +README.txt for the SEGGER RTT Implementation Pack. + +Included files: +=============== +Root Directory + - Examples + - Main_RTT_InputEchoApp.c - Sample application which echoes input on Channel 0. + - Main_RTT_MenuApp.c - Sample application to demonstrate RTT bi-directional functionality. + - Main_RTT_PrintfTest.c - Sample application to test RTT small printf implementation. + - Main_RTT_SpeedTestApp.c - Sample application for measuring RTT performance. embOS needed. + - RTT + - SEGGER_RTT.c - The RTT implementation. + - SEGGER_RTT.h - Header for RTT implementation. + - SEGGER_RTT_Conf.h - Pre-processor configuration for the RTT implementation. + - SEGGER_RTT_Printf.c - Simple implementation of printf to write formatted strings via RTT. + - Syscalls + - RTT_Syscalls_GCC.c - Low-level syscalls to retarget printf() to RTT with GCC / Newlib. + - RTT_Syscalls_IAR.c - Low-level syscalls to retarget printf() to RTT with IAR compiler. + - RTT_Syscalls_KEIL.c - Low-level syscalls to retarget printf() to RTT with KEIL/uVision compiler. + - RTT_Syscalls_SES.c - Low-level syscalls to retarget printf() to RTT with SEGGER Embedded Studio. diff --git a/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_GCC.c b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_GCC.c new file mode 100644 index 000000000..32dab72bf --- /dev/null +++ b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_GCC.c @@ -0,0 +1,121 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2019 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** +* * +* SEGGER RTT * Real Time Transfer for embedded targets * +* * +********************************************************************** +* * +* All rights reserved. * +* * +* SEGGER strongly recommends to not make any changes * +* to or modify the source code of this software in order to stay * +* compatible with the RTT protocol and J-Link. * +* * +* Redistribution and use in source and binary forms, with or * +* without modification, are permitted provided that the following * +* condition is met: * +* * +* o Redistributions of source code must retain the above copyright * +* notice, this condition and the following disclaimer. * +* * +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * +* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * +* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * +* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * +* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * +* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * +* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * +* DAMAGE. * +* * +********************************************************************** +---------------------------END-OF-HEADER------------------------------ +File : SEGGER_RTT_Syscalls_GCC.c +Purpose : Low-level functions for using printf() via RTT in GCC. + To use RTT for printf output, include this file in your + application. +Revision: $Rev: 17697 $ +---------------------------------------------------------------------- +*/ +#if (defined __GNUC__) && !(defined __SES_ARM) && !(defined __CROSSWORKS_ARM) + +#include "SEGGER_RTT.h" +#include // required for _write_r + +/********************************************************************* + * + * Types + * + ********************************************************************** + */ +// +// If necessary define the _reent struct +// to match the one passed by the used standard library. +// +struct _reent; + +/********************************************************************* + * + * Function prototypes + * + ********************************************************************** + */ +int _write(int file, char *ptr, int len); +// _ssize_t _write_r(struct _reent *r, int file, const void *ptr, int len); + +/********************************************************************* + * + * Global functions + * + ********************************************************************** + */ + +/********************************************************************* + * + * _write() + * + * Function description + * Low-level write function. + * libc subroutines will use this system routine for output to all files, + * including stdout. + * Write data via RTT. + */ +int _write(int file, char *ptr, int len) +{ + (void)file; /* Not used, avoid warning */ + SEGGER_RTT_Write(0, ptr, len); + return len; +} + +/********************************************************************* + * + * _write_r() + * + * Function description + * Low-level reentrant write function. + * libc subroutines will use this system routine for output to all files, + * including stdout. + * Write data via RTT. + */ +_ssize_t _write_r(struct _reent *r, int file, const void *ptr, int len) +{ + (void)file; /* Not used, avoid warning */ + (void)r; /* Not used, avoid warning */ + SEGGER_RTT_Write(0, ptr, len); + return len; +} + +#endif +/****** End Of File *************************************************/ diff --git a/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_IAR.c b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_IAR.c new file mode 100644 index 000000000..11a968941 --- /dev/null +++ b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_IAR.c @@ -0,0 +1,115 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2019 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** +* * +* SEGGER RTT * Real Time Transfer for embedded targets * +* * +********************************************************************** +* * +* All rights reserved. * +* * +* SEGGER strongly recommends to not make any changes * +* to or modify the source code of this software in order to stay * +* compatible with the RTT protocol and J-Link. * +* * +* Redistribution and use in source and binary forms, with or * +* without modification, are permitted provided that the following * +* condition is met: * +* * +* o Redistributions of source code must retain the above copyright * +* notice, this condition and the following disclaimer. * +* * +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * +* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * +* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * +* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * +* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * +* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * +* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * +* DAMAGE. * +* * +********************************************************************** +---------------------------END-OF-HEADER------------------------------ +File : SEGGER_RTT_Syscalls_IAR.c +Purpose : Low-level functions for using printf() via RTT in IAR. + To use RTT for printf output, include this file in your + application and set the Library Configuration to Normal. +Revision: $Rev: 17697 $ +---------------------------------------------------------------------- +*/ +#ifdef __IAR_SYSTEMS_ICC__ + +// +// Since IAR EWARM V8 and EWRX V4, yfuns.h is considered as deprecated and LowLevelIOInterface.h +// shall be used instead. To not break any compatibility with older compiler versions, we have a +// version check in here. +// +#if ((defined __ICCARM__) && (__VER__ >= 8000000)) || ((defined __ICCRX__) && (__VER__ >= 400)) + #include +#else + #include +#endif + +#include "SEGGER_RTT.h" +#pragma module_name = "?__write" + +/********************************************************************* +* +* Function prototypes +* +********************************************************************** +*/ +size_t __write(int handle, const unsigned char * buffer, size_t size); + +/********************************************************************* +* +* Global functions +* +********************************************************************** +*/ +/********************************************************************* +* +* __write() +* +* Function description +* Low-level write function. +* Standard library subroutines will use this system routine +* for output to all files, including stdout. +* Write data via RTT. +*/ +size_t __write(int handle, const unsigned char * buffer, size_t size) { + (void) handle; /* Not used, avoid warning */ + SEGGER_RTT_Write(0, (const char*)buffer, size); + return size; +} + +/********************************************************************* +* +* __write_buffered() +* +* Function description +* Low-level write function. +* Standard library subroutines will use this system routine +* for output to all files, including stdout. +* Write data via RTT. +*/ +size_t __write_buffered(int handle, const unsigned char * buffer, size_t size) { + (void) handle; /* Not used, avoid warning */ + SEGGER_RTT_Write(0, (const char*)buffer, size); + return size; +} + +#endif +/****** End Of File *************************************************/ diff --git a/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_KEIL.c b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_KEIL.c new file mode 100644 index 000000000..f84bc1a19 --- /dev/null +++ b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_KEIL.c @@ -0,0 +1,386 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2019 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** +* * +* SEGGER RTT * Real Time Transfer for embedded targets * +* * +********************************************************************** +* * +* All rights reserved. * +* * +* SEGGER strongly recommends to not make any changes * +* to or modify the source code of this software in order to stay * +* compatible with the RTT protocol and J-Link. * +* * +* Redistribution and use in source and binary forms, with or * +* without modification, are permitted provided that the following * +* condition is met: * +* * +* o Redistributions of source code must retain the above copyright * +* notice, this condition and the following disclaimer. * +* * +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * +* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * +* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * +* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * +* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * +* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * +* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * +* DAMAGE. * +* * +********************************************************************** +---------------------------END-OF-HEADER------------------------------ +File : RTT_Syscalls_KEIL.c +Purpose : Retargeting module for KEIL MDK-CM3. + Low-level functions for using printf() via RTT +Revision: $Rev: 17697 $ +---------------------------------------------------------------------- +*/ +#ifdef __CC_ARM + +#include +#include +#include +#include +#include + +#include "SEGGER_RTT.h" +/********************************************************************* +* +* #pragmas +* +********************************************************************** +*/ +#pragma import(__use_no_semihosting) + +#ifdef _MICROLIB + #pragma import(__use_full_stdio) +#endif + +/********************************************************************* +* +* Defines non-configurable +* +********************************************************************** +*/ + +/* Standard IO device handles - arbitrary, but any real file system handles must be + less than 0x8000. */ +#define STDIN 0x8001 // Standard Input Stream +#define STDOUT 0x8002 // Standard Output Stream +#define STDERR 0x8003 // Standard Error Stream + +/********************************************************************* +* +* Public const +* +********************************************************************** +*/ +#if __ARMCC_VERSION < 5000000 +//const char __stdin_name[] = "STDIN"; +const char __stdout_name[] = "STDOUT"; +const char __stderr_name[] = "STDERR"; +#endif + +/********************************************************************* +* +* Public code +* +********************************************************************** +*/ + +/********************************************************************* +* +* _ttywrch +* +* Function description: +* Outputs a character to the console +* +* Parameters: +* c - character to output +* +*/ +void _ttywrch(int c) { + fputc(c, stdout); // stdout + fflush(stdout); +} + +/********************************************************************* +* +* _sys_open +* +* Function description: +* Opens the device/file in order to do read/write operations +* +* Parameters: +* sName - sName of the device/file to open +* OpenMode - This parameter is currently ignored +* +* Return value: +* != 0 - Handle to the object to open, otherwise +* == 0 -"device" is not handled by this module +* +*/ +FILEHANDLE _sys_open(const char * sName, int OpenMode) { + (void)OpenMode; + // Register standard Input Output devices. + if (strcmp(sName, __stdout_name) == 0) { + return (STDOUT); + } else if (strcmp(sName, __stderr_name) == 0) { + return (STDERR); + } else + return (0); // Not implemented +} + +/********************************************************************* +* +* _sys_close +* +* Function description: +* Closes the handle to the open device/file +* +* Parameters: +* hFile - Handle to a file opened via _sys_open +* +* Return value: +* 0 - device/file closed +* +*/ +int _sys_close(FILEHANDLE hFile) { + (void)hFile; + return 0; // Not implemented +} + +/********************************************************************* +* +* _sys_write +* +* Function description: +* Writes the data to an open handle. +* Currently this function only outputs data to the console +* +* Parameters: +* hFile - Handle to a file opened via _sys_open +* pBuffer - Pointer to the data that shall be written +* NumBytes - Number of bytes to write +* Mode - The Mode that shall be used +* +* Return value: +* Number of bytes *not* written to the file/device +* +*/ +int _sys_write(FILEHANDLE hFile, const unsigned char * pBuffer, unsigned NumBytes, int Mode) { + int r = 0; + + (void)Mode; + if (hFile == STDOUT) { + SEGGER_RTT_Write(0, (const char*)pBuffer, NumBytes); + return 0; + } + return r; +} + +/********************************************************************* +* +* _sys_read +* +* Function description: +* Reads data from an open handle. +* Currently this modules does nothing. +* +* Parameters: +* hFile - Handle to a file opened via _sys_open +* pBuffer - Pointer to buffer to store the read data +* NumBytes - Number of bytes to read +* Mode - The Mode that shall be used +* +* Return value: +* Number of bytes read from the file/device +* +*/ +int _sys_read(FILEHANDLE hFile, unsigned char * pBuffer, unsigned NumBytes, int Mode) { + (void)hFile; + (void)pBuffer; + (void)NumBytes; + (void)Mode; + return (0); // Not implemented +} + +/********************************************************************* +* +* _sys_istty +* +* Function description: +* This function shall return whether the opened file +* is a console device or not. +* +* Parameters: +* hFile - Handle to a file opened via _sys_open +* +* Return value: +* 1 - Device is a console +* 0 - Device is not a console +* +*/ +int _sys_istty(FILEHANDLE hFile) { + if (hFile > 0x8000) { + return (1); + } + return (0); // Not implemented +} + +/********************************************************************* +* +* _sys_seek +* +* Function description: +* Seeks via the file to a specific position +* +* Parameters: +* hFile - Handle to a file opened via _sys_open +* Pos - +* +* Return value: +* int - +* +*/ +int _sys_seek(FILEHANDLE hFile, long Pos) { + (void)hFile; + (void)Pos; + return (0); // Not implemented +} + +/********************************************************************* +* +* _sys_ensure +* +* Function description: +* +* +* Parameters: +* hFile - Handle to a file opened via _sys_open +* +* Return value: +* int - +* +*/ +int _sys_ensure(FILEHANDLE hFile) { + (void)hFile; + return (-1); // Not implemented +} + +/********************************************************************* +* +* _sys_flen +* +* Function description: +* Returns the length of the opened file handle +* +* Parameters: +* hFile - Handle to a file opened via _sys_open +* +* Return value: +* Length of the file +* +*/ +long _sys_flen(FILEHANDLE hFile) { + (void)hFile; + return (0); // Not implemented +} + +/********************************************************************* +* +* _sys_tmpnam +* +* Function description: +* This function converts the file number fileno for a temporary +* file to a unique filename, for example, tmp0001. +* +* Parameters: +* pBuffer - Pointer to a buffer to store the name +* FileNum - file number to convert +* MaxLen - Size of the buffer +* +* Return value: +* 1 - Error +* 0 - Success +* +*/ +int _sys_tmpnam(char * pBuffer, int FileNum, unsigned MaxLen) { + (void)pBuffer; + (void)FileNum; + (void)MaxLen; + return (1); // Not implemented +} + +/********************************************************************* +* +* _sys_command_string +* +* Function description: +* This function shall execute a system command. +* +* Parameters: +* cmd - Pointer to the command string +* len - Length of the string +* +* Return value: +* == NULL - Command was not successfully executed +* == sCmd - Command was passed successfully +* +*/ +char * _sys_command_string(char * cmd, int len) { + (void)len; + return cmd; // Not implemented +} + +/********************************************************************* +* +* _sys_exit +* +* Function description: +* This function is called when the application returns from main +* +* Parameters: +* ReturnCode - Return code from the main function +* +* +*/ +void _sys_exit(int ReturnCode) { + (void)ReturnCode; + while (1); // Not implemented +} + +#if __ARMCC_VERSION >= 5000000 +/********************************************************************* +* +* stdout_putchar +* +* Function description: +* Put a character to the stdout +* +* Parameters: +* ch - Character to output +* +* +*/ +int stdout_putchar(int ch) { + (void)ch; + return ch; // Not implemented +} +#endif + +#endif +/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_SES.c b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_SES.c new file mode 100644 index 000000000..3744f01fd --- /dev/null +++ b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_SES.c @@ -0,0 +1,247 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2019 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** +* * +* SEGGER RTT * Real Time Transfer for embedded targets * +* * +********************************************************************** +* * +* All rights reserved. * +* * +* SEGGER strongly recommends to not make any changes * +* to or modify the source code of this software in order to stay * +* compatible with the RTT protocol and J-Link. * +* * +* Redistribution and use in source and binary forms, with or * +* without modification, are permitted provided that the following * +* condition is met: * +* * +* o Redistributions of source code must retain the above copyright * +* notice, this condition and the following disclaimer. * +* * +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * +* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * +* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * +* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * +* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * +* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * +* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * +* DAMAGE. * +* * +********************************************************************** +---------------------------END-OF-HEADER------------------------------ +File : SEGGER_RTT_Syscalls_SES.c +Purpose : Reimplementation of printf, puts and __getchar using RTT + in SEGGER Embedded Studio. + To use RTT for printf output, include this file in your + application. +Revision: $Rev: 18539 $ +---------------------------------------------------------------------- +*/ +#if (defined __SES_ARM) || (defined __SES_RISCV) || (defined __CROSSWORKS_ARM) + +#include "SEGGER_RTT.h" +#include +#include +#include "limits.h" +#include "__libc.h" +#include "__vfprintf.h" + +/********************************************************************* +* +* Defines, configurable +* +********************************************************************** +*/ +// +// Select string formatting implementation. +// +// RTT printf formatting +// - Configurable stack usage. (SEGGER_RTT_PRINTF_BUFFER_SIZE in SEGGER_RTT_Conf.h) +// - No maximum string length. +// - Limited conversion specifiers and flags. (See SEGGER_RTT_printf.c) +// Standard library printf formatting +// - Configurable formatting capabilities. +// - Full conversion specifier and flag support. +// - Maximum string length has to be known or (slightly) slower character-wise output. +// +// #define PRINTF_USE_SEGGER_RTT_FORMATTING 0 // Use standard library formatting +// #define PRINTF_USE_SEGGER_RTT_FORMATTING 1 // Use RTT formatting +// +#ifndef PRINTF_USE_SEGGER_RTT_FORMATTING + #define PRINTF_USE_SEGGER_RTT_FORMATTING 0 +#endif +// +// If using standard library formatting, +// select maximum output string buffer size or character-wise output. +// +// #define PRINTF_BUFFER_SIZE 0 // Use character-wise output +// #define PRINTF_BUFFER_SIZE 128 // Default maximum string length +// +#ifndef PRINTF_BUFFER_SIZE + #define PRINTF_BUFFER_SIZE 128 +#endif + +#if PRINTF_USE_SEGGER_RTT_FORMATTING // Use SEGGER RTT formatting implementation +/********************************************************************* +* +* Function prototypes +* +********************************************************************** +*/ +int SEGGER_RTT_vprintf(unsigned BufferIndex, const char * sFormat, va_list * pParamList); + +/********************************************************************* +* +* Global functions, printf +* +********************************************************************** +*/ +/********************************************************************* +* +* printf() +* +* Function description +* print a formatted string using RTT and SEGGER RTT formatting. +*/ +int printf(const char *fmt,...) { + int n; + va_list args; + + va_start (args, fmt); + n = SEGGER_RTT_vprintf(0, fmt, &args); + va_end(args); + return n; +} + +#elif PRINTF_BUFFER_SIZE == 0 // Use standard library formatting with character-wise output + +/********************************************************************* +* +* Static functions +* +********************************************************************** +*/ +static int _putchar(int x, __printf_tag_ptr ctx) { + (void)ctx; + SEGGER_RTT_Write(0, (char *)&x, 1); + return x; +} + +/********************************************************************* +* +* Global functions, printf +* +********************************************************************** +*/ +/********************************************************************* +* +* printf() +* +* Function description +* print a formatted string character-wise, using RTT and standard +* library formatting. +*/ +int printf(const char *fmt, ...) { + int n; + va_list args; + __printf_t iod; + + va_start(args, fmt); + iod.string = 0; + iod.maxchars = INT_MAX; + iod.output_fn = _putchar; + SEGGER_RTT_LOCK(); + n = __vfprintf(&iod, fmt, args); + SEGGER_RTT_UNLOCK(); + va_end(args); + return n; +} + +#else // Use standard library formatting with static buffer + +/********************************************************************* +* +* Global functions, printf +* +********************************************************************** +*/ +/********************************************************************* +* +* printf() +* +* Function description +* print a formatted string using RTT and standard library formatting. +*/ +int printf(const char *fmt,...) { + int n; + char aBuffer[PRINTF_BUFFER_SIZE]; + va_list args; + + va_start (args, fmt); + n = vsnprintf(aBuffer, sizeof(aBuffer), fmt, args); + if (n > (int)sizeof(aBuffer)) { + SEGGER_RTT_Write(0, aBuffer, sizeof(aBuffer)); + } else if (n > 0) { + SEGGER_RTT_Write(0, aBuffer, n); + } + va_end(args); + return n; +} +#endif + +/********************************************************************* +* +* Global functions +* +********************************************************************** +*/ +/********************************************************************* +* +* puts() +* +* Function description +* print a string using RTT. +*/ +int puts(const char *s) { + return SEGGER_RTT_WriteString(0, s); +} + +/********************************************************************* +* +* __putchar() +* +* Function description +* Write one character via RTT. +*/ +int __putchar(int x, __printf_tag_ptr ctx) { + (void)ctx; + SEGGER_RTT_Write(0, (char *)&x, 1); + return x; +} + +/********************************************************************* +* +* __getchar() +* +* Function description +* Wait for and get a character via RTT. +*/ +int __getchar() { + return SEGGER_RTT_WaitKey(); +} + +#endif +/****** End Of File *************************************************/ diff --git a/lib/segger_rtt/examples/Main_RTT_InputEchoApp.c b/lib/segger_rtt/examples/Main_RTT_InputEchoApp.c new file mode 100644 index 000000000..156169283 --- /dev/null +++ b/lib/segger_rtt/examples/Main_RTT_InputEchoApp.c @@ -0,0 +1,43 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* Solutions for real time microcontroller applications * +********************************************************************** +* * +* (c) 1995 - 2018 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** + +--------- END-OF-HEADER -------------------------------------------- +File : Main_RTT_MenuApp.c +Purpose : Sample application to demonstrate RTT bi-directional functionality +*/ + +#define MAIN_C + +#include + +#include "SEGGER_RTT.h" + +volatile int _Cnt; +volatile int _Delay; + +static char r; + +/********************************************************************* +* +* main +*/ +void main(void) { + + SEGGER_RTT_WriteString(0, "SEGGER Real-Time-Terminal Sample\r\n"); + SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP); + do { + r = SEGGER_RTT_WaitKey(); + SEGGER_RTT_Write(0, &r, 1); + r++; + } while (1); +} + +/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/examples/Main_RTT_MenuApp.c b/lib/segger_rtt/examples/Main_RTT_MenuApp.c new file mode 100644 index 000000000..c6a277ac1 --- /dev/null +++ b/lib/segger_rtt/examples/Main_RTT_MenuApp.c @@ -0,0 +1,70 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* Solutions for real time microcontroller applications * +********************************************************************** +* * +* (c) 1995 - 2018 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** +--------- END-OF-HEADER -------------------------------------------- +File : Main_RTT_MenuApp.c +Purpose : Sample application to demonstrate RTT bi-directional functionality +*/ + +#define MAIN_C + +#include + +#include "SEGGER_RTT.h" + +volatile int _Cnt; +volatile int _Delay; + +/********************************************************************* +* +* main +*/ +void main(void) { + int r; + int CancelOp; + + do { + _Cnt = 0; + + SEGGER_RTT_WriteString(0, "SEGGER Real-Time-Terminal Sample\r\n"); + SEGGER_RTT_WriteString(0, "Press <1> to continue in blocking mode (Application waits if necessary, no data lost)\r\n"); + SEGGER_RTT_WriteString(0, "Press <2> to continue in non-blocking mode (Application does not wait, data lost if fifo full)\r\n"); + do { + r = SEGGER_RTT_WaitKey(); + } while ((r != '1') && (r != '2')); + if (r == '1') { + SEGGER_RTT_WriteString(0, "\r\nSelected <1>. Configuring RTT and starting...\r\n"); + SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL); + } else { + SEGGER_RTT_WriteString(0, "\r\nSelected <2>. Configuring RTT and starting...\r\n"); + SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP); + } + CancelOp = 0; + do { + //for (_Delay = 0; _Delay < 10000; _Delay++); + SEGGER_RTT_printf(0, "Count: %d. Press to get back to menu.\r\n", _Cnt++); + r = SEGGER_RTT_HasKey(); + if (r) { + CancelOp = (SEGGER_RTT_GetKey() == ' ') ? 1 : 0; + } + // + // Check if user selected to cancel the current operation + // + if (CancelOp) { + SEGGER_RTT_WriteString(0, "Operation cancelled, going back to menu...\r\n"); + break; + } + } while (1); + SEGGER_RTT_GetKey(); + SEGGER_RTT_WriteString(0, "\r\n"); + } while (1); +} + +/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/examples/Main_RTT_PrintfTest.c b/lib/segger_rtt/examples/Main_RTT_PrintfTest.c new file mode 100644 index 000000000..de81b15db --- /dev/null +++ b/lib/segger_rtt/examples/Main_RTT_PrintfTest.c @@ -0,0 +1,118 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* Solutions for real time microcontroller applications * +********************************************************************** +* * +* (c) 1995 - 2018 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** + +--------- END-OF-HEADER -------------------------------------------- +File : Main_RTT_MenuApp.c +Purpose : Sample application to demonstrate RTT bi-directional functionality +*/ + +#define MAIN_C + +#include + +#include "SEGGER_RTT.h" + +volatile int _Cnt; + +/********************************************************************* +* +* main +*/ +void main(void) { + + SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL); + + SEGGER_RTT_WriteString(0, "SEGGER Real-Time-Terminal Sample\r\n\r\n"); + SEGGER_RTT_WriteString(0, "###### Testing SEGGER_printf() ######\r\n"); + + SEGGER_RTT_printf(0, "printf Test: %%c, 'S' : %c.\r\n", 'S'); + SEGGER_RTT_printf(0, "printf Test: %%5c, 'E' : %5c.\r\n", 'E'); + SEGGER_RTT_printf(0, "printf Test: %%-5c, 'G' : %-5c.\r\n", 'G'); + SEGGER_RTT_printf(0, "printf Test: %%5.3c, 'G' : %-5c.\r\n", 'G'); + SEGGER_RTT_printf(0, "printf Test: %%.3c, 'E' : %-5c.\r\n", 'E'); + SEGGER_RTT_printf(0, "printf Test: %%c, 'R' : %c.\r\n", 'R'); + + SEGGER_RTT_printf(0, "printf Test: %%s, \"RTT\" : %s.\r\n", "RTT"); + SEGGER_RTT_printf(0, "printf Test: %%s, \"RTT\\r\\nRocks.\" : %s.\r\n", "RTT\r\nRocks."); + + SEGGER_RTT_printf(0, "printf Test: %%u, 12345 : %u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%+u, 12345 : %+u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%.3u, 12345 : %.3u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%.6u, 12345 : %.6u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%6.3u, 12345 : %6.3u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%8.6u, 12345 : %8.6u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%08u, 12345 : %08u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%08.6u, 12345 : %08.6u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%0u, 12345 : %0u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%-.6u, 12345 : %-.6u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%-6.3u, 12345 : %-6.3u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%-8.6u, 12345 : %-8.6u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%-08u, 12345 : %-08u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%-08.6u, 12345 : %-08.6u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%-0u, 12345 : %-0u.\r\n", 12345); + + SEGGER_RTT_printf(0, "printf Test: %%u, -12345 : %u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%+u, -12345 : %+u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%.3u, -12345 : %.3u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%.6u, -12345 : %.6u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%6.3u, -12345 : %6.3u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%8.6u, -12345 : %8.6u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%08u, -12345 : %08u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%08.6u, -12345 : %08.6u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%0u, -12345 : %0u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-.6u, -12345 : %-.6u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-6.3u, -12345 : %-6.3u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-8.6u, -12345 : %-8.6u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-08u, -12345 : %-08u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-08.6u, -12345 : %-08.6u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-0u, -12345 : %-0u.\r\n", -12345); + + SEGGER_RTT_printf(0, "printf Test: %%d, -12345 : %d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%+d, -12345 : %+d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%.3d, -12345 : %.3d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%.6d, -12345 : %.6d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%6.3d, -12345 : %6.3d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%8.6d, -12345 : %8.6d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%08d, -12345 : %08d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%08.6d, -12345 : %08.6d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%0d, -12345 : %0d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-.6d, -12345 : %-.6d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-6.3d, -12345 : %-6.3d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-8.6d, -12345 : %-8.6d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-08d, -12345 : %-08d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-08.6d, -12345 : %-08.6d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-0d, -12345 : %-0d.\r\n", -12345); + + SEGGER_RTT_printf(0, "printf Test: %%x, 0x1234ABC : %x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%+x, 0x1234ABC : %+x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%.3x, 0x1234ABC : %.3x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%.6x, 0x1234ABC : %.6x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%6.3x, 0x1234ABC : %6.3x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%8.6x, 0x1234ABC : %8.6x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%08x, 0x1234ABC : %08x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%08.6x, 0x1234ABC : %08.6x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%0x, 0x1234ABC : %0x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%-.6x, 0x1234ABC : %-.6x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%-6.3x, 0x1234ABC : %-6.3x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%-8.6x, 0x1234ABC : %-8.6x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%-08x, 0x1234ABC : %-08x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%-08.6x, 0x1234ABC : %-08.6x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%-0x, 0x1234ABC : %-0x.\r\n", 0x1234ABC); + + SEGGER_RTT_printf(0, "printf Test: %%p, &_Cnt : %p.\r\n", &_Cnt); + + SEGGER_RTT_WriteString(0, "###### SEGGER_printf() Tests done. ######\r\n"); + do { + _Cnt++; + } while (1); +} + +/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/examples/Main_RTT_SpeedTestApp.c b/lib/segger_rtt/examples/Main_RTT_SpeedTestApp.c new file mode 100644 index 000000000..304b16f4f --- /dev/null +++ b/lib/segger_rtt/examples/Main_RTT_SpeedTestApp.c @@ -0,0 +1,69 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* Solutions for real time microcontroller applications * +********************************************************************** +* * +* (c) 1995 - 2018 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** + +--------- END-OF-HEADER -------------------------------------------- +File : Main_RTT_SpeedTestApp.c +Purpose : Sample program for measuring RTT performance. +*/ + +#include "RTOS.h" +#include "BSP.h" + +#include "SEGGER_RTT.h" +#include + +OS_STACKPTR int StackHP[128], StackLP[128]; /* Task stacks */ +OS_TASK TCBHP, TCBLP; /* Task-control-blocks */ + +static void HPTask(void) { + while (1) { + // + // Measure time needed for RTT output + // Perform dummy write with 0 characters, so we know the overhead of toggling LEDs and RTT in general + // +// Set BP here. Then start sampling on scope + BSP_ClrLED(0); + SEGGER_RTT_Write(0, 0, 0); + BSP_SetLED(0); + BSP_ClrLED(0); + SEGGER_RTT_Write(0, "01234567890123456789012345678901234567890123456789012345678901234567890123456789\r\n", 82); + BSP_SetLED(0); +// Set BP here. Then stop sampling on scope + OS_Delay(200); + } +} + +static void LPTask(void) { + while (1) { + BSP_ToggleLED(1); + OS_Delay (500); + } +} + +/********************************************************************* +* +* main +* +*********************************************************************/ + +int main(void) { + OS_IncDI(); /* Initially disable interrupts */ + OS_InitKern(); /* Initialize OS */ + OS_InitHW(); /* Initialize Hardware for OS */ + BSP_Init(); /* Initialize LED ports */ + BSP_SetLED(0); + /* You need to create at least one task before calling OS_Start() */ + OS_CREATETASK(&TCBHP, "HP Task", HPTask, 100, StackHP); + OS_CREATETASK(&TCBLP, "LP Task", LPTask, 50, StackLP); + OS_Start(); /* Start multitasking */ + return 0; +} + diff --git a/lib/segger_rtt/include/SEGGER_RTT.h b/lib/segger_rtt/include/SEGGER_RTT.h new file mode 100644 index 000000000..0945e347e --- /dev/null +++ b/lib/segger_rtt/include/SEGGER_RTT.h @@ -0,0 +1,321 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2019 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** +* * +* SEGGER RTT * Real Time Transfer for embedded targets * +* * +********************************************************************** +* * +* All rights reserved. * +* * +* SEGGER strongly recommends to not make any changes * +* to or modify the source code of this software in order to stay * +* compatible with the RTT protocol and J-Link. * +* * +* Redistribution and use in source and binary forms, with or * +* without modification, are permitted provided that the following * +* condition is met: * +* * +* o Redistributions of source code must retain the above copyright * +* notice, this condition and the following disclaimer. * +* * +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * +* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * +* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * +* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * +* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * +* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * +* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * +* DAMAGE. * +* * +********************************************************************** +---------------------------END-OF-HEADER------------------------------ +File : SEGGER_RTT.h +Purpose : Implementation of SEGGER real-time transfer which allows + real-time communication on targets which support debugger + memory accesses while the CPU is running. +Revision: $Rev: 17697 $ +---------------------------------------------------------------------- +*/ + +#ifndef SEGGER_RTT_H +#define SEGGER_RTT_H + +#include "SEGGER_RTT_Conf.h" + + + +/********************************************************************* +* +* Defines, defaults +* +********************************************************************** +*/ +#ifndef RTT_USE_ASM + #if (defined __SES_ARM) // SEGGER Embedded Studio + #define _CC_HAS_RTT_ASM_SUPPORT 1 + #elif (defined __CROSSWORKS_ARM) // Rowley Crossworks + #define _CC_HAS_RTT_ASM_SUPPORT 1 + #elif (defined __GNUC__) // GCC + #define _CC_HAS_RTT_ASM_SUPPORT 1 + #elif (defined __clang__) // Clang compiler + #define _CC_HAS_RTT_ASM_SUPPORT 1 + #elif (defined __IASMARM__) // IAR assembler + #define _CC_HAS_RTT_ASM_SUPPORT 1 + #elif (defined __ICCARM__) // IAR compiler + #define _CC_HAS_RTT_ASM_SUPPORT 1 + #else + #define _CC_HAS_RTT_ASM_SUPPORT 0 + #endif + #if (defined __ARM_ARCH_7M__) // Cortex-M3/4 + #define _CORE_HAS_RTT_ASM_SUPPORT 1 + #elif (defined __ARM_ARCH_7EM__) // Cortex-M7 + #define _CORE_HAS_RTT_ASM_SUPPORT 1 + #elif (defined __ARM_ARCH_8M_MAIN__) // Cortex-M33 + #define _CORE_HAS_RTT_ASM_SUPPORT 1 + #elif (defined __ARM7M__) // IAR Cortex-M3/4 + #if (__CORE__ == __ARM7M__) + #define _CORE_HAS_RTT_ASM_SUPPORT 1 + #else + #define _CORE_HAS_RTT_ASM_SUPPORT 0 + #endif + #elif (defined __ARM7EM__) // IAR Cortex-M7 + #if (__CORE__ == __ARM7EM__) + #define _CORE_HAS_RTT_ASM_SUPPORT 1 + #else + #define _CORE_HAS_RTT_ASM_SUPPORT 0 + #endif + #else + #define _CORE_HAS_RTT_ASM_SUPPORT 0 + #endif + // + // If IDE and core support the ASM version, enable ASM version by default + // + #if (_CC_HAS_RTT_ASM_SUPPORT && _CORE_HAS_RTT_ASM_SUPPORT) + #define RTT_USE_ASM (1) + #else + #define RTT_USE_ASM (0) + #endif +#endif + +#ifndef SEGGER_RTT_ASM // defined when SEGGER_RTT.h is included from assembly file +#include +#include + +/********************************************************************* +* +* Defines, fixed +* +********************************************************************** +*/ + +/********************************************************************* +* +* Types +* +********************************************************************** +*/ + +// +// Description for a circular buffer (also called "ring buffer") +// which is used as up-buffer (T->H) +// +typedef struct { + const char* sName; // Optional name. Standard names so far are: "Terminal", "SysView", "J-Scope_t4i4" + char* pBuffer; // Pointer to start of buffer + unsigned SizeOfBuffer; // Buffer size in bytes. Note that one byte is lost, as this implementation does not fill up the buffer in order to avoid the problem of being unable to distinguish between full and empty. + unsigned WrOff; // Position of next item to be written by either target. + volatile unsigned RdOff; // Position of next item to be read by host. Must be volatile since it may be modified by host. + unsigned Flags; // Contains configuration flags +} SEGGER_RTT_BUFFER_UP; + +// +// Description for a circular buffer (also called "ring buffer") +// which is used as down-buffer (H->T) +// +typedef struct { + const char* sName; // Optional name. Standard names so far are: "Terminal", "SysView", "J-Scope_t4i4" + char* pBuffer; // Pointer to start of buffer + unsigned SizeOfBuffer; // Buffer size in bytes. Note that one byte is lost, as this implementation does not fill up the buffer in order to avoid the problem of being unable to distinguish between full and empty. + volatile unsigned WrOff; // Position of next item to be written by host. Must be volatile since it may be modified by host. + unsigned RdOff; // Position of next item to be read by target (down-buffer). + unsigned Flags; // Contains configuration flags +} SEGGER_RTT_BUFFER_DOWN; + +// +// RTT control block which describes the number of buffers available +// as well as the configuration for each buffer +// +// +typedef struct { + char acID[16]; // Initialized to "SEGGER RTT" + int MaxNumUpBuffers; // Initialized to SEGGER_RTT_MAX_NUM_UP_BUFFERS (type. 2) + int MaxNumDownBuffers; // Initialized to SEGGER_RTT_MAX_NUM_DOWN_BUFFERS (type. 2) + SEGGER_RTT_BUFFER_UP aUp[SEGGER_RTT_MAX_NUM_UP_BUFFERS]; // Up buffers, transferring information up from target via debug probe to host + SEGGER_RTT_BUFFER_DOWN aDown[SEGGER_RTT_MAX_NUM_DOWN_BUFFERS]; // Down buffers, transferring information down from host via debug probe to target +} SEGGER_RTT_CB; + +/********************************************************************* +* +* Global data +* +********************************************************************** +*/ +extern SEGGER_RTT_CB _SEGGER_RTT; + +/********************************************************************* +* +* RTT API functions +* +********************************************************************** +*/ +#ifdef __cplusplus + extern "C" { +#endif +int SEGGER_RTT_AllocDownBuffer (const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags); +int SEGGER_RTT_AllocUpBuffer (const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags); +int SEGGER_RTT_ConfigUpBuffer (unsigned BufferIndex, const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags); +int SEGGER_RTT_ConfigDownBuffer (unsigned BufferIndex, const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags); +int SEGGER_RTT_GetKey (void); +unsigned SEGGER_RTT_HasData (unsigned BufferIndex); +int SEGGER_RTT_HasKey (void); +unsigned SEGGER_RTT_HasDataUp (unsigned BufferIndex); +void SEGGER_RTT_Init (void); +unsigned SEGGER_RTT_Read (unsigned BufferIndex, void* pBuffer, unsigned BufferSize); +unsigned SEGGER_RTT_ReadNoLock (unsigned BufferIndex, void* pData, unsigned BufferSize); +int SEGGER_RTT_SetNameDownBuffer (unsigned BufferIndex, const char* sName); +int SEGGER_RTT_SetNameUpBuffer (unsigned BufferIndex, const char* sName); +int SEGGER_RTT_SetFlagsDownBuffer (unsigned BufferIndex, unsigned Flags); +int SEGGER_RTT_SetFlagsUpBuffer (unsigned BufferIndex, unsigned Flags); +int SEGGER_RTT_WaitKey (void); +unsigned SEGGER_RTT_Write (unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); +unsigned SEGGER_RTT_WriteNoLock (unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); +unsigned SEGGER_RTT_WriteSkipNoLock (unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); +unsigned SEGGER_RTT_ASM_WriteSkipNoLock (unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); +unsigned SEGGER_RTT_WriteString (unsigned BufferIndex, const char* s); +void SEGGER_RTT_WriteWithOverwriteNoLock(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); +unsigned SEGGER_RTT_PutChar (unsigned BufferIndex, char c); +unsigned SEGGER_RTT_PutCharSkip (unsigned BufferIndex, char c); +unsigned SEGGER_RTT_PutCharSkipNoLock (unsigned BufferIndex, char c); +unsigned SEGGER_RTT_GetAvailWriteSpace (unsigned BufferIndex); +unsigned SEGGER_RTT_GetBytesInBuffer (unsigned BufferIndex); +// +// Function macro for performance optimization +// +#define SEGGER_RTT_HASDATA(n) (_SEGGER_RTT.aDown[n].WrOff - _SEGGER_RTT.aDown[n].RdOff) + +#if RTT_USE_ASM + #define SEGGER_RTT_WriteSkipNoLock SEGGER_RTT_ASM_WriteSkipNoLock +#endif + +/********************************************************************* +* +* RTT transfer functions to send RTT data via other channels. +* +********************************************************************** +*/ +unsigned SEGGER_RTT_ReadUpBuffer (unsigned BufferIndex, void* pBuffer, unsigned BufferSize); +unsigned SEGGER_RTT_ReadUpBufferNoLock (unsigned BufferIndex, void* pData, unsigned BufferSize); +unsigned SEGGER_RTT_WriteDownBuffer (unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); +unsigned SEGGER_RTT_WriteDownBufferNoLock (unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); + +#define SEGGER_RTT_HASDATA_UP(n) (_SEGGER_RTT.aUp[n].WrOff - _SEGGER_RTT.aUp[n].RdOff) + +/********************************************************************* +* +* RTT "Terminal" API functions +* +********************************************************************** +*/ +int SEGGER_RTT_SetTerminal (unsigned char TerminalId); +int SEGGER_RTT_TerminalOut (unsigned char TerminalId, const char* s); + +/********************************************************************* +* +* RTT printf functions (require SEGGER_RTT_printf.c) +* +********************************************************************** +*/ +int SEGGER_RTT_printf(unsigned BufferIndex, const char * sFormat, ...); +int SEGGER_RTT_vprintf(unsigned BufferIndex, const char * sFormat, va_list * pParamList); + +#ifdef __cplusplus + } +#endif + +#endif // ifndef(SEGGER_RTT_ASM) + +/********************************************************************* +* +* Defines +* +********************************************************************** +*/ + +// +// Operating modes. Define behavior if buffer is full (not enough space for entire message) +// +#define SEGGER_RTT_MODE_NO_BLOCK_SKIP (0) // Skip. Do not block, output nothing. (Default) +#define SEGGER_RTT_MODE_NO_BLOCK_TRIM (1) // Trim: Do not block, output as much as fits. +#define SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL (2) // Block: Wait until there is space in the buffer. +#define SEGGER_RTT_MODE_MASK (3) + +// +// Control sequences, based on ANSI. +// Can be used to control color, and clear the screen +// +#define RTT_CTRL_RESET "\x1B[0m" // Reset to default colors +#define RTT_CTRL_CLEAR "\x1B[2J" // Clear screen, reposition cursor to top left + +#define RTT_CTRL_TEXT_BLACK "\x1B[2;30m" +#define RTT_CTRL_TEXT_RED "\x1B[2;31m" +#define RTT_CTRL_TEXT_GREEN "\x1B[2;32m" +#define RTT_CTRL_TEXT_YELLOW "\x1B[2;33m" +#define RTT_CTRL_TEXT_BLUE "\x1B[2;34m" +#define RTT_CTRL_TEXT_MAGENTA "\x1B[2;35m" +#define RTT_CTRL_TEXT_CYAN "\x1B[2;36m" +#define RTT_CTRL_TEXT_WHITE "\x1B[2;37m" + +#define RTT_CTRL_TEXT_BRIGHT_BLACK "\x1B[1;30m" +#define RTT_CTRL_TEXT_BRIGHT_RED "\x1B[1;31m" +#define RTT_CTRL_TEXT_BRIGHT_GREEN "\x1B[1;32m" +#define RTT_CTRL_TEXT_BRIGHT_YELLOW "\x1B[1;33m" +#define RTT_CTRL_TEXT_BRIGHT_BLUE "\x1B[1;34m" +#define RTT_CTRL_TEXT_BRIGHT_MAGENTA "\x1B[1;35m" +#define RTT_CTRL_TEXT_BRIGHT_CYAN "\x1B[1;36m" +#define RTT_CTRL_TEXT_BRIGHT_WHITE "\x1B[1;37m" + +#define RTT_CTRL_BG_BLACK "\x1B[24;40m" +#define RTT_CTRL_BG_RED "\x1B[24;41m" +#define RTT_CTRL_BG_GREEN "\x1B[24;42m" +#define RTT_CTRL_BG_YELLOW "\x1B[24;43m" +#define RTT_CTRL_BG_BLUE "\x1B[24;44m" +#define RTT_CTRL_BG_MAGENTA "\x1B[24;45m" +#define RTT_CTRL_BG_CYAN "\x1B[24;46m" +#define RTT_CTRL_BG_WHITE "\x1B[24;47m" + +#define RTT_CTRL_BG_BRIGHT_BLACK "\x1B[4;40m" +#define RTT_CTRL_BG_BRIGHT_RED "\x1B[4;41m" +#define RTT_CTRL_BG_BRIGHT_GREEN "\x1B[4;42m" +#define RTT_CTRL_BG_BRIGHT_YELLOW "\x1B[4;43m" +#define RTT_CTRL_BG_BRIGHT_BLUE "\x1B[4;44m" +#define RTT_CTRL_BG_BRIGHT_MAGENTA "\x1B[4;45m" +#define RTT_CTRL_BG_BRIGHT_CYAN "\x1B[4;46m" +#define RTT_CTRL_BG_BRIGHT_WHITE "\x1B[4;47m" + + +#endif + +/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/include/SEGGER_RTT_Conf.h b/lib/segger_rtt/include/SEGGER_RTT_Conf.h new file mode 100644 index 000000000..a4d8dd2e0 --- /dev/null +++ b/lib/segger_rtt/include/SEGGER_RTT_Conf.h @@ -0,0 +1,384 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2019 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** +* * +* SEGGER RTT * Real Time Transfer for embedded targets * +* * +********************************************************************** +* * +* All rights reserved. * +* * +* SEGGER strongly recommends to not make any changes * +* to or modify the source code of this software in order to stay * +* compatible with the RTT protocol and J-Link. * +* * +* Redistribution and use in source and binary forms, with or * +* without modification, are permitted provided that the following * +* condition is met: * +* * +* o Redistributions of source code must retain the above copyright * +* notice, this condition and the following disclaimer. * +* * +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * +* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * +* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * +* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * +* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * +* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * +* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * +* DAMAGE. * +* * +********************************************************************** +---------------------------END-OF-HEADER------------------------------ +File : SEGGER_RTT_Conf.h +Purpose : Implementation of SEGGER real-time transfer (RTT) which + allows real-time communication on targets which support + debugger memory accesses while the CPU is running. +Revision: $Rev: 18601 $ + +*/ + +#ifndef SEGGER_RTT_CONF_H +#define SEGGER_RTT_CONF_H + +#ifdef __IAR_SYSTEMS_ICC__ + #include +#endif + +/********************************************************************* +* +* Defines, configurable +* +********************************************************************** +*/ +#ifndef SEGGER_RTT_MAX_NUM_UP_BUFFERS + #define SEGGER_RTT_MAX_NUM_UP_BUFFERS (3) // Max. number of up-buffers (T->H) available on this target (Default: 3) +#endif + +#ifndef SEGGER_RTT_MAX_NUM_DOWN_BUFFERS + #define SEGGER_RTT_MAX_NUM_DOWN_BUFFERS (3) // Max. number of down-buffers (H->T) available on this target (Default: 3) +#endif + +#ifndef BUFFER_SIZE_UP + #define BUFFER_SIZE_UP (1024) // Size of the buffer for terminal output of target, up to host (Default: 1k) +#endif + +#ifndef BUFFER_SIZE_DOWN + #define BUFFER_SIZE_DOWN (16) // Size of the buffer for terminal input to target from host (Usually keyboard input) (Default: 16) +#endif + +#ifndef SEGGER_RTT_PRINTF_BUFFER_SIZE + #define SEGGER_RTT_PRINTF_BUFFER_SIZE (64u) // Size of buffer for RTT printf to bulk-send chars via RTT (Default: 64) +#endif + +#ifndef SEGGER_RTT_MODE_DEFAULT + #define SEGGER_RTT_MODE_DEFAULT SEGGER_RTT_MODE_NO_BLOCK_SKIP // Mode for pre-initialized terminal channel (buffer 0) +#endif + +/********************************************************************* +* +* RTT memcpy configuration +* +* memcpy() is good for large amounts of data, +* but the overhead is big for small amounts, which are usually stored via RTT. +* With SEGGER_RTT_MEMCPY_USE_BYTELOOP a simple byte loop can be used instead. +* +* SEGGER_RTT_MEMCPY() can be used to replace standard memcpy() in RTT functions. +* This is may be required with memory access restrictions, +* such as on Cortex-A devices with MMU. +*/ +#ifndef SEGGER_RTT_MEMCPY_USE_BYTELOOP + #define SEGGER_RTT_MEMCPY_USE_BYTELOOP 0 // 0: Use memcpy/SEGGER_RTT_MEMCPY, 1: Use a simple byte-loop +#endif +// +// Example definition of SEGGER_RTT_MEMCPY to external memcpy with GCC toolchains and Cortex-A targets +// +//#if ((defined __SES_ARM) || (defined __CROSSWORKS_ARM) || (defined __GNUC__)) && (defined (__ARM_ARCH_7A__)) +// #define SEGGER_RTT_MEMCPY(pDest, pSrc, NumBytes) SEGGER_memcpy((pDest), (pSrc), (NumBytes)) +//#endif + +// +// Target is not allowed to perform other RTT operations while string still has not been stored completely. +// Otherwise we would probably end up with a mixed string in the buffer. +// If using RTT from within interrupts, multiple tasks or multi processors, define the SEGGER_RTT_LOCK() and SEGGER_RTT_UNLOCK() function here. +// +// SEGGER_RTT_MAX_INTERRUPT_PRIORITY can be used in the sample lock routines on Cortex-M3/4. +// Make sure to mask all interrupts which can send RTT data, i.e. generate SystemView events, or cause task switches. +// When high-priority interrupts must not be masked while sending RTT data, SEGGER_RTT_MAX_INTERRUPT_PRIORITY needs to be adjusted accordingly. +// (Higher priority = lower priority number) +// Default value for embOS: 128u +// Default configuration in FreeRTOS: configMAX_SYSCALL_INTERRUPT_PRIORITY: ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) +// In case of doubt mask all interrupts: 1 << (8 - BASEPRI_PRIO_BITS) i.e. 1 << 5 when 3 bits are implemented in NVIC +// or define SEGGER_RTT_LOCK() to completely disable interrupts. +// +#ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY + #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) // Interrupt priority to lock on SEGGER_RTT_LOCK on Cortex-M3/4 (Default: 0x20) +#endif + +/********************************************************************* +* +* RTT lock configuration for SEGGER Embedded Studio, +* Rowley CrossStudio and GCC +*/ +#if ((defined(__SES_ARM) || defined(__SES_RISCV) || defined(__CROSSWORKS_ARM) || defined(__GNUC__) || defined(__clang__)) && !defined (__CC_ARM) && !defined(WIN32)) + #if (defined(__ARM_ARCH_6M__) || defined(__ARM_ARCH_8M_BASE__)) + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + __asm volatile ("mrs %0, primask \n\t" \ + "movs r1, $1 \n\t" \ + "msr primask, r1 \n\t" \ + : "=r" (LockState) \ + : \ + : "r1" \ + ); + + #define SEGGER_RTT_UNLOCK() __asm volatile ("msr primask, %0 \n\t" \ + : \ + : "r" (LockState) \ + : \ + ); \ + } + #elif (defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_8M_MAIN__)) + #ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY + #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) + #endif + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + __asm volatile ("mrs %0, basepri \n\t" \ + "mov r1, %1 \n\t" \ + "msr basepri, r1 \n\t" \ + : "=r" (LockState) \ + : "i"(SEGGER_RTT_MAX_INTERRUPT_PRIORITY) \ + : "r1" \ + ); + + #define SEGGER_RTT_UNLOCK() __asm volatile ("msr basepri, %0 \n\t" \ + : \ + : "r" (LockState) \ + : \ + ); \ + } + + #elif defined(__ARM_ARCH_7A__) + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + __asm volatile ("mrs r1, CPSR \n\t" \ + "mov %0, r1 \n\t" \ + "orr r1, r1, #0xC0 \n\t" \ + "msr CPSR_c, r1 \n\t" \ + : "=r" (LockState) \ + : \ + : "r1" \ + ); + + #define SEGGER_RTT_UNLOCK() __asm volatile ("mov r0, %0 \n\t" \ + "mrs r1, CPSR \n\t" \ + "bic r1, r1, #0xC0 \n\t" \ + "and r0, r0, #0xC0 \n\t" \ + "orr r1, r1, r0 \n\t" \ + "msr CPSR_c, r1 \n\t" \ + : \ + : "r" (LockState) \ + : "r0", "r1" \ + ); \ + } + #elif defined(__riscv) || defined(__riscv_xlen) + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + __asm volatile ("csrr %0, mstatus \n\t" \ + "csrci mstatus, 8 \n\t" \ + "andi %0, %0, 8 \n\t" \ + : "=r" (LockState) \ + : \ + : \ + ); + + #define SEGGER_RTT_UNLOCK() __asm volatile ("csrr a1, mstatus \n\t" \ + "or %0, %0, a1 \n\t" \ + "csrs mstatus, %0 \n\t" \ + : \ + : "r" (LockState) \ + : "a1" \ + ); \ + } + #else + #define SEGGER_RTT_LOCK() + #define SEGGER_RTT_UNLOCK() + #endif +#endif + +/********************************************************************* +* +* RTT lock configuration for IAR EWARM +*/ +#ifdef __ICCARM__ + #if (defined (__ARM6M__) && (__CORE__ == __ARM6M__)) || \ + (defined (__ARM8M_BASELINE__) && (__CORE__ == __ARM8M_BASELINE__)) + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + LockState = __get_PRIMASK(); \ + __set_PRIMASK(1); + + #define SEGGER_RTT_UNLOCK() __set_PRIMASK(LockState); \ + } + #elif (defined (__ARM7EM__) && (__CORE__ == __ARM7EM__)) || \ + (defined (__ARM7M__) && (__CORE__ == __ARM7M__)) || \ + (defined (__ARM8M_MAINLINE__) && (__CORE__ == __ARM8M_MAINLINE__)) || \ + (defined (__ARM8M_MAINLINE__) && (__CORE__ == __ARM8M_MAINLINE__)) + #ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY + #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) + #endif + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + LockState = __get_BASEPRI(); \ + __set_BASEPRI(SEGGER_RTT_MAX_INTERRUPT_PRIORITY); + + #define SEGGER_RTT_UNLOCK() __set_BASEPRI(LockState); \ + } + #endif +#endif + +/********************************************************************* +* +* RTT lock configuration for IAR RX +*/ +#ifdef __ICCRX__ + #define SEGGER_RTT_LOCK() { \ + unsigned long LockState; \ + LockState = __get_interrupt_state(); \ + __disable_interrupt(); + + #define SEGGER_RTT_UNLOCK() __set_interrupt_state(LockState); \ + } +#endif + +/********************************************************************* +* +* RTT lock configuration for IAR RL78 +*/ +#ifdef __ICCRL78__ + #define SEGGER_RTT_LOCK() { \ + __istate_t LockState; \ + LockState = __get_interrupt_state(); \ + __disable_interrupt(); + + #define SEGGER_RTT_UNLOCK() __set_interrupt_state(LockState); \ + } +#endif + +/********************************************************************* +* +* RTT lock configuration for KEIL ARM +*/ +#ifdef __CC_ARM + #if (defined __TARGET_ARCH_6S_M) + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + register unsigned char PRIMASK __asm( "primask"); \ + LockState = PRIMASK; \ + PRIMASK = 1u; \ + __schedule_barrier(); + + #define SEGGER_RTT_UNLOCK() PRIMASK = LockState; \ + __schedule_barrier(); \ + } + #elif (defined(__TARGET_ARCH_7_M) || defined(__TARGET_ARCH_7E_M)) + #ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY + #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) + #endif + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + register unsigned char BASEPRI __asm( "basepri"); \ + LockState = BASEPRI; \ + BASEPRI = SEGGER_RTT_MAX_INTERRUPT_PRIORITY; \ + __schedule_barrier(); + + #define SEGGER_RTT_UNLOCK() BASEPRI = LockState; \ + __schedule_barrier(); \ + } + #endif +#endif + +/********************************************************************* +* +* RTT lock configuration for TI ARM +*/ +#ifdef __TI_ARM__ + #if defined (__TI_ARM_V6M0__) + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + LockState = __get_PRIMASK(); \ + __set_PRIMASK(1); + + #define SEGGER_RTT_UNLOCK() __set_PRIMASK(LockState); \ + } + #elif (defined (__TI_ARM_V7M3__) || defined (__TI_ARM_V7M4__)) + #ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY + #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) + #endif + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + LockState = _set_interrupt_priority(SEGGER_RTT_MAX_INTERRUPT_PRIORITY); + + #define SEGGER_RTT_UNLOCK() _set_interrupt_priority(LockState); \ + } + #endif +#endif + +/********************************************************************* +* +* RTT lock configuration for CCRX +*/ +#ifdef __RX + #define SEGGER_RTT_LOCK() { \ + unsigned long LockState; \ + LockState = get_psw() & 0x010000; \ + clrpsw_i(); + + #define SEGGER_RTT_UNLOCK() set_psw(get_psw() | LockState); \ + } +#endif + +/********************************************************************* +* +* RTT lock configuration for embOS Simulation on Windows +* (Can also be used for generic RTT locking with embOS) +*/ +#if defined(WIN32) || defined(SEGGER_RTT_LOCK_EMBOS) + +void OS_SIM_EnterCriticalSection(void); +void OS_SIM_LeaveCriticalSection(void); + +#define SEGGER_RTT_LOCK() { \ + OS_SIM_EnterCriticalSection(); + +#define SEGGER_RTT_UNLOCK() OS_SIM_LeaveCriticalSection(); \ + } +#endif + +/********************************************************************* +* +* RTT lock configuration fallback +*/ +#ifndef SEGGER_RTT_LOCK + #define SEGGER_RTT_LOCK() // Lock RTT (nestable) (i.e. disable interrupts) +#endif + +#ifndef SEGGER_RTT_UNLOCK + #define SEGGER_RTT_UNLOCK() // Unlock RTT (nestable) (i.e. enable previous interrupt lock state) +#endif + +#endif +/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/src/SEGGER_RTT.c b/lib/segger_rtt/src/SEGGER_RTT.c new file mode 100644 index 000000000..74791e0ac --- /dev/null +++ b/lib/segger_rtt/src/SEGGER_RTT.c @@ -0,0 +1,2005 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2019 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** +* * +* SEGGER RTT * Real Time Transfer for embedded targets * +* * +********************************************************************** +* * +* All rights reserved. * +* * +* SEGGER strongly recommends to not make any changes * +* to or modify the source code of this software in order to stay * +* compatible with the RTT protocol and J-Link. * +* * +* Redistribution and use in source and binary forms, with or * +* without modification, are permitted provided that the following * +* condition is met: * +* * +* o Redistributions of source code must retain the above copyright * +* notice, this condition and the following disclaimer. * +* * +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * +* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * +* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * +* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * +* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * +* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * +* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * +* DAMAGE. * +* * +********************************************************************** +---------------------------END-OF-HEADER------------------------------ +File : SEGGER_RTT.c +Purpose : Implementation of SEGGER real-time transfer (RTT) which + allows real-time communication on targets which support + debugger memory accesses while the CPU is running. +Revision: $Rev: 17697 $ + +Additional information: + Type "int" is assumed to be 32-bits in size + H->T Host to target communication + T->H Target to host communication + + RTT channel 0 is always present and reserved for Terminal usage. + Name is fixed to "Terminal" + + Effective buffer size: SizeOfBuffer - 1 + + WrOff == RdOff: Buffer is empty + WrOff == (RdOff - 1): Buffer is full + WrOff > RdOff: Free space includes wrap-around + WrOff < RdOff: Used space includes wrap-around + (WrOff == (SizeOfBuffer - 1)) && (RdOff == 0): + Buffer full and wrap-around after next byte + + +---------------------------------------------------------------------- +*/ + +#include "SEGGER_RTT.h" + +#include // for memcpy + +/********************************************************************* +* +* Configuration, default values +* +********************************************************************** +*/ + +#ifndef BUFFER_SIZE_UP + #define BUFFER_SIZE_UP 1024 // Size of the buffer for terminal output of target, up to host +#endif + +#ifndef BUFFER_SIZE_DOWN + #define BUFFER_SIZE_DOWN 16 // Size of the buffer for terminal input to target from host (Usually keyboard input) +#endif + +#ifndef SEGGER_RTT_MAX_NUM_UP_BUFFERS + #define SEGGER_RTT_MAX_NUM_UP_BUFFERS 2 // Number of up-buffers (T->H) available on this target +#endif + +#ifndef SEGGER_RTT_MAX_NUM_DOWN_BUFFERS + #define SEGGER_RTT_MAX_NUM_DOWN_BUFFERS 2 // Number of down-buffers (H->T) available on this target +#endif + +#ifndef SEGGER_RTT_BUFFER_SECTION + #if defined(SEGGER_RTT_SECTION) + #define SEGGER_RTT_BUFFER_SECTION SEGGER_RTT_SECTION + #endif +#endif + +#ifndef SEGGER_RTT_ALIGNMENT + #define SEGGER_RTT_ALIGNMENT 0 +#endif + +#ifndef SEGGER_RTT_BUFFER_ALIGNMENT + #define SEGGER_RTT_BUFFER_ALIGNMENT 0 +#endif + +#ifndef SEGGER_RTT_MODE_DEFAULT + #define SEGGER_RTT_MODE_DEFAULT SEGGER_RTT_MODE_NO_BLOCK_SKIP +#endif + +#ifndef SEGGER_RTT_LOCK + #define SEGGER_RTT_LOCK() +#endif + +#ifndef SEGGER_RTT_UNLOCK + #define SEGGER_RTT_UNLOCK() +#endif + +#ifndef STRLEN + #define STRLEN(a) strlen((a)) +#endif + +#ifndef STRCPY + #define STRCPY(pDest, pSrc, NumBytes) strcpy((pDest), (pSrc)) +#endif + +#ifndef SEGGER_RTT_MEMCPY_USE_BYTELOOP + #define SEGGER_RTT_MEMCPY_USE_BYTELOOP 0 +#endif + +#ifndef SEGGER_RTT_MEMCPY + #ifdef MEMCPY + #define SEGGER_RTT_MEMCPY(pDest, pSrc, NumBytes) MEMCPY((pDest), (pSrc), (NumBytes)) + #else + #define SEGGER_RTT_MEMCPY(pDest, pSrc, NumBytes) memcpy((pDest), (pSrc), (NumBytes)) + #endif +#endif + +#ifndef MIN + #define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef MAX + #define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif +// +// For some environments, NULL may not be defined until certain headers are included +// +#ifndef NULL + #define NULL 0 +#endif + +/********************************************************************* +* +* Defines, fixed +* +********************************************************************** +*/ +#if (defined __ICCARM__) || (defined __ICCRX__) + #define RTT_PRAGMA(P) _Pragma(#P) +#endif + +#if SEGGER_RTT_ALIGNMENT || SEGGER_RTT_BUFFER_ALIGNMENT + #if (defined __GNUC__) + #define SEGGER_RTT_ALIGN(Var, Alignment) Var __attribute__ ((aligned (Alignment))) + #elif (defined __ICCARM__) || (defined __ICCRX__) + #define PRAGMA(A) _Pragma(#A) +#define SEGGER_RTT_ALIGN(Var, Alignment) RTT_PRAGMA(data_alignment=Alignment) \ + Var + #elif (defined __CC_ARM) + #define SEGGER_RTT_ALIGN(Var, Alignment) Var __attribute__ ((aligned (Alignment))) + #else + #error "Alignment not supported for this compiler." + #endif +#else + #define SEGGER_RTT_ALIGN(Var, Alignment) Var +#endif + +#if defined(SEGGER_RTT_SECTION) || defined (SEGGER_RTT_BUFFER_SECTION) + #if (defined __GNUC__) + #define SEGGER_RTT_PUT_SECTION(Var, Section) __attribute__ ((section (Section))) Var + #elif (defined __ICCARM__) || (defined __ICCRX__) +#define SEGGER_RTT_PUT_SECTION(Var, Section) RTT_PRAGMA(location=Section) \ + Var + #elif (defined __CC_ARM) + #define SEGGER_RTT_PUT_SECTION(Var, Section) __attribute__ ((section (Section), zero_init)) Var + #else + #error "Section placement not supported for this compiler." + #endif +#else + #define SEGGER_RTT_PUT_SECTION(Var, Section) Var +#endif + + +#if SEGGER_RTT_ALIGNMENT + #define SEGGER_RTT_CB_ALIGN(Var) SEGGER_RTT_ALIGN(Var, SEGGER_RTT_ALIGNMENT) +#else + #define SEGGER_RTT_CB_ALIGN(Var) Var +#endif + +#if SEGGER_RTT_BUFFER_ALIGNMENT + #define SEGGER_RTT_BUFFER_ALIGN(Var) SEGGER_RTT_ALIGN(Var, SEGGER_RTT_BUFFER_ALIGNMENT) +#else + #define SEGGER_RTT_BUFFER_ALIGN(Var) Var +#endif + + +#if defined(SEGGER_RTT_SECTION) + #define SEGGER_RTT_PUT_CB_SECTION(Var) SEGGER_RTT_PUT_SECTION(Var, SEGGER_RTT_SECTION) +#else + #define SEGGER_RTT_PUT_CB_SECTION(Var) Var +#endif + +#if defined(SEGGER_RTT_BUFFER_SECTION) + #define SEGGER_RTT_PUT_BUFFER_SECTION(Var) SEGGER_RTT_PUT_SECTION(Var, SEGGER_RTT_BUFFER_SECTION) +#else + #define SEGGER_RTT_PUT_BUFFER_SECTION(Var) Var +#endif + +/********************************************************************* +* +* Static const data +* +********************************************************************** +*/ + +static unsigned char _aTerminalId[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + +/********************************************************************* +* +* Static data +* +********************************************************************** +*/ +// +// RTT Control Block and allocate buffers for channel 0 +// +SEGGER_RTT_PUT_CB_SECTION(SEGGER_RTT_CB_ALIGN(SEGGER_RTT_CB _SEGGER_RTT)); + +SEGGER_RTT_PUT_BUFFER_SECTION(SEGGER_RTT_BUFFER_ALIGN(static char _acUpBuffer [BUFFER_SIZE_UP])); +SEGGER_RTT_PUT_BUFFER_SECTION(SEGGER_RTT_BUFFER_ALIGN(static char _acDownBuffer[BUFFER_SIZE_DOWN])); + +static unsigned char _ActiveTerminal; + +/********************************************************************* +* +* Static functions +* +********************************************************************** +*/ + +/********************************************************************* +* +* _DoInit() +* +* Function description +* Initializes the control block an buffers. +* May only be called via INIT() to avoid overriding settings. +* +*/ +#define INIT() do { \ + if (_SEGGER_RTT.acID[0] == '\0') { _DoInit(); } \ + } while (0) +static void _DoInit(void) { + SEGGER_RTT_CB* p; + // + // Initialize control block + // + p = &_SEGGER_RTT; + p->MaxNumUpBuffers = SEGGER_RTT_MAX_NUM_UP_BUFFERS; + p->MaxNumDownBuffers = SEGGER_RTT_MAX_NUM_DOWN_BUFFERS; + // + // Initialize up buffer 0 + // + p->aUp[0].sName = "Terminal"; + p->aUp[0].pBuffer = _acUpBuffer; + p->aUp[0].SizeOfBuffer = sizeof(_acUpBuffer); + p->aUp[0].RdOff = 0u; + p->aUp[0].WrOff = 0u; + p->aUp[0].Flags = SEGGER_RTT_MODE_DEFAULT; + // + // Initialize down buffer 0 + // + p->aDown[0].sName = "Terminal"; + p->aDown[0].pBuffer = _acDownBuffer; + p->aDown[0].SizeOfBuffer = sizeof(_acDownBuffer); + p->aDown[0].RdOff = 0u; + p->aDown[0].WrOff = 0u; + p->aDown[0].Flags = SEGGER_RTT_MODE_DEFAULT; + // + // Finish initialization of the control block. + // Copy Id string in three steps to make sure "SEGGER RTT" is not found + // in initializer memory (usually flash) by J-Link + // + STRCPY(&p->acID[7], "RTT", 9); + STRCPY(&p->acID[0], "SEGGER", 7); + p->acID[6] = ' '; +} + +/********************************************************************* +* +* _WriteBlocking() +* +* Function description +* Stores a specified number of characters in SEGGER RTT ring buffer +* and updates the associated write pointer which is periodically +* read by the host. +* The caller is responsible for managing the write chunk sizes as +* _WriteBlocking() will block until all data has been posted successfully. +* +* Parameters +* pRing Ring buffer to post to. +* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. +* NumBytes Number of bytes to be stored in the SEGGER RTT control block. +* +* Return value +* >= 0 - Number of bytes written into buffer. +*/ +static unsigned _WriteBlocking(SEGGER_RTT_BUFFER_UP* pRing, const char* pBuffer, unsigned NumBytes) { + unsigned NumBytesToWrite; + unsigned NumBytesWritten; + unsigned RdOff; + unsigned WrOff; +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + char* pDst; +#endif + // + // Write data to buffer and handle wrap-around if necessary + // + NumBytesWritten = 0u; + WrOff = pRing->WrOff; + do { + RdOff = pRing->RdOff; // May be changed by host (debug probe) in the meantime + if (RdOff > WrOff) { + NumBytesToWrite = RdOff - WrOff - 1u; + } else { + NumBytesToWrite = pRing->SizeOfBuffer - (WrOff - RdOff + 1u); + } + NumBytesToWrite = MIN(NumBytesToWrite, (pRing->SizeOfBuffer - WrOff)); // Number of bytes that can be written until buffer wrap-around + NumBytesToWrite = MIN(NumBytesToWrite, NumBytes); +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + pDst = pRing->pBuffer + WrOff; + NumBytesWritten += NumBytesToWrite; + NumBytes -= NumBytesToWrite; + WrOff += NumBytesToWrite; + while (NumBytesToWrite--) { + *pDst++ = *pBuffer++; + }; +#else + SEGGER_RTT_MEMCPY(pRing->pBuffer + WrOff, pBuffer, NumBytesToWrite); + NumBytesWritten += NumBytesToWrite; + pBuffer += NumBytesToWrite; + NumBytes -= NumBytesToWrite; + WrOff += NumBytesToWrite; +#endif + if (WrOff == pRing->SizeOfBuffer) { + WrOff = 0u; + } + pRing->WrOff = WrOff; + } while (NumBytes); + // + return NumBytesWritten; +} + +/********************************************************************* +* +* _WriteNoCheck() +* +* Function description +* Stores a specified number of characters in SEGGER RTT ring buffer +* and updates the associated write pointer which is periodically +* read by the host. +* It is callers responsibility to make sure data actually fits in buffer. +* +* Parameters +* pRing Ring buffer to post to. +* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. +* NumBytes Number of bytes to be stored in the SEGGER RTT control block. +* +* Notes +* (1) If there might not be enough space in the "Up"-buffer, call _WriteBlocking +*/ +static void _WriteNoCheck(SEGGER_RTT_BUFFER_UP* pRing, const char* pData, unsigned NumBytes) { + unsigned NumBytesAtOnce; + unsigned WrOff; + unsigned Rem; +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + char* pDst; +#endif + + WrOff = pRing->WrOff; + Rem = pRing->SizeOfBuffer - WrOff; + if (Rem > NumBytes) { + // + // All data fits before wrap around + // +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + pDst = pRing->pBuffer + WrOff; + WrOff += NumBytes; + while (NumBytes--) { + *pDst++ = *pData++; + }; + pRing->WrOff = WrOff; +#else + SEGGER_RTT_MEMCPY(pRing->pBuffer + WrOff, pData, NumBytes); + pRing->WrOff = WrOff + NumBytes; +#endif + } else { + // + // We reach the end of the buffer, so need to wrap around + // +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + pDst = pRing->pBuffer + WrOff; + NumBytesAtOnce = Rem; + while (NumBytesAtOnce--) { + *pDst++ = *pData++; + }; + pDst = pRing->pBuffer; + NumBytesAtOnce = NumBytes - Rem; + while (NumBytesAtOnce--) { + *pDst++ = *pData++; + }; + pRing->WrOff = NumBytes - Rem; +#else + NumBytesAtOnce = Rem; + SEGGER_RTT_MEMCPY(pRing->pBuffer + WrOff, pData, NumBytesAtOnce); + NumBytesAtOnce = NumBytes - Rem; + SEGGER_RTT_MEMCPY(pRing->pBuffer, pData + Rem, NumBytesAtOnce); + pRing->WrOff = NumBytesAtOnce; +#endif + } +} + +/********************************************************************* +* +* _PostTerminalSwitch() +* +* Function description +* Switch terminal to the given terminal ID. It is the caller's +* responsibility to ensure the terminal ID is correct and there is +* enough space in the buffer for this to complete successfully. +* +* Parameters +* pRing Ring buffer to post to. +* TerminalId Terminal ID to switch to. +*/ +static void _PostTerminalSwitch(SEGGER_RTT_BUFFER_UP* pRing, unsigned char TerminalId) { + unsigned char ac[2]; + + ac[0] = 0xFFu; + ac[1] = _aTerminalId[TerminalId]; // Caller made already sure that TerminalId does not exceed our terminal limit + _WriteBlocking(pRing, (const char*)ac, 2u); +} + +/********************************************************************* +* +* _GetAvailWriteSpace() +* +* Function description +* Returns the number of bytes that can be written to the ring +* buffer without blocking. +* +* Parameters +* pRing Ring buffer to check. +* +* Return value +* Number of bytes that are free in the buffer. +*/ +static unsigned _GetAvailWriteSpace(SEGGER_RTT_BUFFER_UP* pRing) { + unsigned RdOff; + unsigned WrOff; + unsigned r; + // + // Avoid warnings regarding volatile access order. It's not a problem + // in this case, but dampen compiler enthusiasm. + // + RdOff = pRing->RdOff; + WrOff = pRing->WrOff; + if (RdOff <= WrOff) { + r = pRing->SizeOfBuffer - 1u - WrOff + RdOff; + } else { + r = RdOff - WrOff - 1u; + } + return r; +} + +/********************************************************************* +* +* Public code +* +********************************************************************** +*/ +/********************************************************************* +* +* SEGGER_RTT_ReadUpBufferNoLock() +* +* Function description +* Reads characters from SEGGER real-time-terminal control block +* which have been previously stored by the application. +* Do not lock against interrupts and multiple access. +* Used to do the same operation that J-Link does, to transfer +* RTT data via other channels, such as TCP/IP or UART. +* +* Parameters +* BufferIndex Index of Up-buffer to be used. +* pBuffer Pointer to buffer provided by target application, to copy characters from RTT-up-buffer to. +* BufferSize Size of the target application buffer. +* +* Return value +* Number of bytes that have been read. +* +* Additional information +* This function must not be called when J-Link might also do RTT. +*/ +unsigned SEGGER_RTT_ReadUpBufferNoLock(unsigned BufferIndex, void* pData, unsigned BufferSize) { + unsigned NumBytesRem; + unsigned NumBytesRead; + unsigned RdOff; + unsigned WrOff; + unsigned char* pBuffer; + SEGGER_RTT_BUFFER_UP* pRing; +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + const char* pSrc; +#endif + // + INIT(); + pRing = &_SEGGER_RTT.aUp[BufferIndex]; + pBuffer = (unsigned char*)pData; + RdOff = pRing->RdOff; + WrOff = pRing->WrOff; + NumBytesRead = 0u; + // + // Read from current read position to wrap-around of buffer, first + // + if (RdOff > WrOff) { + NumBytesRem = pRing->SizeOfBuffer - RdOff; + NumBytesRem = MIN(NumBytesRem, BufferSize); +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + pSrc = pRing->pBuffer + RdOff; + NumBytesRead += NumBytesRem; + BufferSize -= NumBytesRem; + RdOff += NumBytesRem; + while (NumBytesRem--) { + *pBuffer++ = *pSrc++; + }; +#else + SEGGER_RTT_MEMCPY(pBuffer, pRing->pBuffer + RdOff, NumBytesRem); + NumBytesRead += NumBytesRem; + pBuffer += NumBytesRem; + BufferSize -= NumBytesRem; + RdOff += NumBytesRem; +#endif + // + // Handle wrap-around of buffer + // + if (RdOff == pRing->SizeOfBuffer) { + RdOff = 0u; + } + } + // + // Read remaining items of buffer + // + NumBytesRem = WrOff - RdOff; + NumBytesRem = MIN(NumBytesRem, BufferSize); + if (NumBytesRem > 0u) { +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + pSrc = pRing->pBuffer + RdOff; + NumBytesRead += NumBytesRem; + BufferSize -= NumBytesRem; + RdOff += NumBytesRem; + while (NumBytesRem--) { + *pBuffer++ = *pSrc++; + }; +#else + SEGGER_RTT_MEMCPY(pBuffer, pRing->pBuffer + RdOff, NumBytesRem); + NumBytesRead += NumBytesRem; + pBuffer += NumBytesRem; + BufferSize -= NumBytesRem; + RdOff += NumBytesRem; +#endif + } + // + // Update read offset of buffer + // + if (NumBytesRead) { + pRing->RdOff = RdOff; + } + // + return NumBytesRead; +} + +/********************************************************************* +* +* SEGGER_RTT_ReadNoLock() +* +* Function description +* Reads characters from SEGGER real-time-terminal control block +* which have been previously stored by the host. +* Do not lock against interrupts and multiple access. +* +* Parameters +* BufferIndex Index of Down-buffer to be used (e.g. 0 for "Terminal"). +* pBuffer Pointer to buffer provided by target application, to copy characters from RTT-down-buffer to. +* BufferSize Size of the target application buffer. +* +* Return value +* Number of bytes that have been read. +*/ +unsigned SEGGER_RTT_ReadNoLock(unsigned BufferIndex, void* pData, unsigned BufferSize) { + unsigned NumBytesRem; + unsigned NumBytesRead; + unsigned RdOff; + unsigned WrOff; + unsigned char* pBuffer; + SEGGER_RTT_BUFFER_DOWN* pRing; +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + const char* pSrc; +#endif + // + INIT(); + pRing = &_SEGGER_RTT.aDown[BufferIndex]; + pBuffer = (unsigned char*)pData; + RdOff = pRing->RdOff; + WrOff = pRing->WrOff; + NumBytesRead = 0u; + // + // Read from current read position to wrap-around of buffer, first + // + if (RdOff > WrOff) { + NumBytesRem = pRing->SizeOfBuffer - RdOff; + NumBytesRem = MIN(NumBytesRem, BufferSize); +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + pSrc = pRing->pBuffer + RdOff; + NumBytesRead += NumBytesRem; + BufferSize -= NumBytesRem; + RdOff += NumBytesRem; + while (NumBytesRem--) { + *pBuffer++ = *pSrc++; + }; +#else + SEGGER_RTT_MEMCPY(pBuffer, pRing->pBuffer + RdOff, NumBytesRem); + NumBytesRead += NumBytesRem; + pBuffer += NumBytesRem; + BufferSize -= NumBytesRem; + RdOff += NumBytesRem; +#endif + // + // Handle wrap-around of buffer + // + if (RdOff == pRing->SizeOfBuffer) { + RdOff = 0u; + } + } + // + // Read remaining items of buffer + // + NumBytesRem = WrOff - RdOff; + NumBytesRem = MIN(NumBytesRem, BufferSize); + if (NumBytesRem > 0u) { +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + pSrc = pRing->pBuffer + RdOff; + NumBytesRead += NumBytesRem; + BufferSize -= NumBytesRem; + RdOff += NumBytesRem; + while (NumBytesRem--) { + *pBuffer++ = *pSrc++; + }; +#else + SEGGER_RTT_MEMCPY(pBuffer, pRing->pBuffer + RdOff, NumBytesRem); + NumBytesRead += NumBytesRem; + pBuffer += NumBytesRem; + BufferSize -= NumBytesRem; + RdOff += NumBytesRem; +#endif + } + if (NumBytesRead) { + pRing->RdOff = RdOff; + } + // + return NumBytesRead; +} + +/********************************************************************* +* +* SEGGER_RTT_ReadUpBuffer +* +* Function description +* Reads characters from SEGGER real-time-terminal control block +* which have been previously stored by the application. +* Used to do the same operation that J-Link does, to transfer +* RTT data via other channels, such as TCP/IP or UART. +* +* Parameters +* BufferIndex Index of Up-buffer to be used. +* pBuffer Pointer to buffer provided by target application, to copy characters from RTT-up-buffer to. +* BufferSize Size of the target application buffer. +* +* Return value +* Number of bytes that have been read. +* +* Additional information +* This function must not be called when J-Link might also do RTT. +* This function locks against all other RTT operations. I.e. during +* the read operation, writing is also locked. +* If only one consumer reads from the up buffer, +* call sEGGER_RTT_ReadUpBufferNoLock() instead. +*/ +unsigned SEGGER_RTT_ReadUpBuffer(unsigned BufferIndex, void* pBuffer, unsigned BufferSize) { + unsigned NumBytesRead; + // + SEGGER_RTT_LOCK(); + // + // Call the non-locking read function + // + NumBytesRead = SEGGER_RTT_ReadUpBufferNoLock(BufferIndex, pBuffer, BufferSize); + // + // Finish up. + // + SEGGER_RTT_UNLOCK(); + // + return NumBytesRead; +} + +/********************************************************************* +* +* SEGGER_RTT_Read +* +* Function description +* Reads characters from SEGGER real-time-terminal control block +* which have been previously stored by the host. +* +* Parameters +* BufferIndex Index of Down-buffer to be used (e.g. 0 for "Terminal"). +* pBuffer Pointer to buffer provided by target application, to copy characters from RTT-down-buffer to. +* BufferSize Size of the target application buffer. +* +* Return value +* Number of bytes that have been read. +*/ +unsigned SEGGER_RTT_Read(unsigned BufferIndex, void* pBuffer, unsigned BufferSize) { + unsigned NumBytesRead; + // + SEGGER_RTT_LOCK(); + // + // Call the non-locking read function + // + NumBytesRead = SEGGER_RTT_ReadNoLock(BufferIndex, pBuffer, BufferSize); + // + // Finish up. + // + SEGGER_RTT_UNLOCK(); + // + return NumBytesRead; +} + +/********************************************************************* +* +* SEGGER_RTT_WriteWithOverwriteNoLock +* +* Function description +* Stores a specified number of characters in SEGGER RTT +* control block. +* SEGGER_RTT_WriteWithOverwriteNoLock does not lock the application +* and overwrites data if the data does not fit into the buffer. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. +* NumBytes Number of bytes to be stored in the SEGGER RTT control block. +* +* Notes +* (1) If there is not enough space in the "Up"-buffer, data is overwritten. +* (2) For performance reasons this function does not call Init() +* and may only be called after RTT has been initialized. +* Either by calling SEGGER_RTT_Init() or calling another RTT API function first. +* (3) Do not use SEGGER_RTT_WriteWithOverwriteNoLock if a J-Link +* connection reads RTT data. +*/ +void SEGGER_RTT_WriteWithOverwriteNoLock(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes) { + const char* pData; + SEGGER_RTT_BUFFER_UP* pRing; + unsigned Avail; +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + char* pDst; +#endif + + pData = (const char *)pBuffer; + // + // Get "to-host" ring buffer and copy some elements into local variables. + // + pRing = &_SEGGER_RTT.aUp[BufferIndex]; + // + // Check if we will overwrite data and need to adjust the RdOff. + // + if (pRing->WrOff == pRing->RdOff) { + Avail = pRing->SizeOfBuffer - 1u; + } else if ( pRing->WrOff < pRing->RdOff) { + Avail = pRing->RdOff - pRing->WrOff - 1u; + } else { + Avail = pRing->RdOff - pRing->WrOff - 1u + pRing->SizeOfBuffer; + } + if (NumBytes > Avail) { + pRing->RdOff += (NumBytes - Avail); + while (pRing->RdOff >= pRing->SizeOfBuffer) { + pRing->RdOff -= pRing->SizeOfBuffer; + } + } + // + // Write all data, no need to check the RdOff, but possibly handle multiple wrap-arounds + // + Avail = pRing->SizeOfBuffer - pRing->WrOff; + do { + if (Avail > NumBytes) { + // + // Last round + // +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + pDst = pRing->pBuffer + pRing->WrOff; + Avail = NumBytes; + while (NumBytes--) { + *pDst++ = *pData++; + }; + pRing->WrOff += Avail; +#else + SEGGER_RTT_MEMCPY(pRing->pBuffer + pRing->WrOff, pData, NumBytes); + pRing->WrOff += NumBytes; +#endif + break; + } else { + // + // Wrap-around necessary, write until wrap-around and reset WrOff + // +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + pDst = pRing->pBuffer + pRing->WrOff; + NumBytes -= Avail; + while (Avail--) { + *pDst++ = *pData++; + }; + pRing->WrOff = 0; +#else + SEGGER_RTT_MEMCPY(pRing->pBuffer + pRing->WrOff, pData, Avail); + pData += Avail; + pRing->WrOff = 0; + NumBytes -= Avail; +#endif + Avail = (pRing->SizeOfBuffer - 1); + } + } while (NumBytes); +} + +/********************************************************************* +* +* SEGGER_RTT_WriteSkipNoLock +* +* Function description +* Stores a specified number of characters in SEGGER RTT +* control block which is then read by the host. +* SEGGER_RTT_WriteSkipNoLock does not lock the application and +* skips all data, if the data does not fit into the buffer. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. +* NumBytes Number of bytes to be stored in the SEGGER RTT control block. +* MUST be > 0!!! +* This is done for performance reasons, so no initial check has do be done. +* +* Return value +* 1: Data has been copied +* 0: No space, data has not been copied +* +* Notes +* (1) If there is not enough space in the "Up"-buffer, all data is dropped. +* (2) For performance reasons this function does not call Init() +* and may only be called after RTT has been initialized. +* Either by calling SEGGER_RTT_Init() or calling another RTT API function first. +*/ +#if (RTT_USE_ASM == 0) +unsigned SEGGER_RTT_WriteSkipNoLock(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes) { + const char* pData; + SEGGER_RTT_BUFFER_UP* pRing; + unsigned Avail; + unsigned RdOff; + unsigned WrOff; + unsigned Rem; + // + // Cases: + // 1) RdOff <= WrOff => Space until wrap-around is sufficient + // 2) RdOff <= WrOff => Space after wrap-around needed (copy in 2 chunks) + // 3) RdOff < WrOff => No space in buf + // 4) RdOff > WrOff => Space is sufficient + // 5) RdOff > WrOff => No space in buf + // + // 1) is the most common case for large buffers and assuming that J-Link reads the data fast enough + // + pData = (const char *)pBuffer; + pRing = &_SEGGER_RTT.aUp[BufferIndex]; + RdOff = pRing->RdOff; + WrOff = pRing->WrOff; + if (RdOff <= WrOff) { // Case 1), 2) or 3) + Avail = pRing->SizeOfBuffer - WrOff - 1u; // Space until wrap-around (assume 1 byte not usable for case that RdOff == 0) + if (Avail >= NumBytes) { // Case 1)? +CopyStraight: + memcpy(pRing->pBuffer + WrOff, pData, NumBytes); + pRing->WrOff = WrOff + NumBytes; + return 1; + } + Avail += RdOff; // Space incl. wrap-around + if (Avail >= NumBytes) { // Case 2? => If not, we have case 3) (does not fit) + Rem = pRing->SizeOfBuffer - WrOff; // Space until end of buffer + memcpy(pRing->pBuffer + WrOff, pData, Rem); // Copy 1st chunk + NumBytes -= Rem; + // + // Special case: First check that assumed RdOff == 0 calculated that last element before wrap-around could not be used + // But 2nd check (considering space until wrap-around and until RdOff) revealed that RdOff is not 0, so we can use the last element + // In this case, we may use a copy straight until buffer end anyway without needing to copy 2 chunks + // Therefore, check if 2nd memcpy is necessary at all + // + if (NumBytes) { + memcpy(pRing->pBuffer, pData + Rem, NumBytes); + } + pRing->WrOff = NumBytes; + return 1; + } + } else { // Potential case 4) + Avail = RdOff - WrOff - 1u; + if (Avail >= NumBytes) { // Case 4)? => If not, we have case 5) (does not fit) + goto CopyStraight; + } + } + return 0; // No space in buffer +} +#endif + +/********************************************************************* +* +* SEGGER_RTT_WriteDownBufferNoLock +* +* Function description +* Stores a specified number of characters in SEGGER RTT +* control block inside a buffer. +* SEGGER_RTT_WriteDownBufferNoLock does not lock the application. +* Used to do the same operation that J-Link does, to transfer +* RTT data from other channels, such as TCP/IP or UART. +* +* Parameters +* BufferIndex Index of "Down"-buffer to be used. +* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. +* NumBytes Number of bytes to be stored in the SEGGER RTT control block. +* +* Return value +* Number of bytes which have been stored in the "Down"-buffer. +* +* Notes +* (1) Data is stored according to buffer flags. +* (2) For performance reasons this function does not call Init() +* and may only be called after RTT has been initialized. +* Either by calling SEGGER_RTT_Init() or calling another RTT API function first. +* +* Additional information +* This function must not be called when J-Link might also do RTT. +*/ +unsigned SEGGER_RTT_WriteDownBufferNoLock(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes) { + unsigned Status; + unsigned Avail; + const char* pData; + SEGGER_RTT_BUFFER_UP* pRing; + + pData = (const char *)pBuffer; + // + // Get "to-target" ring buffer. + // It is save to cast that to a "to-host" buffer. Up and Down buffer differ in volatility of offsets that might be modified by J-Link. + // + pRing = (SEGGER_RTT_BUFFER_UP*)&_SEGGER_RTT.aDown[BufferIndex]; + // + // How we output depends upon the mode... + // + switch (pRing->Flags) { + case SEGGER_RTT_MODE_NO_BLOCK_SKIP: + // + // If we are in skip mode and there is no space for the whole + // of this output, don't bother. + // + Avail = _GetAvailWriteSpace(pRing); + if (Avail < NumBytes) { + Status = 0u; + } else { + Status = NumBytes; + _WriteNoCheck(pRing, pData, NumBytes); + } + break; + case SEGGER_RTT_MODE_NO_BLOCK_TRIM: + // + // If we are in trim mode, trim to what we can output without blocking. + // + Avail = _GetAvailWriteSpace(pRing); + Status = Avail < NumBytes ? Avail : NumBytes; + _WriteNoCheck(pRing, pData, Status); + break; + case SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL: + // + // If we are in blocking mode, output everything. + // + Status = _WriteBlocking(pRing, pData, NumBytes); + break; + default: + Status = 0u; + break; + } + // + // Finish up. + // + return Status; +} + +/********************************************************************* +* +* SEGGER_RTT_WriteNoLock +* +* Function description +* Stores a specified number of characters in SEGGER RTT +* control block which is then read by the host. +* SEGGER_RTT_WriteNoLock does not lock the application. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. +* NumBytes Number of bytes to be stored in the SEGGER RTT control block. +* +* Return value +* Number of bytes which have been stored in the "Up"-buffer. +* +* Notes +* (1) Data is stored according to buffer flags. +* (2) For performance reasons this function does not call Init() +* and may only be called after RTT has been initialized. +* Either by calling SEGGER_RTT_Init() or calling another RTT API function first. +*/ +unsigned SEGGER_RTT_WriteNoLock(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes) { + unsigned Status; + unsigned Avail; + const char* pData; + SEGGER_RTT_BUFFER_UP* pRing; + + pData = (const char *)pBuffer; + // + // Get "to-host" ring buffer. + // + pRing = &_SEGGER_RTT.aUp[BufferIndex]; + // + // How we output depends upon the mode... + // + switch (pRing->Flags) { + case SEGGER_RTT_MODE_NO_BLOCK_SKIP: + // + // If we are in skip mode and there is no space for the whole + // of this output, don't bother. + // + Avail = _GetAvailWriteSpace(pRing); + if (Avail < NumBytes) { + Status = 0u; + } else { + Status = NumBytes; + _WriteNoCheck(pRing, pData, NumBytes); + } + break; + case SEGGER_RTT_MODE_NO_BLOCK_TRIM: + // + // If we are in trim mode, trim to what we can output without blocking. + // + Avail = _GetAvailWriteSpace(pRing); + Status = Avail < NumBytes ? Avail : NumBytes; + _WriteNoCheck(pRing, pData, Status); + break; + case SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL: + // + // If we are in blocking mode, output everything. + // + Status = _WriteBlocking(pRing, pData, NumBytes); + break; + default: + Status = 0u; + break; + } + // + // Finish up. + // + return Status; +} + +/********************************************************************* +* +* SEGGER_RTT_WriteDownBuffer +* +* Function description +* Stores a specified number of characters in SEGGER RTT control block in a buffer. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. +* NumBytes Number of bytes to be stored in the SEGGER RTT control block. +* +* Return value +* Number of bytes which have been stored in the "Down"-buffer. +* +* Notes +* (1) Data is stored according to buffer flags. +* +* Additional information +* This function must not be called when J-Link might also do RTT. +* This function locks against all other RTT operations. I.e. during +* the write operation, writing from the application is also locked. +* If only one consumer writes to the down buffer, +* call SEGGER_RTT_WriteDownBufferNoLock() instead. +*/ +unsigned SEGGER_RTT_WriteDownBuffer(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes) { + unsigned Status; + // + INIT(); + SEGGER_RTT_LOCK(); + // + // Call the non-locking write function + // + Status = SEGGER_RTT_WriteDownBufferNoLock(BufferIndex, pBuffer, NumBytes); + // + // Finish up. + // + SEGGER_RTT_UNLOCK(); + // + return Status; +} + +/********************************************************************* +* +* SEGGER_RTT_Write +* +* Function description +* Stores a specified number of characters in SEGGER RTT +* control block which is then read by the host. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. +* NumBytes Number of bytes to be stored in the SEGGER RTT control block. +* +* Return value +* Number of bytes which have been stored in the "Up"-buffer. +* +* Notes +* (1) Data is stored according to buffer flags. +*/ +unsigned SEGGER_RTT_Write(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes) { + unsigned Status; + // + INIT(); + SEGGER_RTT_LOCK(); + // + // Call the non-locking write function + // + Status = SEGGER_RTT_WriteNoLock(BufferIndex, pBuffer, NumBytes); + // + // Finish up. + // + SEGGER_RTT_UNLOCK(); + // + return Status; +} + +/********************************************************************* +* +* SEGGER_RTT_WriteString +* +* Function description +* Stores string in SEGGER RTT control block. +* This data is read by the host. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* s Pointer to string. +* +* Return value +* Number of bytes which have been stored in the "Up"-buffer. +* +* Notes +* (1) Data is stored according to buffer flags. +* (2) String passed to this function has to be \0 terminated +* (3) \0 termination character is *not* stored in RTT buffer +*/ +unsigned SEGGER_RTT_WriteString(unsigned BufferIndex, const char* s) { + unsigned Len; + + Len = STRLEN(s); + return SEGGER_RTT_Write(BufferIndex, s, Len); +} + +/********************************************************************* +* +* SEGGER_RTT_PutCharSkipNoLock +* +* Function description +* Stores a single character/byte in SEGGER RTT buffer. +* SEGGER_RTT_PutCharSkipNoLock does not lock the application and +* skips the byte, if it does not fit into the buffer. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* c Byte to be stored. +* +* Return value +* Number of bytes which have been stored in the "Up"-buffer. +* +* Notes +* (1) If there is not enough space in the "Up"-buffer, the character is dropped. +* (2) For performance reasons this function does not call Init() +* and may only be called after RTT has been initialized. +* Either by calling SEGGER_RTT_Init() or calling another RTT API function first. +*/ + +unsigned SEGGER_RTT_PutCharSkipNoLock(unsigned BufferIndex, char c) { + SEGGER_RTT_BUFFER_UP* pRing; + unsigned WrOff; + unsigned Status; + // + // Get "to-host" ring buffer. + // + pRing = &_SEGGER_RTT.aUp[BufferIndex]; + // + // Get write position and handle wrap-around if necessary + // + WrOff = pRing->WrOff + 1; + if (WrOff == pRing->SizeOfBuffer) { + WrOff = 0; + } + // + // Output byte if free space is available + // + if (WrOff != pRing->RdOff) { + pRing->pBuffer[pRing->WrOff] = c; + pRing->WrOff = WrOff; + Status = 1; + } else { + Status = 0; + } + // + return Status; +} + +/********************************************************************* +* +* SEGGER_RTT_PutCharSkip +* +* Function description +* Stores a single character/byte in SEGGER RTT buffer. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* c Byte to be stored. +* +* Return value +* Number of bytes which have been stored in the "Up"-buffer. +* +* Notes +* (1) If there is not enough space in the "Up"-buffer, the character is dropped. +*/ + +unsigned SEGGER_RTT_PutCharSkip(unsigned BufferIndex, char c) { + SEGGER_RTT_BUFFER_UP* pRing; + unsigned WrOff; + unsigned Status; + // + // Prepare + // + INIT(); + SEGGER_RTT_LOCK(); + // + // Get "to-host" ring buffer. + // + pRing = &_SEGGER_RTT.aUp[BufferIndex]; + // + // Get write position and handle wrap-around if necessary + // + WrOff = pRing->WrOff + 1; + if (WrOff == pRing->SizeOfBuffer) { + WrOff = 0; + } + // + // Output byte if free space is available + // + if (WrOff != pRing->RdOff) { + pRing->pBuffer[pRing->WrOff] = c; + pRing->WrOff = WrOff; + Status = 1; + } else { + Status = 0; + } + // + // Finish up. + // + SEGGER_RTT_UNLOCK(); + // + return Status; +} + + /********************************************************************* +* +* SEGGER_RTT_PutChar +* +* Function description +* Stores a single character/byte in SEGGER RTT buffer. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* c Byte to be stored. +* +* Return value +* Number of bytes which have been stored in the "Up"-buffer. +* +* Notes +* (1) Data is stored according to buffer flags. +*/ + +unsigned SEGGER_RTT_PutChar(unsigned BufferIndex, char c) { + SEGGER_RTT_BUFFER_UP* pRing; + unsigned WrOff; + unsigned Status; + // + // Prepare + // + INIT(); + SEGGER_RTT_LOCK(); + // + // Get "to-host" ring buffer. + // + pRing = &_SEGGER_RTT.aUp[BufferIndex]; + // + // Get write position and handle wrap-around if necessary + // + WrOff = pRing->WrOff + 1; + if (WrOff == pRing->SizeOfBuffer) { + WrOff = 0; + } + // + // Wait for free space if mode is set to blocking + // + if (pRing->Flags == SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL) { + while (WrOff == pRing->RdOff) { + ; + } + } + // + // Output byte if free space is available + // + if (WrOff != pRing->RdOff) { + pRing->pBuffer[pRing->WrOff] = c; + pRing->WrOff = WrOff; + Status = 1; + } else { + Status = 0; + } + // + // Finish up. + // + SEGGER_RTT_UNLOCK(); + // + return Status; +} + +/********************************************************************* +* +* SEGGER_RTT_GetKey +* +* Function description +* Reads one character from the SEGGER RTT buffer. +* Host has previously stored data there. +* +* Return value +* < 0 - No character available (buffer empty). +* >= 0 - Character which has been read. (Possible values: 0 - 255) +* +* Notes +* (1) This function is only specified for accesses to RTT buffer 0. +*/ +int SEGGER_RTT_GetKey(void) { + char c; + int r; + + r = (int)SEGGER_RTT_Read(0u, &c, 1u); + if (r == 1) { + r = (int)(unsigned char)c; + } else { + r = -1; + } + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_WaitKey +* +* Function description +* Waits until at least one character is avaible in the SEGGER RTT buffer. +* Once a character is available, it is read and this function returns. +* +* Return value +* >=0 - Character which has been read. +* +* Notes +* (1) This function is only specified for accesses to RTT buffer 0 +* (2) This function is blocking if no character is present in RTT buffer +*/ +int SEGGER_RTT_WaitKey(void) { + int r; + + do { + r = SEGGER_RTT_GetKey(); + } while (r < 0); + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_HasKey +* +* Function description +* Checks if at least one character for reading is available in the SEGGER RTT buffer. +* +* Return value +* == 0 - No characters are available to read. +* == 1 - At least one character is available. +* +* Notes +* (1) This function is only specified for accesses to RTT buffer 0 +*/ +int SEGGER_RTT_HasKey(void) { + unsigned RdOff; + int r; + + INIT(); + RdOff = _SEGGER_RTT.aDown[0].RdOff; + if (RdOff != _SEGGER_RTT.aDown[0].WrOff) { + r = 1; + } else { + r = 0; + } + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_HasData +* +* Function description +* Check if there is data from the host in the given buffer. +* +* Return value: +* ==0: No data +* !=0: Data in buffer +* +*/ +unsigned SEGGER_RTT_HasData(unsigned BufferIndex) { + SEGGER_RTT_BUFFER_DOWN* pRing; + unsigned v; + + pRing = &_SEGGER_RTT.aDown[BufferIndex]; + v = pRing->WrOff; + return v - pRing->RdOff; +} + +/********************************************************************* +* +* SEGGER_RTT_HasDataUp +* +* Function description +* Check if there is data remaining to be sent in the given buffer. +* +* Return value: +* ==0: No data +* !=0: Data in buffer +* +*/ +unsigned SEGGER_RTT_HasDataUp(unsigned BufferIndex) { + SEGGER_RTT_BUFFER_UP* pRing; + unsigned v; + + pRing = &_SEGGER_RTT.aUp[BufferIndex]; + v = pRing->RdOff; + return pRing->WrOff - v; +} + +/********************************************************************* +* +* SEGGER_RTT_AllocDownBuffer +* +* Function description +* Run-time configuration of the next down-buffer (H->T). +* The next buffer, which is not used yet is configured. +* This includes: Buffer address, size, name, flags, ... +* +* Parameters +* sName Pointer to a constant name string. +* pBuffer Pointer to a buffer to be used. +* BufferSize Size of the buffer. +* Flags Operating modes. Define behavior if buffer is full (not enough space for entire message). +* +* Return value +* >= 0 - O.K. Buffer Index +* < 0 - Error +*/ +int SEGGER_RTT_AllocDownBuffer(const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags) { + int BufferIndex; + + INIT(); + SEGGER_RTT_LOCK(); + BufferIndex = 0; + do { + if (_SEGGER_RTT.aDown[BufferIndex].pBuffer == NULL) { + break; + } + BufferIndex++; + } while (BufferIndex < _SEGGER_RTT.MaxNumDownBuffers); + if (BufferIndex < _SEGGER_RTT.MaxNumDownBuffers) { + _SEGGER_RTT.aDown[BufferIndex].sName = sName; + _SEGGER_RTT.aDown[BufferIndex].pBuffer = (char*)pBuffer; + _SEGGER_RTT.aDown[BufferIndex].SizeOfBuffer = BufferSize; + _SEGGER_RTT.aDown[BufferIndex].RdOff = 0u; + _SEGGER_RTT.aDown[BufferIndex].WrOff = 0u; + _SEGGER_RTT.aDown[BufferIndex].Flags = Flags; + } else { + BufferIndex = -1; + } + SEGGER_RTT_UNLOCK(); + return BufferIndex; +} + +/********************************************************************* +* +* SEGGER_RTT_AllocUpBuffer +* +* Function description +* Run-time configuration of the next up-buffer (T->H). +* The next buffer, which is not used yet is configured. +* This includes: Buffer address, size, name, flags, ... +* +* Parameters +* sName Pointer to a constant name string. +* pBuffer Pointer to a buffer to be used. +* BufferSize Size of the buffer. +* Flags Operating modes. Define behavior if buffer is full (not enough space for entire message). +* +* Return value +* >= 0 - O.K. Buffer Index +* < 0 - Error +*/ +int SEGGER_RTT_AllocUpBuffer(const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags) { + int BufferIndex; + + INIT(); + SEGGER_RTT_LOCK(); + BufferIndex = 0; + do { + if (_SEGGER_RTT.aUp[BufferIndex].pBuffer == NULL) { + break; + } + BufferIndex++; + } while (BufferIndex < _SEGGER_RTT.MaxNumUpBuffers); + if (BufferIndex < _SEGGER_RTT.MaxNumUpBuffers) { + _SEGGER_RTT.aUp[BufferIndex].sName = sName; + _SEGGER_RTT.aUp[BufferIndex].pBuffer = (char*)pBuffer; + _SEGGER_RTT.aUp[BufferIndex].SizeOfBuffer = BufferSize; + _SEGGER_RTT.aUp[BufferIndex].RdOff = 0u; + _SEGGER_RTT.aUp[BufferIndex].WrOff = 0u; + _SEGGER_RTT.aUp[BufferIndex].Flags = Flags; + } else { + BufferIndex = -1; + } + SEGGER_RTT_UNLOCK(); + return BufferIndex; +} + +/********************************************************************* +* +* SEGGER_RTT_ConfigUpBuffer +* +* Function description +* Run-time configuration of a specific up-buffer (T->H). +* Buffer to be configured is specified by index. +* This includes: Buffer address, size, name, flags, ... +* +* Parameters +* BufferIndex Index of the buffer to configure. +* sName Pointer to a constant name string. +* pBuffer Pointer to a buffer to be used. +* BufferSize Size of the buffer. +* Flags Operating modes. Define behavior if buffer is full (not enough space for entire message). +* +* Return value +* >= 0 - O.K. +* < 0 - Error +* +* Additional information +* Buffer 0 is configured on compile-time. +* May only be called once per buffer. +* Buffer name and flags can be reconfigured using the appropriate functions. +*/ +int SEGGER_RTT_ConfigUpBuffer(unsigned BufferIndex, const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags) { + int r; + + INIT(); + if (BufferIndex < (unsigned)_SEGGER_RTT.MaxNumUpBuffers) { + SEGGER_RTT_LOCK(); + if (BufferIndex > 0u) { + _SEGGER_RTT.aUp[BufferIndex].sName = sName; + _SEGGER_RTT.aUp[BufferIndex].pBuffer = (char*)pBuffer; + _SEGGER_RTT.aUp[BufferIndex].SizeOfBuffer = BufferSize; + _SEGGER_RTT.aUp[BufferIndex].RdOff = 0u; + _SEGGER_RTT.aUp[BufferIndex].WrOff = 0u; + } + _SEGGER_RTT.aUp[BufferIndex].Flags = Flags; + SEGGER_RTT_UNLOCK(); + r = 0; + } else { + r = -1; + } + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_ConfigDownBuffer +* +* Function description +* Run-time configuration of a specific down-buffer (H->T). +* Buffer to be configured is specified by index. +* This includes: Buffer address, size, name, flags, ... +* +* Parameters +* BufferIndex Index of the buffer to configure. +* sName Pointer to a constant name string. +* pBuffer Pointer to a buffer to be used. +* BufferSize Size of the buffer. +* Flags Operating modes. Define behavior if buffer is full (not enough space for entire message). +* +* Return value +* >= 0 O.K. +* < 0 Error +* +* Additional information +* Buffer 0 is configured on compile-time. +* May only be called once per buffer. +* Buffer name and flags can be reconfigured using the appropriate functions. +*/ +int SEGGER_RTT_ConfigDownBuffer(unsigned BufferIndex, const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags) { + int r; + + INIT(); + if (BufferIndex < (unsigned)_SEGGER_RTT.MaxNumDownBuffers) { + SEGGER_RTT_LOCK(); + if (BufferIndex > 0u) { + _SEGGER_RTT.aDown[BufferIndex].sName = sName; + _SEGGER_RTT.aDown[BufferIndex].pBuffer = (char*)pBuffer; + _SEGGER_RTT.aDown[BufferIndex].SizeOfBuffer = BufferSize; + _SEGGER_RTT.aDown[BufferIndex].RdOff = 0u; + _SEGGER_RTT.aDown[BufferIndex].WrOff = 0u; + } + _SEGGER_RTT.aDown[BufferIndex].Flags = Flags; + SEGGER_RTT_UNLOCK(); + r = 0; + } else { + r = -1; + } + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_SetNameUpBuffer +* +* Function description +* Run-time configuration of a specific up-buffer name (T->H). +* Buffer to be configured is specified by index. +* +* Parameters +* BufferIndex Index of the buffer to renamed. +* sName Pointer to a constant name string. +* +* Return value +* >= 0 O.K. +* < 0 Error +*/ +int SEGGER_RTT_SetNameUpBuffer(unsigned BufferIndex, const char* sName) { + int r; + + INIT(); + if (BufferIndex < (unsigned)_SEGGER_RTT.MaxNumUpBuffers) { + SEGGER_RTT_LOCK(); + _SEGGER_RTT.aUp[BufferIndex].sName = sName; + SEGGER_RTT_UNLOCK(); + r = 0; + } else { + r = -1; + } + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_SetNameDownBuffer +* +* Function description +* Run-time configuration of a specific Down-buffer name (T->H). +* Buffer to be configured is specified by index. +* +* Parameters +* BufferIndex Index of the buffer to renamed. +* sName Pointer to a constant name string. +* +* Return value +* >= 0 O.K. +* < 0 Error +*/ +int SEGGER_RTT_SetNameDownBuffer(unsigned BufferIndex, const char* sName) { + int r; + + INIT(); + if (BufferIndex < (unsigned)_SEGGER_RTT.MaxNumDownBuffers) { + SEGGER_RTT_LOCK(); + _SEGGER_RTT.aDown[BufferIndex].sName = sName; + SEGGER_RTT_UNLOCK(); + r = 0; + } else { + r = -1; + } + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_SetFlagsUpBuffer +* +* Function description +* Run-time configuration of specific up-buffer flags (T->H). +* Buffer to be configured is specified by index. +* +* Parameters +* BufferIndex Index of the buffer. +* Flags Flags to set for the buffer. +* +* Return value +* >= 0 O.K. +* < 0 Error +*/ +int SEGGER_RTT_SetFlagsUpBuffer(unsigned BufferIndex, unsigned Flags) { + int r; + + INIT(); + if (BufferIndex < (unsigned)_SEGGER_RTT.MaxNumUpBuffers) { + SEGGER_RTT_LOCK(); + _SEGGER_RTT.aUp[BufferIndex].Flags = Flags; + SEGGER_RTT_UNLOCK(); + r = 0; + } else { + r = -1; + } + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_SetFlagsDownBuffer +* +* Function description +* Run-time configuration of specific Down-buffer flags (T->H). +* Buffer to be configured is specified by index. +* +* Parameters +* BufferIndex Index of the buffer to renamed. +* Flags Flags to set for the buffer. +* +* Return value +* >= 0 O.K. +* < 0 Error +*/ +int SEGGER_RTT_SetFlagsDownBuffer(unsigned BufferIndex, unsigned Flags) { + int r; + + INIT(); + if (BufferIndex < (unsigned)_SEGGER_RTT.MaxNumDownBuffers) { + SEGGER_RTT_LOCK(); + _SEGGER_RTT.aDown[BufferIndex].Flags = Flags; + SEGGER_RTT_UNLOCK(); + r = 0; + } else { + r = -1; + } + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_Init +* +* Function description +* Initializes the RTT Control Block. +* Should be used in RAM targets, at start of the application. +* +*/ +void SEGGER_RTT_Init (void) { + _DoInit(); +} + +/********************************************************************* +* +* SEGGER_RTT_SetTerminal +* +* Function description +* Sets the terminal to be used for output on channel 0. +* +* Parameters +* TerminalId Index of the terminal. +* +* Return value +* >= 0 O.K. +* < 0 Error (e.g. if RTT is configured for non-blocking mode and there was no space in the buffer to set the new terminal Id) +*/ +int SEGGER_RTT_SetTerminal (unsigned char TerminalId) { + unsigned char ac[2]; + SEGGER_RTT_BUFFER_UP* pRing; + unsigned Avail; + int r; + // + INIT(); + // + r = 0; + ac[0] = 0xFFu; + if (TerminalId < sizeof(_aTerminalId)) { // We only support a certain number of channels + ac[1] = _aTerminalId[TerminalId]; + pRing = &_SEGGER_RTT.aUp[0]; // Buffer 0 is always reserved for terminal I/O, so we can use index 0 here, fixed + SEGGER_RTT_LOCK(); // Lock to make sure that no other task is writing into buffer, while we are and number of free bytes in buffer does not change downwards after checking and before writing + if ((pRing->Flags & SEGGER_RTT_MODE_MASK) == SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL) { + _ActiveTerminal = TerminalId; + _WriteBlocking(pRing, (const char*)ac, 2u); + } else { // Skipping mode or trim mode? => We cannot trim this command so handling is the same for both modes + Avail = _GetAvailWriteSpace(pRing); + if (Avail >= 2) { + _ActiveTerminal = TerminalId; // Only change active terminal in case of success + _WriteNoCheck(pRing, (const char*)ac, 2u); + } else { + r = -1; + } + } + SEGGER_RTT_UNLOCK(); + } else { + r = -1; + } + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_TerminalOut +* +* Function description +* Writes a string to the given terminal +* without changing the terminal for channel 0. +* +* Parameters +* TerminalId Index of the terminal. +* s String to be printed on the terminal. +* +* Return value +* >= 0 - Number of bytes written. +* < 0 - Error. +* +*/ +int SEGGER_RTT_TerminalOut (unsigned char TerminalId, const char* s) { + int Status; + unsigned FragLen; + unsigned Avail; + SEGGER_RTT_BUFFER_UP* pRing; + // + INIT(); + // + // Validate terminal ID. + // + if (TerminalId < (char)sizeof(_aTerminalId)) { // We only support a certain number of channels + // + // Get "to-host" ring buffer. + // + pRing = &_SEGGER_RTT.aUp[0]; + // + // Need to be able to change terminal, write data, change back. + // Compute the fixed and variable sizes. + // + FragLen = STRLEN(s); + // + // How we output depends upon the mode... + // + SEGGER_RTT_LOCK(); + Avail = _GetAvailWriteSpace(pRing); + switch (pRing->Flags & SEGGER_RTT_MODE_MASK) { + case SEGGER_RTT_MODE_NO_BLOCK_SKIP: + // + // If we are in skip mode and there is no space for the whole + // of this output, don't bother switching terminals at all. + // + if (Avail < (FragLen + 4u)) { + Status = 0; + } else { + _PostTerminalSwitch(pRing, TerminalId); + Status = (int)_WriteBlocking(pRing, s, FragLen); + _PostTerminalSwitch(pRing, _ActiveTerminal); + } + break; + case SEGGER_RTT_MODE_NO_BLOCK_TRIM: + // + // If we are in trim mode and there is not enough space for everything, + // trim the output but always include the terminal switch. If no room + // for terminal switch, skip that totally. + // + if (Avail < 4u) { + Status = -1; + } else { + _PostTerminalSwitch(pRing, TerminalId); + Status = (int)_WriteBlocking(pRing, s, (FragLen < (Avail - 4u)) ? FragLen : (Avail - 4u)); + _PostTerminalSwitch(pRing, _ActiveTerminal); + } + break; + case SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL: + // + // If we are in blocking mode, output everything. + // + _PostTerminalSwitch(pRing, TerminalId); + Status = (int)_WriteBlocking(pRing, s, FragLen); + _PostTerminalSwitch(pRing, _ActiveTerminal); + break; + default: + Status = -1; + break; + } + // + // Finish up. + // + SEGGER_RTT_UNLOCK(); + } else { + Status = -1; + } + return Status; +} + +/********************************************************************* +* +* SEGGER_RTT_GetAvailWriteSpace +* +* Function description +* Returns the number of bytes available in the ring buffer. +* +* Parameters +* BufferIndex Index of the up buffer. +* +* Return value +* Number of bytes that are free in the selected up buffer. +*/ +unsigned SEGGER_RTT_GetAvailWriteSpace (unsigned BufferIndex){ + return _GetAvailWriteSpace(&_SEGGER_RTT.aUp[BufferIndex]); +} + + +/********************************************************************* +* +* SEGGER_RTT_GetBytesInBuffer() +* +* Function description +* Returns the number of bytes currently used in the up buffer. +* +* Parameters +* BufferIndex Index of the up buffer. +* +* Return value +* Number of bytes that are used in the buffer. +*/ +unsigned SEGGER_RTT_GetBytesInBuffer(unsigned BufferIndex) { + unsigned RdOff; + unsigned WrOff; + unsigned r; + // + // Avoid warnings regarding volatile access order. It's not a problem + // in this case, but dampen compiler enthusiasm. + // + RdOff = _SEGGER_RTT.aUp[BufferIndex].RdOff; + WrOff = _SEGGER_RTT.aUp[BufferIndex].WrOff; + if (RdOff <= WrOff) { + r = WrOff - RdOff; + } else { + r = _SEGGER_RTT.aUp[BufferIndex].SizeOfBuffer - (WrOff - RdOff); + } + return r; +} + +/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/src/SEGGER_RTT_ASM_ARMv7M.S b/lib/segger_rtt/src/SEGGER_RTT_ASM_ARMv7M.S new file mode 100644 index 000000000..0c0eae1c2 --- /dev/null +++ b/lib/segger_rtt/src/SEGGER_RTT_ASM_ARMv7M.S @@ -0,0 +1,235 @@ +/********************************************************************* +* (c) SEGGER Microcontroller GmbH * +* The Embedded Experts * +* www.segger.com * +********************************************************************** + +-------------------------- END-OF-HEADER ----------------------------- + +File : SEGGER_RTT_ASM_ARMv7M.S +Purpose : Assembler implementation of RTT functions for ARMv7M + +Additional information: + This module is written to be assembler-independent and works with + GCC and clang (Embedded Studio) and IAR. +*/ + +#define SEGGER_RTT_ASM // Used to control processed input from header file +#include "SEGGER_RTT.h" + +/********************************************************************* +* +* Defines, fixed +* +********************************************************************** +*/ +#define _CCIAR 0 +#define _CCCLANG 1 + +#if (defined __SES_ARM) || (defined __GNUC__) || (defined __clang__) + #define _CC_TYPE _CCCLANG + #define _PUB_SYM .global + #define _EXT_SYM .extern + #define _END .end + #define _WEAK .weak + #define _THUMB_FUNC .thumb_func + #define _THUMB_CODE .code 16 + #define _WORD .word + #define _SECTION(Sect, Type, AlignExp) .section Sect ##, "ax" + #define _ALIGN(Exp) .align Exp + #define _PLACE_LITS .ltorg + #define _DATA_SECT_START + #define _C_STARTUP _start + #define _STACK_END __stack_end__ + #define _RAMFUNC + // + // .text => Link to flash + // .fast => Link to RAM + // OtherSect => Usually link to RAM + // Alignment is 2^x + // +#elif defined (__IASMARM__) + #define _CC_TYPE _CCIAR + #define _PUB_SYM PUBLIC + #define _EXT_SYM EXTERN + #define _END END + #define _WEAK _WEAK + #define _THUMB_FUNC + #define _THUMB_CODE THUMB + #define _WORD DCD + #define _SECTION(Sect, Type, AlignExp) SECTION Sect ## : ## Type ## :REORDER:NOROOT ## (AlignExp) + #define _ALIGN(Exp) alignrom Exp + #define _PLACE_LITS + #define _DATA_SECT_START DATA + #define _C_STARTUP __iar_program_start + #define _STACK_END sfe(CSTACK) + #define _RAMFUNC SECTION_TYPE SHT_PROGBITS, SHF_WRITE | SHF_EXECINSTR + // + // .text => Link to flash + // .textrw => Link to RAM + // OtherSect => Usually link to RAM + // NOROOT => Allows linker to throw away the function, if not referenced + // Alignment is 2^x + // +#endif + +#if (_CC_TYPE == _CCIAR) + NAME SEGGER_RTT_ASM_ARMv7M +#else + .syntax unified +#endif + +#if defined (RTT_USE_ASM) && (RTT_USE_ASM == 1) + #define SHT_PROGBITS 0x1 + +/********************************************************************* +* +* Public / external symbols +* +********************************************************************** +*/ + + _EXT_SYM __aeabi_memcpy + _EXT_SYM __aeabi_memcpy4 + _EXT_SYM _SEGGER_RTT + + _PUB_SYM SEGGER_RTT_ASM_WriteSkipNoLock + +/********************************************************************* +* +* SEGGER_RTT_WriteSkipNoLock +* +* Function description +* Stores a specified number of characters in SEGGER RTT +* control block which is then read by the host. +* SEGGER_RTT_WriteSkipNoLock does not lock the application and +* skips all data, if the data does not fit into the buffer. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. +* NumBytes Number of bytes to be stored in the SEGGER RTT control block. +* MUST be > 0!!! +* This is done for performance reasons, so no initial check has do be done. +* +* Return value +* 1: Data has been copied +* 0: No space, data has not been copied +* +* Notes +* (1) If there is not enough space in the "Up"-buffer, all data is dropped. +* (2) For performance reasons this function does not call Init() +* and may only be called after RTT has been initialized. +* Either by calling SEGGER_RTT_Init() or calling another RTT API function first. +*/ + _SECTION(.text, CODE, 2) + _ALIGN(2) + _THUMB_FUNC +SEGGER_RTT_ASM_WriteSkipNoLock: // unsigned SEGGER_RTT_WriteSkipNoLock(unsigned BufferIndex, const void* pData, unsigned NumBytes) { + // + // Cases: + // 1) RdOff <= WrOff => Space until wrap-around is sufficient + // 2) RdOff <= WrOff => Space after wrap-around needed (copy in 2 chunks) + // 3) RdOff < WrOff => No space in buf + // 4) RdOff > WrOff => Space is sufficient + // 5) RdOff > WrOff => No space in buf + // + // 1) is the most common case for large buffers and assuming that J-Link reads the data fast enough + // + // Register usage: + // R0 Temporary needed as RdOff, register later on + // R1 pData + // R2 + // R3 register. Hold free for subroutine calls + // R4 + // R5 pRing->pBuffer + // R6 pRing (Points to active struct SEGGER_RTT_BUFFER_DOWN) + // R7 WrOff + // + PUSH {R4-R7} + ADD R3,R0,R0, LSL #+1 + LDR.W R0,=_SEGGER_RTT // pRing = &_SEGGER_RTT.aUp[BufferIndex]; + ADD R0,R0,R3, LSL #+3 + ADD R6,R0,#+24 + LDR R0,[R6, #+16] // RdOff = pRing->RdOff; + LDR R7,[R6, #+12] // WrOff = pRing->WrOff; + LDR R5,[R6, #+4] // pRing->pBuffer + CMP R7,R0 + BCC.N _CheckCase4 // if (RdOff <= WrOff) { => Case 1), 2) or 3) + // + // Handling for case 1, later on identical to case 4 + // + LDR R3,[R6, #+8] // Avail = pRing->SizeOfBuffer - WrOff - 1u; => Space until wrap-around (assume 1 byte not usable for case that RdOff == 0) + SUBS R4,R3,R7 // (Used in case we jump into case 2 afterwards) + SUBS R3,R4,#+1 // + CMP R3,R2 + BCC.N _CheckCase2 // if (Avail >= NumBytes) { => Case 1)? +_Case4: + ADDS R5,R7,R5 // pBuffer += WrOff + ADDS R0,R2,R7 // v = WrOff + NumBytes + // + // 2x unrolling for the copy loop that is used most of the time + // This is a special optimization for small SystemView packets and makes them even faster + // + _ALIGN(2) +_LoopCopyStraight: // memcpy(pRing->pBuffer + WrOff, pData, NumBytes); + LDRB R3,[R1], #+1 + STRB R3,[R5], #+1 // *pDest++ = *pSrc++ + SUBS R2,R2,#+1 + BEQ _CSDone + LDRB R3,[R1], #+1 + STRB R3,[R5], #+1 // *pDest++ = *pSrc++ + SUBS R2,R2,#+1 + BNE _LoopCopyStraight +_CSDone: + STR R0,[R6, #+12] // pRing->WrOff = WrOff + NumBytes; + MOVS R0,#+1 + POP {R4-R7} + BX LR // Return 1 +_CheckCase2: + ADDS R0,R0,R3 // Avail += RdOff; => Space incl. wrap-around + CMP R0,R2 + BCC.N _Case3 // if (Avail >= NumBytes) { => Case 2? => If not, we have case 3) (does not fit) + // + // Handling for case 2 + // + ADDS R0,R7,R5 // v = pRing->pBuffer + WrOff => Do not change pRing->pBuffer here because 2nd chunk needs org. value + SUBS R2,R2,R4 // NumBytes -= Rem; (Rem = pRing->SizeOfBuffer - WrOff; => Space until end of buffer) +_LoopCopyBeforeWrapAround: // memcpy(pRing->pBuffer + WrOff, pData, Rem); => Copy 1st chunk + LDRB R3,[R1], #+1 + STRB R3,[R0], #+1 // *pDest++ = *pSrc++ + SUBS R4,R4,#+1 + BNE _LoopCopyBeforeWrapAround + // + // Special case: First check that assumed RdOff == 0 calculated that last element before wrap-around could not be used + // But 2nd check (considering space until wrap-around and until RdOff) revealed that RdOff is not 0, so we can use the last element + // In this case, we may use a copy straight until buffer end anyway without needing to copy 2 chunks + // Therefore, check if 2nd memcpy is necessary at all + // + ADDS R4,R2,#+0 // Save (needed as counter in loop but must be written to after the loop). Also use this inst to update the flags to skip 2nd loop if possible + BEQ.N _No2ChunkNeeded // if (NumBytes) { +_LoopCopyAfterWrapAround: // memcpy(pRing->pBuffer, pData + Rem, NumBytes); + LDRB R3,[R1], #+1 // pData already points to the next src byte due to copy loop increment before this loop + STRB R3,[R5], #+1 // *pDest++ = *pSrc++ + SUBS R2,R2,#+1 + BNE _LoopCopyAfterWrapAround +_No2ChunkNeeded: + STR R4,[R6, #+12] // pRing->WrOff = NumBytes; => Must be written after copying data because J-Link may read control block asynchronously while writing into buffer + MOVS R0,#+1 + POP {R4-R7} + BX LR // Return 1 +_CheckCase4: + SUBS R0,R0,R7 + SUBS R0,R0,#+1 // Avail = RdOff - WrOff - 1u; + CMP R0,R2 + BCS.N _Case4 // if (Avail >= NumBytes) { => Case 4) == 1) ? => If not, we have case 5) == 3) (does not fit) +_Case3: + MOVS R0,#+0 + POP {R4-R7} + BX LR // Return 0 + _PLACE_LITS + +#endif // defined (RTT_USE_ASM) && (RTT_USE_ASM == 1) + _END + +/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/src/SEGGER_RTT_printf.c b/lib/segger_rtt/src/SEGGER_RTT_printf.c new file mode 100644 index 000000000..6a83cd91b --- /dev/null +++ b/lib/segger_rtt/src/SEGGER_RTT_printf.c @@ -0,0 +1,500 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2019 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** +* * +* SEGGER RTT * Real Time Transfer for embedded targets * +* * +********************************************************************** +* * +* All rights reserved. * +* * +* SEGGER strongly recommends to not make any changes * +* to or modify the source code of this software in order to stay * +* compatible with the RTT protocol and J-Link. * +* * +* Redistribution and use in source and binary forms, with or * +* without modification, are permitted provided that the following * +* condition is met: * +* * +* o Redistributions of source code must retain the above copyright * +* notice, this condition and the following disclaimer. * +* * +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * +* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * +* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * +* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * +* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * +* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * +* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * +* DAMAGE. * +* * +********************************************************************** +---------------------------END-OF-HEADER------------------------------ +File : SEGGER_RTT_printf.c +Purpose : Replacement for printf to write formatted data via RTT +Revision: $Rev: 17697 $ +---------------------------------------------------------------------- +*/ +#include "SEGGER_RTT.h" +#include "SEGGER_RTT_Conf.h" + +/********************************************************************* +* +* Defines, configurable +* +********************************************************************** +*/ + +#ifndef SEGGER_RTT_PRINTF_BUFFER_SIZE + #define SEGGER_RTT_PRINTF_BUFFER_SIZE (64) +#endif + +#include +#include + + +#define FORMAT_FLAG_LEFT_JUSTIFY (1u << 0) +#define FORMAT_FLAG_PAD_ZERO (1u << 1) +#define FORMAT_FLAG_PRINT_SIGN (1u << 2) +#define FORMAT_FLAG_ALTERNATE (1u << 3) + +/********************************************************************* +* +* Types +* +********************************************************************** +*/ + +typedef struct { + char* pBuffer; + unsigned BufferSize; + unsigned Cnt; + + int ReturnValue; + + unsigned RTTBufferIndex; +} SEGGER_RTT_PRINTF_DESC; + +/********************************************************************* +* +* Function prototypes +* +********************************************************************** +*/ + +/********************************************************************* +* +* Static code +* +********************************************************************** +*/ +/********************************************************************* +* +* _StoreChar +*/ +static void _StoreChar(SEGGER_RTT_PRINTF_DESC * p, char c) { + unsigned Cnt; + + Cnt = p->Cnt; + if ((Cnt + 1u) <= p->BufferSize) { + *(p->pBuffer + Cnt) = c; + p->Cnt = Cnt + 1u; + p->ReturnValue++; + } + // + // Write part of string, when the buffer is full + // + if (p->Cnt == p->BufferSize) { + if (SEGGER_RTT_Write(p->RTTBufferIndex, p->pBuffer, p->Cnt) != p->Cnt) { + p->ReturnValue = -1; + } else { + p->Cnt = 0u; + } + } +} + +/********************************************************************* +* +* _PrintUnsigned +*/ +static void _PrintUnsigned(SEGGER_RTT_PRINTF_DESC * pBufferDesc, unsigned v, unsigned Base, unsigned NumDigits, unsigned FieldWidth, unsigned FormatFlags) { + static const char _aV2C[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + unsigned Div; + unsigned Digit; + unsigned Number; + unsigned Width; + char c; + + Number = v; + Digit = 1u; + // + // Get actual field width + // + Width = 1u; + while (Number >= Base) { + Number = (Number / Base); + Width++; + } + if (NumDigits > Width) { + Width = NumDigits; + } + // + // Print leading chars if necessary + // + if ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == 0u) { + if (FieldWidth != 0u) { + if (((FormatFlags & FORMAT_FLAG_PAD_ZERO) == FORMAT_FLAG_PAD_ZERO) && (NumDigits == 0u)) { + c = '0'; + } else { + c = ' '; + } + while ((FieldWidth != 0u) && (Width < FieldWidth)) { + FieldWidth--; + _StoreChar(pBufferDesc, c); + if (pBufferDesc->ReturnValue < 0) { + break; + } + } + } + } + if (pBufferDesc->ReturnValue >= 0) { + // + // Compute Digit. + // Loop until Digit has the value of the highest digit required. + // Example: If the output is 345 (Base 10), loop 2 times until Digit is 100. + // + while (1) { + if (NumDigits > 1u) { // User specified a min number of digits to print? => Make sure we loop at least that often, before checking anything else (> 1 check avoids problems with NumDigits being signed / unsigned) + NumDigits--; + } else { + Div = v / Digit; + if (Div < Base) { // Is our divider big enough to extract the highest digit from value? => Done + break; + } + } + Digit *= Base; + } + // + // Output digits + // + do { + Div = v / Digit; + v -= Div * Digit; + _StoreChar(pBufferDesc, _aV2C[Div]); + if (pBufferDesc->ReturnValue < 0) { + break; + } + Digit /= Base; + } while (Digit); + // + // Print trailing spaces if necessary + // + if ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == FORMAT_FLAG_LEFT_JUSTIFY) { + if (FieldWidth != 0u) { + while ((FieldWidth != 0u) && (Width < FieldWidth)) { + FieldWidth--; + _StoreChar(pBufferDesc, ' '); + if (pBufferDesc->ReturnValue < 0) { + break; + } + } + } + } + } +} + +/********************************************************************* +* +* _PrintInt +*/ +static void _PrintInt(SEGGER_RTT_PRINTF_DESC * pBufferDesc, int v, unsigned Base, unsigned NumDigits, unsigned FieldWidth, unsigned FormatFlags) { + unsigned Width; + int Number; + + Number = (v < 0) ? -v : v; + + // + // Get actual field width + // + Width = 1u; + while (Number >= (int)Base) { + Number = (Number / (int)Base); + Width++; + } + if (NumDigits > Width) { + Width = NumDigits; + } + if ((FieldWidth > 0u) && ((v < 0) || ((FormatFlags & FORMAT_FLAG_PRINT_SIGN) == FORMAT_FLAG_PRINT_SIGN))) { + FieldWidth--; + } + + // + // Print leading spaces if necessary + // + if ((((FormatFlags & FORMAT_FLAG_PAD_ZERO) == 0u) || (NumDigits != 0u)) && ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == 0u)) { + if (FieldWidth != 0u) { + while ((FieldWidth != 0u) && (Width < FieldWidth)) { + FieldWidth--; + _StoreChar(pBufferDesc, ' '); + if (pBufferDesc->ReturnValue < 0) { + break; + } + } + } + } + // + // Print sign if necessary + // + if (pBufferDesc->ReturnValue >= 0) { + if (v < 0) { + v = -v; + _StoreChar(pBufferDesc, '-'); + } else if ((FormatFlags & FORMAT_FLAG_PRINT_SIGN) == FORMAT_FLAG_PRINT_SIGN) { + _StoreChar(pBufferDesc, '+'); + } else { + + } + if (pBufferDesc->ReturnValue >= 0) { + // + // Print leading zeros if necessary + // + if (((FormatFlags & FORMAT_FLAG_PAD_ZERO) == FORMAT_FLAG_PAD_ZERO) && ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == 0u) && (NumDigits == 0u)) { + if (FieldWidth != 0u) { + while ((FieldWidth != 0u) && (Width < FieldWidth)) { + FieldWidth--; + _StoreChar(pBufferDesc, '0'); + if (pBufferDesc->ReturnValue < 0) { + break; + } + } + } + } + if (pBufferDesc->ReturnValue >= 0) { + // + // Print number without sign + // + _PrintUnsigned(pBufferDesc, (unsigned)v, Base, NumDigits, FieldWidth, FormatFlags); + } + } + } +} + +/********************************************************************* +* +* Public code +* +********************************************************************** +*/ +/********************************************************************* +* +* SEGGER_RTT_vprintf +* +* Function description +* Stores a formatted string in SEGGER RTT control block. +* This data is read by the host. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used. (e.g. 0 for "Terminal") +* sFormat Pointer to format string +* pParamList Pointer to the list of arguments for the format string +* +* Return values +* >= 0: Number of bytes which have been stored in the "Up"-buffer. +* < 0: Error +*/ +int SEGGER_RTT_vprintf(unsigned BufferIndex, const char * sFormat, va_list * pParamList) { + char c; + SEGGER_RTT_PRINTF_DESC BufferDesc; + int v; + unsigned NumDigits; + unsigned FormatFlags; + unsigned FieldWidth; + char acBuffer[SEGGER_RTT_PRINTF_BUFFER_SIZE]; + + BufferDesc.pBuffer = acBuffer; + BufferDesc.BufferSize = SEGGER_RTT_PRINTF_BUFFER_SIZE; + BufferDesc.Cnt = 0u; + BufferDesc.RTTBufferIndex = BufferIndex; + BufferDesc.ReturnValue = 0; + + do { + c = *sFormat; + sFormat++; + if (c == 0u) { + break; + } + if (c == '%') { + // + // Filter out flags + // + FormatFlags = 0u; + v = 1; + do { + c = *sFormat; + switch (c) { + case '-': FormatFlags |= FORMAT_FLAG_LEFT_JUSTIFY; sFormat++; break; + case '0': FormatFlags |= FORMAT_FLAG_PAD_ZERO; sFormat++; break; + case '+': FormatFlags |= FORMAT_FLAG_PRINT_SIGN; sFormat++; break; + case '#': FormatFlags |= FORMAT_FLAG_ALTERNATE; sFormat++; break; + default: v = 0; break; + } + } while (v); + // + // filter out field with + // + FieldWidth = 0u; + do { + c = *sFormat; + if ((c < '0') || (c > '9')) { + break; + } + sFormat++; + FieldWidth = (FieldWidth * 10u) + ((unsigned)c - '0'); + } while (1); + + // + // Filter out precision (number of digits to display) + // + NumDigits = 0u; + c = *sFormat; + if (c == '.') { + sFormat++; + do { + c = *sFormat; + if ((c < '0') || (c > '9')) { + break; + } + sFormat++; + NumDigits = NumDigits * 10u + ((unsigned)c - '0'); + } while (1); + } + // + // Filter out length modifier + // + c = *sFormat; + do { + if ((c == 'l') || (c == 'h')) { + sFormat++; + c = *sFormat; + } else { + break; + } + } while (1); + // + // Handle specifiers + // + switch (c) { + case 'c': { + char c0; + v = va_arg(*pParamList, int); + c0 = (char)v; + _StoreChar(&BufferDesc, c0); + break; + } + case 'd': + v = va_arg(*pParamList, int); + _PrintInt(&BufferDesc, v, 10u, NumDigits, FieldWidth, FormatFlags); + break; + case 'u': + v = va_arg(*pParamList, int); + _PrintUnsigned(&BufferDesc, (unsigned)v, 10u, NumDigits, FieldWidth, FormatFlags); + break; + case 'x': + case 'X': + v = va_arg(*pParamList, int); + _PrintUnsigned(&BufferDesc, (unsigned)v, 16u, NumDigits, FieldWidth, FormatFlags); + break; + case 's': + { + const char * s = va_arg(*pParamList, const char *); + do { + c = *s; + s++; + if (c == '\0') { + break; + } + _StoreChar(&BufferDesc, c); + } while (BufferDesc.ReturnValue >= 0); + } + break; + case 'p': + v = va_arg(*pParamList, int); + _PrintUnsigned(&BufferDesc, (unsigned)v, 16u, 8u, 8u, 0u); + break; + case '%': + _StoreChar(&BufferDesc, '%'); + break; + default: + break; + } + sFormat++; + } else { + _StoreChar(&BufferDesc, c); + } + } while (BufferDesc.ReturnValue >= 0); + + if (BufferDesc.ReturnValue > 0) { + // + // Write remaining data, if any + // + if (BufferDesc.Cnt != 0u) { + SEGGER_RTT_Write(BufferIndex, acBuffer, BufferDesc.Cnt); + } + BufferDesc.ReturnValue += (int)BufferDesc.Cnt; + } + return BufferDesc.ReturnValue; +} + +/********************************************************************* +* +* SEGGER_RTT_printf +* +* Function description +* Stores a formatted string in SEGGER RTT control block. +* This data is read by the host. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used. (e.g. 0 for "Terminal") +* sFormat Pointer to format string, followed by the arguments for conversion +* +* Return values +* >= 0: Number of bytes which have been stored in the "Up"-buffer. +* < 0: Error +* +* Notes +* (1) Conversion specifications have following syntax: +* %[flags][FieldWidth][.Precision]ConversionSpecifier +* (2) Supported flags: +* -: Left justify within the field width +* +: Always print sign extension for signed conversions +* 0: Pad with 0 instead of spaces. Ignored when using '-'-flag or precision +* Supported conversion specifiers: +* c: Print the argument as one char +* d: Print the argument as a signed integer +* u: Print the argument as an unsigned integer +* x: Print the argument as an hexadecimal integer +* s: Print the string pointed to by the argument +* p: Print the argument as an 8-digit hexadecimal integer. (Argument shall be a pointer to void.) +*/ +int SEGGER_RTT_printf(unsigned BufferIndex, const char * sFormat, ...) { + int r; + va_list ParamList; + + va_start(ParamList, sFormat); + r = SEGGER_RTT_vprintf(BufferIndex, sFormat, &ParamList); + va_end(ParamList); + return r; +} +/*************************** End of file ****************************/ diff --git a/platformio.ini b/platformio.ini index 093a8d477..3706fa20c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,11 +9,17 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = tbeam +default_envs = tbeam ; Note: the github actions CI test build can't yet build NRF52 targets [common] -; default to a US frequency range, change it as needed for your region and hardware (CN, JP, EU433, EU865) -hw_version = US +; common is not currently used + +; REQUIRED environment variables - if not set the specified default will be sued +; The following environment variables must be set in the shell if you'd like to override them. +; They are used in this ini file as systenv.VARNAME, so in your shell do export "VARNAME=fish" +; HW_VERSION (default US) +; APP_VERSION (default emptystring) +; HW_VERSION (default emptystring) [env] platform = espressif32 @@ -25,7 +31,11 @@ board_build.partitions = partition-table.csv ; note: we add src to our include search path so that lmic_project_config can override ; FIXME: fix lib/BluetoothOTA dependency back on src/ so we can remove -Isrc -build_flags = -Wall -Wextra -Wno-missing-field-initializers -Isrc -Os -Wl,-Map,.pio/build/output.map -DAXP_DEBUG_PORT=Serial -DHW_VERSION_${common.hw_version} +build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Isrc/gps -Ilib/nanopb/include -Os -Wl,-Map,.pio/build/output.map + -DAXP_DEBUG_PORT=Serial + -DHW_VERSION_${sysenv.COUNTRY} + -DAPP_VERSION=${sysenv.APP_VERSION} + -DHW_VERSION=${sysenv.HW_VERSION} ; not needed included in ttgo-t-beam board file ; also to use PSRAM https://docs.platformio.org/en/latest/platforms/espressif32.html#external-ram-psram @@ -38,12 +48,11 @@ build_flags = -Wall -Wextra -Wno-missing-field-initializers -Isrc -Os -Wl,-Map,. ;upload_port = /dev/ttyUSB0 ;monitor_port = /dev/ttyUSB0 -upload_speed = 921600 - ; the default is esptool ; upload_protocol = esp-prog -monitor_speed = 115200 +; monitor_speed = 115200 +monitor_speed = 921600 # debug_tool = esp-prog # debug_port = /dev/ttyACM0 @@ -57,10 +66,7 @@ debug_tool = jlink ;debug_init_cmds = ; monitor adapter_khz 10000 -debug_init_break = tbreak setup - lib_deps = - https://github.com/meshtastic/RadioHead.git#d32df52f8c80eb2525d3774adc1fe36bb4c32952 https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306 SPI ; 1260 ; OneButton - not used yet @@ -68,40 +74,90 @@ lib_deps = Wire ; explicitly needed here because the AXP202 library forgets to add it https://github.com/meshtastic/arduino-fsm.git https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git + https://github.com/meshtastic/RadioLib.git + https://github.com/meshtastic/TinyGPSPlus.git + +; Common settings for ESP targes, mixin with extends = esp32_base +[esp32_base] +src_filter = + ${env.src_filter} - +upload_speed = 921600 +debug_init_break = tbreak setup +build_flags = + ${env.build_flags} -Wall -Wextra -Isrc/esp32 +lib_ignore = segger_rtt ; The 1.0 release of the TBEAM board -[env:tbeam] +[env:tbeam] +extends = esp32_base board = ttgo-t-beam lib_deps = ${env.lib_deps} - AXP202X_Library + https://github.com/meshtastic/AXP202X_Library.git build_flags = - ${env.build_flags} -D TBEAM_V10 + ${esp32_base.build_flags} -D TBEAM_V10 ; The original TBEAM board without the AXP power chip and a few other changes -[env:tbeam0.7] -board = ttgo-t-beam -build_flags = - ${env.build_flags} -D TBEAM_V07 +; Note: I've heard reports this didn't work. Disabled until someone with a 0.7 can test and debug. +;[env:tbeam0.7] +;extends = esp32_base +;board = ttgo-t-beam +;build_flags = +; ${esp32_base.build_flags} -D TBEAM_V07 [env:heltec] ;build_type = debug ; to make it possible to step through our jtag debugger +extends = esp32_base board = heltec_wifi_lora_32_V2 [env:ttgo-lora32-v1] +extends = esp32_base board = ttgo-lora32-v1 build_flags = - ${env.build_flags} -D TTGO_LORA_V1 + ${esp32_base.build_flags} -D TTGO_LORA_V1 ; note: the platformio definition for lora32-v2 seems stale, it is missing a pins_arduino.h file, therefore I don't think it works [env:ttgo-lora32-v2] +extends = esp32_base board = ttgo-lora32-v1 build_flags = - ${env.build_flags} -D TTGO_LORA_V2 + ${esp32_base.build_flags} -D TTGO_LORA_V2 - -; This is a temporary build target to test turning off particular hardare bits in the build (to improve modularity) -[env:bare] -board = ttgo-lora32-v1 +; Common settings for NRF52 based targets +[nrf52_base] +platform = nordicnrf52 +framework = arduino +debug_tool = jlink +build_type = debug ; I'm debugging with ICE a lot now build_flags = - ${env.build_flags} -D BARE_BOARD \ No newline at end of file + ${env.build_flags} -Wno-unused-variable -Isrc/nrf52 +src_filter = + ${env.src_filter} - +lib_ignore = + BluetoothOTA +monitor_port = /dev/ttyACM1 + +debug_extra_cmds = + source gdbinit + +; Set initial breakpoint (defaults to main) +debug_init_break = +;debug_init_break = tbreak loop +;debug_init_break = tbreak Reset_Handler + +; The NRF52840-dk development board +[env:nrf52dk] +extends = nrf52_base +board = nrf52840_dk_modified + +; The PPR board +[env:ppr] +extends = nrf52_base +board = ppr +lib_deps = + ${env.lib_deps} + UC1701 + https://github.com/meshtastic/BQ25703A.git + + + diff --git a/proto b/proto index d13d741a9..bfae47bdc 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit d13d741a985f75b953a9b7f8df6c8c61fcc4730d +Subproject commit bfae47bdc0da23bb1e53fed054d3de2d161389bc diff --git a/src/CustomRF95.cpp b/src/CustomRF95.cpp deleted file mode 100644 index 6e158cf89..000000000 --- a/src/CustomRF95.cpp +++ /dev/null @@ -1,180 +0,0 @@ -#include "CustomRF95.h" -#include "NodeDB.h" -#include "assert.h" -#include "configuration.h" -#include -#include - -#ifdef RF95_IRQ_GPIO - -/// A temporary buffer used for sending/receving packets, sized to hold the biggest buffer we might need -#define MAX_RHPACKETLEN 251 -static uint8_t radiobuf[MAX_RHPACKETLEN]; - -CustomRF95::CustomRF95(MemoryPool &_pool, PointerQueue &_rxDest) - : RH_RF95(NSS_GPIO, RF95_IRQ_GPIO), RadioInterface(_pool, _rxDest), txQueue(MAX_TX_QUEUE) -{ -} - -bool CustomRF95::canSleep() -{ - // We allow initializing mode, because sometimes while testing we don't ever call init() to turn on the hardware - bool isRx = isReceiving(); - DEBUG_MSG("canSleep, mode=%d, isRx=%d, txEmpty=%d, txGood=%d\n", _mode, isRx, txQueue.isEmpty(), _txGood); - return (_mode == RHModeInitialising || _mode == RHModeIdle || _mode == RHModeRx) && !isRx && txQueue.isEmpty(); -} - -bool CustomRF95::sleep() -{ - // we no longer care about interrupts from this device - prepareDeepSleep(); - - // FIXME - leave the device state in rx mode instead - return RH_RF95::sleep(); -} - -bool CustomRF95::init() -{ - bool ok = RH_RF95::init(); - - return ok; -} - -/// Send a packet (possibly by enquing in a private fifo). This routine will -/// later free() the packet to pool. This routine is not allowed to stall because it is called from -/// bluetooth comms code. If the txmit queue is empty it might return an error -ErrorCode CustomRF95::send(MeshPacket *p) -{ - // We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one). - // To do otherwise would be doubly bad because not only would we drop the packet that was on the way in, - // we almost certainly guarantee no one outside will like the packet we are sending. - if (_mode == RHModeIdle || (_mode == RHModeRx && !isReceiving())) { - // if the radio is idle, we can send right away - DEBUG_MSG("immedate send on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", txGood(), rxGood(), rxBad()); - startSend(p); - return ERRNO_OK; - } else { - DEBUG_MSG("enquing packet for send from=0x%x, to=0x%x\n", p->from, p->to); - ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN; - - if (res != ERRNO_OK) // we weren't able to queue it, so we must drop it to prevent leaks - pool.release(p); - - return res; - } -} - -// After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as -// necessary -void CustomRF95::handleInterrupt() -{ - RH_RF95::handleInterrupt(); - - BaseType_t higherPriWoken = false; - if (_mode == RHModeIdle) // We are now done sending or receiving - { - if (sendingPacket) // Were we sending? - { - // We are done sending that packet, release it - pool.releaseFromISR(sendingPacket, &higherPriWoken); - sendingPacket = NULL; - // DEBUG_MSG("Done with send\n"); - } - - // If we just finished receiving a packet, forward it into a queue - if (_rxBufValid) { - // We received a packet - - // Skip the 4 headers that are at the beginning of the rxBuf - size_t payloadLen = _bufLen - RH_RF95_HEADER_LEN; - uint8_t *payload = _buf + RH_RF95_HEADER_LEN; - - // FIXME - throws exception if called in ISR context: frequencyError() - probably the floating point math - int32_t freqerr = -1, snr = lastSNR(); - // DEBUG_MSG("Received packet from mesh src=0x%x,dest=0x%x,id=%d,len=%d rxGood=%d,rxBad=%d,freqErr=%d,snr=%d\n", - // srcaddr, destaddr, id, rxlen, rf95.rxGood(), rf95.rxBad(), freqerr, snr); - - MeshPacket *mp = pool.allocZeroed(); - - SubPacket *p = &mp->payload; - - mp->from = _rxHeaderFrom; - mp->to = _rxHeaderTo; - //_rxHeaderId = _buf[2]; - //_rxHeaderFlags = _buf[3]; - - // If we already have an entry in the DB for this nodenum, goahead and hide the snr/freqerr info there. - // Note: we can't create it at this point, because it might be a bogus User node allocation. But odds are we will - // already have a record we can hide this debugging info in. - NodeInfo *info = nodeDB.getNode(mp->from); - if (info) { - info->snr = snr; - info->frequency_error = freqerr; - } - - if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) { - pool.releaseFromISR(mp, &higherPriWoken); - } else { - // parsing was successful, queue for our recipient - mp->has_payload = true; - - assert(rxDest.enqueueFromISR(mp, &higherPriWoken)); // NOWAIT - fixme, if queue is full, delete older messages - } - - clearRxBuf(); // This message accepted and cleared - } - - higherPriWoken |= handleIdleISR(); - } - - // If we call this _IT WILL NOT RETURN_ - if (higherPriWoken) - portYIELD_FROM_ISR(); -} - -/** The ISR doesn't have any good work to do, give a new assignment. - * - * Return true if a higher pri task has woken - */ -bool CustomRF95::handleIdleISR() -{ - BaseType_t higherPriWoken = false; - - // First send any outgoing packets we have ready - MeshPacket *txp = txQueue.dequeuePtrFromISR(0); - if (txp) - startSend(txp); - else { - // Nothing to send, let's switch back to receive mode - setModeRx(); - } - - return higherPriWoken; -} - -/// This routine might be called either from user space or ISR -void CustomRF95::startSend(MeshPacket *txp) -{ - assert(!sendingPacket); - - // DEBUG_MSG("sending queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", rf95.txGood(), rf95.rxGood(), rf95.rxBad()); - assert(txp->has_payload); - - size_t numbytes = pb_encode_to_bytes(radiobuf, sizeof(radiobuf), SubPacket_fields, &txp->payload); - - sendingPacket = txp; - - setHeaderTo(txp->to); - setHeaderFrom(nodeDB.getNodeNum()); // We must do this before each send, because we might have just changed our nodenum - - // setHeaderId(0); - - assert(numbytes <= 251); // Make sure we don't overflow the tiny max packet size - - // uint32_t start = millis(); // FIXME, store this in the class - - int res = RH_RF95::send(radiobuf, numbytes); - assert(res); -} - -#endif \ No newline at end of file diff --git a/src/CustomRF95.h b/src/CustomRF95.h deleted file mode 100644 index 77b1ee8e1..000000000 --- a/src/CustomRF95.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include "RadioInterface.h" -#include "mesh.pb.h" -#include -#include - -#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission - -/** - * A version of the RF95 driver which is smart enough to manage packets via queues (no polling or blocking in user threads!) - */ -class CustomRF95 : public RH_RF95, public RadioInterface -{ - friend class MeshRadio; // for debugging we let that class touch pool - - PointerQueue txQueue; - - public: - /** pool is the pool we will alloc our rx packets from - * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool - */ - CustomRF95(MemoryPool &pool, PointerQueue &rxDest); - - /** - * Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving) - * - * This method must be used before putting the CPU into deep or light sleep. - */ - bool canSleep(); - - /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep. - virtual bool sleep(); - - /// Send a packet (possibly by enquing in a private fifo). This routine will - /// later free() the packet to pool. This routine is not allowed to stall because it is called from - /// bluetooth comms code. If the txmit queue is empty it might return an error - ErrorCode send(MeshPacket *p); - - bool init(); - - protected: - // After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as - // necessary - virtual void handleInterrupt(); - - private: - /// Send a new packet - this low level call can be called from either ISR or userspace - void startSend(MeshPacket *txp); - - /// Return true if a higher pri task has woken - bool handleIdleISR(); -}; \ No newline at end of file diff --git a/src/GPS.cpp b/src/GPS.cpp deleted file mode 100644 index 8ca8dd36b..000000000 --- a/src/GPS.cpp +++ /dev/null @@ -1,212 +0,0 @@ - -#include "GPS.h" -#include "configuration.h" -#include "time.h" -#include - -HardwareSerial _serial_gps(GPS_SERIAL_NUM); - -bool timeSetFromGPS; // We try to set our time from GPS each time we wake from sleep - -GPS gps; - -// stuff that really should be in in the instance instead... -static uint32_t - timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time -static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock - -static bool wantNewLocation = true; - -GPS::GPS() : PeriodicTask() {} - -void GPS::setup() -{ - readFromRTC(); // read the main CPU RTC at first - -#ifdef GPS_RX_PIN - _serial_gps.begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN); - // _serial_gps.setRxBufferSize(1024); // the default is 256 - // ublox.enableDebugging(Serial); - - // note: the lib's implementation has the wrong docs for what the return val is - // it is not a bool, it returns zero for success - isConnected = ublox.begin(_serial_gps); - - // try a second time, the ublox lib serial parsing is buggy? - if (!isConnected) - isConnected = ublox.begin(_serial_gps); - - if (isConnected) { - DEBUG_MSG("Connected to GPS successfully, TXpin=%d\n", GPS_TX_PIN); - - bool factoryReset = false; - bool ok; - if (factoryReset) { - // It is useful to force back into factory defaults (9600baud, NEMA to test the behavior of boards that don't have - // GPS_TX connected) - ublox.factoryReset(); - delay(2000); - isConnected = ublox.begin(_serial_gps); - DEBUG_MSG("Factory reset success=%d\n", isConnected); - if (isConnected) { - ublox.assumeAutoPVT(true, true); // Just parse NEMA for now - } - } else { - ok = ublox.setUART1Output(COM_TYPE_UBX, 500); // Use native API - assert(ok); - ok = ublox.setNavigationFrequency(1, 500); // Produce 4x/sec to keep the amount of time we stall in getPVT low - assert(ok); - // ok = ublox.setAutoPVT(false); // Not implemented on NEO-6M - // assert(ok); - // ok = ublox.setDynamicModel(DYN_MODEL_BIKE); // probably PEDESTRIAN but just in case assume bike speeds - // assert(ok); - ok = ublox.powerSaveMode(); // use power save mode - assert(ok); - } - ok = ublox.saveConfiguration(2000); - assert(ok); - } else { - // Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just - // assume NEMA at 9600 baud. - DEBUG_MSG("ERROR: No bidirectional GPS found, hoping that it still might work\n"); - - // tell lib, we are expecting the module to send PVT messages by itself to our Rx pin - // you can set second parameter to "false" if you want to control the parsing and eviction of the data (need to call - // checkUblox cyclically) - ublox.assumeAutoPVT(true, true); - } -#endif -} - -void GPS::readFromRTC() -{ - struct timeval tv; /* btw settimeofday() is helpfull here too*/ - - if (!gettimeofday(&tv, NULL)) { - uint32_t now = millis(); - - DEBUG_MSG("Read RTC time as %ld (cur millis %u) valid=%d\n", tv.tv_sec, now, timeSetFromGPS); - timeStartMsec = now; - zeroOffsetSecs = tv.tv_sec; - } -} - -/// If we haven't yet set our RTC this boot, set it from a GPS derived time -void GPS::perhapsSetRTC(const struct timeval *tv) -{ - if (!timeSetFromGPS) { - timeSetFromGPS = true; - DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec); - settimeofday(tv, NULL); - readFromRTC(); - } -} - -#include - -// for the time being we need to rapidly read from the serial port to prevent overruns -void GPS::loop() -{ - PeriodicTask::loop(); -} - -uint32_t GPS::getTime() -{ - return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs; -} - -uint32_t GPS::getValidTime() -{ - return timeSetFromGPS ? getTime() : 0; -} - -/// Returns true if we think the board can enter deep or light sleep now (we might be trying to get a GPS lock) -bool GPS::canSleep() -{ - return true; // we leave GPS on during sleep now, so sleep is okay !wantNewLocation; -} - -/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs -void GPS::prepareSleep() -{ - if (isConnected) - ublox.powerOff(); -} - -void GPS::doTask() -{ -#ifdef GPS_RX_PIN - uint8_t fixtype = 3; // If we are only using the RX pin, assume we have a 3d fix - - if (isConnected) { - // Consume all characters that have arrived - - // getPVT automatically calls checkUblox - ublox.checkUblox(); // See if new data is available. Process bytes as they come in. - - // If we don't have a fix (a quick check), don't try waiting for a solution) - // Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions - // turn off for now - // fixtype = ublox.getFixType(); - DEBUG_MSG("fix type %d\n", fixtype); - } - - // DEBUG_MSG("sec %d\n", ublox.getSecond()); - // DEBUG_MSG("lat %d\n", ublox.getLatitude()); - - // any fix that has time - if (!timeSetFromGPS && ublox.getT()) { - struct timeval tv; - - /* Convert to unix time -The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 -(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). -*/ - struct tm t; - t.tm_sec = ublox.getSecond(); - t.tm_min = ublox.getMinute(); - t.tm_hour = ublox.getHour(); - t.tm_mday = ublox.getDay(); - t.tm_mon = ublox.getMonth() - 1; - t.tm_year = ublox.getYear() - 1900; - t.tm_isdst = false; - time_t res = mktime(&t); - tv.tv_sec = res; - tv.tv_usec = 0; // time.centisecond() * (10 / 1000); - - DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec); - if (t.tm_year < 0 || t.tm_year >= 300) - DEBUG_MSG("Ignoring invalid GPS time\n"); - else - perhapsSetRTC(&tv); - } - - if ((fixtype >= 3 && fixtype <= 4) && ublox.getP()) // rd fixes only - { - // we only notify if position has changed - latitude = ublox.getLatitude() * 1e-7; - longitude = ublox.getLongitude() * 1e-7; - altitude = ublox.getAltitude() / 1000; // in mm convert to meters - DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d\n", latitude, longitude, altitude); - - hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0 - if (hasValidLocation) { - wantNewLocation = false; - notifyObservers(); - // ublox.powerOff(); - } - } else // we didn't get a location update, go back to sleep and hope the characters show up - wantNewLocation = true; -#endif - - // Once we have sent a location once we only poll the GPS rarely, otherwise check back every 1s until we have something over - // the serial - setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000); -} - -void GPS::startLock() -{ - DEBUG_MSG("Looking for GPS lock\n"); - wantNewLocation = true; - setPeriod(1); -} diff --git a/src/GPS.h b/src/GPS.h deleted file mode 100644 index 5d0d4b876..000000000 --- a/src/GPS.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include "Observer.h" -#include "PeriodicTask.h" -#include "SparkFun_Ublox_Arduino_Library.h" -#include "sys/time.h" - -/** - * A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading) - * - * When new data is available it will notify observers. - */ -class GPS : public PeriodicTask, public Observable -{ - SFE_UBLOX_GPS ublox; - - public: - double latitude, longitude; - uint32_t altitude; - bool isConnected; // Do we have a GPS we are talking to - - GPS(); - - /// Return time since 1970 in secs. Until we have a GPS lock we will be returning time based at zero - uint32_t getTime(); - - /// Return time since 1970 in secs. If we don't have a GPS lock return zero - uint32_t getValidTime(); - - void setup(); - - virtual void loop(); - - virtual void doTask(); - - /// If we haven't yet set our RTC this boot, set it from a GPS derived time - void perhapsSetRTC(const struct timeval *tv); - - /// Returns true if we think the board can enter deep or light sleep now (we might be trying to get a GPS lock) - bool canSleep(); - - /// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs - void prepareSleep(); - - /// Restart our lock attempt - try to get and broadcast a GPS reading ASAP - void startLock(); - - /// Returns ture if we have acquired GPS lock. - bool hasLock() const { return hasValidLocation; } - - private: - void readFromRTC(); - - bool hasValidLocation = false; // default to false, until we complete our first read -}; - -extern GPS gps; diff --git a/src/MeshRadio.cpp b/src/MeshRadio.cpp deleted file mode 100644 index 1f7d20d43..000000000 --- a/src/MeshRadio.cpp +++ /dev/null @@ -1,150 +0,0 @@ -#include "RH_RF95.h" -#include "error.h" -#include -#include -#include - -#include "MeshRadio.h" -#include "NodeDB.h" -#include "configuration.h" -#include -#include - -/** - * ## LoRaWAN for North America - -LoRaWAN defines 64, 125 kHz channels from 902.3 to 914.9 MHz increments. - -The maximum output power for North America is +30 dBM. - -The band is from 902 to 928 MHz. It mentions channel number and its respective channel frequency. All the 13 channels are -separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency. -*/ - -/// Sometimes while debugging it is useful to set this false, to disable rf95 accesses -bool useHardware = true; - -MeshRadio::MeshRadio(MemoryPool &_pool, PointerQueue &_rxDest) - : radioIf(_pool, _rxDest) // , manager(radioIf) -{ - myNodeInfo.num_channels = NUM_CHANNELS; - - // Can't print strings this early - serial not setup yet - // DEBUG_MSG("Set meshradio defaults name=%s\n", channelSettings.name); -} - -bool MeshRadio::init() -{ - if (!useHardware) - return true; - - DEBUG_MSG("Starting meshradio init...\n"); - -#ifdef RESET_GPIO - pinMode(RESET_GPIO, OUTPUT); // Deassert reset - digitalWrite(RESET_GPIO, HIGH); - - // pulse reset - digitalWrite(RESET_GPIO, LOW); - delay(10); - digitalWrite(RESET_GPIO, HIGH); - delay(10); -#endif - - radioIf.setThisAddress( - nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor time. - - if (!radioIf.init()) { - DEBUG_MSG("LoRa radio init failed\n"); - DEBUG_MSG("Uncomment '#define SERIAL_DEBUG' in RH_RF95.cpp for detailed debug info\n"); - return false; - } - - // not needed - defaults on - // rf95.setPayloadCRC(true); - - reloadConfig(); - - return true; -} - -/** hash a string into an integer - * - * djb2 by Dan Bernstein. - * http://www.cse.yorku.ca/~oz/hash.html - */ -unsigned long hash(char *str) -{ - unsigned long hash = 5381; - int c; - - while ((c = *str++) != 0) - hash = ((hash << 5) + hash) + (unsigned char)c; /* hash * 33 + c */ - - return hash; -} - -void MeshRadio::reloadConfig() -{ - radioIf.setModeIdle(); // Need to be idle before doing init - - // Set up default configuration - // No Sync Words in LORA mode. - radioIf.setModemConfig( - (RH_RF95::ModemConfigChoice)channelSettings.modem_config); // Radio default - // setModemConfig(Bw125Cr48Sf4096); // slow and reliable? - // rf95.setPreambleLength(8); // Default is 8 - - // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM - int channel_num = hash(channelSettings.name) % NUM_CHANNELS; - float center_freq = CH0 + CH_SPACING * channel_num; - if (!radioIf.setFrequency(center_freq)) { - DEBUG_MSG("setFrequency failed\n"); - assert(0); // fixme panic - } - - // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on - - // The default transmitter power is 13dBm, using PA_BOOST. - // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then - // you can set transmitter powers from 5 to 23 dBm: - // FIXME - can we do this? It seems to be in the Heltec board. - radioIf.setTxPower(channelSettings.tx_power, false); - - DEBUG_MSG("Set radio: name=%s, config=%u, ch=%d, txpower=%d\n", channelSettings.name, channelSettings.modem_config, - channel_num, channelSettings.tx_power); - - // Done with init tell radio to start receiving - radioIf.setModeRx(); -} - -ErrorCode MeshRadio::send(MeshPacket *p) -{ - lastTxStart = millis(); - - if (useHardware) - return radioIf.send(p); - else { - radioIf.pool.release(p); - return ERRNO_OK; - } -} - -#define TX_WATCHDOG_TIMEOUT 30 * 1000 - -void MeshRadio::loop() -{ - // It should never take us more than 30 secs to send a packet, if it does, we have a bug, FIXME, move most of this - // into CustomRF95 - uint32_t now = millis(); - if (lastTxStart != 0 && (now - lastTxStart) > TX_WATCHDOG_TIMEOUT && radioIf.mode() == RHGenericDriver::RHModeTx) { - DEBUG_MSG("ERROR! Bug! Tx packet took too long to send, forcing radio into rx mode\n"); - radioIf.setModeRx(); - if (radioIf.sendingPacket) { // There was probably a packet we were trying to send, free it - radioIf.pool.release(radioIf.sendingPacket); - radioIf.sendingPacket = NULL; - } - recordCriticalError(ErrTxWatchdog); - lastTxStart = 0; // Stop checking for now, because we just warned the developer - } -} diff --git a/src/MeshRadio.h b/src/MeshRadio.h deleted file mode 100644 index efb9e4faa..000000000 --- a/src/MeshRadio.h +++ /dev/null @@ -1,101 +0,0 @@ -#pragma once - -#include "CustomRF95.h" -#include "MemoryPool.h" -#include "MeshTypes.h" -#include "PointerQueue.h" -#include "configuration.h" -#include "mesh.pb.h" -#include - -// US channel settings -#define CH0_US 903.08f // MHz -#define CH_SPACING_US 2.16f // MHz -#define NUM_CHANNELS_US 13 - -// EU433 channel settings -#define CH0_EU433 433.175f // MHz -#define CH_SPACING_EU433 0.2f // MHz -#define NUM_CHANNELS_EU433 8 - -// EU865 channel settings -#define CH0_EU865 865.2f // MHz -#define CH_SPACING_EU865 0.3f // MHz -#define NUM_CHANNELS_EU865 10 - -// CN channel settings -#define CH0_CN 470.0f // MHz -#define CH_SPACING_CN 2.0f // MHz FIXME, this is just a guess for 470-510 -#define NUM_CHANNELS_CN 20 - -// JP channel settings -#define CH0_JP 920.0f // MHz -#define CH_SPACING_JP 0.5f // MHz FIXME, this is just a guess for 920-925 -#define NUM_CHANNELS_JP 10 - -// FIXME add defs for other regions and use them here -#ifdef HW_VERSION_US -#define CH0 CH0_US -#define CH_SPACING CH_SPACING_US -#define NUM_CHANNELS NUM_CHANNELS_US -#elif defined(HW_VERSION_EU433) -#define CH0 CH0_EU433 -#define CH_SPACING CH_SPACING_EU433 -#define NUM_CHANNELS NUM_CHANNELS_EU433 -#elif defined(HW_VERSION_EU865) -#define CH0 CH0_EU865 -#define CH_SPACING CH_SPACING_EU865 -#define NUM_CHANNELS NUM_CHANNELS_EU865 -#elif defined(HW_VERSION_CN) -#define CH0 CH0_CN -#define CH_SPACING CH_SPACING_CN -#define NUM_CHANNELS NUM_CHANNELS_CN -#elif defined(HW_VERSION_JP) -#define CH0 CH0_JP -#define CH_SPACING CH_SPACING_JP -#define NUM_CHANNELS NUM_CHANNELS_JP -#else -#error "HW_VERSION not set" -#endif - -/** - * A raw low level interface to our mesh. Only understands nodenums and bytes (not protobufs or node ids) - */ -class MeshRadio -{ - public: - // Kinda ugly way of selecting different radio implementations, but soon this MeshRadio class will be going away - // entirely. At that point we can make things pretty. -#ifdef RF95_IRQ_GPIO - CustomRF95 - radioIf; // the raw radio interface - for now I'm leaving public - because this class is shrinking to be almost nothing -#else - SimRadio radioIf; -#endif - - /** pool is the pool we will alloc our rx packets from - * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool - */ - MeshRadio(MemoryPool &pool, PointerQueue &rxDest); - - bool init(); - - /// Send a packet (possibly by enquing in a private fifo). This routine will - /// later free() the packet to pool. This routine is not allowed to stall because it is called from - /// bluetooth comms code. If the txmit queue is empty it might return an error - ErrorCode send(MeshPacket *p); - - /// Do loop callback operations (we currently FIXME poll the receive mailbox here) - /// for received packets it will call the rx handler - void loop(); - - /// The radioConfig object just changed, call this to force the hw to change to the new settings - void reloadConfig(); - - private: - // RHReliableDatagram manager; // don't use mesh yet - // RHMesh manager; - - /// Used for the tx timer watchdog, to check for bugs in our transmit code, msec of last time we did a send - uint32_t lastTxStart = 0; -}; diff --git a/src/MeshTypes.h b/src/MeshTypes.h deleted file mode 100644 index ea31f6c19..000000000 --- a/src/MeshTypes.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -// low level types - -#include - -typedef uint8_t NodeNum; - -#define NODENUM_BROADCAST 255 -#define ERRNO_OK 0 -#define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER - -typedef int ErrorCode; \ No newline at end of file diff --git a/src/OSTimer.cpp b/src/OSTimer.cpp new file mode 100644 index 000000000..0da3b7d28 --- /dev/null +++ b/src/OSTimer.cpp @@ -0,0 +1,49 @@ +#include "OSTimer.h" +#include "configuration.h" + +#ifdef NO_ESP32 + +/** + * Schedule a callback to run. The callback must _not_ block, though it is called from regular thread level (not ISR) + * + * NOTE! xTimerPend... seems to ignore the time passed in on ESP32 - I haven't checked on NRF52 + * + * @return true if successful, false if the timer fifo is too full. + */ +bool scheduleOSCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec) +{ + return xTimerPendFunctionCall(callback, param1, param2, pdMS_TO_TICKS(delayMsec)); +} + +#else + +// Super skanky quick hack to use hardware timers of the ESP32 +static hw_timer_t *timer; +static PendableFunction tCallback; +static void *tParam1; +static uint32_t tParam2; + +static void IRAM_ATTR onTimer() +{ + (*tCallback)(tParam1, tParam2); +} + +bool scheduleHWCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec) +{ + if (!timer) { + timer = timerBegin(0, 80, true); // one usec per tick (main clock is 80MhZ on ESP32) + assert(timer); + timerAttachInterrupt(timer, &onTimer, true); + } + + tCallback = callback; + tParam1 = param1; + tParam2 = param2; + + timerAlarmWrite(timer, delayMsec * 1000L, false); // Do not reload, we want it to be a single shot timer + timerRestart(timer); + timerAlarmEnable(timer); + return true; +} + +#endif \ No newline at end of file diff --git a/src/OSTimer.h b/src/OSTimer.h new file mode 100644 index 000000000..cdf2386a6 --- /dev/null +++ b/src/OSTimer.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +typedef void (*PendableFunction)(void *pvParameter1, uint32_t ulParameter2); + +/** + * Schedule a callback to run. The callback must _not_ block, though it is called from regular thread level (not ISR) + * + * NOTE! ESP32 implementation is busted - always waits 0 ticks + * + * @return true if successful, false if the timer fifo is too full. + */ + bool scheduleOSCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec); + + +/// Uses a hardware timer, but calls the handler in _interrupt_ context +bool scheduleHWCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec); \ No newline at end of file diff --git a/src/Observer.cpp b/src/Observer.cpp index 468f5ade9..1025f8bc0 100644 --- a/src/Observer.cpp +++ b/src/Observer.cpp @@ -1,13 +1,2 @@ #include "Observer.h" -Observer::~Observer() -{ - if (observed) - observed->removeObserver(this); - observed = NULL; -} - -void Observer::observe(Observable *o) -{ - o->addObserver(this); -} \ No newline at end of file diff --git a/src/Observer.h b/src/Observer.h index b4de9d83b..701495b38 100644 --- a/src/Observer.h +++ b/src/Observer.h @@ -1,41 +1,98 @@ #pragma once #include - +#include #include -class Observable; +template class Observable; -class Observer +/** + * An observer which can be mixed in as a baseclass. Implement onNotify as a method in your class. + */ +template class Observer { - Observable *observed; + Observable *observed = NULL; public: - Observer() : observed(NULL) {} - virtual ~Observer(); - void observe(Observable *o); + void observe(Observable *o); private: - friend class Observable; + friend class Observable; - virtual void onNotify(Observable *o) = 0; + protected: + /** + * returns 0 if other observers should continue to be called + * returns !0 if the observe calls should be aborted and this result code returned for notifyObservers + **/ + virtual int onNotify(T arg) = 0; }; -class Observable +/** + * An observer that calls an arbitrary method + */ +template class CallbackObserver : public Observer { - std::list observers; + typedef int (Callback::*ObserverCallback)(T arg); + + Callback *objPtr; + ObserverCallback method; public: - void notifyObservers() + CallbackObserver(Callback *_objPtr, ObserverCallback _method) : objPtr(_objPtr), method(_method) {} + + protected: + virtual int onNotify(T arg) { return (objPtr->*method)(arg); } +}; + +/** + * An observable class that will notify observers anytime notifyObservers is called. Argument type T can be any type, but for + * performance reasons a pointer or word sized object is recommended. + */ +template class Observable +{ + std::list *> observers; + + public: + /** + * Tell all observers about a change, observers can process arg as they wish + * + * returns !0 if an observer chose to abort processing by returning this code + */ + int notifyObservers(T arg) { - for (std::list::const_iterator iterator = observers.begin(); iterator != observers.end(); ++iterator) { - (*iterator)->onNotify(this); + for (typename std::list *>::const_iterator iterator = observers.begin(); iterator != observers.end(); + ++iterator) { + int result = (*iterator)->onNotify(arg); + if (result != 0) + return result; } + + return 0; } - void addObserver(Observer *o) { observers.push_back(o); } + private: + friend class Observer; - void removeObserver(Observer *o) { observers.remove(o); } + // Not called directly, instead call observer.observe + void addObserver(Observer *o) { observers.push_back(o); } + + void removeObserver(Observer *o) { observers.remove(o); } }; + +template Observer::~Observer() +{ + if (observed) + observed->removeObserver(this); + observed = NULL; +} + +template void Observer::observe(Observable *o) +{ + // We can only watch one thing at a time + assert(!observed); + + observed = o; + o->addObserver(this); +} \ No newline at end of file diff --git a/src/PeriodicTask.cpp b/src/PeriodicTask.cpp index 99115faf9..5a5d3621c 100644 --- a/src/PeriodicTask.cpp +++ b/src/PeriodicTask.cpp @@ -1,21 +1,39 @@ #include "PeriodicTask.h" #include "Periodic.h" +PeriodicScheduler periodicScheduler; PeriodicTask::PeriodicTask(uint32_t initialPeriod) : period(initialPeriod) {} -/// call this from loop -void PeriodicTask::loop() +void PeriodicTask::setup() { - { - meshtastic::LockGuard lg(&lock); - uint32_t now = millis(); - if (!period || (now - lastMsec) < period) { - return; + periodicScheduler.schedule(this); +} + +/// call this from loop +void PeriodicScheduler::loop() +{ + meshtastic::LockGuard lg(&lock); + + uint32_t now = millis(); + for (auto t : tasks) { + if (t->period && (now - t->lastMsec) >= t->period) { + + t->doTask(); + t->lastMsec = now; } - lastMsec = now; } - // Release the lock in case the task wants to change the period. - doTask(); +} + +void PeriodicScheduler::schedule(PeriodicTask *t) +{ + meshtastic::LockGuard lg(&lock); + tasks.insert(t); +} + +void PeriodicScheduler::unschedule(PeriodicTask *t) +{ + meshtastic::LockGuard lg(&lock); + tasks.erase(t); } void Periodic::doTask() diff --git a/src/PeriodicTask.h b/src/PeriodicTask.h index f4a35a2c5..9d2a06b6d 100644 --- a/src/PeriodicTask.h +++ b/src/PeriodicTask.h @@ -1,8 +1,40 @@ #pragma once -#include - #include "lock.h" +#include +#include + +class PeriodicTask; + +/** + * Runs all PeriodicTasks in the system. + * + * Currently called from main loop() but eventually should be its own thread blocked on a freertos timer. + */ +class PeriodicScheduler +{ + friend class PeriodicTask; + + /** + * This really should be some form of heap, and when the period gets changed on a task it should get + * rescheduled in that heap. Currently it is just a dumb array and everytime we run loop() we check + * _every_ tasks. If it was a heap we'd only have to check the first task. + */ + std::unordered_set tasks; + + // Protects the above variables. + meshtastic::Lock lock; + + public: + /// Run any next tasks which are due for execution + void loop(); + + private: + void schedule(PeriodicTask *t); + void unschedule(PeriodicTask *t); +}; + +extern PeriodicScheduler periodicScheduler; /** * A base class for tasks that want their doTask() method invoked periodically @@ -13,26 +45,33 @@ */ class PeriodicTask { + friend class PeriodicScheduler; + uint32_t lastMsec = 0; uint32_t period = 1; // call soon after creation - // Protects the above variables. - meshtastic::Lock lock; - public: - virtual ~PeriodicTask() {} + virtual ~PeriodicTask() { periodicScheduler.unschedule(this); } + /** + * Constructor (will schedule with the global PeriodicScheduler) + */ PeriodicTask(uint32_t initialPeriod = 1); - /// call this from loop - virtual void loop(); + /** MUST be be called once at startup (but after threading is running - i.e. not from a constructor) + */ + void setup(); - /// Set a new period in msecs (can be called from doTask or elsewhere and the scheduler will cope) - void setPeriod(uint32_t p) - { - meshtastic::LockGuard lg(&lock); - period = p; - } + /** + * Set a new period in msecs (can be called from doTask or elsewhere and the scheduler will cope) + * While zero this task is disabled and will not run + */ + void setPeriod(uint32_t p) { period = p; } + + /** + * Syntatic sugar for suspending tasks + */ + void disable() { setPeriod(0); } protected: virtual void doTask() = 0; diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index ce7005ffa..b60204fd5 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -7,6 +7,7 @@ #include "main.h" #include "screen.h" #include "sleep.h" +#include "target_specific.h" static void sdsEnter() { @@ -30,22 +31,6 @@ static void lsEnter() DEBUG_MSG("lsEnter begin, ls_secs=%u\n", radioConfig.preferences.ls_secs); screen.setOn(false); - uint32_t now = millis(); - while (!service.radio.radioIf.canSleep()) { - delay(10); // Kinda yucky - wait until radio says say we can shutdown (finished in process sends/receives) - - if (millis() - now > 30 * 1000) { // If we wait too long just report an error and go to sleep - recordCriticalError(ErrSleepEnterWait); - break; - } - } - - gps.prepareSleep(); // abandon in-process parsing - - // if (!isUSBPowered) // FIXME - temp hack until we can put gps in sleep mode, if we have AC when we go to sleep then - // leave GPS on - // setGPSPower(false); // kill GPS power - DEBUG_MSG("lsEnter end\n"); } @@ -53,6 +38,7 @@ static void lsIdle() { DEBUG_MSG("lsIdle begin ls_secs=%u\n", radioConfig.preferences.ls_secs); +#ifndef NO_ESP32 uint32_t secsSlept = 0; esp_sleep_source_t wakeCause = ESP_SLEEP_WAKEUP_UNDEFINED; bool reached_ls_secs = false; @@ -95,12 +81,13 @@ static void lsIdle() powerFSM.trigger(EVENT_WAKE_TIMER); } } +#endif } static void lsExit() { // setGPSPower(true); // restore GPS power - gps.startLock(); + gps->startLock(); } static void nbEnter() @@ -166,6 +153,13 @@ void PowerFSM_setup() powerFSM.add_transition(&stateDARK, &stateON, EVENT_PRESS, NULL, "Press"); powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, screenPress, "Press"); // reenter On to restart our timers + // Handle critically low power battery by forcing deep sleep + powerFSM.add_transition(&stateBOOT, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateLS, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateNB, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateDARK, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateON, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateDARK, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing"); powerFSM.add_transition(&stateON, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing"); @@ -186,10 +180,13 @@ void PowerFSM_setup() powerFSM.add_timed_transition(&stateDARK, &stateNB, radioConfig.preferences.phone_timeout_secs * 1000, NULL, "Phone timeout"); +#ifndef NRF52_SERIES + // We never enter light-sleep state on NRF52 (because the CPU uses so little power normally) powerFSM.add_timed_transition(&stateNB, &stateLS, radioConfig.preferences.min_wake_secs * 1000, NULL, "Min wake timeout"); powerFSM.add_timed_transition(&stateDARK, &stateLS, radioConfig.preferences.wait_bluetooth_secs * 1000, NULL, "Bluetooth timeout"); +#endif powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.mesh_sds_timeout_secs * 1000, NULL, "mesh timeout"); diff --git a/src/PowerFSM.h b/src/PowerFSM.h index c94ffabf9..ecaea70ac 100644 --- a/src/PowerFSM.h +++ b/src/PowerFSM.h @@ -13,6 +13,7 @@ #define EVENT_BLUETOOTH_PAIR 7 #define EVENT_NODEDB_UPDATED 8 // NodeDB has a big enough change that we think you should turn on the screen #define EVENT_CONTACT_FROM_PHONE 9 // the phone just talked to us over bluetooth +#define EVENT_LOW_BATTERY 10 // Battery is critically low, go to sleep extern Fsm powerFSM; diff --git a/src/RadioInterface.cpp b/src/RadioInterface.cpp deleted file mode 100644 index 1406646b8..000000000 --- a/src/RadioInterface.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "CustomRF95.h" -#include "NodeDB.h" -#include "assert.h" -#include "configuration.h" -#include -#include - -RadioInterface::RadioInterface(MemoryPool &_pool, PointerQueue &_rxDest) : pool(_pool), rxDest(_rxDest) {} - -ErrorCode SimRadio::send(MeshPacket *p) -{ - DEBUG_MSG("SimRadio.send\n"); - pool.release(p); - return ERRNO_OK; -} \ No newline at end of file diff --git a/src/RadioInterface.h b/src/RadioInterface.h deleted file mode 100644 index 452b8841c..000000000 --- a/src/RadioInterface.h +++ /dev/null @@ -1,123 +0,0 @@ -#pragma once - -#include "MemoryPool.h" -#include "MeshTypes.h" -#include "PointerQueue.h" -#include "mesh.pb.h" -#include - -#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission - -/** - * Basic operations all radio chipsets must implement. - * - * This defines the SOLE API for talking to radios (because soon we will have alternate radio implementations) - */ -class RadioInterface -{ - friend class MeshRadio; // for debugging we let that class touch pool - - protected: - MemoryPool &pool; - PointerQueue &rxDest; - - MeshPacket *sendingPacket = NULL; // The packet we are currently sending - public: - /** pool is the pool we will alloc our rx packets from - * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool - */ - RadioInterface(MemoryPool &pool, PointerQueue &rxDest); - - /** - * Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving) - * - * This method must be used before putting the CPU into deep or light sleep. - */ - virtual bool canSleep() { return true; } - - /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep. - /// return true for success - virtual bool sleep() { return true; } - - /// Send a packet (possibly by enquing in a private fifo). This routine will - /// later free() the packet to pool. This routine is not allowed to stall because it is called from - /// bluetooth comms code. If the txmit queue is empty it might return an error - virtual ErrorCode send(MeshPacket *p) = 0; -}; - -class SimRadio : public RadioInterface -{ - public: - /** pool is the pool we will alloc our rx packets from - * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool - */ - SimRadio(MemoryPool &_pool, PointerQueue &_rxDest) : RadioInterface(_pool, _rxDest) {} - - virtual ErrorCode send(MeshPacket *p); - - // methods from radiohead - - /// Sets the address of this node. Defaults to 0xFF. Subclasses or the user may want to change this. - /// This will be used to test the adddress in incoming messages. In non-promiscuous mode, - /// only messages with a TO header the same as thisAddress or the broadcast addess (0xFF) will be accepted. - /// In promiscuous mode, all messages will be accepted regardless of the TO header. - /// In a conventional multinode system, all nodes will have a unique address - /// (which you could store in EEPROM). - /// You would normally set the header FROM address to be the same as thisAddress (though you dont have to, - /// allowing the possibilty of address spoofing). - /// \param[in] thisAddress The address of this node. - virtual void setThisAddress(uint8_t thisAddress) {} - - /// 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() { return true; } - - /// Sets the transmitter and receiver - /// centre frequency. - /// \param[in] centre Frequency in MHz. 137.0 to 1020.0. Caution: RFM95/96/97/98 comes in several - /// different frequency ranges, and setting a frequency outside that range of your radio will probably not work - /// \return true if the selected frquency centre is within range - bool setFrequency(float centre) { return true; } - - /// Select one of the predefined modem configurations. If you need a modem configuration not provided - /// here, use setModemRegisters() with your own ModemConfig. - /// Caution: the slowest protocols may require a radio module with TCXO temperature controlled oscillator - /// for reliable operation. - /// \param[in] index The configuration choice. - /// \return true if index is a valid choice. - bool setModemConfig(RH_RF95::ModemConfigChoice index) { return true; } - - /// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, - /// disables them. - void setModeIdle() {} - - /// If current mode is Tx or Idle, changes it to Rx. - /// Starts the receiver in the RF95/96/97/98. - void setModeRx() {} - - /// Returns the operating mode of the library. - /// \return the current mode, one of RF69_MODE_* - virtual RHGenericDriver::RHMode mode() { return RHGenericDriver::RHModeIdle; } - - /// Sets the transmitter power output level, and configures the transmitter pin. - /// Be a good neighbour and set the lowest power level you need. - /// Some SX1276/77/78/79 and compatible modules (such as RFM95/96/97/98) - /// use the PA_BOOST transmitter pin for high power output (and optionally the PA_DAC) - /// while some (such as the Modtronix inAir4 and inAir9) - /// use the RFO transmitter pin for lower power but higher efficiency. - /// You must set the appropriate power level and useRFO argument for your module. - /// Check with your module manufacturer which transmtter pin is used on your module - /// to ensure you are setting useRFO correctly. - /// Failure to do so will result in very low - /// transmitter power output. - /// Caution: legal power limits may apply in certain countries. - /// After init(), the power will be set to 13dBm, with useRFO false (ie PA_BOOST enabled). - /// \param[in] power Transmitter power level in dBm. For RFM95/96/97/98 LORA with useRFO false, - /// valid values are from +5 to +23. - /// For Modtronix inAir4 and inAir9 with useRFO true (ie RFO pins in use), - /// valid values are from -1 to 14. - /// \param[in] useRFO If true, enables the use of the RFO transmitter pins instead of - /// the PA_BOOST pin (false). Choose the correct setting for your module. - void setTxPower(int8_t power, bool useRFO = false) {} -}; diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp new file mode 100644 index 000000000..04205f498 --- /dev/null +++ b/src/RedirectablePrint.cpp @@ -0,0 +1,13 @@ +#include "RedirectablePrint.h" +#include + +/** + * A printer that doesn't go anywhere + */ +NoopPrint noopPrint; + +void RedirectablePrint::setDestination(Print *_dest) +{ + assert(_dest); + dest = _dest; +} \ No newline at end of file diff --git a/src/RedirectablePrint.h b/src/RedirectablePrint.h new file mode 100644 index 000000000..f75aea010 --- /dev/null +++ b/src/RedirectablePrint.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +/** + * A Printable that can be switched to squirt its bytes to a different sink. + * This class is mostly useful to allow debug printing to be redirected away from Serial + * to some other transport if we switch Serial usage (on the fly) to some other purpose. + */ +class RedirectablePrint : public Print +{ + Print *dest; + + public: + RedirectablePrint(Print *_dest) : dest(_dest) {} + + /** + * Set a new destination + */ + void setDestination(Print *dest); + + virtual size_t write(uint8_t c) { return dest->write(c); } +}; + +class NoopPrint : public Print +{ + public: + virtual size_t write(uint8_t c) { return 1; } +}; + +/** + * A printer that doesn't go anywhere + */ +extern NoopPrint noopPrint; \ No newline at end of file diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp new file mode 100644 index 000000000..9e84a958f --- /dev/null +++ b/src/SerialConsole.cpp @@ -0,0 +1,37 @@ +#include "SerialConsole.h" +#include "configuration.h" +#include + +#define Port Serial + +SerialConsole console; + +SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port) +{ + canWrite = false; // We don't send packets to our port until it has talked to us first + // setDestination(&noopPrint); for testing, try turning off 'all' debug output and see what leaks +} + +/// Do late init that can't happen at constructor time +void SerialConsole::init() +{ + Port.begin(SERIAL_BAUD); + StreamAPI::init(); + emitRebooted(); +} + +/** + * we override this to notice when we've received a protobuf over the serial + * stream. Then we shunt off debug serial output. + */ +void SerialConsole::handleToRadio(const uint8_t *buf, size_t len) +{ + // Note: for the time being we could _allow_ debug printing to keep going out the console + // I _think_ this is okay because we currently only print debug msgs from loop() and we are only + // dispatching serial protobuf msgs from loop() as well. When things are more threaded in the future this + // will need to change. + // setDestination(&noopPrint); + canWrite = true; + + StreamAPI::handleToRadio(buf, len); +} \ No newline at end of file diff --git a/src/SerialConsole.h b/src/SerialConsole.h new file mode 100644 index 000000000..b39eda23b --- /dev/null +++ b/src/SerialConsole.h @@ -0,0 +1,31 @@ +#pragma once + +#include "RedirectablePrint.h" +#include "StreamAPI.h" +/** + * Provides both debug printing and, if the client starts sending protobufs to us, switches to send/receive protobufs + * (and starts dropping debug printing - FIXME, eventually those prints should be encapsulated in protobufs). + */ +class SerialConsole : public StreamAPI, public RedirectablePrint +{ + public: + SerialConsole(); + + /// Do late init that can't happen at constructor time + virtual void init(); + + /** + * we override this to notice when we've received a protobuf over the serial stream. Then we shunt off + * debug serial output. + */ + virtual void handleToRadio(const uint8_t *buf, size_t len); + + virtual size_t write(uint8_t c) + { + if (c == '\n') // prefix any newlines with carriage return + RedirectablePrint::write('\r'); + return RedirectablePrint::write(c); + } +}; + +extern SerialConsole console; diff --git a/src/WorkerThread.cpp b/src/WorkerThread.cpp new file mode 100644 index 000000000..f84d83be2 --- /dev/null +++ b/src/WorkerThread.cpp @@ -0,0 +1,37 @@ +#include "WorkerThread.h" +#include + +void Thread::start(const char *name, size_t stackSize, uint32_t priority) +{ + auto r = xTaskCreate(callRun, name, stackSize, this, priority, &taskHandle); + assert(r == pdPASS); +} + +void Thread::callRun(void *_this) +{ + ((Thread *)_this)->doRun(); +} + +void WorkerThread::doRun() +{ + while (!wantExit) { + block(); + loop(); + } +} + +/** + * Notify this thread so it can run + */ +void NotifiedWorkerThread::notify(uint32_t v, eNotifyAction action) +{ + xTaskNotify(taskHandle, v, action); +} + + + +void NotifiedWorkerThread::block() +{ + xTaskNotifyWait(0, // don't clear notification on entry + clearOnRead, ¬ification, portMAX_DELAY); // Wait forever +} diff --git a/src/WorkerThread.h b/src/WorkerThread.h new file mode 100644 index 000000000..86ec08e13 --- /dev/null +++ b/src/WorkerThread.h @@ -0,0 +1,89 @@ +#include + +class Thread +{ + protected: + TaskHandle_t taskHandle = NULL; + + /** + * set this to true to ask thread to cleanly exit asap + */ + volatile bool wantExit = false; + + public: + void start(const char *name, size_t stackSize = 1024, uint32_t priority = tskIDLE_PRIORITY); + + virtual ~Thread() { vTaskDelete(taskHandle); } + + protected: + /** + * The method that will be called when start is called. + */ + virtual void doRun() = 0; + + private: + static void callRun(void *_this); +}; + +/** + * This wraps threading (FreeRTOS for now) with a blocking API intended for efficiently converting onlyschool arduino loop() code. + * + * Use as a mixin base class for the classes you want to convert. + * + * https://www.freertos.org/RTOS_Task_Notification_As_Mailbox.html + */ +class WorkerThread : public Thread +{ + protected: + /** + * A method that should block execution - either waiting ona queue/mutex or a "task notification" + */ + virtual void block() = 0; + + virtual void loop() = 0; + + /** + * The method that will be called when start is called. + */ + virtual void doRun(); +}; + +/** + * A worker thread that waits on a freertos notification + */ +class NotifiedWorkerThread : public WorkerThread +{ + public: + /** + * Notify this thread so it can run + */ + void notify(uint32_t v = 0, eNotifyAction action = eNoAction); + + /** + * Notify from an ISR + * + * This must be inline or IRAM_ATTR on ESP32 + */ + inline void notifyFromISR(BaseType_t *highPriWoken, uint32_t v = 0, eNotifyAction action = eNoAction) + { + xTaskNotifyFromISR(taskHandle, v, action, highPriWoken); + } + + protected: + /** + * The notification that was most recently used to wake the thread. Read from loop() + */ + uint32_t notification = 0; + + /** + * What notification bits should be cleared just after we read and return them in notification? + * + * Defaults to clear all of them. + */ + uint32_t clearOnRead = UINT32_MAX; + + /** + * A method that should block execution - either waiting ona queue/mutex or a "task notification" + */ + virtual void block(); +}; \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index 86bdd231b..04f9d26a1 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -31,8 +31,9 @@ along with this program. If not, see . // If app version is not specified we assume we are not being invoked by the build script #ifndef APP_VERSION -#define APP_VERSION 0.0.0 // this def normally comes from build-all.sh -#define HW_VERSION 1.0 - US // normally comes from build-all.sh and contains the region code +#error APP_VERSION, HW_VERSION, and HW_VERSION_countryname must be set by the build environment +//#define APP_VERSION 0.0.0 // this def normally comes from build-all.sh +//#define HW_VERSION 1.0 - US // normally comes from build-all.sh and contains the region code #endif // ----------------------------------------------------------------------------- @@ -45,41 +46,40 @@ along with this program. If not, see . //#define USE_JTAG #endif -#define DEBUG_PORT Serial // Serial debug port -#define SERIAL_BAUD 115200 // Serial debug baud rate - #define REQUIRE_RADIO true // If true, we will fail to start if the radio is not found +/// Convert a preprocessor name into a quoted string #define xstr(s) str(s) #define str(s) #s -// ----------------------------------------------------------------------------- -// DEBUG -// ----------------------------------------------------------------------------- +/// Convert a preprocessor name into a quoted string and if that string is empty use "unset" +#define optstr(s) (xstr(s)[0] ? xstr(s) : "unset") -#ifdef DEBUG_PORT -#define DEBUG_MSG(...) DEBUG_PORT.printf(__VA_ARGS__) +#ifdef NRF52840_XXAA // All of the NRF52 targets are configured using variant.h, so this section shouldn't need to be + // board specific + +// +// Standard definitions for NRF52 targets +// + +#define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth) + +// We bind to the GPS using variant.h instead for this platform (Serial1) + +// FIXME, not yet ready for NRF52 +#define RTC_DATA_ATTR + +#define LED_PIN PIN_LED1 // LED1 on nrf52840-DK +#define BUTTON_PIN PIN_BUTTON1 + +// FIXME, use variant.h defs for all of this!!! (even on the ESP32 targets) #else -#define DEBUG_MSG(...) -#endif -// ----------------------------------------------------------------------------- -// OLED -// ----------------------------------------------------------------------------- - -#define SSD1306_ADDRESS 0x3C - -// Flip the screen upside down by default as it makes more sense on T-BEAM -// devices. Comment this out to not rotate screen 180 degrees. -#define FLIP_SCREEN_VERTICALLY - -// ----------------------------------------------------------------------------- -// GPS -// ----------------------------------------------------------------------------- +// +// Standard definitions for ESP32 targets +// #define GPS_SERIAL_NUM 1 -#define GPS_BAUDRATE 9600 - #define GPS_RX_PIN 34 #ifdef USE_JTAG #define GPS_TX_PIN -1 @@ -96,6 +96,27 @@ along with this program. If not, see . #define MOSI_GPIO 27 #define NSS_GPIO 18 +#endif + +// ----------------------------------------------------------------------------- +// OLED +// ----------------------------------------------------------------------------- + +#define SSD1306_ADDRESS 0x3C + +// Flip the screen upside down by default as it makes more sense on T-BEAM +// devices. Comment this out to not rotate screen 180 degrees. +#define FLIP_SCREEN_VERTICALLY + +// DEBUG LED +#define LED_INVERTED 0 // define as 1 if LED is active low (on) + +// ----------------------------------------------------------------------------- +// GPS +// ----------------------------------------------------------------------------- + +#define GPS_BAUDRATE 9600 + #if defined(TBEAM_V10) // This string must exactly match the case used in release file names or the android updater won't work #define HW_VENDOR "tbeam" @@ -117,6 +138,8 @@ along with this program. If not, see . // Leave undefined to disable our PMU IRQ handler #define PMU_IRQ 35 +#define AXP192_SLAVE_ADDRESS 0x34 + #elif defined(TBEAM_V07) // This string must exactly match the case used in release file names or the android updater won't work #define HW_VENDOR "tbeam0.7" @@ -198,16 +221,58 @@ along with this program. If not, see . #define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio #define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number #define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number -#elif defined(BARE_BOARD) -// This string must exactly match the case used in release file names or the android updater won't work -#define HW_VENDOR "bare" +#endif +#ifdef ARDUINO_NRF52840_PCA10056 + +// This string must exactly match the case used in release file names or the android updater won't work +#define HW_VENDOR "nrf52dk" + +// This board uses 0 to be mean LED on +#undef LED_INVERTED +#define LED_INVERTED 1 + +// Uncomment to confirm if we can build the RF95 driver for NRF52 +#if 0 +#define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio +#define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio +#define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number +#define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number +#endif + +#elif defined(ARDUINO_NRF52840_PPR) + +#define HW_VENDOR "ppr" + +#endif + +// ----------------------------------------------------------------------------- +// DEBUG +// ----------------------------------------------------------------------------- + +#define SERIAL_BAUD 921600 // Serial debug baud rate + +#ifdef NO_ESP32 +#define USE_SEGGER +#endif +#ifdef USE_SEGGER +#include "SEGGER_RTT.h" +#define DEBUG_MSG(...) SEGGER_RTT_printf(0, __VA_ARGS__) +#else +#include "SerialConsole.h" + +#define DEBUG_PORT console // Serial debug port + +#ifdef DEBUG_PORT +#define DEBUG_MSG(...) DEBUG_PORT.printf(__VA_ARGS__) +#else +#define DEBUG_MSG(...) +#endif #endif // ----------------------------------------------------------------------------- // AXP192 (Rev1-specific options) // ----------------------------------------------------------------------------- -// #define AXP192_SLAVE_ADDRESS 0x34 // Now defined in axp20x.h #define GPS_POWER_CTRL_CH 3 #define LORA_POWER_CTRL_CH 2 diff --git a/src/debug.cpp b/src/debug.cpp index a866d56a9..9d8f19f09 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -2,9 +2,7 @@ #include -#include -#include - +#include "freertosinc.h" #include "configuration.h" namespace meshtastic @@ -12,9 +10,11 @@ namespace meshtastic void printThreadInfo(const char *extra) { +#ifndef NO_ESP32 uint32_t taskHandle = reinterpret_cast(xTaskGetCurrentTaskHandle()); DEBUG_MSG("printThreadInfo(%s) task: %" PRIx32 " core id: %u min free stack: %u\n", extra, taskHandle, xPortGetCoreID(), uxTaskGetStackHighWaterMark(nullptr)); +#endif } } // namespace meshtastic diff --git a/src/error.h b/src/error.h index cc3328c3b..6adcc0ecb 100644 --- a/src/error.h +++ b/src/error.h @@ -1,7 +1,9 @@ #pragma once +#include + /// Error codes for critical error -enum CriticalErrorCode { NoError, ErrTxWatchdog, ErrSleepEnterWait }; +enum CriticalErrorCode { NoError, ErrTxWatchdog, ErrSleepEnterWait, ErrNoRadio, ErrUnspecified }; /// Record an error that should be reported via analytics void recordCriticalError(CriticalErrorCode code, uint32_t address = 0); diff --git a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp b/src/esp32/BluetoothSoftwareUpdate.cpp similarity index 56% rename from lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp rename to src/esp32/BluetoothSoftwareUpdate.cpp index 62bdd499d..0f56cecaa 100644 --- a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp +++ b/src/esp32/BluetoothSoftwareUpdate.cpp @@ -1,21 +1,30 @@ -#include "BluetoothUtil.h" #include "BluetoothSoftwareUpdate.h" -#include "configuration.h" -#include -#include -#include -#include -#include +#include "BluetoothUtil.h" #include "CallbackCharacteristic.h" +#include "RadioLibInterface.h" +#include "configuration.h" +#include "lock.h" +#include +#include +#include +#include +#include + +using namespace meshtastic; CRC32 crc; uint32_t rebootAtMsec = 0; // If not zero we will reboot at this time (used to reboot shortly after the update completes) +uint32_t updateExpectedSize, updateActualSize; + +Lock *updateLock; + class TotalSizeCharacteristic : public CallbackCharacteristic { -public: + public: TotalSizeCharacteristic() - : CallbackCharacteristic("e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ) + : CallbackCharacteristic("e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e", + BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ) { } @@ -23,43 +32,54 @@ public: { BLEKeepAliveCallbacks::onWrite(c); + LockGuard g(updateLock); // Check if there is enough to OTA Update uint32_t len = getValue32(c, 0); + updateExpectedSize = len; + updateActualSize = 0; crc.reset(); bool canBegin = Update.begin(len); DEBUG_MSG("Setting update size %u, result %d\n", len, canBegin); - if (!canBegin) + if (!canBegin) { // Indicate failure by forcing the size to 0 - c->setValue(0UL); - else { + uint32_t zero = 0; + c->setValue(zero); + } else { // This totally breaks abstraction to up up into the app layer for this, but quick hack to make sure we only // talk to one service during the sw update. - //DEBUG_MSG("FIXME, crufty shutdown of mesh bluetooth for sw update."); - //void stopMeshBluetoothService(); - //stopMeshBluetoothService(); + // DEBUG_MSG("FIXME, crufty shutdown of mesh bluetooth for sw update."); + // void stopMeshBluetoothService(); + // stopMeshBluetoothService(); + + if (RadioLibInterface::instance) + RadioLibInterface::instance->sleep(); // FIXME, nasty hack - the RF95 ISR/SPI code on ESP32 can fail while we are + // writing flash - shut the radio off during updates } } }; +#define MAX_BLOCKSIZE 512 + class DataCharacteristic : public CallbackCharacteristic { -public: - DataCharacteristic() - : CallbackCharacteristic( - "e272ebac-d463-4b98-bc84-5cc1a39ee517", BLECharacteristic::PROPERTY_WRITE) - { - } + public: + DataCharacteristic() : CallbackCharacteristic("e272ebac-d463-4b98-bc84-5cc1a39ee517", BLECharacteristic::PROPERTY_WRITE) {} void onWrite(BLECharacteristic *c) { BLEKeepAliveCallbacks::onWrite(c); + LockGuard g(updateLock); std::string value = c->getValue(); uint32_t len = value.length(); - uint8_t *data = c->getData(); + assert(len <= MAX_BLOCKSIZE); + static uint8_t + data[MAX_BLOCKSIZE]; // we temporarily copy here because I'm worried that a fast sender might be able overwrite srcbuf + memcpy(data, c->getData(), len); // DEBUG_MSG("Writing %u\n", len); crc.update(data, len); Update.write(data, len); + updateActualSize += len; } }; @@ -67,49 +87,46 @@ static BLECharacteristic *resultC; class CRC32Characteristic : public CallbackCharacteristic { -public: - CRC32Characteristic() - : CallbackCharacteristic( - "4826129c-c22a-43a3-b066-ce8f0d5bacc6", BLECharacteristic::PROPERTY_WRITE) - { - } + public: + CRC32Characteristic() : CallbackCharacteristic("4826129c-c22a-43a3-b066-ce8f0d5bacc6", BLECharacteristic::PROPERTY_WRITE) {} void onWrite(BLECharacteristic *c) { BLEKeepAliveCallbacks::onWrite(c); + LockGuard g(updateLock); uint32_t expectedCRC = getValue32(c, 0); + uint32_t actualCRC = crc.finalize(); DEBUG_MSG("expected CRC %u\n", expectedCRC); uint8_t result = 0xff; - // Check the CRC before asking the update to happen. - if (crc.finalize() != expectedCRC) + if (updateActualSize != updateExpectedSize) { + DEBUG_MSG("Expected %u bytes, but received %u bytes!\n", updateExpectedSize, updateActualSize); + result = 0xe1; // FIXME, use real error codes + } else if (actualCRC != expectedCRC) // Check the CRC before asking the update to happen. { - DEBUG_MSG("Invalid CRC!\n"); + DEBUG_MSG("Invalid CRC! expected=%u, actual=%u\n", expectedCRC, actualCRC); result = 0xe0; // FIXME, use real error codes - } - else - { - if (Update.end()) - { + } else { + if (Update.end()) { DEBUG_MSG("OTA done, rebooting in 5 seconds!\n"); rebootAtMsec = millis() + 5000; - } - else - { + } else { DEBUG_MSG("Error Occurred. Error #: %d\n", Update.getError()); } result = Update.getError(); } + + if (RadioLibInterface::instance) + RadioLibInterface::instance->startReceive(); // Resume radio + assert(resultC); resultC->setValue(&result, 1); resultC->notify(); } }; - - void bluetoothRebootCheck() { if (rebootAtMsec && millis() > rebootAtMsec) @@ -122,11 +139,15 @@ See bluetooth-api.md */ BLEService *createUpdateService(BLEServer *server, std::string hwVendor, std::string swVersion, std::string hwVersion) { + if (!updateLock) + updateLock = new Lock(); + // Create the BLE Service BLEService *service = server->createService(BLEUUID("cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30"), 25, 0); assert(!resultC); - resultC = new BLECharacteristic("5e134862-7411-4424-ac4a-210937432c77", BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + resultC = new BLECharacteristic("5e134862-7411-4424-ac4a-210937432c77", + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); addWithDesc(service, new TotalSizeCharacteristic, "total image size"); addWithDesc(service, new DataCharacteristic, "data"); @@ -135,7 +156,8 @@ BLEService *createUpdateService(BLEServer *server, std::string hwVendor, std::st resultC->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification - BLECharacteristic *swC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ); + BLECharacteristic *swC = + new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ); swC->setValue(swVersion); service->addCharacteristic(addBLECharacteristic(swC)); @@ -143,7 +165,8 @@ BLEService *createUpdateService(BLEServer *server, std::string hwVendor, std::st mfC->setValue(hwVendor); service->addCharacteristic(addBLECharacteristic(mfC)); - BLECharacteristic *hwvC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ); + BLECharacteristic *hwvC = + new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ); hwvC->setValue(hwVersion); service->addCharacteristic(addBLECharacteristic(hwvC)); diff --git a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.h b/src/esp32/BluetoothSoftwareUpdate.h similarity index 53% rename from lib/BluetoothOTA/src/BluetoothSoftwareUpdate.h rename to src/esp32/BluetoothSoftwareUpdate.h index 60b1f6696..60517a7f2 100644 --- a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.h +++ b/src/esp32/BluetoothSoftwareUpdate.h @@ -1,8 +1,11 @@ #pragma once #include +#include +#include +#include -BLEService *createUpdateService(BLEServer* server, std::string hwVendor, std::string swVersion, std::string hwVersion); +BLEService *createUpdateService(BLEServer *server, std::string hwVendor, std::string swVersion, std::string hwVersion); void destroyUpdateService(); void bluetoothRebootCheck(); \ No newline at end of file diff --git a/src/esp32/BluetoothUtil.cpp b/src/esp32/BluetoothUtil.cpp new file mode 100644 index 000000000..ccaf41639 --- /dev/null +++ b/src/esp32/BluetoothUtil.cpp @@ -0,0 +1,306 @@ +#include "BluetoothUtil.h" +#include "BluetoothSoftwareUpdate.h" +#include "configuration.h" +#include +#include +#include +#include + +SimpleAllocator btPool; + +/** + * Create standard device info service + **/ +BLEService *createDeviceInfomationService(BLEServer *server, std::string hwVendor, std::string swVersion, + std::string hwVersion = "") +{ + BLEService *deviceInfoService = server->createService(BLEUUID((uint16_t)ESP_GATT_UUID_DEVICE_INFO_SVC)); + + BLECharacteristic *swC = + new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ); + BLECharacteristic *mfC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_MANU_NAME), BLECharacteristic::PROPERTY_READ); + // BLECharacteristic SerialNumberCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_SERIAL_NUMBER_STR), + // BLECharacteristic::PROPERTY_READ); + + /* + * Mandatory characteristic for device info service? + + BLECharacteristic *m_pnpCharacteristic = m_deviceInfoService->createCharacteristic(ESP_GATT_UUID_PNP_ID, + BLECharacteristic::PROPERTY_READ); + + uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version; + uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >> + 8), (uint8_t) version }; m_pnpCharacteristic->setValue(pnp, sizeof(pnp)); + */ + swC->setValue(swVersion); + deviceInfoService->addCharacteristic(addBLECharacteristic(swC)); + mfC->setValue(hwVendor); + deviceInfoService->addCharacteristic(addBLECharacteristic(mfC)); + if (!hwVersion.empty()) { + BLECharacteristic *hwvC = + new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ); + hwvC->setValue(hwVersion); + deviceInfoService->addCharacteristic(addBLECharacteristic(hwvC)); + } + // SerialNumberCharacteristic.setValue("FIXME"); + // deviceInfoService->addCharacteristic(&SerialNumberCharacteristic); + + // m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, + // BLECharacteristic::PROPERTY_READ); m_manufacturerCharacteristic->setValue(name); + + /* add these later? + ESP_GATT_UUID_SYSTEM_ID + */ + + // caller must call service->start(); + return deviceInfoService; +} + +bool _BLEClientConnected = false; + +class MyServerCallbacks : public BLEServerCallbacks +{ + void onConnect(BLEServer *pServer) { _BLEClientConnected = true; }; + + void onDisconnect(BLEServer *pServer) { _BLEClientConnected = false; } +}; + +#define MAX_DESCRIPTORS 32 +#define MAX_CHARACTERISTICS 32 + +static BLECharacteristic *chars[MAX_CHARACTERISTICS]; +static size_t numChars; +static BLEDescriptor *descs[MAX_DESCRIPTORS]; +static size_t numDescs; + +/// Add a characteristic that we will delete when we restart +BLECharacteristic *addBLECharacteristic(BLECharacteristic *c) +{ + assert(numChars < MAX_CHARACTERISTICS); + chars[numChars++] = c; + return c; +} + +/// Add a characteristic that we will delete when we restart +BLEDescriptor *addBLEDescriptor(BLEDescriptor *c) +{ + assert(numDescs < MAX_DESCRIPTORS); + descs[numDescs++] = c; + + return c; +} + +// Help routine to add a description to any BLECharacteristic and add it to the service +// We default to require an encrypted BOND for all these these characterstics +void addWithDesc(BLEService *service, BLECharacteristic *c, const char *description) +{ + c->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + + BLEDescriptor *desc = new BLEDescriptor(BLEUUID((uint16_t)ESP_GATT_UUID_CHAR_DESCRIPTION), strlen(description) + 1); + assert(desc); + desc->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + desc->setValue(description); + c->addDescriptor(desc); + service->addCharacteristic(c); + addBLECharacteristic(c); + addBLEDescriptor(desc); +} + +static BLECharacteristic *batteryLevelC; + +/** + * Create a battery level service + */ +BLEService *createBatteryService(BLEServer *server) +{ + // Create the BLE Service + BLEService *pBattery = server->createService(BLEUUID((uint16_t)0x180F)); + + batteryLevelC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_BATTERY_LEVEL), + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + + addWithDesc(pBattery, batteryLevelC, "Percentage 0 - 100"); + batteryLevelC->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification + + // I don't think we need to advertise this + // server->getAdvertising()->addServiceUUID(pBattery->getUUID()); + pBattery->start(); + + return pBattery; +} + +/** + * Update the battery level we are currently telling clients. + * level should be a pct between 0 and 100 + */ +void updateBatteryLevel(uint8_t level) +{ + // Pretend to update battery levels - fixme do elsewhere + if (batteryLevelC) { + batteryLevelC->setValue(&level, 1); + batteryLevelC->notify(); + } +} + +void dumpCharacteristic(BLECharacteristic *c) +{ + std::string value = c->getValue(); + + if (value.length() > 0) { + DEBUG_MSG("New value: "); + for (int i = 0; i < value.length(); i++) + DEBUG_MSG("%c", value[i]); + + DEBUG_MSG("\n"); + } +} + +/** converting endianness pull out a 32 bit value */ +uint32_t getValue32(BLECharacteristic *c, uint32_t defaultValue) +{ + std::string value = c->getValue(); + uint32_t r = defaultValue; + + if (value.length() == 4) + r = value[0] | (value[1] << 8UL) | (value[2] << 16UL) | (value[3] << 24UL); + + return r; +} + +class MySecurity : public BLESecurityCallbacks +{ + protected: + bool onConfirmPIN(uint32_t pin) + { + Serial.printf("onConfirmPIN %u\n", pin); + return false; + } + + uint32_t onPassKeyRequest() + { + Serial.println("onPassKeyRequest"); + return 123511; // not used + } + + void onPassKeyNotify(uint32_t pass_key) + { + Serial.printf("onPassKeyNotify %06u\n", pass_key); + startCb(pass_key); + } + + bool onSecurityRequest() + { + Serial.println("onSecurityRequest"); + return true; + } + + void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) + { + if (cmpl.success) { + uint16_t length; + esp_ble_gap_get_whitelist_size(&length); + Serial.printf(" authenticated and connected to phone\n"); + } else { + Serial.printf("phone authenticate failed %d\n", cmpl.fail_reason); + } + + // Remove our custom PIN request screen. + stopCb(); + } + + public: + StartBluetoothPinScreenCallback startCb; + StopBluetoothPinScreenCallback stopCb; +}; + +BLEServer *pServer; + +BLEService *pDevInfo, *pUpdate; + +void deinitBLE() +{ + assert(pServer); + + pServer->getAdvertising()->stop(); + + destroyUpdateService(); + + pUpdate->stop(); + pDevInfo->stop(); + pUpdate->stop(); // we delete them below + + // First shutdown bluetooth + BLEDevice::deinit(false); + + // do not delete this - it is dynamically allocated, but only once - statically in BLEDevice + // delete pServer->getAdvertising(); + + delete pUpdate; + delete pDevInfo; + delete pServer; + + batteryLevelC = NULL; // Don't let anyone generate bogus notifies + + for (int i = 0; i < numChars; i++) + delete chars[i]; + numChars = 0; + + for (int i = 0; i < numDescs; i++) + delete descs[i]; + numDescs = 0; + + btPool.reset(); +} + +BLEServer *initBLE(StartBluetoothPinScreenCallback startBtPinScreen, StopBluetoothPinScreenCallback stopBtPinScreen, + std::string deviceName, std::string hwVendor, std::string swVersion, std::string hwVersion) +{ + BLEDevice::init(deviceName); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + + /* + * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation + */ + static MySecurity mySecurity; + mySecurity.startCb = startBtPinScreen; + mySecurity.stopCb = stopBtPinScreen; + BLEDevice::setSecurityCallbacks(&mySecurity); + + // Create the BLE Server + pServer = BLEDevice::createServer(); + static MyServerCallbacks myCallbacks; + pServer->setCallbacks(&myCallbacks); + + pDevInfo = createDeviceInfomationService(pServer, hwVendor, swVersion, hwVersion); + + // We now let users create the battery service only if they really want (not all devices have a battery) + // BLEService *pBattery = createBatteryService(pServer); + + pUpdate = createUpdateService(pServer, hwVendor, swVersion, + hwVersion); // We need to advertise this so our android ble scan operation can see it + + // It seems only one service can be advertised - so for now don't advertise our updater + // pServer->getAdvertising()->addServiceUUID(pUpdate->getUUID()); + + // start all our services (do this after creating all of them) + pDevInfo->start(); + pUpdate->start(); + + // FIXME turn on this restriction only after the device is paired with a phone + // advert->setScanFilter(false, true); // We let anyone scan for us (FIXME, perhaps only allow that until we are paired with a + // phone and configured) but only let whitelist phones connect + + static BLESecurity security; // static to avoid allocs + BLESecurity *pSecurity = &security; + pSecurity->setCapability(ESP_IO_CAP_OUT); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + + return pServer; +} + +// Called from loop +void loopBLE() +{ + bluetoothRebootCheck(); +} diff --git a/lib/BluetoothOTA/src/BluetoothUtil.h b/src/esp32/BluetoothUtil.h similarity index 100% rename from lib/BluetoothOTA/src/BluetoothUtil.h rename to src/esp32/BluetoothUtil.h diff --git a/lib/BluetoothOTA/src/CallbackCharacteristic.h b/src/esp32/CallbackCharacteristic.h similarity index 100% rename from lib/BluetoothOTA/src/CallbackCharacteristic.h rename to src/esp32/CallbackCharacteristic.h diff --git a/src/esp32/ESP32CryptoEngine.cpp b/src/esp32/ESP32CryptoEngine.cpp new file mode 100644 index 000000000..bccfae557 --- /dev/null +++ b/src/esp32/ESP32CryptoEngine.cpp @@ -0,0 +1,83 @@ +#include "CryptoEngine.h" +#include "configuration.h" + +#include "crypto/includes.h" + +#include "crypto/common.h" + +// #include "esp_system.h" + +#include "crypto/aes.h" +#include "crypto/aes_wrap.h" +#include "mbedtls/aes.h" + +#define MAX_BLOCKSIZE 256 + +class ESP32CryptoEngine : public CryptoEngine +{ + + mbedtls_aes_context aes; + + /// How many bytes in our key + uint8_t keySize = 0; + + public: + ESP32CryptoEngine() { mbedtls_aes_init(&aes); } + + ~ESP32CryptoEngine() { mbedtls_aes_free(&aes); } + + /** + * Set the key used for encrypt, decrypt. + * + * As a special case: If all bytes are zero, we assume _no encryption_ and send all data in cleartext. + * + * @param numBytes must be 16 (AES128), 32 (AES256) or 0 (no crypt) + * @param bytes a _static_ buffer that will remain valid for the life of this crypto instance (i.e. this class will cache the + * provided pointer) + */ + virtual void setKey(size_t numBytes, uint8_t *bytes) + { + keySize = numBytes; + DEBUG_MSG("Installing AES%d key!\n", numBytes * 8); + if (numBytes != 0) { + auto res = mbedtls_aes_setkey_enc(&aes, bytes, numBytes * 8); + assert(!res); + } + } + + /** + * Encrypt a packet + * + * @param bytes is updated in place + */ + virtual void encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes) + { + if (keySize != 0) { + uint8_t stream_block[16]; + static uint8_t scratch[MAX_BLOCKSIZE]; + size_t nc_off = 0; + + // DEBUG_MSG("ESP32 encrypt!\n"); + initNonce(fromNode, packetNum); + assert(numBytes <= MAX_BLOCKSIZE); + memcpy(scratch, bytes, numBytes); + memset(scratch + numBytes, 0, + sizeof(scratch) - numBytes); // Fill rest of buffer with zero (in case cypher looks at it) + + auto res = mbedtls_aes_crypt_ctr(&aes, numBytes, &nc_off, nonce, stream_block, scratch, bytes); + assert(!res); + } + } + + virtual void decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes) + { + // DEBUG_MSG("ESP32 decrypt!\n"); + + // For CTR, the implementation is the same + encrypt(fromNode, packetNum, numBytes, bytes); + } + + private: +}; + +CryptoEngine *crypto = new ESP32CryptoEngine(); diff --git a/src/MeshBluetoothService.cpp b/src/esp32/MeshBluetoothService.cpp similarity index 82% rename from src/MeshBluetoothService.cpp rename to src/esp32/MeshBluetoothService.cpp index 16af14921..ecda8bf40 100644 --- a/src/MeshBluetoothService.cpp +++ b/src/esp32/MeshBluetoothService.cpp @@ -6,19 +6,44 @@ #include #include "CallbackCharacteristic.h" +#include "GPS.h" #include "MeshService.h" #include "NodeDB.h" +#include "PhoneAPI.h" #include "PowerFSM.h" #include "configuration.h" #include "mesh-pb-constants.h" #include "mesh.pb.h" -#include "GPS.h" - // This scratch buffer is used for various bluetooth reads/writes - but it is safe because only one bt operation can be in // proccess at once static uint8_t trBytes[_max(_max(_max(_max(ToRadio_size, RadioConfig_size), User_size), MyNodeInfo_size), FromRadio_size)]; +static CallbackCharacteristic *meshFromNumCharacteristic; + +BLEService *meshService; + +// If defined we will also support the old API +#define SUPPORT_OLD_BLE_API + +class BluetoothPhoneAPI : public PhoneAPI +{ + /** + * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies) + */ + virtual void onNowHasData(uint32_t fromRadioNum) + { + PhoneAPI::onNowHasData(fromRadioNum); + + if (meshFromNumCharacteristic) { // this ptr might change from sleep to sleep, or even be null + meshFromNumCharacteristic->setValue(fromRadioNum); + meshFromNumCharacteristic->notify(); + } + } +}; + +BluetoothPhoneAPI *bluetoothPhoneAPI; + class ProtobufCharacteristic : public CallbackCharacteristic { const pb_msgdesc_t *fields; @@ -58,6 +83,7 @@ class ProtobufCharacteristic : public CallbackCharacteristic } }; +#ifdef SUPPORT_OLD_BLE_API class NodeInfoCharacteristic : public BLECharacteristic, public BLEKeepAliveCallbacks { public: @@ -114,7 +140,7 @@ class RadioCharacteristic : public ProtobufCharacteristic { DEBUG_MSG("Writing radio config\n"); ProtobufCharacteristic::onWrite(c); - service.reloadConfig(); + bluetoothPhoneAPI->handleSetRadio(radioConfig); } }; @@ -135,92 +161,11 @@ class OwnerCharacteristic : public ProtobufCharacteristic static User o; // if the phone doesn't set ID we are careful to keep ours, we also always keep our macaddr if (writeToDest(c, &o)) { - int changed = 0; - - if (*o.long_name) { - changed |= strcmp(owner.long_name, o.long_name); - strcpy(owner.long_name, o.long_name); - } - if (*o.short_name) { - changed |= strcmp(owner.short_name, o.short_name); - strcpy(owner.short_name, o.short_name); - } - if (*o.id) { - changed |= strcmp(owner.id, o.id); - strcpy(owner.id, o.id); - } - - if (changed) // If nothing really changed, don't broadcast on the network or write to flash - service.reloadOwner(); + bluetoothPhoneAPI->handleSetOwner(o); } } }; -class ToRadioCharacteristic : public CallbackCharacteristic -{ - public: - ToRadioCharacteristic() : CallbackCharacteristic("f75c76d2-129e-4dad-a1dd-7866124401e7", BLECharacteristic::PROPERTY_WRITE) {} - - void onWrite(BLECharacteristic *c) - { - BLEKeepAliveCallbacks::onWrite(c); - DEBUG_MSG("Got on write\n"); - - service.handleToRadio(c->getValue()); - } -}; - -class FromRadioCharacteristic : public CallbackCharacteristic -{ - public: - FromRadioCharacteristic() : CallbackCharacteristic("8ba2bcc2-ee02-4a55-a531-c525c5e454d5", BLECharacteristic::PROPERTY_READ) - { - } - - void onRead(BLECharacteristic *c) - { - BLEKeepAliveCallbacks::onRead(c); - MeshPacket *mp = service.getForPhone(); - - // Someone is going to read our value as soon as this callback returns. So fill it with the next message in the queue - // or make empty if the queue is empty - if (!mp) { - DEBUG_MSG("toPhone queue is empty\n"); - c->setValue((uint8_t *)"", 0); - } else { - static FromRadio fRadio; - - // Encapsulate as a FromRadio packet - memset(&fRadio, 0, sizeof(fRadio)); - fRadio.which_variant = FromRadio_packet_tag; - fRadio.variant.packet = *mp; - - size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), FromRadio_fields, &fRadio); - DEBUG_MSG("delivering toPhone packet to phone %d bytes\n", numbytes); - c->setValue(trBytes, numbytes); - - service.releaseToPool(mp); // we just copied the bytes, so don't need this buffer anymore - } - } -}; - -class FromNumCharacteristic : public CallbackCharacteristic -{ - public: - FromNumCharacteristic() - : CallbackCharacteristic("ed9da18c-a800-4f66-a670-aa7547e34453", BLECharacteristic::PROPERTY_WRITE | - BLECharacteristic::PROPERTY_READ | - BLECharacteristic::PROPERTY_NOTIFY) - { - } - - void onRead(BLECharacteristic *c) - { - BLEKeepAliveCallbacks::onRead(c); - DEBUG_MSG("FIXME implement fromnum read\n"); - } -}; - class MyNodeInfoCharacteristic : public ProtobufCharacteristic { public: @@ -233,7 +178,7 @@ class MyNodeInfoCharacteristic : public ProtobufCharacteristic void onRead(BLECharacteristic *c) { // update gps connection state - myNodeInfo.has_gps = gps.isConnected; + myNodeInfo.has_gps = gps->isConnected; ProtobufCharacteristic::onRead(c); @@ -242,27 +187,73 @@ class MyNodeInfoCharacteristic : public ProtobufCharacteristic } }; -FromNumCharacteristic *meshFromNumCharacteristic; +#endif -/** - * Tell any bluetooth clients that the number of rx packets has changed - */ -void bluetoothNotifyFromNum(uint32_t newValue) +class ToRadioCharacteristic : public CallbackCharacteristic { - if (meshFromNumCharacteristic) { - // if bt not running ignore - meshFromNumCharacteristic->setValue(newValue); - meshFromNumCharacteristic->notify(); - } -} + public: + ToRadioCharacteristic() : CallbackCharacteristic("f75c76d2-129e-4dad-a1dd-7866124401e7", BLECharacteristic::PROPERTY_WRITE) {} -BLEService *meshService; + void onWrite(BLECharacteristic *c) + { + BLEKeepAliveCallbacks::onWrite(c); + DEBUG_MSG("Got on write\n"); + + bluetoothPhoneAPI->handleToRadio(c->getData(), c->getValue().length()); + } +}; + +class FromRadioCharacteristic : public CallbackCharacteristic +{ + public: + FromRadioCharacteristic() : CallbackCharacteristic("8ba2bcc2-ee02-4a55-a531-c525c5e454d5", BLECharacteristic::PROPERTY_READ) + { + } + + void onRead(BLECharacteristic *c) + { + BLEKeepAliveCallbacks::onRead(c); + size_t numBytes = bluetoothPhoneAPI->getFromRadio(trBytes); + + // Someone is going to read our value as soon as this callback returns. So fill it with the next message in the queue + // or make empty if the queue is empty + if (numBytes) { + c->setValue(trBytes, numBytes); + } else { + c->setValue((uint8_t *)"", 0); + } + } +}; + +class FromNumCharacteristic : public CallbackCharacteristic +{ + public: + FromNumCharacteristic() + : CallbackCharacteristic("ed9da18c-a800-4f66-a670-aa7547e34453", BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_NOTIFY) + { + // observe(&service.fromNumChanged); + } + + void onRead(BLECharacteristic *c) + { + BLEKeepAliveCallbacks::onRead(c); + DEBUG_MSG("FIXME implement fromnum read\n"); + } +}; /* See bluetooth-api.md for documentation. */ BLEService *createMeshBluetoothService(BLEServer *server) { + // Only create our phone API object once + if (!bluetoothPhoneAPI) { + bluetoothPhoneAPI = new BluetoothPhoneAPI(); + bluetoothPhoneAPI->init(); + } + // Create the BLE Service, we need more than the default of 15 handles BLEService *service = server->createService(BLEUUID("6ba1b218-15a8-461f-9fa8-5dcae273eafd"), 30, 0); @@ -272,10 +263,12 @@ BLEService *createMeshBluetoothService(BLEServer *server) addWithDesc(service, meshFromNumCharacteristic, "fromRadio"); addWithDesc(service, new ToRadioCharacteristic, "toRadio"); addWithDesc(service, new FromRadioCharacteristic, "fromNum"); +#ifdef SUPPORT_OLD_BLE_API addWithDesc(service, new MyNodeInfoCharacteristic, "myNode"); addWithDesc(service, new RadioCharacteristic, "radio"); addWithDesc(service, new OwnerCharacteristic, "owner"); addWithDesc(service, new NodeInfoCharacteristic, "nodeinfo"); +#endif meshFromNumCharacteristic->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification diff --git a/src/MeshBluetoothService.h b/src/esp32/MeshBluetoothService.h similarity index 100% rename from src/MeshBluetoothService.h rename to src/esp32/MeshBluetoothService.h diff --git a/lib/BluetoothOTA/src/SimpleAllocator.cpp b/src/esp32/SimpleAllocator.cpp similarity index 100% rename from lib/BluetoothOTA/src/SimpleAllocator.cpp rename to src/esp32/SimpleAllocator.cpp diff --git a/lib/BluetoothOTA/src/SimpleAllocator.h b/src/esp32/SimpleAllocator.h similarity index 100% rename from lib/BluetoothOTA/src/SimpleAllocator.h rename to src/esp32/SimpleAllocator.h diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp new file mode 100644 index 000000000..7f9780862 --- /dev/null +++ b/src/esp32/main-esp32.cpp @@ -0,0 +1,234 @@ +#include "BluetoothUtil.h" +#include "MeshBluetoothService.h" +#include "PowerFSM.h" +#include "configuration.h" +#include "main.h" +#include "power.h" +#include "target_specific.h" + +bool bluetoothOn; + +// This routine is called multiple times, once each time we come back from sleep +void reinitBluetooth() +{ + DEBUG_MSG("Starting bluetooth\n"); + + // FIXME - we are leaking like crazy + // AllocatorScope scope(btPool); + + // Note: these callbacks might be coming in from a different thread. + BLEServer *serve = initBLE( + [](uint32_t pin) { + powerFSM.trigger(EVENT_BLUETOOTH_PAIR); + screen.startBluetoothPinScreen(pin); + }, + []() { screen.stopBluetoothPinScreen(); }, getDeviceName(), HW_VENDOR, optstr(APP_VERSION), + optstr(HW_VERSION)); // FIXME, use a real name based on the macaddr + createMeshBluetoothService(serve); + + // Start advertising - this must be done _after_ creating all services + serve->getAdvertising()->start(); +} + +// Enable/disable bluetooth. +void setBluetoothEnable(bool on) +{ + if (on != bluetoothOn) { + DEBUG_MSG("Setting bluetooth enable=%d\n", on); + + bluetoothOn = on; + if (on) { + Serial.printf("Pre BT: %u heap size\n", ESP.getFreeHeap()); + // ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) ); + reinitBluetooth(); + } else { + // We have to totally teardown our bluetooth objects to prevent leaks + stopMeshBluetoothService(); // Must do before shutting down bluetooth + deinitBLE(); + destroyMeshBluetoothService(); // must do after deinit, because it frees our service + Serial.printf("Shutdown BT: %u heap size\n", ESP.getFreeHeap()); + // ESP_ERROR_CHECK( heap_trace_stop() ); + // heap_trace_dump(); + } + } +} + +void getMacAddr(uint8_t *dmac) +{ + assert(esp_efuse_mac_get_default(dmac) == ESP_OK); +} + +#ifdef TBEAM_V10 + +// FIXME. nasty hack cleanup how we load axp192 +#undef AXP192_SLAVE_ADDRESS +#include "axp20x.h" +AXP20X_Class axp; +bool pmu_irq = false; + +/// Reads power status to powerStatus singleton. +// +// TODO(girts): move this and other axp stuff to power.h/power.cpp. +void readPowerStatus() +{ + powerStatus.haveBattery = axp.isBatteryConnect(); + if (powerStatus.haveBattery) { + powerStatus.batteryVoltageMv = axp.getBattVoltage(); + } + powerStatus.usb = axp.isVBUSPlug(); + powerStatus.charging = axp.isChargeing(); +} +#endif // TBEAM_V10 + +#ifdef AXP192_SLAVE_ADDRESS +/** + * Init the power manager chip + * + * axp192 power + DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the axp192 + share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this on!) LDO1 + 30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can + not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS + */ +void axp192Init() +{ + if (axp192_found) { + if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS)) { + DEBUG_MSG("AXP192 Begin PASS\n"); + + // axp.setChgLEDMode(LED_BLINK_4HZ); + DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("----------------------------------------\n"); + + axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio + axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power + axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON); + axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON); + axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON); + axp.setDCDC1Voltage(3300); // for the OLED power + + DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE"); + + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1320MA); // actual limit (in HW) on the tbeam is 450mA +#if 0 + + // Not connected + //val = 0xfc; + //axp._writeByte(AXP202_VHTF_CHGSET, 1, &val); // Set temperature protection + + //not used + //val = 0x46; + //axp._writeByte(AXP202_OFF_CTL, 1, &val); // enable bat detection +#endif + axp.debugCharging(); + +#ifdef PMU_IRQ + pinMode(PMU_IRQ, INPUT); + attachInterrupt( + PMU_IRQ, [] { pmu_irq = true; }, FALLING); + + axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1); + axp.enableIRQ(AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ | AXP202_CHARGING_IRQ | + AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ, + 1); + + axp.clearIRQ(); +#endif + readPowerStatus(); + } else { + DEBUG_MSG("AXP192 Begin FAIL\n"); + } + } else { + DEBUG_MSG("AXP192 not found\n"); + } +} +#endif + +void esp32Setup() +{ + randomSeed(esp_random()); // ESP docs say this is fairly random + +#ifdef AXP192_SLAVE_ADDRESS + axp192Init(); +#endif +} + +#if 0 +// Turn off for now + +uint32_t axpDebugRead() +{ + axp.debugCharging(); + DEBUG_MSG("vbus current %f\n", axp.getVbusCurrent()); + DEBUG_MSG("charge current %f\n", axp.getBattChargeCurrent()); + DEBUG_MSG("bat voltage %f\n", axp.getBattVoltage()); + DEBUG_MSG("batt pct %d\n", axp.getBattPercentage()); + DEBUG_MSG("is battery connected %d\n", axp.isBatteryConnect()); + DEBUG_MSG("is USB connected %d\n", axp.isVBUSPlug()); + DEBUG_MSG("is charging %d\n", axp.isChargeing()); + + return 30 * 1000; +} + +Periodic axpDebugOutput(axpDebugRead); +#endif + +#define MIN_BAT_MILLIVOLTS 3690 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/ + +/// loop code specific to ESP32 targets +void esp32Loop() +{ + loopBLE(); + + // for debug printing + // radio.radioIf.canSleep(); + +#ifdef PMU_IRQ + if (pmu_irq) { + pmu_irq = false; + axp.readIRQ(); + + DEBUG_MSG("pmu irq!\n"); + + if (axp.isChargingIRQ()) { + DEBUG_MSG("Battery start charging\n"); + } + if (axp.isChargingDoneIRQ()) { + DEBUG_MSG("Battery fully charged\n"); + } + if (axp.isVbusRemoveIRQ()) { + DEBUG_MSG("USB unplugged\n"); + } + if (axp.isVbusPlugInIRQ()) { + DEBUG_MSG("USB plugged In\n"); + } + if (axp.isBattPlugInIRQ()) { + DEBUG_MSG("Battery inserted\n"); + } + if (axp.isBattRemoveIRQ()) { + DEBUG_MSG("Battery removed\n"); + } + if (axp.isPEKShortPressIRQ()) { + DEBUG_MSG("PEK short button press\n"); + } + + readPowerStatus(); + axp.clearIRQ(); + } + + if (powerStatus.haveBattery && !powerStatus.usb && + axp.getBattVoltage() < MIN_BAT_MILLIVOLTS) // If we have a battery at all and it is less than 10% full, force deep sleep + powerFSM.trigger(EVENT_LOW_BATTERY); + +#endif // T_BEAM_V10 +} \ No newline at end of file diff --git a/src/freertosinc.h b/src/freertosinc.h new file mode 100644 index 000000000..0d86ee2c9 --- /dev/null +++ b/src/freertosinc.h @@ -0,0 +1,16 @@ +#pragma once + +// The FreeRTOS includes are in a different directory on ESP32 and I can't figure out how to make that work with platformio gcc options +// so this is my quick hack to make things work + +#ifdef ARDUINO_ARCH_ESP32 +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif \ No newline at end of file diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp new file mode 100644 index 000000000..bb2d30b54 --- /dev/null +++ b/src/gps/GPS.cpp @@ -0,0 +1,81 @@ + +#include "GPS.h" +#include "configuration.h" +#include "time.h" +#include +#include + +#ifdef GPS_RX_PIN +HardwareSerial _serial_gps_real(GPS_SERIAL_NUM); +HardwareSerial &GPS::_serial_gps = _serial_gps_real; +#else +// Assume NRF52 +HardwareSerial &GPS::_serial_gps = Serial1; +#endif + +bool timeSetFromGPS; // We try to set our time from GPS each time we wake from sleep + +GPS *gps; + +// stuff that really should be in in the instance instead... +static uint32_t + timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time +static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock + +void readFromRTC() +{ + struct timeval tv; /* btw settimeofday() is helpfull here too*/ + + if (!gettimeofday(&tv, NULL)) { + uint32_t now = millis(); + + DEBUG_MSG("Read RTC time as %ld (cur millis %u) valid=%d\n", tv.tv_sec, now, timeSetFromGPS); + timeStartMsec = now; + zeroOffsetSecs = tv.tv_sec; + } +} + +/// If we haven't yet set our RTC this boot, set it from a GPS derived time +void perhapsSetRTC(const struct timeval *tv) +{ + if (!timeSetFromGPS) { + timeSetFromGPS = true; + DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec); +#ifndef NO_ESP32 + settimeofday(tv, NULL); +#else + DEBUG_MSG("ERROR TIME SETTING NOT IMPLEMENTED!\n"); +#endif + readFromRTC(); + } +} + +void perhapsSetRTC(struct tm &t) +{ + /* Convert to unix time + The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 + (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). + */ + time_t res = mktime(&t); + struct timeval tv; + tv.tv_sec = res; + tv.tv_usec = 0; // time.centisecond() * (10 / 1000); + + // DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec); + if (t.tm_year < 0 || t.tm_year >= 300) + DEBUG_MSG("Ignoring invalid GPS time\n"); + else + perhapsSetRTC(&tv); +} + +#include + +uint32_t getTime() +{ + return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs; +} + +uint32_t getValidTime() +{ + return timeSetFromGPS ? getTime() : 0; +} diff --git a/src/gps/GPS.h b/src/gps/GPS.h new file mode 100644 index 000000000..3eb972843 --- /dev/null +++ b/src/gps/GPS.h @@ -0,0 +1,55 @@ +#pragma once + +#include "Observer.h" +#include "PeriodicTask.h" +#include "sys/time.h" + +/// If we haven't yet set our RTC this boot, set it from a GPS derived time +void perhapsSetRTC(const struct timeval *tv); +void perhapsSetRTC(struct tm &t); + +/// Return time since 1970 in secs. Until we have a GPS lock we will be returning time based at zero +uint32_t getTime(); + +/// Return time since 1970 in secs. If we don't have a GPS lock return zero +uint32_t getValidTime(); + +void readFromRTC(); + +/** + * A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading) + * + * When new data is available it will notify observers. + */ +class GPS : public Observable +{ + protected: + bool hasValidLocation = false; // default to false, until we complete our first read + + static HardwareSerial &_serial_gps; + + public: + int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double + int32_t altitude = 0; + bool isConnected = false; // Do we have a GPS we are talking to + + virtual ~GPS() {} + + /** + * Returns true if we succeeded + */ + virtual bool setup() { return true; } + + /// A loop callback for subclasses that need it. FIXME, instead just block on serial reads + virtual void loop() {} + + /// Returns ture if we have acquired GPS lock. + bool hasLock() const { return hasValidLocation; } + + /** + * Restart our lock attempt - try to get and broadcast a GPS reading ASAP + * called after the CPU wakes from light-sleep state */ + virtual void startLock() {} +}; + +extern GPS *gps; diff --git a/src/gps/NEMAGPS.cpp b/src/gps/NEMAGPS.cpp new file mode 100644 index 000000000..7d19f8869 --- /dev/null +++ b/src/gps/NEMAGPS.cpp @@ -0,0 +1,65 @@ +#include "NEMAGPS.h" +#include "configuration.h" + +static int32_t toDegInt(RawDegrees d) +{ + int32_t degMult = 10000000; // 1e7 + int32_t r = d.deg * degMult + d.billionths / 100; + if (d.negative) + r *= -1; + return r; +} + +void NEMAGPS::loop() +{ + + while (_serial_gps.available() > 0) { + int c = _serial_gps.read(); + // Serial.write(c); + reader.encode(c); + } + + uint32_t now = millis(); + if ((now - lastUpdateMsec) > 20 * 1000) { // Ugly hack for now - limit update checks to once every 20 secs (but still consume + // serial chars at whatever rate) + lastUpdateMsec = now; + + auto ti = reader.time; + auto d = reader.date; + if (ti.isUpdated() && ti.isValid() && d.isValid()) { + /* Convert to unix time + The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 + (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). + */ + struct tm t; + t.tm_sec = ti.second(); + t.tm_min = ti.minute(); + t.tm_hour = ti.hour(); + t.tm_mday = d.day(); + t.tm_mon = d.month() - 1; + t.tm_year = d.year() - 1900; + t.tm_isdst = false; + perhapsSetRTC(t); + + isConnected = true; // we seem to have a real GPS (but not necessarily a lock) + } + + if (reader.location.isUpdated()) { + if (reader.altitude.isValid()) + altitude = reader.altitude.meters(); + + if (reader.location.isValid()) { + auto loc = reader.location.value(); + latitude = toDegInt(loc.lat); + longitude = toDegInt(loc.lng); + } + + // expect gps pos lat=37.520825, lon=-122.309162, alt=158 + DEBUG_MSG("new NEMA GPS pos lat=%f, lon=%f, alt=%d\n", latitude * 1e-7, longitude * 1e-7, altitude); + + hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0 + if (hasValidLocation) + notifyObservers(NULL); + } + } +} \ No newline at end of file diff --git a/src/gps/NEMAGPS.h b/src/gps/NEMAGPS.h new file mode 100644 index 000000000..5bea0d41f --- /dev/null +++ b/src/gps/NEMAGPS.h @@ -0,0 +1,21 @@ +#pragma once + +#include "GPS.h" +#include "Observer.h" +#include "PeriodicTask.h" +#include "TinyGPS++.h" + +/** + * A gps class thatreads from a NEMA GPS stream (and FIXME - eventually keeps the gps powered down except when reading) + * + * When new data is available it will notify observers. + */ +class NEMAGPS : public GPS +{ + TinyGPSPlus reader; + + uint32_t lastUpdateMsec = 0; + + public: + virtual void loop(); +}; diff --git a/src/gps/UBloxGPS.cpp b/src/gps/UBloxGPS.cpp new file mode 100644 index 000000000..ca8f955c5 --- /dev/null +++ b/src/gps/UBloxGPS.cpp @@ -0,0 +1,139 @@ +#include "UBloxGPS.h" +#include "sleep.h" +#include + +UBloxGPS::UBloxGPS() : PeriodicTask() +{ + notifySleepObserver.observe(¬ifySleep); +} + +bool UBloxGPS::setup() +{ +#ifdef GPS_RX_PIN + _serial_gps.begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN); +#else + _serial_gps.begin(GPS_BAUDRATE); +#endif + // _serial_gps.setRxBufferSize(1024); // the default is 256 + // ublox.enableDebugging(Serial); + + // note: the lib's implementation has the wrong docs for what the return val is + // it is not a bool, it returns zero for success + isConnected = ublox.begin(_serial_gps); + + // try a second time, the ublox lib serial parsing is buggy? + if (!isConnected) + isConnected = ublox.begin(_serial_gps); + + if (isConnected) { + DEBUG_MSG("Connected to UBLOX GPS successfully\n"); + + bool factoryReset = false; + bool ok; + if (factoryReset) { + // It is useful to force back into factory defaults (9600baud, NEMA to test the behavior of boards that don't have + // GPS_TX connected) + ublox.factoryReset(); + delay(3000); + isConnected = ublox.begin(_serial_gps); + DEBUG_MSG("Factory reset success=%d\n", isConnected); + ok = ublox.saveConfiguration(3000); + assert(ok); + return false; + } else { + ok = ublox.setUART1Output(COM_TYPE_UBX, 500); // Use native API + assert(ok); + ok = ublox.setNavigationFrequency(1, 500); // Produce 4x/sec to keep the amount of time we stall in getPVT low + assert(ok); + // ok = ublox.setAutoPVT(false); // Not implemented on NEO-6M + // assert(ok); + // ok = ublox.setDynamicModel(DYN_MODEL_BIKE); // probably PEDESTRIAN but just in case assume bike speeds + // assert(ok); + ok = ublox.powerSaveMode(); // use power save mode + assert(ok); + } + ok = ublox.saveConfiguration(3000); + assert(ok); + + PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device + + return true; + } else { + return false; + } +} + +/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs +int UBloxGPS::prepareSleep(void *unused) +{ + if (isConnected) + ublox.powerOff(); + + return 0; +} + +void UBloxGPS::doTask() +{ + uint8_t fixtype = 3; // If we are only using the RX pin, assume we have a 3d fix + + assert(isConnected); + + // Consume all characters that have arrived + + // getPVT automatically calls checkUblox + ublox.checkUblox(); // See if new data is available. Process bytes as they come in. + + // If we don't have a fix (a quick check), don't try waiting for a solution) + // Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions + // turn off for now + // fixtype = ublox.getFixType(); + // DEBUG_MSG("fix type %d\n", fixtype); + + // DEBUG_MSG("sec %d\n", ublox.getSecond()); + // DEBUG_MSG("lat %d\n", ublox.getLatitude()); + + // any fix that has time + if (ublox.getT()) { + /* Convert to unix time +The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 +(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). +*/ + struct tm t; + t.tm_sec = ublox.getSecond(); + t.tm_min = ublox.getMinute(); + t.tm_hour = ublox.getHour(); + t.tm_mday = ublox.getDay(); + t.tm_mon = ublox.getMonth() - 1; + t.tm_year = ublox.getYear() - 1900; + t.tm_isdst = false; + perhapsSetRTC(t); + } + + if ((fixtype >= 3 && fixtype <= 4) && ublox.getP()) // rd fixes only + { + // we only notify if position has changed + latitude = ublox.getLatitude(); + longitude = ublox.getLongitude(); + altitude = ublox.getAltitude() / 1000; // in mm convert to meters + DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d\n", latitude * 1e-7, longitude * 1e-7, altitude); + + hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0 + if (hasValidLocation) { + wantNewLocation = false; + notifyObservers(NULL); + // ublox.powerOff(); + } + } else // we didn't get a location update, go back to sleep and hope the characters show up + wantNewLocation = true; + + // Once we have sent a location once we only poll the GPS rarely, otherwise check back every 1s until we have something over + // the serial + setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000); +} + +void UBloxGPS::startLock() +{ + DEBUG_MSG("Looking for GPS lock\n"); + wantNewLocation = true; + setPeriod(1); +} diff --git a/src/gps/UBloxGPS.h b/src/gps/UBloxGPS.h new file mode 100644 index 000000000..39b125981 --- /dev/null +++ b/src/gps/UBloxGPS.h @@ -0,0 +1,41 @@ +#pragma once + +#include "GPS.h" +#include "Observer.h" +#include "PeriodicTask.h" +#include "SparkFun_Ublox_Arduino_Library.h" + +/** + * A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading) + * + * When new data is available it will notify observers. + */ +class UBloxGPS : public GPS, public PeriodicTask +{ + SFE_UBLOX_GPS ublox; + + bool wantNewLocation = true; + + CallbackObserver notifySleepObserver = CallbackObserver(this, &UBloxGPS::prepareSleep); + + public: + UBloxGPS(); + + /** + * Returns true if we succeeded + */ + virtual bool setup(); + + virtual void doTask(); + + /** + * Restart our lock attempt - try to get and broadcast a GPS reading ASAP + * called after the CPU wakes from light-sleep state */ + virtual void startLock(); + + private: + + /// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs + /// always returns 0 to indicate okay to sleep + int prepareSleep(void *unused); +}; diff --git a/src/lock.h b/src/lock.h index 57d466a89..a6afa2f6c 100644 --- a/src/lock.h +++ b/src/lock.h @@ -1,7 +1,6 @@ #pragma once -#include -#include +#include "freertosinc.h" namespace meshtastic { diff --git a/src/main.cpp b/src/main.cpp index 910d45cfe..3103335b5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,45 +21,40 @@ */ -#include "BluetoothUtil.h" -#include "GPS.h" -#include "MeshBluetoothService.h" #include "MeshRadio.h" #include "MeshService.h" +#include "NEMAGPS.h" #include "NodeDB.h" #include "Periodic.h" #include "PowerFSM.h" +#include "Router.h" +#include "UBloxGPS.h" #include "configuration.h" -#include "esp32/pm.h" -#include "esp_pm.h" +#include "error.h" #include "power.h" -#include "rom/rtc.h" +// #include "rom/rtc.h" +#include "ReliableRouter.h" +#include "main.h" #include "screen.h" #include "sleep.h" #include -#include +// #include -#ifdef TBEAM_V10 -#include "axp20x.h" -AXP20X_Class axp; -bool pmu_irq = false; +#ifndef NO_ESP32 +#include "BluetoothUtil.h" #endif -// Global Screen singleton -#ifdef I2C_SDA -meshtastic::Screen screen(SSD1306_ADDRESS, I2C_SDA, I2C_SCL); -#else -// Fake values for pins to keep build happy, we won't ever initialize it. -meshtastic::Screen screen(SSD1306_ADDRESS, 0, 0); -#endif +// We always create a screen object, but we only init it if we find the hardware +meshtastic::Screen screen(SSD1306_ADDRESS); // Global power status singleton -static meshtastic::PowerStatus powerStatus; +meshtastic::PowerStatus powerStatus; bool ssd1306_found; bool axp192_found; -bool bluetoothOn; +ReliableRouter realRouter; +Router &router = realRouter; // Users of router don't care what sort of subclass implements that API // ----------------------------------------------------------------------------- // Application @@ -81,7 +76,7 @@ void scanI2Cdevice(void) ssd1306_found = true; DEBUG_MSG("ssd1306 display found\n"); } -#ifdef TBEAM_V10 +#ifdef AXP192_SLAVE_ADDRESS if (addr == AXP192_SLAVE_ADDRESS) { axp192_found = true; DEBUG_MSG("axp192 PMU found\n"); @@ -97,107 +92,11 @@ void scanI2Cdevice(void) DEBUG_MSG("done\n"); } -#ifdef TBEAM_V10 -/// Reads power status to powerStatus singleton. -// -// TODO(girts): move this and other axp stuff to power.h/power.cpp. -void readPowerStatus() -{ - powerStatus.haveBattery = axp.isBatteryConnect(); - if (powerStatus.haveBattery) { - powerStatus.batteryVoltageMv = axp.getBattVoltage(); - } - powerStatus.usb = axp.isVBUSPlug(); - powerStatus.charging = axp.isChargeing(); -} -#endif // TBEAM_V10 - -/** - * Init the power manager chip - * - * axp192 power - DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the axp192 - share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this on!) LDO1 - 30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can - not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS - */ -void axp192Init() -{ -#ifdef TBEAM_V10 - if (axp192_found) { - if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS)) { - DEBUG_MSG("AXP192 Begin PASS\n"); - - // axp.setChgLEDMode(LED_BLINK_4HZ); - DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("----------------------------------------\n"); - - axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio - axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power - axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON); - axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON); - axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON); - axp.setDCDC1Voltage(3300); // for the OLED power - - DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE"); - -#if 0 - // cribbing from https://github.com/m5stack/M5StickC/blob/master/src/AXP192.cpp to fix charger to be more like 300ms. - // I finally found an english datasheet. Will look at this later - but suffice it to say the default code from TTGO has 'issues' - - axp.adc1Enable(0xff, 1); // turn on all adcs - uint8_t val = 0xc2; - axp._writeByte(0x33, 1, &val); // Bat charge voltage to 4.2, Current 280mA - val = 0b11110010; - // Set ADC sample rate to 200hz - // axp._writeByte(0x84, 1, &val); - - // Not connected - //val = 0xfc; - //axp._writeByte(AXP202_VHTF_CHGSET, 1, &val); // Set temperature protection - - //not used - //val = 0x46; - //axp._writeByte(AXP202_OFF_CTL, 1, &val); // enable bat detection -#endif - axp.debugCharging(); - -#ifdef PMU_IRQ - pinMode(PMU_IRQ, INPUT); - attachInterrupt( - PMU_IRQ, [] { pmu_irq = true; }, FALLING); - - axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1); - axp.enableIRQ(AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ | AXP202_CHARGING_IRQ | - AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ, - 1); - - axp.clearIRQ(); -#endif - readPowerStatus(); - } else { - DEBUG_MSG("AXP192 Begin FAIL\n"); - } - } else { - DEBUG_MSG("AXP192 not found\n"); - } -#endif -} - const char *getDeviceName() { uint8_t dmac[6]; - assert(esp_efuse_mac_get_default(dmac) == ESP_OK); + + getMacAddr(dmac); // Meshtastic_ab3c static char name[20]; @@ -205,11 +104,35 @@ const char *getDeviceName() return name; } +static uint32_t ledBlinker() +{ + static bool ledOn; + ledOn ^= 1; + + setLed(ledOn); + + // have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that + return powerStatus.charging ? 1000 : (ledOn ? 2 : 1000); +} + +Periodic ledPeriodic(ledBlinker); + +#include "RF95Interface.h" +#include "SX1262Interface.h" + +#ifdef NO_ESP32 +#include "variant.h" +#endif + void setup() { +#ifdef USE_SEGGER + SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_TRIM); +#endif + // Debug #ifdef DEBUG_PORT - DEBUG_PORT.begin(SERIAL_BAUD); + DEBUG_PORT.init(); // Set serial baud rate and init our mesh console #endif initDeepSleep(); @@ -226,8 +149,10 @@ void setup() #ifdef I2C_SDA Wire.begin(I2C_SDA, I2C_SCL); - scanI2Cdevice(); +#else + Wire.begin(); #endif + scanI2Cdevice(); // Buttons & LED #ifdef BUTTON_PIN @@ -236,29 +161,84 @@ void setup() #endif #ifdef LED_PIN pinMode(LED_PIN, OUTPUT); - digitalWrite(LED_PIN, 1); // turn on for now + digitalWrite(LED_PIN, 1 ^ LED_INVERTED); // turn on for now #endif - // Hello - DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", xstr(APP_VERSION), xstr(HW_VERSION)); + ledPeriodic.setup(); + // Hello + DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", optstr(APP_VERSION), optstr(HW_VERSION)); + +#ifndef NO_ESP32 // Don't init display if we don't have one or we are waking headless due to a timer event if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) ssd1306_found = false; // forget we even have the hardware + esp32Setup(); +#endif + +#ifdef NRF52_SERIES + nrf52Setup(); +#endif + // Initialize the screen first so we can show the logo while we start up everything else. if (ssd1306_found) screen.setup(); - axp192Init(); - screen.print("Started...\n"); - // Init GPS - gps.setup(); + readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time) + +// If we know we have a L80 GPS, don't try UBLOX +#ifndef L80_RESET + // Init GPS - first try ublox + gps = new UBloxGPS(); + if (!gps->setup()) { + // Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just + // assume NEMA at 9600 baud. + DEBUG_MSG("ERROR: No UBLOX GPS found, hoping that NEMA might work\n"); + delete gps; + gps = new NEMAGPS(); + gps->setup(); + } +#else + gps = new NEMAGPS(); + gps->setup(); +#endif service.init(); +#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); +#endif + + // Init our SPI controller +#ifdef NRF52_SERIES + SPI.begin(); +#else + // ESP32 + SPI.begin(SCK_GPIO, MISO_GPIO, MOSI_GPIO, NSS_GPIO); + SPI.setFrequency(4000000); +#endif + + // MUST BE AFTER service.init, so we have our radio config settings (from nodedb init) + RadioInterface *rIf = +#if defined(RF95_IRQ_GPIO) + // new CustomRF95(); old Radiohead based driver + new RF95Interface(NSS_GPIO, RF95_IRQ_GPIO, RESET_GPIO, SPI); +#elif defined(SX1262_CS) + new SX1262Interface(SX1262_CS, SX1262_DIO1, SX1262_RESET, SX1262_BUSY, SPI); +#else + new SimRadio(); +#endif + + router.addInterface(rIf); + + if (!rIf->init()) + recordCriticalError(ErrNoRadio); + // This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values PowerFSM_setup(); // we will transition to ON in a couple of seconds, FIXME, only do this for cold boots, not waking from SDS @@ -266,62 +246,6 @@ void setup() setCPUFast(false); // 80MHz is fine for our slow peripherals } -void initBluetooth() -{ - DEBUG_MSG("Starting bluetooth\n"); - - // FIXME - we are leaking like crazy - // AllocatorScope scope(btPool); - - // Note: these callbacks might be coming in from a different thread. - BLEServer *serve = initBLE( - [](uint32_t pin) { - powerFSM.trigger(EVENT_BLUETOOTH_PAIR); - screen.startBluetoothPinScreen(pin); - }, - []() { screen.stopBluetoothPinScreen(); }, getDeviceName(), HW_VENDOR, xstr(APP_VERSION), - xstr(HW_VERSION)); // FIXME, use a real name based on the macaddr - createMeshBluetoothService(serve); - - // Start advertising - this must be done _after_ creating all services - serve->getAdvertising()->start(); -} - -void setBluetoothEnable(bool on) -{ - if (on != bluetoothOn) { - DEBUG_MSG("Setting bluetooth enable=%d\n", on); - - bluetoothOn = on; - if (on) { - Serial.printf("Pre BT: %u heap size\n", ESP.getFreeHeap()); - // ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) ); - initBluetooth(); - } else { - // We have to totally teardown our bluetooth objects to prevent leaks - stopMeshBluetoothService(); // Must do before shutting down bluetooth - deinitBLE(); - destroyMeshBluetoothService(); // must do after deinit, because it frees our service - Serial.printf("Shutdown BT: %u heap size\n", ESP.getFreeHeap()); - // ESP_ERROR_CHECK( heap_trace_stop() ); - // heap_trace_dump(); - } - } -} - -uint32_t ledBlinker() -{ - static bool ledOn; - ledOn ^= 1; - - setLed(ledOn); - - // have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that - return powerStatus.charging ? 1000 : (ledOn ? 2 : 1000); -} - -Periodic ledPeriodic(ledBlinker); - #if 0 // Turn off for now @@ -340,56 +264,28 @@ uint32_t axpDebugRead() } Periodic axpDebugOutput(axpDebugRead); +axpDebugOutput.setup(); #endif void loop() { uint32_t msecstosleep = 1000 * 30; // How long can we sleep before we again need to service the main loop? + gps->loop(); // FIXME, remove from main, instead block on read + router.loop(); powerFSM.run_machine(); - gps.loop(); service.loop(); - ledPeriodic.loop(); + periodicScheduler.loop(); // axpDebugOutput.loop(); - loopBLE(); - // for debug printing - // service.radio.radioIf.canSleep(); +#ifdef DEBUG_PORT + DEBUG_PORT.loop(); // Send/receive protobufs over the serial port +#endif -#ifdef PMU_IRQ - if (pmu_irq) { - pmu_irq = false; - axp.readIRQ(); - - DEBUG_MSG("pmu irq!\n"); - - if (axp.isChargingIRQ()) { - DEBUG_MSG("Battery start charging\n"); - } - if (axp.isChargingDoneIRQ()) { - DEBUG_MSG("Battery fully charged\n"); - } - if (axp.isVbusRemoveIRQ()) { - DEBUG_MSG("USB unplugged\n"); - } - if (axp.isVbusPlugInIRQ()) { - DEBUG_MSG("USB plugged In\n"); - } - if (axp.isBattPlugInIRQ()) { - DEBUG_MSG("Battery inserted\n"); - } - if (axp.isBattRemoveIRQ()) { - DEBUG_MSG("Battery removed\n"); - } - if (axp.isPEKShortPressIRQ()) { - DEBUG_MSG("PEK short button press\n"); - } - - readPowerStatus(); - axp.clearIRQ(); - } -#endif // T_BEAM_V10 +#ifndef NO_ESP32 + esp32Loop(); +#endif #ifdef BUTTON_PIN // if user presses button for more than 3 secs, discard our network prefs and reboot (FIXME, use a debounce lib instead of @@ -424,8 +320,7 @@ void loop() screen.debug()->setChannelNameStatus(channelSettings.name); screen.debug()->setPowerStatus(powerStatus); // TODO(#4): use something based on hdop to show GPS "signal" strength. - screen.debug()->setGPSStatus(gps.hasLock() ? "ok" : ":("); - screen.loop(); + screen.debug()->setGPSStatus(gps->hasLock() ? "good" : "bad"); // No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in) // i.e. don't just keep spinning in loop as fast as we can. diff --git a/src/main.h b/src/main.h index 9842ea96f..9d0cde8db 100644 --- a/src/main.h +++ b/src/main.h @@ -9,3 +9,10 @@ extern bool isUSBPowered; // Global Screen singleton. extern meshtastic::Screen screen; + +// Return a human readable string of the form "Meshtastic_ab13" +const char *getDeviceName(); + +void getMacAddr(uint8_t *dmac); + +void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop(); \ No newline at end of file diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp new file mode 100644 index 000000000..d72be1118 --- /dev/null +++ b/src/mesh/CryptoEngine.cpp @@ -0,0 +1,32 @@ +#include "CryptoEngine.h" +#include "configuration.h" + +void CryptoEngine::setKey(size_t numBytes, uint8_t *bytes) +{ + DEBUG_MSG("WARNING: Using stub crypto - all crypto is sent in plaintext!\n"); +} + +/** + * Encrypt a packet + * + * @param bytes is updated in place + */ +void CryptoEngine::encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes) +{ + DEBUG_MSG("WARNING: noop encryption!\n"); +} + +void CryptoEngine::decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes) +{ + DEBUG_MSG("WARNING: noop decryption!\n"); +} + +/** + * Init our 128 bit nonce for a new packet + */ +void CryptoEngine::initNonce(uint32_t fromNode, uint64_t packetNum) +{ + memset(nonce, 0, sizeof(nonce)); + *((uint64_t *)&nonce[0]) = packetNum; + *((uint32_t *)&nonce[8]) = fromNode; +} \ No newline at end of file diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h new file mode 100644 index 000000000..04e592e2c --- /dev/null +++ b/src/mesh/CryptoEngine.h @@ -0,0 +1,50 @@ +#pragma once + +#include + +/** + * see docs/software/crypto.md for details. + * + */ + +class CryptoEngine +{ + protected: + /** Our per packet nonce */ + uint8_t nonce[16]; + + public: + virtual ~CryptoEngine() {} + + /** + * Set the key used for encrypt, decrypt. + * + * As a special case: If all bytes are zero, we assume _no encryption_ and send all data in cleartext. + * + * @param numBytes must be 16 (AES128), 32 (AES256) or 0 (no crypt) + * @param bytes a _static_ buffer that will remain valid for the life of this crypto instance (i.e. this class will cache the + * provided pointer) + */ + virtual void setKey(size_t numBytes, uint8_t *bytes); + + /** + * Encrypt a packet + * + * @param bytes is updated in place + */ + virtual void encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes); + virtual void decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes); + + protected: + /** + * Init our 128 bit nonce for a new packet + * + * The NONCE is constructed by concatenating (from MSB to LSB): + * a 64 bit packet number (stored in little endian order) + * a 32 bit sending node number (stored in little endian order) + * a 32 bit block counter (starts at zero) + */ + void initNonce(uint32_t fromNode, uint64_t packetNum); +}; + +extern CryptoEngine *crypto; diff --git a/src/mesh/DSRRouter.cpp b/src/mesh/DSRRouter.cpp new file mode 100644 index 000000000..00c2a80d3 --- /dev/null +++ b/src/mesh/DSRRouter.cpp @@ -0,0 +1,80 @@ +#include "DSRRouter.h" +#include "configuration.h" + +/* when we receive any packet + +- sniff and update tables (especially useful to find adjacent nodes). Update user, network and position info. +- if we need to route() that packet, resend it to the next_hop based on our nodedb. +- if it is broadcast or destined for our node, deliver locally +- handle routereply/routeerror/routediscovery messages as described below +- then free it + +routeDiscovery + +- if we've already passed through us (or is from us), then it ignore it +- use the nodes already mentioned in the request to update our routing table +- if they were looking for us, send back a routereply +- if max_hops is zero and they weren't looking for us, drop (FIXME, send back error - I think not though?) +- if we receive a discovery packet, we use it to populate next_hop (if needed) towards the requester (after decrementing max_hops) +- if we receive a discovery packet, and we have a next_hop in our nodedb for that destination we send a (reliable) we send a route +reply towards the requester + +when sending any reliable packet + +- if timeout doing retries, send a routeError (nak) message back towards the original requester. all nodes eavesdrop on that +packet and update their route caches. + +when we receive a routereply packet + +- update next_hop on the node, if the new reply needs fewer hops than the existing one (we prefer shorter paths). fixme, someday +use a better heuristic + +when we receive a routeError packet + +- delete the route for that failed recipient, restartRouteDiscovery() +- if we receive routeerror in response to a discovery, +- fixme, eventually keep caches of possible other routes. +*/ + +void DSRRouter::sniffReceived(const MeshPacket *p) +{ + + // FIXME, update nodedb + + // Handle route discovery packets (will be a broadcast message) + if (p->decoded.which_payload == SubPacket_request_tag) { + // FIXME - always start request with the senders nodenum + + if (weAreInRoute(p->decoded.request)) { + DEBUG_MSG("Ignoring a route request that contains us\n"); + } else { + updateRoutes(p->decoded.request, false); // Update our routing tables based on the route that came in so far on this request + + if (p->decoded.dest == getNodeNum()) { + // They were looking for us, send back a route reply (the sender address will be first in the list) + sendRouteReply(p->decoded.request); + } else { + // They were looking for someone else, forward it along (as a zero hop broadcast) + NodeNum nextHop = getNextHop(p->decoded.dest); + if (nextHop) { + // in our route cache, reply to the requester (the sender address will be first in the list) + sendRouteReply(p->decoded.request, nextHop); + } else { + // Not in our route cache, rebroadcast on their behalf (after adding ourselves to the request route) + resendRouteRequest(p); + } + } + } + } + + // Handle regular packets + if (p->to == getNodeNum()) { // Destined for us (at least for this hop) + + // We need to route this packet + if (p->decoded.dest != p->to) { + // FIXME + } + } + + return ReliableRouter::sniffReceived(p); +} \ No newline at end of file diff --git a/src/mesh/DSRRouter.h b/src/mesh/DSRRouter.h new file mode 100644 index 000000000..5ecbdc8e5 --- /dev/null +++ b/src/mesh/DSRRouter.h @@ -0,0 +1,39 @@ +#include "ReliableRouter.h" + +class DSRRouter : public ReliableRouter +{ + + protected: + /** + * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to + * update routing tables etc... based on what we overhear (even for messages not destined to our node) + */ + virtual void sniffReceived(const MeshPacket *p); + + private: + /** + * Does our node appear in the specified route + */ + bool weAreInRoute(const RouteDiscovery &route); + + /** + * Given a DSR route, use that route to update our DB of possible routes + **/ + void updateRoutes(const RouteDiscovery &route, bool reverse); + + /** + * send back a route reply (the sender address will be first in the list) + */ + void sendRouteReply(const RouteDiscovery &route, NodeNum toAppend = 0); + + /** + * Given a nodenum return the next node we should forward to if we want to reach that node. + * + * @return 0 if no route found + */ + NodeNum getNextHop(NodeNum dest); + + /** Not in our route cache, rebroadcast on their behalf (after adding ourselves to the request route) + */ + void resendRouteRequest(const MeshPacket *p); +}; \ No newline at end of file diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp new file mode 100644 index 000000000..d3cc5cbde --- /dev/null +++ b/src/mesh/FloodingRouter.cpp @@ -0,0 +1,55 @@ +#include "FloodingRouter.h" +#include "configuration.h" +#include "mesh-pb-constants.h" + +FloodingRouter::FloodingRouter() {} + +/** + * Send a packet on a suitable interface. This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error + */ +ErrorCode FloodingRouter::send(MeshPacket *p) +{ + // Add any messages _we_ send to the seen message list (so we will ignore all retransmissions we see) + wasSeenRecently(p); // FIXME, move this to a sniffSent method + + return Router::send(p); +} + +/** + * Called from loop() + * 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. + * + * Note: this method will free the provided packet + */ +void FloodingRouter::handleReceived(MeshPacket *p) +{ + if (wasSeenRecently(p)) { + DEBUG_MSG("Ignoring incoming msg, because we've already seen it\n"); + packetPool.release(p); + } else { + // If a broadcast, possibly _also_ send copies out into the mesh. + // (FIXME, do something smarter than naive flooding here) + if (p->to == NODENUM_BROADCAST && p->hop_limit > 0) { + if (p->id != 0) { + MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it + + tosend->hop_limit--; // bump down the hop count + + DEBUG_MSG("Rebroadcasting received floodmsg to neighbors, fr=0x%x,to=0x%x,id=%d,hop_limit=%d\n", p->from, p->to, + p->id, tosend->hop_limit); + // Note: we are careful to resend using the original senders node id + // We are careful not to call our hooked version of send() - because we don't want to check this again + Router::send(tosend); + + } else { + DEBUG_MSG("Ignoring a simple (0 id) broadcast\n"); + } + } + + // handle the packet as normal + Router::handleReceived(p); + } +} diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h new file mode 100644 index 000000000..48a8f0bc7 --- /dev/null +++ b/src/mesh/FloodingRouter.h @@ -0,0 +1,56 @@ +#pragma once + +#include "PacketHistory.h" +#include "PeriodicTask.h" +#include "Router.h" + +/** + * This is a mixin that extends Router with the ability to do Naive Flooding (in the standard mesh protocol sense) + * + * Rules for broadcasting (listing here for now, will move elsewhere eventually): + + If to==BROADCAST and id==0, this is a simple broadcast (0 hops). It will be + sent only by the current node and other nodes will not attempt to rebroadcast + it. + + If to==BROADCAST and id!=0, this is a "naive flooding" broadcast. The initial + node will send it on all local interfaces. + + When other nodes receive this message, they will + first check if their recentBroadcasts table contains the (from, id) pair that + indicates this message. If so, we've already seen it - so we discard it. If + not, we add it to the table and then resend this message on all interfaces. + When resending we are careful to use the "from" ID of the original sender. Not + our own ID. When resending we pick a random delay between 0 and 10 seconds to + decrease the chance of collisions with transmitters we can not even hear. + + Any entries in recentBroadcasts that are older than X seconds (longer than the + max time a flood can take) will be discarded. + */ +class FloodingRouter : public Router, protected PacketHistory +{ + private: + public: + /** + * Constructor + * + */ + FloodingRouter(); + + /** + * Send a packet on a suitable interface. This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error + */ + virtual ErrorCode send(MeshPacket *p); + + protected: + /** + * Called from loop() + * 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. + * + * Note: this method will free the provided packet + */ + virtual void handleReceived(MeshPacket *p); +}; diff --git a/src/MemoryPool.h b/src/mesh/MemoryPool.h similarity index 89% rename from src/MemoryPool.h rename to src/mesh/MemoryPool.h index babcd2225..89c514c90 100644 --- a/src/MemoryPool.h +++ b/src/mesh/MemoryPool.h @@ -24,13 +24,14 @@ template class MemoryPool buf = new T[maxElements]; // prefill dead - for (int i = 0; i < maxElements; i++) + for (size_t i = 0; i < maxElements; i++) release(&buf[i]); } ~MemoryPool() { delete[] buf; } /// Return a queable object which has been prefilled with zeros. Panic if no buffer is available + /// Note: this method is safe to call from regular OR ISR code T *allocZeroed() { T *p = allocZeroed(0); @@ -40,7 +41,7 @@ template class MemoryPool } /// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you probably - /// don't want this version) + /// don't want this version). T *allocZeroed(TickType_t maxWait) { T *p = dead.dequeuePtr(maxWait); @@ -65,7 +66,7 @@ template class MemoryPool { assert(dead.enqueue(p, 0)); assert(p >= buf && - (p - buf) < + (size_t)(p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool } @@ -74,7 +75,7 @@ template class MemoryPool { assert(dead.enqueueFromISR(p, higherPriWoken)); assert(p >= buf && - (p - buf) < + (size_t)(p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool } }; diff --git a/src/mesh/MeshRadio.h b/src/mesh/MeshRadio.h new file mode 100644 index 000000000..3d41d23ca --- /dev/null +++ b/src/mesh/MeshRadio.h @@ -0,0 +1,61 @@ +#pragma once + +#include "MemoryPool.h" +#include "MeshTypes.h" +#include "PointerQueue.h" +#include "configuration.h" +#include "mesh.pb.h" + +// US channel settings +#define CH0_US 903.08f // MHz +#define CH_SPACING_US 2.16f // MHz +#define NUM_CHANNELS_US 13 + +// EU433 channel settings +#define CH0_EU433 433.175f // MHz +#define CH_SPACING_EU433 0.2f // MHz +#define NUM_CHANNELS_EU433 8 + +// EU865 channel settings +#define CH0_EU865 865.2f // MHz +#define CH_SPACING_EU865 0.3f // MHz +#define NUM_CHANNELS_EU865 10 + +// CN channel settings +#define CH0_CN 470.0f // MHz +#define CH_SPACING_CN 2.0f // MHz FIXME, this is just a guess for 470-510 +#define NUM_CHANNELS_CN 20 + +// JP channel settings +#define CH0_JP 920.0f // MHz +#define CH_SPACING_JP 0.5f // MHz FIXME, this is just a guess for 920-925 +#define NUM_CHANNELS_JP 10 + +// FIXME add defs for other regions and use them here +#ifdef HW_VERSION_US +#define CH0 CH0_US +#define CH_SPACING CH_SPACING_US +#define NUM_CHANNELS NUM_CHANNELS_US +#elif defined(HW_VERSION_EU433) +#define CH0 CH0_EU433 +#define CH_SPACING CH_SPACING_EU433 +#define NUM_CHANNELS NUM_CHANNELS_EU433 +#elif defined(HW_VERSION_EU865) +#define CH0 CH0_EU865 +#define CH_SPACING CH_SPACING_EU865 +#define NUM_CHANNELS NUM_CHANNELS_EU865 +#elif defined(HW_VERSION_CN) +#define CH0 CH0_CN +#define CH_SPACING CH_SPACING_CN +#define NUM_CHANNELS NUM_CHANNELS_CN +#elif defined(HW_VERSION_JP) +#define CH0 CH0_JP +#define CH_SPACING CH_SPACING_JP +#define NUM_CHANNELS NUM_CHANNELS_JP +#else +// HW version not set - assume US +#define CH0 CH0_US +#define CH_SPACING CH_SPACING_US +#define NUM_CHANNELS NUM_CHANNELS_US +#endif + diff --git a/src/MeshService.cpp b/src/mesh/MeshService.cpp similarity index 55% rename from src/MeshService.cpp rename to src/mesh/MeshService.cpp index 85f4856d2..540ca7cb1 100644 --- a/src/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -1,9 +1,10 @@ #include #include +#include #include "GPS.h" -#include "MeshBluetoothService.h" +//#include "MeshBluetoothService.h" #include "MeshService.h" #include "NodeDB.h" #include "Periodic.h" @@ -43,41 +44,38 @@ FIXME in the initial proof of concept we just skip the entire want/deny flow and MeshService service; -// I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX -#define MAX_PACKETS \ - (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + MAX_TX_QUEUE + \ - 2) // max number of packets which can be in flight (either queued from reception or queued for sending) +#include "Router.h" -#define MAX_RX_FROMRADIO \ - 4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big +static uint32_t sendOwnerCb() +{ + service.sendOurOwner(); -MeshService::MeshService() - : packetPool(MAX_PACKETS), toPhoneQueue(MAX_RX_TOPHONE), fromRadioQueue(MAX_RX_FROMRADIO), fromNum(0), - radio(packetPool, fromRadioQueue) + return radioConfig.preferences.send_owner_interval * radioConfig.preferences.position_broadcast_secs * 1000; +} + +static Periodic sendOwnerPeriod(sendOwnerCb); + +MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE) { // assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro } void MeshService::init() { + sendOwnerPeriod.setup(); nodeDB.init(); - if (!radio.init()) - DEBUG_MSG("radio init failed\n"); - - gps.addObserver(this); - - // No need to call this here, our periodic task will fire quite soon - // sendOwnerPeriod(); + gpsObserver.observe(gps); + packetReceivedObserver.observe(&router.notifyPacketReceived); } void MeshService::sendOurOwner(NodeNum dest, bool wantReplies) { - MeshPacket *p = allocForSending(); + MeshPacket *p = router.allocForSending(); p->to = dest; - p->payload.want_response = wantReplies; - p->payload.which_variant = SubPacket_user_tag; - User &u = p->payload.variant.user; + p->decoded.want_response = wantReplies; + p->decoded.which_payload = SubPacket_user_tag; + User &u = p->decoded.user; u = owner; DEBUG_MSG("sending owner %s/%s/%s\n", u.id, u.long_name, u.short_name); @@ -85,19 +83,18 @@ void MeshService::sendOurOwner(NodeNum dest, bool wantReplies) } /// handle a user packet that just arrived on the radio, return NULL if we should not process this packet at all -MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp) +const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp) { bool wasBroadcast = mp->to == NODENUM_BROADCAST; bool isCollision = mp->from == myNodeInfo.my_node_num; // we win if we have a lower macaddr - bool weWin = memcmp(&owner.macaddr, &mp->payload.variant.user.macaddr, sizeof(owner.macaddr)) < 0; + bool weWin = memcmp(&owner.macaddr, &mp->decoded.user.macaddr, sizeof(owner.macaddr)) < 0; if (isCollision) { if (weWin) { DEBUG_MSG("NOTE! Received a nodenum collision and we are vetoing\n"); - releaseToPool(mp); // discard it mp = NULL; sendOurOwner(); // send our owner as a _broadcast_ because that other guy is mistakenly using our nodenum @@ -118,46 +115,44 @@ MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp) sendOurOwner(mp->from); - String lcd = String("Joined: ") + mp->payload.variant.user.long_name + "\n"; + String lcd = String("Joined: ") + mp->decoded.user.long_name + "\n"; screen.print(lcd.c_str()); } return mp; } -void MeshService::handleIncomingPosition(MeshPacket *mp) +void MeshService::handleIncomingPosition(const MeshPacket *mp) { - if (mp->has_payload && mp->payload.which_variant == SubPacket_position_tag) { - DEBUG_MSG("handled incoming position time=%u\n", mp->payload.variant.position.time); + if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.which_payload == SubPacket_position_tag) { + DEBUG_MSG("handled incoming position time=%u\n", mp->decoded.position.time); - if (mp->payload.variant.position.time) { + if (mp->decoded.position.time) { struct timeval tv; - uint32_t secs = mp->payload.variant.position.time; + uint32_t secs = mp->decoded.position.time; tv.tv_sec = secs; tv.tv_usec = 0; - gps.perhapsSetRTC(&tv); + perhapsSetRTC(&tv); } } else { DEBUG_MSG("Ignoring incoming packet - not a position\n"); } } -void MeshService::handleFromRadio(MeshPacket *mp) +int MeshService::handleFromRadio(const MeshPacket *mp) { powerFSM.trigger(EVENT_RECEIVED_PACKET); // Possibly keep the node from sleeping - mp->rx_time = gps.getValidTime(); // store the arrival timestamp for the phone - // If it is a position packet, perhaps set our clock (if we don't have a GPS of our own, otherwise wait for that to work) - if (!gps.isConnected) + if (!gps->isConnected) handleIncomingPosition(mp); else { DEBUG_MSG("Ignoring incoming time, because we have a GPS\n"); } - if (mp->has_payload && mp->payload.which_variant == SubPacket_user_tag) { + if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.which_payload == SubPacket_user_tag) { mp = handleFromRadioUser(mp); } @@ -174,44 +169,26 @@ void MeshService::handleFromRadio(MeshPacket *mp) if (d) releaseToPool(d); } - assert(toPhoneQueue.enqueue(mp, 0)); // FIXME, instead of failing for full queue, delete the oldest mssages - if (mp->payload.want_response) + MeshPacket *copied = packetPool.allocCopy(*mp); + assert(toPhoneQueue.enqueue(copied, 0)); // FIXME, instead of failing for full queue, delete the oldest mssages + + if (mp->decoded.want_response) sendNetworkPing(mp->from); } else { DEBUG_MSG("Not delivering vetoed User message\n"); } + + return 0; } -void MeshService::handleFromRadio() -{ - MeshPacket *mp; - uint32_t oldFromNum = fromNum; - while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) { - handleFromRadio(mp); - } - if (oldFromNum != fromNum) // We don't want to generate extra notifies for multiple new packets - bluetoothNotifyFromNum(fromNum); -} - -uint32_t sendOwnerCb() -{ - service.sendOurOwner(); - - return radioConfig.preferences.send_owner_interval * radioConfig.preferences.position_broadcast_secs * 1000; -} - -Periodic sendOwnerPeriod(sendOwnerCb); - /// Do idle processing (mostly processing messages which have been queued from the radio) void MeshService::loop() { - radio.loop(); // FIXME, possibly move radio interaction to own thread - - handleFromRadio(); - - // occasionally send our owner info - sendOwnerPeriod.loop(); + if (oldFromNum != fromNum) { // We don't want to generate extra notifies for multiple new packets + fromNumChanged.notifyObservers(fromNum); + oldFromNum = fromNum; + } } /// The radioConfig object just changed, call this to force the hw to change to the new settings @@ -219,41 +196,38 @@ void MeshService::reloadConfig() { // If we can successfully set this radio to these settings, save them to disk nodeDB.resetRadioConfig(); // Don't let the phone send us fatally bad settings - radio.reloadConfig(); + configChanged.notifyObservers(NULL); nodeDB.saveToDisk(); } -/// Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh) -void MeshService::handleToRadio(std::string s) +/** + * Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh) + * Called by PhoneAPI.handleToRadio. Note: p is a scratch buffer, this function is allowed to write to it but it can not keep a + * reference + */ +void MeshService::handleToRadio(MeshPacket &p) { - static ToRadio r; // this is a static scratch object, any data must be copied elsewhere before returning + handleIncomingPosition(&p); // If it is a position packet, perhaps set our clock - if (pb_decode_from_bytes((const uint8_t *)s.c_str(), s.length(), ToRadio_fields, &r)) { - switch (r.which_variant) { - case ToRadio_packet_tag: { - // If our phone is sending a position, see if we can use it to set our RTC - handleIncomingPosition(&r.variant.packet); // If it is a position packet, perhaps set our clock + if (p.from == 0) // If the phone didn't set a sending node ID, use ours + p.from = nodeDB.getNodeNum(); - r.variant.packet.rx_time = gps.getValidTime(); // Record the time the packet arrived from the phone (so we update our - // nodedb for the local node) + if (p.id == 0) + p.id = generatePacketId(); // If the phone didn't supply one, then pick one - // Send the packet into the mesh - sendToMesh(packetPool.allocCopy(r.variant.packet)); + p.rx_time = getValidTime(); // Record the time the packet arrived from the phone + // (so we update our nodedb for the local node) - bool loopback = false; // if true send any packet the phone sends back itself (for testing) - if (loopback) { - MeshPacket *mp = packetPool.allocCopy(r.variant.packet); - handleFromRadio(mp); - bluetoothNotifyFromNum(fromNum); // tell the phone a new packet arrived - } - break; - } - default: - DEBUG_MSG("Error: unexpected ToRadio variant\n"); - break; - } - } else { - DEBUG_MSG("Error: ignoring malformed toradio\n"); + // Send the packet into the mesh + + sendToMesh(packetPool.allocCopy(p)); + + bool loopback = false; // if true send any packet the phone sends back itself (for testing) + if (loopback) { + // no need to copy anymore because handle from radio assumes it should _not_ delete + // packetPool.allocCopy(r.variant.packet); + handleFromRadio(&p); + // handleFromRadio will tell the phone a new packet arrived } } @@ -264,36 +238,21 @@ void MeshService::sendToMesh(MeshPacket *p) // Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other // nodes shouldn't trust it anyways) Note: for now, we allow a device with a local GPS to include the time, so that gpsless // devices can get time. - if (p->has_payload && p->payload.which_variant == SubPacket_position_tag) { - if (!gps.isConnected) { - DEBUG_MSG("Stripping time %u from position send\n", p->payload.variant.position.time); - p->payload.variant.position.time = 0; + if (p->which_payload == MeshPacket_decoded_tag && p->decoded.which_payload == SubPacket_position_tag) { + if (!gps->isConnected) { + DEBUG_MSG("Stripping time %u from position send\n", p->decoded.position.time); + p->decoded.position.time = 0; } else - DEBUG_MSG("Providing time to mesh %u\n", p->payload.variant.position.time); + DEBUG_MSG("Providing time to mesh %u\n", p->decoded.position.time); } - // If the phone sent a packet just to us, don't send it out into the network - if (p->to == nodeDB.getNodeNum()) - DEBUG_MSG("Dropping locally processed message\n"); - else { - // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it - if (radio.send(p) != ERRNO_OK) - DEBUG_MSG("Dropped packet because send queue was full!\n"); + // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it + if (router.sendLocal(p) != ERRNO_OK) { + DEBUG_MSG("No radio was able to send packet, discarding...\n"); + releaseToPool(p); } } -MeshPacket *MeshService::allocForSending() -{ - MeshPacket *p = packetPool.allocZeroed(); - - p->has_payload = true; - p->from = nodeDB.getNodeNum(); - p->to = NODENUM_BROADCAST; - p->rx_time = gps.getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp - - return p; -} - void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies) { NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum()); @@ -313,30 +272,31 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies) assert(node->has_position); // Update our local node info with our position (even if we don't decide to update anyone else) - MeshPacket *p = allocForSending(); + MeshPacket *p = router.allocForSending(); p->to = dest; - p->payload.which_variant = SubPacket_position_tag; - p->payload.variant.position = node->position; - p->payload.want_response = wantReplies; - p->payload.variant.position.time = - gps.getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid. + p->decoded.which_payload = SubPacket_position_tag; + p->decoded.position = node->position; + p->decoded.want_response = wantReplies; + p->decoded.position.time = getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid. sendToMesh(p); } -void MeshService::onGPSChanged() +int MeshService::onGPSChanged(void *unused) { - // Update our local node info with our position (even if we don't decide to update anyone else) - MeshPacket *p = allocForSending(); - p->payload.which_variant = SubPacket_position_tag; + // DEBUG_MSG("got gps notify\n"); - Position &pos = p->payload.variant.position; + // Update our local node info with our position (even if we don't decide to update anyone else) + MeshPacket *p = router.allocForSending(); + p->decoded.which_payload = SubPacket_position_tag; + + Position &pos = p->decoded.position; // !zero or !zero lat/long means valid - if (gps.latitude != 0 || gps.longitude != 0) { - if (gps.altitude != 0) - pos.altitude = gps.altitude; - pos.latitude = gps.latitude; - pos.longitude = gps.longitude; - pos.time = gps.getValidTime(); + if (gps->latitude != 0 || gps->longitude != 0) { + if (gps->altitude != 0) + pos.altitude = gps->altitude; + pos.latitude_i = gps->latitude; + pos.longitude_i = gps->longitude; + pos.time = getValidTime(); } // We limit our GPS broadcasts to a max rate @@ -353,10 +313,6 @@ void MeshService::onGPSChanged() releaseToPool(p); } -} -void MeshService::onNotify(Observable *o) -{ - DEBUG_MSG("got gps notify\n"); - onGPSChanged(); + return 0; } diff --git a/src/MeshService.h b/src/mesh/MeshService.h similarity index 64% rename from src/MeshService.h rename to src/mesh/MeshService.h index 884529237..f6e688e19 100644 --- a/src/MeshService.h +++ b/src/mesh/MeshService.h @@ -2,9 +2,11 @@ #include #include +#include #include "MemoryPool.h" #include "MeshRadio.h" +#include "MeshTypes.h" #include "Observer.h" #include "PointerQueue.h" #include "mesh.pb.h" @@ -13,9 +15,11 @@ * Top level app for this service. keeps the mesh, the radio config and the queue of received packets. * */ -class MeshService : private Observer +class MeshService { - MemoryPool packetPool; + CallbackObserver gpsObserver = CallbackObserver(this, &MeshService::onGPSChanged); + CallbackObserver packetReceivedObserver = + CallbackObserver(this, &MeshService::handleFromRadio); /// received packets waiting for the phone to process them /// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure @@ -23,15 +27,18 @@ class MeshService : private Observer /// FIXME - save this to flash on deep sleep PointerQueue toPhoneQueue; - /// Packets which have just arrived from the radio, ready to be processed by this service and possibly - /// forwarded to the phone. - PointerQueue fromRadioQueue; - /// The current nonce for the newest packet which has been queued for the phone - uint32_t fromNum; + uint32_t fromNum = 0; + + /// Updated in loop() to detect when fromNum changes + uint32_t oldFromNum = 0; public: - MeshRadio radio; + /// Called when some new packets have arrived from one of the radios + Observable fromNumChanged; + + /// Called when radio config has changed (radios should observe this and set their hardware as required) + Observable configChanged; MeshService(); @@ -47,8 +54,12 @@ class MeshService : private Observer /// Allows the bluetooth handler to free packets after they have been sent void releaseToPool(MeshPacket *p) { packetPool.release(p); } - /// Given a ToRadio buffer (from bluetooth) parse it and properly handle it (setup radio, owner or send packet into the mesh) - void handleToRadio(std::string s); + /** + * Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh) + * Called by PhoneAPI.handleToRadio. Note: p is a scratch buffer, this function is allowed to write to it but it can not keep + * a reference + */ + void handleToRadio(MeshPacket &p); /// The radioConfig object just changed, call this to force the hw to change to the new settings void reloadConfig(); @@ -56,9 +67,6 @@ class MeshService : private Observer /// The owner User record just got updated, update our node DB and broadcast the info into the mesh void reloadOwner() { sendOurOwner(); } - /// Allocate and return a meshpacket which defaults as send to broadcast from the current node. - MeshPacket *allocForSending(); - /// Called when the user wakes up our GUI, normally sends our latest location to the mesh (if we have it), otherwise at least /// sends our owner void sendNetworkPing(NodeNum dest, bool wantReplies = false); @@ -76,21 +84,18 @@ class MeshService : private Observer void sendToMesh(MeshPacket *p); /// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh - void onGPSChanged(); + /// returns 0 to allow futher processing + int onGPSChanged(void *arg); - virtual void onNotify(Observable *o); - - /// handle all the packets that just arrived from the mesh radio - void handleFromRadio(); - - /// Handle a packet that just arrived from the radio. We will either eventually enqueue the message to the phone or return it to the free pool - void handleFromRadio(MeshPacket *p); + /// Handle a packet that just arrived from the radio. This method does _not_ free the provided packet. If it needs + /// to keep the packet around it makes a copy + int handleFromRadio(const MeshPacket *p); /// handle a user packet that just arrived on the radio, return NULL if we should not process this packet at all - MeshPacket *handleFromRadioUser(MeshPacket *mp); + const MeshPacket *handleFromRadioUser(const MeshPacket *mp); /// look at inbound packets and if they contain a position with time, possibly set our clock - void handleIncomingPosition(MeshPacket *mp); + void handleIncomingPosition(const MeshPacket *mp); }; extern MeshService service; diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h new file mode 100644 index 000000000..f491ce508 --- /dev/null +++ b/src/mesh/MeshTypes.h @@ -0,0 +1,32 @@ +#pragma once + +// low level types + +#include "MemoryPool.h" +#include "mesh.pb.h" +#include + +typedef uint8_t NodeNum; +typedef uint8_t PacketId; // A packet sequence number + +#define NODENUM_BROADCAST 255 +#define ERRNO_OK 0 +#define ERRNO_NO_INTERFACES 33 +#define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER + +/** + * the max number of hops a message can pass through, used as the default max for hop_limit in MeshPacket. + * + * We reserve 3 bits in the header so this could be up to 7, but given the high range of lora and typical usecases, keeping + * maxhops to 3 should be fine for a while. This also serves to prevent routing/flooding attempts to be attempted for + * too long. + **/ +#define HOP_MAX 7 + +/// We normally just use max 3 hops for sending reliable messages +#define HOP_RELIABLE 3 + +typedef int ErrorCode; + +/// Alloc and free packets to our global, ISR safe pool +extern MemoryPool packetPool; \ No newline at end of file diff --git a/src/NodeDB.cpp b/src/mesh/NodeDB.cpp similarity index 86% rename from src/NodeDB.cpp rename to src/mesh/NodeDB.cpp index 3dad39dd2..cc83f2f47 100644 --- a/src/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -5,6 +5,7 @@ #include "FS.h" #include "SPIFFS.h" +#include "CryptoEngine.h" #include "GPS.h" #include "NodeDB.h" #include "PowerFSM.h" @@ -30,7 +31,12 @@ DeviceState versions used to be defined in the .proto file but really only this #define DEVICESTATE_CUR_VER 7 #define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER +#ifndef NO_ESP32 #define FS SPIFFS +#endif + +// FIXME - move this somewhere else +extern void getMacAddr(uint8_t *dmac); /** * @@ -46,7 +52,7 @@ NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_cou void NodeDB::resetRadioConfig() { - /// 16 bytes of random PSK for our _public_ default channel that all devices power up on + /// 16 bytes of random PSK for our _public_ default channel that all devices power up on (AES128) static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf}; @@ -70,9 +76,20 @@ void NodeDB::resetRadioConfig() channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range channelSettings.tx_power = 23; - memcpy(&channelSettings.psk, &defaultpsk, sizeof(channelSettings.psk)); + memcpy(&channelSettings.psk.bytes, &defaultpsk, sizeof(channelSettings.psk)); + channelSettings.psk.size = sizeof(defaultpsk); strcpy(channelSettings.name, "Default"); } + + // Tell our crypto engine about the psk + crypto->setKey(channelSettings.psk.size, channelSettings.psk.bytes); + + // temp hack for quicker testing + /* + radioConfig.preferences.screen_on_secs = 30; + radioConfig.preferences.wait_bluetooth_secs = 30; + radioConfig.preferences.position_broadcast_secs = 15; + */ } void NodeDB::init() @@ -92,12 +109,8 @@ void NodeDB::init() // default to no GPS, until one has been found by probing myNodeInfo.has_gps = false; - strncpy(myNodeInfo.region, xstr(HW_VERSION), sizeof(myNodeInfo.region)); - strncpy(myNodeInfo.firmware_version, xstr(APP_VERSION), sizeof(myNodeInfo.firmware_version)); - strncpy(myNodeInfo.hw_model, HW_VENDOR, sizeof(myNodeInfo.hw_model)); - // Init our blank owner info to reasonable defaults - esp_efuse_mac_get_default(ourMacAddr); + getMacAddr(ourMacAddr); sprintf(owner.id, "!%02x%02x%02x%02x%02x%02x", ourMacAddr[0], ourMacAddr[1], ourMacAddr[2], ourMacAddr[3], ourMacAddr[4], ourMacAddr[5]); memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr)); @@ -116,14 +129,15 @@ void NodeDB::init() info->user = owner; info->has_user = true; - if (!FS.begin(true)) // FIXME - do this in main? - { - DEBUG_MSG("ERROR SPIFFS Mount Failed\n"); - // FIXME - report failure to phone - } - // saveToDisk(); loadFromDisk(); + + // We set these _after_ loading from disk - because they come from the build and are more trusted than + // what is stored in flash + strncpy(myNodeInfo.region, optstr(HW_VERSION), sizeof(myNodeInfo.region)); + strncpy(myNodeInfo.firmware_version, optstr(APP_VERSION), sizeof(myNodeInfo.firmware_version)); + strncpy(myNodeInfo.hw_model, HW_VENDOR, sizeof(myNodeInfo.hw_model)); + resetRadioConfig(); // If bogus settings got saved, then fix them DEBUG_MSG("NODENUM=0x%x, dbsize=%d\n", myNodeInfo.my_node_num, *numNodes); @@ -157,8 +171,15 @@ const char *preftmp = "/db.proto.tmp"; void NodeDB::loadFromDisk() { +#ifdef FS static DeviceState scratch; + if (!FS.begin(true)) // FIXME - do this in main? + { + DEBUG_MSG("ERROR SPIFFS Mount Failed\n"); + // FIXME - report failure to phone + } + File f = FS.open(preffile); if (f) { DEBUG_MSG("Loading saved preferences\n"); @@ -185,10 +206,14 @@ void NodeDB::loadFromDisk() } else { DEBUG_MSG("No saved preferences found\n"); } +#else + DEBUG_MSG("ERROR: Filesystem not implemented\n"); +#endif } void NodeDB::saveToDisk() { +#ifdef FS File f = FS.open(preftmp, "w"); if (f) { DEBUG_MSG("Writing preferences\n"); @@ -213,6 +238,9 @@ void NodeDB::saveToDisk() } else { DEBUG_MSG("ERROR: can't write prefs\n"); // FIXME report to app } +#else + DEBUG_MSG("ERROR filesystem not implemented\n"); +#endif } const NodeInfo *NodeDB::readNextInfo() @@ -226,7 +254,7 @@ const NodeInfo *NodeDB::readNextInfo() /// Given a node, return how many seconds in the past (vs now) that we last heard from it uint32_t sinceLastSeen(const NodeInfo *n) { - uint32_t now = gps.getTime(); + uint32_t now = getTime(); uint32_t last_seen = n->position.time; int delta = (int)(now - last_seen); @@ -254,9 +282,9 @@ size_t NodeDB::getNumOnlineNodes() /// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw void NodeDB::updateFrom(const MeshPacket &mp) { - if (mp.has_payload) { - const SubPacket &p = mp.payload; - DEBUG_MSG("Update DB node 0x%x for variant %d, rx_time=%u\n", mp.from, p.which_variant, mp.rx_time); + if (mp.which_payload == MeshPacket_decoded_tag) { + const SubPacket &p = mp.decoded; + DEBUG_MSG("Update DB node 0x%x, rx_time=%u\n", mp.from, mp.rx_time); int oldNumNodes = *numNodes; NodeInfo *info = getOrCreateNode(mp.from); @@ -269,11 +297,13 @@ void NodeDB::updateFrom(const MeshPacket &mp) info->position.time = mp.rx_time; } - switch (p.which_variant) { + info->snr = mp.rx_snr; // keep the most recent SNR we received for this node. + + switch (p.which_payload) { case SubPacket_position_tag: { // we carefully preserve the old time, because we always trust our local timestamps more uint32_t oldtime = info->position.time; - info->position = p.variant.position; + info->position = p.position; info->position.time = oldtime; info->has_position = true; updateGUIforNode = info; @@ -282,9 +312,9 @@ void NodeDB::updateFrom(const MeshPacket &mp) case SubPacket_data_tag: { // Keep a copy of the most recent text message. - if (p.variant.data.typ == Data_Type_CLEAR_TEXT) { - DEBUG_MSG("Received text msg from=0%0x, msg=%.*s\n", mp.from, p.variant.data.payload.size, - p.variant.data.payload.bytes); + if (p.data.typ == Data_Type_CLEAR_TEXT) { + DEBUG_MSG("Received text msg from=0x%0x, id=%d, msg=%.*s\n", mp.from, mp.id, p.data.payload.size, + p.data.payload.bytes); if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum()) { // We only store/display messages destined for us. devicestate.rx_text_message = mp; @@ -299,10 +329,10 @@ void NodeDB::updateFrom(const MeshPacket &mp) case SubPacket_user_tag: { DEBUG_MSG("old user %s/%s/%s\n", info->user.id, info->user.long_name, info->user.short_name); - bool changed = memcmp(&info->user, &p.variant.user, + bool changed = memcmp(&info->user, &p.user, sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay - info->user = p.variant.user; + info->user = p.user; DEBUG_MSG("updating changed=%d user %s/%s/%s\n", changed, info->user.id, info->user.long_name, info->user.short_name); info->has_user = true; @@ -316,9 +346,6 @@ void NodeDB::updateFrom(const MeshPacket &mp) } break; } - - default: - break; // Ignore other packet types } } } diff --git a/src/NodeDB.h b/src/mesh/NodeDB.h similarity index 100% rename from src/NodeDB.h rename to src/mesh/NodeDB.h diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp new file mode 100644 index 000000000..7361daad8 --- /dev/null +++ b/src/mesh/PacketHistory.cpp @@ -0,0 +1,55 @@ +#include "PacketHistory.h" +#include "configuration.h" + +/// We clear our old flood record five minute after we see the last of it +#define FLOOD_EXPIRE_TIME (5 * 60 * 1000L) + +PacketHistory::PacketHistory() +{ + recentPackets.reserve(MAX_NUM_NODES); // Prealloc the worst case # of records - to prevent heap fragmentation + // setup our periodic task +} + +/** + * Update recentBroadcasts and return true if we have already seen this packet + */ +bool PacketHistory::wasSeenRecently(const MeshPacket *p, bool withUpdate) +{ + if (p->id == 0) { + DEBUG_MSG("Ignoring message with zero id\n"); + return false; // Not a floodable message ID, so we don't care + } + + 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 + } else { + if (r.id == p->id && r.sender == p->from) { + 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++; + } + } + + // Didn't find an existing record, make one + if (withUpdate) { + PacketRecord r; + r.id = p->id; + r.sender = p->from; + r.rxTimeMsec = now; + recentPackets.push_back(r); + DEBUG_MSG("Adding packet record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + } + + return false; +} \ No newline at end of file diff --git a/src/mesh/PacketHistory.h b/src/mesh/PacketHistory.h new file mode 100644 index 000000000..38edf7d77 --- /dev/null +++ b/src/mesh/PacketHistory.h @@ -0,0 +1,68 @@ +#pragma once + +#include "Router.h" +#include +#include + +using namespace std; + +/** + * A record of a recent message broadcast + */ +struct PacketRecord { + NodeNum sender; + PacketId id; + uint32_t rxTimeMsec; // Unix time in msecs - the time we received it + + bool operator==(const PacketRecord &p) const { return sender == p.sender && id == p.id; } +}; + +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 (abs(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; + } +}; + +/** + * This is a mixin that adds a record of past packets we have seen + */ +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; + + public: + PacketHistory(); + + /** + * Update recentBroadcasts and return true if we have already seen this packet + * + * @param withUpdate if true and not found we add an entry to recentPackets + */ + bool wasSeenRecently(const MeshPacket *p, bool withUpdate = true); +}; diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp new file mode 100644 index 000000000..194a15e68 --- /dev/null +++ b/src/mesh/PhoneAPI.cpp @@ -0,0 +1,237 @@ +#include "PhoneAPI.h" +#include "MeshService.h" +#include "NodeDB.h" +#include + +PhoneAPI::PhoneAPI() +{ + assert(FromRadio_size <= MAX_TO_FROM_RADIO_SIZE); + assert(ToRadio_size <= MAX_TO_FROM_RADIO_SIZE); +} + +void PhoneAPI::init() +{ + observe(&service.fromNumChanged); +} + +/** + * Handle a ToRadio protobuf + */ +void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) +{ + if (pb_decode_from_bytes(buf, bufLength, ToRadio_fields, &toRadioScratch)) { + switch (toRadioScratch.which_variant) { + case ToRadio_packet_tag: { + // If our phone is sending a position, see if we can use it to set our RTC + MeshPacket &p = toRadioScratch.variant.packet; + service.handleToRadio(p); + break; + } + case ToRadio_want_config_id_tag: + config_nonce = toRadioScratch.variant.want_config_id; + DEBUG_MSG("Client wants config, nonce=%u\n", config_nonce); + state = STATE_SEND_MY_INFO; + + DEBUG_MSG("Reset nodeinfo read pointer\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 + break; + + case ToRadio_set_owner_tag: + DEBUG_MSG("Client is setting owner\n"); + handleSetOwner(toRadioScratch.variant.set_owner); + break; + + case ToRadio_set_radio_tag: + DEBUG_MSG("Client is setting radio\n"); + handleSetRadio(toRadioScratch.variant.set_radio); + break; + + default: + DEBUG_MSG("Error: unexpected ToRadio variant\n"); + break; + } + } else { + DEBUG_MSG("Error: ignoring malformed toradio\n"); + } +} + +/** + * Get the next packet we want to send to the phone, or NULL if no such packet is available. + * + * We assume buf is at least FromRadio_size bytes long. + * + * Our sending states progress in the following sequence (the client app ASSUMES THIS SEQUENCE, DO NOT CHANGE IT): + * STATE_SEND_MY_INFO, // send our my info record + STATE_SEND_RADIO, + STATE_SEND_NODEINFO, // states progress in this order as the device sends to to the client + STATE_SEND_COMPLETE_ID, + STATE_SEND_PACKETS // send packets or debug strings + */ +size_t PhoneAPI::getFromRadio(uint8_t *buf) +{ + if (!available()) + return false; + + // In case we send a FromRadio packet + memset(&fromRadioScratch, 0, sizeof(fromRadioScratch)); + + // Advance states as needed + switch (state) { + case STATE_SEND_NOTHING: + break; + + case STATE_SEND_MY_INFO: + fromRadioScratch.which_variant = FromRadio_my_info_tag; + fromRadioScratch.variant.my_info = myNodeInfo; + state = STATE_SEND_RADIO; + break; + + case STATE_SEND_RADIO: + fromRadioScratch.which_variant = FromRadio_radio_tag; + fromRadioScratch.variant.radio = radioConfig; + state = STATE_SEND_NODEINFO; + break; + + case STATE_SEND_NODEINFO: { + const NodeInfo *info = nodeInfoForPhone; + nodeInfoForPhone = NULL; // We just consumed a nodeinfo, will need a new one next time + + if (info) { + DEBUG_MSG("Sending nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", info->num, info->position.time, info->user.id, + info->user.long_name); + fromRadioScratch.which_variant = FromRadio_node_info_tag; + fromRadioScratch.variant.node_info = *info; + // Stay in current state until done sending nodeinfos + } else { + DEBUG_MSG("Done sending nodeinfos\n"); + state = STATE_SEND_COMPLETE_ID; + // Go ahead and send that ID right now + return getFromRadio(buf); + } + break; + } + + case STATE_SEND_COMPLETE_ID: + fromRadioScratch.which_variant = FromRadio_config_complete_id_tag; + fromRadioScratch.variant.config_complete_id = config_nonce; + config_nonce = 0; + state = STATE_SEND_PACKETS; + break; + + case STATE_LEGACY: // Treat as the same as send packets + case STATE_SEND_PACKETS: + // Do we have a message from the mesh? + if (packetForPhone) { + // Encapsulate as a FromRadio packet + fromRadioScratch.which_variant = FromRadio_packet_tag; + fromRadioScratch.variant.packet = *packetForPhone; + + service.releaseToPool(packetForPhone); // we just copied the bytes, so don't need this buffer anymore + packetForPhone = NULL; + } + break; + + default: + assert(0); // unexpected state - FIXME, make an error code and reboot + } + + // Do we have a message from the mesh? + if (fromRadioScratch.which_variant != 0) { + // Encapsulate as a FromRadio packet + DEBUG_MSG("encoding toPhone packet to phone variant=%d", fromRadioScratch.which_variant); + size_t numbytes = pb_encode_to_bytes(buf, FromRadio_size, FromRadio_fields, &fromRadioScratch); + DEBUG_MSG(", %d bytes\n", numbytes); + return numbytes; + } + + DEBUG_MSG("no FromRadio packet available\n"); + return 0; +} + +/** + * Return true if we have data available to send to the phone + */ +bool PhoneAPI::available() +{ + switch (state) { + case STATE_SEND_NOTHING: + return false; + + case STATE_SEND_MY_INFO: + return true; + + case STATE_SEND_NODEINFO: + if (!nodeInfoForPhone) + nodeInfoForPhone = nodeDB.readNextInfo(); + return true; // Always say we have something, because we might need to advance our state machine + + case STATE_SEND_RADIO: + return true; + + case STATE_SEND_COMPLETE_ID: + return true; + + case STATE_LEGACY: // Treat as the same as send packets + case STATE_SEND_PACKETS: + // Try to pull a new packet from the service (if we haven't already) + if (!packetForPhone) + packetForPhone = service.getForPhone(); + return !!packetForPhone; + + default: + assert(0); // unexpected state - FIXME, make an error code and reboot + } + + return false; +} + +// +// The following routines are only public for now - until the rev1 bluetooth API is removed +// + +void PhoneAPI::handleSetOwner(const User &o) +{ + int changed = 0; + + if (*o.long_name) { + changed |= strcmp(owner.long_name, o.long_name); + strcpy(owner.long_name, o.long_name); + } + if (*o.short_name) { + changed |= strcmp(owner.short_name, o.short_name); + strcpy(owner.short_name, o.short_name); + } + if (*o.id) { + changed |= strcmp(owner.id, o.id); + strcpy(owner.id, o.id); + } + + if (changed) // If nothing really changed, don't broadcast on the network or write to flash + service.reloadOwner(); +} + +void PhoneAPI::handleSetRadio(const RadioConfig &r) +{ + radioConfig = r; + + service.reloadConfig(); +} + +/** + * Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool + */ +void PhoneAPI::handleToRadioPacket(MeshPacket *p) {} + +/// If the mesh service tells us fromNum has changed, tell the phone +int PhoneAPI::onNotify(uint32_t newValue) +{ + if (state == STATE_SEND_PACKETS || state == STATE_LEGACY) { + DEBUG_MSG("Telling client we have new packets %u\n", newValue); + onNowHasData(newValue); + } else + DEBUG_MSG("(Client not yet interested in packets)\n"); + + return 0; +} \ No newline at end of file diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h new file mode 100644 index 000000000..f08c9009f --- /dev/null +++ b/src/mesh/PhoneAPI.h @@ -0,0 +1,101 @@ +#pragma once + +#include "Observer.h" +#include "mesh-pb-constants.h" +#include "mesh.pb.h" +#include + +// Make sure that we never let our packets grow too large for one BLE packet +#define MAX_TO_FROM_RADIO_SIZE 512 + +/** + * Provides our protobuf based API which phone/PC clients can use to talk to our device + * over UDP, bluetooth or serial. + * + * Subclass to customize behavior for particular type of transport (BLE, UDP, TCP, serial) + * + * Eventually there should be once instance of this class for each live connection (because it has a bit of state + * for that connection) + */ +class PhoneAPI + : public Observer // FIXME, we shouldn't be inheriting from Observer, instead use CallbackObserver as a member +{ + enum State { + STATE_LEGACY, // Temporary default state - until Android apps are all updated, uses the old BLE API + STATE_SEND_NOTHING, // (Eventual) Initial state, don't send anything until the client starts asking for config + STATE_SEND_MY_INFO, // send our my info record + STATE_SEND_RADIO, + // STATE_SEND_OWNER, no need to send Owner specially, it is just part of the nodedb + STATE_SEND_NODEINFO, // states progress in this order as the device sends to to the client + STATE_SEND_COMPLETE_ID, + STATE_SEND_PACKETS // send packets or debug strings + }; + + State state = STATE_LEGACY; + + /** + * Each packet sent to the phone has an incrementing count + */ + uint32_t fromRadioNum = 0; + + /// We temporarily keep the packet here between the call to available and getFromRadio. We will free it after the phone + /// downloads it + MeshPacket *packetForPhone = NULL; + + /// We temporarily keep the nodeInfo here between the call to available and getFromRadio + const NodeInfo *nodeInfoForPhone = NULL; + + ToRadio toRadioScratch; // this is a static scratch object, any data must be copied elsewhere before returning + + /// Use to ensure that clients don't get confused about old messages from the radio + uint32_t config_nonce = 0; + + public: + PhoneAPI(); + + /// Do late init that can't happen at constructor time + virtual void init(); + + /** + * Handle a ToRadio protobuf + */ + virtual void handleToRadio(const uint8_t *buf, size_t len); + + /** + * Get the next packet we want to send to the phone + * + * We assume buf is at least FromRadio_size bytes long. + * Returns number of bytes in the FromRadio packet (or 0 if no packet available) + */ + size_t getFromRadio(uint8_t *buf); + + /** + * Return true if we have data available to send to the phone + */ + bool available(); + + // + // The following routines are only public for now - until the rev1 bluetooth API is removed + // + + void handleSetOwner(const User &o); + void handleSetRadio(const RadioConfig &r); + + protected: + /// Our fromradio packet while it is being assembled + FromRadio fromRadioScratch; + + /** + * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies) + */ + virtual void onNowHasData(uint32_t fromRadioNum) {} + + private: + /** + * Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool + */ + void handleToRadioPacket(MeshPacket *p); + + /// If the mesh service tells us fromNum has changed, tell the phone + virtual int onNotify(uint32_t newValue); +}; diff --git a/src/PointerQueue.h b/src/mesh/PointerQueue.h similarity index 100% rename from src/PointerQueue.h rename to src/mesh/PointerQueue.h diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp new file mode 100644 index 000000000..0557dd082 --- /dev/null +++ b/src/mesh/RF95Interface.cpp @@ -0,0 +1,126 @@ +#include "RF95Interface.h" +#include "MeshRadio.h" // kinda yucky, but we need to know which region we are in +#include "RadioLibRF95.h" +#include + +#define MAX_POWER 17 +// if we use 20 we are limited to 1% duty cycle or hw might overheat. For continuous operation set a limit of 17 + +RF95Interface::RF95Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, SPIClass &spi) + : RadioLibInterface(cs, irq, rst, 0, spi) +{ + // FIXME - we assume devices never get destroyed +} + +/// Initialise the Driver transport hardware and software. +/// Make sure the Driver is properly configured before calling init(). +/// \return true if initialisation succeeded. +bool RF95Interface::init() +{ + RadioLibInterface::init(); + + applyModemConfig(); + if (power > MAX_POWER) // This chip has lower power limits than some + power = MAX_POWER; + + iface = lora = new RadioLibRF95(&module); + int res = lora->begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength); + DEBUG_MSG("LORA init result %d\n", res); + + if (res == ERR_NONE) + res = lora->setCRC(SX126X_LORA_CRC_ON); + + if (res == ERR_NONE) + startReceive(); // start receiving + + return res == ERR_NONE; +} + +void INTERRUPT_ATTR RF95Interface::disableInterrupt() +{ + lora->clearDio0Action(); +} + +bool RF95Interface::reconfigure() +{ + applyModemConfig(); + + // set mode to standby + setStandby(); + + // configure publicly accessible settings + int err = lora->setSpreadingFactor(sf); + assert(err == ERR_NONE); + + err = lora->setBandwidth(bw); + assert(err == ERR_NONE); + + err = lora->setCodingRate(cr); + 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); + assert(err == ERR_NONE); + + if (power > MAX_POWER) // This chip has lower power limits than some + power = MAX_POWER; + err = lora->setOutputPower(power); + assert(err == ERR_NONE); + + startReceive(); // restart receiving + + return ERR_NONE; +} + +/** + * Add SNR data to received messages + */ +void RF95Interface::addReceiveMetadata(MeshPacket *mp) +{ + mp->rx_snr = lora->getSNR(); +} + +void RF95Interface::setStandby() +{ + int err = lora->standby(); + assert(err == ERR_NONE); + + isReceiving = false; // If we were receiving, not any more + disableInterrupt(); + completeSending(); // If we were sending, not anymore +} + +void RF95Interface::startReceive() +{ + setStandby(); + int err = lora->startReceive(); + assert(err == ERR_NONE); + + isReceiving = true; + + // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits + enableInterrupt(isrRxLevel0); +} + +/** Could we send right now (i.e. either not actively receving or transmitting)? */ +bool RF95Interface::isActivelyReceiving() +{ + return lora->isReceiving(); +} + +bool RF95Interface::sleep() +{ + // put chipset into sleep mode + disableInterrupt(); + lora->sleep(); + + return true; +} diff --git a/src/mesh/RF95Interface.h b/src/mesh/RF95Interface.h new file mode 100644 index 000000000..6c5e2ab5c --- /dev/null +++ b/src/mesh/RF95Interface.h @@ -0,0 +1,55 @@ +#pragma once + +#include "MeshRadio.h" // kinda yucky, but we need to know which region we are in +#include "RadioLibInterface.h" +#include "RadioLibRF95.h" + +/** + * Our new not radiohead adapter for RF95 style radios + */ +class RF95Interface : public RadioLibInterface +{ + RadioLibRF95 *lora; // Either a RFM95 or RFM96 depending on what was stuffed on this board + + public: + RF95Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, 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->setDio0Action(callback); } + + /** are we actively receiving a packet (only called during receiving state) */ + virtual bool isActivelyReceiving(); + + /** + * Start waiting to receive a message + */ + virtual void startReceive(); + + /** + * Add SNR data to received messages + */ + virtual void addReceiveMetadata(MeshPacket *mp); + + virtual void setStandby(); +}; \ No newline at end of file diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp new file mode 100644 index 000000000..45ecc42a8 --- /dev/null +++ b/src/mesh/RadioInterface.cpp @@ -0,0 +1,129 @@ + +#include "RadioInterface.h" +#include "MeshRadio.h" +#include "MeshService.h" +#include "NodeDB.h" +#include "assert.h" +#include "configuration.h" +#include "sleep.h" +#include +#include +#include + +/** + * ## LoRaWAN for North America + +LoRaWAN defines 64, 125 kHz channels from 902.3 to 914.9 MHz increments. + +The maximum output power for North America is +30 dBM. + +The band is from 902 to 928 MHz. It mentions channel number and its respective channel frequency. All the 13 channels are +separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency. +*/ + +// 1kb was too small +#define RADIO_STACK_SIZE 4096 + +RadioInterface::RadioInterface() : txQueue(MAX_TX_QUEUE) +{ + assert(sizeof(PacketHeader) == 4); // make sure the compiler did what we expected + + myNodeInfo.num_channels = NUM_CHANNELS; + + // Can't print strings this early - serial not setup yet + // DEBUG_MSG("Set meshradio defaults name=%s\n", channelSettings.name); +} + +bool RadioInterface::init() +{ + DEBUG_MSG("Starting meshradio init...\n"); + + configChangedObserver.observe(&service.configChanged); + preflightSleepObserver.observe(&preflightSleep); + notifyDeepSleepObserver.observe(¬ifyDeepSleep); + + // we now expect interfaces to operate in promiscous mode + // radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor + // time. + + // we want this thread to run at very high priority, because it is effectively running as a user space ISR + start("radio", RADIO_STACK_SIZE, configMAX_PRIORITIES - 1); // Start our worker thread + + return true; +} + +/** hash a string into an integer + * + * djb2 by Dan Bernstein. + * http://www.cse.yorku.ca/~oz/hash.html + */ +unsigned long hash(char *str) +{ + unsigned long hash = 5381; + int c; + + while ((c = *str++) != 0) + hash = ((hash << 5) + hash) + (unsigned char)c; /* hash * 33 + c */ + + return hash; +} + +/** + * Pull our channel settings etc... from protobufs to the dumb interface settings + */ +void RadioInterface::applyModemConfig() +{ + // Set up default configuration + // No Sync Words in LORA mode. + modemConfig = (ModemConfigChoice)channelSettings.modem_config; + + // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM + int channel_num = hash(channelSettings.name) % NUM_CHANNELS; + freq = CH0 + CH_SPACING * channel_num; + power = channelSettings.tx_power; + + DEBUG_MSG("Set radio: name=%s, config=%u, ch=%d, power=%d\n", channelSettings.name, channelSettings.modem_config, channel_num, + channelSettings.tx_power); +} + +ErrorCode SimRadio::send(MeshPacket *p) +{ + DEBUG_MSG("SimRadio.send\n"); + packetPool.release(p); + return ERRNO_OK; +} + +void RadioInterface::deliverToReceiver(MeshPacket *p) +{ + assert(rxDest); + assert(rxDest->enqueue(p, 0)); // NOWAIT - fixme, if queue is full, delete older messages +} + +/*** + * given a packet set sendingPacket and decode the protobufs into radiobuf. Returns # of payload bytes to send + */ +size_t RadioInterface::beginSending(MeshPacket *p) +{ + assert(!sendingPacket); + + // DEBUG_MSG("sending queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", rf95.txGood(), rf95.rxGood(), rf95.rxBad()); + assert(p->which_payload == MeshPacket_encrypted_tag); // It should have already been encoded by now + + lastTxStart = millis(); + + PacketHeader *h = (PacketHeader *)radiobuf; + + h->from = p->from; + h->to = p->to; + h->id = p->id; + assert(p->hop_limit <= HOP_MAX); + h->flags = p->hop_limit | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0); + + // if the sender nodenum is zero, that means uninitialized + assert(h->from); + + memcpy(radiobuf + sizeof(PacketHeader), p->encrypted.bytes, p->encrypted.size); + + sendingPacket = p; + return p->encrypted.size + sizeof(PacketHeader); +} \ No newline at end of file diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h new file mode 100644 index 000000000..419763750 --- /dev/null +++ b/src/mesh/RadioInterface.h @@ -0,0 +1,163 @@ +#pragma once + +#include "MemoryPool.h" +#include "MeshTypes.h" +#include "Observer.h" +#include "PointerQueue.h" +#include "WorkerThread.h" +#include "mesh.pb.h" + +#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission + +#define MAX_RHPACKETLEN 256 + +#define PACKET_FLAGS_HOP_MASK 0x07 +#define PACKET_FLAGS_WANT_ACK_MASK 0x08 + +/** + * This structure has to exactly match the wire layout when sent over the radio link. Used to keep compatibility + * wtih the old radiohead implementation. + */ +typedef struct { + uint8_t to, from, id; + + /** + * Usage of flags: + * + * The bottom three bits of flags are use to store hop_limit when sent over the wire. + **/ + uint8_t flags; +} PacketHeader; + +typedef enum { + Bw125Cr45Sf128 = 0, ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range + Bw500Cr45Sf128, ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range + Bw31_25Cr48Sf512, ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range + Bw125Cr48Sf4096, ///< Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range +} ModemConfigChoice; + +/** + * Basic operations all radio chipsets must implement. + * + * This defines the SOLE API for talking to radios (because soon we will have alternate radio implementations) + */ +class RadioInterface : protected NotifiedWorkerThread +{ + friend class MeshRadio; // for debugging we let that class touch pool + PointerQueue *rxDest = NULL; + + CallbackObserver configChangedObserver = + CallbackObserver(this, &RadioInterface::reloadConfig); + + CallbackObserver preflightSleepObserver = + CallbackObserver(this, &RadioInterface::preflightSleepCb); + + CallbackObserver notifyDeepSleepObserver = + CallbackObserver(this, &RadioInterface::notifyDeepSleepDb); + + protected: + MeshPacket *sendingPacket = NULL; // The packet we are currently sending + PointerQueue txQueue; + uint32_t lastTxStart = 0L; + + /** + * A temporary buffer used for sending/receving packets, sized to hold the biggest buffer we might need + * */ + uint8_t radiobuf[MAX_RHPACKETLEN]; + + /** + * Enqueue a received packet for the registered receiver + */ + void deliverToReceiver(MeshPacket *p); + + public: + float freq = 915.0; // FIXME, init all these params from user setings + int8_t power = 17; + ModemConfigChoice modemConfig; + + /** pool is the pool we will alloc our rx packets from + * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool + */ + RadioInterface(); + + /** + * Set where to deliver received packets. This method should only be used by the Router class + */ + void setReceiver(PointerQueue *_rxDest) { rxDest = _rxDest; } + + /** + * Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving) + * + * This method must be used before putting the CPU into deep or light sleep. + */ + virtual bool canSleep() { return true; } + + /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep. + virtual bool sleep() { return true; } + + /** + * Send a packet (possibly by enquing in a private fifo). This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error + */ + virtual ErrorCode send(MeshPacket *p) = 0; + + // methods from radiohead + + /// 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() = 0; + + protected: + /*** + * given a packet set sendingPacket and decode the protobufs into radiobuf. Returns # of bytes to send (including the + * PacketHeader & payload). + * + * Used as the first step of + */ + size_t beginSending(MeshPacket *p); + + virtual void loop() {} // Idle processing + + /** + * Convert our modemConfig enum into wf, sf, etc... + * + * These paramaters will be pull from the channelSettings global + */ + virtual void applyModemConfig(); + + private: + /// Return 0 if sleep is okay + int preflightSleepCb(void *unused = NULL) { return canSleep() ? 0 : 1; } + + int notifyDeepSleepDb(void *unused = NULL) + { + sleep(); + return 0; + } + + int reloadConfig(void *unused) + { + reconfigure(); + return 0; + } +}; + +class SimRadio : public RadioInterface +{ + public: + virtual ErrorCode send(MeshPacket *p); + + // methods from radiohead + + /// 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() { return true; } +}; diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp new file mode 100644 index 000000000..9d2710716 --- /dev/null +++ b/src/mesh/RadioLibInterface.cpp @@ -0,0 +1,328 @@ +#include "RadioLibInterface.h" +#include "MeshTypes.h" +#include "OSTimer.h" +#include "mesh-pb-constants.h" +#include +#include +#include + +// FIXME, we default to 4MHz SPI, SPI mode 0, check if the datasheet says it can really do that +static SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); + +RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, + SPIClass &spi, PhysicalLayer *_iface) + : module(cs, irq, rst, busy, spi, spiSettings), iface(_iface) +{ + assert(!instance); // We assume only one for now + instance = this; +} + +#ifndef NO_ESP32 +// ESP32 doesn't use that flag +#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR() +#else +#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x) +#endif + +void INTERRUPT_ATTR RadioLibInterface::isrLevel0Common(PendingISR cause) +{ + instance->disableInterrupt(); + + instance->pending = cause; + BaseType_t xHigherPriorityTaskWoken; + instance->notifyFromISR(&xHigherPriorityTaskWoken, cause, eSetValueWithOverwrite); + + /* Force a context switch if xHigherPriorityTaskWoken is now set to pdTRUE. + The macro used to do this is dependent on the port and may be called + portEND_SWITCHING_ISR. */ + YIELD_FROM_ISR(xHigherPriorityTaskWoken); +} + +void INTERRUPT_ATTR RadioLibInterface::isrRxLevel0() +{ + isrLevel0Common(ISR_RX); +} + +void INTERRUPT_ATTR RadioLibInterface::isrTxLevel0() +{ + isrLevel0Common(ISR_TX); +} + +/** Our ISR code currently needs this to find our active instance + */ +RadioLibInterface *RadioLibInterface::instance; + +/** + * Convert our modemConfig enum into wf, sf, etc... + */ +void RadioLibInterface::applyModemConfig() +{ + RadioInterface::applyModemConfig(); + + switch (modemConfig) { + case Bw125Cr45Sf128: ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range + bw = 125; + cr = 5; + sf = 7; + break; + case Bw500Cr45Sf128: ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range + bw = 500; + cr = 5; + sf = 7; + break; + case Bw31_25Cr48Sf512: ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range + bw = 31.25; + cr = 8; + sf = 9; + break; + case Bw125Cr48Sf4096: + bw = 125; + cr = 8; + sf = 12; + break; + default: + assert(0); // Unknown enum + } +} + +/** Could we send right now (i.e. either not actively receving or transmitting)? */ +bool RadioLibInterface::canSendImmediately() +{ + // We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one). + // To do otherwise would be doubly bad because not only would we drop the packet that was on the way in, + // we almost certainly guarantee no one outside will like the packet we are sending. + bool busyTx = sendingPacket != NULL; + bool busyRx = isReceiving && isActivelyReceiving(); + + if (busyTx || busyRx) { + if (busyTx) + DEBUG_MSG("Can not send yet, busyTx\n"); + if (busyRx) + DEBUG_MSG("Can not send yet, busyRx\n"); + return false; + } else + return true; +} + +/// Send a packet (possibly by enquing in a private fifo). This routine will +/// later free() the packet to pool. This routine is not allowed to stall because it is called from +/// bluetooth comms code. If the txmit queue is empty it might return an error +ErrorCode RadioLibInterface::send(MeshPacket *p) +{ + DEBUG_MSG("enqueuing for send on mesh fr=0x%x,to=0x%x,id=%d (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, txGood, + rxGood, rxBad); + ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN; + + if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks + packetPool.release(p); + return res; + } + + // We want all sending/receiving to be done by our daemon thread, We use a delay here because this packet might have been sent + // in response to a packet we just received. So we want to make sure the other side has had a chance to reconfigure its radio + startTransmitTimer(true); + + return res; +} + +bool RadioLibInterface::canSleep() +{ + bool res = txQueue.isEmpty(); + if (!res) // only print debug messages if we are vetoing sleep + DEBUG_MSG("radio wait to sleep, txEmpty=%d\n", txQueue.isEmpty()); + + return res; +} + +/** At the low end we want to pick a delay large enough that anyone who just completed sending (some other node) + * has had enough time to switch their radio back into receive mode. + */ +#define MIN_TX_WAIT_MSEC 100 + +/** + * At the high end, this value is used to spread node attempts across time so when they are replying to a packet + * they don't both check that the airwaves are clear at the same moment. As long as they are off by some amount + * one of the two will be first to start transmitting and the other will see that. I bet 500ms is more than enough + * to guarantee this. + */ +#define MAX_TX_WAIT_MSEC 2000 // stress test would still fail occasionally with 1000 + +/** radio helper thread callback. + +We never immediately transmit after any operation (either rx or tx). Instead we should start receiving and +wait a random delay of 50 to 200 ms to make sure we are not stomping on someone else. The 50ms delay at the beginning ensures all +possible listeners have had time to finish processing the previous packet and now have their radio in RX state. The up to 200ms +random delay gives a chance for all possible senders to have high odds of detecting that someone else started transmitting first +and then they will wait until that packet finishes. + +NOTE: the large flood rebroadcast delay might still be needed even with this approach. Because we might not be able to hear other +transmitters that we are potentially stomping on. Requires further thought. + +FIXME, the MIN_TX_WAIT_MSEC and MAX_TX_WAIT_MSEC values should be tuned via logic analyzer later. +*/ +void RadioLibInterface::loop() +{ + pending = ISR_NONE; + + switch (notification) { + case ISR_TX: + handleTransmitInterrupt(); + startReceive(); + startTransmitTimer(); + break; + case ISR_RX: + handleReceiveInterrupt(); + startReceive(); + startTransmitTimer(); + break; + case TRANSMIT_DELAY_COMPLETED: + // If we are not currently in receive mode, then restart the timer and try again later (this can happen if the main thread + // has placed the unit into standby) FIXME, how will this work if the chipset is in sleep mode? + if (!txQueue.isEmpty()) { + if (!canSendImmediately()) { + startTransmitTimer(); // try again in a little while + } else { + // Send any outgoing packets we have ready + MeshPacket *txp = txQueue.dequeuePtr(0); + assert(txp); + startSend(txp); + } + } + break; + default: + assert(0); // We expected to receive a valid notification from the ISR + } +} + +#ifndef NO_ESP32 +#define USE_HW_TIMER +#else +// Not needed on NRF52 +#define IRAM_ATTR +#endif + +void IRAM_ATTR RadioLibInterface::timerCallback(void *p1, uint32_t p2) +{ + RadioLibInterface *t = (RadioLibInterface *)p1; + + t->timerRunning = false; + + // We use without overwrite, so that if there is already an interrupt pending to be handled, that gets handle properly (the + // ISR handler will restart our timer) +#ifndef USE_HW_TIMER + t->notify(TRANSMIT_DELAY_COMPLETED, eSetValueWithoutOverwrite); +#else + BaseType_t xHigherPriorityTaskWoken; + instance->notifyFromISR(&xHigherPriorityTaskWoken, TRANSMIT_DELAY_COMPLETED, eSetValueWithoutOverwrite); + + /* Force a context switch if xHigherPriorityTaskWoken is now set to pdTRUE. + The macro used to do this is dependent on the port and may be called + portEND_SWITCHING_ISR. */ + YIELD_FROM_ISR(xHigherPriorityTaskWoken); +#endif +} + +void RadioLibInterface::startTransmitTimer(bool withDelay) +{ + // If we have work to do and the timer wasn't already scheduled, schedule it now + if (!timerRunning && !txQueue.isEmpty()) { + timerRunning = true; + uint32_t delay = + !withDelay ? 0 : random(MIN_TX_WAIT_MSEC, MAX_TX_WAIT_MSEC); // See documentation for loop() wrt these values + // DEBUG_MSG("xmit timer %d\n", delay); +#ifdef USE_HW_TIMER + bool okay = scheduleHWCallback(timerCallback, this, 0, delay); +#else + bool okay = scheduleOSCallback(timerCallback, this, 0, delay); +#endif + assert(okay); + } +} + +void RadioLibInterface::handleTransmitInterrupt() +{ + // DEBUG_MSG("handling lora TX interrupt\n"); + assert(sendingPacket); // Were we sending? - FIXME, this was null coming out of light sleep due to RF95 ISR! + + completeSending(); +} + +void RadioLibInterface::completeSending() +{ + if (sendingPacket) { + txGood++; + DEBUG_MSG("Completed sending to=0x%x, id=%u\n", sendingPacket->to, sendingPacket->id); + + // We are done sending that packet, release it + packetPool.release(sendingPacket); + sendingPacket = NULL; + // DEBUG_MSG("Done with send\n"); + } +} + +void RadioLibInterface::handleReceiveInterrupt() +{ + assert(isReceiving); + isReceiving = false; + + // read the number of actually received bytes + size_t length = iface->getPacketLength(); + + int state = iface->readData(radiobuf, length); + if (state != ERR_NONE) { + DEBUG_MSG("ignoring received packet due to error=%d\n", state); + rxBad++; + } else { + // Skip the 4 headers that are at the beginning of the rxBuf + int32_t payloadLen = length - sizeof(PacketHeader); + const uint8_t *payload = radiobuf + sizeof(PacketHeader); + + // check for short packets + if (payloadLen < 0) { + DEBUG_MSG("ignoring received packet too short\n"); + rxBad++; + } else { + const PacketHeader *h = (PacketHeader *)radiobuf; + + rxGood++; + + // Note: we deliver _all_ packets to our router (i.e. our interface is intentionally promiscuous). + // This allows the router and other apps on our node to sniff packets (usually routing) between other + // nodes. + MeshPacket *mp = packetPool.allocZeroed(); + + mp->from = h->from; + mp->to = h->to; + mp->id = h->id; + assert(HOP_MAX <= PACKET_FLAGS_HOP_MASK); // If hopmax changes, carefully check this code + mp->hop_limit = h->flags & PACKET_FLAGS_HOP_MASK; + mp->want_ack = !!(h->flags & PACKET_FLAGS_WANT_ACK_MASK); + + addReceiveMetadata(mp); + + mp->which_payload = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point + assert(payloadLen <= sizeof(mp->encrypted.bytes)); + memcpy(mp->encrypted.bytes, payload, payloadLen); + mp->encrypted.size = payloadLen; + + DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id); + + deliverToReceiver(mp); + } + } +} + +/** start an immediate transmit */ +void RadioLibInterface::startSend(MeshPacket *txp) +{ + DEBUG_MSG("Starting low level send from=0x%x, id=%u, want_ack=%d\n", txp->from, txp->id, txp->want_ack); + setStandby(); // Cancel any already in process receives + + size_t numbytes = beginSending(txp); + + int res = iface->startTransmit(radiobuf, numbytes); + assert(res == ERR_NONE); + + // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits + enableInterrupt(isrTxLevel0); +} diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h new file mode 100644 index 000000000..5f9149d71 --- /dev/null +++ b/src/mesh/RadioLibInterface.h @@ -0,0 +1,138 @@ +#pragma once + +#include "RadioInterface.h" + +#include + +// ESP32 has special rules about ISR code +#ifdef ARDUINO_ARCH_ESP32 +#define INTERRUPT_ATTR IRAM_ATTR +#else +#define INTERRUPT_ATTR +#endif + +class RadioLibInterface : public RadioInterface +{ + /// Used as our notification from the ISR + enum PendingISR { ISR_NONE = 0, ISR_RX, ISR_TX, TRANSMIT_DELAY_COMPLETED }; + + volatile PendingISR pending = ISR_NONE; + volatile bool timerRunning = false; + + /** + * Raw ISR handler that just calls our polymorphic method + */ + static void isrTxLevel0(), isrLevel0Common(PendingISR code); + + /** + * Debugging counts + */ + uint32_t rxBad = 0, rxGood = 0, txGood = 0; + + protected: + float bw = 125; + uint8_t sf = 9; + uint8_t cr = 7; + + /** + * FIXME, use a meshtastic sync word, but hashed with the Channel name. Currently picking the same default + * the RF95 used (0x14). Note: do not use 0x34 - that is reserved for lorawan + */ + uint8_t syncWord = SX126X_SYNC_WORD_PRIVATE; + + float currentLimit = 100; // FIXME + uint16_t preambleLength = 32; // 8 is default, but FIXME use longer to increase the amount of sleep time when receiving + + Module module; // The HW interface to the radio + + /** + * provides lowest common denominator RadioLib API + */ + PhysicalLayer *iface; + + /// are _trying_ to receive a packet currently (note - we might just be waiting for one) + bool isReceiving; + + public: + /** Our ISR code currently needs this to find our active instance + */ + static RadioLibInterface *instance; + + /** + * Glue functions called from ISR land + */ + virtual void disableInterrupt() = 0; + + /** + * Enable a particular ISR callback glue function + */ + virtual void enableInterrupt(void (*)()) = 0; + + public: + RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi, + PhysicalLayer *iface = NULL); + + virtual ErrorCode send(MeshPacket *p); + + /** + * Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving) + * + * This method must be used before putting the CPU into deep or light sleep. + */ + virtual bool canSleep(); + + /** + * Start waiting to receive a message + * + * External functions can call this method to wake the device from sleep. + */ + virtual void startReceive() = 0; + + private: + /** start an immediate transmit */ + void startSend(MeshPacket *txp); + + /** if we have something waiting to send, start a short random timer so we can come check for collision before actually doing + * the transmit + * + * If the timer was already running, we just wait for that one to occur. + * */ + void startTransmitTimer(bool withDelay = true); + + void handleTransmitInterrupt(); + void handleReceiveInterrupt(); + + static void timerCallback(void *p1, uint32_t p2); + + protected: + /** + * Convert our modemConfig enum into wf, sf, etc... + * + * These paramaters will be pull from the channelSettings global + */ + virtual void applyModemConfig(); + + /** Could we send right now (i.e. either not actively receiving or transmitting)? */ + virtual bool canSendImmediately(); + + /** are we actively receiving a packet (only called during receiving state) */ + virtual bool isActivelyReceiving() = 0; + + /** + * Raw ISR handler that just calls our polymorphic method + */ + static void isrRxLevel0(); + + /** + * If a send was in progress finish it and return the buffer to the pool */ + void completeSending(); + + /** + * Add SNR data to received messages + */ + virtual void addReceiveMetadata(MeshPacket *mp) = 0; + + virtual void loop(); // Idle processing + + virtual void setStandby() = 0; +}; \ No newline at end of file diff --git a/src/mesh/RadioLibRF95.cpp b/src/mesh/RadioLibRF95.cpp new file mode 100644 index 000000000..c82c1b0c7 --- /dev/null +++ b/src/mesh/RadioLibRF95.cpp @@ -0,0 +1,68 @@ +#include "RadioLibRF95.h" + +#define RF95_CHIP_VERSION 0x12 +#define RF95_ALT_VERSION 0x11 // Supposedly some versions of the chip have id 0x11 + +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, + uint16_t preambleLength, uint8_t gain) +{ + // execute common part + int16_t state = SX127x::begin(RF95_CHIP_VERSION, syncWord, currentLimit, preambleLength); + if (state != ERR_NONE) + state = SX127x::begin(RF95_ALT_VERSION, syncWord, currentLimit, preambleLength); + RADIOLIB_ASSERT(state); + + // configure settings not accessible by API + state = config(); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setBandwidth(bw); + RADIOLIB_ASSERT(state); + + state = setSpreadingFactor(sf); + RADIOLIB_ASSERT(state); + + state = setCodingRate(cr); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + state = setGain(gain); + + return (state); +} + +int16_t RadioLibRF95::setFrequency(float freq) +{ + // RADIOLIB_CHECK_RANGE(freq, 862.0, 1020.0, ERR_INVALID_FREQUENCY); + + // set frequency + return (SX127x::setFrequencyRaw(freq)); +} + +#define RH_RF95_MODEM_STATUS_CLEAR 0x10 +#define RH_RF95_MODEM_STATUS_HEADER_INFO_VALID 0x08 +#define RH_RF95_MODEM_STATUS_RX_ONGOING 0x04 +#define RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED 0x02 +#define RH_RF95_MODEM_STATUS_SIGNAL_DETECTED 0x01 + +bool RadioLibRF95::isReceiving() +{ + // 0x0b == Look for header info valid, signal synchronized or signal detected + uint8_t reg = readReg(SX127X_REG_MODEM_STAT); + // Serial.printf("reg %x\n", reg); + return (reg & (RH_RF95_MODEM_STATUS_SIGNAL_DETECTED | RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED | + RH_RF95_MODEM_STATUS_HEADER_INFO_VALID)) != 0; +} + +uint8_t RadioLibRF95::readReg(uint8_t addr) +{ + return _mod->SPIreadRegister(addr); +} \ No newline at end of file diff --git a/src/mesh/RadioLibRF95.h b/src/mesh/RadioLibRF95.h new file mode 100644 index 000000000..3c45a8f44 --- /dev/null +++ b/src/mesh/RadioLibRF95.h @@ -0,0 +1,73 @@ +#pragma once +#include + +/*! + \class RFM95 + + \brief Derived class for %RFM95 modules. Overrides some methods from SX1278 due to different parameter ranges. +*/ +class RadioLibRF95: public SX1278 { + public: + + // constructor + + /*! + \brief Default constructor. Called from Arduino sketch when creating new LoRa instance. + + \param mod Instance of Module that will be used to communicate with the %LoRa chip. + */ + RadioLibRF95(Module* mod); + + // basic methods + + /*! + \brief %LoRa modem initialization method. Must be called at least once from Arduino sketch to initialize the module. + + \param freq Carrier frequency in MHz. Allowed values range from 868.0 MHz to 915.0 MHz. + + \param bw %LoRa link bandwidth in kHz. Allowed values are 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125, 250 and 500 kHz. + + \param sf %LoRa link spreading factor. Allowed values range from 6 to 12. + + \param cr %LoRa link coding rate denominator. Allowed values range from 5 to 8. + + \param syncWord %LoRa sync word. Can be used to distinguish different networks. Note that value 0x34 is reserved for LoRaWAN networks. + + \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. + + \param gain Gain of receiver LNA (low-noise amplifier). Can be set to any integer in range 1 to 6 where 1 is the highest gain. + Set to 0 to enable automatic gain control (recommended). + + \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); + + // configuration methods + + /*! + \brief Sets carrier frequency. Allowed values range from 868.0 MHz to 915.0 MHz. + + \param freq Carrier frequency to be set in MHz. + + \returns \ref status_codes + */ + int16_t setFrequency(float freq); + + // Return true if we are actively receiving a message currently + bool isReceiving(); + + /// For debugging + uint8_t readReg(uint8_t addr); + +#ifndef RADIOLIB_GODMODE + private: +#endif + +}; + diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp new file mode 100644 index 000000000..0500f2799 --- /dev/null +++ b/src/mesh/ReliableRouter.cpp @@ -0,0 +1,178 @@ +#include "ReliableRouter.h" +#include "MeshTypes.h" +#include "configuration.h" +#include "mesh-pb-constants.h" + +// ReliableRouter::ReliableRouter() {} + +/** + * If the message is want_ack, then add it to a list of packets to retransmit. + * If we run out of retransmissions, send a nak packet towards the original client to indicate failure. + */ +ErrorCode ReliableRouter::send(MeshPacket *p) +{ + if (p->want_ack) { + // If someone asks for acks on broadcast, we need the hop limit to be at least one, so that first node that receives our + // message will rebroadcast + if (p->to == NODENUM_BROADCAST && p->hop_limit == 0) + p->hop_limit = 1; + + auto copy = packetPool.allocCopy(*p); + startRetransmission(copy); + } + + return FloodingRouter::send(p); +} + +/** + * If we receive a want_ack packet (do not check for wasSeenRecently), send back an ack (this might generate multiple ack sends in + * case the our first ack gets lost) + * + * If we receive an ack packet (do check wasSeenRecently), clear out any retransmissions and + * forward the ack to the application layer. + * + * If we receive a nak packet (do check wasSeenRecently), clear out any retransmissions + * and forward the nak to the application layer. + * + * Otherwise, let superclass handle it. + */ +void ReliableRouter::handleReceived(MeshPacket *p) +{ + NodeNum ourNode = getNodeNum(); + + if (p->from == ourNode && p->to == NODENUM_BROADCAST) { + DEBUG_MSG("Received someone rebroadcasting for us fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + + // We are seeing someone rebroadcast one of our broadcast attempts. + // If this is the first time we saw this, cancel any retransmissions we have queued up and generate an internal ack for + // the original sending process. + if (stopRetransmission(p->from, p->id)) { + DEBUG_MSG("Someone is retransmitting for us, generate implicit ack\n"); + sendAckNak(true, p->from, p->id); + } + } else if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (for now) + if (p->want_ack) { + sendAckNak(true, p->from, p->id); + } + + if (perhapsDecode(p)) { + // If the payload is valid, look for ack/nak + + PacketId ackId = p->decoded.which_ack == SubPacket_success_id_tag ? p->decoded.ack.success_id : 0; + PacketId nakId = p->decoded.which_ack == SubPacket_fail_id_tag ? p->decoded.ack.fail_id : 0; + + // we are careful to only read/update wasSeenRecently _after_ confirming this is an ack (to not mess + // up broadcasts) + if ((ackId || nakId) && !wasSeenRecently(p, false)) { + if (ackId) { + DEBUG_MSG("Received a ack=%d, stopping retransmissions\n", ackId); + stopRetransmission(p->to, ackId); + } else { + DEBUG_MSG("Received a nak=%d, stopping retransmissions\n", nakId); + stopRetransmission(p->to, nakId); + } + } + } + } + + // handle the packet as normal + FloodingRouter::handleReceived(p); +} + +/** + * Send an ack or a nak packet back towards whoever sent idFrom + */ +void ReliableRouter::sendAckNak(bool isAck, NodeNum to, PacketId idFrom) +{ + auto p = allocForSending(); + p->hop_limit = 0; // Assume just immediate neighbors for now + p->to = to; + DEBUG_MSG("Sending an ack=0x%x,to=0x%x,idFrom=%d,id=%d\n", isAck, to, idFrom, p->id); + + if (isAck) { + p->decoded.ack.success_id = idFrom; + p->decoded.which_ack = SubPacket_success_id_tag; + } else { + p->decoded.ack.fail_id = idFrom; + p->decoded.which_ack = SubPacket_fail_id_tag; + } + + sendLocal(p); // we sometimes send directly to the local node +} + +#define NUM_RETRANSMISSIONS 3 + +PendingPacket::PendingPacket(MeshPacket *p) +{ + packet = p; + numRetransmissions = NUM_RETRANSMISSIONS - 1; // We subtract one, because we assume the user just did the first send + setNextTx(); +} + +/** + * Stop any retransmissions we are doing of the specified node/packet ID pair + */ +bool ReliableRouter::stopRetransmission(NodeNum from, PacketId id) +{ + auto key = GlobalPacketId(from, id); + return stopRetransmission(key); +} + +bool ReliableRouter::stopRetransmission(GlobalPacketId key) +{ + auto old = pending.find(key); // If we have an old record, someone messed up because id got reused + if (old != pending.end()) { + auto numErased = pending.erase(key); + assert(numErased == 1); + packetPool.release(old->second.packet); + return true; + } else + return false; +} +/** + * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. + */ +void ReliableRouter::startRetransmission(MeshPacket *p) +{ + auto id = GlobalPacketId(p); + auto rec = PendingPacket(p); + + stopRetransmission(p->from, p->id); + pending[id] = rec; +} + +/** + * Do any retransmissions that are scheduled (FIXME - for the time being called from loop) + */ +void ReliableRouter::doRetransmissions() +{ + uint32_t now = millis(); + + // FIXME, we should use a better datastructure rather than walking through this map. + // for(auto el: pending) { + for (auto it = pending.begin(), nextIt = it; it != pending.end(); it = nextIt) { + ++nextIt; // we use this odd pattern because we might be deleting it... + auto &p = it->second; + + // FIXME, handle 51 day rolloever here!!! + if (p.nextTxMsec <= now) { + if (p.numRetransmissions == 0) { + DEBUG_MSG("Reliable send failed, returning a nak fr=0x%x,to=0x%x,id=%d\n", p.packet->from, p.packet->to, + p.packet->id); + sendAckNak(false, p.packet->from, p.packet->id); + stopRetransmission(it->first); + } else { + DEBUG_MSG("Sending reliable retransmission fr=0x%x,to=0x%x,id=%d, tries left=%d\n", p.packet->from, p.packet->to, + p.packet->id, p.numRetransmissions); + + // Note: we call the superclass version because we don't want to have our version of send() add a new + // retransmission record + FloodingRouter::send(packetPool.allocCopy(*p.packet)); + + // Queue again + --p.numRetransmissions; + p.setNextTx(); + } + } + } +} \ No newline at end of file diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h new file mode 100644 index 000000000..7030793ae --- /dev/null +++ b/src/mesh/ReliableRouter.h @@ -0,0 +1,122 @@ +#pragma once + +#include "FloodingRouter.h" +#include "PeriodicTask.h" +#include + +/** + * An identifier for a globalally unique message - a pair of the sending nodenum and the packet id assigned + * to that message + */ +struct GlobalPacketId { + NodeNum node; + PacketId id; + + bool operator==(const GlobalPacketId &p) const { return node == p.node && id == p.id; } + + GlobalPacketId(const MeshPacket *p) + { + node = p->from; + id = p->id; + } + + GlobalPacketId(NodeNum _from, PacketId _id) + { + node = _from; + id = _id; + } +}; + +/** + * A packet queued for retransmission + */ +struct PendingPacket { + MeshPacket *packet; + + /** The next time we should try to retransmit this packet */ + uint32_t nextTxMsec; + + /** Starts at NUM_RETRANSMISSIONS -1(normally 3) and counts down. Once zero it will be removed from the list */ + uint8_t numRetransmissions; + + /** True if we have started trying to find a route - for DSR usage + * While trying to find a route we don't actually send the data packet. We just leave it here pending until + * we have a route or we've failed to find one. + */ + bool wantRoute = false; + + PendingPacket() {} + PendingPacket(MeshPacket *p); + + void setNextTx() { nextTxMsec = millis() + random(20 * 1000, 22 * 1000); } +}; + +class GlobalPacketIdHashFunction +{ + public: + size_t operator()(const GlobalPacketId &p) const { return (hash()(p.node)) ^ (hash()(p.id)); } +}; + +/** + * This is a mixin that extends Router with the ability to do (one hop only) reliable message sends. + */ +class ReliableRouter : public FloodingRouter +{ + private: + unordered_map pending; + + public: + /** + * Constructor + * + */ + // ReliableRouter(); + + /** + * Send a packet on a suitable interface. This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error + */ + virtual ErrorCode send(MeshPacket *p); + + /** Do our retransmission handling */ + virtual void loop() + { + doRetransmissions(); + FloodingRouter::loop(); + } + + protected: + /** + * Called from loop() + * 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. + * + * Note: this method will free the provided packet + */ + virtual void handleReceived(MeshPacket *p); + + private: + /** + * Send an ack or a nak packet back towards whoever sent idFrom + */ + void sendAckNak(bool isAck, NodeNum to, PacketId idFrom); + + /** + * Stop any retransmissions we are doing of the specified node/packet ID pair + * + * @return true if we found and removed a transmission with this ID + */ + bool stopRetransmission(NodeNum from, PacketId id); + bool stopRetransmission(GlobalPacketId p); + + /** + * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. + */ + void startRetransmission(MeshPacket *p); + + /** + * Do any retransmissions that are scheduled (FIXME - for the time being called from loop) + */ + void doRetransmissions(); +}; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp new file mode 100644 index 000000000..0ef7b8333 --- /dev/null +++ b/src/mesh/Router.cpp @@ -0,0 +1,196 @@ +#include "Router.h" +#include "CryptoEngine.h" +#include "GPS.h" +#include "configuration.h" +#include "mesh-pb-constants.h" +#include + +/** + * Router todo + * + * DONE: Implement basic interface and use it elsewhere in app + * Add naive flooding mixin (& drop duplicate rx broadcasts), add tools for sending broadcasts with incrementing sequence #s + * Add an optional adjacent node only 'send with ack' mixin. If we timeout waiting for the ack, call handleAckTimeout(packet) + * Add DSR mixin + * + **/ + +#define MAX_RX_FROMRADIO \ + 4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big + +// I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX +#define MAX_PACKETS \ + (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + MAX_TX_QUEUE + \ + 2) // max number of packets which can be in flight (either queued from reception or queued for sending) + +MemoryPool packetPool(MAX_PACKETS); + +/** + * Constructor + * + * Currently we only allow one interface, that may change in the future + */ +Router::Router() : fromRadioQueue(MAX_RX_FROMRADIO) {} + +/** + * do idle processing + * Mostly looking in our incoming rxPacket queue and calling handleReceived. + */ +void Router::loop() +{ + MeshPacket *mp; + while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) { + handleReceived(mp); + } +} + +#define NUM_PACKET_ID 255 // 0 is consider invalid + +/// Generate a unique packet id +// FIXME, move this someplace better +PacketId generatePacketId() +{ + static uint32_t i; // Note: trying to keep this in noinit didn't help for working across reboots + static bool didInit = false; + + if (!didInit) { + didInit = true; + i = random(0, NUM_PACKET_ID + + 1); // pick a random initial sequence number at boot (to prevent repeated reboots always starting at 0) + } + + i++; + return (i % NUM_PACKET_ID) + 1; // return number between 1 and 255 +} + +MeshPacket *Router::allocForSending() +{ + MeshPacket *p = packetPool.allocZeroed(); + + p->which_payload = MeshPacket_decoded_tag; // Assume payload is decoded at start. + p->from = nodeDB.getNodeNum(); + p->to = NODENUM_BROADCAST; + p->hop_limit = HOP_RELIABLE; + p->id = generatePacketId(); + p->rx_time = getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp + + return p; +} + +ErrorCode Router::sendLocal(MeshPacket *p) +{ + if (p->to == nodeDB.getNodeNum()) { + DEBUG_MSG("Enqueuing internal message for the receive queue\n"); + fromRadioQueue.enqueue(p); + return ERRNO_OK; + } else + return send(p); +} + +/** + * Send a packet on a suitable interface. This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error. + */ +ErrorCode Router::send(MeshPacket *p) +{ + assert(p->to != nodeDB.getNodeNum()); // should have already been handled by sendLocal + + // Never set the want_ack flag on broadcast packets sent over the air. + if (p->to == NODENUM_BROADCAST) + p->want_ack = false; + + // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) + + assert(p->which_payload == MeshPacket_encrypted_tag || + p->which_payload == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now + + // First convert from protobufs to raw bytes + if (p->which_payload == MeshPacket_decoded_tag) { + static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union + + size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), SubPacket_fields, &p->decoded); + + assert(numbytes <= MAX_RHPACKETLEN); + crypto->encrypt(p->from, p->id, numbytes, bytes); + + // Copy back into the packet and set the variant type + memcpy(p->encrypted.bytes, bytes, numbytes); + p->encrypted.size = numbytes; + p->which_payload = MeshPacket_encrypted_tag; + } + + if (iface) { + // DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + return iface->send(p); + } else { + DEBUG_MSG("Dropping packet - no interfaces - fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + packetPool.release(p); + return ERRNO_NO_INTERFACES; + } +} + +/** + * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to + * update routing tables etc... based on what we overhear (even for messages not destined to our node) + */ +void Router::sniffReceived(const MeshPacket *p) +{ + DEBUG_MSG("FIXME-update-db Sniffing packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); +} + +bool Router::perhapsDecode(MeshPacket *p) +{ + if (p->which_payload == MeshPacket_decoded_tag) + return true; // If packet was already decoded just return + + assert(p->which_payload == MeshPacket_encrypted_tag); + + // FIXME - someday don't send routing packets encrypted. That would allow us to route for other channels without + // being able to decrypt their data. + // Try to decrypt the packet if we can + static uint8_t bytes[MAX_RHPACKETLEN]; + memcpy(bytes, p->encrypted.bytes, + p->encrypted.size); // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf + crypto->decrypt(p->from, p->id, p->encrypted.size, bytes); + + // Take those raw bytes and convert them back into a well structured protobuf we can understand + if (!pb_decode_from_bytes(bytes, p->encrypted.size, SubPacket_fields, &p->decoded)) { + DEBUG_MSG("Invalid protobufs in received mesh packet!\n"); + return false; + } else { + // parsing was successful + p->which_payload = MeshPacket_decoded_tag; + return true; + } +} + +NodeNum Router::getNodeNum() +{ + return nodeDB.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) +{ + // FIXME, this class shouldn't EVER need to know about the GPS, move getValidTime() into a non gps dependent function + // Also, we should set the time from the ISR and it should have msec level resolution + p->rx_time = getValidTime(); // store the arrival timestamp for the phone + + // Take those raw bytes and convert them back into a well structured protobuf we can understand + if (perhapsDecode(p)) { + // parsing was successful, queue for our recipient + + sniffReceived(p); + + if (p->to == NODENUM_BROADCAST || p->to == getNodeNum()) { + DEBUG_MSG("Notifying observers of received packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + notifyPacketReceived.notifyObservers(p); + } + } + + packetPool.release(p); +} \ No newline at end of file diff --git a/src/mesh/Router.h b/src/mesh/Router.h new file mode 100644 index 000000000..8c811667e --- /dev/null +++ b/src/mesh/Router.h @@ -0,0 +1,97 @@ +#pragma once + +#include "MemoryPool.h" +#include "MeshTypes.h" +#include "Observer.h" +#include "PointerQueue.h" +#include "RadioInterface.h" +#include "mesh.pb.h" + +/** + * A mesh aware router that supports multiple interfaces. + */ +class Router +{ + private: + RadioInterface *iface; + + /// Packets which have just arrived from the radio, ready to be processed by this service and possibly + /// forwarded to the phone. + PointerQueue fromRadioQueue; + + public: + /// Local services that want to see _every_ packet this node receives can observe this. + /// Observers should always return 0 and _copy_ any packets they want to keep for use later (this packet will be getting + /// freed) + Observable notifyPacketReceived; + + /** + * Constructor + * + */ + Router(); + + /** + * Currently we only allow one interface, that may change in the future + */ + void addInterface(RadioInterface *_iface) + { + iface = _iface; + iface->setReceiver(&fromRadioQueue); + } + + /** + * do idle processing + * Mostly looking in our incoming rxPacket queue and calling handleReceived. + */ + virtual void loop(); + + /** + * Works like send, but if we are sending to the local node, we directly put the message in the receive queue + */ + ErrorCode sendLocal(MeshPacket *p); + + /// Allocate and return a meshpacket which defaults as send to broadcast from the current node. + MeshPacket *allocForSending(); + + /** + * @return our local nodenum */ + NodeNum getNodeNum(); + + protected: + /** + * Send a packet on a suitable interface. This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error + */ + virtual ErrorCode send(MeshPacket *p); + + /** + * Called from loop() + * 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. + * + * Note: this packet will never be called for messages sent/generated by this node. + * Note: this method will free the provided packet. + */ + virtual void handleReceived(MeshPacket *p); + + /** + * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to + * update routing tables etc... based on what we overhear (even for messages not destined to our node) + */ + virtual void sniffReceived(const MeshPacket *p); + + /** + * Remove any encryption and decode the protobufs inside this packet (if necessary). + * + * @return true for success, false for corrupt packet. + */ + bool perhapsDecode(MeshPacket *p); +}; + +extern Router &router; + +/// Generate a unique packet id +// FIXME, move this someplace better +PacketId generatePacketId(); \ No newline at end of file diff --git a/src/mesh/SX1262Interface.cpp b/src/mesh/SX1262Interface.cpp new file mode 100644 index 000000000..ad305a4f1 --- /dev/null +++ b/src/mesh/SX1262Interface.cpp @@ -0,0 +1,117 @@ +#include "SX1262Interface.h" +#include + +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) +{ +} + +/// 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() +{ + RadioLibInterface::init(); + + float tcxoVoltage = 0; // None - we use an XTAL + bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC? + + applyModemConfig(); + if (power > 22) // This chip has lower power limits than some + power = 22; + int res = lora.begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO); + DEBUG_MSG("LORA init result %d\n", res); + + 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() +{ + applyModemConfig(); + + // set mode to standby + setStandby(); + + // configure publicly accessible settings + int err = lora.setSpreadingFactor(sf); + assert(err == ERR_NONE); + + err = lora.setBandwidth(bw); + assert(err == ERR_NONE); + + err = lora.setCodingRate(cr); + 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); + assert(err == ERR_NONE); + + 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 SX1262Interface::setStandby() +{ + int err = lora.standby(); + assert(err == ERR_NONE); + + 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) +{ + mp->rx_snr = lora.getSNR(); +} + +void SX1262Interface::startReceive() +{ + setStandby(); + int err = lora.startReceive(); + assert(err == ERR_NONE); + + isReceiving = true; + + // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits + enableInterrupt(isrRxLevel0); +} + +/** Could we send right now (i.e. either not actively receving or transmitting)? */ +bool SX1262Interface::isActivelyReceiving() +{ + return lora.getPacketLength() > 0; +} + +bool SX1262Interface::sleep() +{ + // put chipset into sleep mode + disableInterrupt(); + lora.sleep(); + + return true; +} \ No newline at end of file diff --git a/src/mesh/SX1262Interface.h b/src/mesh/SX1262Interface.h new file mode 100644 index 000000000..c1e1fb1c6 --- /dev/null +++ b/src/mesh/SX1262Interface.h @@ -0,0 +1,55 @@ +#pragma once + +#include "RadioLibInterface.h" + +/** + * Our adapter for SX1262 radios + */ +class SX1262Interface : public RadioLibInterface +{ + 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 INTERRUPT_ATTR disableInterrupt() { lora.clearDio1Action(); } + + /** + * 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(); + /** + * 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 new file mode 100644 index 000000000..49ccb3d6b --- /dev/null +++ b/src/mesh/StreamAPI.cpp @@ -0,0 +1,91 @@ +#include "StreamAPI.h" +#include "configuration.h" + +#define START1 0x94 +#define START2 0xc3 +#define HEADER_LEN 4 + +void StreamAPI::loop() +{ + writeStream(); + readStream(); +} + +/** + * Read any rx chars from the link and call handleToRadio + */ +void StreamAPI::readStream() +{ + while (stream->available()) { // Currently we never want to block + uint8_t c = stream->read(); + + // 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 + + rxBuf[ptr] = c; // store all bytes (including framing) + + 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 + uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing + + if (ptr == HEADER_LEN) { + // 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) { + // If we didn't just fail the packet and we now have the right # of bytes, parse it + handleToRadio(rxBuf + HEADER_LEN, len); + rxPtr = 0; // start over again + } + } + } +} + +/** + * call getFromRadio() and deliver encapsulated packets to the Stream + */ +void StreamAPI::writeStream() +{ + if (canWrite) { + uint32_t len; + do { + // Send every packet we can + len = getFromRadio(txBuf + HEADER_LEN); + emitTxBuffer(len); + } while (len); + } +} + +/** + * Send the current txBuffer over our stream + */ +void StreamAPI::emitTxBuffer(size_t len) +{ + if (len != 0) { + txBuf[0] = START1; + txBuf[1] = START2; + txBuf[2] = (len >> 8) & 0xff; + txBuf[3] = len & 0xff; + + stream->write(txBuf, len + HEADER_LEN); + } +} + +void StreamAPI::emitRebooted() +{ + // In case we send a FromRadio packet + memset(&fromRadioScratch, 0, sizeof(fromRadioScratch)); + fromRadioScratch.which_variant = FromRadio_rebooted_tag; + fromRadioScratch.variant.rebooted = true; + + DEBUG_MSG("Emitting reboot packet for serial shell\n"); + emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, FromRadio_size, FromRadio_fields, &fromRadioScratch)); +} \ No newline at end of file diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h new file mode 100644 index 000000000..ed0a5fbd4 --- /dev/null +++ b/src/mesh/StreamAPI.h @@ -0,0 +1,77 @@ +#pragma once + +#include "PhoneAPI.h" +#include "Stream.h" + +// A To/FromRadio packet + our 32 bit header +#define MAX_STREAM_BUF_SIZE (MAX_TO_FROM_RADIO_SIZE + sizeof(uint32_t)) + +/** + * A version of our 'phone' API that talks over a Stream. So therefore well suited to use with serial links + * or TCP connections. + * + * ## Wire encoding + +When sending protobuf packets over serial or TCP each packet is preceded by uint32 sent in network byte order (big endian). +The upper 16 bits must be 0x94C3. The lower 16 bits are packet length (this encoding gives room to eventually allow quite large +packets). + +Implementations validate length against the maximum possible size of a BLE packet (our lowest common denominator) of 512 bytes. If +the length provided is larger than that we assume the packet is corrupted and begin again looking for 0x4403 framing. + +The packets flowing towards the device are ToRadio protobufs, the packets flowing from the device are FromRadio protobufs. +The 0x94C3 marker can be used as framing to (eventually) resync if packets are corrupted over the wire. + +Note: the 0x94C3 framing was chosen to prevent confusion with the 7 bit ascii character set. It also doesn't collide with any +valid utf8 encoding. This makes it a bit easier to start a device outputting regular debug output on its serial port and then only +after it has received a valid packet from the PC, turn off unencoded debug printing and switch to this packet encoding. + + */ +class StreamAPI : public PhoneAPI +{ + /** + * The stream we read/write from + */ + Stream *stream; + + uint8_t rxBuf[MAX_STREAM_BUF_SIZE]; + size_t rxPtr = 0; + + public: + StreamAPI(Stream *_stream) : stream(_stream) {} + + /** + * Currently we require frequent invocation from loop() to check for arrived serial packets and to send new packets to the + * phone. + * FIXME, to support better power behavior instead move to a thread and block on serial reads. + */ + void loop(); + + private: + /** + * Read any rx chars from the link and call handleToRadio + */ + void readStream(); + + /** + * call getFromRadio() and deliver encapsulated packets to the Stream + */ + void writeStream(); + + protected: + /** + * Send a FromRadio.rebooted = true packet to the phone + */ + void emitRebooted(); + + /** + * Send the current txBuffer over our stream + */ + void emitTxBuffer(size_t len); + + /// Are we allowed to write packets to our output stream (subclasses can turn this off - i.e. SerialConsole) + bool canWrite = true; + + /// Subclasses can use this scratch buffer if they wish + uint8_t txBuf[MAX_STREAM_BUF_SIZE]; +}; \ No newline at end of file diff --git a/src/TypedQueue.h b/src/mesh/TypedQueue.h similarity index 94% rename from src/TypedQueue.h rename to src/mesh/TypedQueue.h index 6dfdc8e4f..a16e624f9 100644 --- a/src/TypedQueue.h +++ b/src/mesh/TypedQueue.h @@ -3,8 +3,7 @@ #include #include -#include -#include +#include "freertosinc.h" /** * A wrapper for freertos queues. Note: each element object should be small diff --git a/src/mesh-pb-constants.cpp b/src/mesh/mesh-pb-constants.cpp similarity index 95% rename from src/mesh-pb-constants.cpp rename to src/mesh/mesh-pb-constants.cpp index 204fdd8fe..7e3800112 100644 --- a/src/mesh-pb-constants.cpp +++ b/src/mesh/mesh-pb-constants.cpp @@ -35,8 +35,9 @@ bool pb_decode_from_bytes(const uint8_t *srcbuf, size_t srcbufsize, const pb_msg /// Read from an Arduino File bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count) { + bool status = false; +#ifndef NO_ESP32 File *file = (File *)stream->state; - bool status; if (buf == NULL) { while (count-- && file->read() != EOF) @@ -48,14 +49,18 @@ bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count) if (file->available() == 0) stream->bytes_left = 0; - +#endif return status; } /// Write to an arduino file bool writecb(pb_ostream_t *stream, const uint8_t *buf, size_t count) { +#ifndef NO_ESP32 File *file = (File *)stream->state; // DEBUG_MSG("writing %d bytes to protobuf file\n", count); return file->write(buf, count) == count; +#else + return false; +#endif } diff --git a/src/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h similarity index 100% rename from src/mesh-pb-constants.h rename to src/mesh/mesh-pb-constants.h diff --git a/src/mesh.pb.c b/src/mesh/mesh.pb.c similarity index 70% rename from src/mesh.pb.c rename to src/mesh/mesh.pb.c index e8c0faa28..321576556 100644 --- a/src/mesh.pb.c +++ b/src/mesh/mesh.pb.c @@ -15,6 +15,9 @@ PB_BIND(Data, Data, 2) PB_BIND(User, User, AUTO) +PB_BIND(RouteDiscovery, RouteDiscovery, AUTO) + + PB_BIND(SubPacket, SubPacket, 2) @@ -39,21 +42,19 @@ PB_BIND(MyNodeInfo, MyNodeInfo, AUTO) PB_BIND(DeviceState, DeviceState, 4) +PB_BIND(DebugString, DebugString, 2) + + PB_BIND(FromRadio, FromRadio, 2) PB_BIND(ToRadio, ToRadio, 2) +PB_BIND(ManufacturingData, ManufacturingData, AUTO) + -#ifndef PB_CONVERT_DOUBLE_FLOAT -/* On some platforms (such as AVR), double is really float. - * To be able to encode/decode double on these platforms, you need. - * to define PB_CONVERT_DOUBLE_FLOAT in pb.h or compiler command line. - */ -PB_STATIC_ASSERT(sizeof(double) == 8, DOUBLE_MUST_BE_8_BYTES) -#endif diff --git a/src/mesh.pb.h b/src/mesh/mesh.pb.h similarity index 68% rename from src/mesh.pb.h rename to src/mesh/mesh.pb.h index 2c524662e..ce9bc4e36 100644 --- a/src/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -19,7 +19,7 @@ typedef enum _Constants { } Constants; typedef enum _Data_Type { - Data_Type_SIGNAL_OPAQUE = 0, + Data_Type_OPAQUE = 0, Data_Type_CLEAR_TEXT = 1, Data_Type_CLEAR_READACK = 2 } Data_Type; @@ -32,10 +32,11 @@ typedef enum _ChannelSettings_ModemConfig { } ChannelSettings_ModemConfig; /* Struct definitions */ +typedef PB_BYTES_ARRAY_T(32) ChannelSettings_psk_t; typedef struct _ChannelSettings { int32_t tx_power; ChannelSettings_ModemConfig modem_config; - pb_byte_t psk[16]; + ChannelSettings_psk_t psk; char name[12]; } ChannelSettings; @@ -45,6 +46,17 @@ typedef struct _Data { Data_payload_t payload; } Data; +typedef struct _DebugString { + char message[256]; +} DebugString; + +typedef struct _ManufacturingData { + uint32_t fradioFreq; + pb_callback_t hw_model; + pb_callback_t hw_version; + int32_t selftest_result; +} ManufacturingData; + typedef struct _MyNodeInfo { int32_t my_node_num; bool has_gps; @@ -58,10 +70,10 @@ typedef struct _MyNodeInfo { } MyNodeInfo; typedef struct _Position { - double latitude; - double longitude; int32_t altitude; int32_t battery_level; + int32_t latitude_i; + int32_t longitude_i; uint32_t time; } Position; @@ -81,6 +93,11 @@ typedef struct _RadioConfig_UserPreferences { bool promiscuous_mode; } RadioConfig_UserPreferences; +typedef struct _RouteDiscovery { + pb_size_t route_count; + int32_t route[8]; +} RouteDiscovery; + typedef struct _User { char id[16]; char long_name[40]; @@ -89,13 +106,13 @@ typedef struct _User { } User; typedef struct _NodeInfo { - int32_t num; + uint32_t num; bool has_user; User user; bool has_position; Position position; - int32_t snr; - int32_t frequency_error; + uint32_t next_hop; + float snr; } NodeInfo; typedef struct _RadioConfig { @@ -106,21 +123,37 @@ typedef struct _RadioConfig { } RadioConfig; typedef struct _SubPacket { - pb_size_t which_variant; + pb_size_t which_payload; union { Position position; Data data; User user; - } variant; + RouteDiscovery request; + RouteDiscovery reply; + }; bool want_response; + uint32_t dest; + pb_size_t which_ack; + union { + uint32_t success_id; + uint32_t fail_id; + } ack; } SubPacket; +typedef PB_BYTES_ARRAY_T(256) MeshPacket_encrypted_t; typedef struct _MeshPacket { - int32_t from; - int32_t to; - bool has_payload; - SubPacket payload; + uint32_t from; + uint32_t to; + pb_size_t which_payload; + union { + SubPacket decoded; + MeshPacket_encrypted_t encrypted; + }; + uint32_t id; + float rx_snr; uint32_t rx_time; + uint32_t hop_limit; + bool want_ack; } MeshPacket; typedef struct _DeviceState { @@ -144,6 +177,12 @@ typedef struct _FromRadio { pb_size_t which_variant; union { MeshPacket packet; + MyNodeInfo my_info; + NodeInfo node_info; + RadioConfig radio; + DebugString debug_string; + uint32_t config_complete_id; + bool rebooted; } variant; } FromRadio; @@ -151,6 +190,9 @@ typedef struct _ToRadio { pb_size_t which_variant; union { MeshPacket packet; + uint32_t want_config_id; + RadioConfig set_radio; + User set_owner; } variant; } ToRadio; @@ -160,7 +202,7 @@ typedef struct _ToRadio { #define _Constants_MAX Constants_Unused #define _Constants_ARRAYSIZE ((Constants)(Constants_Unused+1)) -#define _Data_Type_MIN Data_Type_SIGNAL_OPAQUE +#define _Data_Type_MIN Data_Type_OPAQUE #define _Data_Type_MAX Data_Type_CLEAR_READACK #define _Data_Type_ARRAYSIZE ((Data_Type)(Data_Type_CLEAR_READACK+1)) @@ -173,29 +215,35 @@ typedef struct _ToRadio { #define Position_init_default {0, 0, 0, 0, 0} #define Data_init_default {_Data_Type_MIN, {0, {0}}} #define User_init_default {"", "", "", {0}} -#define SubPacket_init_default {0, {Position_init_default}, 0} -#define MeshPacket_init_default {0, 0, false, SubPacket_init_default, 0} -#define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0}, ""} +#define RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}} +#define SubPacket_init_default {0, {Position_init_default}, 0, 0, 0, {0}} +#define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0, 0, 0} +#define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""} #define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default} #define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0} #define MyNodeInfo_init_default {0, 0, 0, "", "", "", 0, 0, 0} #define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default}, false, MeshPacket_init_default, 0} +#define DebugString_init_default {""} #define FromRadio_init_default {0, 0, {MeshPacket_init_default}} #define ToRadio_init_default {0, {MeshPacket_init_default}} +#define ManufacturingData_init_default {0, {{NULL}, NULL}, {{NULL}, NULL}, 0} #define Position_init_zero {0, 0, 0, 0, 0} #define Data_init_zero {_Data_Type_MIN, {0, {0}}} #define User_init_zero {"", "", "", {0}} -#define SubPacket_init_zero {0, {Position_init_zero}, 0} -#define MeshPacket_init_zero {0, 0, false, SubPacket_init_zero, 0} -#define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0}, ""} +#define RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}} +#define SubPacket_init_zero {0, {Position_init_zero}, 0, 0, 0, {0}} +#define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0, 0, 0} +#define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""} #define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero} #define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0} #define MyNodeInfo_init_zero {0, 0, 0, "", "", "", 0, 0, 0} #define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero}, false, MeshPacket_init_zero, 0} +#define DebugString_init_zero {""} #define FromRadio_init_zero {0, 0, {MeshPacket_init_zero}} #define ToRadio_init_zero {0, {MeshPacket_init_zero}} +#define ManufacturingData_init_zero {0, {{NULL}, NULL}, {{NULL}, NULL}, 0} /* Field tags (for use in manual encoding/decoding) */ #define ChannelSettings_tx_power_tag 1 @@ -204,6 +252,11 @@ typedef struct _ToRadio { #define ChannelSettings_name_tag 5 #define Data_typ_tag 1 #define Data_payload_tag 2 +#define DebugString_message_tag 1 +#define ManufacturingData_fradioFreq_tag 1 +#define ManufacturingData_hw_model_tag 2 +#define ManufacturingData_hw_version_tag 3 +#define ManufacturingData_selftest_result_tag 4 #define MyNodeInfo_my_node_num_tag 1 #define MyNodeInfo_has_gps_tag 2 #define MyNodeInfo_num_channels_tag 3 @@ -213,11 +266,11 @@ typedef struct _ToRadio { #define MyNodeInfo_error_code_tag 7 #define MyNodeInfo_error_address_tag 8 #define MyNodeInfo_error_count_tag 9 -#define Position_latitude_tag 1 -#define Position_longitude_tag 2 +#define Position_latitude_i_tag 7 +#define Position_longitude_i_tag 8 #define Position_altitude_tag 3 #define Position_battery_level_tag 4 -#define Position_time_tag 6 +#define Position_time_tag 9 #define RadioConfig_UserPreferences_position_broadcast_secs_tag 1 #define RadioConfig_UserPreferences_send_owner_interval_tag 2 #define RadioConfig_UserPreferences_num_missed_to_fail_tag 3 @@ -231,6 +284,7 @@ typedef struct _ToRadio { #define RadioConfig_UserPreferences_min_wake_secs_tag 11 #define RadioConfig_UserPreferences_keep_all_packets_tag 100 #define RadioConfig_UserPreferences_promiscuous_mode_tag 101 +#define RouteDiscovery_route_tag 2 #define User_id_tag 1 #define User_long_name_tag 2 #define User_short_name_tag 3 @@ -238,18 +292,28 @@ typedef struct _ToRadio { #define NodeInfo_num_tag 1 #define NodeInfo_user_tag 2 #define NodeInfo_position_tag 3 -#define NodeInfo_snr_tag 5 -#define NodeInfo_frequency_error_tag 6 +#define NodeInfo_snr_tag 7 +#define NodeInfo_next_hop_tag 5 #define RadioConfig_preferences_tag 1 #define RadioConfig_channel_settings_tag 2 #define SubPacket_position_tag 1 #define SubPacket_data_tag 3 #define SubPacket_user_tag 4 +#define SubPacket_request_tag 6 +#define SubPacket_reply_tag 7 +#define SubPacket_success_id_tag 10 +#define SubPacket_fail_id_tag 11 #define SubPacket_want_response_tag 5 +#define SubPacket_dest_tag 9 +#define MeshPacket_decoded_tag 3 +#define MeshPacket_encrypted_tag 8 #define MeshPacket_from_tag 1 #define MeshPacket_to_tag 2 -#define MeshPacket_payload_tag 3 -#define MeshPacket_rx_time_tag 4 +#define MeshPacket_id_tag 6 +#define MeshPacket_rx_time_tag 9 +#define MeshPacket_rx_snr_tag 7 +#define MeshPacket_hop_limit_tag 10 +#define MeshPacket_want_ack_tag 11 #define DeviceState_radio_tag 1 #define DeviceState_my_node_tag 2 #define DeviceState_owner_tag 3 @@ -258,16 +322,25 @@ typedef struct _ToRadio { #define DeviceState_version_tag 8 #define DeviceState_rx_text_message_tag 7 #define FromRadio_packet_tag 2 +#define FromRadio_my_info_tag 3 +#define FromRadio_node_info_tag 4 +#define FromRadio_radio_tag 6 +#define FromRadio_debug_string_tag 7 +#define FromRadio_config_complete_id_tag 8 +#define FromRadio_rebooted_tag 9 #define FromRadio_num_tag 1 #define ToRadio_packet_tag 1 +#define ToRadio_want_config_id_tag 100 +#define ToRadio_set_radio_tag 101 +#define ToRadio_set_owner_tag 102 /* Struct field encoding specification for nanopb */ #define Position_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, DOUBLE, latitude, 1) \ -X(a, STATIC, SINGULAR, DOUBLE, longitude, 2) \ X(a, STATIC, SINGULAR, INT32, altitude, 3) \ X(a, STATIC, SINGULAR, INT32, battery_level, 4) \ -X(a, STATIC, SINGULAR, UINT32, time, 6) +X(a, STATIC, SINGULAR, SINT32, latitude_i, 7) \ +X(a, STATIC, SINGULAR, SINT32, longitude_i, 8) \ +X(a, STATIC, SINGULAR, FIXED32, time, 9) #define Position_CALLBACK NULL #define Position_DEFAULT NULL @@ -285,30 +358,47 @@ X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, macaddr, 4) #define User_CALLBACK NULL #define User_DEFAULT NULL +#define RouteDiscovery_FIELDLIST(X, a) \ +X(a, STATIC, REPEATED, INT32, route, 2) +#define RouteDiscovery_CALLBACK NULL +#define RouteDiscovery_DEFAULT NULL + #define SubPacket_FIELDLIST(X, a) \ -X(a, STATIC, ONEOF, MESSAGE, (variant,position,variant.position), 1) \ -X(a, STATIC, ONEOF, MESSAGE, (variant,data,variant.data), 3) \ -X(a, STATIC, ONEOF, MESSAGE, (variant,user,variant.user), 4) \ -X(a, STATIC, SINGULAR, BOOL, want_response, 5) +X(a, STATIC, ONEOF, MESSAGE, (payload,position,position), 1) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,data,data), 3) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,user,user), 4) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,request,request), 6) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,reply,reply), 7) \ +X(a, STATIC, SINGULAR, BOOL, want_response, 5) \ +X(a, STATIC, SINGULAR, UINT32, dest, 9) \ +X(a, STATIC, ONEOF, UINT32, (ack,success_id,ack.success_id), 10) \ +X(a, STATIC, ONEOF, UINT32, (ack,fail_id,ack.fail_id), 11) #define SubPacket_CALLBACK NULL #define SubPacket_DEFAULT NULL -#define SubPacket_variant_position_MSGTYPE Position -#define SubPacket_variant_data_MSGTYPE Data -#define SubPacket_variant_user_MSGTYPE User +#define SubPacket_payload_position_MSGTYPE Position +#define SubPacket_payload_data_MSGTYPE Data +#define SubPacket_payload_user_MSGTYPE User +#define SubPacket_payload_request_MSGTYPE RouteDiscovery +#define SubPacket_payload_reply_MSGTYPE RouteDiscovery #define MeshPacket_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, INT32, from, 1) \ -X(a, STATIC, SINGULAR, INT32, to, 2) \ -X(a, STATIC, OPTIONAL, MESSAGE, payload, 3) \ -X(a, STATIC, SINGULAR, UINT32, rx_time, 4) +X(a, STATIC, SINGULAR, UINT32, from, 1) \ +X(a, STATIC, SINGULAR, UINT32, to, 2) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,decoded,decoded), 3) \ +X(a, STATIC, ONEOF, BYTES, (payload,encrypted,encrypted), 8) \ +X(a, STATIC, SINGULAR, UINT32, id, 6) \ +X(a, STATIC, SINGULAR, FLOAT, rx_snr, 7) \ +X(a, STATIC, SINGULAR, FIXED32, rx_time, 9) \ +X(a, STATIC, SINGULAR, UINT32, hop_limit, 10) \ +X(a, STATIC, SINGULAR, BOOL, want_ack, 11) #define MeshPacket_CALLBACK NULL #define MeshPacket_DEFAULT NULL -#define MeshPacket_payload_MSGTYPE SubPacket +#define MeshPacket_payload_decoded_MSGTYPE SubPacket #define ChannelSettings_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, INT32, tx_power, 1) \ X(a, STATIC, SINGULAR, UENUM, modem_config, 3) \ -X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, psk, 4) \ +X(a, STATIC, SINGULAR, BYTES, psk, 4) \ X(a, STATIC, SINGULAR, STRING, name, 5) #define ChannelSettings_CALLBACK NULL #define ChannelSettings_DEFAULT NULL @@ -339,11 +429,11 @@ X(a, STATIC, SINGULAR, BOOL, promiscuous_mode, 101) #define RadioConfig_UserPreferences_DEFAULT NULL #define NodeInfo_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, INT32, num, 1) \ +X(a, STATIC, SINGULAR, UINT32, num, 1) \ X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \ X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \ -X(a, STATIC, SINGULAR, INT32, snr, 5) \ -X(a, STATIC, SINGULAR, INT32, frequency_error, 6) +X(a, STATIC, SINGULAR, UINT32, next_hop, 5) \ +X(a, STATIC, SINGULAR, FLOAT, snr, 7) #define NodeInfo_CALLBACK NULL #define NodeInfo_DEFAULT NULL #define NodeInfo_user_MSGTYPE User @@ -379,22 +469,51 @@ X(a, STATIC, SINGULAR, UINT32, version, 8) #define DeviceState_receive_queue_MSGTYPE MeshPacket #define DeviceState_rx_text_message_MSGTYPE MeshPacket +#define DebugString_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, message, 1) +#define DebugString_CALLBACK NULL +#define DebugString_DEFAULT NULL + #define FromRadio_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, num, 1) \ -X(a, STATIC, ONEOF, MESSAGE, (variant,packet,variant.packet), 2) +X(a, STATIC, ONEOF, MESSAGE, (variant,packet,variant.packet), 2) \ +X(a, STATIC, ONEOF, MESSAGE, (variant,my_info,variant.my_info), 3) \ +X(a, STATIC, ONEOF, MESSAGE, (variant,node_info,variant.node_info), 4) \ +X(a, STATIC, ONEOF, MESSAGE, (variant,radio,variant.radio), 6) \ +X(a, STATIC, ONEOF, MESSAGE, (variant,debug_string,variant.debug_string), 7) \ +X(a, STATIC, ONEOF, UINT32, (variant,config_complete_id,variant.config_complete_id), 8) \ +X(a, STATIC, ONEOF, BOOL, (variant,rebooted,variant.rebooted), 9) #define FromRadio_CALLBACK NULL #define FromRadio_DEFAULT NULL #define FromRadio_variant_packet_MSGTYPE MeshPacket +#define FromRadio_variant_my_info_MSGTYPE MyNodeInfo +#define FromRadio_variant_node_info_MSGTYPE NodeInfo +#define FromRadio_variant_radio_MSGTYPE RadioConfig +#define FromRadio_variant_debug_string_MSGTYPE DebugString #define ToRadio_FIELDLIST(X, a) \ -X(a, STATIC, ONEOF, MESSAGE, (variant,packet,variant.packet), 1) +X(a, STATIC, ONEOF, MESSAGE, (variant,packet,variant.packet), 1) \ +X(a, STATIC, ONEOF, UINT32, (variant,want_config_id,variant.want_config_id), 100) \ +X(a, STATIC, ONEOF, MESSAGE, (variant,set_radio,variant.set_radio), 101) \ +X(a, STATIC, ONEOF, MESSAGE, (variant,set_owner,variant.set_owner), 102) #define ToRadio_CALLBACK NULL #define ToRadio_DEFAULT NULL #define ToRadio_variant_packet_MSGTYPE MeshPacket +#define ToRadio_variant_set_radio_MSGTYPE RadioConfig +#define ToRadio_variant_set_owner_MSGTYPE User + +#define ManufacturingData_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, fradioFreq, 1) \ +X(a, CALLBACK, SINGULAR, STRING, hw_model, 2) \ +X(a, CALLBACK, SINGULAR, STRING, hw_version, 3) \ +X(a, STATIC, SINGULAR, SINT32, selftest_result, 4) +#define ManufacturingData_CALLBACK pb_default_field_callback +#define ManufacturingData_DEFAULT NULL extern const pb_msgdesc_t Position_msg; extern const pb_msgdesc_t Data_msg; extern const pb_msgdesc_t User_msg; +extern const pb_msgdesc_t RouteDiscovery_msg; extern const pb_msgdesc_t SubPacket_msg; extern const pb_msgdesc_t MeshPacket_msg; extern const pb_msgdesc_t ChannelSettings_msg; @@ -403,13 +522,16 @@ extern const pb_msgdesc_t RadioConfig_UserPreferences_msg; extern const pb_msgdesc_t NodeInfo_msg; extern const pb_msgdesc_t MyNodeInfo_msg; extern const pb_msgdesc_t DeviceState_msg; +extern const pb_msgdesc_t DebugString_msg; extern const pb_msgdesc_t FromRadio_msg; extern const pb_msgdesc_t ToRadio_msg; +extern const pb_msgdesc_t ManufacturingData_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define Position_fields &Position_msg #define Data_fields &Data_msg #define User_fields &User_msg +#define RouteDiscovery_fields &RouteDiscovery_msg #define SubPacket_fields &SubPacket_msg #define MeshPacket_fields &MeshPacket_msg #define ChannelSettings_fields &ChannelSettings_msg @@ -418,23 +540,28 @@ extern const pb_msgdesc_t ToRadio_msg; #define NodeInfo_fields &NodeInfo_msg #define MyNodeInfo_fields &MyNodeInfo_msg #define DeviceState_fields &DeviceState_msg +#define DebugString_fields &DebugString_msg #define FromRadio_fields &FromRadio_msg #define ToRadio_fields &ToRadio_msg +#define ManufacturingData_fields &ManufacturingData_msg /* Maximum encoded size of messages (where known) */ -#define Position_size 46 +#define Position_size 39 #define Data_size 256 #define User_size 72 -#define SubPacket_size 261 -#define MeshPacket_size 292 -#define ChannelSettings_size 44 -#define RadioConfig_size 120 +#define RouteDiscovery_size 88 +#define SubPacket_size 273 +#define MeshPacket_size 312 +#define ChannelSettings_size 60 +#define RadioConfig_size 136 #define RadioConfig_UserPreferences_size 72 -#define NodeInfo_size 155 +#define NodeInfo_size 132 #define MyNodeInfo_size 85 -#define DeviceState_size 15080 -#define FromRadio_size 301 -#define ToRadio_size 295 +#define DeviceState_size 15021 +#define DebugString_size 258 +#define FromRadio_size 321 +#define ToRadio_size 315 +/* ManufacturingData_size depends on runtime parameters */ #ifdef __cplusplus } /* extern "C" */ diff --git a/src/nrf52/FS.h b/src/nrf52/FS.h new file mode 100644 index 000000000..819693660 --- /dev/null +++ b/src/nrf52/FS.h @@ -0,0 +1,3 @@ +#pragma once + +// FIXME - make a FS abstraction for NRF52 \ No newline at end of file diff --git a/src/nrf52/NRF52Bluetooth.cpp b/src/nrf52/NRF52Bluetooth.cpp new file mode 100644 index 000000000..9cf6e3708 --- /dev/null +++ b/src/nrf52/NRF52Bluetooth.cpp @@ -0,0 +1,241 @@ +#include "NRF52Bluetooth.h" +#include "configuration.h" +#include "main.h" +#include + +/* HRM Service Definitions + * Heart Rate Monitor Service: 0x180D + * Heart Rate Measurement Char: 0x2A37 + * Body Sensor Location Char: 0x2A38 + */ +BLEService hrms = BLEService(UUID16_SVC_HEART_RATE); +BLECharacteristic hrmc = BLECharacteristic(UUID16_CHR_HEART_RATE_MEASUREMENT); +BLECharacteristic bslc = BLECharacteristic(UUID16_CHR_BODY_SENSOR_LOCATION); + +BLEDis bledis; // DIS (Device Information Service) helper class instance +BLEBas blebas; // BAS (Battery Service) helper class instance +BLEDfu bledfu; // DFU software update helper service + +uint8_t bps = 0; + +void connect_callback(uint16_t conn_handle) +{ + // Get the reference to current connection + BLEConnection *connection = Bluefruit.Connection(conn_handle); + + char central_name[32] = {0}; + connection->getPeerName(central_name, sizeof(central_name)); + + DEBUG_MSG("Connected to %s\n", central_name); +} + +/** + * Callback invoked when a connection is dropped + * @param conn_handle connection where this event happens + * @param reason is a BLE_HCI_STATUS_CODE which can be found in ble_hci.h + */ +void disconnect_callback(uint16_t conn_handle, uint8_t reason) +{ + (void)conn_handle; + (void)reason; + + DEBUG_MSG("Disconnected, reason = 0x%x\n", reason); +} + +void cccd_callback(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value) +{ + // Display the raw request packet + DEBUG_MSG("CCCD Updated: %u\n", cccd_value); + + // Check the characteristic this CCCD update is associated with in case + // this handler is used for multiple CCCD records. + if (chr->uuid == hrmc.uuid) { + if (chr->notifyEnabled(conn_hdl)) { + DEBUG_MSG("Heart Rate Measurement 'Notify' enabled\n"); + } else { + DEBUG_MSG("Heart Rate Measurement 'Notify' disabled\n"); + } + } +} + +void startAdv(void) +{ + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + + // Include HRM Service UUID + Bluefruit.Advertising.addService(hrms); + + // Include Name + Bluefruit.Advertising.addName(); + + /* Start Advertising + * - Enable auto advertising if disconnected + * - Interval: fast mode = 20 ms, slow mode = 152.5 ms + * - Timeout for fast mode is 30 seconds + * - Start(timeout) with timeout = 0 will advertise forever (until connected) + * + * For recommended advertising interval + * https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds +} + +void setupHRM(void) +{ + // Configure the Heart Rate Monitor service + // See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.heart_rate.xml + // Supported Characteristics: + // Name UUID Requirement Properties + // ---------------------------- ------ ----------- ---------- + // Heart Rate Measurement 0x2A37 Mandatory Notify + // Body Sensor Location 0x2A38 Optional Read + // Heart Rate Control Point 0x2A39 Conditional Write <-- Not used here + hrms.begin(); + + // Note: You must call .begin() on the BLEService before calling .begin() on + // any characteristic(s) within that service definition.. Calling .begin() on + // a BLECharacteristic will cause it to be added to the last BLEService that + // was 'begin()'ed! + + // Configure the Heart Rate Measurement characteristic + // See: + // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.heart_rate_measurement.xml + // Properties = Notify + // Min Len = 1 + // Max Len = 8 + // B0 = UINT8 - Flag (MANDATORY) + // b5:7 = Reserved + // b4 = RR-Internal (0 = Not present, 1 = Present) + // b3 = Energy expended status (0 = Not present, 1 = Present) + // b1:2 = Sensor contact status (0+1 = Not supported, 2 = Supported but contact not detected, 3 = Supported and + // detected) b0 = Value format (0 = UINT8, 1 = UINT16) + // B1 = UINT8 - 8-bit heart rate measurement value in BPM + // B2:3 = UINT16 - 16-bit heart rate measurement value in BPM + // B4:5 = UINT16 - Energy expended in joules + // B6:7 = UINT16 - RR Internal (1/1024 second resolution) + hrmc.setProperties(CHR_PROPS_NOTIFY); + hrmc.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); + hrmc.setFixedLen(2); + hrmc.setCccdWriteCallback(cccd_callback); // Optionally capture CCCD updates + hrmc.begin(); + uint8_t hrmdata[2] = {0b00000110, 0x40}; // Set the characteristic to use 8-bit values, with the sensor connected and detected + hrmc.write(hrmdata, 2); + + // Configure the Body Sensor Location characteristic + // See: + // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.body_sensor_location.xml + // Properties = Read + // Min Len = 1 + // Max Len = 1 + // B0 = UINT8 - Body Sensor Location + // 0 = Other + // 1 = Chest + // 2 = Wrist + // 3 = Finger + // 4 = Hand + // 5 = Ear Lobe + // 6 = Foot + // 7:255 = Reserved + bslc.setProperties(CHR_PROPS_READ); + bslc.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); + bslc.setFixedLen(1); + bslc.begin(); + bslc.write8(2); // Set the characteristic to 'Wrist' (2) +} + +void NRF52Bluetooth::setup() +{ + // Initialise the Bluefruit module + DEBUG_MSG("Initialise the Bluefruit nRF52 module\n"); + Bluefruit.begin(); + + // Set the advertised device name (keep it short!) + Bluefruit.setName(getDeviceName()); // FIXME + + // Set the connect/disconnect callback handlers + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // Configure and Start the Device Information Service + DEBUG_MSG("Configuring the Device Information Service\n"); + bledis.setManufacturer("meshtastic.org"); + bledis.setModel("NRF52-meshtastic"); // FIXME + bledis.begin(); + + // Start the BLE Battery Service and set it to 100% + DEBUG_MSG("Configuring the Battery Service\n"); + blebas.begin(); + blebas.write(42); // FIXME, report real power levels + + bledfu.begin(); // Install the DFU helper + + // Setup the Heart Rate Monitor service using + // BLEService and BLECharacteristic classes + DEBUG_MSG("Configuring the Heart Rate Monitor Service\n"); + setupHRM(); + + // Setup the advertising packet(s) + DEBUG_MSG("Setting up the advertising payload(s)\n"); + startAdv(); + + DEBUG_MSG("Advertising\n"); +} + +/* +void loop() +{ + digitalToggle(LED_RED); + + if ( Bluefruit.connected() ) { + uint8_t hrmdata[2] = { 0b00000110, bps++ }; // Sensor connected, increment BPS value + + // Note: We use .notify instead of .write! + // If it is connected but CCCD is not enabled + // The characteristic's value is still updated although notification is not sent + if ( hrmc.notify(hrmdata, sizeof(hrmdata)) ){ + Serial.print("Heart Rate Measurement updated to: "); Serial.println(bps); + }else{ + Serial.println("ERROR: Notify not set in the CCCD or not connected!"); + } + } + + // Only send update once per second + delay(1000); +} +*/ + +/* +examples of advanced characteristics. use setReadAuthorizeCallback to prepare data for reads by others + +err_t BLEDfu::begin(void) +{ + // Invoke base class begin() + VERIFY_STATUS( BLEService::begin() ); + + // No need to keep packet & revision characteristics + BLECharacteristic chr_packet(UUID128_CHR_DFU_PACKET); + chr_packet.setTempMemory(); + chr_packet.setProperties(CHR_PROPS_WRITE_WO_RESP); + chr_packet.setMaxLen(20); + VERIFY_STATUS( chr_packet.begin() ); + + _chr_control.setProperties(CHR_PROPS_WRITE | CHR_PROPS_NOTIFY); + _chr_control.setMaxLen(23); + _chr_control.setWriteAuthorizeCallback(bledfu_control_wr_authorize_cb); + VERIFY_STATUS( _chr_control.begin() ); + + BLECharacteristic chr_revision(UUID128_CHR_DFU_REVISON); + chr_revision.setTempMemory(); + chr_revision.setProperties(CHR_PROPS_READ); + chr_revision.setFixedLen(2); + VERIFY_STATUS( chr_revision.begin()); + chr_revision.write16(DFU_REV_APPMODE); + + return ERROR_NONE; +} +*/ \ No newline at end of file diff --git a/src/nrf52/NRF52Bluetooth.h b/src/nrf52/NRF52Bluetooth.h new file mode 100644 index 000000000..40f13d8bc --- /dev/null +++ b/src/nrf52/NRF52Bluetooth.h @@ -0,0 +1,8 @@ +#pragma once + +class NRF52Bluetooth +{ + public: + void setup(); +}; + diff --git a/src/nrf52/NRF52CryptoEngine.cpp b/src/nrf52/NRF52CryptoEngine.cpp new file mode 100644 index 000000000..ee1650ea2 --- /dev/null +++ b/src/nrf52/NRF52CryptoEngine.cpp @@ -0,0 +1,5 @@ + +#include "CryptoEngine.h" + +// FIXME, do a NRF52 version +CryptoEngine *crypto = new CryptoEngine(); \ No newline at end of file diff --git a/src/nrf52/PmuBQ25703A.cpp b/src/nrf52/PmuBQ25703A.cpp new file mode 100644 index 000000000..25dc8fb9c --- /dev/null +++ b/src/nrf52/PmuBQ25703A.cpp @@ -0,0 +1,97 @@ +#include "PmuBQ25703A.h" +#include + +// Default address for device. Note, it is without read/write bit. When read with analyser, +// this will appear 1 bit shifted to the left +#define BQ25703ADevaddr 0xD6 + +const byte Lorro_BQ25703A::BQ25703Aaddr = BQ25703ADevaddr; + +void PmuBQ25703A::init() +{ + // Set the watchdog timer to not have a timeout + regs.chargeOption0.set_WDTMR_ADJ(0); + assert(writeRegEx(regs.chargeOption0)); // FIXME, instead log a critical hw failure and reboot + delay(15); // FIXME, why are these delays required? - check datasheet + + // Set the ADC on IBAT and PSYS to record values + // When changing bitfield values, call the writeRegEx function + // This is so you can change all the bits you want before sending out the byte. + regs.chargeOption1.set_EN_IBAT(1); + regs.chargeOption1.set_EN_PSYS(1); + assert(writeRegEx(regs.chargeOption1)); + delay(15); + + // Set ADC to make continuous readings. (uses more power) + regs.aDCOption.set_ADC_CONV(1); + // Set individual ADC registers to read. All have default off. + regs.aDCOption.set_EN_ADC_VBUS(1); + regs.aDCOption.set_EN_ADC_PSYS(1); + regs.aDCOption.set_EN_ADC_IDCHG(1); + regs.aDCOption.set_EN_ADC_ICHG(1); + regs.aDCOption.set_EN_ADC_VSYS(1); + regs.aDCOption.set_EN_ADC_VBAT(1); + // Once bits have been twiddled, send bytes to device + assert(writeRegEx(regs.aDCOption)); + delay(15); +} + +/* + + +//Initialise the device and library +Lorro_BQ25703A BQ25703A; + +//Instantiate with reference to global set +extern Lorro_BQ25703A::Regt BQ25703Areg; + +uint32_t previousMillis; +uint16_t loopInterval = 1000; + +void setup() { + + Serial.begin(115200); + + +} + +void loop() { + + uint32_t currentMillis = millis(); + + if( currentMillis - previousMillis > loopInterval ){ + previousMillis = currentMillis; + + Serial.print( "Voltage of VBUS: " ); + Serial.print( BQ25703Areg.aDCVBUSPSYS.get_VBUS() ); + Serial.println( "mV" ); + delay( 15 ); + + Serial.print( "System power usage: " ); + Serial.print( BQ25703Areg.aDCVBUSPSYS.get_sysPower() ); + Serial.println( "W" ); + delay( 15 ); + + Serial.print( "Voltage of VBAT: " ); + Serial.print( BQ25703Areg.aDCVSYSVBAT.get_VBAT() ); + Serial.println( "mV" ); + delay( 15 ); + + Serial.print( "Voltage of VSYS: " ); + Serial.print( BQ25703Areg.aDCVSYSVBAT.get_VSYS() ); + Serial.println( "mV" ); + delay( 15 ); + + Serial.print( "Charging current: " ); + Serial.print( BQ25703Areg.aDCIBAT.get_ICHG() ); + Serial.println( "mA" ); + delay( 15 ); + + Serial.print( "Voltage of VSYS: " ); + Serial.print( BQ25703Areg.aDCIBAT.get_IDCHG() ); + Serial.println( "mA" ); + delay( 15 ); + + } + +}*/ \ No newline at end of file diff --git a/src/nrf52/PmuBQ25703A.h b/src/nrf52/PmuBQ25703A.h new file mode 100644 index 000000000..804b85bf9 --- /dev/null +++ b/src/nrf52/PmuBQ25703A.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +class PmuBQ25703A : private Lorro_BQ25703A +{ + Lorro_BQ25703A::Regt regs; + + public: + /** + * Configure the PMU for our board + */ + void init(); + + // Methods to have a common API with AXP192 + bool isBatteryConnect() { return true; } // FIXME + bool isVBUSPlug() { return true; } + bool isChargeing() { return true; } // FIXME, intentional misspelling + + /// battery voltage in mV + int getBattVoltage() { return 3200; } +}; diff --git a/src/nrf52/SPIFFS.h b/src/nrf52/SPIFFS.h new file mode 100644 index 000000000..819693660 --- /dev/null +++ b/src/nrf52/SPIFFS.h @@ -0,0 +1,3 @@ +#pragma once + +// FIXME - make a FS abstraction for NRF52 \ No newline at end of file diff --git a/src/nrf52/main-bare.cpp b/src/nrf52/main-bare.cpp new file mode 100644 index 000000000..fb1b4a27e --- /dev/null +++ b/src/nrf52/main-bare.cpp @@ -0,0 +1,2 @@ +#include "target_specific.h" + diff --git a/src/nrf52/main-nrf52.cpp b/src/nrf52/main-nrf52.cpp new file mode 100644 index 000000000..0fe2c8d96 --- /dev/null +++ b/src/nrf52/main-nrf52.cpp @@ -0,0 +1,71 @@ +#include "NRF52Bluetooth.h" +#include "configuration.h" +#include +#include +#include +#include + +// #define USE_SOFTDEVICE + +static inline void debugger_break(void) +{ + __asm volatile("bkpt #0x01\n\t" + "mov pc, lr\n\t"); +} + +// handle standard gcc assert failures +void __attribute__((noreturn)) __assert_func(const char *file, int line, const char *func, const char *failedexpr) +{ + DEBUG_MSG("assert failed %s: %d, %s, test=%s\n", file, line, func, failedexpr); + // debugger_break(); FIXME doesn't work, possibly not for segger + while (1) + ; // FIXME, reboot! +} + +void getMacAddr(uint8_t *dmac) +{ + ble_gap_addr_t addr; + +#ifdef USE_SOFTDEVICE + uint32_t res = sd_ble_gap_addr_get(&addr); + assert(res == NRF_SUCCESS); + memcpy(dmac, addr.addr, 6); +#else + const uint8_t *src = (const uint8_t *)NRF_FICR->DEVICEADDR; + dmac[5] = src[0]; + dmac[4] = src[1]; + dmac[3] = src[2]; + dmac[2] = src[3]; + dmac[1] = src[4]; + dmac[0] = src[5] | 0xc0; // MSB high two bits get set elsewhere in the bluetooth stack +#endif +} + +NRF52Bluetooth *nrf52Bluetooth; + +static bool bleOn = false; +void setBluetoothEnable(bool on) +{ + if (on != bleOn) { + if (on) { + if (!nrf52Bluetooth) { + nrf52Bluetooth = new NRF52Bluetooth(); + nrf52Bluetooth->setup(); + } + } else { + DEBUG_MSG("FIXME: implement BLE disable\n"); + } + bleOn = on; + } +} + +#include "PmuBQ25703A.h" + +PmuBQ25703A pmu; + +void nrf52Setup() +{ + // Not yet on board + // pmu.init(); + DEBUG_MSG("FIXME, need to call randomSeed on nrf52!\n"); +} \ No newline at end of file diff --git a/src/power.h b/src/power.h index 9172592ce..6b190cbf8 100644 --- a/src/power.h +++ b/src/power.h @@ -16,3 +16,5 @@ struct PowerStatus { }; } // namespace meshtastic + +extern meshtastic::PowerStatus powerStatus; diff --git a/src/screen.cpp b/src/screen.cpp index b552e1b17..dd2f99c07 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -21,7 +21,6 @@ along with this program. If not, see . */ #include -#include #include "GPS.h" #include "NodeDB.h" @@ -55,7 +54,6 @@ static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int1 // draw an xbm image. // Please note that everything that should be transitioned // needs to be drawn relative to x and y - display->drawXbm(x + 32, y, icon_width, icon_height, (const uint8_t *)icon_bits); display->setFont(ArialMT_Plain_16); @@ -83,7 +81,7 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state MeshPacket &mp = devicestate.rx_text_message; NodeInfo *node = nodeDB.getNode(mp.from); // DEBUG_MSG("drawing text message from 0x%x: %s\n", mp.from, - // mp.payload.variant.data.payload.bytes); + // mp.decoded.variant.data.decoded.bytes); // Demo for drawStringMaxWidth: // with the third parameter you can define the width after which words will @@ -96,7 +94,8 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state // the max length of this buffer is much longer than we can possibly print static char tempBuf[96]; - snprintf(tempBuf, sizeof(tempBuf), " %s", mp.payload.variant.data.payload.bytes); + assert(mp.decoded.which_payload == SubPacket_data_tag); + snprintf(tempBuf, sizeof(tempBuf), " %s", mp.decoded.data.payload.bytes); display->drawStringMaxWidth(4 + x, 10 + y, 128, tempBuf); } @@ -281,7 +280,7 @@ static float estimatedHeading(double lat, double lon) /// valid lat/lon static bool hasPosition(NodeInfo *n) { - return n->has_position && (n->position.latitude != 0 || n->position.longitude != 0); + return n->has_position && (n->position.latitude_i != 0 || n->position.longitude_i != 0); } /// We will skip one node - the one for us, so we just blindly loop over all @@ -289,6 +288,9 @@ static bool hasPosition(NodeInfo *n) static size_t nodeIndex; static int8_t prevFrame = -1; +/// Convert an integer GPS coords to a floating point +#define DegD(i) (i * 1e-7) + static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { // We only advance our nodeIndex if the frame # has changed - because @@ -314,16 +316,16 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ const char *username = node->has_user ? node->user.long_name : "Unknown Name"; static char signalStr[20]; - snprintf(signalStr, sizeof(signalStr), "Signal: %d", node->snr); + snprintf(signalStr, sizeof(signalStr), "Signal: %.0f", node->snr); uint32_t agoSecs = sinceLastSeen(node); static char lastStr[20]; if (agoSecs < 120) // last 2 mins? - snprintf(lastStr, sizeof(lastStr), "%d seconds ago", agoSecs); + snprintf(lastStr, sizeof(lastStr), "%u seconds ago", agoSecs); else if (agoSecs < 120 * 60) // last 2 hrs - snprintf(lastStr, sizeof(lastStr), "%d minutes ago", agoSecs / 60); + snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / 60); else - snprintf(lastStr, sizeof(lastStr), "%d hours ago", agoSecs / 60 / 60); + snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60); static float simRadian; simRadian += 0.1; // For testing, have the compass spin unless both @@ -335,7 +337,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ NodeInfo *ourNode = nodeDB.getNode(nodeDB.getNodeNum()); if (ourNode && hasPosition(ourNode) && hasPosition(node)) { Position &op = ourNode->position, &p = node->position; - float d = latLongToMeter(p.latitude, p.longitude, op.latitude, op.longitude); + float d = 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 @@ -343,8 +345,8 @@ 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(p.latitude, p.longitude, op.latitude, op.longitude); - float myHeading = estimatedHeading(p.latitude, p.longitude); + float bearingToOther = bearing(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); + float myHeading = estimatedHeading(DegD(p.latitude_i), DegD(p.longitude_i)); headingRadian = bearingToOther - myHeading; } else { // Debug info for gps lock errors @@ -383,7 +385,6 @@ void _screen_header() if (!disp) return; - // Message count //snprintf(buffer, sizeof(buffer), "#%03d", ttn_get_count() % 1000); //display->setTextAlignment(TEXT_ALIGN_LEFT); @@ -401,7 +402,7 @@ void _screen_header() } #endif -Screen::Screen(uint8_t address, uint8_t sda, uint8_t scl) : cmdQueue(32), dispdev(address, sda, scl), ui(&dispdev) {} +Screen::Screen(uint8_t address, int sda, int scl) : cmdQueue(32), dispdev(address, sda, scl), ui(&dispdev) {} void Screen::handleSetOn(bool on) { @@ -412,6 +413,7 @@ void Screen::handleSetOn(bool on) if (on) { DEBUG_MSG("Turning on screen\n"); dispdev.displayOn(); + dispdev.displayOn(); } else { DEBUG_MSG("Turning off screen\n"); dispdev.displayOff(); @@ -422,6 +424,8 @@ void Screen::handleSetOn(bool on) void Screen::setup() { + PeriodicTask::setup(); + // We don't set useDisplay until setup() is called, because some boards have a declaration of this object but the device // is never found when probing i2c and therefore we don't call setup and never want to do (invalid) accesses to this device. useDisplay = true; @@ -592,7 +596,7 @@ void Screen::handleStartBluetoothPinScreen(uint32_t pin) static FrameCallback btFrames[] = {drawFrameBluetooth}; - snprintf(btPIN, sizeof(btPIN), "%06d", pin); + snprintf(btPIN, sizeof(btPIN), "%06u", pin); ui.disableAllIndicators(); ui.setFrames(btFrames, 1); diff --git a/src/screen.h b/src/screen.h index 6d088b1f9..be5444c1e 100644 --- a/src/screen.h +++ b/src/screen.h @@ -9,6 +9,7 @@ #include "TypedQueue.h" #include "lock.h" #include "power.h" +#include namespace meshtastic { @@ -88,7 +89,7 @@ class DebugInfo class Screen : public PeriodicTask { public: - Screen(uint8_t address, uint8_t sda, uint8_t scl); + Screen(uint8_t address, int sda = -1, int scl = -1); Screen(const Screen &) = delete; Screen &operator=(const Screen &) = delete; @@ -99,7 +100,14 @@ class Screen : public PeriodicTask void setup(); /// Turns the screen on/off. - void setOn(bool on) { enqueueCmd(CmdItem{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF}); } + void setOn(bool on) + { + if (!on) + handleSetOn( + false); // We handle off commands immediately, because they might be called because the CPU is shutting down + else + enqueueCmd(CmdItem{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF}); + } /// Handles a button press. void onPress() { enqueueCmd(CmdItem{.cmd = Cmd::ON_PRESS}); } diff --git a/src/sleep.cpp b/src/sleep.cpp index e204ea5f5..4b8db06b0 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -1,30 +1,39 @@ #include "sleep.h" -#include "BluetoothUtil.h" #include "GPS.h" -#include "MeshBluetoothService.h" #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" -#include "Periodic.h" #include "configuration.h" +#include "error.h" + +#include "main.h" +#include "target_specific.h" + +#ifndef NO_ESP32 #include "esp32/pm.h" #include "esp_pm.h" -#include "main.h" #include "rom/rtc.h" -#include #include +#include "BluetoothUtil.h" + +esp_sleep_source_t wakeCause; // the reason we booted this time +#endif + #ifdef TBEAM_V10 #include "axp20x.h" extern AXP20X_Class axp; #endif +/// Called to ask any observers if they want to veto sleep. Return 1 to veto or 0 to allow sleep to happen +Observable preflightSleep; + +/// Called to tell observers we are now entering sleep and you should prepare. Must return 0 +/// notifySleep will be called for light or deep sleep, notifyDeepSleep is only called for deep sleep +Observable notifySleep, notifyDeepSleep; + // deep sleep support RTC_DATA_ATTR int bootCount = 0; -esp_sleep_source_t wakeCause; // the reason we booted this time - -#define xstr(s) str(s) -#define str(s) #s // ----------------------------------------------------------------------------- // Application @@ -38,14 +47,16 @@ esp_sleep_source_t wakeCause; // the reason we booted this time */ void setCPUFast(bool on) { +#ifndef NO_ESP32 setCpuFrequencyMhz(on ? 240 : 80); +#endif } void setLed(bool ledOn) { #ifdef LED_PIN // toggle the led so we can get some rough sense of how often loop is pausing - digitalWrite(LED_PIN, ledOn); + digitalWrite(LED_PIN, ledOn ^ LED_INVERTED); #endif #ifdef TBEAM_V10 @@ -69,6 +80,7 @@ void setGPSPower(bool on) // Perform power on init that we do on each wake from deep sleep void initDeepSleep() { +#ifndef NO_ESP32 bootCount++; wakeCause = esp_sleep_get_wakeup_cause(); /* @@ -96,22 +108,51 @@ void initDeepSleep() reason = "timeout"; DEBUG_MSG("booted, wake cause %d (boot count %d), reset_reason=%s\n", wakeCause, bootCount, reason); +#endif +} + +/// return true if sleep is allowed +static bool doPreflightSleep() +{ + if (preflightSleep.notifyObservers(NULL) != 0) + return false; // vetoed + else + return true; +} + +/// Tell devices we are going to sleep and wait for them to handle things +static void waitEnterSleep() +{ + uint32_t now = millis(); + while (!doPreflightSleep()) { + delay(100); // Kinda yucky - wait until radio says say we can shutdown (finished in process sends/receives) + + if (millis() - now > 30 * 1000) { // If we wait too long just report an error and go to sleep + recordCriticalError(ErrSleepEnterWait); + break; + } + } + + // Code that still needs to be moved into notifyObservers + Serial.flush(); // send all our characters before we stop cpu clock + setBluetoothEnable(false); // has to be off before calling light sleep + + notifySleep.notifyObservers(NULL); } void doDeepSleep(uint64_t msecToWake) { DEBUG_MSG("Entering deep sleep for %llu seconds\n", msecToWake / 1000); +#ifndef NO_ESP32 // not using wifi yet, but once we are this is needed to shutoff the radio hw // esp_wifi_stop(); - - BLEDevice::deinit(false); // We are required to shutdown bluetooth before deep or light sleep + waitEnterSleep(); + notifySleep.notifyObservers(NULL); // also tell the regular sleep handlers + notifyDeepSleep.notifyObservers(NULL); screen.setOn(false); // datasheet says this will draw only 10ua - // Put radio in sleep mode (will still draw power but only 0.2uA) - service.radio.radioIf.sleep(); - nodeDB.saveToDisk(); #ifdef RESET_OLED @@ -189,8 +230,10 @@ void doDeepSleep(uint64_t msecToWake) esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs esp_deep_sleep_start(); // TBD mA sleep current (battery) +#endif } +#ifndef NO_ESP32 /** * enter light sleep (preserves ram but stops everything about CPU). * @@ -199,10 +242,10 @@ void doDeepSleep(uint64_t msecToWake) esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more reasonable default { // DEBUG_MSG("Enter light sleep\n"); - uint64_t sleepUsec = sleepMsec * 1000LL; - Serial.flush(); // send all our characters before we stop cpu clock - setBluetoothEnable(false); // has to be off before calling light sleep + waitEnterSleep(); + + uint64_t sleepUsec = sleepMsec * 1000LL; // NOTE! ESP docs say we must disable bluetooth and wifi before light sleep @@ -227,10 +270,14 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r assert(esp_sleep_enable_gpio_wakeup() == ESP_OK); assert(esp_sleep_enable_timer_wakeup(sleepUsec) == ESP_OK); assert(esp_light_sleep_start() == ESP_OK); - // DEBUG_MSG("Exit light sleep b=%d, rf95=%d, pmu=%d\n", digitalRead(BUTTON_PIN), digitalRead(RF95_IRQ_GPIO), - // digitalRead(PMU_IRQ)); - return esp_sleep_get_wakeup_cause(); + + esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); + if (cause == ESP_SLEEP_WAKEUP_GPIO) + DEBUG_MSG("Exit light sleep gpio: btn=%d, rf95=%d\n", !digitalRead(BUTTON_PIN), digitalRead(RF95_IRQ_GPIO)); + + return cause; } +#endif #if 0 // not legal on the stock android ESP build diff --git a/src/sleep.h b/src/sleep.h index 49976516b..b3446882a 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -1,11 +1,16 @@ #pragma once #include "Arduino.h" -#include "esp_sleep.h" +#include "Observer.h" +#include "configuration.h" void doDeepSleep(uint64_t msecToWake); +#ifndef NO_ESP32 +#include "esp_sleep.h" esp_sleep_wakeup_cause_t doLightSleep(uint64_t msecToWake); -void setBluetoothEnable(bool on); + +extern esp_sleep_source_t wakeCause; +#endif void setGPSPower(bool on); // Perform power on init that we do on each wake from deep sleep @@ -15,7 +20,15 @@ void setCPUFast(bool on); void setLed(bool ledOn); extern int bootCount; -extern esp_sleep_source_t wakeCause; // is bluetooth sw currently running? -extern bool bluetoothOn; \ No newline at end of file +extern bool bluetoothOn; + +/// Called to ask any observers if they want to veto sleep. Return 1 to veto or 0 to allow sleep to happen +extern Observable preflightSleep; + +/// Called to tell observers we are now entering (light or deep) sleep and you should prepare. Must return 0 +extern Observable notifySleep; + +/// Called to tell observers we are now entering (deep) sleep and you should prepare. Must return 0 +extern Observable notifyDeepSleep; \ No newline at end of file diff --git a/src/target_specific.h b/src/target_specific.h new file mode 100644 index 000000000..6acca364a --- /dev/null +++ b/src/target_specific.h @@ -0,0 +1,6 @@ +#pragma once + +// Functions that are unique to particular target types (esp32, bare, nrf52 etc...) + +// Enable/disable bluetooth. +void setBluetoothEnable(bool on); \ No newline at end of file diff --git a/variants/pca10056-rc-clock/variant.cpp b/variants/pca10056-rc-clock/variant.cpp new file mode 100644 index 000000000..bd85e9713 --- /dev/null +++ b/variants/pca10056-rc-clock/variant.cpp @@ -0,0 +1,49 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" +#include "nrf.h" + +const uint32_t g_ADigitalPinMap[] = +{ + // P0 + 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , + 8 , 9 , 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47 +}; + + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2);; +} + diff --git a/variants/pca10056-rc-clock/variant.h b/variants/pca10056-rc-clock/variant.h new file mode 100644 index 000000000..b7201812a --- /dev/null +++ b/variants/pca10056-rc-clock/variant.h @@ -0,0 +1,157 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_PCA10056_ +#define _VARIANT_PCA10056_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +// This file is the same as the standard pac10056 variant, except that @geeksville broke the xtal on his devboard so +// he has to use a RC clock. + +// #define USE_LFXO // Board uses 32khz crystal for LF +#define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (13) +#define PIN_LED2 (14) + +#define LED_BUILTIN PIN_LED1 +#define LED_CONN PIN_LED2 + +#define LED_RED PIN_LED1 +#define LED_BLUE PIN_LED2 + +#define LED_STATE_ON 0 // State when LED is litted + +/* + * Buttons + */ +#define PIN_BUTTON1 11 +#define PIN_BUTTON2 12 +#define PIN_BUTTON3 24 +#define PIN_BUTTON4 25 + +/* + * Analog pins + */ +#define PIN_A0 (3) +#define PIN_A1 (4) +#define PIN_A2 (28) +#define PIN_A3 (29) +#define PIN_A4 (30) +#define PIN_A5 (31) +#define PIN_A6 (0xff) +#define PIN_A7 (0xff) + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; +static const uint8_t A6 = PIN_A6; +static const uint8_t A7 = PIN_A7; +#define ADC_RESOLUTION 14 + +// Other pins +#define PIN_AREF (2) +#define PIN_NFC1 (9) +#define PIN_NFC2 (10) + +static const uint8_t AREF = PIN_AREF; + +/* + * Serial interfaces + */ + +// Arduino Header D0, D1 +#define PIN_SERIAL1_RX (33) // P1.01 +#define PIN_SERIAL1_TX (34) // P1.02 + +// Connected to Jlink CDC +#define PIN_SERIAL2_RX (8) +#define PIN_SERIAL2_TX (6) + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (46) +#define PIN_SPI_MOSI (45) +#define PIN_SPI_SCK (47) + +static const uint8_t SS = 44; +static const uint8_t MOSI = PIN_SPI_MOSI; +static const uint8_t MISO = PIN_SPI_MISO; +static const uint8_t SCK = PIN_SPI_SCK; + +/* + * Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (26) +#define PIN_WIRE_SCL (27) + +// QSPI Pins +#define PIN_QSPI_SCK 19 +#define PIN_QSPI_CS 17 +#define PIN_QSPI_IO0 20 +#define PIN_QSPI_IO1 21 +#define PIN_QSPI_IO2 22 +#define PIN_QSPI_IO3 23 + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES MX25R6435F +#define EXTERNAL_FLASH_USE_QSPI + +// CUSTOM GPIOs the SX1262MB2CAS shield when installed on the NRF52840-DK development board +#define SX1262_CS (32 + 8) // P1.08 +#define SX1262_DIO1 (32 + 6) // P1.06 +#define SX1262_BUSY (32 + 4) // P1.04 +#define SX1262_RESET (0 + 3) // P0.03 +#define SX1262_ANT_SW (32 + 10) // P1.10 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif diff --git a/variants/ppr/variant.cpp b/variants/ppr/variant.cpp new file mode 100644 index 000000000..f5f219e9b --- /dev/null +++ b/variants/ppr/variant.cpp @@ -0,0 +1,42 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0xff, 12, 13, 0xff, 15, 0xff, 17, 18, 0xff, 20, 0xff, 22, 0xff, 24, 0xff, 26, 0xff, 28, 29, + 30, 31, + + // P1 + 32, 0xff, 34, 0xff, 36, 0xff, 38, 0xff, 0xff, 41, 42, 43, 0xff, 45}; + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); +} diff --git a/variants/ppr/variant.h b/variants/ppr/variant.h new file mode 100644 index 000000000..d59e17503 --- /dev/null +++ b/variants/ppr/variant.h @@ -0,0 +1,156 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#pragma once + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +// This board does not have a 32khz crystal +// #define USE_LFXO // Board uses 32khz crystal for LF +#define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (46) +#define NUM_DIGITAL_PINS (46) +#define NUM_ANALOG_INPUTS (0) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (0) +#define PIN_LED2 (1) + +#define LED_BUILTIN PIN_LED1 +#define LED_CONN PIN_LED2 + +#define LED_RED PIN_LED1 +#define LED_GREEN PIN_LED2 + +// FIXME, bluefruit automatically blinks this led while connected. call AdafruitBluefruit::autoConnLed to change this. +#define LED_BLUE LED_GREEN + +#define LED_STATE_ON 1 // State when LED is litted + +/* + * Buttons + */ +#define PIN_BUTTON1 4 // center +#define PIN_BUTTON2 2 +#define PIN_BUTTON3 3 +#define PIN_BUTTON4 5 +#define PIN_BUTTON5 6 + +/* + * Analog pins + */ +#define PIN_A0 (0xff) +#define PIN_A1 (0xff) +#define PIN_A2 (0xff) +#define PIN_A3 (0xff) +#define PIN_A4 (0xff) +#define PIN_A5 (0xff) +#define PIN_A6 (0xff) +#define PIN_A7 (0xff) + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; +static const uint8_t A6 = PIN_A6; +static const uint8_t A7 = PIN_A7; +#define ADC_RESOLUTION 14 + +// Other pins +#define PIN_AREF (0xff) +//#define PIN_NFC1 (9) +//#define PIN_NFC2 (10) + +static const uint8_t AREF = PIN_AREF; + +/* + * Serial interfaces + */ + +// GPS is on Serial1 +#define PIN_SERIAL1_RX (8) +#define PIN_SERIAL1_TX (9) + +// Connected to Jlink CDC +//#define PIN_SERIAL2_RX (8) +//#define PIN_SERIAL2_TX (6) + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (15) +#define PIN_SPI_MOSI (13) +#define PIN_SPI_SCK (12) + +// static const uint8_t SS = 44; +static const uint8_t MOSI = PIN_SPI_MOSI; +static const uint8_t MISO = PIN_SPI_MISO; +static const uint8_t SCK = PIN_SPI_SCK; + +/* + * Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (32 + 2) +#define PIN_WIRE_SCL (32) + +// CUSTOM GPIOs the SX1262 +#define SX1262_CS (10) +#define SX1262_DIO1 (20) +#define SX1262_DIO2 (26) +#define SX1262_BUSY (18) +#define SX1262_RESET (17) +// #define SX1262_ANT_SW (32 + 10) +#define SX1262_RXEN (22) +#define SX1262_TXEN (24) + +// ERC12864-10 LCD +#define ERC12864_CS (32 + 4) +#define ERC12864_RESET (32 + 6) +#define ERC12864_CD (32 + 9) + +// L80 GPS +#define L80_PPS (28) +#define L80_RESET (29) + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/