diff --git a/.gitignore b/.gitignore index 7ca9335a..db044b5a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,8 @@ out/ .DS_Store .vscode/settings.json .vscode/extensions.json +.idea +cmake-* +.cache +.ccls +compile_commands.json diff --git a/README.md b/README.md index 708db2c4..fa5b3cd0 100644 --- a/README.md +++ b/README.md @@ -74,17 +74,7 @@ They can also be managed via LoRa in the mobile app by using the Remote Manageme ## 🛠 Hardware Compatibility -MeshCore is designed for use with: -* Heltec V3 LoRa Boards -* RAK4631 -* XiaoS3 WIO (sx1262 combo) -* XiaoC3 (plus external sx126x module) -* LilyGo T3S3 -* Heltec T114 -* Station G2 -* Sensecap T1000e -* Heltec V2 -* LilyGo TLora32 v1.6 +MeshCore is designed for devices listed in the [MeshCore Flasher](https://flasher.meshcore.co.uk) ## 📜 License @@ -104,7 +94,7 @@ Here are some general principals you should try to adhere to: - Report bugs and request features on the [GitHub Issues](https://github.com/ripplebiz/MeshCore/issues) page. - Find additional guides and components on [my site](https://buymeacoffee.com/ripplebiz). -- Join [Andy Kirby's Discord](https://discord.gg/GBxVx2JMAy) to chat with the developers and get help from the community. +- Join [MeshCore Discord](https://discord.gg/BMwCtwHj5V) to chat with the developers and get help from the community. ## RAK Wireless Board Support in PlatformIO diff --git a/boards/heltec_mesh_solar.json b/boards/heltec_mesh_solar.json new file mode 100644 index 00000000..c9125811 --- /dev/null +++ b/boards/heltec_mesh_solar.json @@ -0,0 +1,61 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A","0x8029"], + ["0x239A","0x0029"], + ["0x239A","0x002A"], + ["0x239A","0x802A"] + ], + "usb_product": "HT-n5262", + "mcu": "nrf52840", + "variant": "heltec_mesh_solar", + "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", + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52.cfg" + }, + "frameworks": [ + "arduino" + ], + "name": "Heltec Mesh Solar Board", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink" + ], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://heltec.org/", + "vendor": "Heltec" +} \ No newline at end of file diff --git a/boards/heltec_vision_master_e213.json b/boards/heltec_vision_master_e213.json new file mode 100644 index 00000000..81efd8f5 --- /dev/null +++ b/boards/heltec_vision_master_e213.json @@ -0,0 +1,44 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_16MB.csv", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "psram_type": "opi", + "hwids": [ + ["0x303A", "0x1001"], + ["0x303A", "0x0002"] + ], + "mcu": "esp32s3", + "variant": "heltec_vision_master_e213" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "Heltec Vision Master E213", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 8388608, + "maximum_size": 16777216, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://heltec.org/project/vision-master-e213/", + "vendor": "Heltec" +} diff --git a/boards/heltec_vision_master_e290.json b/boards/heltec_vision_master_e290.json new file mode 100644 index 00000000..07577557 --- /dev/null +++ b/boards/heltec_vision_master_e290.json @@ -0,0 +1,44 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_16MB.csv", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "psram_type": "opi", + "hwids": [ + ["0x303A", "0x1001"], + ["0x303A", "0x0002"] + ], + "mcu": "esp32s3", + "variant": "heltec_vision_master_e290" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "Heltec Vision Master E290", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 8388608, + "maximum_size": 16777216, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://heltec.org/project/vision-master-e290/", + "vendor": "Heltec" +} diff --git a/boards/seeed-wio-tracker-l1.json b/boards/seeed-wio-tracker-l1.json index 3602baab..6235b8bf 100644 --- a/boards/seeed-wio-tracker-l1.json +++ b/boards/seeed-wio-tracker-l1.json @@ -40,8 +40,8 @@ ], "name": "Seeed Wio Tracker L1", "upload": { - "maximum_ram_size": 248832, - "maximum_size": 815104, + "maximum_ram_size": 237568, + "maximum_size": 811008, "protocol": "nrfutil", "speed": 115200, "protocols": [ diff --git a/build.sh b/build.sh index 095a1633..47fec4a3 100755 --- a/build.sh +++ b/build.sh @@ -3,6 +3,7 @@ # usage # sh build.sh build-firmware RAK_4631_Repeater # sh build.sh build-firmwares +# sh build.sh build-matching-firmwares RAK_4631 # sh build.sh build-companion-firmwares # sh build.sh build-repeater-firmwares # sh build.sh build-room-server-firmwares @@ -144,6 +145,16 @@ mkdir -p out if [[ $1 == "build-firmware" ]]; then if [ "$2" ]; then build_firmware $2 + else + echo "usage: $0 build-firmware " + exit 1 + fi +elif [[ $1 == "build-matching-firmwares" ]]; then + if [ "$2" ]; then + build_all_firmwares_matching $2 + else + echo "usage: $0 build-matching-firmwares " + exit 1 fi elif [[ $1 == "build-firmwares" ]]; then build_firmwares diff --git a/docs/faq.md b/docs/faq.md index 1c67c9c7..6dc8fe9e 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -31,13 +31,14 @@ author: https://github.com/LitBomb - [4.2. Q: Why is my T-Deck Plus not getting any satellite lock?](#42-q-why-is-my-t-deck-plus-not-getting-any-satellite-lock) - [4.3. Q: Why is my OG (non-Plus) T-Deck not getting any satellite lock?](#43-q-why-is-my-og-non-plus-t-deck-not-getting-any-satellite-lock) - [4.4. Q: What size of SD card does the T-Deck support?](#44-q-what-size-of-sd-card-does-the-t-deck-support) - - [4.5. Q: How do I get maps on T-Deck?](#45-q-how-do-i-get-maps-on-t-deck) - - [4.6. Q: Where do the map tiles go?](#46-q-where-do-the-map-tiles-go) - - [4.7. Q: How to unlock deeper map zoom and server management features on T-Deck?](#47-q-how-to-unlock-deeper-map-zoom-and-server-management-features-on-t-deck) - - [4.8. Q: How to decipher the diagnostics screen on T-Deck?](#48-q-how-to-decipher-the-diagnostics-screen-on-t-deck) - - [4.9. Q: The T-Deck sound is too loud?](#49-q-the-t-deck-sound-is-too-loud) - - [4.10. Q: Can you customize the sound?](#410-q-can-you-customize-the-sound) - - [4.11. Q: What is the 'Import from Clipboard' feature on the t-deck and is there a way to manually add nodes without having to receive adverts?](#411-q-what-is-the-import-from-clipboard-feature-on-the-t-deck-and-is-there-a-way-to-manually-add-nodes-without-having-to-receive-adverts) + - [4.5. Q: What is the public key for the default public channel?](#45-q-what-is-the-public-key-for-the-default-public-channel) + - [4.6. Q: How do I get maps on T-Deck?](#46-q-how-do-i-get-maps-on-t-deck) + - [4.7. Q: Where do the map tiles go?](#47-q-where-do-the-map-tiles-go) + - [4.8. Q: How to unlock deeper map zoom and server management features on T-Deck?](#48-q-how-to-unlock-deeper-map-zoom-and-server-management-features-on-t-deck) + - [4.9. Q: How to decipher the diagnostics screen on T-Deck?](#49-q-how-to-decipher-the-diagnostics-screen-on-t-deck) + - [4.10. Q: The T-Deck sound is too loud?](#410-q-the-t-deck-sound-is-too-loud) + - [4.11. Q: Can you customize the sound?](#411-q-can-you-customize-the-sound) + - [4.12. Q: What is the 'Import from Clipboard' feature on the t-deck and is there a way to manually add nodes without having to receive adverts?](#412-q-what-is-the-import-from-clipboard-feature-on-the-t-deck-and-is-there-a-way-to-manually-add-nodes-without-having-to-receive-adverts) - [5. General](#5-general) - [5.1. Q: What are BW, SF, and CR?](#51-q-what-are-bw-sf-and-cr) - [5.2. Q: Do MeshCore clients repeat?](#52-q-do-meshcore-clients-repeat) @@ -65,18 +66,20 @@ author: https://github.com/LitBomb - [6.4. Q: I can't connect via Bluetooth, what is the Bluetooth pairing code?](#64-q-i-cant-connect-via-bluetooth-what-is-the-bluetooth-pairing-code) - [6.5. Q: My Heltec V3 keeps disconnecting from my smartphone. It can't hold a solid Bluetooth connection.](#65-q-my-heltec-v3-keeps-disconnecting-from-my-smartphone--it-cant-hold-a-solid-bluetooth-connection) - [6.6. Q: My RAK/T1000-E/xiao\_nRF52 device seems to be corrupted, how do I wipe it clean to start fresh?](#66-q-my-rakt1000-exiao_nrf52-device-seems-to-be-corrupted-how-do-i-wipe-it-clean-to-start-fresh) - - [6.7. Q: WebFlasher fails on Linux with failed to open](#67-q-webflasher-fails-on-Linux-with-failed-to-open) - + - [6.7. Q: WebFlasher fails on Linux with failed to open](#67-q-webflasher-fails-on-linux-with-failed-to-open) - [7. Other Questions:](#7-other-questions) - [7.1 Q: How to update nRF (RAK, T114, Seed XIAO) repeater and room server firmware over the air using the new simpler DFU app?](#71-q-how-to-update-nrf-rak-t114-seed-xiao-repeater-and-room-server-firmware-over-the-air-using-the-new-simpler-dfu-app) - [7.2 Q: How to update ESP32-based devices over the air?](#72-q-how-to-update-esp32-based-devices-over-the-air) - [7.3 Q: Is there a way to lower the chance of a failed OTA device firmware update (DFU)?](#73-q-is-there-a-way-to-lower-the-chance-of-a-failed-ota-device-firmware-update-dfu) + - [7.4 Q are the MeshCore logo and font available?](#74-q-are-the-meshcore-logo-and-font-available) ## 1. Introduction ### 1.1. Q: What is MeshCore? -**A:** MeshCore is free and open source +**A:** MeshCore is a multi platform system for enabling secure text based communications utilising LoRa radio hardware. It can be used for Off-Grid Communication, Emergency Response & Disaster Recovery, Outdoor Activities, Tactical Security including law enforcement and private security and also IoT sensor networks. ([source](https://meshcore.co.uk/)) + +MeshCore is free and open source: * MeshCore is the routing and firmware etc, available on GitHub under MIT license * There are clients made by the community, such as the web clients, these are free to use, and some are open source too * The cross platform mobile app developed by [Liam Cottle](https://liamcottle.net) for Android/iOS/PC etc is free to download and use @@ -176,7 +179,7 @@ The T-Deck firmware is free to download and most features are available without In UK and EU, 867.5MHz is not allowed to use 250kHz bandwidth and it only allows 2.5% duty cycle for clients. 869.525Mhz allows an airtime of 10%, 250KHz bandwidth, and a higher EIRP, therefore MeshCore nodes can send more often and with more power. That is why this frequency is chosen for UK and EU. This is also why Meshtastic also uses this frequency. -[Source]([https://](https://discord.com/channels/826570251612323860/1330643963501351004/1356540643853209641)) +[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1356540643853209641) the rest of the radio settings are the same for all frequencies: - Spread Factor (SF): 11 @@ -268,7 +271,7 @@ You can get the latitude and longitude from Google Maps by right-clicking the lo GPS on T-Deck is always enabled. You can skip the "GPS clock sync" and the T-Deck will continue to try to get a GPS lock. You can go to the `GPS Info` screen; you should see the `Sentences:` counter increasing if the baud rate is correct. -[Source]([https://](https://discord.com/channels/826570251612323860/1330643963501351004/1356609240302616689)) +[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1356609240302616689) ### 4.3. Q: Why is my OG (non-Plus) T-Deck not getting any satellite lock? **A:** The OG (non-Plus) T-Deck doesn't come with a GPS. If you added a GPS to your OG T-Deck, please refer to the manual of your GPS to see what baud rate it requires. Alternatively, you can try to set the baud rate from 9600, 19200, etc., and up to 115200 to see which one works. @@ -276,7 +279,18 @@ GPS on T-Deck is always enabled. You can skip the "GPS clock sync" and the T-De ### 4.4. Q: What size of SD card does the T-Deck support? **A:** Users have had no issues using 16GB or 32GB SD cards. Format the SD card to **FAT32**. -### 4.5. Q: How do I get maps on T-Deck? +### 4.5. Q: what is the public key for the default public channel? +**A:** +T-Deck uses the same key the smartphone apps use but in base64 +`izOH6cXN6mrJ5e26oRXNcg==` +The third character is the capital letter 'O', not zero `0` + +The smartphone app key is in hex: +` 8b3387e9c5cdea6ac9e5edbaa115cd72` + +[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1354194409213792388) + +### 4.6. Q: How do I get maps on T-Deck? **A:** You need map tiles. You can get pre-downloaded map tiles here (a good way to support development): - (Europe) - (US) @@ -290,14 +304,14 @@ There is also a modified script that adds additional error handling and parallel UK map tiles are available separately from Andy Kirby on his discord server: -### 4.6. Q: Where do the map tiles go? +### 4.7. Q: Where do the map tiles go? Once you have the tiles downloaded, copy the `\tiles` folder to the root of your T-Deck's SD card. -### 4.7. Q: How to unlock deeper map zoom and server management features on T-Deck? +### 4.8. Q: How to unlock deeper map zoom and server management features on T-Deck? **A:** You can download, install, and use the T-Deck firmware for free, but it has some features (map zoom, server administration) that are enabled if you purchase an unlock code for \$10 per T-Deck device. Unlock page: -### 4.8. Q: How to decipher the diagnostics screen on T-Deck? +### 4.9. Q: How to decipher the diagnostics screen on T-Deck? **A: ** Space is tight on T-Deck's screen, so the information is a bit cryptic. The format is : `{hops} l:{packet-length}({payload-len}) t:{packet-type} snr:{n} rssi:{n}` @@ -316,12 +330,12 @@ See here for packet-type: [https://github.com/ripplebiz/MeshCore/blob/main/src/P [Source](https://discord.com/channels/1343693475589263471/1343693475589263474/1350611321040932966) -### 4.9. Q: The T-Deck sound is too loud? -### 4.10. Q: Can you customize the sound? +### 4.10. Q: The T-Deck sound is too loud? +### 4.11. Q: Can you customize the sound? **A:** You can customise the sounds on the T-Deck, just by placing `.mp3` files onto the `root` dir of the SD card. `startup.mp3`, `alert.mp3` and `new-advert.mp3` -### 4.11. Q: What is the 'Import from Clipboard' feature on the t-deck and is there a way to manually add nodes without having to receive adverts? +### 4.12. Q: What is the 'Import from Clipboard' feature on the t-deck and is there a way to manually add nodes without having to receive adverts? **A:** 'Import from Clipboard' is for importing a contact via a file named 'clipboard.txt' on the SD card. The opposite, is in the Identity screen, the 'Card to Clipboard' menu, which writes to 'clipboard.txt' so you can share yourself (call these 'biz cards', that start with "meshcore://...") @@ -382,7 +396,7 @@ The third character is the capital letter 'O', not zero `0` - Firmware repo: ### 5.8. Q: How can I support MeshCore? -**A:** Provide your honest feedback on GitHub and on AndyKirby's Discord server . Spread the word of MeshCore to your friends and communities; help them get started with MeshCore. Support Scott's MeshCore development at . +**A:** Provide your honest feedback on GitHub and on [MeshCore Discord server](https://discord.gg/BMwCtwHj5V). Spread the word of MeshCore to your friends and communities; help them get started with MeshCore. Support Scott's MeshCore development at . Support Liam Cottle's smartphone client development by unlocking the server administration wait gate with in-app purchase @@ -450,45 +464,65 @@ This could change in the future if MeshCore develops a client firmware that repe ### 5.13. Q: Can I use a Raspberry Pi to update a MeshCore radio? ** A:** Yes. -You will need to install picocom on the pi. -`sudo apt install picocom` +Below are the instructions to flash firmware onto a supported LoRa device using a Raspberry Pi over USB serial. -Then run the following commands to setup the repeater. -``` -picocom -b 115200 /dev/ttyUSB0 --imap lfcrlf -set name your_repeater_name -time epoch_time -password your_unique_password -set advert.interval 240 -advert -``` -Note: If using a RAK the path will most likely be /dev/ttyACM0 +> Instructions for nRF devices like RAK, T1000-E, T114 are immediately after the ESP instructions -Epoch time comes from https://www.epochconverter.com/ +For ESP-based devices (e.g. Heltec V3) you need: +- Download firmware file from flasher.meshcore.co.uk + - Go to the web site on a browser, find the section that has the firmware up need + - Click the Download button, right click on the file you need, for example, + - `Heltec_V3_companion_radio_ble-v1.7.1-165fb33.bin` + - Non-merged bin keeps the existing Bluetooth pairing database + - `Heltec_v3_companion_radio_usb-v1.7.1-165fb33-merged.bin` + - Merged bin overwrites everything including the bootloader, existing Bluetooth pairing database, but keeps configurations. + - Right click on the file name and copy the link and note it for later use here is an example: `https://flasher.meshcore.dev/releases/download/companion-v1.7.1/Heltec_v3_companion_radio_ble-v1.7.1-165fb33.bin` + - Run: + - `wget https://flasher.meshcore.dev/releases/download/companion-v1.7.1/Heltec_v3_companion_radio_ble-v1.7.1-165fb33.bin` to download the firmware file for your device type. or the version you need - USB, BLE, Repeater, Room Server, merged bin or non-merged bin + - If the above wget command only downloads a very small file (10K bytes instead of more than 100K byte, use this command instead: + - `wget --user-agent="Mozilla/5.0" --content-disposition "https://flasher.meshcore.dev/releases/download/companion-v1.7.1/Heltec_v3_companion_radio_usb-v1.7.1-165fb33.bin"` + - Confirm the `ttyXXXX` device path on your Raspberry Pi: + - Go to `/dev` directory, run ls command to find confirm your device path + - They are usually `/dev/ttyUSB0` for ESP devices + - For ESP-based devices, install esptool from the shell: + - `pip install esptool --break-system-packages` + - To flash, use the following command: + - For non-merged bin: + - `esptool.py -p /dev/ttyUSB0 --chip esp32-s3 write_flash 0x10000 .bin` + - For merged bin: + - `esptool.py -p /dev/ttyUSB0 --chip esp32-s3 write_flash 0x00000 .bin` + -You can also flash the repeater using esptool. You will need to install esptool with the following command... -`pip install esptool --break-system-packages` +**Instructions for nRF devices:** -Then to flash the firmware to Heltec, obtain the .bin file from https://flasher.meshcore.co.uk/ (download all firmware link) +For nRF devices (e.g. RAK, Heltec T114) you need the following: +- Download firmware file from flasher.meshcore.co.uk + - Go to the web site on a browser, find the section that has the firmware up need + - You need the ZIP version for the adafruit flash tool (below) + - Click the Download button, right click on the ZIP file, for example: + - `RAK_4631_companion_radio_ble-v1.7.1-165fb33.zip` + - Right click on the file name and copy the link and note it for later use here is an example: `https://flasher.meshcore.dev/releases/download/companion-v1.7.1/RAK_4631_companion_radio_ble-v1.7.1-165fb33.zip` + - Run: + - `wget https://flasher.meshcore.dev/releases/download/companion-v1.7.1/RAK_4631_companion_radio_ble-v1.7.1-165fb33.zip` to download the firmware file for your device type. or the version you need - USB, BLE, Repeater, Room Server, ZIP file only + - Confirm the `ttyXXXX` device path on your Raspberry Pi: + - Go to `/dev` directory, run ls command to find confirm your device path + - They are usually `/dev/ttyACM0` for nRF devices + - For nRF-based devices, install adafruit-nrfutil + - `pip install adafruit-nrfutil --break-system-packages` + - Use this command to flash the nRF device: + - `adafruit-nrfutil --verbose dfu serial --package RAK_4631_companion_radio_usb-v1.7.1-165fb33.zip -p /dev/ttyACM0 -b 115200 --singlebank --touch 1200` + + +To manage a repeater or room server connected to a Pi over USB serial using shell commands, you need to install `picocom`. To install `picocom`, run the following command: +- `sudo apt install picocom` -For Heltec: -`esptool.py -p /dev/ttyUSB0 --chip esp32-s3 write_flash 0x00000 firmware.bin` +To start managing your USB serial-connected device using picocom, use the following command: + - `picocom -b 115200 /dev/ttyUSB0 --imap lfcrlf` -If flashing a visual studio code build bin file, flash with the following offset: -`esptool.py -p /dev/ttyUSB0 --chip esp32-s3 write_flash 0x10000 firmware.bin` +From here, reference repeater and room server command line commands on MeshCore github wiki here: + - https://github.com/ripplebiz/MeshCore/wiki/Repeater-&-Room-Server-CLI-Reference -For Pi -Download the zip from the online flasher website and use the following command: - -Note: Requires adafruit-nrfutil command which can be installed as follows. -`pip install adafruit-nrfutil --break-system-packages` - -``` -adafruit-nrfutil --verbose dfu serial --package t1000_e_bootloader-0.9.1-5-g488711a_s140_7.3.0.zip -p /dev/ttyACM0 -b 115200 --singlebank --touch 1200 -``` - -[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1342120825251299388) ### 5.14. Q: Are there are projects built around MeshCore? @@ -541,9 +575,9 @@ You can get the epoch time on and use it to se **A:** 1. Connect USB-C cable to your device, per your device's instruction, get it to flash mode: - - For RAK, double click its reset button - - For T1000-e, quickly disconnect and reconnect the magnetic side of the cable from the device TWICE - - For Heltec T114, click the reset button once (the bottom button) + - For RAK, click the reset button **TWICE** + - For T1000-e, quickly disconnect and reconnect the magnetic side of the cable from the device **TWICE** + - For Heltec T114, click the reset button **TWICE** (the bottom button) - For Xiao nRF52, click the reset button once. If that doesn't work, quickly double click the reset button twice. If that doesn't work, disconnection the board from your PC and reconnect again ([seeed studio wiki](https://wiki.seeedstudio.com/XIAO_BLE/#access-the-swd-pins-for-debugging-and-reflashing-bootloader)) 5. A new folder will appear on your computer's desktop 6. Download the `flash_erase*.uf2` file for your device on flasher.meshcore.co.uk @@ -612,7 +646,9 @@ Currently, the following boards are supported: - Seeed Studio XIAO nRF52840 BLE SENSE - RAK 4631 +### 7.4 Q are the MeshCore logo and font available? +**A:** Yes, it is on the MeshCore github repo here: https://github.com/ripplebiz/MeshCore/tree/main/logo --- diff --git a/examples/companion_radio/AbstractUITask.h b/examples/companion_radio/AbstractUITask.h new file mode 100644 index 00000000..1277bba9 --- /dev/null +++ b/examples/companion_radio/AbstractUITask.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#ifdef PIN_BUZZER + #include +#endif + +#include "NodePrefs.h" + +enum class UIEventType { + none, + contactMessage, + channelMessage, + roomMessage, + newContactMessage, + ack +}; + +class AbstractUITask { +protected: + mesh::MainBoard* _board; + BaseSerialInterface* _serial; + bool _connected; + + AbstractUITask(mesh::MainBoard* board, BaseSerialInterface* serial) : _board(board), _serial(serial) { + _connected = false; + } + +public: + void setHasConnection(bool connected) { _connected = connected; } + bool hasConnection() const { return _connected; } + uint16_t getBattMilliVolts() const { return _board->getBattMilliVolts(); } + bool isSerialEnabled() const { return _serial->isEnabled(); } + void enableSerial() { _serial->enable(); } + void disableSerial() { _serial->disable(); } + virtual void msgRead(int msgcount) = 0; + virtual void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) = 0; + virtual void soundBuzzer(UIEventType bet = UIEventType::none) = 0; + virtual void loop() = 0; +}; diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index a7c0333c..1fa5478b 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -41,7 +41,7 @@ #define CMD_SEND_TRACE_PATH 36 #define CMD_SET_DEVICE_PIN 37 #define CMD_SET_OTHER_PARAMS 38 -#define CMD_SEND_TELEMETRY_REQ 39 +#define CMD_SEND_TELEMETRY_REQ 39 // can deprecate this #define CMD_GET_CUSTOM_VARS 40 #define CMD_SET_CUSTOM_VAR 41 #define CMD_GET_ADVERT_PATH 42 @@ -49,6 +49,7 @@ // NOTE: CMD range 44..49 parked, potentially for WiFi operations #define CMD_SEND_BINARY_REQ 50 #define CMD_FACTORY_RESET 51 +#define CMD_SEND_PATH_DISCOVERY_REQ 52 #define RESP_CODE_OK 0 #define RESP_CODE_ERR 1 @@ -97,6 +98,7 @@ #define PUSH_CODE_NEW_ADVERT 0x8A #define PUSH_CODE_TELEMETRY_RESPONSE 0x8B #define PUSH_CODE_BINARY_RESPONSE 0x8C +#define PUSH_CODE_PATH_DISCOVERY_RESPONSE 0x8D #define ERR_CODE_UNSUPPORTED_CMD 1 #define ERR_CODE_NOT_FOUND 2 @@ -107,10 +109,6 @@ #define MAX_SIGN_DATA_LEN (8 * 1024) // 8K -#ifdef DISPLAY_CLASS -#include "UITask.h" -#endif - void MyMesh::writeOKFrame() { uint8_t buf[1]; buf[0] = RESP_CODE_OK; @@ -166,12 +164,12 @@ void MyMesh::updateContactFromFrame(ContactInfo &contact, uint32_t& last_mod, co i += 32; memcpy(&contact.last_advert_timestamp, &frame[i], 4); i += 4; - if (i + 8 >= len) { // optional fields + if (len >= i + 8) { // optional fields memcpy(&contact.gps_lat, &frame[i], 4); i += 4; memcpy(&contact.gps_lon, &frame[i], 4); i += 4; - if (i + 4 >= len) { + if (len >= i + 4) { memcpy(&last_mod, &frame[i], 4); } } @@ -245,7 +243,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path } } else { #ifdef DISPLAY_CLASS - ui_task.soundBuzzer(UIEventType::newContactMessage); + if (_ui) _ui->soundBuzzer(UIEventType::newContactMessage); #endif } @@ -265,6 +263,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path } memcpy(p->pubkey_prefix, contact.id.pub_key, sizeof(p->pubkey_prefix)); + strcpy(p->name, contact.name); p->recv_timestamp = getRTCClock()->getCurrentTime(); p->path_len = path_len; memcpy(p->path, path, p->path_len); @@ -273,6 +272,20 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); } +static int sort_by_recent(const void *a, const void *b) { + return ((AdvertPath *) b)->recv_timestamp - ((AdvertPath *) a)->recv_timestamp; +} + +int MyMesh::getRecentlyHeard(AdvertPath dest[], int max_num) { + if (max_num > ADVERT_PATH_TABLE_SIZE) max_num = ADVERT_PATH_TABLE_SIZE; + qsort(advert_paths, ADVERT_PATH_TABLE_SIZE, sizeof(advert_paths[0]), sort_by_recent); + + for (int i = 0; i < max_num; i++) { + dest[i] = advert_paths[i]; + } + return max_num; +} + void MyMesh::onContactPathUpdated(const ContactInfo &contact) { out_frame[0] = PUSH_CODE_PATH_UPDATED; memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE); @@ -337,10 +350,10 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe #ifdef DISPLAY_CLASS // we only want to show text messages on display, not cli data bool should_display = txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_SIGNED_PLAIN; - if (should_display) { - ui_task.newMsg(path_len, from.name, text, offline_queue_len); + if (should_display && _ui) { + _ui->newMsg(path_len, from.name, text, offline_queue_len); if (!_serial->isConnected()) { - ui_task.soundBuzzer(UIEventType::contactMessage); + _ui->soundBuzzer(UIEventType::contactMessage); } } #endif @@ -399,7 +412,7 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe _serial->writeFrame(frame, 1); } else { #ifdef DISPLAY_CLASS - ui_task.soundBuzzer(UIEventType::channelMessage); + if (_ui) _ui->soundBuzzer(UIEventType::channelMessage); #endif } #ifdef DISPLAY_CLASS @@ -409,7 +422,7 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe if (getChannel(channel_idx, channel_details)) { channel_name = channel_details.name; } - ui_task.newMsg(path_len, channel_name, text, offline_queue_len); + if (_ui) _ui->newMsg(path_len, channel_name, text, offline_queue_len); #endif } @@ -437,6 +450,9 @@ uint8_t MyMesh::onContactRequest(const ContactInfo &contact, uint32_t sender_tim permissions |= cp & TELEM_PERM_ENVIRONMENT; } + uint8_t perm_mask = ~(data[1]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions + permissions &= perm_mask; + if (permissions & TELEM_PERM_BASE) { // only respond if base permission bit is set telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); @@ -527,6 +543,39 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data, } } +bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { + if (extra_type == PAYLOAD_TYPE_RESPONSE && extra_len > 4) { + uint32_t tag; + memcpy(&tag, extra, 4); + + if (tag == pending_discovery) { // check for matching response tag) + pending_discovery = 0; + + if (in_path_len > MAX_PATH_SIZE || out_path_len > MAX_PATH_SIZE) { + MESH_DEBUG_PRINTLN("onContactPathRecv, invalid path sizes: %d, %d", in_path_len, out_path_len); + } else { + int i = 0; + out_frame[i++] = PUSH_CODE_PATH_DISCOVERY_RESPONSE; + out_frame[i++] = 0; // reserved + memcpy(&out_frame[i], contact.id.pub_key, 6); + i += 6; // pub_key_prefix + out_frame[i++] = out_path_len; + memcpy(&out_frame[i], out_path, out_path_len); + i += out_path_len; + out_frame[i++] = in_path_len; + memcpy(&out_frame[i], in_path, in_path_len); + i += in_path_len; + // NOTE: telemetry data in 'extra' is discarded at present + + _serial->writeFrame(out_frame, i); + } + return false; // DON'T send reciprocal path! + } + } + // let base class handle received path and data + return BaseChatMesh::onContactPathRecv(contact, in_path, in_path_len, out_path, out_path_len, extra_type, extra, extra_len); +} + void MyMesh::onRawDataRecv(mesh::Packet *packet) { if (packet->payload_len + 4 > sizeof(out_frame)) { MESH_DEBUG_PRINTLN("onRawDataRecv(), payload_len too long: %d", packet->payload_len); @@ -582,14 +631,14 @@ uint32_t MyMesh::calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t void MyMesh::onSendTimeout() {} -MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store) +MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui) : BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables), - _serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store) { + _serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui) { _iter_started = false; _cli_rescue = false; offline_queue_len = 0; app_target_ver = 0; - pending_login = pending_status = pending_telemetry = pending_req = 0; + clearPendingReqs(); next_ack_idx = 0; sign_data = NULL; dirty_contacts_expiry = 0; @@ -661,6 +710,7 @@ void MyMesh::begin(bool has_display) { _active_ble_pin = 0; #endif + resetContacts(); _store->loadContacts(this); addChannel("Public", PUBLIC_GROUP_PSK); // pre-configure Andy's public channel _store->loadChannels(this); @@ -987,7 +1037,7 @@ void MyMesh::handleCmdFrame(size_t len) { if ((out_len = getFromOfflineQueue(out_frame)) > 0) { _serial->writeFrame(out_frame, out_len); #ifdef DISPLAY_CLASS - ui_task.msgRead(offline_queue_len); + if (_ui) _ui->msgRead(offline_queue_len); #endif } else { out_frame[0] = RESP_CODE_NO_MORE_MESSAGES; @@ -1097,6 +1147,9 @@ void MyMesh::handleCmdFrame(size_t len) { if (_store->saveMainIdentity(identity)) { self_id = identity; writeOKFrame(); + // re-load contacts, to recalc shared secrets + resetContacts(); + _store->loadContacts(this); } else { writeErrFrame(ERR_CODE_FILE_IO_ERROR); } @@ -1130,7 +1183,7 @@ void MyMesh::handleCmdFrame(size_t len) { if (result == MSG_SEND_FAILED) { writeErrFrame(ERR_CODE_TABLE_FULL); } else { - pending_req = pending_telemetry = pending_status = 0; + clearPendingReqs(); memcpy(&pending_login, recipient->id.pub_key, 4); // match this to onContactResponse() out_frame[0] = RESP_CODE_SENT; out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0; @@ -1150,7 +1203,7 @@ void MyMesh::handleCmdFrame(size_t len) { if (result == MSG_SEND_FAILED) { writeErrFrame(ERR_CODE_TABLE_FULL); } else { - pending_req = pending_telemetry = pending_login = 0; + clearPendingReqs(); // FUTURE: pending_status = tag; // match this in onContactResponse() memcpy(&pending_status, recipient->id.pub_key, 4); // legacy matching scheme out_frame[0] = RESP_CODE_SENT; @@ -1162,6 +1215,35 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_NOT_FOUND); // contact not found } + } else if (cmd_frame[0] == CMD_SEND_PATH_DISCOVERY_REQ && cmd_frame[1] == 0 && len >= 2 + PUB_KEY_SIZE) { + uint8_t *pub_key = &cmd_frame[2]; + ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE); + if (recipient) { + uint32_t tag, est_timeout; + // 'Path Discovery' is just a special case of flood + Telemetry req + uint8_t req_data[9]; + req_data[0] = REQ_TYPE_GET_TELEMETRY_DATA; + req_data[1] = ~(TELEM_PERM_BASE); // NEW: inverse permissions mask (ie. we only want BASE telemetry) + memset(&req_data[2], 0, 3); // reserved + getRNG()->random(&req_data[5], 4); // random blob to help make packet-hash unique + auto save = recipient->out_path_len; // temporarily force sendRequest() to flood + recipient->out_path_len = -1; + int result = sendRequest(*recipient, req_data, sizeof(req_data), tag, est_timeout); + recipient->out_path_len = save; + if (result == MSG_SEND_FAILED) { + writeErrFrame(ERR_CODE_TABLE_FULL); + } else { + clearPendingReqs(); + pending_discovery = tag; // match this in onContactResponse() + out_frame[0] = RESP_CODE_SENT; + out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0; + memcpy(&out_frame[2], &tag, 4); + memcpy(&out_frame[6], &est_timeout, 4); + _serial->writeFrame(out_frame, 10); + } + } else { + writeErrFrame(ERR_CODE_NOT_FOUND); // contact not found + } } else if (cmd_frame[0] == CMD_SEND_TELEMETRY_REQ && len >= 4 + PUB_KEY_SIZE) { // can deprecate, in favour of CMD_SEND_BINARY_REQ uint8_t *pub_key = &cmd_frame[4]; ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE); @@ -1171,7 +1253,7 @@ void MyMesh::handleCmdFrame(size_t len) { if (result == MSG_SEND_FAILED) { writeErrFrame(ERR_CODE_TABLE_FULL); } else { - pending_status = pending_login = pending_req = 0; + clearPendingReqs(); pending_telemetry = tag; // match this in onContactResponse() out_frame[0] = RESP_CODE_SENT; out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0; @@ -1207,7 +1289,7 @@ void MyMesh::handleCmdFrame(size_t len) { if (result == MSG_SEND_FAILED) { writeErrFrame(ERR_CODE_TABLE_FULL); } else { - pending_status = pending_login = pending_telemetry = 0; + clearPendingReqs(); pending_req = tag; // match this in onContactResponse() out_frame[0] = RESP_CODE_SENT; out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0; @@ -1557,7 +1639,7 @@ void MyMesh::loop() { } #ifdef DISPLAY_CLASS - ui_task.setHasConnection(_serial->isConnected()); + if (_ui) _ui->setHasConnection(_serial->isConnected()); #endif } diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index e2e96ff4..0a5057ea 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -2,9 +2,7 @@ #include #include -#ifdef DISPLAY_CLASS -#include "UITask.h" -#endif +#include "AbstractUITask.h" /*------------ Frame Protocol --------------*/ #define FIRMWARE_VER_CODE 7 @@ -77,9 +75,17 @@ #define REQ_TYPE_KEEP_ALIVE 0x02 #define REQ_TYPE_GET_TELEMETRY_DATA 0x03 +struct AdvertPath { + uint8_t pubkey_prefix[7]; + uint8_t path_len; + char name[32]; + uint32_t recv_timestamp; + uint8_t path[MAX_PATH_SIZE]; +}; + class MyMesh : public BaseChatMesh, public DataStoreHost { public: - MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store); + MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui=NULL); void begin(bool has_display); void startInterface(BaseSerialInterface &serial); @@ -93,6 +99,8 @@ public: bool advert(); void enterCLIRescue(); + int getRecentlyHeard(AdvertPath dest[], int max_num); + protected: float getAirtimeBudgetFactor() const override; int getInterferenceThreshold() const override; @@ -101,6 +109,7 @@ protected: void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override; bool isAutoAddEnabled() const override; + bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; void onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) override; void onContactPathUpdated(const ContactInfo &contact) override; bool processAck(const uint8_t *data) override; @@ -133,6 +142,10 @@ protected: bool onChannelLoaded(uint8_t channel_idx, const ChannelDetails& ch) override { return setChannel(channel_idx, ch); } bool getChannelForSave(uint8_t channel_idx, ChannelDetails& ch) override { return getChannel(channel_idx, ch); } + void clearPendingReqs() { + pending_login = pending_status = pending_telemetry = pending_discovery = pending_req = 0; + } + private: void writeOKFrame(); void writeErrFrame(uint8_t err_code); @@ -161,9 +174,10 @@ private: NodePrefs _prefs; uint32_t pending_login; uint32_t pending_status; - uint32_t pending_telemetry; // pending _TELEMETRY_REQ + uint32_t pending_telemetry, pending_discovery; // pending _TELEMETRY_REQ uint32_t pending_req; // pending _BINARY_REQ BaseSerialInterface *_serial; + AbstractUITask* _ui; ContactsIterator _iter; uint32_t _iter_filter_since; @@ -196,17 +210,8 @@ private: AckTableEntry expected_ack_table[EXPECTED_ACK_TABLE_SIZE]; // circular table int next_ack_idx; - struct AdvertPath { - uint8_t pubkey_prefix[7]; - uint8_t path_len; - uint32_t recv_timestamp; - uint8_t path[MAX_PATH_SIZE]; - }; #define ADVERT_PATH_TABLE_SIZE 16 AdvertPath advert_paths[ADVERT_PATH_TABLE_SIZE]; // circular table }; extern MyMesh the_mesh; -#ifdef DISPLAY_CLASS -extern UITask ui_task; -#endif \ No newline at end of file diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 34c30498..1d5ec564 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -75,14 +75,19 @@ static uint32_t _atoi(const char* sp) { #endif /* GLOBAL OBJECTS */ -StdRNG fast_rng; -SimpleMeshTables tables; -MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store); - #ifdef DISPLAY_CLASS #include "UITask.h" - UITask ui_task(&board); + UITask ui_task(&board, &serial_interface); #endif + +StdRNG fast_rng; +SimpleMeshTables tables; +MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store + #ifdef DISPLAY_CLASS + , &ui_task + #endif +); + /* END GLOBAL OBJECTS */ void halt() { @@ -99,7 +104,10 @@ void setup() { if (display.begin()) { disp = &display; disp->startFrame(); - disp->print("Please wait..."); + #ifdef ST7789 + disp->setTextSize(2); + #endif + disp->drawTextCentered(disp->width() / 2, 28, "Loading..."); disp->endFrame(); } #endif diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp new file mode 100644 index 00000000..f3ad28c6 --- /dev/null +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -0,0 +1,597 @@ +#include "UITask.h" +#include +#include "../MyMesh.h" +#include "target.h" + +#define AUTO_OFF_MILLIS 15000 // 15 seconds +#define BOOT_SCREEN_MILLIS 3000 // 3 seconds + +#ifdef PIN_STATUS_LED +#define LED_ON_MILLIS 20 +#define LED_ON_MSG_MILLIS 200 +#define LED_CYCLE_MILLIS 4000 +#endif + +#define LONG_PRESS_MILLIS 1200 + +#ifndef UI_RECENT_LIST_SIZE + #define UI_RECENT_LIST_SIZE 4 +#endif + +#define PRESS_LABEL "long press" + +#include "icons.h" + +class SplashScreen : public UIScreen { + UITask* _task; + unsigned long dismiss_after; + char _version_info[12]; + +public: + SplashScreen(UITask* task) : _task(task) { + // strip off dash and commit hash by changing dash to null terminator + // e.g: v1.2.3-abcdef -> v1.2.3 + const char *ver = FIRMWARE_VERSION; + const char *dash = strchr(ver, '-'); + + int len = dash ? dash - ver : strlen(ver); + if (len >= sizeof(_version_info)) len = sizeof(_version_info) - 1; + memcpy(_version_info, ver, len); + _version_info[len] = 0; + + dismiss_after = millis() + BOOT_SCREEN_MILLIS; + } + + int render(DisplayDriver& display) override { + // meshcore logo + display.setColor(DisplayDriver::BLUE); + int logoWidth = 128; + display.drawXbm((display.width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); + + // version info + display.setColor(DisplayDriver::LIGHT); + display.setTextSize(2); + display.drawTextCentered(display.width()/2, 22, _version_info); + + display.setTextSize(1); + display.drawTextCentered(display.width()/2, 42, FIRMWARE_BUILD_DATE); + + return 1000; + } + + void poll() override { + if (millis() >= dismiss_after) { + _task->gotoHomeScreen(); + } + } +}; + +class HomeScreen : public UIScreen { + enum HomePage { + FIRST, + RECENT, + RADIO, + BLUETOOTH, + ADVERT, + SHUTDOWN, + Count // keep as last + }; + + UITask* _task; + mesh::RTCClock* _rtc; + SensorManager* _sensors; + NodePrefs* _node_prefs; + uint8_t _page; + bool _shutdown_init; + AdvertPath recent[UI_RECENT_LIST_SIZE]; + + void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) { + // Convert millivolts to percentage + const int minMilliVolts = 3000; // Minimum voltage (e.g., 3.0V) + const int maxMilliVolts = 4200; // Maximum voltage (e.g., 4.2V) + int batteryPercentage = ((batteryMilliVolts - minMilliVolts) * 100) / (maxMilliVolts - minMilliVolts); + if (batteryPercentage < 0) batteryPercentage = 0; // Clamp to 0% + if (batteryPercentage > 100) batteryPercentage = 100; // Clamp to 100% + + // battery icon + int iconWidth = 24; + int iconHeight = 10; + int iconX = display.width() - iconWidth - 5; // Position the icon near the top-right corner + int iconY = 0; + display.setColor(DisplayDriver::GREEN); + + // battery outline + display.drawRect(iconX, iconY, iconWidth, iconHeight); + + // battery "cap" + display.fillRect(iconX + iconWidth, iconY + (iconHeight / 4), 3, iconHeight / 2); + + // fill the battery based on the percentage + int fillWidth = (batteryPercentage * (iconWidth - 4)) / 100; + display.fillRect(iconX + 2, iconY + 2, fillWidth, iconHeight - 4); + } + +public: + HomeScreen(UITask* task, mesh::RTCClock* rtc, SensorManager* sensors, NodePrefs* node_prefs) + : _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0), _shutdown_init(false) { } + + void poll() override { + if (_shutdown_init && !_task->isButtonPressed()) { // must wait for USR button to be released + _task->shutdown(); + } + } + + int render(DisplayDriver& display) override { + char tmp[80]; + // node name + display.setCursor(0, 0); + display.setTextSize(1); + display.setColor(DisplayDriver::GREEN); + display.print(_node_prefs->node_name); + + // battery voltage + renderBatteryIndicator(display, _task->getBattMilliVolts()); + + // curr page indicator + int y = 14; + int x = display.width() / 2 - 25; + for (uint8_t i = 0; i < HomePage::Count; i++, x += 10) { + if (i == _page) { + display.fillRect(x-1, y-1, 3, 3); + } else { + display.fillRect(x, y, 1, 1); + } + } + + if (_page == HomePage::FIRST) { + display.setColor(DisplayDriver::YELLOW); + display.setTextSize(2); + sprintf(tmp, "MSG: %d", _task->getMsgCount()); + display.drawTextCentered(display.width() / 2, 20, tmp); + + if (_task->hasConnection()) { + display.setColor(DisplayDriver::GREEN); + display.setTextSize(1); + display.drawTextCentered(display.width() / 2, 43, "< Connected >"); + } else if (the_mesh.getBLEPin() != 0) { // BT pin + display.setColor(DisplayDriver::RED); + display.setTextSize(2); + sprintf(tmp, "Pin:%d", the_mesh.getBLEPin()); + display.drawTextCentered(display.width() / 2, 43, tmp); + } + } else if (_page == HomePage::RECENT) { + the_mesh.getRecentlyHeard(recent, UI_RECENT_LIST_SIZE); + display.setColor(DisplayDriver::GREEN); + int y = 20; + for (int i = 0; i < UI_RECENT_LIST_SIZE; i++, y += 11) { + auto a = &recent[i]; + if (a->name[0] == 0) continue; // empty slot + display.setCursor(0, y); + display.print(a->name); + int secs = _rtc->getCurrentTime() - a->recv_timestamp; + if (secs < 60) { + sprintf(tmp, "%ds", secs); + } else if (secs < 60*60) { + sprintf(tmp, "%dm", secs / 60); + } else { + sprintf(tmp, "%dh", secs / (60*60)); + } + display.setCursor(display.width() - display.getTextWidth(tmp) - 1, y); + display.print(tmp); + } + } else if (_page == HomePage::RADIO) { + display.setColor(DisplayDriver::YELLOW); + display.setTextSize(1); + // freq / sf + display.setCursor(0, 20); + sprintf(tmp, "FQ: %06.3f SF: %d", _node_prefs->freq, _node_prefs->sf); + display.print(tmp); + + display.setCursor(0, 31); + sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr); + display.print(tmp); + + // tx power, noise floor + display.setCursor(0, 42); + sprintf(tmp, "TX: %ddBm", _node_prefs->tx_power_dbm); + display.print(tmp); + display.setCursor(0, 53); + sprintf(tmp, "Noise floor: %d", radio_driver.getNoiseFloor()); + display.print(tmp); + } else if (_page == HomePage::BLUETOOTH) { + display.setColor(DisplayDriver::GREEN); + display.drawXbm((display.width() - 32) / 2, 18, + _task->isSerialEnabled() ? bluetooth_on : bluetooth_off, + 32, 32); + display.setTextSize(1); + display.drawTextCentered(display.width() / 2, 64 - 11, "toggle: " PRESS_LABEL); + } else if (_page == HomePage::ADVERT) { + display.setColor(DisplayDriver::GREEN); + display.drawXbm((display.width() - 32) / 2, 18, advert_icon, 32, 32); + display.drawTextCentered(display.width() / 2, 64 - 11, "advert: " PRESS_LABEL); + } else if (_page == HomePage::SHUTDOWN) { + display.setColor(DisplayDriver::GREEN); + display.setTextSize(1); + if (_shutdown_init) { + display.drawTextCentered(display.width() / 2, 34, "shutting down..."); + } else { + display.drawXbm((display.width() - 32) / 2, 18, power_icon, 32, 32); + display.drawTextCentered(display.width() / 2, 64 - 11, "off: " PRESS_LABEL); + } + } + return 5000; // next render after 5000 ms + } + + bool handleInput(char c) override { + if (c == KEY_LEFT) { + _page = (_page + HomePage::Count - 1) % HomePage::Count; + return true; + } + if (c == KEY_RIGHT || c == KEY_SELECT) { + _page = (_page + 1) % HomePage::Count; + if (_page == HomePage::RECENT) { + _task->showAlert("Recent adverts", 800); + } + return true; + } + if (c == KEY_ENTER && _page == HomePage::BLUETOOTH) { + if (_task->isSerialEnabled()) { // toggle Bluetooth on/off + _task->disableSerial(); + } else { + _task->enableSerial(); + } + return true; + } + if (c == KEY_ENTER && _page == HomePage::ADVERT) { + #ifdef PIN_BUZZER + _task->soundBuzzer(UIEventType::ack); + #endif + if (the_mesh.advert()) { + _task->showAlert("Advert sent!", 1000); + } else { + _task->showAlert("Advert failed..", 1000); + } + return true; + } + if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) { + _shutdown_init = true; // need to wait for button to be released + return true; + } + return false; + } +}; + +class MsgPreviewScreen : public UIScreen { + UITask* _task; + mesh::RTCClock* _rtc; + + struct MsgEntry { + uint32_t timestamp; + char origin[62]; + char msg[78]; + }; + #define MAX_UNREAD_MSGS 32 + int num_unread; + MsgEntry unread[MAX_UNREAD_MSGS]; + +public: + MsgPreviewScreen(UITask* task, mesh::RTCClock* rtc) : _task(task), _rtc(rtc) { num_unread = 0; } + + void addPreview(uint8_t path_len, const char* from_name, const char* msg) { + if (num_unread >= MAX_UNREAD_MSGS) return; // full + + auto p = &unread[num_unread++]; + p->timestamp = _rtc->getCurrentTime(); + if (path_len == 0xFF) { + sprintf(p->origin, "(D) %s:", from_name); + } else { + sprintf(p->origin, "(%d) %s:", (uint32_t) path_len, from_name); + } + StrHelper::strncpy(p->msg, msg, sizeof(p->msg)); + } + + int render(DisplayDriver& display) override { + char tmp[16]; + display.setCursor(0, 0); + display.setTextSize(1); + display.setColor(DisplayDriver::GREEN); + sprintf(tmp, "Unread: %d", num_unread); + display.print(tmp); + + auto p = &unread[0]; + + int secs = _rtc->getCurrentTime() - p->timestamp; + if (secs < 60) { + sprintf(tmp, "%ds", secs); + } else if (secs < 60*60) { + sprintf(tmp, "%dm", secs / 60); + } else { + sprintf(tmp, "%dh", secs / (60*60)); + } + display.setCursor(display.width() - display.getTextWidth(tmp) - 2, 0); + display.print(tmp); + + display.drawRect(0, 11, display.width(), 1); // horiz line + + display.setCursor(0, 14); + display.setColor(DisplayDriver::YELLOW); + display.print(p->origin); + + display.setCursor(0, 25); + display.setColor(DisplayDriver::LIGHT); + display.printWordWrap(p->msg, display.width()); + + return 1000; // next render after 1000 ms + } + + bool handleInput(char c) override { + if (c == KEY_SELECT || c == KEY_RIGHT) { + num_unread--; + if (num_unread == 0) { + _task->gotoHomeScreen(); + } else { + // delete first/curr item from unread queue + for (int i = 0; i < num_unread; i++) { + unread[i] = unread[i + 1]; + } + } + return true; + } + if (c == KEY_ENTER) { + num_unread = 0; // clear unread queue + _task->gotoHomeScreen(); + return true; + } + return false; + } +}; + +void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs) { + _display = display; + _sensors = sensors; + _auto_off = millis() + AUTO_OFF_MILLIS; + +#if defined(PIN_USER_BTN) + user_btn.begin(); +#endif + + _node_prefs = node_prefs; + if (_display != NULL) { + _display->turnOn(); + } + +#ifdef PIN_BUZZER + buzzer.begin(); +#endif + + ui_started_at = millis(); + _alert_expiry = 0; + + splash = new SplashScreen(this); + home = new HomeScreen(this, &rtc_clock, sensors, node_prefs); + msg_preview = new MsgPreviewScreen(this, &rtc_clock); + setCurrScreen(splash); +} + +void UITask::showAlert(const char* text, int duration_millis) { + strcpy(_alert, text); + _alert_expiry = millis() + duration_millis; +} + +void UITask::soundBuzzer(UIEventType bet) { +#if defined(PIN_BUZZER) +switch(bet){ + case UIEventType::contactMessage: + // gemini's pick + buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7"); + break; + case UIEventType::channelMessage: + buzzer.play("kerplop:d=16,o=6,b=120:32g#,32c#"); + break; + case UIEventType::ack: + buzzer.play("ack:d=32,o=8,b=120:c"); + break; + case UIEventType::roomMessage: + case UIEventType::newContactMessage: + case UIEventType::none: + default: + break; +} +#endif +} + +void UITask::msgRead(int msgcount) { + _msgcount = msgcount; + if (msgcount == 0) { + gotoHomeScreen(); + } +} + +void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) { + _msgcount = msgcount; + + ((MsgPreviewScreen *) msg_preview)->addPreview(path_len, from_name, text); + setCurrScreen(msg_preview); + + if (_display != NULL) { + if (!_display->isOn()) _display->turnOn(); + _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer + _next_refresh = 0; // trigger refresh + } +} + +void UITask::userLedHandler() { +#ifdef PIN_STATUS_LED + static int state = 0; + static int next_change = 0; + static int last_increment = 0; + + int cur_time = millis(); + if (cur_time > next_change) { + if (state == 0) { + state = 1; + if (_msgcount > 0) { + last_increment = LED_ON_MSG_MILLIS; + } else { + last_increment = LED_ON_MILLIS; + } + next_change = cur_time + last_increment; + } else { + state = 0; + next_change = cur_time + LED_CYCLE_MILLIS - last_increment; + } + digitalWrite(PIN_STATUS_LED, state); + } +#endif +} + +void UITask::setCurrScreen(UIScreen* c) { + curr = c; + _next_refresh = 0; +} + +/* + hardware-agnostic pre-shutdown activity should be done here +*/ +void UITask::shutdown(bool restart){ + + #ifdef PIN_BUZZER + /* note: we have a choice here - + we can do a blocking buzzer.loop() with non-deterministic consequences + or we can set a flag and delay the shutdown for a couple of seconds + while a non-blocking buzzer.loop() plays out in UITask::loop() + */ + buzzer.shutdown(); + uint32_t buzzer_timer = millis(); // fail-safe shutdown + while (buzzer.isPlaying() && (millis() - 2500) < buzzer_timer) + buzzer.loop(); + + #endif // PIN_BUZZER + + if (restart) { + _board->reboot(); + } else { + _display->turnOff(); + _board->powerOff(); + } +} + +bool UITask::isButtonPressed() const { +#ifdef PIN_USER_BTN + return user_btn.isPressed(); +#else + return false; +#endif +} + +void UITask::loop() { + char c = 0; +#if defined(PIN_USER_BTN) + int ev = user_btn.check(); + if (ev == BUTTON_EVENT_CLICK) { + c = checkDisplayOn(KEY_SELECT); + } else if (ev == BUTTON_EVENT_LONG_PRESS) { + c = handleLongPress(KEY_ENTER); + } +#endif +#if defined(WIO_TRACKER_L1) + ev = joystick_left.check(); + if (ev == BUTTON_EVENT_CLICK) { + c = checkDisplayOn(KEY_LEFT); + } else if (ev == BUTTON_EVENT_LONG_PRESS) { + c = handleLongPress(KEY_LEFT); + } + ev = joystick_right.check(); + if (ev == BUTTON_EVENT_CLICK) { + c = checkDisplayOn(KEY_RIGHT); + } else if (ev == BUTTON_EVENT_LONG_PRESS) { + c = handleLongPress(KEY_RIGHT); + } +#endif + + if (c != 0 && curr) { + curr->handleInput(c); + _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer + _next_refresh = 0; // trigger refresh + } + + userLedHandler(); + +#ifdef PIN_BUZZER + if (buzzer.isPlaying()) buzzer.loop(); +#endif + + if (curr) curr->poll(); + + if (_display != NULL && _display->isOn()) { + if (millis() >= _next_refresh && curr) { + _display->startFrame(); + int delay_millis = curr->render(*_display); + if (millis() < _alert_expiry) { // render alert popup + _display->setTextSize(1); + int y = _display->height() / 3; + int p = _display->height() / 32; + _display->setColor(DisplayDriver::DARK); + _display->fillRect(p, y, _display->width() - p*2, y); + _display->setColor(DisplayDriver::LIGHT); // draw box border + _display->drawRect(p, y, _display->width() - p*2, y); + _display->drawTextCentered(_display->width() / 2, y + p*3, _alert); + _next_refresh = _alert_expiry; // will need refresh when alert is dismissed + } else { + _next_refresh = millis() + delay_millis; + } + _display->endFrame(); + } + if (millis() > _auto_off) { + _display->turnOff(); + } + } + +#ifdef AUTO_SHUTDOWN_MILLIVOLTS + if (millis() > next_batt_chck) { + uint16_t milliVolts = getBattMilliVolts(); + if (milliVolts > 0 && milliVolts < AUTO_SHUTDOWN_MILLIVOLTS) { + shutdown(); + } + next_batt_chck = millis() + 8000; + } +#endif +} + +char UITask::checkDisplayOn(char c) { + if (_display != NULL) { + if (!_display->isOn()) { + _display->turnOn(); // turn display on and consume event + c = 0; + } + _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer + _next_refresh = 0; // trigger refresh + } + return c; +} + +char UITask::handleLongPress(char c) { + if (millis() - ui_started_at < 8000) { // long press in first 8 seconds since startup -> CLI/rescue + the_mesh.enterCLIRescue(); + c = 0; // consume event + } + return c; +} + +/* +void UITask::handleButtonTriplePress() { + MESH_DEBUG_PRINTLN("UITask: triple press triggered"); + // Toggle buzzer quiet mode + #ifdef PIN_BUZZER + if (buzzer.isQuiet()) { + buzzer.quiet(false); + soundBuzzer(UIEventType::ack); + showAlert("Buzzer: ON", 600); + } else { + buzzer.quiet(true); + showAlert("Buzzer: OFF", 600); + } + _next_refresh = 0; // trigger refresh + #endif +} +*/ diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h new file mode 100644 index 00000000..f9e01550 --- /dev/null +++ b/examples/companion_radio/ui-new/UITask.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#ifdef PIN_BUZZER + #include +#endif + +#include "../AbstractUITask.h" +#include "../NodePrefs.h" + +class UITask : public AbstractUITask { + DisplayDriver* _display; + SensorManager* _sensors; +#ifdef PIN_BUZZER + genericBuzzer buzzer; +#endif + unsigned long _next_refresh, _auto_off; + NodePrefs* _node_prefs; + char _alert[80]; + unsigned long _alert_expiry; + int _msgcount; + unsigned long ui_started_at, next_batt_chck; + + UIScreen* splash; + UIScreen* home; + UIScreen* msg_preview; + UIScreen* curr; + + void userLedHandler(); + + // Button action handlers + char checkDisplayOn(char c); + char handleLongPress(char c); + + void setCurrScreen(UIScreen* c); + +public: + + UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : AbstractUITask(board, serial), _display(NULL), _sensors(NULL) { + next_batt_chck = _next_refresh = 0; + ui_started_at = 0; + curr = NULL; + } + void begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs); + + void gotoHomeScreen() { setCurrScreen(home); } + void showAlert(const char* text, int duration_millis); + int getMsgCount() const { return _msgcount; } + bool hasDisplay() const { return _display != NULL; } + bool isButtonPressed() const; + + // from AbstractUITask + void msgRead(int msgcount) override; + void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override; + void soundBuzzer(UIEventType bet = UIEventType::none) override; + void loop() override; + + void shutdown(bool restart = false); +}; diff --git a/examples/companion_radio/ui-new/icons.h b/examples/companion_radio/ui-new/icons.h new file mode 100644 index 00000000..5220f409 --- /dev/null +++ b/examples/companion_radio/ui-new/icons.h @@ -0,0 +1,118 @@ +#pragma once + +#include + +// 'meshcore', 128x13px +static const uint8_t meshcore_logo [] = { + 0x3c, 0x01, 0xe3, 0xff, 0xc7, 0xff, 0x8f, 0x03, 0x87, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, + 0x3c, 0x03, 0xe3, 0xff, 0xc7, 0xff, 0x8e, 0x03, 0x8f, 0xfe, 0x3f, 0xfe, 0x1f, 0xff, 0x1f, 0xfe, + 0x3e, 0x03, 0xc3, 0xff, 0x8f, 0xff, 0x0e, 0x07, 0x8f, 0xfe, 0x7f, 0xfe, 0x1f, 0xff, 0x1f, 0xfc, + 0x3e, 0x07, 0xc7, 0x80, 0x0e, 0x00, 0x0e, 0x07, 0x9e, 0x00, 0x78, 0x0e, 0x3c, 0x0f, 0x1c, 0x00, + 0x3e, 0x0f, 0xc7, 0x80, 0x1e, 0x00, 0x0e, 0x07, 0x1e, 0x00, 0x70, 0x0e, 0x38, 0x0f, 0x3c, 0x00, + 0x7f, 0x0f, 0xc7, 0xfe, 0x1f, 0xfc, 0x1f, 0xff, 0x1c, 0x00, 0x70, 0x0e, 0x38, 0x0e, 0x3f, 0xf8, + 0x7f, 0x1f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x0e, 0x38, 0x0e, 0x3f, 0xf8, + 0x7f, 0x3f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x1e, 0x3f, 0xfe, 0x3f, 0xf0, + 0x77, 0x3b, 0x87, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xfc, 0x38, 0x00, + 0x77, 0xfb, 0x8f, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xf8, 0x38, 0x00, + 0x73, 0xf3, 0x8f, 0xff, 0x0f, 0xff, 0x1c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x78, 0x7f, 0xf8, + 0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfe, 0x3c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x3c, 0x7f, 0xf8, + 0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8, +}; + +static const uint8_t bluetooth_on[] = { + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x30, 0x00, 0x00, + 0x00, 0x3C, 0x00, 0x00, + 0x00, 0x3E, 0x00, 0x00, + 0x00, 0x3F, 0x80, 0x00, + 0x00, 0x3F, 0xC0, 0x00, + 0x00, 0x3B, 0xE0, 0x00, + 0x30, 0x38, 0xF8, 0x00, + 0x3C, 0x38, 0x7C, 0x00, + 0x3E, 0x38, 0x7C, 0x00, + 0x1F, 0xB8, 0xF8, 0x70, + 0x07, 0xF9, 0xF0, 0x78, + 0x03, 0xFF, 0xC0, 0x78, + 0x00, 0xFF, 0x80, 0x3C, + 0x00, 0x7F, 0x07, 0x1C, + 0x00, 0x7E, 0x07, 0x1C, + 0x03, 0xFF, 0x82, 0x1C, + 0x03, 0xFF, 0xC0, 0x78, + 0x07, 0xFB, 0xE0, 0x78, + 0x0F, 0xB8, 0xF8, 0x70, + 0x3E, 0x38, 0x7C, 0x00, + 0x3C, 0x38, 0x7C, 0x00, + 0x38, 0x38, 0xF8, 0x00, + 0x00, 0x39, 0xF0, 0x00, + 0x00, 0x3F, 0xC0, 0x00, + 0x00, 0x3F, 0x80, 0x00, + 0x00, 0x3E, 0x00, 0x00, + 0x00, 0x3C, 0x00, 0x00, + 0x00, 0x38, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t bluetooth_off[] = { + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x80, 0x00, + 0x00, 0x03, 0xC0, 0x00, + 0x00, 0x03, 0xE0, 0x00, + 0x38, 0x03, 0xF8, 0x00, + 0x3C, 0x03, 0xFC, 0x00, + 0x3E, 0x03, 0xBF, 0x00, + 0x0F, 0x83, 0x8F, 0x80, + 0x07, 0xC3, 0x87, 0xC0, + 0x03, 0xF0, 0x03, 0xC0, + 0x00, 0xF8, 0x0F, 0x80, + 0x00, 0x7C, 0x0F, 0x00, + 0x00, 0x1F, 0x0E, 0x00, + 0x00, 0x0F, 0x80, 0x00, + 0x00, 0x07, 0xE0, 0x00, + 0x00, 0x07, 0xF0, 0x00, + 0x00, 0x0F, 0xF8, 0x00, + 0x00, 0x3F, 0xBE, 0x00, + 0x00, 0x7F, 0x9F, 0x00, + 0x00, 0xFB, 0x8F, 0xC0, + 0x03, 0xE3, 0x83, 0xE0, + 0x03, 0xC3, 0x87, 0xF0, + 0x03, 0x83, 0x8F, 0xFC, + 0x00, 0x03, 0xBF, 0x3C, + 0x00, 0x03, 0xFC, 0x1C, + 0x00, 0x03, 0xF8, 0x00, + 0x00, 0x03, 0xE0, 0x00, + 0x00, 0x03, 0xC0, 0x00, + 0x00, 0x03, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t power_icon[] = { + 0x00, 0x01, 0x80, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x00, + 0x00, 0x33, 0xCC, 0x00, 0x00, 0xF3, 0xCF, 0x00, 0x01, 0xF3, 0xCF, 0x80, + 0x03, 0xF3, 0xCF, 0xC0, 0x07, 0xF3, 0xCF, 0xE0, 0x0F, 0xE3, 0xC7, 0xF0, + 0x1F, 0xC3, 0xC3, 0xF8, 0x1F, 0x83, 0xC1, 0xF8, 0x3F, 0x03, 0xC0, 0xFC, + 0x3E, 0x03, 0xC0, 0x7C, 0x3E, 0x03, 0xC0, 0x7C, 0x7E, 0x01, 0x80, 0x7E, + 0x7C, 0x00, 0x00, 0x3E, 0x7C, 0x00, 0x00, 0x3E, 0x7C, 0x00, 0x00, 0x3E, + 0x7C, 0x00, 0x00, 0x3E, 0x7C, 0x00, 0x00, 0x3E, 0x3E, 0x00, 0x00, 0x7C, + 0x3E, 0x00, 0x00, 0x7C, 0x3F, 0x00, 0x00, 0xFC, 0x1F, 0x80, 0x01, 0xF8, + 0x1F, 0xC0, 0x03, 0xF8, 0x0F, 0xE0, 0x07, 0xF0, 0x0F, 0xF8, 0x1F, 0xF0, + 0x07, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x3F, 0xFC, 0x00, 0x00, 0x0F, 0xF0, 0x00, +}; + +static const uint8_t advert_icon[] = { +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x30, +0x1C, 0x00, 0x00, 0x38, 0x18, 0x00, 0x00, 0x18, 0x30, 0x00, 0x00, 0x0C, +0x30, 0x60, 0x06, 0x0C, 0x60, 0xE0, 0x07, 0x06, 0x61, 0xC0, 0x03, 0x86, +0xE1, 0x81, 0x81, 0x87, 0xC3, 0x07, 0xE0, 0xC3, 0xC3, 0x0F, 0xF0, 0xC3, +0xC3, 0x0F, 0xF0, 0xC3, 0xC3, 0x0F, 0xF0, 0xC3, 0xC3, 0x0F, 0xF0, 0xC3, +0xC3, 0x07, 0xE0, 0xC3, 0xC1, 0x83, 0xC1, 0x83, 0x61, 0x80, 0x01, 0x86, +0x60, 0xC0, 0x03, 0x06, 0x70, 0xE0, 0x07, 0x0E, 0x30, 0x40, 0x02, 0x0C, +0x38, 0x00, 0x00, 0x1C, 0x18, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x00, 0x30, +0x04, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; \ No newline at end of file diff --git a/examples/companion_radio/Button.cpp b/examples/companion_radio/ui-orig/Button.cpp similarity index 100% rename from examples/companion_radio/Button.cpp rename to examples/companion_radio/ui-orig/Button.cpp diff --git a/examples/companion_radio/Button.h b/examples/companion_radio/ui-orig/Button.h similarity index 100% rename from examples/companion_radio/Button.h rename to examples/companion_radio/ui-orig/Button.h diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp similarity index 99% rename from examples/companion_radio/UITask.cpp rename to examples/companion_radio/ui-orig/UITask.cpp index a7f03a26..29d995a7 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -1,8 +1,7 @@ #include "UITask.h" #include #include -#include "NodePrefs.h" -#include "MyMesh.h" +#include "../MyMesh.h" #define AUTO_OFF_MILLIS 15000 // 15 seconds #define BOOT_SCREEN_MILLIS 3000 // 3 seconds diff --git a/examples/companion_radio/UITask.h b/examples/companion_radio/ui-orig/UITask.h similarity index 73% rename from examples/companion_radio/UITask.h rename to examples/companion_radio/ui-orig/UITask.h index 77ef875f..a59ddc41 100644 --- a/examples/companion_radio/UITask.h +++ b/examples/companion_radio/ui-orig/UITask.h @@ -9,28 +9,18 @@ #include #endif -#include "NodePrefs.h" +#include "../AbstractUITask.h" +#include "../NodePrefs.h" + #include "Button.h" - enum class UIEventType -{ - none, - contactMessage, - channelMessage, - roomMessage, - newContactMessage, - ack -}; - -class UITask { +class UITask : public AbstractUITask { DisplayDriver* _display; - mesh::MainBoard* _board; SensorManager* _sensors; #ifdef PIN_BUZZER genericBuzzer buzzer; #endif unsigned long _next_refresh, _auto_off; - bool _connected; NodePrefs* _node_prefs; char _version_info[32]; char _origin[62]; @@ -64,19 +54,20 @@ class UITask { public: - UITask(mesh::MainBoard* board) : _board(board), _display(NULL), _sensors(NULL) { + UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : AbstractUITask(board, serial), _display(NULL), _sensors(NULL) { _next_refresh = 0; ui_started_at = 0; - _connected = false; } void begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs); - void setHasConnection(bool connected) { _connected = connected; } bool hasDisplay() const { return _display != NULL; } void clearMsgPreview(); - void msgRead(int msgcount); - void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount); - void soundBuzzer(UIEventType bet = UIEventType::none); + + // from AbstractUITask + void msgRead(int msgcount) override; + void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override; + void soundBuzzer(UIEventType bet = UIEventType::none) override; + void loop() override; + void shutdown(bool restart = false); - void loop(); }; diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 37e5ee40..3fecb282 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -102,6 +102,7 @@ struct RepeaterStats { uint16_t err_events; // was 'n_full_events' int16_t last_snr; // x 4 uint16_t n_direct_dups, n_flood_dups; + uint32_t total_rx_air_time_secs; }; struct ClientInfo { @@ -162,7 +163,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { } void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr) { - #if MAX_NEIGHBOURS // check if neighbours enabled + #if MAX_NEIGHBOURS // check if neighbours enabled // find existing neighbour, else use least recently updated uint32_t oldest_timestamp = 0xFFFFFFFF; NeighbourInfo* neighbour = &neighbours[0]; @@ -212,16 +213,19 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { stats.last_snr = (int16_t)(radio_driver.getLastSNR() * 4); stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups(); stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups(); + stats.total_rx_air_time_secs = getReceiveAirTime() / 1000; memcpy(&reply_data[4], &stats, sizeof(stats)); return 4 + sizeof(stats); // reply_len } case REQ_TYPE_GET_TELEMETRY_DATA: { + uint8_t perm_mask = ~(payload[1]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions + telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); // query other sensors -- target specific - sensors.querySensors(sender->is_admin ? 0xFF : 0x00, telemetry); + sensors.querySensors((sender->is_admin ? 0xFF : 0x00) & perm_mask, telemetry); uint8_t tlen = telemetry.getSize(); memcpy(&reply_data[4], telemetry.getBuffer(), tlen); @@ -677,7 +681,7 @@ public: _prefs.cr = LORA_CR; _prefs.tx_power_dbm = LORA_TX_POWER; _prefs.advert_interval = 1; // default to 2 minutes for NEW installs - _prefs.flood_advert_interval = 3; // 3 hours + _prefs.flood_advert_interval = 12; // 12 hours _prefs.flood_max = 64; _prefs.interference_threshold = 0; // disabled } @@ -699,8 +703,8 @@ public: const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; } const char* getRole() override { return FIRMWARE_ROLE; } const char* getNodeName() { return _prefs.node_name; } - NodePrefs* getNodePrefs() { - return &_prefs; + NodePrefs* getNodePrefs() { + return &_prefs; } void savePrefs() override { @@ -807,7 +811,7 @@ public: *dp = 0; // null terminator } - const uint8_t* getSelfIdPubKey() override { return self_id.pub_key; } + mesh::LocalIdentity& getSelfId() override { return self_id; } void clearStats() override { radio_driver.resetStats(); @@ -874,7 +878,7 @@ void halt() { while (1) ; } -static char command[80]; +static char command[160]; void setup() { Serial.begin(115200); diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 9a416835..712d02a5 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -298,7 +298,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { // uint32_t now = getRTCClock()->getCurrentTimeUnique(); // memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag') - + switch (payload[0]) { case REQ_TYPE_GET_STATUS: { ServerStats stats; @@ -326,10 +326,12 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { } case REQ_TYPE_GET_TELEMETRY_DATA: { + uint8_t perm_mask = ~(payload[1]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions + telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); // query other sensors -- target specific - sensors.querySensors(sender->permission == RoomPermission::ADMIN ? 0xFF : 0x00, telemetry); + sensors.querySensors((sender->permission == RoomPermission::ADMIN ? 0xFF : 0x00) & perm_mask, telemetry); uint8_t tlen = telemetry.getSize(); memcpy(&reply_data[4], telemetry.getBuffer(), tlen); @@ -744,9 +746,9 @@ public: _prefs.tx_power_dbm = LORA_TX_POWER; _prefs.disable_fwd = 1; _prefs.advert_interval = 1; // default to 2 minutes for NEW installs - _prefs.flood_advert_interval = 3; // 3 hours + _prefs.flood_advert_interval = 12; // 12 hours _prefs.flood_max = 64; - _prefs.interference_threshold = 0; // disabled + _prefs.interference_threshold = 0; // disabled #ifdef ROOM_PASSWORD StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password)); #endif @@ -776,8 +778,8 @@ public: const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; } const char* getRole() override { return FIRMWARE_ROLE; } const char* getNodeName() { return _prefs.node_name; } - NodePrefs* getNodePrefs() { - return &_prefs; + NodePrefs* getNodePrefs() { + return &_prefs; } void savePrefs() override { @@ -861,7 +863,7 @@ public: strcpy(reply, "not supported"); } - const uint8_t* getSelfIdPubKey() override { return self_id.pub_key; } + mesh::LocalIdentity& getSelfId() override { return self_id; } void clearStats() override { radio_driver.resetStats(); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 0816af72..ce36e0c2 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -244,10 +244,12 @@ uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag') if (req_type == REQ_TYPE_GET_TELEMETRY_DATA) { // allow all + uint8_t perm_mask = ~(payload[0]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions + telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); // query other sensors -- target specific - sensors.querySensors(0xFF, telemetry); // allow all telemetry permissions for admin or guest + sensors.querySensors(0xFF & perm_mask, telemetry); // allow all telemetry permissions for admin or guest // TODO: let requester know permissions they have: telemetry.addPresence(TELEM_CHANNEL_SELF, perms); uint8_t tlen = telemetry.getSize(); @@ -545,7 +547,26 @@ void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* r Serial.printf("\n"); } reply[0] = 0; - } else { + } else if (memcmp(command, "io ", 2) == 0) { // io {value}: write, io: read + if (command[2] == ' ') { // it's a write + uint32_t val; + uint32_t g = board.getGpio(); + if (command[3] == 'r') { // reset bits + sscanf(&command[4], "%x", &val); + val = g & ~val; + } else if (command[3] == 's') { // set bits + sscanf(&command[4], "%x", &val); + val |= g; + } else if (command[3] == 't') { // toggle bits + sscanf(&command[4], "%x", &val); + val ^= g; + } else { // set value + sscanf(&command[3], "%x", &val); + } + board.setGpio(val); + } + sprintf(reply, "%x", board.getGpio()); + } else{ _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands } } diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 8f6e3bc3..0d87617b 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -88,7 +88,7 @@ public: void formatNeighborsReply(char *reply) override { strcpy(reply, "not supported"); } - const uint8_t* getSelfIdPubKey() override { return self_id.pub_key; } + mesh::LocalIdentity& getSelfId() override { return self_id; } void clearStats() override { } void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override; diff --git a/examples/simple_sensor/main.cpp b/examples/simple_sensor/main.cpp index c9e282a2..2dacd1b4 100644 --- a/examples/simple_sensor/main.cpp +++ b/examples/simple_sensor/main.cpp @@ -50,7 +50,7 @@ void halt() { while (1) ; } -static char command[120]; +static char command[160]; void setup() { Serial.begin(115200); diff --git a/platformio.ini b/platformio.ini index cd1c21ad..e935d77e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -47,6 +47,7 @@ build_src_filter = +<*.cpp> + + + + ; ----------------- ESP32 --------------------- @@ -67,7 +68,7 @@ lib_deps = ; esp32c6 uses arduino framework 3.x [esp32c6_base] extends = esp32_base -platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.21/platform-espressif32.zip ; ----------------- NRF52 --------------------- @@ -102,3 +103,33 @@ build_src_filter = ${arduino_base.build_src_filter} + lib_deps = ${arduino_base.lib_deps} file://arch/stm32/Adafruit_LittleFS_stm32 + +[sensor_base] +build_flags = + -D ENV_INCLUDE_GPS=1 + -D ENV_INCLUDE_AHTX0=1 + -D ENV_INCLUDE_BME280=1 + -D ENV_INCLUDE_BMP280=1 + -D ENV_INCLUDE_SHTC3=1 + -D ENV_INCLUDE_SHT4X=1 + -D ENV_INCLUDE_LPS22HB=1 + -D ENV_INCLUDE_INA3221=1 + -D ENV_INCLUDE_INA219=1 + -D ENV_INCLUDE_INA226=1 + -D ENV_INCLUDE_INA260=1 + -D ENV_INCLUDE_MLX90614=1 + -D ENV_INCLUDE_VL53L0X=1 +lib_deps = + adafruit/Adafruit INA3221 Library @ ^1.0.1 + adafruit/Adafruit INA219 @ ^1.2.3 + robtillaart/INA226 @ ^0.6.4 + adafruit/Adafruit INA260 Library @ ^1.5.3 + adafruit/Adafruit AHTX0 @ ^2.0.5 + adafruit/Adafruit BME280 Library @ ^2.3.0 + adafruit/Adafruit BMP280 Library @ ^2.6.8 + adafruit/Adafruit SHTC3 Library @ ^1.0.1 + sensirion/Sensirion I2C SHT4x @ ^1.1.2 + arduino-libraries/Arduino_LPS22HB @ ^1.0.2 + adafruit/Adafruit MLX90614 Library @ ^2.1.5 + adafruit/Adafruit_VL53L0X @ ^1.2.4 + stevemarple/MicroNMEA @ ^2.0.6 diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 7f39dc49..0a154985 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -159,6 +159,7 @@ void Dispatcher::checkRecv() { pkt->_snr = _radio->getLastSNR() * 4.0f; score = _radio->packetScore(_radio->getLastSNR(), len); air_time = _radio->getEstAirtimeFor(len); + rx_air_time += air_time; } } } @@ -169,9 +170,9 @@ void Dispatcher::checkRecv() { if (pkt) { #if MESH_PACKET_LOGGING Serial.print(getLogDateTime()); - Serial.printf(": RX, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d", + Serial.printf(": RX, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d time=%d", pkt->getRawLength(), pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len, - (int)pkt->getSNR(), (int)_radio->getLastRSSI(), (int)(score*1000)); + (int)pkt->getSNR(), (int)_radio->getLastRSSI(), (int)(score*1000), air_time); static uint8_t packet_hash[MAX_HASH_SIZE]; pkt->calculatePacketHash(packet_hash); diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 2200f81b..25a41d82 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -114,7 +114,7 @@ typedef uint32_t DispatcherAction; */ class Dispatcher { Packet* outbound; // current outbound packet - unsigned long outbound_expiry, outbound_start, total_air_time; + unsigned long outbound_expiry, outbound_start, total_air_time, rx_air_time; unsigned long next_tx_time; unsigned long cad_busy_start; unsigned long radio_nonrx_start; @@ -134,7 +134,9 @@ protected: Dispatcher(Radio& radio, MillisecondClock& ms, PacketManager& mgr) : _radio(&radio), _ms(&ms), _mgr(&mgr) { - outbound = NULL; total_air_time = 0; next_tx_time = 0; + outbound = NULL; + total_air_time = rx_air_time = 0; + next_tx_time = 0; cad_busy_start = 0; next_floor_calib_time = next_agc_reset_time = 0; _err_flags = 0; @@ -167,6 +169,7 @@ public: void sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis=0); unsigned long getTotalAirTime() const { return total_air_time; } // in milliseconds + unsigned long getReceiveAirTime() const {return rx_air_time; } uint32_t getNumSentFlood() const { return n_sent_flood; } uint32_t getNumSentDirect() const { return n_sent_direct; } uint32_t getNumRecvFlood() const { return n_recv_flood; } diff --git a/src/MeshCore.h b/src/MeshCore.h index 98134e50..d8886136 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -41,6 +41,8 @@ public: virtual void onAfterTransmit() { } virtual void reboot() = 0; virtual void powerOff() { /* no op */ } + virtual uint32_t getGpio() { return 0; } + virtual void setGpio(uint32_t values) {} virtual uint8_t getStartupReason() const = 0; virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported }; diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 476e6e8f..60366c65 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -235,9 +235,13 @@ bool BaseChatMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const ui ContactInfo& from = contacts[i]; - // NOTE: for this impl, we just replace the current 'out_path' regardless, whenever sender sends us a new out_path. + return onContactPathRecv(from, packet->path, packet->path_len, path, path_len, extra_type, extra, extra_len); +} + +bool BaseChatMesh::onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { + // NOTE: default impl, we just replace the current 'out_path' regardless, whenever sender sends us a new out_path. // FUTURE: could store multiple out_paths per contact, and try to find which is the 'best'(?) - memcpy(from.out_path, path, from.out_path_len = path_len); // store a copy of path, for sendDirect() + memcpy(from.out_path, out_path, from.out_path_len = out_path_len); // store a copy of path, for sendDirect() from.lastmod = getRTCClock()->getCurrentTime(); onContactPathUpdated(from); diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 683af852..9a4aa810 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -88,11 +88,14 @@ protected: memset(connections, 0, sizeof(connections)); } + void resetContacts() { num_contacts = 0; } + // 'UI' concepts, for sub-classes to implement virtual bool isAutoAddEnabled() const { return true; } virtual void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) = 0; virtual bool processAck(const uint8_t *data) = 0; virtual void onContactPathUpdated(const ContactInfo& contact) = 0; + virtual bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len); virtual void onMessageRecv(const ContactInfo& contact, mesh::Packet* pkt, uint32_t sender_timestamp, const char *text) = 0; virtual void onCommandDataRecv(const ContactInfo& contact, mesh::Packet* pkt, uint32_t sender_timestamp, const char *text) = 0; virtual void onSignedMessageRecv(const ContactInfo& contact, mesh::Packet* pkt, uint32_t sender_timestamp, const uint8_t *sender_prefix, const char *text) = 0; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index d62253f9..2abb4f7c 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -206,6 +206,11 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "> %d", ((uint32_t) _prefs->advert_interval) * 2); } else if (memcmp(config, "guest.password", 14) == 0) { sprintf(reply, "> %s", _prefs->guest_password); + } else if (sender_timestamp == 0 && memcmp(config, "prv.key", 7) == 0) { // from serial command line only + uint8_t prv_key[PRV_KEY_SIZE]; + int len = _callbacks->getSelfId().writeTo(prv_key, PRV_KEY_SIZE); + mesh::Utils::toHex(tmp, prv_key, len); + sprintf(reply, "> %s", tmp); } else if (memcmp(config, "name", 4) == 0) { sprintf(reply, "> %s", _prefs->node_name); } else if (memcmp(config, "repeat", 6) == 0) { @@ -233,7 +238,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "> %s", StrHelper::ftoa(_prefs->freq)); } else if (memcmp(config, "public.key", 10) == 0) { strcpy(reply, "> "); - mesh::Utils::toHex(&reply[2], _callbacks->getSelfIdPubKey(), PUB_KEY_SIZE); + mesh::Utils::toHex(&reply[2], _callbacks->getSelfId().pub_key, PUB_KEY_SIZE); } else if (memcmp(config, "role", 4) == 0) { sprintf(reply, "> %s", _callbacks->getRole()); } else { @@ -285,6 +290,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch StrHelper::strncpy(_prefs->guest_password, &config[15], sizeof(_prefs->guest_password)); savePrefs(); strcpy(reply, "OK"); + } else if (sender_timestamp == 0 && memcmp(config, "prv.key ", 8) == 0) { // from serial command line only + uint8_t prv_key[PRV_KEY_SIZE]; + bool success = mesh::Utils::fromHex(prv_key, PRV_KEY_SIZE, &config[8]); + if (success) { + _callbacks->getSelfId().readFrom(prv_key, PRV_KEY_SIZE); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error, invalid key"); + } } else if (memcmp(config, "name ", 5) == 0) { StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name)); savePrefs(); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index e2608379..92deb718 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -43,7 +43,7 @@ public: virtual void dumpLogFile() = 0; virtual void setTxPower(uint8_t power_dbm) = 0; virtual void formatNeighborsReply(char *reply) = 0; - virtual const uint8_t* getSelfIdPubKey() = 0; + virtual mesh::LocalIdentity& getSelfId() = 0; virtual void clearStats() = 0; virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0; }; @@ -53,7 +53,7 @@ class CommonCLI { NodePrefs* _prefs; CommonCLICallbacks* _callbacks; mesh::MainBoard* _board; - char tmp[80]; + char tmp[PRV_KEY_SIZE*2 + 4]; mesh::RTCClock* getRTCClock() { return _rtc; } void savePrefs(); diff --git a/src/helpers/RefCountedDigitalPin.h b/src/helpers/RefCountedDigitalPin.h index 14b67fb1..753f6c30 100644 --- a/src/helpers/RefCountedDigitalPin.h +++ b/src/helpers/RefCountedDigitalPin.h @@ -5,25 +5,25 @@ class RefCountedDigitalPin { uint8_t _pin; int8_t _claims = 0; - + uint8_t _active = 0; public: - RefCountedDigitalPin(uint8_t pin): _pin(pin) { } + RefCountedDigitalPin(uint8_t pin,uint8_t active=HIGH): _pin(pin), _active(active) { } void begin() { pinMode(_pin, OUTPUT); - digitalWrite(_pin, LOW); // initial state + digitalWrite(_pin, !_active); // initial state } void claim() { _claims++; if (_claims > 0) { - digitalWrite(_pin, HIGH); + digitalWrite(_pin, _active); } } void release() { _claims--; if (_claims == 0) { - digitalWrite(_pin, LOW); + digitalWrite(_pin, !_active); } } }; diff --git a/src/helpers/esp32/SerialBLEInterface.cpp b/src/helpers/esp32/SerialBLEInterface.cpp index 8a8710a7..1be703a8 100644 --- a/src/helpers/esp32/SerialBLEInterface.cpp +++ b/src/helpers/esp32/SerialBLEInterface.cpp @@ -83,6 +83,7 @@ void SerialBLEInterface::onConnect(BLEServer* pServer) { void SerialBLEInterface::onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param) { BLE_DEBUG_PRINTLN("onConnect(), conn_id=%d, mtu=%d", param->connect.conn_id, pServer->getPeerMTU(param->connect.conn_id)); + last_conn_id = param->connect.conn_id; } void SerialBLEInterface::onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) { @@ -143,6 +144,7 @@ void SerialBLEInterface::disable() { BLE_DEBUG_PRINTLN("SerialBLEInterface::disable"); pServer->getAdvertising()->stop(); + pServer->disconnect(last_conn_id); pService->stop(); oldDeviceConnected = deviceConnected = false; adv_restart_time = 0; diff --git a/src/helpers/esp32/SerialBLEInterface.h b/src/helpers/esp32/SerialBLEInterface.h index bf1eee09..29ad897a 100644 --- a/src/helpers/esp32/SerialBLEInterface.h +++ b/src/helpers/esp32/SerialBLEInterface.h @@ -13,6 +13,7 @@ class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLE bool deviceConnected; bool oldDeviceConnected; bool _isEnabled; + uint16_t last_conn_id; uint32_t _pin_code; unsigned long _last_write; unsigned long adv_restart_time; @@ -56,6 +57,7 @@ public: adv_restart_time = 0; _isEnabled = false; _last_write = 0; + last_conn_id = 0; send_queue_len = recv_queue_len = 0; } diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index a8c11d97..170a7331 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -115,6 +115,20 @@ void SerialBLEInterface::enable() { void SerialBLEInterface::disable() { _isEnabled = false; BLE_DEBUG_PRINTLN("SerialBLEInterface::disable"); + +#ifdef RAK_BOARD + Bluefruit.disconnect(Bluefruit.connHandle()); +#else + uint16_t conn_id; + if (Bluefruit.getConnectedHandles(&conn_id, 1) > 0) { + Bluefruit.disconnect(conn_id); + } +#endif + + Bluefruit.Advertising.restartOnDisconnect(false); + Bluefruit.Advertising.stop(); + Bluefruit.Advertising.clearData(); + stopAdv(); } diff --git a/src/helpers/nrf52/T114Board.h b/src/helpers/nrf52/T114Board.h index 154ccb22..cd58134d 100644 --- a/src/helpers/nrf52/T114Board.h +++ b/src/helpers/nrf52/T114Board.h @@ -60,5 +60,9 @@ public: NVIC_SystemReset(); } + void powerOff() override { + sd_power_system_off(); + } + bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/src/helpers/nrf52/TechoBoard.h b/src/helpers/nrf52/TechoBoard.h index c8ef7006..2c05c4ed 100644 --- a/src/helpers/nrf52/TechoBoard.h +++ b/src/helpers/nrf52/TechoBoard.h @@ -43,6 +43,25 @@ public: return "LilyGo T-Echo"; } + void powerOff() override { + #ifdef LED_RED + digitalWrite(LED_RED, LOW); + #endif + #ifdef LED_GREEN + digitalWrite(LED_GREEN, LOW); + #endif + #ifdef LED_BLUE + digitalWrite(LED_BLUE, LOW); + #endif + #ifdef DISP_BACKLIGHT + digitalWrite(DISP_BACKLIGHT, LOW); + #endif + #ifdef PIN_PWR_EN + digitalWrite(PIN_PWR_EN, LOW); + #endif + sd_power_system_off(); + } + void reboot() override { NVIC_SystemReset(); } diff --git a/src/helpers/nrf52/ThinkNodeM1Board.h b/src/helpers/nrf52/ThinkNodeM1Board.h index 97334bd3..c1ffcbbf 100644 --- a/src/helpers/nrf52/ThinkNodeM1Board.h +++ b/src/helpers/nrf52/ThinkNodeM1Board.h @@ -55,4 +55,8 @@ public: void reboot() override { NVIC_SystemReset(); } + + void powerOff() override { + sd_power_system_off(); + } }; diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 0f3289b8..f444b67b 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -65,6 +65,14 @@ static Adafruit_INA219 INA219(TELEM_INA219_ADDRESS); static Adafruit_INA260 INA260; #endif +#if ENV_INCLUDE_INA226 +#define TELEM_INA226_ADDRESS 0x44 +#define TELEM_INA226_SHUNT_VALUE 0.100 +#define TELEM_INA226_MAX_AMP 0.8 +#include +static INA226 INA226(TELEM_INA226_ADDRESS); +#endif + #if ENV_INCLUDE_MLX90614 #define TELEM_MLX90614_ADDRESS 0x5A // MLX90614 IR temperature sensor I2C address #include @@ -202,6 +210,17 @@ bool EnvironmentSensorManager::begin() { } #endif + #if ENV_INCLUDE_INA226 + if (INA226.begin()) { + MESH_DEBUG_PRINTLN("Found INA226 at address: %02X", TELEM_INA226_ADDRESS); + INA226.setMaxCurrentShunt(TELEM_INA226_MAX_AMP, TELEM_INA226_SHUNT_VALUE); + INA226_initialized = true; + } else { + INA226_initialized = false; + MESH_DEBUG_PRINTLN("INA226 was not found at I2C address %02X", TELEM_INA226_ADDRESS); + } + #endif + #if ENV_INCLUDE_MLX90614 if (MLX90614.begin(TELEM_MLX90614_ADDRESS, TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found MLX90614 at address: %02X", TELEM_MLX90614_ADDRESS); @@ -323,6 +342,15 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen } #endif + #if ENV_INCLUDE_INA226 + if (INA226_initialized) { + telemetry.addVoltage(next_available_channel, INA226.getBusVoltage()); + telemetry.addCurrent(next_available_channel, INA226.getCurrent_mA() / 1000.0); + telemetry.addPower(next_available_channel, INA226.getPower_mW() / 1000.0); + next_available_channel++; + } + #endif + #if ENV_INCLUDE_MLX90614 if (MLX90614_initialized) { telemetry.addTemperature(TELEM_CHANNEL_SELF, MLX90614.readObjectTempC()); diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index bb0fd2b9..3302d6f6 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -14,6 +14,7 @@ protected: bool INA3221_initialized = false; bool INA219_initialized = false; bool INA260_initialized = false; + bool INA226_initialized = false; bool SHTC3_initialized = false; bool LPS22HB_initialized = false; bool MLX90614_initialized = false; diff --git a/src/helpers/ui/DisplayDriver.h b/src/helpers/ui/DisplayDriver.h index 2d8b69c1..d81d99fb 100644 --- a/src/helpers/ui/DisplayDriver.h +++ b/src/helpers/ui/DisplayDriver.h @@ -21,9 +21,15 @@ public: virtual void setColor(Color c) = 0; virtual void setCursor(int x, int y) = 0; virtual void print(const char* str) = 0; + virtual void printWordWrap(const char* str, int max_width) { print(str); } // fallback to basic print() if no override virtual void fillRect(int x, int y, int w, int h) = 0; virtual void drawRect(int x, int y, int w, int h) = 0; virtual void drawXbm(int x, int y, const uint8_t* bits, int w, int h) = 0; virtual uint16_t getTextWidth(const char* str) = 0; + virtual void drawTextCentered(int mid_x, int y, const char* str) { // helper method (override to optimise) + int w = getTextWidth(str); + setCursor(mid_x - w/2, y); + print(str); + } virtual void endFrame() = 0; }; diff --git a/src/helpers/ui/E213Display.cpp b/src/helpers/ui/E213Display.cpp index 92bf37fb..a0e71f31 100644 --- a/src/helpers/ui/E213Display.cpp +++ b/src/helpers/ui/E213Display.cpp @@ -2,20 +2,58 @@ #include "../../MeshCore.h" +BaseDisplay* E213Display::detectEInk() +{ + // Test 1: Logic of BUSY pin + + // Determines controller IC manufacturer + // Fitipower: busy when LOW + // Solomon Systech: busy when HIGH + + // Force display BUSY by holding reset pin active + pinMode(DISP_RST, OUTPUT); + digitalWrite(DISP_RST, LOW); + + delay(10); + + // Read whether pin is HIGH or LOW while busy + pinMode(DISP_BUSY, INPUT); + bool busyLogic = digitalRead(DISP_BUSY); + + // Test complete. Release pin + pinMode(DISP_RST, INPUT); + + if (busyLogic == LOW) { +#ifdef VISION_MASTER_E213 + return new EInkDisplay_VisionMasterE213 ; +#else + return new EInkDisplay_WirelessPaperV1_1 ; +#endif + } else {// busy HIGH +#ifdef VISION_MASTER_E213 + return new EInkDisplay_VisionMasterE213V1_1 ; +#else + return new EInkDisplay_WirelessPaperV1_1_1 ; +#endif + } +} + bool E213Display::begin() { if (_init) return true; powerOn(); - display.begin(); - + if(display==NULL) { + display = detectEInk(); + } + display->begin(); // Set to landscape mode rotated 180 degrees - display.setRotation(3); + display->setRotation(3); _init = true; _isOn = true; clear(); - display.fastmodeOn(); // Enable fast mode for quicker (partial) updates + display->fastmodeOn(); // Enable fast mode for quicker (partial) updates return true; } @@ -23,15 +61,23 @@ bool E213Display::begin() { void E213Display::powerOn() { #ifdef PIN_VEXT_EN pinMode(PIN_VEXT_EN, OUTPUT); +#ifdef PIN_VEXT_EN_ACTIVE + digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); +#else digitalWrite(PIN_VEXT_EN, LOW); // Active low +#endif delay(50); // Allow power to stabilize #endif } void E213Display::powerOff() { #ifdef PIN_VEXT_EN +#ifdef PIN_VEXT_EN_ACTIVE + digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); +#else digitalWrite(PIN_VEXT_EN, HIGH); // Turn off power #endif +#endif } void E213Display::turnOn() { @@ -46,21 +92,23 @@ void E213Display::turnOff() { } void E213Display::clear() { - display.clear(); + display->clear(); + } void E213Display::startFrame(Color bkg) { // Fill screen with white first to ensure clean background - display.fillRect(0, 0, width(), height(), WHITE); + display->fillRect(0, 0, width(), height(), WHITE); + if (bkg == LIGHT) { // Fill with black if light background requested (inverted for e-ink) - display.fillRect(0, 0, width(), height(), BLACK); + display->fillRect(0, 0, width(), height(), BLACK); } } void E213Display::setTextSize(int sz) { // The library handles text size internally - display.setTextSize(sz); + display->setTextSize(sz); } void E213Display::setColor(Color c) { @@ -68,19 +116,19 @@ void E213Display::setColor(Color c) { } void E213Display::setCursor(int x, int y) { - display.setCursor(x, y); + display->setCursor(x, y); } void E213Display::print(const char *str) { - display.print(str); + display->print(str); } void E213Display::fillRect(int x, int y, int w, int h) { - display.fillRect(x, y, w, h, BLACK); + display->fillRect(x, y, w, h, BLACK); } void E213Display::drawRect(int x, int y, int w, int h) { - display.drawRect(x, y, w, h, BLACK); + display->drawRect(x, y, w, h, BLACK); } void E213Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { @@ -98,7 +146,7 @@ void E213Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { // If the bit is set, draw the pixel if (bitSet) { - display.drawPixel(x + bx, y + by, BLACK); + display->drawPixel(x + bx, y + by, BLACK); } } } @@ -107,10 +155,10 @@ void E213Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { uint16_t E213Display::getTextWidth(const char *str) { int16_t x1, y1; uint16_t w, h; - display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); + display->getTextBounds(str, 0, 0, &x1, &y1, &w, &h); return w; } void E213Display::endFrame() { - display.update(); + display->update(); } diff --git a/src/helpers/ui/E213Display.h b/src/helpers/ui/E213Display.h index 330a2b6d..657bfb4c 100644 --- a/src/helpers/ui/E213Display.h +++ b/src/helpers/ui/E213Display.h @@ -8,13 +8,17 @@ // Display driver for E213 e-ink display class E213Display : public DisplayDriver { - EInkDisplay_VisionMasterE213 display; + BaseDisplay* display=NULL; bool _init = false; bool _isOn = false; public: E213Display() : DisplayDriver(250, 122) {} - + ~E213Display(){ + if(display!=NULL) { + delete display; + } + } bool begin(); bool isOn() override { return _isOn; } void turnOn() override; @@ -32,6 +36,7 @@ public: void endFrame() override; private: + BaseDisplay* detectEInk(); void powerOn(); void powerOff(); }; \ No newline at end of file diff --git a/src/helpers/ui/E290Display.cpp b/src/helpers/ui/E290Display.cpp new file mode 100644 index 00000000..23ff2d95 --- /dev/null +++ b/src/helpers/ui/E290Display.cpp @@ -0,0 +1,116 @@ +#include "E290Display.h" + +#include "../../MeshCore.h" + +bool E290Display::begin() { + if (_init) return true; + + powerOn(); + display.begin(); + + // Set to landscape mode rotated 180 degrees + display.setRotation(3); + + _init = true; + _isOn = true; + + clear(); + display.fastmodeOn(); // Enable fast mode for quicker (partial) updates + + return true; +} + +void E290Display::powerOn() { +#ifdef PIN_VEXT_EN + pinMode(PIN_VEXT_EN, OUTPUT); + digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); + delay(50); // Allow power to stabilize +#endif +} + +void E290Display::powerOff() { +#ifdef PIN_VEXT_EN + digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); // Turn off power +#endif +} + +void E290Display::turnOn() { + if (!_init) begin(); + powerOn(); + _isOn = true; +} + +void E290Display::turnOff() { + powerOff(); + _isOn = false; +} + +void E290Display::clear() { + display.clear(); +} + +void E290Display::startFrame(Color bkg) { + // Fill screen with white first to ensure clean background + display.fillRect(0, 0, width(), height(), WHITE); + if (bkg == LIGHT) { + // Fill with black if light background requested (inverted for e-ink) + display.fillRect(0, 0, width(), height(), BLACK); + } +} + +void E290Display::setTextSize(int sz) { + // The library handles text size internally + display.setTextSize(sz); +} + +void E290Display::setColor(Color c) { + // implemented in individual display methods +} + +void E290Display::setCursor(int x, int y) { + display.setCursor(x, y); +} + +void E290Display::print(const char *str) { + display.print(str); +} + +void E290Display::fillRect(int x, int y, int w, int h) { + display.fillRect(x, y, w, h, BLACK); +} + +void E290Display::drawRect(int x, int y, int w, int h) { + display.drawRect(x, y, w, h, BLACK); +} + +void E290Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { + // Width in bytes for bitmap processing + uint16_t widthInBytes = (w + 7) / 8; + + // Process the bitmap row by row + for (int by = 0; by < h; by++) { + // Scan across the row bit by bit + for (int bx = 0; bx < w; bx++) { + // Get the current bit using MSB ordering (like GxEPDDisplay) + uint16_t byteOffset = (by * widthInBytes) + (bx / 8); + uint8_t bitMask = 0x80 >> (bx & 7); + bool bitSet = bits[byteOffset] & bitMask; + + // If the bit is set, draw the pixel + if (bitSet) { + display.drawPixel(x + bx, y + by, BLACK); + } + } + } +} + +uint16_t E290Display::getTextWidth(const char *str) { + int16_t x1, y1; + uint16_t w, h; + display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); + return w; +} + +void E290Display::endFrame() { + display.update(); +} diff --git a/src/helpers/ui/E290Display.h b/src/helpers/ui/E290Display.h new file mode 100644 index 00000000..16f45382 --- /dev/null +++ b/src/helpers/ui/E290Display.h @@ -0,0 +1,37 @@ +#pragma once + +#include "DisplayDriver.h" + +#include +#include +#include + +// Display driver for E290 e-ink display +class E290Display : public DisplayDriver { + EInkDisplay_VisionMasterE290 display; + bool _init = false; + bool _isOn = false; + +public: + E290Display() : DisplayDriver(296, 128) {} + + bool begin(); + bool isOn() override { return _isOn; } + void turnOn() override; + void turnOff() override; + void clear() override; + void startFrame(Color bkg = DARK) override; + void setTextSize(int sz) override; + void setColor(Color c) override; + void setCursor(int x, int y) override; + void print(const char *str) override; + void fillRect(int x, int y, int w, int h) override; + void drawRect(int x, int y, int w, int h) override; + void drawXbm(int x, int y, const uint8_t *bits, int w, int h) override; + uint16_t getTextWidth(const char *str) override; + void endFrame() override; + +private: + void powerOn(); + void powerOff(); +}; \ No newline at end of file diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index 875e29ac..ace25460 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -29,8 +29,8 @@ void GxEPDDisplay::turnOn() { if (!_init) begin(); #if DISP_BACKLIGHT digitalWrite(DISP_BACKLIGHT, HIGH); - _isOn = true; #endif + _isOn = true; } void GxEPDDisplay::turnOff() { @@ -47,6 +47,7 @@ void GxEPDDisplay::clear() { void GxEPDDisplay::startFrame(Color bkg) { display.fillScreen(GxEPD_WHITE); + display.setTextColor(_curr_color = GxEPD_BLACK); } void GxEPDDisplay::setTextSize(int sz) { @@ -67,7 +68,12 @@ void GxEPDDisplay::setTextSize(int sz) { } void GxEPDDisplay::setColor(Color c) { - display.setTextColor(GxEPD_BLACK); + // colours need to be inverted for epaper displays + if (c == DARK) { + display.setTextColor(_curr_color = GxEPD_WHITE); + } else { + display.setTextColor(_curr_color = GxEPD_BLACK); + } } void GxEPDDisplay::setCursor(int x, int y) { @@ -79,11 +85,11 @@ void GxEPDDisplay::print(const char* str) { } void GxEPDDisplay::fillRect(int x, int y, int w, int h) { - display.fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, GxEPD_BLACK); + display.fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _curr_color); } void GxEPDDisplay::drawRect(int x, int y, int w, int h) { - display.drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, GxEPD_BLACK); + display.drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _curr_color); } void GxEPDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { @@ -116,7 +122,7 @@ void GxEPDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { // If the bit is set, draw a block of pixels if (bitSet) { // Draw the block as a filled rectangle - display.fillRect(x1, y1, block_w, block_h, GxEPD_BLACK); + display.fillRect(x1, y1, block_w, block_h, _curr_color); } } } @@ -126,7 +132,7 @@ uint16_t GxEPDDisplay::getTextWidth(const char* str) { int16_t x1, y1; uint16_t w, h; display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); - return w / SCALE_X; + return ceil((w + 1) / SCALE_X); } void GxEPDDisplay::endFrame() { diff --git a/src/helpers/ui/GxEPDDisplay.h b/src/helpers/ui/GxEPDDisplay.h index ec2bcec0..49746dee 100644 --- a/src/helpers/ui/GxEPDDisplay.h +++ b/src/helpers/ui/GxEPDDisplay.h @@ -28,6 +28,7 @@ class GxEPDDisplay : public DisplayDriver { GxEPD2_BW display; bool _init = false; bool _isOn = false; + uint16_t _curr_color; public: // there is a margin in y... diff --git a/src/helpers/ui/MomentaryButton.cpp b/src/helpers/ui/MomentaryButton.cpp new file mode 100644 index 00000000..9ddf1327 --- /dev/null +++ b/src/helpers/ui/MomentaryButton.cpp @@ -0,0 +1,75 @@ +#include "MomentaryButton.h" + +MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse, bool pulldownup) { + _pin = pin; + _reverse = reverse; + _pull = pulldownup; + down_at = 0; + prev = _reverse ? HIGH : LOW; + cancel = 0; + _long_millis = long_press_millis; +} + +void MomentaryButton::begin() { + if (_pin >= 0) { + pinMode(_pin, _pull ? (_reverse ? INPUT_PULLUP : INPUT_PULLDOWN) : INPUT); + } +} + +bool MomentaryButton::isPressed() const { + return isPressed(digitalRead(_pin)); +} + +void MomentaryButton::cancelClick() { + cancel = 1; +} + +bool MomentaryButton::isPressed(int level) const { + if (_reverse) { + return level == LOW; + } else { + return level != LOW; + } +} + +int MomentaryButton::check(bool repeat_click) { + if (_pin < 0) return BUTTON_EVENT_NONE; + + int event = BUTTON_EVENT_NONE; + int btn = digitalRead(_pin); + if (btn != prev) { + if (isPressed(btn)) { + down_at = millis(); + } else { + // button UP + if (_long_millis > 0) { + if (down_at > 0 && (unsigned long)(millis() - down_at) < _long_millis) { // only a CLICK if still within the long_press millis + event = BUTTON_EVENT_CLICK; + } + } else { + event = BUTTON_EVENT_CLICK; // any UP results in CLICK event when NOT using long_press feature + } + if (event == BUTTON_EVENT_CLICK && cancel) { + event = BUTTON_EVENT_NONE; + } + down_at = 0; + } + prev = btn; + } + if (!isPressed(btn) && cancel) { // always clear the pending 'cancel' once button is back in UP state + cancel = 0; + } + + if (_long_millis > 0 && down_at > 0 && (unsigned long)(millis() - down_at) >= _long_millis) { + event = BUTTON_EVENT_LONG_PRESS; + down_at = 0; + } + if (down_at > 0 && repeat_click) { + unsigned long diff = (unsigned long)(millis() - down_at); + if (diff >= 700) { + event = BUTTON_EVENT_CLICK; // wait 700 millis before repeating the click events + } + } + + return event; +} \ No newline at end of file diff --git a/src/helpers/ui/MomentaryButton.h b/src/helpers/ui/MomentaryButton.h new file mode 100644 index 00000000..0bcc776c --- /dev/null +++ b/src/helpers/ui/MomentaryButton.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#define BUTTON_EVENT_NONE 0 +#define BUTTON_EVENT_CLICK 1 +#define BUTTON_EVENT_LONG_PRESS 2 + +class MomentaryButton { + int8_t _pin; + int8_t prev, cancel; + bool _reverse, _pull; + int _long_millis; + unsigned long down_at; + + bool isPressed(int level) const; + +public: + MomentaryButton(int8_t pin, int long_press_mills=0, bool reverse=false, bool pulldownup=false); + void begin(); + int check(bool repeat_click=false); // returns one of BUTTON_EVENT_* + void cancelClick(); // suppress next BUTTON_EVENT_CLICK (if already in DOWN state) + uint8_t getPin() { return _pin; } + bool isPressed() const; +}; diff --git a/src/helpers/ui/SSD1306Display.cpp b/src/helpers/ui/SSD1306Display.cpp index 8d977db0..c9da0cf8 100644 --- a/src/helpers/ui/SSD1306Display.cpp +++ b/src/helpers/ui/SSD1306Display.cpp @@ -7,6 +7,9 @@ bool SSD1306Display::i2c_probe(TwoWire& wire, uint8_t addr) { } bool SSD1306Display::begin() { + #ifdef DISPLAY_ROTATION + display.setRotation(DISPLAY_ROTATION); + #endif return display.begin(SSD1306_SWITCHCAPVCC, DISPLAY_ADDRESS, true, false) && i2c_probe(Wire, DISPLAY_ADDRESS); } diff --git a/src/helpers/ui/ST7789Display.cpp b/src/helpers/ui/ST7789Display.cpp index 9e71e6bd..185ecc0e 100644 --- a/src/helpers/ui/ST7789Display.cpp +++ b/src/helpers/ui/ST7789Display.cpp @@ -62,6 +62,9 @@ void ST7789Display::clear() { void ST7789Display::startFrame(Color bkg) { display.clear(); + _color = ST77XX_WHITE; + display.setRGB(_color); + display.setFont(ArialMT_Plain_16); } void ST7789Display::setTextSize(int sz) { @@ -81,7 +84,9 @@ void ST7789Display::setColor(Color c) { switch (c) { case DisplayDriver::DARK : _color = ST77XX_BLACK; + display.setColor(OLEDDISPLAY_COLOR::BLACK); break; +#if 0 case DisplayDriver::LIGHT : _color = ST77XX_WHITE; break; @@ -100,8 +105,10 @@ void ST7789Display::setColor(Color c) { case DisplayDriver::ORANGE : _color = ST77XX_ORANGE; break; +#endif default: _color = ST77XX_WHITE; + display.setColor(OLEDDISPLAY_COLOR::WHITE); break; } display.setRGB(_color); @@ -116,6 +123,10 @@ void ST7789Display::print(const char* str) { display.drawString(_x, _y, str); } +void ST7789Display::printWordWrap(const char* str, int max_width) { + display.drawStringMaxWidth(_x, _y, max_width*SCALE_X, str); +} + void ST7789Display::fillRect(int x, int y, int w, int h) { display.fillRect(x*SCALE_X + X_OFFSET, y*SCALE_Y + Y_OFFSET, w*SCALE_X, h*SCALE_Y); } diff --git a/src/helpers/ui/ST7789Display.h b/src/helpers/ui/ST7789Display.h index b267a2cb..8056de81 100644 --- a/src/helpers/ui/ST7789Display.h +++ b/src/helpers/ui/ST7789Display.h @@ -27,6 +27,7 @@ public: void setColor(Color c) override; void setCursor(int x, int y) override; void print(const char* str) override; + void printWordWrap(const char* str, int max_width) override; void fillRect(int x, int y, int w, int h) override; void drawRect(int x, int y, int w, int h) override; void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override; diff --git a/src/helpers/ui/UIScreen.h b/src/helpers/ui/UIScreen.h new file mode 100644 index 00000000..6faa591a --- /dev/null +++ b/src/helpers/ui/UIScreen.h @@ -0,0 +1,21 @@ +#pragma once + +#include "DisplayDriver.h" + +#define KEY_LEFT 0xB4 +#define KEY_UP 0xB5 +#define KEY_DOWN 0xB6 +#define KEY_RIGHT 0xB7 +#define KEY_SELECT 10 +#define KEY_ENTER 13 +#define KEY_BACK 27 // Esc + +class UIScreen { +protected: + UIScreen() { } +public: + virtual int render(DisplayDriver& display) =0; // return value is number of millis until next render + virtual bool handleInput(char c) { return false; } + virtual void poll() { } +}; + diff --git a/variants/generic_espnow/platformio.ini b/variants/generic_espnow/platformio.ini index dbc902f0..cf3e4c94 100644 --- a/variants/generic_espnow/platformio.ini +++ b/variants/generic_espnow/platformio.ini @@ -60,7 +60,7 @@ build_flags = ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 ; NOTE: DO NOT ENABLE --> -D ESPNOW_DEBUG_LOGGING=1 build_src_filter = ${Generic_ESPNOW.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${Generic_ESPNOW.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/heltec_ct62/HT-CT62Board.h b/variants/heltec_ct62/HT-CT62Board.h index e5a627b8..d26e7b26 100644 --- a/variants/heltec_ct62/HT-CT62Board.h +++ b/variants/heltec_ct62/HT-CT62Board.h @@ -8,9 +8,35 @@ #include class Heltec_CT62_Board : public ESP32Board { -public: + uint32_t gpio_state = 0; -uint16_t getBattMilliVolts() override { +public: + void begin() { + ESP32Board::begin(); +#if defined(PIN_BOARD_RELAY_CH1) && defined(PIN_BOARD_RELAY_CH2) + pinMode(PIN_BOARD_RELAY_CH1, OUTPUT); + pinMode(PIN_BOARD_RELAY_CH2, OUTPUT); +#endif +#if defined(PIN_BOARD_DIGITAL_IN) + pinMode(PIN_BOARD_DIGITAL_IN, INPUT); +#endif + } + uint32_t getGpio() override { +#if defined(PIN_BOARD_DIGITAL_IN) + return gpio_state | (digitalRead(PIN_BOARD_DIGITAL_IN) ? 1 : 0); +#else + return 0; +#endif + } + void setGpio(uint32_t values) override { +#if defined(PIN_BOARD_RELAY_CH1) && defined(PIN_BOARD_RELAY_CH2) + gpio_state = values; + digitalWrite(PIN_BOARD_RELAY_CH1, values & 2); + digitalWrite(PIN_BOARD_RELAY_CH2, values & 4); +#endif + } + + uint16_t getBattMilliVolts() override { #ifdef PIN_VBAT_READ analogReadResolution(12); // ESP32-C3 ADC is 12-bit - 3.3/4096 (ref voltage/max counts) uint32_t raw = 0; diff --git a/variants/heltec_ct62/platformio.ini b/variants/heltec_ct62/platformio.ini index ba23a5a6..9721d037 100644 --- a/variants/heltec_ct62/platformio.ini +++ b/variants/heltec_ct62/platformio.ini @@ -61,7 +61,7 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_ct62.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${Heltec_ct62.lib_deps} ${esp32_ota.lib_deps} @@ -80,9 +80,30 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_ct62.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + lib_deps = ${Heltec_ct62.lib_deps} ${esp32_ota.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:Heltec_ct62_sensor] +extends = Heltec_ct62 +build_flags = + ${Heltec_ct62.build_flags} + -D ADVERT_NAME='"HT-CT62 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D PIN_BOARD_SDA=-1 + -D PIN_BOARD_SCL=-1 + -D PIN_BOARD_RELAY_CH1=0 + -D PIN_BOARD_RELAY_CH2=1 + -D PIN_BOARD_DIGITAL_IN=19 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_ct62.build_src_filter} + +<../examples/simple_sensor> +lib_deps = + ${Heltec_ct62.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/heltec_mesh_solar/MeshSolarBoard.cpp b/variants/heltec_mesh_solar/MeshSolarBoard.cpp new file mode 100644 index 00000000..54929cd1 --- /dev/null +++ b/variants/heltec_mesh_solar/MeshSolarBoard.cpp @@ -0,0 +1,77 @@ +#include +#include "MeshSolarBoard.h" + +#include +#include + +static BLEDfu bledfu; + +static void connect_callback(uint16_t conn_handle) +{ + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) +{ + (void)conn_handle; + (void)reason; + + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + +void MeshSolarBoard::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + + meshSolarStart(); + +#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) + Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL); +#endif + + Wire.begin(); +} + +bool MeshSolarBoard::startOTAUpdate(const char* id, char reply[]) { + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 + Bluefruit.setTxPower(4); + // Set the BLE device name + Bluefruit.setName("MESH_SOLAR_OTA"); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // To be consistent OTA DFU should be added first if it exists + bledfu.begin(); + + // Set up and start advertising + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + 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 + + strcpy(reply, "OK - started"); + return true; +} diff --git a/variants/heltec_mesh_solar/MeshSolarBoard.h b/variants/heltec_mesh_solar/MeshSolarBoard.h new file mode 100644 index 00000000..3bec144f --- /dev/null +++ b/variants/heltec_mesh_solar/MeshSolarBoard.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +#ifdef HELTEC_MESH_SOLAR +#include "meshSolarApp.h" +#endif + +// LoRa radio module pins for Heltec T114 +#define P_LORA_DIO_1 20 +#define P_LORA_NSS 24 +#define P_LORA_RESET 25 +#define P_LORA_BUSY 17 +#define P_LORA_SCLK 19 +#define P_LORA_MISO 23 +#define P_LORA_MOSI 22 + +#define SX126X_DIO2_AS_RF_SWITCH true +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + + +class MeshSolarBoard : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + void begin(); + uint8_t getStartupReason() const override { return startup_reason; } + + uint16_t getBattMilliVolts() override { + return meshSolarGetBattVoltage(); + } + + const char* getManufacturerName() const override { + return "Heltec Mesh Solar"; + } + + void reboot() override { + NVIC_SystemReset(); + } + + bool startOTAUpdate(const char* id, char reply[]) override; +}; diff --git a/variants/heltec_mesh_solar/platformio.ini b/variants/heltec_mesh_solar/platformio.ini new file mode 100644 index 00000000..9fd3edd5 --- /dev/null +++ b/variants/heltec_mesh_solar/platformio.ini @@ -0,0 +1,91 @@ +[Heltec_mesh_solar] +extends = nrf52_base +board = heltec_mesh_solar +platform_packages = framework-arduinoadafruitnrf52 +board_build.ldscript = boards/nrf52840_s140_v6.ld +build_flags = ${nrf52_base.build_flags} + -I src/helpers/nrf52 + -I lib/nrf52/s140_nrf52_6.1.1_API/include + -I lib/nrf52/s140_nrf52_6.1.1_API/include/nrf52 + -I variants/heltec_mesh_solar + -D HELTEC_MESH_SOLAR + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 +build_src_filter = ${nrf52_base.build_src_filter} + + + +<../variants/heltec_mesh_solar> +lib_deps = + ${nrf52_base.lib_deps} + rweather/Crypto @ ^0.4.0 + stevemarple/MicroNMEA @ ^2.0.6 + adafruit/Adafruit NeoPixel@^1.10.0 + https://github.com/NMIoT/meshsolar/archive/dfc5330dad443982e6cdd37a61d33fc7252f468b.zip +debug_tool = jlink +upload_protocol = nrfutil + +[env:Heltec_mesh_solar_repeater] +extends = Heltec_mesh_solar +build_src_filter = ${Heltec_mesh_solar.build_src_filter} + +<../examples/simple_repeater> + +build_flags = + ${Heltec_mesh_solar.build_flags} + -D ADVERT_NAME='"Heltec_Mesh_Solar Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + +[env:Heltec_mesh_solar_room_server] +extends = Heltec_mesh_solar +build_src_filter = ${Heltec_mesh_solar.build_src_filter} + +<../examples/simple_room_server> +build_flags = + ${Heltec_mesh_solar.build_flags} + -D ADVERT_NAME='"Heltec_Mesh_Solar Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + +[env:Heltec_mesh_solar_companion_radio_ble] +extends = Heltec_mesh_solar +build_flags = + ${Heltec_mesh_solar.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 +; -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_mesh_solar.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +lib_deps = + ${Heltec_mesh_solar.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Heltec_mesh_solar_companion_radio_usb] +extends = Heltec_mesh_solar +build_flags = + ${Heltec_mesh_solar.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 +; -D BLE_PIN_CODE=123456 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_mesh_solar.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +lib_deps = + ${Heltec_mesh_solar.lib_deps} + densaugeo/base64 @ ~1.4.0 \ No newline at end of file diff --git a/variants/heltec_mesh_solar/target.cpp b/variants/heltec_mesh_solar/target.cpp new file mode 100644 index 00000000..ad79f717 --- /dev/null +++ b/variants/heltec_mesh_solar/target.cpp @@ -0,0 +1,123 @@ +#include +#include "target.h" +#include +#include + +MeshSolarBoard board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +SolarSensorManager sensors = SolarSensorManager(nmea); + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + return radio.std_init(&SPI); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} + +void SolarSensorManager::start_gps() { + if (!gps_active) { + gps_active = true; + _location->begin(); + } +} + +void SolarSensorManager::stop_gps() { + if (gps_active) { + gps_active = false; + _location->stop(); + } +} + +bool SolarSensorManager::begin() { + Serial1.begin(9600); + + // We'll consider GPS detected if we see any data on Serial1 + gps_detected = (Serial1.available() > 0); + + if (gps_detected) { + MESH_DEBUG_PRINTLN("GPS detected"); + } else { + MESH_DEBUG_PRINTLN("No GPS detected"); + } + + return true; +} + +bool SolarSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { + if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission? + telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); + } + return true; +} + +void SolarSensorManager::loop() { + static long next_gps_update = 0; + + _location->loop(); + + if (millis() > next_gps_update) { + if (_location->isValid()) { + node_lat = ((double)_location->getLatitude())/1000000.; + node_lon = ((double)_location->getLongitude())/1000000.; + node_altitude = ((double)_location->getAltitude()) / 1000.0; + MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); + } + next_gps_update = millis() + 1000; + } +} + +int SolarSensorManager::getNumSettings() const { + return gps_detected ? 1 : 0; // only show GPS setting if GPS is detected +} + +const char* SolarSensorManager::getSettingName(int i) const { + return (gps_detected && i == 0) ? "gps" : NULL; +} + +const char* SolarSensorManager::getSettingValue(int i) const { + if (gps_detected && i == 0) { + return gps_active ? "1" : "0"; + } + return NULL; +} + +bool SolarSensorManager::setSettingValue(const char* name, const char* value) { + if (gps_detected && strcmp(name, "gps") == 0) { + if (strcmp(value, "0") == 0) { + stop_gps(); + } else { + start_gps(); + } + return true; + } + return false; // not supported +} diff --git a/variants/heltec_mesh_solar/target.h b/variants/heltec_mesh_solar/target.h new file mode 100644 index 00000000..e301a273 --- /dev/null +++ b/variants/heltec_mesh_solar/target.h @@ -0,0 +1,46 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include +#endif + +class SolarSensorManager : public SensorManager { + bool gps_active = false; + bool gps_detected = false; + LocationProvider* _location; + + void start_gps(); + void stop_gps(); +public: + SolarSensorManager(LocationProvider &location): _location(&location) { } + bool begin() override; + bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; + void loop() override; + int getNumSettings() const override; + const char* getSettingName(int i) const override; + const char* getSettingValue(int i) const override; + bool setSettingValue(const char* name, const char* value) override; +}; + +extern MeshSolarBoard board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SolarSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_mesh_solar/variant.cpp b/variants/heltec_mesh_solar/variant.cpp new file mode 100644 index 00000000..03dd54b7 --- /dev/null +++ b/variants/heltec_mesh_solar/variant.cpp @@ -0,0 +1,16 @@ +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47 +}; + +void initVariant() +{ + pinMode(PIN_USER_BTN, INPUT); + pinMode(BQ4050_EMERGENCY_SHUTDOWN_PIN, INPUT); +} diff --git a/variants/heltec_mesh_solar/variant.h b/variants/heltec_mesh_solar/variant.h new file mode 100644 index 00000000..14956619 --- /dev/null +++ b/variants/heltec_mesh_solar/variant.h @@ -0,0 +1,127 @@ +/* + * variant.h + * Copyright (C) 2023 Seeed K.K. + * MIT License + */ + +#pragma once + +#include "WVariant.h" + +//////////////////////////////////////////////////////////////////////////////// +// Low frequency clock source + +#define USE_LFXO // 32.768 kHz crystal oscillator +#define VARIANT_MCK (64000000ul) + + +//////////////////////////////////////////////////////////////////////////////// +// Power + +#define NRF_APM + +//////////////////////////////////////////////////////////////////////////////// +// Number of pins + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +//////////////////////////////////////////////////////////////////////////////// +// UART pin definition + +#define PIN_SERIAL1_RX (37) +#define PIN_SERIAL1_TX (39) + +#define PIN_SERIAL2_RX (9) +#define PIN_SERIAL2_TX (10) + +//////////////////////////////////////////////////////////////////////////////// +// I2C pin definition +#define WIRE_INTERFACES_COUNT (2) + +#define PIN_WIRE_SDA (6) +#define PIN_WIRE_SCL (26) + +#define PIN_WIRE1_SDA (30) +#define PIN_WIRE1_SCL (5) + +//////////////////////////////////////////////////////////////////////////////// +// SPI pin definition + +#define SPI_INTERFACES_COUNT (2) + +#define PIN_SPI_MISO (23) +#define PIN_SPI_MOSI (22) +#define PIN_SPI_SCK (19) +#define PIN_SPI_NSS (24) + +//////////////////////////////////////////////////////////////////////////////// +// Builtin LEDs + +#define LED_BUILTIN (12) +#define PIN_LED LED_BUILTIN +#define LED_RED LED_BUILTIN +#define LED_BLUE (-1) // No blue led, prevents Bluefruit flashing the green LED during advertising +#define LED_PIN LED_BUILTIN + +#define LED_STATE_ON LOW + +#define PIN_NEOPIXEL (47) +#define NEOPIXEL_NUM (1) + +//////////////////////////////////////////////////////////////////////////////// +// Builtin buttons + +#define PIN_BUTTON1 (42) +#define BUTTON_PIN PIN_BUTTON1 + +// #define PIN_BUTTON2 (11) +// #define BUTTON_PIN2 PIN_BUTTON2 + +#define PIN_USER_BTN BUTTON_PIN + +#define EXTERNAL_FLASH_DEVICES MX25R1635F +#define EXTERNAL_FLASH_USE_QSPI + +//////////////////////////////////////////////////////////////////////////////// +// Lora + +#define USE_SX1262 +#define LORA_CS (24) +#define SX126X_DIO1 (20) +#define SX126X_BUSY (17) +#define SX126X_RESET (25) +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +#define PIN_SPI1_MISO (43) +#define PIN_SPI1_MOSI (41) +#define PIN_SPI1_SCK (40) + +//////////////////////////////////////////////////////////////////////////////// +// Buzzer + +// #define PIN_BUZZER (46) + + +//////////////////////////////////////////////////////////////////////////////// +// GPS + +#define GPS_RESET (38) + +//////////////////////////////////////////////////////////////////////////////// +// TFT +// #define PIN_TFT_SCL (40) +// #define PIN_TFT_SDA (41) +// #define PIN_TFT_RST (2) +// #define PIN_TFT_VDD_CTL (3) +// #define PIN_TFT_LEDA_CTL (15) +// #define PIN_TFT_CS (11) +// #define PIN_TFT_DC (12) + +//////////////////////////////////////////////////////////////////////////////// +#define BQ4050_SDA_PIN (33) // I2C data line pin +#define BQ4050_SCL_PIN (32) // I2C clock line pin +#define BQ4050_EMERGENCY_SHUTDOWN_PIN (35) // Emergency shutdown pin \ No newline at end of file diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 8d9013a5..357ab854 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -39,6 +39,7 @@ extends = Heltec_tracker_base build_flags = ${Heltec_tracker_base.build_flags} -I src/helpers/ui + -I examples/companion_radio/ui-new -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial -D DISPLAY_ROTATION=1 -D DISPLAY_CLASS=ST7735Display @@ -51,7 +52,9 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_tracker_base.build_src_filter} + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> + lib_deps = ${Heltec_tracker_base.lib_deps} diff --git a/variants/heltec_tracker/target.cpp b/variants/heltec_tracker/target.cpp index f41702c5..5ba9a8fb 100644 --- a/variants/heltec_tracker/target.cpp +++ b/variants/heltec_tracker/target.cpp @@ -21,6 +21,7 @@ HWTSensorManager sensors = HWTSensorManager(nmea); #ifdef DISPLAY_CLASS DISPLAY_CLASS display(&board.periph_power); // peripheral power pin is shared + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/heltec_tracker/target.h b/variants/heltec_tracker/target.h index c08be80a..8ac5eb72 100644 --- a/variants/heltec_tracker/target.h +++ b/variants/heltec_tracker/target.h @@ -10,6 +10,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif class HWTSensorManager : public SensorManager { @@ -36,6 +37,7 @@ extern HWTSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/heltec_v2/platformio.ini b/variants/heltec_v2/platformio.ini index 43bc43ad..ea41f845 100644 --- a/variants/heltec_v2/platformio.ini +++ b/variants/heltec_v2/platformio.ini @@ -35,6 +35,7 @@ build_flags = build_src_filter = ${Heltec_lora32_v2.build_src_filter} +<../examples/simple_repeater> + + + lib_deps = ${Heltec_lora32_v2.lib_deps} ${esp32_ota.lib_deps} @@ -53,6 +54,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v2.build_src_filter} + + + +<../examples/simple_room_server> lib_deps = ${Heltec_lora32_v2.lib_deps} @@ -76,6 +78,7 @@ lib_deps = extends = Heltec_lora32_v2 build_flags = ${Heltec_lora32_v2.build_flags} + -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 @@ -84,7 +87,9 @@ build_flags = build_src_filter = ${Heltec_lora32_v2.build_src_filter} + + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Heltec_lora32_v2.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -93,6 +98,7 @@ lib_deps = extends = Heltec_lora32_v2 build_flags = ${Heltec_lora32_v2.build_flags} + -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 @@ -104,7 +110,9 @@ build_flags = build_src_filter = ${Heltec_lora32_v2.build_src_filter} + + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Heltec_lora32_v2.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/heltec_v2/target.cpp b/variants/heltec_v2/target.cpp index 418f1f7f..df71d3f4 100644 --- a/variants/heltec_v2/target.cpp +++ b/variants/heltec_v2/target.cpp @@ -18,6 +18,7 @@ SensorManager sensors; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/heltec_v2/target.h b/variants/heltec_v2/target.h index 0c330316..2e5b17de 100644 --- a/variants/heltec_v2/target.h +++ b/variants/heltec_v2/target.h @@ -9,6 +9,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif extern HeltecV2Board board; @@ -18,6 +19,7 @@ extern SensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 37a77c58..ca5de218 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -3,8 +3,10 @@ extends = esp32_base board = esp32-s3-devkitc-1 build_flags = ${esp32_base.build_flags} + ${sensor_base.build_flags} -I variants/heltec_v3 -D HELTEC_LORA_V3 + -D ESP32_CPU_FREQ=80 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 @@ -17,12 +19,6 @@ build_flags = -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 - -D ENV_INCLUDE_AHTX0=1 - -D ENV_INCLUDE_BME280=1 - -D ENV_INCLUDE_BMP280=1 - -D ENV_INCLUDE_INA3221=1 - -D ENV_INCLUDE_INA219=1 - -D ENV_INCLUDE_GPS=1 -D PIN_GPS_RX=47 -D PIN_GPS_TX=48 -D PIN_GPS_EN=26 @@ -31,13 +27,7 @@ build_src_filter = ${esp32_base.build_src_filter} + lib_deps = ${esp32_base.lib_deps} - adafruit/Adafruit SSD1306 @ ^2.5.13 - adafruit/Adafruit INA3221 Library @ ^1.0.1 - adafruit/Adafruit INA219 @ ^1.2.3 - adafruit/Adafruit AHTX0 @ ^2.0.5 - adafruit/Adafruit BME280 Library @ ^2.3.0 - adafruit/Adafruit BMP280 Library@^2.6.8 - stevemarple/MicroNMEA @ ^2.0.6 + ${sensor_base.lib_deps} [env:Heltec_v3_repeater] extends = Heltec_lora32_v3 @@ -57,6 +47,7 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter} lib_deps = ${Heltec_lora32_v3.lib_deps} ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 [env:Heltec_v3_Bridge] extends = Heltec_lora32_v3 @@ -117,6 +108,7 @@ lib_deps = extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D DISPLAY_CLASS=SSD1306Display @@ -124,7 +116,9 @@ build_flags = ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Heltec_lora32_v3.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -133,18 +127,22 @@ lib_deps = extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} - -D MAX_CONTACTS=100 + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=160 -D MAX_GROUP_CHANNELS=8 -D DISPLAY_CLASS=SSD1306Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D AUTO_SHUTDOWN_MILLIVOLTS=3400 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + + + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Heltec_lora32_v3.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -163,6 +161,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + + + +<../examples/companion_radio> lib_deps = @@ -179,8 +178,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D ENV_PIN_SDA=33 -D ENV_PIN_SCL=34 - -D ENV_INCLUDE_MLX90614=1 - -D ENV_INCLUDE_VL53L0X=1 -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -190,8 +187,6 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter} lib_deps = ${Heltec_lora32_v3.lib_deps} ${esp32_ota.lib_deps} - adafruit/Adafruit MLX90614 Library @ ^2.1.5 - adafruit/Adafruit_VL53L0X @ ^1.2.4 [env:Heltec_WSL3_repeater] extends = Heltec_lora32_v3 @@ -209,6 +204,7 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter} lib_deps = ${Heltec_lora32_v3.lib_deps} ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 [env:Heltec_WSL3_room_server] extends = Heltec_lora32_v3 @@ -240,7 +236,21 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> +lib_deps = + ${Heltec_lora32_v3.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Heltec_WSL3_companion_radio_usb] +extends = Heltec_lora32_v3 +build_flags = + ${Heltec_lora32_v3.build_flags} + -D MAX_CONTACTS=140 + -D MAX_GROUP_CHANNELS=8 +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v3.build_src_filter} + +<../examples/companion_radio/*.cpp> lib_deps = ${Heltec_lora32_v3.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/heltec_v3/target.cpp b/variants/heltec_v3/target.cpp index 4cbc78fb..78b88197 100644 --- a/variants/heltec_v3/target.cpp +++ b/variants/heltec_v3/target.cpp @@ -25,6 +25,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/heltec_v3/target.h b/variants/heltec_v3/target.h index 992a3d2c..b2125664 100644 --- a/variants/heltec_v3/target.h +++ b/variants/heltec_v3/target.h @@ -10,6 +10,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif extern HeltecV3Board board; @@ -19,6 +20,7 @@ extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/heltec_vision_master_e213/HeltecE213Board.cpp b/variants/heltec_vision_master_e213/HeltecE213Board.cpp new file mode 100644 index 00000000..d32d274e --- /dev/null +++ b/variants/heltec_vision_master_e213/HeltecE213Board.cpp @@ -0,0 +1,69 @@ +#include "HeltecE213Board.h" + +void HeltecE213Board::begin() { + ESP32Board::begin(); + + pinMode(PIN_ADC_CTRL, OUTPUT); + digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + + periph_power.begin(); + + esp_reset_reason_t reason = esp_reset_reason(); + if (reason == ESP_RST_DEEPSLEEP) { + long wakeup_source = esp_sleep_get_ext1_wakeup_status(); + if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep) + startup_reason = BD_STARTUP_RX_PACKET; + } + + rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); + rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); + } + } + + void HeltecE213Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + + // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep + rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); + rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); + + rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); + + if (pin_wake_btn < 0) { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + } else { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + } + + if (secs > 0) { + esp_sleep_enable_timer_wakeup(secs * 1000000); + } + + // Finally set ESP32 into sleep + esp_deep_sleep_start(); // CPU halts here and never returns! + } + + void HeltecE213Board::powerOff() { + // TODO: re-enable this when there is a definite wake-up source pin: + // enterDeepSleep(0); + } + + uint16_t HeltecE213Board::getBattMilliVolts() { + analogReadResolution(10); + digitalWrite(PIN_ADC_CTRL, HIGH); + + uint32_t raw = 0; + for (int i = 0; i < 8; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / 8; + + digitalWrite(PIN_ADC_CTRL, LOW); + + return (5.42 * (3.3 / 1024.0) * raw) * 1000; + } + + const char* HeltecE213Board::getManufacturerName() const { + return "Heltec E213"; + } + diff --git a/variants/heltec_vision_master_e213/HeltecE213Board.h b/variants/heltec_vision_master_e213/HeltecE213Board.h new file mode 100644 index 00000000..dd622064 --- /dev/null +++ b/variants/heltec_vision_master_e213/HeltecE213Board.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include + +// LoRa radio module pins for heltec_vision_master_e213 +#define P_LORA_DIO_1 14 +#define P_LORA_NSS 8 +#define P_LORA_RESET 12 +#define P_LORA_BUSY 13 +#define P_LORA_SCLK 9 +#define P_LORA_MISO 11 +#define P_LORA_MOSI 10 + +class HeltecE213Board : public ESP32Board { + +public: + RefCountedDigitalPin periph_power; + + HeltecE213Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { } + + void begin(); + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); + void powerOff() override; + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override ; + +}; diff --git a/variants/heltec_vision_master_e213/pins_arduino.h b/variants/heltec_vision_master_e213/pins_arduino.h new file mode 100644 index 00000000..56f5ef15 --- /dev/null +++ b/variants/heltec_vision_master_e213/pins_arduino.h @@ -0,0 +1,61 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +static const uint8_t LED_BUILTIN = 45; // LED is not populated on earliest board variant +#define BUILTIN_LED LED_BUILTIN // Backward compatibility +#define LED_BUILTIN LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 39; +static const uint8_t SCL = 38; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO1 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_vision_master_e213/platformio.ini b/variants/heltec_vision_master_e213/platformio.ini new file mode 100644 index 00000000..29611dfa --- /dev/null +++ b/variants/heltec_vision_master_e213/platformio.ini @@ -0,0 +1,84 @@ +[Heltec_Vision_Master_E213_base] +extends = esp32_base +board = heltec_vision_master_e213 +build_flags = + ${esp32_base.build_flags} + -I variants/heltec_vision_master_e213 + -D HELTEC_VISION_MASTER_E213 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D P_LORA_TX_LED=45 + -D PIN_USER_BTN=0 + -D PIN_VEXT_EN=18 + -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_VBAT_READ=7 + -D PIN_ADC_CTRL=46 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_BOARD_SDA=39 + -D PIN_BOARD_SCL=38 + -D DISP_CS=5 + -D DISP_BUSY=1 + -D DISP_DC=2 + -D DISP_RST=3 + -D DISP_SCLK=4 + -D DISP_MOSI=6 + -D Vision_Master_E213 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/heltec_vision_master_e213> +lib_deps = + ${esp32_base.lib_deps} + https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip + +[env:Heltec_Vision_Master_E213_radio_ble] +extends = Heltec_Vision_Master_E213_base +build_flags = + ${Heltec_Vision_Master_E213_base.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D DISPLAY_CLASS=E213Display + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${Heltec_Vision_Master_E213_base.build_src_filter} + + + + + +<../examples/companion_radio> +lib_deps = + ${Heltec_Vision_Master_E213_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Heltec_Vision_Master_E213_repeater] +extends = Heltec_Vision_Master_E213_base +build_flags = + ${Heltec_Vision_Master_E213_base.build_flags} + -D DISPLAY_CLASS=E213Display + -D ADVERT_NAME='"Heltec E213 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 +build_src_filter = ${Heltec_Vision_Master_E213_base.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_Vision_Master_E213_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:Heltec_Vision_Master_E213_room_server] +extends = Heltec_Vision_Master_E213_base +build_flags = + ${Heltec_Vision_Master_E213_base.build_flags} + -D DISPLAY_CLASS=E213Display + -D ADVERT_NAME='"Heltec E213 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +build_src_filter = ${Heltec_Vision_Master_E213_base.build_src_filter} + + + +<../examples/simple_room_server> +lib_deps = + ${Heltec_Vision_Master_E213_base.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/heltec_vision_master_e213/target.cpp b/variants/heltec_vision_master_e213/target.cpp new file mode 100644 index 00000000..dfba0103 --- /dev/null +++ b/variants/heltec_vision_master_e213/target.cpp @@ -0,0 +1,53 @@ +#include "target.h" +#include + +HeltecE213Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi(FSPI); + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +SensorManager sensors; + +#ifdef DISPLAY_CLASS +DISPLAY_CLASS display; +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/heltec_vision_master_e213/target.h b/variants/heltec_vision_master_e213/target.h new file mode 100644 index 00000000..ec113879 --- /dev/null +++ b/variants/heltec_vision_master_e213/target.h @@ -0,0 +1,27 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS +#include +#endif + +extern HeltecE213Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +#ifdef DISPLAY_CLASS +extern DISPLAY_CLASS display; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/heltec_vision_master_e290/HeltecE290Board.cpp b/variants/heltec_vision_master_e290/HeltecE290Board.cpp new file mode 100644 index 00000000..7d8c654d --- /dev/null +++ b/variants/heltec_vision_master_e290/HeltecE290Board.cpp @@ -0,0 +1,69 @@ +#include "HeltecE290Board.h" + +void HeltecE290Board::begin() { + ESP32Board::begin(); + + pinMode(PIN_ADC_CTRL, OUTPUT); + digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + + periph_power.begin(); + + esp_reset_reason_t reason = esp_reset_reason(); + if (reason == ESP_RST_DEEPSLEEP) { + long wakeup_source = esp_sleep_get_ext1_wakeup_status(); + if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep) + startup_reason = BD_STARTUP_RX_PACKET; + } + + rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); + rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); + } + } + + void HeltecE290Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + + // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep + rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); + rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); + + rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); + + if (pin_wake_btn < 0) { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + } else { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + } + + if (secs > 0) { + esp_sleep_enable_timer_wakeup(secs * 1000000); + } + + // Finally set ESP32 into sleep + esp_deep_sleep_start(); // CPU halts here and never returns! + } + + void HeltecE290Board::powerOff() { + // TODO: re-enable this when there is a definite wake-up source pin: + // enterDeepSleep(0); + } + + uint16_t HeltecE290Board::getBattMilliVolts() { + analogReadResolution(10); + digitalWrite(PIN_ADC_CTRL, HIGH); + + uint32_t raw = 0; + for (int i = 0; i < 8; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / 8; + + digitalWrite(PIN_ADC_CTRL, LOW); + + return (5.42 * (3.3 / 1024.0) * raw) * 1000; + } + + const char* HeltecE290Board::getManufacturerName() const { + return "Heltec E290"; + } + diff --git a/variants/heltec_vision_master_e290/HeltecE290Board.h b/variants/heltec_vision_master_e290/HeltecE290Board.h new file mode 100644 index 00000000..95f8c03e --- /dev/null +++ b/variants/heltec_vision_master_e290/HeltecE290Board.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include + +// LoRa radio module pins for heltec_vision_master_e290 +#define P_LORA_DIO_1 14 +#define P_LORA_NSS 8 +#define P_LORA_RESET 12 +#define P_LORA_BUSY 13 +#define P_LORA_SCLK 9 +#define P_LORA_MISO 11 +#define P_LORA_MOSI 10 + +class HeltecE290Board : public ESP32Board { + +public: + RefCountedDigitalPin periph_power; + + HeltecE290Board() : periph_power(PIN_VEXT_EN) { } + + void begin(); + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); + void powerOff() override; + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override ; + +}; diff --git a/variants/heltec_vision_master_e290/pins_arduino.h b/variants/heltec_vision_master_e290/pins_arduino.h new file mode 100644 index 00000000..56f5ef15 --- /dev/null +++ b/variants/heltec_vision_master_e290/pins_arduino.h @@ -0,0 +1,61 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +static const uint8_t LED_BUILTIN = 45; // LED is not populated on earliest board variant +#define BUILTIN_LED LED_BUILTIN // Backward compatibility +#define LED_BUILTIN LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 39; +static const uint8_t SCL = 38; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO1 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_vision_master_e290/platformio.ini b/variants/heltec_vision_master_e290/platformio.ini new file mode 100644 index 00000000..b3ba33be --- /dev/null +++ b/variants/heltec_vision_master_e290/platformio.ini @@ -0,0 +1,78 @@ +[Heltec_Vision_Master_E290_base] +extends = esp32_base +board = heltec_vision_master_e290 +build_flags = + ${esp32_base.build_flags} + -I variants/heltec_vision_master_e290 + -D HELTEC_VISION_MASTER_E290 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D P_LORA_TX_LED=45 + -D PIN_USER_BTN=0 + -D PIN_VEXT_EN=18 + -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_VBAT_READ=7 + -D PIN_ADC_CTRL=46 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_BOARD_SDA=39 + -D PIN_BOARD_SCL=38 + -D Vision_Master_E290 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/heltec_vision_master_e290> +lib_deps = + ${esp32_base.lib_deps} + https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip + +[env:Heltec_Vision_Master_E290_radio_ble] +extends = Heltec_Vision_Master_E290_base +build_flags = + ${Heltec_Vision_Master_E290_base.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D DISPLAY_CLASS=E290Display + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${Heltec_Vision_Master_E290_base.build_src_filter} + + + + + +<../examples/companion_radio> +lib_deps = + ${Heltec_Vision_Master_E290_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Heltec_Vision_Master_E290_repeater] +extends = Heltec_Vision_Master_E290_base +build_flags = + ${Heltec_Vision_Master_E290_base.build_flags} + -D DISPLAY_CLASS=E290Display + -D ADVERT_NAME='"Heltec E290 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 +build_src_filter = ${Heltec_Vision_Master_E290_base.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_Vision_Master_E290_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:Heltec_Vision_Master_E290_room_server] +extends = Heltec_Vision_Master_E290_base +build_flags = + ${Heltec_Vision_Master_E290_base.build_flags} + -D DISPLAY_CLASS=E290Display + -D ADVERT_NAME='"Heltec E290 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +build_src_filter = ${Heltec_Vision_Master_E290_base.build_src_filter} + + + +<../examples/simple_room_server> +lib_deps = + ${Heltec_Vision_Master_E290_base.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/heltec_vision_master_e290/target.cpp b/variants/heltec_vision_master_e290/target.cpp new file mode 100644 index 00000000..2e897e49 --- /dev/null +++ b/variants/heltec_vision_master_e290/target.cpp @@ -0,0 +1,53 @@ +#include "target.h" +#include + +HeltecE290Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi(FSPI); + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +SensorManager sensors; + +#ifdef DISPLAY_CLASS +DISPLAY_CLASS display; +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/heltec_vision_master_e290/target.h b/variants/heltec_vision_master_e290/target.h new file mode 100644 index 00000000..00b27e54 --- /dev/null +++ b/variants/heltec_vision_master_e290/target.h @@ -0,0 +1,27 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS +#include +#endif + +extern HeltecE290Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +#ifdef DISPLAY_CLASS +extern DISPLAY_CLASS display; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 513ba4b9..b0c9ed1d 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -31,7 +31,7 @@ build_src_filter = ${esp32_base.build_src_filter} +<../variants/heltec_wireless_paper> lib_deps = ${esp32_base.lib_deps} - todd-herbert/heltec-eink-modules @ 4.5.0 + https://github.com/todd-herbert/heltec-eink-modules/archive/9207eb6ab2b96f66298e0488740218c17b006af7.zip [env:Heltec_Wireless_Paper_companion_radio_ble] extends = Heltec_Wireless_Paper_base diff --git a/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.cpp b/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.cpp new file mode 100644 index 00000000..8634cda1 --- /dev/null +++ b/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.cpp @@ -0,0 +1,99 @@ +#ifdef XIAO_NRF52 + +#include +#include "ikoka_stick_nrf_board.h" + +#include +#include + +static BLEDfu bledfu; + +static void connect_callback(uint16_t conn_handle) +{ + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) +{ + (void)conn_handle; + (void)reason; + + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + +void ikoka_stick_nrf_board::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + + pinMode(PIN_VBAT, INPUT); + pinMode(VBAT_ENABLE, OUTPUT); + digitalWrite(VBAT_ENABLE, HIGH); + +#ifdef PIN_USER_BTN + pinMode(PIN_USER_BTN, INPUT_PULLUP); +#endif + +#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) + Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL); +#endif + + Wire.begin(); + +#ifdef P_LORA_TX_LED + pinMode(P_LORA_TX_LED, OUTPUT); + digitalWrite(P_LORA_TX_LED, HIGH); +#endif + +// pinMode(SX126X_POWER_EN, OUTPUT); +// digitalWrite(SX126X_POWER_EN, HIGH); + delay(10); // give sx1262 some time to power up +} + +bool ikoka_stick_nrf_board::startOTAUpdate(const char* id, char reply[]) { + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 + Bluefruit.setTxPower(4); + // Set the BLE device name + Bluefruit.setName("XIAO_NRF52_OTA"); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // To be consistent OTA DFU should be added first if it exists + bledfu.begin(); + + // Set up and start advertising + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + 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 + + strcpy(reply, "OK - started"); + return true; + + + return false; +} + +#endif diff --git a/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h b/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h new file mode 100644 index 00000000..1bd8b31d --- /dev/null +++ b/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include + +#ifdef XIAO_NRF52 + +// redefine lora pins if using the S3 variant of SX1262 board +#ifdef SX1262_XIAO_S3_VARIANT + #undef P_LORA_DIO_1 + #undef P_LORA_BUSY + #undef P_LORA_RESET + #undef P_LORA_NSS + #undef SX126X_RXEN + #define P_LORA_DIO_1 D0 + #define P_LORA_BUSY D1 + #define P_LORA_RESET D2 + #define P_LORA_NSS D3 + #define SX126X_RXEN D4 +#endif + +class ikoka_stick_nrf_board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + void begin(); + uint8_t getStartupReason() const override { return startup_reason; } + +#if defined(P_LORA_TX_LED) + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off + } +#endif + + uint16_t getBattMilliVolts() override { + // Please read befor going further ;) + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + + // We can't drive VBAT_ENABLE to HIGH as long + // as we don't know wether we are charging or not ... + // this is a 3mA loss (4/1500) + digitalWrite(VBAT_ENABLE, LOW); + int adcvalue = 0; + analogReadResolution(12); + analogReference(AR_INTERNAL_3_0); + delay(10); + adcvalue = analogRead(PIN_VBAT); + return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096; + } + + const char* getManufacturerName() const override { + return "Ikoka Stick (Xiao-nrf52)"; + } + + void reboot() override { + NVIC_SystemReset(); + } + + bool startOTAUpdate(const char* id, char reply[]) override; +}; + +#endif diff --git a/variants/ikoka_stick_nrf/platformio.ini b/variants/ikoka_stick_nrf/platformio.ini new file mode 100644 index 00000000..e78bc058 --- /dev/null +++ b/variants/ikoka_stick_nrf/platformio.ini @@ -0,0 +1,127 @@ +[nrf52840_xiao] +extends = nrf52_base +platform_packages = + toolchain-gccarmnoneeabi@~1.100301.0 + framework-arduinoadafruitnrf52 +board = seeed-xiao-afruitnrf52-nrf52840 +board_build.ldscript = boards/nrf52840_s140_v7.ld +build_flags = ${nrf52_base.build_flags} + -D NRF52_PLATFORM -D XIAO_NRF52 + -I lib/nrf52/s140_nrf52_7.3.0_API/include + -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52 +lib_ignore = + BluetoothOTA + lvgl + lib5b4 +lib_deps = + ${nrf52_base.lib_deps} + rweather/Crypto @ ^0.4.0 + adafruit/Adafruit INA3221 Library @ ^1.0.1 + adafruit/Adafruit INA219 @ ^1.2.3 + adafruit/Adafruit AHTX0 @ ^2.0.5 + adafruit/Adafruit BME280 Library @ ^2.3.0 + adafruit/Adafruit SSD1306 @ ^2.5.13 + + +[ikoka_stick_nrf] +extends = nrf52840_xiao +;board_build.ldscript = boards/nrf52840_s140_v7.ld +build_flags = ${nrf52840_xiao.build_flags} + -D P_LORA_TX_LED=11 + -I variants/ikoka_stick_nrf + -I src/helpers/nrf52 + -D DISPLAY_CLASS=SSD1306Display + -D DISPLAY_ROTATION=2 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=9 + -D P_LORA_DIO_1=D1 + -D P_LORA_RESET=D2 + -D P_LORA_BUSY=D3 + -D P_LORA_NSS=D4 + -D SX126X_RXEN=D5 + -D SX126X_TXEN=RADIOLIB_NC + -D SX126X_DIO2_AS_RF_SWITCH=1 + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_USER_BTN=0 + -D PIN_WIRE_SCL=7 + -D PIN_WIRE_SDA=6 + -D ENV_INCLUDE_AHTX0=1 + -D ENV_INCLUDE_BME280=1 + -D ENV_INCLUDE_INA3221=1 + -D ENV_INCLUDE_INA219=1 +build_src_filter = ${nrf52840_xiao.build_src_filter} + + + + + + + + + +<../variants/ikoka_stick_nrf> +debug_tool = jlink +upload_protocol = nrfutil + +[env:ikoka_stick_nrf_companion_radio_ble] +extends = ikoka_stick_nrf +build_flags = + ${ikoka_stick_nrf.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 + -I examples/companion_radio/ui-new +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ikoka_stick_nrf.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ikoka_stick_nrf.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:ikoka_stick_nrf_companion_radio_usb] +extends = ikoka_stick_nrf +build_flags = + ${ikoka_stick_nrf.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -I examples/companion_radio/ui-new +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ikoka_stick_nrf.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ikoka_stick_nrf.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:ikoka_stick_nrf_repeater] +extends = ikoka_stick_nrf +build_flags = + ${ikoka_stick_nrf.build_flags} + -D ADVERT_NAME='"Ikoka Stick Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ikoka_stick_nrf.build_src_filter} + + + +<../examples/simple_repeater/*.cpp> + +[env:ikoka_stick_nrf_room_server] +extends = ikoka_stick_nrf +build_flags = + ${ikoka_stick_nrf.build_flags} + -D ADVERT_NAME='"Ikoka Stick Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ikoka_stick_nrf.build_src_filter} + +<../examples/simple_room_server/*.cpp> diff --git a/variants/ikoka_stick_nrf/target.cpp b/variants/ikoka_stick_nrf/target.cpp new file mode 100644 index 00000000..c2712761 --- /dev/null +++ b/variants/ikoka_stick_nrf/target.cpp @@ -0,0 +1,44 @@ +#include +#include "target.h" +#include + +ikoka_stick_nrf_board board; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +EnvironmentSensorManager sensors; + +bool radio_init() { + rtc_clock.begin(Wire); + + return radio.std_init(&SPI); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/ikoka_stick_nrf/target.h b/variants/ikoka_stick_nrf/target.h new file mode 100644 index 00000000..8311503a --- /dev/null +++ b/variants/ikoka_stick_nrf/target.h @@ -0,0 +1,28 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include + +#ifdef DISPLAY_CLASS + #include + #include + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +extern ikoka_stick_nrf_board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/ikoka_stick_nrf/variant.cpp b/variants/ikoka_stick_nrf/variant.cpp new file mode 100644 index 00000000..16542e27 --- /dev/null +++ b/variants/ikoka_stick_nrf/variant.cpp @@ -0,0 +1,86 @@ +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" +#include "nrf.h" + +const uint32_t g_ADigitalPinMap[] = +{ + // D0 .. D10 + 2, // D0 is P0.02 (A0) + 3, // D1 is P0.03 (A1) + 28, // D2 is P0.28 (A2) + 29, // D3 is P0.29 (A3) + 4, // D4 is P0.04 (A4,SDA) + 5, // D5 is P0.05 (A5,SCL) + 43, // D6 is P1.11 (TX) + 44, // D7 is P1.12 (RX) + 45, // D8 is P1.13 (SCK) + 46, // D9 is P1.14 (MISO) + 47, // D10 is P1.15 (MOSI) + + // LEDs + 26, // D11 is P0.26 (LED RED) + 6, // D12 is P0.06 (LED BLUE) + 30, // D13 is P0.30 (LED GREEN) + 14, // D14 is P0.14 (READ_BAT) + + // LSM6DS3TR + 40, // D15 is P1.08 (6D_PWR) + 27, // D16 is P0.27 (6D_I2C_SCL) + 7, // D17 is P0.07 (6D_I2C_SDA) + 11, // D18 is P0.11 (6D_INT1) + + // MIC + 42, // D19 is P1.10 (MIC_PWR) + 32, // D20 is P1.00 (PDM_CLK) + 16, // D21 is P0.16 (PDM_DATA) + + // BQ25100 + 13, // D22 is P0.13 (HICHG) + 17, // D23 is P0.17 (~CHG) + + // + 21, // D24 is P0.21 (QSPI_SCK) + 25, // D25 is P0.25 (QSPI_CSN) + 20, // D26 is P0.20 (QSPI_SIO_0 DI) + 24, // D27 is P0.24 (QSPI_SIO_1 DO) + 22, // D28 is P0.22 (QSPI_SIO_2 WP) + 23, // D29 is P0.23 (QSPI_SIO_3 HOLD) + + // NFC + 9, // D30 is P0.09 (NFC1) + 10, // D31 is P0.10 (NFC2) + + // VBAT + 31, // D32 is P0.31 (VBAT) +}; + +void initVariant() +{ + // Disable reading of the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + pinMode(VBAT_ENABLE, OUTPUT); + //digitalWrite(VBAT_ENABLE, HIGH); + // This was taken from Seeed github butis not coherent with the doc, + // VBAT_ENABLE should be kept to LOW to protect P0.14, (1500/500)*(4.2-3.3)+3.3 = 3.9V > 3.6V + // This induces a 3mA current in the resistors :( but it's better than burning the nrf + digitalWrite(VBAT_ENABLE, LOW); + + // Low charging current (50mA) + // https://wiki.seeedstudio.com/XIAO_BLE#battery-charging-current + //pinMode(PIN_CHARGING_CURRENT, INPUT); + + // High charging current (100mA) + pinMode(PIN_CHARGING_CURRENT, OUTPUT); + digitalWrite(PIN_CHARGING_CURRENT, LOW); + + pinMode(PIN_QSPI_CS, OUTPUT); + digitalWrite(PIN_QSPI_CS, HIGH); + + pinMode(LED_RED, OUTPUT); + digitalWrite(LED_RED, HIGH); + pinMode(LED_GREEN, OUTPUT); + digitalWrite(LED_GREEN, HIGH); + pinMode(LED_BLUE, OUTPUT); + digitalWrite(LED_BLUE, HIGH); +} diff --git a/variants/ikoka_stick_nrf/variant.h b/variants/ikoka_stick_nrf/variant.h new file mode 100644 index 00000000..f94ebe49 --- /dev/null +++ b/variants/ikoka_stick_nrf/variant.h @@ -0,0 +1,149 @@ +#ifndef _IKOKA_STICK_NRF_H_ +#define _IKOKA_STICK_NRF_H_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#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 + +#define PINS_COUNT (33) +#define NUM_DIGITAL_PINS (33) +#define NUM_ANALOG_INPUTS (8) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED (LED_RED) +#define LED_PWR (PINS_COUNT) +#define PIN_NEOPIXEL (PINS_COUNT) +#define NEOPIXEL_NUM (0) + +#define LED_BUILTIN (PIN_LED) + +#define LED_RED (11) +#define LED_GREEN (13) +#define LED_BLUE (12) + +#define LED_STATE_ON (1) // State when LED is litted + +// Buttons +#define PIN_BUTTON1 (PINS_COUNT) + +// Digital PINs +static const uint8_t D0 = 0 ; +static const uint8_t D1 = 1 ; +static const uint8_t D2 = 2 ; +static const uint8_t D3 = 3 ; +static const uint8_t D4 = 4 ; +static const uint8_t D5 = 5 ; +static const uint8_t D6 = 6 ; +static const uint8_t D7 = 7 ; +static const uint8_t D8 = 8 ; +static const uint8_t D9 = 9 ; +static const uint8_t D10 = 10; + +#define VBAT_ENABLE (14) // Output LOW to enable reading of the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + +#define PIN_CHARGING_CURRENT (22) // Battery Charging current + // https://wiki.seeedstudio.com/XIAO_BLE#battery-charging-current + +// Analog pins +#define PIN_A0 (0) +#define PIN_A1 (1) +#define PIN_A2 (2) +#define PIN_A3 (3) +#define PIN_A4 (4) +#define PIN_A5 (5) +#define PIN_VBAT (32) // Read the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + +#define BAT_NOT_CHARGING (23) // LOW when charging + +#define AREF_VOLTAGE (3.0) +#define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge + +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; + +#define ADC_RESOLUTION (12) + +// Other pins +#define PIN_NFC1 (30) +#define PIN_NFC2 (31) + +// Serial interfaces +#define PIN_SERIAL1_RX (7) +#define PIN_SERIAL1_TX (6) + +// SPI Interfaces +#define SPI_INTERFACES_COUNT (2) + +#define PIN_SPI_MISO (9) +#define PIN_SPI_MOSI (10) +#define PIN_SPI_SCK (8) + +#define PIN_SPI1_MISO (25) +#define PIN_SPI1_MOSI (26) +#define PIN_SPI1_SCK (29) + +// Lora SPI is on SPI0 +#define P_LORA_SCLK PIN_SPI_SCK +#define P_LORA_MISO PIN_SPI_MISO +#define P_LORA_MOSI PIN_SPI_MOSI + +// Wire Interfaces +#define WIRE_INTERFACES_COUNT (1) + +// #define PIN_WIRE_SDA (17) // 4 and 5 are used for the sx1262 ! +// #define PIN_WIRE_SCL (16) // use WIRE1_SDA + +static const uint8_t SDA = PIN_WIRE_SDA; +static const uint8_t SCL = PIN_WIRE_SCL; + +//#define PIN_WIRE1_SDA (17) +//#define PIN_WIRE1_SCL (16) +#define PIN_LSM6DS3TR_C_POWER (15) +#define PIN_LSM6DS3TR_C_INT1 (18) + +// PDM Interfaces +#define PIN_PDM_PWR (19) +#define PIN_PDM_CLK (20) +#define PIN_PDM_DIN (21) + +// QSPI Pins +#define PIN_QSPI_SCK (24) +#define PIN_QSPI_CS (25) +#define PIN_QSPI_IO0 (26) +#define PIN_QSPI_IO1 (27) +#define PIN_QSPI_IO2 (28) +#define PIN_QSPI_IO3 (29) + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES (P25Q16H) +#define EXTERNAL_FLASH_USE_QSPI + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif diff --git a/variants/lilygo_t3s3/platformio.ini b/variants/lilygo_t3s3/platformio.ini index f3a95e96..722a3243 100644 --- a/variants/lilygo_t3s3/platformio.ini +++ b/variants/lilygo_t3s3/platformio.ini @@ -89,6 +89,7 @@ lib_deps = extends = LilyGo_T3S3_sx1262 build_flags = ${LilyGo_T3S3_sx1262.build_flags} + -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 @@ -96,7 +97,9 @@ build_flags = ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter} + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_T3S3_sx1262.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -105,6 +108,7 @@ lib_deps = extends = LilyGo_T3S3_sx1262 build_flags = ${LilyGo_T3S3_sx1262.build_flags} + -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 @@ -116,7 +120,9 @@ build_flags = build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter} + + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_T3S3_sx1262.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/lilygo_t3s3/target.cpp b/variants/lilygo_t3s3/target.cpp index b7c4542c..1c7b3b09 100644 --- a/variants/lilygo_t3s3/target.cpp +++ b/variants/lilygo_t3s3/target.cpp @@ -13,6 +13,7 @@ SensorManager sensors; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif #ifndef LORA_CR diff --git a/variants/lilygo_t3s3/target.h b/variants/lilygo_t3s3/target.h index b768b2b0..f184c757 100644 --- a/variants/lilygo_t3s3/target.h +++ b/variants/lilygo_t3s3/target.h @@ -9,6 +9,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif extern ESP32Board board; @@ -18,6 +19,7 @@ extern SensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/lilygo_t3s3_sx1276/platformio.ini b/variants/lilygo_t3s3_sx1276/platformio.ini index 74eced9c..8f1c00e4 100644 --- a/variants/lilygo_t3s3_sx1276/platformio.ini +++ b/variants/lilygo_t3s3_sx1276/platformio.ini @@ -88,6 +88,7 @@ extends = LilyGo_T3S3_sx1276 upload_speed = 115200 build_flags = ${LilyGo_T3S3_sx1276.build_flags} + -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 @@ -95,7 +96,9 @@ build_flags = -D MESH_DEBUG=1 build_src_filter = ${LilyGo_T3S3_sx1276.build_src_filter} + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_T3S3_sx1276.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -104,6 +107,7 @@ lib_deps = extends = LilyGo_T3S3_sx1276 build_flags = ${LilyGo_T3S3_sx1276.build_flags} + -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 @@ -115,7 +119,9 @@ build_flags = build_src_filter = ${LilyGo_T3S3_sx1276.build_src_filter} + + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_T3S3_sx1276.lib_deps} densaugeo/base64 @ ~1.4.0 \ No newline at end of file diff --git a/variants/lilygo_t3s3_sx1276/target.cpp b/variants/lilygo_t3s3_sx1276/target.cpp index b2ee4455..042ff206 100644 --- a/variants/lilygo_t3s3_sx1276/target.cpp +++ b/variants/lilygo_t3s3_sx1276/target.cpp @@ -18,6 +18,7 @@ SensorManager sensors; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/lilygo_t3s3_sx1276/target.h b/variants/lilygo_t3s3_sx1276/target.h index 52ecf867..98a0fe35 100644 --- a/variants/lilygo_t3s3_sx1276/target.h +++ b/variants/lilygo_t3s3_sx1276/target.h @@ -9,6 +9,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif extern ESP32Board board; @@ -18,6 +19,7 @@ extern SensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/lilygo_tbeam_SX1262/platformio.ini b/variants/lilygo_tbeam_SX1262/platformio.ini index eac899f0..7bb008ac 100644 --- a/variants/lilygo_tbeam_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_SX1262/platformio.ini @@ -37,6 +37,7 @@ extends = LilyGo_TBeam_SX1262 board_build.upload.maximum_ram_size=2000000 build_flags = ${LilyGo_TBeam_SX1262.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -49,7 +50,9 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter} + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_TBeam_SX1262.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/lilygo_tbeam_SX1262/target.cpp b/variants/lilygo_tbeam_SX1262/target.cpp index fb05958b..a8caecb3 100644 --- a/variants/lilygo_tbeam_SX1262/target.cpp +++ b/variants/lilygo_tbeam_SX1262/target.cpp @@ -25,6 +25,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/lilygo_tbeam_SX1262/target.h b/variants/lilygo_tbeam_SX1262/target.h index cb9b28df..5f33abb8 100644 --- a/variants/lilygo_tbeam_SX1262/target.h +++ b/variants/lilygo_tbeam_SX1262/target.h @@ -9,6 +9,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif extern TBeamBoard board; @@ -18,6 +19,7 @@ extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/lilygo_tbeam_SX1276/platformio.ini b/variants/lilygo_tbeam_SX1276/platformio.ini index b0f6c001..e0391f1d 100644 --- a/variants/lilygo_tbeam_SX1276/platformio.ini +++ b/variants/lilygo_tbeam_SX1276/platformio.ini @@ -36,6 +36,7 @@ extends = LilyGo_TBeam_SX1276 board_build.upload.maximum_ram_size=2000000 build_flags = ${LilyGo_TBeam_SX1276.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -46,7 +47,9 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TBeam_SX1276.build_src_filter} + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_TBeam_SX1276.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -67,4 +70,20 @@ build_src_filter = ${LilyGo_TBeam_SX1276.build_src_filter} +<../examples/simple_repeater> lib_deps = ${LilyGo_TBeam_SX1276.lib_deps} - ${esp32_ota.lib_deps} \ No newline at end of file + ${esp32_ota.lib_deps} + +[env:Tbeam_SX1276_room_server] +extends = LilyGo_TBeam_SX1276 +build_flags = + ${LilyGo_TBeam_SX1276.build_flags} + -D ADVERT_NAME='"Tbeam Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_SX1276.build_src_filter} + +<../examples/simple_room_server> +lib_deps = + ${LilyGo_TBeam_SX1276.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/lilygo_tbeam_SX1276/target.cpp b/variants/lilygo_tbeam_SX1276/target.cpp index 7e2537bb..0a7517a2 100644 --- a/variants/lilygo_tbeam_SX1276/target.cpp +++ b/variants/lilygo_tbeam_SX1276/target.cpp @@ -25,6 +25,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/lilygo_tbeam_SX1276/target.h b/variants/lilygo_tbeam_SX1276/target.h index bcd8cb0b..b382b652 100644 --- a/variants/lilygo_tbeam_SX1276/target.h +++ b/variants/lilygo_tbeam_SX1276/target.h @@ -9,6 +9,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif extern TBeamBoard board; @@ -18,6 +19,7 @@ extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index 5c2d6e86..9b10f459 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -73,6 +73,7 @@ lib_deps = extends = T_Beam_S3_Supreme_SX1262 build_flags = ${T_Beam_S3_Supreme_SX1262.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -82,7 +83,9 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter} + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${T_Beam_S3_Supreme_SX1262.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/lilygo_tbeam_supreme_SX1262/target.cpp b/variants/lilygo_tbeam_supreme_SX1262/target.cpp index 68d54396..8ad306f1 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/target.cpp +++ b/variants/lilygo_tbeam_supreme_SX1262/target.cpp @@ -5,6 +5,7 @@ TBeamBoard board; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif static SPIClass spi; diff --git a/variants/lilygo_tbeam_supreme_SX1262/target.h b/variants/lilygo_tbeam_supreme_SX1262/target.h index 62a92329..c6ffa0a6 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/target.h +++ b/variants/lilygo_tbeam_supreme_SX1262/target.h @@ -11,6 +11,8 @@ #ifdef DISPLAY_CLASS #include extern DISPLAY_CLASS display; + #include + extern MomentaryButton user_btn; #endif extern TBeamBoard board; diff --git a/variants/lilygo_tlora_c6/platformio.ini b/variants/lilygo_tlora_c6/platformio.ini index 7f89c3da..5aab2213 100644 --- a/variants/lilygo_tlora_c6/platformio.ini +++ b/variants/lilygo_tlora_c6/platformio.ini @@ -79,7 +79,7 @@ build_flags = ${tlora_c6.build_flags} build_src_filter = ${tlora_c6.build_src_filter} + - - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${tlora_c6.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index adf4a7e4..992cedcd 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -105,13 +105,16 @@ lib_deps = extends = LilyGo_TLora_V2_1_1_6 build_flags = ${LilyGo_TLora_V2_1_1_6.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_TLora_V2_1_1_6.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -120,6 +123,7 @@ lib_deps = extends = LilyGo_TLora_V2_1_1_6 build_flags = ${LilyGo_TLora_V2_1_1_6.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -130,7 +134,9 @@ build_flags = build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} + + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_TLora_V2_1_1_6.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/lilygo_tlora_v2_1/target.cpp b/variants/lilygo_tlora_v2_1/target.cpp index 5e8f15b2..65a78c19 100644 --- a/variants/lilygo_tlora_v2_1/target.cpp +++ b/variants/lilygo_tlora_v2_1/target.cpp @@ -14,6 +14,7 @@ EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/lilygo_tlora_v2_1/target.h b/variants/lilygo_tlora_v2_1/target.h index f05b8055..380d733b 100644 --- a/variants/lilygo_tlora_v2_1/target.h +++ b/variants/lilygo_tlora_v2_1/target.h @@ -10,6 +10,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif extern LilyGoTLoraBoard board; @@ -19,6 +20,7 @@ extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/meshadventurer/platformio.ini b/variants/meshadventurer/platformio.ini index f8995e18..60cc55eb 100644 --- a/variants/meshadventurer/platformio.ini +++ b/variants/meshadventurer/platformio.ini @@ -78,7 +78,7 @@ lib_deps = [env:Meshadventurer_sx1262_companion_radio_usb] extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + build_flags = ${Meshadventurer.build_flags} @@ -96,7 +96,7 @@ lib_deps = [env:Meshadventurer_sx1262_companion_radio_ble] extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + + build_flags = @@ -157,7 +157,7 @@ lib_deps = [env:Meshadventurer_sx1268_companion_radio_usb] extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + build_flags = ${Meshadventurer.build_flags} @@ -175,7 +175,7 @@ lib_deps = [env:Meshadventurer_sx1268_companion_radio_ble] extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + + build_flags = diff --git a/variants/minewsemi_me25ls01/platformio.ini b/variants/minewsemi_me25ls01/platformio.ini index f7265af4..3436062f 100644 --- a/variants/minewsemi_me25ls01/platformio.ini +++ b/variants/minewsemi_me25ls01/platformio.ini @@ -51,6 +51,7 @@ lib_deps = ${nrf52840_me25ls01.lib_deps} [env:Minewsemi_me25ls01_companion_radio_ble] extends = me25ls01 build_flags = ${me25ls01.build_flags} + -I examples/companion_radio/ui-orig -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -66,6 +67,7 @@ build_flags = ${me25ls01.build_flags} build_src_filter = ${me25ls01.build_src_filter} + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${me25ls01.lib_deps} adafruit/RTClib @ ^2.1.3 @@ -146,6 +148,7 @@ lib_deps = ${me25ls01.lib_deps} [env:Minewsemi_me25ls01_companion_radio_usb] extends = me25ls01 build_flags = ${me25ls01.build_flags} + -I examples/companion_radio/ui-orig -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 ;-D BLE_PIN_CODE=123456 @@ -158,7 +161,8 @@ build_flags = ${me25ls01.build_flags} -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${me25ls01.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${me25ls01.lib_deps} adafruit/RTClib @ ^2.1.3 diff --git a/variants/nano_g2_ultra/nano-g2.cpp b/variants/nano_g2_ultra/nano-g2.cpp index 08a1b091..9a278287 100644 --- a/variants/nano_g2_ultra/nano-g2.cpp +++ b/variants/nano_g2_ultra/nano-g2.cpp @@ -34,6 +34,8 @@ void NanoG2Ultra::begin() pinMode(EXT_NOTIFY_OUT, OUTPUT); digitalWrite(EXT_NOTIFY_OUT, LOW); + pinMode(GPS_EN, OUTPUT); // Initialize GPS power pin + Wire.begin(); pinMode(SX126X_POWER_EN, OUTPUT); digitalWrite(SX126X_POWER_EN, HIGH); diff --git a/variants/nano_g2_ultra/nano-g2.h b/variants/nano_g2_ultra/nano-g2.h index 99dc75fa..884ed7f8 100644 --- a/variants/nano_g2_ultra/nano-g2.h +++ b/variants/nano_g2_ultra/nano-g2.h @@ -21,6 +21,8 @@ #define BUTTON_PIN PIN_BUTTON1 #define PIN_USER_BTN BUTTON_PIN +// GPS +#define GPS_EN PIN_GPS_STANDBY // built-ins #define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 diff --git a/variants/nano_g2_ultra/platformio.ini b/variants/nano_g2_ultra/platformio.ini index c2bb1f23..7fb2de28 100644 --- a/variants/nano_g2_ultra/platformio.ini +++ b/variants/nano_g2_ultra/platformio.ini @@ -34,10 +34,11 @@ extends = Nano_G2_Ultra build_flags = ${Nano_G2_Ultra.build_flags} -I src/helpers/ui + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 - -D BLE_DEBUG_LOGGING=1 +; -D BLE_DEBUG_LOGGING=0 -D OFFLINE_QUEUE_SIZE=256 -D DISPLAY_CLASS=SH1106Display -D PIN_BUZZER=4 @@ -47,7 +48,36 @@ build_src_filter = ${Nano_G2_Ultra.build_src_filter} + + + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Nano_G2_Ultra.lib_deps} + densaugeo/base64 @ ~1.4.0 + adafruit/Adafruit SH110X @ ~2.1.13 + adafruit/Adafruit GFX Library @ ^1.12.1 + stevemarple/MicroNMEA @ ^2.0.6 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:Nano_G2_Ultra_companion_radio_usb] +extends = Nano_G2_Ultra +build_flags = + ${Nano_G2_Ultra.build_flags} + -I src/helpers/ui + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=SH1106Display + -D PIN_BUZZER=4 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Nano_G2_Ultra.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Nano_G2_Ultra.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/nano_g2_ultra/target.cpp b/variants/nano_g2_ultra/target.cpp index a67085ce..81e7744f 100644 --- a/variants/nano_g2_ultra/target.cpp +++ b/variants/nano_g2_ultra/target.cpp @@ -1,5 +1,6 @@ -#include #include "target.h" + +#include #include #include @@ -16,126 +17,119 @@ NanoG2UltraSensorManager sensors = NanoG2UltraSensorManager(nmea); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; +MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif -bool radio_init() -{ +bool radio_init() { rtc_clock.begin(Wire); return radio.std_init(&SPI); } -uint32_t radio_get_rng_seed() -{ +uint32_t radio_get_rng_seed() { return radio.random(0x7FFFFFFF); } -void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) -{ +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setFrequency(freq); radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) -{ +void radio_set_tx_power(uint8_t dbm) { radio.setOutputPower(dbm); } -void NanoG2UltraSensorManager::start_gps() -{ - if (!gps_active) - { - MESH_DEBUG_PRINTLN("starting GPS"); - digitalWrite(PIN_GPS_STANDBY, HIGH); +void NanoG2UltraSensorManager::start_gps() { + MESH_DEBUG_PRINTLN("Starting GPS"); + if (!gps_active) { + digitalWrite(PIN_GPS_STANDBY, HIGH); // Wake GPS from standby + Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX); + Serial1.begin(9600); + MESH_DEBUG_PRINTLN("Waiting for gps to power up"); + delay(1000); gps_active = true; } + _location->begin(); } -void NanoG2UltraSensorManager::stop_gps() -{ - if (gps_active) - { - MESH_DEBUG_PRINTLN("stopping GPS"); - digitalWrite(PIN_GPS_STANDBY, LOW); +void NanoG2UltraSensorManager::stop_gps() { + MESH_DEBUG_PRINTLN("Stopping GPS"); + if (gps_active) { + digitalWrite(PIN_GPS_STANDBY, LOW); // sleep GPS gps_active = false; } + _location->stop(); } -bool NanoG2UltraSensorManager::begin() -{ - Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX); // be sure to tx into rx and rx into tx - Serial1.begin(115200); - - pinMode(PIN_GPS_STANDBY, OUTPUT); +bool NanoG2UltraSensorManager::begin() { digitalWrite(PIN_GPS_STANDBY, HIGH); // Wake GPS from standby - delay(500); + Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX); + Serial1.begin(9600); + MESH_DEBUG_PRINTLN("Checking GPS switch state"); + delay(1000); - // We'll consider GPS detected if we see any data on Serial1 - if (Serial1.available() > 0) - { - MESH_DEBUG_PRINTLN("GPS detected"); + // Check initial switch state to determine if GPS should be active + if (Serial1.available() > 0) { + MESH_DEBUG_PRINTLN("GPS was on at boot, GPS enabled"); + start_gps(); + } else { + MESH_DEBUG_PRINTLN("GPS was not on at boot, GPS disabled"); } - else - { - MESH_DEBUG_PRINTLN("No GPS detected"); - } - digitalWrite(GPS_EN, LOW); // Put GPS back into standby mode + return true; } -bool NanoG2UltraSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP &telemetry) -{ - if (requester_permissions & TELEM_PERM_LOCATION) - { // does requester have permission? +bool NanoG2UltraSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP &telemetry) { + if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission? telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); } return true; } -void NanoG2UltraSensorManager::loop() -{ +void NanoG2UltraSensorManager::loop() { static long next_gps_update = 0; + + if (!gps_active) { + return; // GPS is not active, skip further processing + } + _location->loop(); - if (millis() > next_gps_update && gps_active) // don't bother if gps position is not enabled - { - if (_location->isValid()) - { + + if (millis() > next_gps_update) { + if (_location->isValid()) { node_lat = ((double)_location->getLatitude()) / 1000000.; node_lon = ((double)_location->getLongitude()) / 1000000.; node_altitude = ((double)_location->getAltitude()) / 1000.0; - MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); + MESH_DEBUG_PRINTLN("VALID location: lat %f lon %f", node_lat, node_lon); + } else { + MESH_DEBUG_PRINTLN("INVALID location, waiting for fix"); } - next_gps_update = millis() + (1000 * 60); // after initial update, only check every minute TODO: should be configurable + MESH_DEBUG_PRINTLN("GPS satellites: %d", _location->satellitesCount()); + next_gps_update = millis() + 1000; } } -int NanoG2UltraSensorManager::getNumSettings() const { return 1; } // just one supported: "gps" (power switch) +int NanoG2UltraSensorManager::getNumSettings() const { + return 1; +} // just one supported: "gps" (power switch) -const char *NanoG2UltraSensorManager::getSettingName(int i) const -{ +const char *NanoG2UltraSensorManager::getSettingName(int i) const { return i == 0 ? "gps" : NULL; } -const char *NanoG2UltraSensorManager::getSettingValue(int i) const -{ - if (i == 0) - { +const char *NanoG2UltraSensorManager::getSettingValue(int i) const { + if (i == 0) { return gps_active ? "1" : "0"; } return NULL; } -bool NanoG2UltraSensorManager::setSettingValue(const char *name, const char *value) -{ - if (strcmp(name, "gps") == 0) - { - if (strcmp(value, "0") == 0) - { +bool NanoG2UltraSensorManager::setSettingValue(const char *name, const char *value) { + if (strcmp(name, "gps") == 0) { + if (strcmp(value, "0") == 0) { stop_gps(); - } - else - { + } else { start_gps(); } return true; @@ -143,8 +137,7 @@ bool NanoG2UltraSensorManager::setSettingValue(const char *name, const char *val return false; // not supported } -mesh::LocalIdentity radio_new_identity() -{ +mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } diff --git a/variants/nano_g2_ultra/target.h b/variants/nano_g2_ultra/target.h index 5cde6405..3e58b900 100644 --- a/variants/nano_g2_ultra/target.h +++ b/variants/nano_g2_ultra/target.h @@ -1,13 +1,15 @@ #pragma once #define RADIOLIB_STATIC_ONLY 1 -#include #include "nano-g2.h" -#include -#include + +#include #include #include +#include +#include #ifdef DISPLAY_CLASS +#include #include #endif #include @@ -37,6 +39,7 @@ extern NanoG2UltraSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; +extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/picow/platformio.ini b/variants/picow/platformio.ini index 8b6c2506..7b75c224 100644 --- a/variants/picow/platformio.ini +++ b/variants/picow/platformio.ini @@ -52,7 +52,7 @@ build_flags = ${picow.build_flags} ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${picow.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${picow.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -66,7 +66,7 @@ lib_deps = ${picow.lib_deps} ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${picow.build_src_filter} -; +<../examples/companion_radio> +; +<../examples/companion_radio/*.cpp> ; lib_deps = ${picow.lib_deps} ; densaugeo/base64 @ ~1.4.0 @@ -81,7 +81,7 @@ lib_deps = ${picow.lib_deps} ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${picow.build_src_filter} -; +<../examples/companion_radio> +; +<../examples/companion_radio/*.cpp> ; lib_deps = ${picow.lib_deps} ; densaugeo/base64 @ ~1.4.0 diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 78b25aa7..6b77bdcf 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -39,6 +39,7 @@ extends = Faketec build_src_filter = ${Faketec.build_src_filter} +<../examples/simple_repeater> + + + build_flags = ${Faketec.build_flags} -D ADVERT_NAME='"Faketec Repeater"' @@ -57,6 +58,7 @@ extends = Faketec build_src_filter = ${Faketec.build_src_filter} +<../examples/simple_room_server> + + + build_flags = ${Faketec.build_flags} -D ADVERT_NAME='"Faketec Room"' -D ADVERT_LAT=0.0 @@ -85,14 +87,17 @@ lib_deps = ${Faketec.lib_deps} [env:Faketec_companion_radio_usb] extends = Faketec build_flags = ${Faketec.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D DISPLAY_CLASS=SSD1306Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Faketec.build_src_filter} - +<../examples/companion_radio> + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 @@ -100,6 +105,7 @@ lib_deps = ${Faketec.lib_deps} [env:Faketec_companion_radio_ble] extends = Faketec build_flags = ${Faketec.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -110,8 +116,10 @@ build_flags = ${Faketec.build_flags} -D MESH_DEBUG=1 build_src_filter = ${Faketec.build_src_filter} + - +<../examples/companion_radio> + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 @@ -129,6 +137,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Faketec.build_src_filter} + + + +<../examples/simple_sensor> lib_deps = ${Faketec.lib_deps} diff --git a/variants/promicro/target.cpp b/variants/promicro/target.cpp index 03a5a16a..b26320e4 100644 --- a/variants/promicro/target.cpp +++ b/variants/promicro/target.cpp @@ -20,6 +20,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true); #endif bool radio_init() { diff --git a/variants/promicro/target.h b/variants/promicro/target.h index de2719e6..38c4b4e8 100644 --- a/variants/promicro/target.h +++ b/variants/promicro/target.h @@ -8,6 +8,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif #include @@ -19,6 +20,7 @@ extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/rak3x72/target.h b/variants/rak3x72/target.h index 61e4747d..e0c1441e 100644 --- a/variants/rak3x72/target.h +++ b/variants/rak3x72/target.h @@ -13,6 +13,12 @@ class RAK3x72Board : public STM32Board { public: + void begin() override { + STM32Board::begin(); + pinMode(PA0, OUTPUT); + pinMode(PA1, OUTPUT); + } + const char* getManufacturerName() const override { return "RAK 3x72"; } @@ -25,6 +31,17 @@ public: } return ((double)raw) * ADC_MULTIPLIER / 8 / 4096; } + + void setGpio(uint32_t values) override { + // set led values + digitalWrite(PA0, values & 1); + digitalWrite(PA1, (values & 2) >> 1); + } + + uint32_t getGpio() override { + // get led value + return (digitalRead(PA1) << 1) | digitalRead(PA0); + } }; extern RAK3x72Board board; diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index ba4a8e2f..c9091878 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -4,6 +4,7 @@ platform = https://github.com/maxgerhardt/platform-nordicnrf52.git#rak board = wiscore_rak4631 board_check = true build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} -I variants/rak4631 -D RAK_4631 -D RAK_BOARD @@ -18,33 +19,16 @@ build_flags = ${nrf52_base.build_flags} -D LORA_TX_POWER=22 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 - -D ENV_INCLUDE_GPS=1 - -D ENV_INCLUDE_AHTX0=1 - -D ENV_INCLUDE_BME280=1 - -D ENV_INCLUDE_BMP280=1 - -D ENV_INCLUDE_SHTC3=1 - -D ENV_INCLUDE_LPS22HB=1 - -D ENV_INCLUDE_INA3221=1 - -D ENV_INCLUDE_INA219=1 - -D ENV_INCLUDE_INA260=1 - -D ENV_INCLUDE_SHT4X=1 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631> + + + + + lib_deps = ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} adafruit/Adafruit SSD1306 @ ^2.5.13 - stevemarple/MicroNMEA @ ^2.0.6 - arduino-libraries/Arduino_LPS22HB@^1.0.2 - adafruit/Adafruit INA3221 Library @ ^1.0.1 - adafruit/Adafruit INA219 @ ^1.2.3 - adafruit/Adafruit AHTX0 @ ^2.0.5 - adafruit/Adafruit BME280 Library @ ^2.3.0 - adafruit/Adafruit BMP280 Library @ ^2.6.8 - adafruit/Adafruit SHTC3 Library @ ^1.0.1 - adafruit/Adafruit INA260 Library @ ^1.5.3 - sparkfun/SparkFun u-blox GNSS Arduino Library @ ^2.2.27 - sensirion/Sensirion I2C SHT4x @ ^1.1.2 + sparkfun/SparkFun u-blox GNSS Arduino Library@^2.2.27 [env:RAK_4631_Repeater] extends = rak4631 @@ -82,6 +66,7 @@ build_src_filter = ${rak4631.build_src_filter} extends = rak4631 build_flags = ${rak4631.build_flags} + -I examples/companion_radio/ui-new -D PIN_USER_BTN=9 -D PIN_USER_BTN_ANA=31 -D DISPLAY_CLASS=SSD1306Display @@ -90,8 +75,8 @@ build_flags = ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${rak4631.build_src_filter} - + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${rak4631.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -100,6 +85,7 @@ lib_deps = extends = rak4631 build_flags = ${rak4631.build_flags} + -I examples/companion_radio/ui-new -D PIN_USER_BTN=9 -D PIN_USER_BTN_ANA=31 -D DISPLAY_CLASS=SSD1306Display @@ -111,9 +97,9 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${rak4631.build_src_filter} - + + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${rak4631.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -133,3 +119,18 @@ build_src_filter = ${rak4631.build_src_filter} lib_deps = ${rak4631.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:RAK_4631_sensor] +extends = rak4631 +build_flags = + ${rak4631.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RAK4631 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 +build_src_filter = ${rak4631.build_src_filter} + + + +<../examples/simple_sensor> \ No newline at end of file diff --git a/variants/rak4631/target.cpp b/variants/rak4631/target.cpp index 4e9d3cce..618c9fc5 100644 --- a/variants/rak4631/target.cpp +++ b/variants/rak4631/target.cpp @@ -4,8 +4,13 @@ RAK4631Board board; +#ifndef PIN_USER_BTN + #define PIN_USER_BTN (-1) +#endif + #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); diff --git a/variants/rak4631/target.h b/variants/rak4631/target.h index c4c88183..5e93b7fc 100644 --- a/variants/rak4631/target.h +++ b/variants/rak4631/target.h @@ -11,6 +11,8 @@ #ifdef DISPLAY_CLASS #include extern DISPLAY_CLASS display; + #include + extern MomentaryButton user_btn; #endif extern RAK4631Board board; diff --git a/variants/sensecap_solar/platformio.ini b/variants/sensecap_solar/platformio.ini index 9626e9cd..bbac5d99 100644 --- a/variants/sensecap_solar/platformio.ini +++ b/variants/sensecap_solar/platformio.ini @@ -85,7 +85,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${SenseCap_Solar.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${SenseCap_Solar.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -100,7 +100,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${SenseCap_Solar.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${SenseCap_Solar.lib_deps} densaugeo/base64 @ ~1.4.0 \ No newline at end of file diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index 5d9a6823..3ed73f43 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -22,6 +22,7 @@ build_flags = build_src_filter = ${esp32_base.build_src_filter} +<../variants/station_g2> + + + lib_deps = ${esp32_base.lib_deps} adafruit/Adafruit SH110X @ ~2.1.13 @@ -65,14 +66,16 @@ lib_deps = extends = Station_G2 build_flags = ${Station_G2.build_flags} + -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SH1106Display -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Station_G2.build_src_filter} - + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Station_G2.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -81,6 +84,7 @@ lib_deps = extends = Station_G2 build_flags = ${Station_G2.build_flags} + -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SH1106Display -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 @@ -91,8 +95,8 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Station_G2.build_src_filter} + - + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Station_G2.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/station_g2/target.cpp b/variants/station_g2/target.cpp index 2b19f5f0..5423af68 100644 --- a/variants/station_g2/target.cpp +++ b/variants/station_g2/target.cpp @@ -18,10 +18,7 @@ SensorManager sensors; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; -#endif - -#ifndef LORA_CR - #define LORA_CR 5 + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/station_g2/target.h b/variants/station_g2/target.h index 6d80f098..3f67af3a 100644 --- a/variants/station_g2/target.h +++ b/variants/station_g2/target.h @@ -10,6 +10,7 @@ #ifdef DISPLAY_CLASS #include + #include #endif extern StationG2Board board; @@ -19,6 +20,7 @@ extern SensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index 24584757..f87d71e0 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -84,12 +84,21 @@ public: digitalWrite(PIN_3V3_EN, LOW); #endif + // set led on and wait for button release before poweroff + #ifdef LED_PIN + digitalWrite(LED_PIN, HIGH); + #endif + #ifdef BUTTON_PIN + while(digitalRead(BUTTON_PIN)); + #endif #ifdef LED_PIN digitalWrite(LED_PIN, LOW); #endif + #ifdef BUTTON_PIN nrf_gpio_cfg_sense_input(digitalPinToInterrupt(BUTTON_PIN), NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_HIGH); #endif + sd_power_system_off(); } diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index 00974208..2811e243 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -37,6 +37,7 @@ upload_protocol = nrfutil [env:t1000e_companion_radio_ble] extends = t1000-e build_flags = ${t1000-e.build_flags} + -I examples/companion_radio/ui-orig -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -53,6 +54,7 @@ build_src_filter = ${t1000-e.build_src_filter} + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${t1000-e.lib_deps} densaugeo/base64 @ ~1.4.0 stevemarple/MicroNMEA @ ^2.0.6 diff --git a/variants/t114/platformio.ini b/variants/t114/platformio.ini index dac12da9..e48ee121 100644 --- a/variants/t114/platformio.ini +++ b/variants/t114/platformio.ini @@ -30,6 +30,7 @@ build_src_filter = ${nrf52840_t114.build_src_filter} + +<../variants/t114> + + + + + lib_deps = @@ -72,6 +73,7 @@ build_flags = extends = Heltec_t114 build_flags = ${Heltec_t114.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -81,7 +83,8 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_t114.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Heltec_t114.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -90,6 +93,7 @@ lib_deps = extends = Heltec_t114 build_flags = ${Heltec_t114.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 ; -D BLE_PIN_CODE=123456 @@ -98,7 +102,8 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_t114.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Heltec_t114.lib_deps} densaugeo/base64 @ ~1.4.0 \ No newline at end of file diff --git a/variants/t114/target.cpp b/variants/t114/target.cpp index d97c03f6..d2fa6c4c 100644 --- a/variants/t114/target.cpp +++ b/variants/t114/target.cpp @@ -16,6 +16,7 @@ T114SensorManager sensors = T114SensorManager(nmea); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/t114/target.h b/variants/t114/target.h index 8831d9f7..35e86f60 100644 --- a/variants/t114/target.h +++ b/variants/t114/target.h @@ -10,6 +10,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif class T114SensorManager : public SensorManager { @@ -37,6 +38,7 @@ extern T114SensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/techo/platformio.ini b/variants/techo/platformio.ini index 8ad5ca03..76712178 100644 --- a/variants/techo/platformio.ini +++ b/variants/techo/platformio.ini @@ -21,6 +21,7 @@ build_flags = ${nrf52840_techo.build_flags} -D LORA_TX_POWER=22 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 + -D P_LORA_TX_LED=LED_GREEN build_src_filter = ${nrf52840_techo.build_src_filter} + + @@ -61,19 +62,23 @@ extends = LilyGo_Techo build_flags = ${LilyGo_Techo.build_flags} -I src/helpers/ui + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D DISPLAY_CLASS=GxEPDDisplay -D OFFLINE_QUEUE_SIZE=256 + -D UI_RECENT_LIST_SIZE=9 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_Techo.build_src_filter} + + + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_Techo.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/techo/target.cpp b/variants/techo/target.cpp index 1e413531..9a10491d 100644 --- a/variants/techo/target.cpp +++ b/variants/techo/target.cpp @@ -16,6 +16,7 @@ TechoSensorManager sensors = TechoSensorManager(nmea); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/techo/target.h b/variants/techo/target.h index 7c05e742..58fba687 100644 --- a/variants/techo/target.h +++ b/variants/techo/target.h @@ -10,6 +10,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif class TechoSensorManager : public SensorManager { @@ -36,6 +37,7 @@ extern TechoSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/techo/variant.cpp b/variants/techo/variant.cpp index ad1fd560..0bad7829 100644 --- a/variants/techo/variant.cpp +++ b/variants/techo/variant.cpp @@ -24,6 +24,8 @@ void initVariant() { pinMode(LED_GREEN, OUTPUT); pinMode(LED_BLUE, OUTPUT); digitalWrite(LED_BLUE, HIGH); + digitalWrite(LED_GREEN, HIGH); + digitalWrite(LED_RED, HIGH); pinMode(PIN_TXCO, OUTPUT); digitalWrite(PIN_TXCO, HIGH); diff --git a/variants/techo/variant.h b/variants/techo/variant.h index 6aebf82f..da8d81d4 100644 --- a/variants/techo/variant.h +++ b/variants/techo/variant.h @@ -61,19 +61,15 @@ //////////////////////////////////////////////////////////////////////////////// // Builtin LEDs -#define LED_RED (34) -#define LED_GREEN (33) +#define LED_RED (13) #define LED_BLUE (14) +#define LED_GREEN (15) -#define PIN_STATUS_LED LED_GREEN -#define LED_BUILTIN LED_GREEN -#define PIN_LED LED_BUILTIN +//#define PIN_STATUS_LED LED_BLUE +#define LED_BUILTIN (-1) #define LED_PIN LED_BUILTIN #define LED_STATE_ON LOW -#define PIN_NEOPIXEL (14) -#define NEOPIXEL_NUM (2) - //////////////////////////////////////////////////////////////////////////////// // Builtin buttons diff --git a/variants/thinknode_m1/platformio.ini b/variants/thinknode_m1/platformio.ini index 2104a080..fa8cd55b 100644 --- a/variants/thinknode_m1/platformio.ini +++ b/variants/thinknode_m1/platformio.ini @@ -68,6 +68,7 @@ extends = ThinkNode_M1 build_flags = ${ThinkNode_M1.build_flags} -I src/helpers/ui + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -83,7 +84,9 @@ build_src_filter = ${ThinkNode_M1.build_src_filter} + + + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${ThinkNode_M1.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/thinknode_m1/target.cpp b/variants/thinknode_m1/target.cpp index 19230232..2b04d7c6 100644 --- a/variants/thinknode_m1/target.cpp +++ b/variants/thinknode_m1/target.cpp @@ -16,6 +16,7 @@ ThinkNodeM1SensorManager sensors = ThinkNodeM1SensorManager(nmea); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/thinknode_m1/target.h b/variants/thinknode_m1/target.h index c938d422..eac221c3 100644 --- a/variants/thinknode_m1/target.h +++ b/variants/thinknode_m1/target.h @@ -10,6 +10,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif class ThinkNodeM1SensorManager : public SensorManager { @@ -37,6 +38,7 @@ extern ThinkNodeM1SensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/waveshare_rp2040_lora/platformio.ini b/variants/waveshare_rp2040_lora/platformio.ini index 515f7020..60b39ad6 100644 --- a/variants/waveshare_rp2040_lora/platformio.ini +++ b/variants/waveshare_rp2040_lora/platformio.ini @@ -77,7 +77,7 @@ build_flags = ${waveshare_rp2040_lora.build_flags} ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${waveshare_rp2040_lora.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${waveshare_rp2040_lora.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -91,7 +91,7 @@ lib_deps = ${waveshare_rp2040_lora.lib_deps} ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${waveshare_rp2040_lora.build_src_filter} -; +<../examples/companion_radio> +; +<../examples/companion_radio/*.cpp> ; lib_deps = ${waveshare_rp2040_lora.lib_deps} ; densaugeo/base64 @ ~1.4.0 @@ -106,7 +106,7 @@ lib_deps = ${waveshare_rp2040_lora.lib_deps} ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${waveshare_rp2040_lora.build_src_filter} -; +<../examples/companion_radio> +; +<../examples/companion_radio/*.cpp> ; lib_deps = ${waveshare_rp2040_lora.lib_deps} ; densaugeo/base64 @ ~1.4.0 diff --git a/variants/waveshare_rp2040_lora/target.cpp b/variants/waveshare_rp2040_lora/target.cpp index 895f6db2..7bc1d043 100644 --- a/variants/waveshare_rp2040_lora/target.cpp +++ b/variants/waveshare_rp2040_lora/target.cpp @@ -12,19 +12,9 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; -#ifndef LORA_CR -#define LORA_CR 5 -#endif - bool radio_init() { rtc_clock.begin(Wire); -#ifdef SX126X_DIO3_TCXO_VOLTAGE - float tcxo = SX126X_DIO3_TCXO_VOLTAGE; -#else - float tcxo = 1.6f; -#endif - SPI1.setSCK(P_LORA_SCLK); SPI1.setTX(P_LORA_MOSI); SPI1.setRX(P_LORA_MISO); @@ -34,30 +24,8 @@ bool radio_init() { SPI1.begin(false); - int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, - LORA_TX_POWER, 8, tcxo); - - if (status != RADIOLIB_ERR_NONE) { - Serial.print("ERROR: radio init failed: "); - Serial.println(status); - return false; // fail - } - - radio.setCRC(1); - -#ifdef SX126X_CURRENT_LIMIT - radio.setCurrentLimit(SX126X_CURRENT_LIMIT); -#endif - -#ifdef SX126X_DIO2_AS_RF_SWITCH - radio.setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); -#endif - -#ifdef SX126X_RX_BOOSTED_GAIN - radio.setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); -#endif - - return true; // success + //passing NULL skips init of SPI + return radio.std_init(NULL); } uint32_t radio_get_rng_seed() { diff --git a/variants/wio-e5-mini/platformio.ini b/variants/wio-e5-mini/platformio.ini index 93508d8e..3d98d93e 100644 --- a/variants/wio-e5-mini/platformio.ini +++ b/variants/wio-e5-mini/platformio.ini @@ -37,11 +37,13 @@ build_src_filter = ${lora_e5_mini.build_src_filter} [env:wio-e5-mini_companion_radio_usb] extends = lora_e5_mini build_flags = ${lora_e5_mini.build_flags} + -I examples/companion_radio/ui-orig -D LORA_TX_POWER=22 -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${lora_e5_mini.build_src_filter} +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${lora_e5_mini.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.h b/variants/wio-tracker-l1/WioTrackerL1Board.h index 03aef79c..f04b673f 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.h +++ b/variants/wio-tracker-l1/WioTrackerL1Board.h @@ -38,5 +38,9 @@ public: NVIC_SystemReset(); } + void powerOff() override { + sd_power_system_off(); + } + bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 380ff90f..ddf7cf47 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -56,13 +56,16 @@ lib_deps = ${WioTrackerL1.lib_deps} [env:WioTrackerL1_companion_radio_usb] extends = WioTrackerL1 build_flags = ${WioTrackerL1.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D DISPLAY_CLASS=SH1106Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${WioTrackerL1.build_src_filter} - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> + + lib_deps = ${WioTrackerL1.lib_deps} @@ -73,6 +76,7 @@ lib_deps = ${WioTrackerL1.lib_deps} [env:WioTrackerL1_companion_radio_ble] extends = WioTrackerL1 build_flags = ${WioTrackerL1.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -84,8 +88,10 @@ build_flags = ${WioTrackerL1.build_flags} -D PIN_BUZZER=12 build_src_filter = ${WioTrackerL1.build_src_filter} + - +<../examples/companion_radio> + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${WioTrackerL1.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 diff --git a/variants/wio-tracker-l1/target.cpp b/variants/wio-tracker-l1/target.cpp index 0809e19e..349d73b4 100644 --- a/variants/wio-tracker-l1/target.cpp +++ b/variants/wio-tracker-l1/target.cpp @@ -16,6 +16,9 @@ WioTrackerL1SensorManager sensors = WioTrackerL1SensorManager(nmea); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); + MomentaryButton joystick_left(JOYSTICK_LEFT, 1000, true); + MomentaryButton joystick_right(JOYSTICK_RIGHT, 1000, true); #endif bool radio_init() { diff --git a/variants/wio-tracker-l1/target.h b/variants/wio-tracker-l1/target.h index ab42b7b5..6f5da7c6 100644 --- a/variants/wio-tracker-l1/target.h +++ b/variants/wio-tracker-l1/target.h @@ -9,6 +9,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif #include @@ -38,6 +39,9 @@ extern AutoDiscoverRTCClock rtc_clock; extern WioTrackerL1SensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; + extern MomentaryButton joystick_left; + extern MomentaryButton joystick_right; #endif bool radio_init(); diff --git a/variants/wio-tracker-l1/variant.h b/variants/wio-tracker-l1/variant.h index 094f8edf..af01177e 100644 --- a/variants/wio-tracker-l1/variant.h +++ b/variants/wio-tracker-l1/variant.h @@ -91,12 +91,12 @@ #define PIN_GPS_EN (18) // QSPI Pins -#define PIN_QSPI_SCK (21) -#define PIN_QSPI_CS (22) -#define PIN_QSPI_IO0 (23) -#define PIN_QSPI_IO1 (24) -#define PIN_QSPI_IO2 (25) -#define PIN_QSPI_IO3 (26) +#define PIN_QSPI_SCK (19) +#define PIN_QSPI_CS (20) +#define PIN_QSPI_IO0 (21) +#define PIN_QSPI_IO1 (22) +#define PIN_QSPI_IO2 (23) +#define PIN_QSPI_IO3 (24) #define EXTERNAL_FLASH_DEVICES P25Q16H #define EXTERNAL_FLASH_USE_QSPI diff --git a/variants/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index 3e4bfdb4..659313db 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -61,11 +61,12 @@ build_flags = lib_deps = ${Xiao_esp32_C3.lib_deps} ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 [env:Xiao_C3_companion_radio_ble] extends = Xiao_esp32_C3 build_src_filter = ${Xiao_esp32_C3.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + build_flags = ${Xiao_esp32_C3.build_flags} @@ -88,7 +89,7 @@ lib_deps = [env:Xiao_C3_companion_radio_usb] extends = Xiao_esp32_C3 build_src_filter = ${Xiao_esp32_C3.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + build_flags = ${Xiao_esp32_C3.build_flags} @@ -127,6 +128,7 @@ build_flags = lib_deps = ${Xiao_esp32_C3_custom.lib_deps} ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 [env:Xiao_C3_Repeater_sx1268_custom] extends = Xiao_esp32_C3_custom @@ -146,4 +148,5 @@ build_flags = ; -D MESH_DEBUG=1 lib_deps = ${Xiao_esp32_C3_custom.lib_deps} - ${esp32_ota.lib_deps} \ No newline at end of file + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 diff --git a/variants/xiao_c6/target.cpp b/variants/xiao_c6/XiaoC6Board.cpp similarity index 98% rename from variants/xiao_c6/target.cpp rename to variants/xiao_c6/XiaoC6Board.cpp index caca57bc..555fed62 100644 --- a/variants/xiao_c6/target.cpp +++ b/variants/xiao_c6/XiaoC6Board.cpp @@ -1,7 +1,7 @@ #include #include "target.h" -ESP32Board board; +XiaoC6Board board; #if defined(P_LORA_SCLK) static SPIClass spi(0); @@ -47,3 +47,5 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } + + diff --git a/variants/xiao_c6/XiaoC6Board.h b/variants/xiao_c6/XiaoC6Board.h new file mode 100644 index 00000000..86c3475c --- /dev/null +++ b/variants/xiao_c6/XiaoC6Board.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +class XiaoC6Board : public ESP32Board { +public: + void begin() { + ESP32Board::begin(); + +#ifdef USE_XIAO_ESP32C6_EXTERNAL_ANTENNA +// Connect an external antenna to your XIAO ESP32C6 otherwise, it may be damaged! + pinMode(3, OUTPUT); + digitalWrite(3, LOW); // Activate RF switch control + + delay(100); + + pinMode(14, OUTPUT); + digitalWrite(14, HIGH); // Use external antenna +#endif + } + + const char* getManufacturerName() const override { + return "Xiao C6"; + } +}; + + diff --git a/variants/xiao_c6/platformio.ini b/variants/xiao_c6/platformio.ini index bc1c789b..fdf0f337 100644 --- a/variants/xiao_c6/platformio.ini +++ b/variants/xiao_c6/platformio.ini @@ -28,6 +28,7 @@ build_flags = -D DISABLE_WIFI_OTA=1 build_src_filter = ${esp32c6_base.build_src_filter} +<../variants/xiao_c6> + + [env:Xiao_C6_Repeater] extends = Xiao_C6 @@ -61,7 +62,62 @@ build_flags = ${Xiao_C6.build_flags} build_src_filter = ${Xiao_C6.build_src_filter} + - - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${Xiao_C6.lib_deps} densaugeo/base64 @ ~1.4.0 + +; Meshimi variant +[Meshimi] +extends = Xiao_C6 +board_build.partitions = max_app_4MB.csv +build_flags = + ${Xiao_C6.build_flags} + -D P_LORA_TX_LED=15 + -D P_LORA_SCLK=19 + -D P_LORA_MISO=20 + -D P_LORA_MOSI=18 + -D P_LORA_NSS=21 + -D P_LORA_DIO_1=7 + -D P_LORA_BUSY=6 + -D P_LORA_RESET=2 + -D PIN_BOARD_SDA=22 + -D PIN_BOARD_SCL=23 + -D SX126X_RXEN=4 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D USE_XIAO_ESP32C6_EXTERNAL_ANTENNA=1 + +[env:Meshimi_Repeater] +extends = Meshimi +build_src_filter = ${Meshimi.build_src_filter} + +<../examples/simple_repeater/main.cpp> +build_flags = + ${Meshimi.build_flags} + -D ADVERT_NAME='"Meshimi Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 +lib_deps = + ${Meshimi.lib_deps} + +[env:Meshimi_companion_radio_ble] +extends = Meshimi +build_flags = ${Meshimi.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 + -D ENABLE_PRIVATE_KEY_IMPORT=1 + -D ENABLE_PRIVATE_KEY_EXPORT=1 +build_src_filter = ${Meshimi.build_src_filter} + + + - + +<../examples/companion_radio/*.cpp> +lib_deps = + ${Meshimi.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/xiao_c6/target.h b/variants/xiao_c6/target.h index c26d5958..0fbb0bb2 100644 --- a/variants/xiao_c6/target.h +++ b/variants/xiao_c6/target.h @@ -2,13 +2,14 @@ #define RADIOLIB_STATIC_ONLY 1 #include +#include #include #include #include #include #include -extern ESP32Board board; +extern XiaoC6Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index bba3e632..fd4c362b 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -68,7 +68,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Xiao_nrf52.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${Xiao_nrf52.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -83,7 +83,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Xiao_nrf52.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${Xiao_nrf52.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/xiao_rp2040/platformio.ini b/variants/xiao_rp2040/platformio.ini index 960fdbba..619350ec 100644 --- a/variants/xiao_rp2040/platformio.ini +++ b/variants/xiao_rp2040/platformio.ini @@ -58,7 +58,7 @@ build_flags = ${Xiao_rp2040.build_flags} ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Xiao_rp2040.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${Xiao_rp2040.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -72,7 +72,7 @@ lib_deps = ${Xiao_rp2040.lib_deps} ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${Xiao_rp2040.build_src_filter} -; +<../examples/companion_radio> +; +<../examples/companion_radio/*.cpp> ; lib_deps = ${Xiao_rp2040.lib_deps} ; densaugeo/base64 @ ~1.4.0 @@ -87,7 +87,7 @@ lib_deps = ${Xiao_rp2040.lib_deps} ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${Xiao_rp2040.build_src_filter} -; +<../examples/companion_radio> +; +<../examples/companion_radio/*.cpp> ; lib_deps = ${Xiao_rp2040.lib_deps} ; densaugeo/base64 @ ~1.4.0 diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index 4d6fed88..fe4670b5 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -79,6 +79,7 @@ lib_deps = extends = Xiao_S3_WIO build_flags = ${Xiao_S3_WIO.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -90,7 +91,9 @@ build_flags = build_src_filter = ${Xiao_S3_WIO.build_src_filter} + + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Xiao_S3_WIO.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -108,7 +111,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Xiao_S3_WIO.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${Xiao_S3_WIO.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/xiao_s3_wio/target.cpp b/variants/xiao_s3_wio/target.cpp index 4b2b059f..ed8584ff 100644 --- a/variants/xiao_s3_wio/target.cpp +++ b/variants/xiao_s3_wio/target.cpp @@ -18,6 +18,7 @@ SensorManager sensors; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/xiao_s3_wio/target.h b/variants/xiao_s3_wio/target.h index b768b2b0..f184c757 100644 --- a/variants/xiao_s3_wio/target.h +++ b/variants/xiao_s3_wio/target.h @@ -9,6 +9,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif extern ESP32Board board; @@ -18,6 +19,7 @@ extern SensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init();