From 811ea175fa46d7d7505cdb964332729e62377acc Mon Sep 17 00:00:00 2001 From: uncle lit <43320854+LitBomb@users.noreply.github.com> Date: Tue, 1 Jul 2025 22:04:16 -0700 Subject: [PATCH 01/77] Update faq.md - rewrite Linux firmware update with more detailed instructions - fix T114 reset typo: double click twice, not once - add link reference to MeshCore logo on github - add public channel key for t-deck to the t-deck section - reference the verbiage "what is meshcore?" on meshcore.co.uk homepage --- docs/faq.md | 138 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 87 insertions(+), 51 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 1c67c9c7..ab7c63a0 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 @@ -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://...") @@ -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 --- From 62f1ab4b069e406b2a1281180fd389422f36e1e6 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Sun, 6 Jul 2025 17:40:52 +0200 Subject: [PATCH 02/77] remove hardware compatibility list - it was outdated. --- README.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/README.md b/README.md index 708db2c4..281c9502 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 From 6aa41bd67d85e20f9cbf48e90e719439a9f75fe5 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Fri, 11 Jul 2025 11:37:01 +0200 Subject: [PATCH 03/77] faq: fix broken links, replace discord link from Andy's discord to MeshCore discord --- docs/faq.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index ab7c63a0..6dc8fe9e 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -179,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 @@ -271,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. @@ -396,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 From 10bb05c31a5e41d24c5a29721478c49dae5ec89f Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Fri, 11 Jul 2025 11:39:50 +0200 Subject: [PATCH 04/77] replaced Andy Kirby's discord with MeshCore discord. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 281c9502..fa5b3cd0 100644 --- a/README.md +++ b/README.md @@ -94,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 From b5a8a1a8836cdd23312f995d01d481718c1c5935 Mon Sep 17 00:00:00 2001 From: Florent Date: Tue, 22 Jul 2025 21:08:15 +0200 Subject: [PATCH 05/77] sensors: gpio command --- examples/simple_sensor/SensorMesh.cpp | 11 ++++++++++- src/MeshCore.h | 2 ++ variants/rak3x72/target.h | 17 +++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index abdc7182..38af81e6 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -545,7 +545,16 @@ void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* r Serial.printf("\n"); } reply[0] = 0; - } else { + } else if (memcmp(command, "gpio ", 4) == 0) { // gpio {value}: write, gpio: read + if (command[4] == ' ') { // it's a write + uint32_t val; + sscanf(&command[5], "%x", &val); + board.setGpio(val); + strcpy(reply, "Ok"); + } else { + sprintf(reply, "%x", board.getGpio()); + } + } else{ _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands } } 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/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; From 479b8ed0ce9add21f647cc5300107f2bd2cf25b5 Mon Sep 17 00:00:00 2001 From: Florent Date: Wed, 23 Jul 2025 08:02:57 +0200 Subject: [PATCH 06/77] sensors: gpio apply masks --- examples/simple_sensor/SensorMesh.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 38af81e6..24e6b62b 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -545,15 +545,25 @@ void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* r Serial.printf("\n"); } reply[0] = 0; - } else if (memcmp(command, "gpio ", 4) == 0) { // gpio {value}: write, gpio: read - if (command[4] == ' ') { // it's a write + } else if (memcmp(command, "io ", 2) == 0) { // io {value}: write, io: read + if (command[2] == ' ') { // it's a write uint32_t val; - sscanf(&command[5], "%x", &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); - strcpy(reply, "Ok"); - } else { - sprintf(reply, "%x", board.getGpio()); } + sprintf(reply, "%x", board.getGpio()); } else{ _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands } From 55453e1136441900b4aaa019f2d06ce4ae3d1aa9 Mon Sep 17 00:00:00 2001 From: TasmanDevil Date: Fri, 25 Jul 2025 22:53:56 +0200 Subject: [PATCH 07/77] disable led light on t-echo device --- variants/techo/variant.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/techo/variant.h b/variants/techo/variant.h index 6aebf82f..59f69161 100644 --- a/variants/techo/variant.h +++ b/variants/techo/variant.h @@ -61,9 +61,9 @@ //////////////////////////////////////////////////////////////////////////////// // Builtin LEDs -#define LED_RED (34) -#define LED_GREEN (33) -#define LED_BLUE (14) +#define LED_RED (-1) +#define LED_GREEN (-1) +#define LED_BLUE (-1) #define PIN_STATUS_LED LED_GREEN #define LED_BUILTIN LED_GREEN From b0946b3f6b6c74188381a740524b12b7079c5824 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 27 Jul 2025 20:38:20 +1000 Subject: [PATCH 08/77] Revert "disable led light on t-echo device" This reverts commit 55453e1136441900b4aaa019f2d06ce4ae3d1aa9. --- variants/techo/variant.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/techo/variant.h b/variants/techo/variant.h index 59f69161..6aebf82f 100644 --- a/variants/techo/variant.h +++ b/variants/techo/variant.h @@ -61,9 +61,9 @@ //////////////////////////////////////////////////////////////////////////////// // Builtin LEDs -#define LED_RED (-1) -#define LED_GREEN (-1) -#define LED_BLUE (-1) +#define LED_RED (34) +#define LED_GREEN (33) +#define LED_BLUE (14) #define PIN_STATUS_LED LED_GREEN #define LED_BUILTIN LED_GREEN From dca20ea994723441803c9f330004467fd273793a Mon Sep 17 00:00:00 2001 From: 446564 Date: Tue, 29 Jul 2025 15:56:07 -0700 Subject: [PATCH 09/77] add wsl3 usb companion --- variants/heltec_v3/platformio.ini | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 67415ae9..97eef510 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -223,6 +223,20 @@ 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> +lib_deps = + ${Heltec_lora32_v3.lib_deps} + densaugeo/base64 @ ~1.4.0 + [env:Heltec_WSL3_sensor] extends = Heltec_lora32_v3 build_flags = From f66d900ae29a92810db0d041612cb6994a18bdea Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 30 Jul 2025 14:57:38 +1000 Subject: [PATCH 10/77] * companion: bug fix for CMD_ADD_UPDATE_CONTACT --- examples/companion_radio/MyMesh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index a7c0333c..9b6a56e2 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -166,12 +166,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); } } From f87e856347e8047ba3ce01bdad77ae92418f5088 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 30 Jul 2025 16:00:47 +1000 Subject: [PATCH 11/77] * companion: fix for CMD_IMPORT_PRIVATE_KEY, to re-calc shared secrets --- examples/companion_radio/MyMesh.cpp | 4 ++++ src/helpers/BaseChatMesh.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 9b6a56e2..74d6c89a 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -661,6 +661,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); @@ -1097,6 +1098,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); } diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 683af852..0e809c46 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -88,6 +88,8 @@ 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; From ea2ce93c0240ddaabe6a686f03d658a2b3973023 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 30 Jul 2025 09:41:20 -0700 Subject: [PATCH 12/77] Add a counter to track rx time for repeater stats --- examples/simple_repeater/main.cpp | 2 ++ src/Dispatcher.cpp | 5 +++-- src/Dispatcher.h | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index a28df7a4..587603c7 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -98,6 +98,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 { @@ -208,6 +209,7 @@ 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)); 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..a4923063 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; @@ -167,6 +167,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; } From 1dfc0e69755d4d6770ad20f1e6499f3ebc25d050 Mon Sep 17 00:00:00 2001 From: taco Date: Thu, 31 Jul 2025 07:03:58 +1000 Subject: [PATCH 13/77] fix: Wio Tracker L1: use correct max ram and flash size for S140 v7.3.0 softdevice --- boards/seeed-wio-tracker-l1.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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": [ From fcdf342db69de7f1700874ded2c502517455da89 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 31 Jul 2025 13:04:16 +1000 Subject: [PATCH 14/77] * Companion: experimental CMD_SEND_DISCOVERY_REQ -> PUSH_CODE_DISCOVERY_RESPONSE --- examples/companion_radio/MyMesh.cpp | 67 ++++++++++++++++++++++++++--- examples/companion_radio/MyMesh.h | 3 +- src/helpers/BaseChatMesh.cpp | 8 +++- src/helpers/BaseChatMesh.h | 1 + 4 files changed, 70 insertions(+), 9 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 74d6c89a..16212157 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_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_DISCOVERY_RESPONSE 0x8D #define ERR_CODE_UNSUPPORTED_CMD 1 #define ERR_CODE_NOT_FOUND 2 @@ -527,6 +529,38 @@ 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_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; + + _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); @@ -589,7 +623,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe _cli_rescue = false; offline_queue_len = 0; app_target_ver = 0; - pending_login = pending_status = pending_telemetry = pending_req = 0; + pending_discovery = pending_login = pending_status = pending_telemetry = pending_req = 0; next_ack_idx = 0; sign_data = NULL; dirty_contacts_expiry = 0; @@ -1134,7 +1168,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; + pending_discovery = pending_req = pending_telemetry = pending_status = 0; 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; @@ -1154,7 +1188,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; + pending_discovery = pending_req = pending_telemetry = pending_login = 0; // 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; @@ -1166,6 +1200,27 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_NOT_FOUND); // contact not found } + } else if (cmd_frame[0] == CMD_SEND_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 + int result = sendRequest(*recipient, REQ_TYPE_GET_TELEMETRY_DATA, tag, est_timeout); + if (result == MSG_SEND_FAILED) { + writeErrFrame(ERR_CODE_TABLE_FULL); + } else { + pending_telemetry = pending_status = pending_login = pending_req = 0; + 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); @@ -1175,7 +1230,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; + pending_discovery = pending_status = pending_login = pending_req = 0; pending_telemetry = tag; // match this in onContactResponse() out_frame[0] = RESP_CODE_SENT; out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0; @@ -1211,7 +1266,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; + pending_discovery = pending_status = pending_login = pending_telemetry = 0; pending_req = tag; // match this in onContactResponse() out_frame[0] = RESP_CODE_SENT; out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0; diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index e2e96ff4..81288614 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -101,6 +101,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; @@ -161,7 +162,7 @@ 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; 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 0e809c46..9a4aa810 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -95,6 +95,7 @@ protected: 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; From 32e8ce413011e321512cf044b30f76d0fef13956 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 31 Jul 2025 13:45:53 +1000 Subject: [PATCH 15/77] * REQ_TYPE_GET_TELEMETRY_DATA, first reserved byte (of 4) is now inverse mask to apply to permissions --- examples/companion_radio/MyMesh.cpp | 23 ++++++++++++++++------- examples/companion_radio/MyMesh.h | 4 ++++ examples/simple_repeater/main.cpp | 4 +++- examples/simple_room_server/main.cpp | 4 +++- examples/simple_sensor/SensorMesh.cpp | 4 +++- 5 files changed, 29 insertions(+), 10 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 16212157..599312e1 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -439,6 +439,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); @@ -551,6 +554,7 @@ bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t i 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); } @@ -623,7 +627,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe _cli_rescue = false; offline_queue_len = 0; app_target_ver = 0; - pending_discovery = pending_login = pending_status = pending_telemetry = pending_req = 0; + clearPendingReqs(); next_ack_idx = 0; sign_data = NULL; dirty_contacts_expiry = 0; @@ -1168,7 +1172,7 @@ void MyMesh::handleCmdFrame(size_t len) { if (result == MSG_SEND_FAILED) { writeErrFrame(ERR_CODE_TABLE_FULL); } else { - pending_discovery = 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; @@ -1188,7 +1192,7 @@ void MyMesh::handleCmdFrame(size_t len) { if (result == MSG_SEND_FAILED) { writeErrFrame(ERR_CODE_TABLE_FULL); } else { - pending_discovery = 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; @@ -1206,11 +1210,16 @@ void MyMesh::handleCmdFrame(size_t len) { if (recipient) { uint32_t tag, est_timeout; // 'Path Discovery' is just a special case of flood + Telemetry req - int result = sendRequest(*recipient, REQ_TYPE_GET_TELEMETRY_DATA, tag, est_timeout); + 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 + int result = sendRequest(*recipient, req_data, sizeof(req_data), tag, est_timeout); if (result == MSG_SEND_FAILED) { writeErrFrame(ERR_CODE_TABLE_FULL); } else { - pending_telemetry = pending_status = pending_login = pending_req = 0; + clearPendingReqs(); pending_discovery = tag; // match this in onContactResponse() out_frame[0] = RESP_CODE_SENT; out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0; @@ -1230,7 +1239,7 @@ void MyMesh::handleCmdFrame(size_t len) { if (result == MSG_SEND_FAILED) { writeErrFrame(ERR_CODE_TABLE_FULL); } else { - pending_discovery = 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; @@ -1266,7 +1275,7 @@ void MyMesh::handleCmdFrame(size_t len) { if (result == MSG_SEND_FAILED) { writeErrFrame(ERR_CODE_TABLE_FULL); } else { - pending_discovery = 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; diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 81288614..01889223 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -134,6 +134,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); diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index a28df7a4..b439f909 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -214,10 +214,12 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { 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); diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 9a416835..9a284622 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -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); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 0816af72..5966e928 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(); From 2824fc31a4a27249068af03ab8bb4a55ce18c8c8 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 31 Jul 2025 14:38:11 +1000 Subject: [PATCH 16/77] * CMD_SEND_PATH_DISCOVERY_REQ: now force the sendRequest() to use flood mode --- examples/companion_radio/MyMesh.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 599312e1..901e3db5 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -49,7 +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_DISCOVERY_REQ 52 +#define CMD_SEND_PATH_DISCOVERY_REQ 52 #define RESP_CODE_OK 0 #define RESP_CODE_ERR 1 @@ -98,7 +98,7 @@ #define PUSH_CODE_NEW_ADVERT 0x8A #define PUSH_CODE_TELEMETRY_RESPONSE 0x8B #define PUSH_CODE_BINARY_RESPONSE 0x8C -#define PUSH_CODE_DISCOVERY_RESPONSE 0x8D +#define PUSH_CODE_PATH_DISCOVERY_RESPONSE 0x8D #define ERR_CODE_UNSUPPORTED_CMD 1 #define ERR_CODE_NOT_FOUND 2 @@ -544,7 +544,7 @@ bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t i MESH_DEBUG_PRINTLN("onContactPathRecv, invalid path sizes: %d, %d", in_path_len, out_path_len); } else { int i = 0; - out_frame[i++] = PUSH_CODE_DISCOVERY_RESPONSE; + 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 @@ -1204,7 +1204,7 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_NOT_FOUND); // contact not found } - } else if (cmd_frame[0] == CMD_SEND_DISCOVERY_REQ && cmd_frame[1] == 0 && len >= 2 + PUB_KEY_SIZE) { + } 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) { @@ -1215,7 +1215,10 @@ void MyMesh::handleCmdFrame(size_t len) { 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 { From 28af68c18798cf592b593de186e38b81661fcc23 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 1 Aug 2025 19:28:44 +1000 Subject: [PATCH 17/77] * new CommonCLI commands: "get prv.key", "set prv.key {hex}" --- examples/simple_repeater/main.cpp | 4 ++-- examples/simple_room_server/main.cpp | 2 +- examples/simple_sensor/SensorMesh.h | 2 +- examples/simple_sensor/main.cpp | 2 +- src/helpers/CommonCLI.cpp | 16 +++++++++++++++- src/helpers/CommonCLI.h | 4 ++-- 6 files changed, 22 insertions(+), 8 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index b439f909..76eebf8c 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -717,7 +717,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(); @@ -780,7 +780,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 9a284622..34b94ad2 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -863,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.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/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(); From febc63d2867abe6bb1af30cb1c6a89b3ab8f7c6d Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 1 Aug 2025 20:05:37 +1000 Subject: [PATCH 18/77] * fix: init rx_air_time to 0 --- src/Dispatcher.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Dispatcher.h b/src/Dispatcher.h index a4923063..25a41d82 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -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; From a49b5aaba77290b725f4099096da8840b269df5c Mon Sep 17 00:00:00 2001 From: taco Date: Sat, 2 Aug 2025 04:07:07 +1000 Subject: [PATCH 19/77] fix: wrong QSPI pins for wio tracker l1 --- variants/wio-tracker-l1/variant.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 From 8d3bdc69452551672da4ce39719d9c38bfd44f02 Mon Sep 17 00:00:00 2001 From: recrof Date: Sat, 2 Aug 2025 16:26:21 +0200 Subject: [PATCH 20/77] pin the pioarduino version to last working one --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index cd1c21ad..29abac4b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -67,7 +67,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 --------------------- From 6861b0702fb772d740eecdd0af6e5501ba6143a9 Mon Sep 17 00:00:00 2001 From: recrof Date: Sat, 2 Aug 2025 21:40:56 +0200 Subject: [PATCH 21/77] create sensor template in platformio.ini, update heltec v3 and rak4631 to use new template --- platformio.ini | 28 ++++++++++++++++++++++ variants/heltec_v3/platformio.ini | 19 ++------------- variants/rak4631/platformio.ini | 39 ++++++++++++++----------------- 3 files changed, 48 insertions(+), 38 deletions(-) diff --git a/platformio.ini b/platformio.ini index 29abac4b..02190102 100644 --- a/platformio.ini +++ b/platformio.ini @@ -102,3 +102,31 @@ 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_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 + 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/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 97eef510..08eb500e 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -3,6 +3,7 @@ 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 RADIO_CLASS=CustomSX1262 @@ -17,12 +18,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 +26,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 @@ -157,8 +146,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 @@ -168,8 +155,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 diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index ba4a8e2f..8fb4d1bb 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,14 @@ 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 @@ -133,3 +115,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 From 612dde73e9aaeb114b4e8b080b04717a35fce318 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Mon, 4 Aug 2025 14:29:49 +0800 Subject: [PATCH 22/77] Add HELTEC MeshSolar board. --- boards/heltec_mesh_solar.json | 61 +++++++++++ src/helpers/nrf52/MeshSolarBoard.cpp | 77 +++++++++++++ src/helpers/nrf52/MeshSolarBoard.h | 44 ++++++++ variants/heltec_mesh_solar/platformio.ini | 92 ++++++++++++++++ variants/heltec_mesh_solar/target.cpp | 123 +++++++++++++++++++++ variants/heltec_mesh_solar/target.h | 46 ++++++++ variants/heltec_mesh_solar/variant.cpp | 16 +++ variants/heltec_mesh_solar/variant.h | 127 ++++++++++++++++++++++ 8 files changed, 586 insertions(+) create mode 100644 boards/heltec_mesh_solar.json create mode 100644 src/helpers/nrf52/MeshSolarBoard.cpp create mode 100644 src/helpers/nrf52/MeshSolarBoard.h create mode 100644 variants/heltec_mesh_solar/platformio.ini create mode 100644 variants/heltec_mesh_solar/target.cpp create mode 100644 variants/heltec_mesh_solar/target.h create mode 100644 variants/heltec_mesh_solar/variant.cpp create mode 100644 variants/heltec_mesh_solar/variant.h 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/src/helpers/nrf52/MeshSolarBoard.cpp b/src/helpers/nrf52/MeshSolarBoard.cpp new file mode 100644 index 00000000..54929cd1 --- /dev/null +++ b/src/helpers/nrf52/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/src/helpers/nrf52/MeshSolarBoard.h b/src/helpers/nrf52/MeshSolarBoard.h new file mode 100644 index 00000000..3bec144f --- /dev/null +++ b/src/helpers/nrf52/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..713f2876 --- /dev/null +++ b/variants/heltec_mesh_solar/platformio.ini @@ -0,0 +1,92 @@ +[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> +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> +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..5aae8c3a --- /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 From fb8a4d12b1d0f125bd74338e5326b94a9649af46 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Tue, 5 Aug 2025 15:24:51 +0800 Subject: [PATCH 23/77] refactor variants #393 --- .../nrf52 => variants/heltec_mesh_solar}/MeshSolarBoard.cpp | 0 .../nrf52 => variants/heltec_mesh_solar}/MeshSolarBoard.h | 0 variants/heltec_mesh_solar/platformio.ini | 1 - variants/heltec_mesh_solar/target.h | 2 +- 4 files changed, 1 insertion(+), 2 deletions(-) rename {src/helpers/nrf52 => variants/heltec_mesh_solar}/MeshSolarBoard.cpp (100%) rename {src/helpers/nrf52 => variants/heltec_mesh_solar}/MeshSolarBoard.h (100%) diff --git a/src/helpers/nrf52/MeshSolarBoard.cpp b/variants/heltec_mesh_solar/MeshSolarBoard.cpp similarity index 100% rename from src/helpers/nrf52/MeshSolarBoard.cpp rename to variants/heltec_mesh_solar/MeshSolarBoard.cpp diff --git a/src/helpers/nrf52/MeshSolarBoard.h b/variants/heltec_mesh_solar/MeshSolarBoard.h similarity index 100% rename from src/helpers/nrf52/MeshSolarBoard.h rename to variants/heltec_mesh_solar/MeshSolarBoard.h diff --git a/variants/heltec_mesh_solar/platformio.ini b/variants/heltec_mesh_solar/platformio.ini index 713f2876..5cd8af86 100644 --- a/variants/heltec_mesh_solar/platformio.ini +++ b/variants/heltec_mesh_solar/platformio.ini @@ -16,7 +16,6 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_RX_BOOSTED_GAIN=1 build_src_filter = ${nrf52_base.build_src_filter} + - + +<../variants/heltec_mesh_solar> lib_deps = ${nrf52_base.lib_deps} diff --git a/variants/heltec_mesh_solar/target.h b/variants/heltec_mesh_solar/target.h index 5aae8c3a..e301a273 100644 --- a/variants/heltec_mesh_solar/target.h +++ b/variants/heltec_mesh_solar/target.h @@ -3,7 +3,7 @@ #define RADIOLIB_STATIC_ONLY 1 #include #include -#include +#include #include #include #include From 00dc193b0d51f98719e16ff8c47f81d0543c2edc Mon Sep 17 00:00:00 2001 From: uncle lit <43320854+LitBomb@users.noreply.github.com> Date: Tue, 1 Jul 2025 22:04:16 -0700 Subject: [PATCH 24/77] Update faq.md - rewrite Linux firmware update with more detailed instructions - fix T114 reset typo: double click twice, not once - add link reference to MeshCore logo on github - add public channel key for t-deck to the t-deck section - reference the verbiage "what is meshcore?" on meshcore.co.uk homepage --- docs/faq.md | 138 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 87 insertions(+), 51 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 1c67c9c7..ab7c63a0 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 @@ -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://...") @@ -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 --- From fc334a05c64c85fa91ca5f11b2c8a745f1abc115 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Sun, 6 Jul 2025 17:40:52 +0200 Subject: [PATCH 25/77] remove hardware compatibility list - it was outdated. --- README.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/README.md b/README.md index 708db2c4..281c9502 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 From ca9687e2120b08744dcdeeceffc3e92ee481a473 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Fri, 11 Jul 2025 11:37:01 +0200 Subject: [PATCH 26/77] faq: fix broken links, replace discord link from Andy's discord to MeshCore discord --- docs/faq.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index ab7c63a0..6dc8fe9e 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -179,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 @@ -271,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. @@ -396,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 From 28360ba45951258b09bee70587835af98f705fc2 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Fri, 11 Jul 2025 11:39:50 +0200 Subject: [PATCH 27/77] replaced Andy Kirby's discord with MeshCore discord. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 281c9502..fa5b3cd0 100644 --- a/README.md +++ b/README.md @@ -94,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 From 6214b75e8375cd247e420ecd3ee85930aff33054 Mon Sep 17 00:00:00 2001 From: TasmanDevil Date: Fri, 25 Jul 2025 22:53:56 +0200 Subject: [PATCH 28/77] disable led light on t-echo device --- variants/techo/variant.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/techo/variant.h b/variants/techo/variant.h index 6aebf82f..59f69161 100644 --- a/variants/techo/variant.h +++ b/variants/techo/variant.h @@ -61,9 +61,9 @@ //////////////////////////////////////////////////////////////////////////////// // Builtin LEDs -#define LED_RED (34) -#define LED_GREEN (33) -#define LED_BLUE (14) +#define LED_RED (-1) +#define LED_GREEN (-1) +#define LED_BLUE (-1) #define PIN_STATUS_LED LED_GREEN #define LED_BUILTIN LED_GREEN From d1e13d0b9e7f599e69ab5c56bcee927385390728 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 27 Jul 2025 20:38:20 +1000 Subject: [PATCH 29/77] Revert "disable led light on t-echo device" This reverts commit 55453e1136441900b4aaa019f2d06ce4ae3d1aa9. --- variants/techo/variant.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/techo/variant.h b/variants/techo/variant.h index 59f69161..6aebf82f 100644 --- a/variants/techo/variant.h +++ b/variants/techo/variant.h @@ -61,9 +61,9 @@ //////////////////////////////////////////////////////////////////////////////// // Builtin LEDs -#define LED_RED (-1) -#define LED_GREEN (-1) -#define LED_BLUE (-1) +#define LED_RED (34) +#define LED_GREEN (33) +#define LED_BLUE (14) #define PIN_STATUS_LED LED_GREEN #define LED_BUILTIN LED_GREEN From fa3500944b597be7c77318ead24abff0122f10e5 Mon Sep 17 00:00:00 2001 From: Alexander Begoon Date: Sat, 2 Aug 2025 13:06:40 +0200 Subject: [PATCH 30/77] Add Meshimi configuration and environment setups in platformio.ini --- .gitignore | 2 ++ variants/xiao_c6/platformio.ini | 54 +++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/.gitignore b/.gitignore index 7ca9335a..1bca8e04 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ out/ .DS_Store .vscode/settings.json .vscode/extensions.json +.idea +cmake-* \ No newline at end of file diff --git a/variants/xiao_c6/platformio.ini b/variants/xiao_c6/platformio.ini index bc1c789b..9ad07aef 100644 --- a/variants/xiao_c6/platformio.ini +++ b/variants/xiao_c6/platformio.ini @@ -65,3 +65,57 @@ build_src_filter = ${Xiao_C6.build_src_filter} 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 + +[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> +lib_deps = + ${Meshimi.lib_deps} + densaugeo/base64 @ ~1.4.0 From 53c1f704127f6f924cfff234c9097f3c00f82198 Mon Sep 17 00:00:00 2001 From: 446564 Date: Sat, 2 Aug 2025 15:42:57 -0700 Subject: [PATCH 31/77] various fixes for nano g2 gps - baud rate changed to default - switch state detected at boot to enable position - small edge cases fixed - more mesh debugging output added Co-authored-by: LitBomb Co-authored-by: Nick from BOSTON --- variants/nano_g2_ultra/nano-g2.cpp | 2 + variants/nano_g2_ultra/nano-g2.h | 2 + variants/nano_g2_ultra/platformio.ini | 2 +- variants/nano_g2_ultra/target.cpp | 84 ++++++++++++++------------- 4 files changed, 48 insertions(+), 42 deletions(-) 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..511c0ae7 100644 --- a/variants/nano_g2_ultra/platformio.ini +++ b/variants/nano_g2_ultra/platformio.ini @@ -37,7 +37,7 @@ build_flags = -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 diff --git a/variants/nano_g2_ultra/target.cpp b/variants/nano_g2_ultra/target.cpp index a67085ce..287675df 100644 --- a/variants/nano_g2_ultra/target.cpp +++ b/variants/nano_g2_ultra/target.cpp @@ -42,45 +42,44 @@ 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); 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 (gps_active = 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; } @@ -96,17 +95,24 @@ bool NanoG2UltraSensorManager::querySensors(uint8_t requester_permissions, Cayen 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()) - { - node_lat = ((double)_location->getLatitude()) / 1000000.; - node_lon = ((double)_location->getLongitude()) / 1000000.; + + 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; } } @@ -128,14 +134,10 @@ const char *NanoG2UltraSensorManager::getSettingValue(int i) const bool NanoG2UltraSensorManager::setSettingValue(const char *name, const char *value) { - if (strcmp(name, "gps") == 0) - { - if (strcmp(value, "0") == 0) - { + if (strcmp(name, "gps") == 0) { + if (strcmp(value, "0") == 0) { stop_gps(); - } - else - { + } else { start_gps(); } return true; From 4d97bee02a970ccf71b63afa3d7e33011b968bb0 Mon Sep 17 00:00:00 2001 From: Ded Date: Wed, 6 Aug 2025 18:52:57 -0700 Subject: [PATCH 32/77] good oops --- variants/nano_g2_ultra/target.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/nano_g2_ultra/target.cpp b/variants/nano_g2_ultra/target.cpp index 287675df..b6084236 100644 --- a/variants/nano_g2_ultra/target.cpp +++ b/variants/nano_g2_ultra/target.cpp @@ -73,7 +73,7 @@ bool NanoG2UltraSensorManager::begin() delay(1000); // Check initial switch state to determine if GPS should be active - if (gps_active = Serial1.available() > 0) { + if (Serial1.available() > 0) { MESH_DEBUG_PRINTLN("GPS was on at boot, GPS enabled"); start_gps(); } else { From ce4e559c01dbd20d492189d9d3e2ab7851334cb1 Mon Sep 17 00:00:00 2001 From: Alexander Begoon Date: Thu, 7 Aug 2025 22:30:33 +0200 Subject: [PATCH 33/77] Add support for Xiao ESP32C6 with external antenna configuration --- src/helpers/esp32/XiaoC6Board.h | 28 ++++++++++++++++++++++++++++ variants/xiao_c6/platformio.ini | 1 + variants/xiao_c6/target.cpp | 2 +- variants/xiao_c6/target.h | 3 ++- 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 src/helpers/esp32/XiaoC6Board.h diff --git a/src/helpers/esp32/XiaoC6Board.h b/src/helpers/esp32/XiaoC6Board.h new file mode 100644 index 00000000..86c3475c --- /dev/null +++ b/src/helpers/esp32/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 9ad07aef..c27df55e 100644 --- a/variants/xiao_c6/platformio.ini +++ b/variants/xiao_c6/platformio.ini @@ -87,6 +87,7 @@ build_flags = -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 diff --git a/variants/xiao_c6/target.cpp b/variants/xiao_c6/target.cpp index caca57bc..ff77474a 100644 --- a/variants/xiao_c6/target.cpp +++ b/variants/xiao_c6/target.cpp @@ -1,7 +1,7 @@ #include #include "target.h" -ESP32Board board; +XiaoC6Board board; #if defined(P_LORA_SCLK) static SPIClass spi(0); diff --git a/variants/xiao_c6/target.h b/variants/xiao_c6/target.h index c26d5958..e1469228 100644 --- a/variants/xiao_c6/target.h +++ b/variants/xiao_c6/target.h @@ -4,11 +4,12 @@ #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; From 4b95c981bbc1b8fbbd9eeb8c89e533aa61ef26a9 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 8 Aug 2025 20:01:31 +1000 Subject: [PATCH 34/77] * UI revamp for companion radios --- examples/companion_radio/MyMesh.cpp | 15 + examples/companion_radio/MyMesh.h | 16 +- examples/companion_radio/UITask.cpp | 704 ++++++++++++++--------- examples/companion_radio/UITask.h | 62 +- examples/companion_radio/icons.h | 118 ++++ examples/companion_radio/main.cpp | 7 +- src/helpers/esp32/SerialBLEInterface.cpp | 2 + src/helpers/esp32/SerialBLEInterface.h | 2 + src/helpers/nrf52/SerialBLEInterface.cpp | 10 + src/helpers/nrf52/T114Board.h | 4 + src/helpers/nrf52/ThinkNodeM1Board.h | 4 + src/helpers/ui/DisplayDriver.h | 6 + src/helpers/ui/GxEPDDisplay.cpp | 13 +- src/helpers/ui/GxEPDDisplay.h | 1 + src/helpers/ui/MomentaryButton.cpp | 74 +++ src/helpers/ui/MomentaryButton.h | 25 + src/helpers/ui/ST7789Display.cpp | 11 + src/helpers/ui/ST7789Display.h | 1 + src/helpers/ui/UIScreen.h | 21 + variants/heltec_v3/platformio.ini | 55 +- variants/heltec_v3/target.cpp | 1 + variants/heltec_v3/target.h | 2 + variants/t114/platformio.ini | 1 + variants/t114/target.cpp | 1 + variants/t114/target.h | 2 + variants/xiao_c3/platformio.ini | 5 +- 26 files changed, 840 insertions(+), 323 deletions(-) create mode 100644 examples/companion_radio/icons.h create mode 100644 src/helpers/ui/MomentaryButton.cpp create mode 100644 src/helpers/ui/MomentaryButton.h create mode 100644 src/helpers/ui/UIScreen.h diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 901e3db5..75828d5e 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -267,6 +267,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); @@ -275,6 +276,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); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 01889223..42819813 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -77,6 +77,14 @@ #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); @@ -93,6 +101,8 @@ public: bool advert(); void enterCLIRescue(); + int getRecentlyHeard(AdvertPath dest[], int max_num); + protected: float getAirtimeBudgetFactor() const override; int getInterferenceThreshold() const override; @@ -201,12 +211,6 @@ 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 }; diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index a7f03a26..9f5fe5b1 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -1,8 +1,8 @@ #include "UITask.h" -#include #include #include "NodePrefs.h" #include "MyMesh.h" +#include "target.h" #define AUTO_OFF_MILLIS 15000 // 15 seconds #define BOOT_SCREEN_MILLIS 3000 // 3 seconds @@ -13,80 +13,366 @@ #define LED_CYCLE_MILLIS 4000 #endif -#ifndef USER_BTN_PRESSED -#define USER_BTN_PRESSED LOW -#endif +#define LONG_PRESS_MILLIS 1200 -// 'meshcore', 128x13px -static const uint8_t meshcore_logo [] PROGMEM = { - 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, +#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[4]; + + 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, 4); + display.setColor(DisplayDriver::GREEN); + int y = 20; + for (int i = 0; i < 4; 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), 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; - clearMsgPreview(); + +#if defined(PIN_USER_BTN) + user_btn.begin(); +#endif + _node_prefs = node_prefs; if (_display != NULL) { _display->turnOn(); } - // strip off dash and commit hash by changing dash to null terminator - // e.g: v1.2.3-abcdef -> v1.2.3 - char *version = strdup(FIRMWARE_VERSION); - char *dash = strchr(version, '-'); - if (dash) { - *dash = 0; - } - - // v1.2.3 (1 Jan 2025) - sprintf(_version_info, "%s (%s)", version, FIRMWARE_BUILD_DATE); - #ifdef PIN_BUZZER buzzer.begin(); #endif - // Initialize digital button if available -#ifdef PIN_USER_BTN - _userButton = new Button(PIN_USER_BTN, USER_BTN_PRESSED); - _userButton->begin(); - - // Set up digital button callbacks - _userButton->onShortPress([this]() { handleButtonShortPress(); }); - _userButton->onDoublePress([this]() { handleButtonDoublePress(); }); - _userButton->onTriplePress([this]() { handleButtonTriplePress(); }); - _userButton->onQuadruplePress([this]() { handleButtonQuadruplePress(); }); - _userButton->onLongPress([this]() { handleButtonLongPress(); }); - _userButton->onAnyPress([this]() { handleButtonAnyPress(); }); -#endif - - // Initialize analog button if available -#ifdef PIN_USER_BTN_ANA - _userButtonAnalog = new Button(PIN_USER_BTN_ANA, USER_BTN_PRESSED, true, 20); - _userButtonAnalog->begin(); - - // Set up analog button callbacks - _userButtonAnalog->onShortPress([this]() { handleButtonShortPress(); }); - _userButtonAnalog->onDoublePress([this]() { handleButtonDoublePress(); }); - _userButtonAnalog->onTriplePress([this]() { handleButtonTriplePress(); }); - _userButtonAnalog->onQuadruplePress([this]() { handleButtonQuadruplePress(); }); - _userButtonAnalog->onLongPress([this]() { handleButtonLongPress(); }); - _userButtonAnalog->onAnyPress([this]() { handleButtonAnyPress(); }); -#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) { @@ -109,147 +395,28 @@ switch(bet){ break; } #endif -// Serial.print("DBG: Buzzzzzz -> "); -// Serial.println((int) bet); } void UITask::msgRead(int msgcount) { _msgcount = msgcount; if (msgcount == 0) { - clearMsgPreview(); + gotoHomeScreen(); } } -void UITask::clearMsgPreview() { - _origin[0] = 0; - _msg[0] = 0; - _need_refresh = true; -} - void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) { _msgcount = msgcount; - if (path_len == 0xFF) { - sprintf(_origin, "(F) %s", from_name); - } else { - sprintf(_origin, "(%d) %s", (uint32_t) path_len, from_name); - } - StrHelper::strncpy(_msg, text, sizeof(_msg)); + ((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 - _need_refresh = true; + _next_refresh = 0; // trigger refresh } } -void UITask::renderBatteryIndicator(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 = 12; - 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); -} - -void UITask::renderCurrScreen() { - if (_display == NULL) return; // assert() ?? - - char tmp[80]; - if (_alert[0]) { - _display->setTextSize(1.4); - uint16_t textWidth = _display->getTextWidth(_alert); - _display->setCursor((_display->width() - textWidth) / 2, 22); - _display->setColor(DisplayDriver::GREEN); - _display->print(_alert); - _alert[0] = 0; - _need_refresh = true; - return; - } else if (_origin[0] && _msg[0]) { // message preview - // render message preview - _display->setCursor(0, 0); - _display->setTextSize(1); - _display->setColor(DisplayDriver::GREEN); - _display->print(_node_prefs->node_name); - - _display->setCursor(0, 12); - _display->setColor(DisplayDriver::YELLOW); - _display->print(_origin); - _display->setCursor(0, 24); - _display->setColor(DisplayDriver::LIGHT); - _display->print(_msg); - - _display->setCursor(_display->width() - 28, 9); - _display->setTextSize(2); - _display->setColor(DisplayDriver::ORANGE); - sprintf(tmp, "%d", _msgcount); - _display->print(tmp); - _display->setColor(DisplayDriver::YELLOW); // last color will be kept on T114 - } else if ((millis() - ui_started_at) < BOOT_SCREEN_MILLIS) { // boot screen - // 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(1); - uint16_t textWidth = _display->getTextWidth(_version_info); - _display->setCursor((_display->width() - textWidth) / 2, 22); - _display->print(_version_info); - } else { // home screen - // node name - _display->setCursor(0, 0); - _display->setTextSize(1); - _display->setColor(DisplayDriver::GREEN); - _display->print(_node_prefs->node_name); - - // battery voltage - renderBatteryIndicator(_board->getBattMilliVolts()); - - // freq / sf - _display->setCursor(0, 20); - _display->setColor(DisplayDriver::YELLOW); - sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf); - _display->print(tmp); - - // bw / cr - _display->setCursor(0, 30); - sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr); - _display->print(tmp); - - // BT pin - if (!_connected && the_mesh.getBLEPin() != 0) { - _display->setColor(DisplayDriver::RED); - _display->setTextSize(2); - _display->setCursor(0, 43); - sprintf(tmp, "Pin:%d", the_mesh.getBLEPin()); - _display->print(tmp); - _display->setColor(DisplayDriver::GREEN); - } else { - _display->setColor(DisplayDriver::LIGHT); - } - } - _need_refresh = false; -} - void UITask::userLedHandler() { #ifdef PIN_STATUS_LED static int state = 0; @@ -275,6 +442,11 @@ void UITask::userLedHandler() { #endif } +void UITask::setCurrScreen(UIScreen* c) { + curr = c; + _next_refresh = 0; +} + /* hardware-agnostic pre-shutdown activity should be done here */ @@ -293,96 +465,103 @@ void UITask::shutdown(bool restart){ #endif // PIN_BUZZER - if (restart) + if (restart) { _board->reboot(); - else + } else { + _display->turnOff(); _board->powerOff(); + } +} + +bool UITask::isButtonPressed() const { +#ifdef PIN_USER_BTN + return user_btn.isPressed(); +#else + return false; +#endif } void UITask::loop() { - #ifdef PIN_USER_BTN - if (_userButton) { - _userButton->update(); - } - #endif - #ifdef PIN_USER_BTN_ANA - if (_userButtonAnalog) { - _userButtonAnalog->update(); - } - #endif + 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 (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 (_display != NULL && _display->isOn()) { - static bool _firstBoot = true; - if(_firstBoot && (millis() - ui_started_at) >= BOOT_SCREEN_MILLIS) { - _need_refresh = true; - _firstBoot = false; - } - if (millis() >= _next_refresh && _need_refresh) { - _display->startFrame(); - renderCurrScreen(); - _display->endFrame(); + if (curr) curr->poll(); - _next_refresh = millis() + 1000; // refresh every second + 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 } -void UITask::handleButtonAnyPress() { - MESH_DEBUG_PRINTLN("UITask: any press triggered"); - // called on any button press before other events, to wake up the display quickly - // do not refresh the display here, as it may block the button handler +char UITask::checkDisplayOn(char c) { if (_display != NULL) { - _displayWasOn = _display->isOn(); // Track display state before any action - if (!_displayWasOn) { - _display->turnOn(); + 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; } -void UITask::handleButtonShortPress() { - MESH_DEBUG_PRINTLN("UITask: short press triggered"); - if (_display != NULL) { - // Only clear message preview if display was already on before button press - if (_displayWasOn) { - // If display was on and showing message preview, clear it - if (_origin[0] && _msg[0]) { - clearMsgPreview(); - } else { - // Otherwise, refresh the display - _need_refresh = true; - } - } else { - _need_refresh = true; // display just turned on, so we need to refresh - } - // Note: Display turn-on and auto-off timer extension are handled by handleButtonAnyPress +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::handleButtonDoublePress() { - MESH_DEBUG_PRINTLN("UITask: double press triggered, sending advert"); - // ADVERT - #ifdef PIN_BUZZER - soundBuzzer(UIEventType::ack); - #endif - if (the_mesh.advert()) { - MESH_DEBUG_PRINTLN("Advert sent!"); - sprintf(_alert, "Advert sent!"); - } else { - MESH_DEBUG_PRINTLN("Advert failed!"); - sprintf(_alert, "Advert failed.."); - } - _need_refresh = true; -} - +/* void UITask::handleButtonTriplePress() { MESH_DEBUG_PRINTLN("UITask: triple press triggered"); // Toggle buzzer quiet mode @@ -390,43 +569,12 @@ void UITask::handleButtonTriplePress() { if (buzzer.isQuiet()) { buzzer.quiet(false); soundBuzzer(UIEventType::ack); - sprintf(_alert, "Buzzer: ON"); + showAlert("Buzzer: ON", 600); } else { buzzer.quiet(true); - sprintf(_alert, "Buzzer: OFF"); + showAlert("Buzzer: OFF", 600); } - _need_refresh = true; + _next_refresh = 0; // trigger refresh #endif } - -void UITask::handleButtonQuadruplePress() { - MESH_DEBUG_PRINTLN("UITask: quad press triggered"); - if (_sensors != NULL) { - // toggle GPS onn/off - int num = _sensors->getNumSettings(); - for (int i = 0; i < num; i++) { - if (strcmp(_sensors->getSettingName(i), "gps") == 0) { - if (strcmp(_sensors->getSettingValue(i), "1") == 0) { - _sensors->setSettingValue("gps", "0"); - soundBuzzer(UIEventType::ack); - sprintf(_alert, "GPS: Disabled"); - } else { - _sensors->setSettingValue("gps", "1"); - soundBuzzer(UIEventType::ack); - sprintf(_alert, "GPS: Enabled"); - } - break; - } - } - } - _need_refresh = true; -} - -void UITask::handleButtonLongPress() { - MESH_DEBUG_PRINTLN("UITask: long press triggered"); - if (millis() - ui_started_at < 8000) { // long press in first 8 seconds since startup -> CLI/rescue - the_mesh.enterCLIRescue(); - } else { - shutdown(); - } -} \ No newline at end of file +*/ diff --git a/examples/companion_radio/UITask.h b/examples/companion_radio/UITask.h index 77ef875f..818779e5 100644 --- a/examples/companion_radio/UITask.h +++ b/examples/companion_radio/UITask.h @@ -2,18 +2,18 @@ #include #include +#include #include -#include +#include +#include #ifdef PIN_BUZZER #include #endif #include "NodePrefs.h" -#include "Button.h" - enum class UIEventType -{ +enum class UIEventType { none, contactMessage, channelMessage, @@ -22,9 +22,12 @@ ack }; +#define MAX_TOP_LEVEL 8 + class UITask { DisplayDriver* _display; mesh::MainBoard* _board; + BaseSerialInterface* _serial; SensorManager* _sensors; #ifdef PIN_BUZZER genericBuzzer buzzer; @@ -32,48 +35,45 @@ class UITask { unsigned long _next_refresh, _auto_off; bool _connected; NodePrefs* _node_prefs; - char _version_info[32]; - char _origin[62]; - char _msg[80]; char _alert[80]; + unsigned long _alert_expiry; int _msgcount; - bool _need_refresh = true; - bool _displayWasOn = false; // Track display state before button press - unsigned long ui_started_at; + unsigned long ui_started_at, next_batt_chck; - // Button handlers -#ifdef PIN_USER_BTN - Button* _userButton = nullptr; -#endif -#ifdef PIN_USER_BTN_ANA - Button* _userButtonAnalog = nullptr; -#endif + UIScreen* splash; + UIScreen* home; + UIScreen* msg_preview; + UIScreen* curr; - void renderCurrScreen(); void userLedHandler(); - void renderBatteryIndicator(uint16_t batteryMilliVolts); // Button action handlers - void handleButtonAnyPress(); - void handleButtonShortPress(); - void handleButtonDoublePress(); - void handleButtonTriplePress(); - void handleButtonQuadruplePress(); - void handleButtonLongPress(); + char checkDisplayOn(char c); + char handleLongPress(char c); + + void setCurrScreen(UIScreen* c); - public: - UITask(mesh::MainBoard* board) : _board(board), _display(NULL), _sensors(NULL) { - _next_refresh = 0; - ui_started_at = 0; - _connected = false; + UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : _board(board), _serial(serial), _display(NULL), _sensors(NULL) { + next_batt_chck = _next_refresh = 0; + ui_started_at = 0; + _connected = false; + curr = NULL; } void begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs); + void gotoHomeScreen() { setCurrScreen(home); } + void showAlert(const char* text, int duration_millis); 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(); } + int getMsgCount() const { return _msgcount; } bool hasDisplay() const { return _display != NULL; } - void clearMsgPreview(); + bool isButtonPressed() const; 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); diff --git a/examples/companion_radio/icons.h b/examples/companion_radio/icons.h new file mode 100644 index 00000000..5220f409 --- /dev/null +++ b/examples/companion_radio/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/main.cpp b/examples/companion_radio/main.cpp index 34c30498..d9b3f68b 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -81,7 +81,7 @@ 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 /* END GLOBAL OBJECTS */ @@ -99,7 +99,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/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..8049f5c0 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -115,6 +115,16 @@ void SerialBLEInterface::enable() { void SerialBLEInterface::disable() { _isEnabled = false; BLE_DEBUG_PRINTLN("SerialBLEInterface::disable"); + + uint16_t conn_id; + if (Bluefruit.getConnectedHandles(&conn_id, 1) > 0) { + Bluefruit.disconnect(conn_id); + } + + 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/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/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/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index 875e29ac..50df9d10 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -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,11 @@ void GxEPDDisplay::setTextSize(int sz) { } void GxEPDDisplay::setColor(Color c) { - display.setTextColor(GxEPD_BLACK); + if (c == DARK) { + display.setTextColor(_curr_color = GxEPD_BLACK); + } else { + display.setTextColor(_curr_color = GxEPD_WHITE); + } } void GxEPDDisplay::setCursor(int x, int y) { @@ -79,11 +84,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 +121,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); } } } 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..783f7ba7 --- /dev/null +++ b/src/helpers/ui/MomentaryButton.cpp @@ -0,0 +1,74 @@ +#include "MomentaryButton.h" + +MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse) { + _pin = pin; + _reverse = reverse; + down_at = 0; + prev = _reverse ? HIGH : LOW; + cancel = 0; + _long_millis = long_press_millis; +} + +void MomentaryButton::begin(bool pulldownup) { + if (_pin >= 0) { + pinMode(_pin, pulldownup ? (_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..c46561e3 --- /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; + 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); + void begin(bool pulldownup=false); + 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/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/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 08eb500e..ec0956d8 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -6,6 +6,7 @@ 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 @@ -46,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_room_server] extends = Heltec_lora32_v3 @@ -91,6 +93,7 @@ build_flags = ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + + +<../examples/companion_radio> lib_deps = ${Heltec_lora32_v3.lib_deps} @@ -100,16 +103,18 @@ lib_deps = extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} - -D MAX_CONTACTS=100 + -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> lib_deps = @@ -130,6 +135,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + + + +<../examples/companion_radio> lib_deps = @@ -172,6 +178,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 @@ -237,3 +244,49 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter} lib_deps = ${Heltec_lora32_v3.lib_deps} ${esp32_ota.lib_deps} + +[env:Heltec_WSL3_espnow_bridge] +extends = Heltec_lora32_v3 +build_flags = + ${Heltec_lora32_v3.build_flags} +; -D LORA_FREQ=915.8 + -D MESH_PACKET_LOGGING=1 + -D ENV_INCLUDE_AHTX0=0 + -D ENV_INCLUDE_BME280=0 + -D ENV_INCLUDE_BMP280=0 + -D ENV_INCLUDE_INA3221=0 + -D ENV_INCLUDE_INA219=0 + -D ENV_INCLUDE_MLX90614=0 + -D ENV_INCLUDE_VL53L0X=0 + -D ENV_INCLUDE_GPS=0 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v3.build_src_filter} + +<../examples/simple_bridge/main.cpp> + + +lib_deps = + ${Heltec_lora32_v3.lib_deps} + bakercp/CRC32 @ ^2.0.0 + +[env:Heltec_WSL3_serial_bridge] +extends = Heltec_lora32_v3 +build_flags = + ${Heltec_lora32_v3.build_flags} +; -D LORA_FREQ=915.8 + -D MESH_PACKET_LOGGING=1 + -D SERIAL_BRIDGE_RX=47 + -D SERIAL_BRIDGE_TX=48 + -D ENV_INCLUDE_AHTX0=0 + -D ENV_INCLUDE_BME280=0 + -D ENV_INCLUDE_BMP280=0 + -D ENV_INCLUDE_INA3221=0 + -D ENV_INCLUDE_INA219=0 + -D ENV_INCLUDE_MLX90614=0 + -D ENV_INCLUDE_VL53L0X=0 + -D ENV_INCLUDE_GPS=0 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v3.build_src_filter} + +<../examples/simple_bridge/main.cpp> + +<../examples/simple_bridge/SerialBridgeRadio.cpp> +lib_deps = + ${Heltec_lora32_v3.lib_deps} + bakercp/CRC32 @ ^2.0.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/t114/platformio.ini b/variants/t114/platformio.ini index dac12da9..815ef173 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 = 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/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index 3e4bfdb4..59f198e0 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -61,6 +61,7 @@ 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 @@ -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 From 8d7a49867f485e7f06c752019b14d75b633fa064 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sat, 9 Aug 2025 00:21:10 +1200 Subject: [PATCH 35/77] add support for new companion ui on thinknode m1 --- examples/companion_radio/UITask.cpp | 2 +- src/helpers/ui/GxEPDDisplay.cpp | 5 +++-- variants/thinknode_m1/platformio.ini | 1 + variants/thinknode_m1/target.cpp | 1 + variants/thinknode_m1/target.h | 2 ++ 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index 9f5fe5b1..73bd00fc 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -305,7 +305,7 @@ public: } else { sprintf(tmp, "%dh", secs / (60*60)); } - display.setCursor(display.width() - display.getTextWidth(tmp), 0); + display.setCursor(display.width() - display.getTextWidth(tmp) - 2, 0); display.print(tmp); display.drawRect(0, 11, display.width(), 1); // horiz line diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index 50df9d10..c3d75bbd 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -68,10 +68,11 @@ void GxEPDDisplay::setTextSize(int sz) { } void GxEPDDisplay::setColor(Color c) { + // colours need to be inverted for epaper displays if (c == DARK) { - display.setTextColor(_curr_color = GxEPD_BLACK); - } else { display.setTextColor(_curr_color = GxEPD_WHITE); + } else { + display.setTextColor(_curr_color = GxEPD_BLACK); } } diff --git a/variants/thinknode_m1/platformio.ini b/variants/thinknode_m1/platformio.ini index 2104a080..972444ba 100644 --- a/variants/thinknode_m1/platformio.ini +++ b/variants/thinknode_m1/platformio.ini @@ -83,6 +83,7 @@ build_src_filter = ${ThinkNode_M1.build_src_filter} + + + + + +<../examples/companion_radio> lib_deps = ${ThinkNode_M1.lib_deps} 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(); From 1e711f57f49f8daaedc3c041f37411d4bf07528c Mon Sep 17 00:00:00 2001 From: Florent Date: Fri, 8 Aug 2025 15:34:58 +0200 Subject: [PATCH 36/77] techo: initial support of new companion ui --- src/helpers/ui/GxEPDDisplay.cpp | 6 +++--- variants/techo/platformio.ini | 1 + variants/techo/target.cpp | 1 + variants/techo/target.h | 2 ++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index 50df9d10..8a138c6c 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -68,10 +68,10 @@ void GxEPDDisplay::setTextSize(int sz) { } void GxEPDDisplay::setColor(Color c) { - if (c == DARK) { - display.setTextColor(_curr_color = GxEPD_BLACK); - } else { + if (c == DARK) { // invert colors for epds display.setTextColor(_curr_color = GxEPD_WHITE); + } else { + display.setTextColor(_curr_color = GxEPD_BLACK); } } diff --git a/variants/techo/platformio.ini b/variants/techo/platformio.ini index 8ad5ca03..45eba9fc 100644 --- a/variants/techo/platformio.ini +++ b/variants/techo/platformio.ini @@ -73,6 +73,7 @@ build_src_filter = ${LilyGo_Techo.build_src_filter} + + + + + +<../examples/companion_radio> lib_deps = ${LilyGo_Techo.lib_deps} 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(); From 6902dd81fa8eb8276c2582a0c9a561609f8e5c9a Mon Sep 17 00:00:00 2001 From: Alexander Begoon Date: Fri, 8 Aug 2025 23:04:14 +0200 Subject: [PATCH 37/77] Move variant specific code --- variants/xiao_c6/{target.cpp => XiaoC6Board.cpp} | 2 ++ {src/helpers/esp32 => variants/xiao_c6}/XiaoC6Board.h | 0 variants/xiao_c6/platformio.ini | 1 + variants/xiao_c6/target.h | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) rename variants/xiao_c6/{target.cpp => XiaoC6Board.cpp} (99%) rename {src/helpers/esp32 => variants/xiao_c6}/XiaoC6Board.h (100%) diff --git a/variants/xiao_c6/target.cpp b/variants/xiao_c6/XiaoC6Board.cpp similarity index 99% rename from variants/xiao_c6/target.cpp rename to variants/xiao_c6/XiaoC6Board.cpp index ff77474a..555fed62 100644 --- a/variants/xiao_c6/target.cpp +++ b/variants/xiao_c6/XiaoC6Board.cpp @@ -47,3 +47,5 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } + + diff --git a/src/helpers/esp32/XiaoC6Board.h b/variants/xiao_c6/XiaoC6Board.h similarity index 100% rename from src/helpers/esp32/XiaoC6Board.h rename to variants/xiao_c6/XiaoC6Board.h diff --git a/variants/xiao_c6/platformio.ini b/variants/xiao_c6/platformio.ini index c27df55e..95dede08 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 diff --git a/variants/xiao_c6/target.h b/variants/xiao_c6/target.h index e1469228..0fbb0bb2 100644 --- a/variants/xiao_c6/target.h +++ b/variants/xiao_c6/target.h @@ -2,9 +2,9 @@ #define RADIOLIB_STATIC_ONLY 1 #include +#include #include #include -#include #include #include #include From 331a29b082a6d7fc5c92c5638b25221446dddf1d Mon Sep 17 00:00:00 2001 From: wel97459 Date: Fri, 8 Aug 2025 20:17:06 -0700 Subject: [PATCH 38/77] Changed radio_init to use radio.std_init this also changes the preamble to 16 as before it was 8 --- variants/waveshare_rp2040_lora/target.cpp | 36 ++--------------------- 1 file changed, 2 insertions(+), 34 deletions(-) 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() { From 6e26a6a78cea7f95c461cc47586348e061c5307f Mon Sep 17 00:00:00 2001 From: Tomas Gabor Date: Sat, 9 Aug 2025 21:28:31 +0200 Subject: [PATCH 39/77] Added room option to T-Beam SX1276 --- variants/lilygo_tbeam_SX1276/platformio.ini | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/variants/lilygo_tbeam_SX1276/platformio.ini b/variants/lilygo_tbeam_SX1276/platformio.ini index b0f6c001..72040eab 100644 --- a/variants/lilygo_tbeam_SX1276/platformio.ini +++ b/variants/lilygo_tbeam_SX1276/platformio.ini @@ -65,6 +65,22 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TBeam_SX1276.build_src_filter} +<../examples/simple_repeater> +lib_deps = + ${LilyGo_TBeam_SX1276.lib_deps} + ${esp32_ota.lib_deps} + +[env:Tbeam_SX1276_room] +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} \ No newline at end of file From cdc762ada2d85f58be09cd0a46e5c90de5711e7c Mon Sep 17 00:00:00 2001 From: 446564 Date: Sat, 9 Aug 2025 17:37:51 -0700 Subject: [PATCH 40/77] add lsp ignores adds cache and ccls dirs and clangd files to git ignore --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1bca8e04..db044b5a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,7 @@ out/ .vscode/settings.json .vscode/extensions.json .idea -cmake-* \ No newline at end of file +cmake-* +.cache +.ccls +compile_commands.json From 153051ab824a23eceba3ff12ae5a160a4224f63d Mon Sep 17 00:00:00 2001 From: 446564 Date: Sat, 9 Aug 2025 20:47:35 -0700 Subject: [PATCH 41/77] add new UI to nano g2 --- variants/nano_g2_ultra/platformio.ini | 1 + variants/nano_g2_ultra/target.cpp | 53 +++++++++++---------------- variants/nano_g2_ultra/target.h | 9 +++-- 3 files changed, 29 insertions(+), 34 deletions(-) diff --git a/variants/nano_g2_ultra/platformio.ini b/variants/nano_g2_ultra/platformio.ini index 511c0ae7..7cbac320 100644 --- a/variants/nano_g2_ultra/platformio.ini +++ b/variants/nano_g2_ultra/platformio.ini @@ -47,6 +47,7 @@ build_src_filter = ${Nano_G2_Ultra.build_src_filter} + + + + + +<../examples/companion_radio> lib_deps = ${Nano_G2_Ultra.lib_deps} diff --git a/variants/nano_g2_ultra/target.cpp b/variants/nano_g2_ultra/target.cpp index b6084236..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,29 +17,26 @@ 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); } @@ -64,8 +62,7 @@ void NanoG2UltraSensorManager::stop_gps() { _location->stop(); } -bool NanoG2UltraSensorManager::begin() -{ +bool NanoG2UltraSensorManager::begin() { digitalWrite(PIN_GPS_STANDBY, HIGH); // Wake GPS from standby Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX); Serial1.begin(9600); @@ -83,29 +80,26 @@ bool NanoG2UltraSensorManager::begin() 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 + return; // GPS is not active, skip further processing } _location->loop(); if (millis() > next_gps_update) { if (_location->isValid()) { - node_lat = ((double)_location->getLatitude())/1000000.; - node_lon = ((double)_location->getLongitude())/1000000.; + node_lat = ((double)_location->getLatitude()) / 1000000.; + node_lon = ((double)_location->getLongitude()) / 1000000.; node_altitude = ((double)_location->getAltitude()) / 1000.0; MESH_DEBUG_PRINTLN("VALID location: lat %f lon %f", node_lat, node_lon); } else { @@ -116,24 +110,22 @@ void NanoG2UltraSensorManager::loop() } } -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) -{ +bool NanoG2UltraSensorManager::setSettingValue(const char *name, const char *value) { if (strcmp(name, "gps") == 0) { if (strcmp(value, "0") == 0) { stop_gps(); @@ -145,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(); From af7db5593baf6ef24670d0e936b4f1acb3eaf107 Mon Sep 17 00:00:00 2001 From: gumbero Date: Sun, 10 Aug 2025 14:25:43 +0200 Subject: [PATCH 42/77] Update platformio.ini --- variants/lilygo_tbeam_SX1276/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/lilygo_tbeam_SX1276/platformio.ini b/variants/lilygo_tbeam_SX1276/platformio.ini index 72040eab..8febe7b3 100644 --- a/variants/lilygo_tbeam_SX1276/platformio.ini +++ b/variants/lilygo_tbeam_SX1276/platformio.ini @@ -69,7 +69,7 @@ lib_deps = ${LilyGo_TBeam_SX1276.lib_deps} ${esp32_ota.lib_deps} -[env:Tbeam_SX1276_room] +[env:Tbeam_SX1276_room_server] extends = LilyGo_TBeam_SX1276 build_flags = ${LilyGo_TBeam_SX1276.build_flags} @@ -83,4 +83,4 @@ build_src_filter = ${LilyGo_TBeam_SX1276.build_src_filter} +<../examples/simple_room_server> lib_deps = ${LilyGo_TBeam_SX1276.lib_deps} - ${esp32_ota.lib_deps} \ No newline at end of file + ${esp32_ota.lib_deps} From bed311313ad3e384554b9c46c258e4262bc0c55c Mon Sep 17 00:00:00 2001 From: kelsey hudson Date: Sun, 10 Aug 2025 10:34:47 -0700 Subject: [PATCH 43/77] Adding support for Ikoka Stick with Seeed Xiao nRF54 baseboard. Adds a new variant 'ikoka_stick_nrf' and associated support files. This is based on the Xiao nRF54 code with pin numbers and functions changed to suit the differences in hardware between the WIO SX1262 board and the Ikoka Stick. Sets the default LoRa transmit power to 9dBm to avoid burning up the frontend in Ikoka Sticks equipped with the Ebyte 33dBm S22 module on first boot. Adds support for an SSD1306 display connected to the display header. Note the display pinout is the same as the RAK4631 display header so make sure to use a display wired accordingly (aliexpress etc. SSD1306s typically have Vcc & GND reversed from what this board expects). Adds support for display rotation to SSD1306Display via a platformIO define. This support was added following the same paradigms found elsewhere in the code for rotating a display. --- src/helpers/ui/SSD1306Display.cpp | 3 + .../ikoka_stick_nrf/ikoka_stick_nrf_board.cpp | 99 ++++++++++++ .../ikoka_stick_nrf/ikoka_stick_nrf_board.h | 66 ++++++++ variants/ikoka_stick_nrf/platformio.ini | 133 ++++++++++++++++ variants/ikoka_stick_nrf/target.cpp | 43 +++++ variants/ikoka_stick_nrf/target.h | 26 +++ variants/ikoka_stick_nrf/variant.cpp | 86 ++++++++++ variants/ikoka_stick_nrf/variant.h | 149 ++++++++++++++++++ 8 files changed, 605 insertions(+) create mode 100644 variants/ikoka_stick_nrf/ikoka_stick_nrf_board.cpp create mode 100644 variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h create mode 100644 variants/ikoka_stick_nrf/platformio.ini create mode 100644 variants/ikoka_stick_nrf/target.cpp create mode 100644 variants/ikoka_stick_nrf/target.h create mode 100644 variants/ikoka_stick_nrf/variant.cpp create mode 100644 variants/ikoka_stick_nrf/variant.h 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/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..a5f33b01 --- /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, LOW); // turn TX LED on + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, HIGH); // 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..3e62b5e7 --- /dev/null +++ b/variants/ikoka_stick_nrf/platformio.ini @@ -0,0 +1,133 @@ +[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 +; -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> +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 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ikoka_stick_nrf.build_src_filter} + + + +<../examples/companion_radio> +lib_deps = + ${ikoka_stick_nrf.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:ikoka_stick_nrf_alt_pinout_companion_radio_ble] +extends = env:ikoka_stick_nrf_companion_radio_ble +build_flags = + ${env:ikoka_stick_nrf_companion_radio_ble.build_flags} + -D SX1262_XIAO_S3_VARIANT + +[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/main.cpp> + +[env:ikoka_stick_nrf_alt_pinout_repeater] +extends = env:ikoka_stick_nrf_repeater +build_flags = + ${env:ikoka_stick_nrf_repeater.build_flags} + -D SX1262_XIAO_S3_VARIANT + +[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/main.cpp> diff --git a/variants/ikoka_stick_nrf/target.cpp b/variants/ikoka_stick_nrf/target.cpp new file mode 100644 index 00000000..e50150eb --- /dev/null +++ b/variants/ikoka_stick_nrf/target.cpp @@ -0,0 +1,43 @@ +#include +#include "target.h" +#include + +ikoka_stick_nrf_board board; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#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..758cd019 --- /dev/null +++ b/variants/ikoka_stick_nrf/target.h @@ -0,0 +1,26 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include + +#ifdef DISPLAY_CLASS + #include + extern DISPLAY_CLASS display; +#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 From e84e3066ff0e54a0eaaf19116dc229796afdf865 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 12 Aug 2025 10:01:35 +1000 Subject: [PATCH 44/77] * MomentaryButton: pullupdown param moved to constructor --- src/helpers/ui/MomentaryButton.cpp | 7 ++++--- src/helpers/ui/MomentaryButton.h | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/helpers/ui/MomentaryButton.cpp b/src/helpers/ui/MomentaryButton.cpp index 783f7ba7..9ddf1327 100644 --- a/src/helpers/ui/MomentaryButton.cpp +++ b/src/helpers/ui/MomentaryButton.cpp @@ -1,17 +1,18 @@ #include "MomentaryButton.h" -MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse) { +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(bool pulldownup) { +void MomentaryButton::begin() { if (_pin >= 0) { - pinMode(_pin, pulldownup ? (_reverse ? INPUT_PULLUP : INPUT_PULLDOWN) : INPUT); + pinMode(_pin, _pull ? (_reverse ? INPUT_PULLUP : INPUT_PULLDOWN) : INPUT); } } diff --git a/src/helpers/ui/MomentaryButton.h b/src/helpers/ui/MomentaryButton.h index c46561e3..0bcc776c 100644 --- a/src/helpers/ui/MomentaryButton.h +++ b/src/helpers/ui/MomentaryButton.h @@ -9,15 +9,15 @@ class MomentaryButton { int8_t _pin; int8_t prev, cancel; - bool _reverse; + 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); - void begin(bool pulldownup=false); + 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; } From 86ec82fd06768a8d4afc5a35edb7b12af3640d36 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 12 Aug 2025 20:56:59 +1000 Subject: [PATCH 45/77] * Heltec CT62: sensor role (with 2 channel relay support + 1 digital input) --- variants/heltec_ct62/HT-CT62Board.h | 30 +++++++++++++++++++++++++++-- variants/heltec_ct62/platformio.ini | 21 ++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) 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..ec24cdcd 100644 --- a/variants/heltec_ct62/platformio.ini +++ b/variants/heltec_ct62/platformio.ini @@ -86,3 +86,24 @@ 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} From be243a2663a6a2d462228e11ed4210bb5bf5e0e7 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Wed, 13 Aug 2025 18:12:48 +0800 Subject: [PATCH 46/77] Add heltec_vision_master_e213 board. --- boards/heltec_vision_master_e213.json | 44 +++++++ src/helpers/RefCountedDigitalPin.h | 10 +- src/helpers/ui/E213Display.cpp | 121 +++++++++++++++--- src/helpers/ui/E213Display.h | 13 ++ .../HeltecE213Board.cpp | 69 ++++++++++ .../HeltecE213Board.h | 30 +++++ .../heltec_vision_master_e213/pins_arduino.h | 61 +++++++++ .../heltec_vision_master_e213/platformio.ini | 84 ++++++++++++ variants/heltec_vision_master_e213/target.cpp | 53 ++++++++ variants/heltec_vision_master_e213/target.h | 27 ++++ variants/heltec_wireless_paper/platformio.ini | 2 +- 11 files changed, 493 insertions(+), 21 deletions(-) create mode 100644 boards/heltec_vision_master_e213.json create mode 100644 variants/heltec_vision_master_e213/HeltecE213Board.cpp create mode 100644 variants/heltec_vision_master_e213/HeltecE213Board.h create mode 100644 variants/heltec_vision_master_e213/pins_arduino.h create mode 100644 variants/heltec_vision_master_e213/platformio.ini create mode 100644 variants/heltec_vision_master_e213/target.cpp create mode 100644 variants/heltec_vision_master_e213/target.h diff --git a/boards/heltec_vision_master_e213.json b/boards/heltec_vision_master_e213.json new file mode 100644 index 00000000..9bc6c389 --- /dev/null +++ b/boards/heltec_vision_master_e213.json @@ -0,0 +1,44 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_8MB.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": "8MB", + "maximum_ram_size": 8388608, + "maximum_size": 8388608, + "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/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/ui/E213Display.cpp b/src/helpers/ui/E213Display.cpp index 92bf37fb..0cb3dbf6 100644 --- a/src/helpers/ui/E213Display.cpp +++ b/src/helpers/ui/E213Display.cpp @@ -2,20 +2,59 @@ #include "../../MeshCore.h" +EInkDetectionResult 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) + return V_LCMEN213EFC1; + else // busy HIGH + return V_E0213A367; +} + + bool E213Display::begin() { if (_init) return true; powerOn(); - display.begin(); + _version = detectEInk(); + if(_version==V_LCMEN213EFC1) { + display.begin(); + // Set to landscape mode rotated 180 degrees + display.setRotation(3); + } else{ + display1.begin(); + // Set to landscape mode rotated 180 degrees + display1.setRotation(3); + } - // Set to landscape mode rotated 180 degrees - display.setRotation(3); _init = true; _isOn = true; clear(); - display.fastmodeOn(); // Enable fast mode for quicker (partial) updates + if(_version==V_LCMEN213EFC1) { + display.fastmodeOn(); // Enable fast mode for quicker (partial) updates + } else{ + display1.fastmodeOn(); // Enable fast mode for quicker (partial) updates + } return true; } @@ -23,15 +62,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 +93,37 @@ void E213Display::turnOff() { } void E213Display::clear() { - display.clear(); + if(_version==V_LCMEN213EFC1) { + display.clear(); + } else{ + display1.clear(); + } } void E213Display::startFrame(Color bkg) { // Fill screen with white first to ensure clean background - display.fillRect(0, 0, width(), height(), WHITE); + if(_version==V_LCMEN213EFC1) { + display.fillRect(0, 0, width(), height(), WHITE); + } else{ + display1.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); + if(_version==V_LCMEN213EFC1) { + display.fillRect(0, 0, width(), height(), BLACK); + } else{ + display1.fillRect(0, 0, width(), height(), BLACK); + } } } void E213Display::setTextSize(int sz) { // The library handles text size internally - display.setTextSize(sz); + if(_version==V_LCMEN213EFC1) { + display.setTextSize(sz); + } else{ + display1.setTextSize(sz); + } } void E213Display::setColor(Color c) { @@ -68,19 +131,35 @@ void E213Display::setColor(Color c) { } void E213Display::setCursor(int x, int y) { - display.setCursor(x, y); + if(_version==V_LCMEN213EFC1) { + display.setCursor(x, y); + } else{ + display1.setCursor(x, y); + } } void E213Display::print(const char *str) { - display.print(str); + if(_version==V_LCMEN213EFC1) { + display.print(str); + } else { + display1.print(str); + } } void E213Display::fillRect(int x, int y, int w, int h) { - display.fillRect(x, y, w, h, BLACK); + if(_version==V_LCMEN213EFC1) { + display.fillRect(x, y, w, h, BLACK); + } else { + display1.fillRect(x, y, w, h, BLACK); + } } void E213Display::drawRect(int x, int y, int w, int h) { - display.drawRect(x, y, w, h, BLACK); + if(_version==V_LCMEN213EFC1) { + display.drawRect(x, y, w, h, BLACK); + } else { + display1.drawRect(x, y, w, h, BLACK); + } } void E213Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { @@ -98,7 +177,11 @@ 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); + if(_version==V_LCMEN213EFC1) { + display.drawPixel(x + bx, y + by, BLACK); + } else { + display1.drawPixel(x + bx, y + by, BLACK); + } } } } @@ -107,10 +190,18 @@ 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); + if(_version==V_LCMEN213EFC1) { + display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); + } else { + display1.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); + } return w; } void E213Display::endFrame() { - display.update(); + if(_version==V_LCMEN213EFC1) { + display.update(); + } else { + display1.update(); + } } diff --git a/src/helpers/ui/E213Display.h b/src/helpers/ui/E213Display.h index 330a2b6d..46d13d0f 100644 --- a/src/helpers/ui/E213Display.h +++ b/src/helpers/ui/E213Display.h @@ -6,9 +6,21 @@ #include #include +enum EInkDetectionResult { + V_LCMEN213EFC1 = 0, // Initial version + V_E0213A367 = 1, // E213 PCB marked V1.1 (Mid 2025) +}; + // Display driver for E213 e-ink display class E213Display : public DisplayDriver { +#ifdef VISION_MASTER_E213 EInkDisplay_VisionMasterE213 display; + EInkDisplay_VisionMasterE213V1_1 display1; +#else + EInkDisplay_WirelessPaperV1_1 display; + EInkDisplay_WirelessPaperV1_1_1 display1; +#endif + EInkDetectionResult _version =V_LCMEN213EFC1; bool _init = false; bool _isOn = false; @@ -32,6 +44,7 @@ public: void endFrame() override; private: + EInkDetectionResult detectEInk(); void powerOn(); void powerOff(); }; \ No newline at end of file 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_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 From dc9b4f8e846ea5a40029beeca2924d1c417c4d0e Mon Sep 17 00:00:00 2001 From: 446564 Date: Wed, 13 Aug 2025 09:47:01 -0700 Subject: [PATCH 47/77] add nano g2 usb companion --- variants/nano_g2_ultra/platformio.ini | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/variants/nano_g2_ultra/platformio.ini b/variants/nano_g2_ultra/platformio.ini index 511c0ae7..25871fc9 100644 --- a/variants/nano_g2_ultra/platformio.ini +++ b/variants/nano_g2_ultra/platformio.ini @@ -55,3 +55,27 @@ lib_deps = 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 + -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> +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 From fad4a7fb5170a85202e55b7d4d079d0e77b5424c Mon Sep 17 00:00:00 2001 From: Quency-D Date: Thu, 14 Aug 2025 10:26:26 +0800 Subject: [PATCH 48/77] Modify the flash size to 16MB. --- boards/heltec_vision_master_e213.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/boards/heltec_vision_master_e213.json b/boards/heltec_vision_master_e213.json index 9bc6c389..81efd8f5 100644 --- a/boards/heltec_vision_master_e213.json +++ b/boards/heltec_vision_master_e213.json @@ -2,7 +2,7 @@ "build": { "arduino": { "ldscript": "esp32s3_out.ld", - "partitions": "default_8MB.csv", + "partitions": "default_16MB.csv", "memory_type": "qio_opi" }, "core": "esp32", @@ -31,9 +31,9 @@ "frameworks": ["arduino", "espidf"], "name": "Heltec Vision Master E213", "upload": { - "flash_size": "8MB", + "flash_size": "16MB", "maximum_ram_size": 8388608, - "maximum_size": 8388608, + "maximum_size": 16777216, "use_1200bps_touch": true, "wait_for_upload_port": true, "require_upload_port": true, From 6d18e2c57b1806e1e6aca1c2cf2faad2f4f2e54e Mon Sep 17 00:00:00 2001 From: Quency-D Date: Thu, 14 Aug 2025 10:30:27 +0800 Subject: [PATCH 49/77] Add heltec_vision_master_e290 board. --- boards/heltec_vision_master_e290.json | 44 +++++++ src/helpers/ui/E290Display.cpp | 116 ++++++++++++++++++ src/helpers/ui/E290Display.h | 37 ++++++ .../HeltecE290Board.cpp | 69 +++++++++++ .../HeltecE290Board.h | 30 +++++ .../heltec_vision_master_e290/pins_arduino.h | 61 +++++++++ .../heltec_vision_master_e290/platformio.ini | 78 ++++++++++++ variants/heltec_vision_master_e290/target.cpp | 53 ++++++++ variants/heltec_vision_master_e290/target.h | 27 ++++ 9 files changed, 515 insertions(+) create mode 100644 boards/heltec_vision_master_e290.json create mode 100644 src/helpers/ui/E290Display.cpp create mode 100644 src/helpers/ui/E290Display.h create mode 100644 variants/heltec_vision_master_e290/HeltecE290Board.cpp create mode 100644 variants/heltec_vision_master_e290/HeltecE290Board.h create mode 100644 variants/heltec_vision_master_e290/pins_arduino.h create mode 100644 variants/heltec_vision_master_e290/platformio.ini create mode 100644 variants/heltec_vision_master_e290/target.cpp create mode 100644 variants/heltec_vision_master_e290/target.h 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/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/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 From aa7f9d8df634f1c377cb252aeb54437f9353b88d Mon Sep 17 00:00:00 2001 From: Quency-D Date: Thu, 14 Aug 2025 17:43:46 +0800 Subject: [PATCH 50/77] Use the base class to optimize screen display code. --- src/helpers/ui/E213Display.cpp | 109 ++++++++++----------------------- src/helpers/ui/E213Display.h | 22 +++---- 2 files changed, 40 insertions(+), 91 deletions(-) diff --git a/src/helpers/ui/E213Display.cpp b/src/helpers/ui/E213Display.cpp index 0cb3dbf6..a0e71f31 100644 --- a/src/helpers/ui/E213Display.cpp +++ b/src/helpers/ui/E213Display.cpp @@ -2,7 +2,7 @@ #include "../../MeshCore.h" -EInkDetectionResult E213Display::detectEInk() +BaseDisplay* E213Display::detectEInk() { // Test 1: Logic of BUSY pin @@ -23,38 +23,37 @@ EInkDetectionResult E213Display::detectEInk() // Test complete. Release pin pinMode(DISP_RST, INPUT); - if (busyLogic == LOW) - return V_LCMEN213EFC1; - else // busy HIGH - return V_E0213A367; + 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(); - _version = detectEInk(); - if(_version==V_LCMEN213EFC1) { - display.begin(); - // Set to landscape mode rotated 180 degrees - display.setRotation(3); - } else{ - display1.begin(); - // Set to landscape mode rotated 180 degrees - display1.setRotation(3); + if(display==NULL) { + display = detectEInk(); } - + display->begin(); + // Set to landscape mode rotated 180 degrees + display->setRotation(3); _init = true; _isOn = true; clear(); - if(_version==V_LCMEN213EFC1) { - display.fastmodeOn(); // Enable fast mode for quicker (partial) updates - } else{ - display1.fastmodeOn(); // Enable fast mode for quicker (partial) updates - } + display->fastmodeOn(); // Enable fast mode for quicker (partial) updates return true; } @@ -93,37 +92,23 @@ void E213Display::turnOff() { } void E213Display::clear() { - if(_version==V_LCMEN213EFC1) { - display.clear(); - } else{ - display1.clear(); - } + display->clear(); + } void E213Display::startFrame(Color bkg) { // Fill screen with white first to ensure clean background - if(_version==V_LCMEN213EFC1) { - display.fillRect(0, 0, width(), height(), WHITE); - } else{ - display1.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) - if(_version==V_LCMEN213EFC1) { - display.fillRect(0, 0, width(), height(), BLACK); - } else{ - display1.fillRect(0, 0, width(), height(), BLACK); - } + display->fillRect(0, 0, width(), height(), BLACK); } } void E213Display::setTextSize(int sz) { // The library handles text size internally - if(_version==V_LCMEN213EFC1) { - display.setTextSize(sz); - } else{ - display1.setTextSize(sz); - } + display->setTextSize(sz); } void E213Display::setColor(Color c) { @@ -131,35 +116,19 @@ void E213Display::setColor(Color c) { } void E213Display::setCursor(int x, int y) { - if(_version==V_LCMEN213EFC1) { - display.setCursor(x, y); - } else{ - display1.setCursor(x, y); - } + display->setCursor(x, y); } void E213Display::print(const char *str) { - if(_version==V_LCMEN213EFC1) { - display.print(str); - } else { - display1.print(str); - } + display->print(str); } void E213Display::fillRect(int x, int y, int w, int h) { - if(_version==V_LCMEN213EFC1) { - display.fillRect(x, y, w, h, BLACK); - } else { - display1.fillRect(x, y, w, h, BLACK); - } + display->fillRect(x, y, w, h, BLACK); } void E213Display::drawRect(int x, int y, int w, int h) { - if(_version==V_LCMEN213EFC1) { - display.drawRect(x, y, w, h, BLACK); - } else { - display1.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) { @@ -177,11 +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) { - if(_version==V_LCMEN213EFC1) { - display.drawPixel(x + bx, y + by, BLACK); - } else { - display1.drawPixel(x + bx, y + by, BLACK); - } + display->drawPixel(x + bx, y + by, BLACK); } } } @@ -190,18 +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; - if(_version==V_LCMEN213EFC1) { - display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); - } else { - display1.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); - } + display->getTextBounds(str, 0, 0, &x1, &y1, &w, &h); return w; } void E213Display::endFrame() { - if(_version==V_LCMEN213EFC1) { - display.update(); - } else { - display1.update(); - } + display->update(); } diff --git a/src/helpers/ui/E213Display.h b/src/helpers/ui/E213Display.h index 46d13d0f..657bfb4c 100644 --- a/src/helpers/ui/E213Display.h +++ b/src/helpers/ui/E213Display.h @@ -6,27 +6,19 @@ #include #include -enum EInkDetectionResult { - V_LCMEN213EFC1 = 0, // Initial version - V_E0213A367 = 1, // E213 PCB marked V1.1 (Mid 2025) -}; - // Display driver for E213 e-ink display class E213Display : public DisplayDriver { -#ifdef VISION_MASTER_E213 - EInkDisplay_VisionMasterE213 display; - EInkDisplay_VisionMasterE213V1_1 display1; -#else - EInkDisplay_WirelessPaperV1_1 display; - EInkDisplay_WirelessPaperV1_1_1 display1; -#endif - EInkDetectionResult _version =V_LCMEN213EFC1; + 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; @@ -44,7 +36,7 @@ public: void endFrame() override; private: - EInkDetectionResult detectEInk(); + BaseDisplay* detectEInk(); void powerOn(); void powerOff(); }; \ No newline at end of file From d7c2293cb839d189564c9e3b464f267a2c3d6ddd Mon Sep 17 00:00:00 2001 From: MikesAllotment Date: Thu, 14 Aug 2025 14:17:26 +0100 Subject: [PATCH 51/77] Added support for new EnhancedUI MomentaryButton for Heltec V2 and Faketec ProMicro variants --- variants/heltec_v2/platformio.ini | 4 ++++ variants/heltec_v2/target.cpp | 1 + variants/heltec_v2/target.h | 2 ++ variants/promicro/platformio.ini | 5 +++++ variants/promicro/target.cpp | 1 + variants/promicro/target.h | 2 ++ 6 files changed, 15 insertions(+) diff --git a/variants/heltec_v2/platformio.ini b/variants/heltec_v2/platformio.ini index 43bc43ad..04768df4 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} @@ -84,6 +86,7 @@ build_flags = build_src_filter = ${Heltec_lora32_v2.build_src_filter} + + + + +<../examples/companion_radio> lib_deps = ${Heltec_lora32_v2.lib_deps} @@ -104,6 +107,7 @@ build_flags = build_src_filter = ${Heltec_lora32_v2.build_src_filter} + + + + +<../examples/companion_radio> lib_deps = ${Heltec_lora32_v2.lib_deps} 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/promicro/platformio.ini b/variants/promicro/platformio.ini index 78b25aa7..0ec88070 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 @@ -93,6 +95,7 @@ build_flags = ${Faketec.build_flags} build_src_filter = ${Faketec.build_src_filter} +<../examples/companion_radio> + + + lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 @@ -112,6 +115,7 @@ build_src_filter = ${Faketec.build_src_filter} + +<../examples/companion_radio> + + + lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 @@ -129,6 +133,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(); From 86671c0ff80a38833cc6a550168c33ae7f17b2de Mon Sep 17 00:00:00 2001 From: taco Date: Sat, 16 Aug 2025 09:29:48 +1000 Subject: [PATCH 52/77] Support NewUI on WioTrackerL1 --- variants/wio-tracker-l1/platformio.ini | 1 + variants/wio-tracker-l1/target.cpp | 1 + variants/wio-tracker-l1/target.h | 2 ++ 3 files changed, 4 insertions(+) diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 380ff90f..cefe9649 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -85,6 +85,7 @@ build_flags = ${WioTrackerL1.build_flags} build_src_filter = ${WioTrackerL1.build_src_filter} + +<../examples/companion_radio> + + + lib_deps = ${WioTrackerL1.lib_deps} adafruit/RTClib @ ^2.1.3 diff --git a/variants/wio-tracker-l1/target.cpp b/variants/wio-tracker-l1/target.cpp index 0809e19e..dc484546 100644 --- a/variants/wio-tracker-l1/target.cpp +++ b/variants/wio-tracker-l1/target.cpp @@ -16,6 +16,7 @@ WioTrackerL1SensorManager sensors = WioTrackerL1SensorManager(nmea); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/wio-tracker-l1/target.h b/variants/wio-tracker-l1/target.h index ab42b7b5..d5fe4d70 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,7 @@ extern AutoDiscoverRTCClock rtc_clock; extern WioTrackerL1SensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); From e9ffc3ea93d7912460c38cbaafb726f5455ad8cf Mon Sep 17 00:00:00 2001 From: kelsey hudson Date: Fri, 15 Aug 2025 19:29:22 -0700 Subject: [PATCH 53/77] Ikoka Stick: repeater/room server functionality Make Repeater and Room Server work as build targets. Remove esp32-related alternate pinout cruft from the Ikoka Stick NRF build tree. --- variants/ikoka_stick_nrf/platformio.ini | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/variants/ikoka_stick_nrf/platformio.ini b/variants/ikoka_stick_nrf/platformio.ini index 3e62b5e7..b883e8f7 100644 --- a/variants/ikoka_stick_nrf/platformio.ini +++ b/variants/ikoka_stick_nrf/platformio.ini @@ -93,12 +93,6 @@ lib_deps = ${ikoka_stick_nrf.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:ikoka_stick_nrf_alt_pinout_companion_radio_ble] -extends = env:ikoka_stick_nrf_companion_radio_ble -build_flags = - ${env:ikoka_stick_nrf_companion_radio_ble.build_flags} - -D SX1262_XIAO_S3_VARIANT - [env:ikoka_stick_nrf_repeater] extends = ikoka_stick_nrf build_flags = @@ -111,13 +105,8 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${ikoka_stick_nrf.build_src_filter} - +<../examples/simple_repeater/main.cpp> - -[env:ikoka_stick_nrf_alt_pinout_repeater] -extends = env:ikoka_stick_nrf_repeater -build_flags = - ${env:ikoka_stick_nrf_repeater.build_flags} - -D SX1262_XIAO_S3_VARIANT + + + +<../examples/simple_repeater> [env:ikoka_stick_nrf_room_server] extends = ikoka_stick_nrf @@ -130,4 +119,4 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${ikoka_stick_nrf.build_src_filter} - +<../examples/simple_room_server/main.cpp> + +<../examples/simple_room_server> From acde9921b53e6a0f95caeb25bec1c698a46fb5f8 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 16 Aug 2025 20:04:54 +1000 Subject: [PATCH 54/77] * Refactor of UITask, moved to /ui-new --- examples/companion_radio/AbstractUITask.h | 46 ++++++++++++++++++ examples/companion_radio/MyMesh.cpp | 24 ++++------ examples/companion_radio/MyMesh.h | 10 ++-- examples/companion_radio/main.cpp | 13 +++-- .../companion_radio/{ => ui-new}/UITask.cpp | 3 +- .../companion_radio/{ => ui-new}/UITask.h | 39 +++++---------- examples/companion_radio/{ => ui-new}/icons.h | 0 variants/heltec_v3/platformio.ini | 48 +------------------ variants/t114/platformio.ini | 4 ++ 9 files changed, 86 insertions(+), 101 deletions(-) create mode 100644 examples/companion_radio/AbstractUITask.h rename examples/companion_radio/{ => ui-new}/UITask.cpp (99%) rename examples/companion_radio/{ => ui-new}/UITask.h (62%) rename examples/companion_radio/{ => ui-new}/icons.h (100%) diff --git a/examples/companion_radio/AbstractUITask.h b/examples/companion_radio/AbstractUITask.h new file mode 100644 index 00000000..706254af --- /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(); +}; diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 75828d5e..1fa5478b 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -109,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; @@ -247,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 } @@ -354,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 @@ -416,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 @@ -426,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 } @@ -635,9 +631,9 @@ 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; @@ -1041,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; @@ -1643,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 42819813..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 @@ -87,7 +85,7 @@ struct AdvertPath { 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); @@ -179,6 +177,7 @@ private: 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; @@ -216,6 +215,3 @@ private: }; 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 d9b3f68b..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, &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() { diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp similarity index 99% rename from examples/companion_radio/UITask.cpp rename to examples/companion_radio/ui-new/UITask.cpp index 73bd00fc..64b788b2 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -1,7 +1,6 @@ #include "UITask.h" #include -#include "NodePrefs.h" -#include "MyMesh.h" +#include "../MyMesh.h" #include "target.h" #define AUTO_OFF_MILLIS 15000 // 15 seconds diff --git a/examples/companion_radio/UITask.h b/examples/companion_radio/ui-new/UITask.h similarity index 62% rename from examples/companion_radio/UITask.h rename to examples/companion_radio/ui-new/UITask.h index 818779e5..c8b211b6 100644 --- a/examples/companion_radio/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -11,29 +11,16 @@ #include #endif -#include "NodePrefs.h" +#include "../AbstractUITask.h" +#include "../NodePrefs.h" -enum class UIEventType { - none, - contactMessage, - channelMessage, - roomMessage, - newContactMessage, - ack -}; - -#define MAX_TOP_LEVEL 8 - -class UITask { +class UITask : public AbstractUITask { DisplayDriver* _display; - mesh::MainBoard* _board; - BaseSerialInterface* _serial; SensorManager* _sensors; #ifdef PIN_BUZZER genericBuzzer buzzer; #endif unsigned long _next_refresh, _auto_off; - bool _connected; NodePrefs* _node_prefs; char _alert[80]; unsigned long _alert_expiry; @@ -55,28 +42,24 @@ class UITask { public: - UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : _board(board), _serial(serial), _display(NULL), _sensors(NULL) { + UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : AbstractUITask(board, serial), _display(NULL), _sensors(NULL) { next_batt_chck = _next_refresh = 0; ui_started_at = 0; - _connected = false; curr = NULL; } void begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs); void gotoHomeScreen() { setCurrScreen(home); } void showAlert(const char* text, int duration_millis); - 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(); } int getMsgCount() const { return _msgcount; } bool hasDisplay() const { return _display != NULL; } bool isButtonPressed() const; - 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 AbsractUITask + 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/companion_radio/icons.h b/examples/companion_radio/ui-new/icons.h similarity index 100% rename from examples/companion_radio/icons.h rename to examples/companion_radio/ui-new/icons.h diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index ec0956d8..c65bbec5 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -103,6 +103,7 @@ lib_deps = extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=160 -D MAX_GROUP_CHANNELS=8 -D DISPLAY_CLASS=SSD1306Display @@ -117,6 +118,7 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + +<../examples/companion_radio> + +<../examples/companion_radio/ui-new> lib_deps = ${Heltec_lora32_v3.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -244,49 +246,3 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter} lib_deps = ${Heltec_lora32_v3.lib_deps} ${esp32_ota.lib_deps} - -[env:Heltec_WSL3_espnow_bridge] -extends = Heltec_lora32_v3 -build_flags = - ${Heltec_lora32_v3.build_flags} -; -D LORA_FREQ=915.8 - -D MESH_PACKET_LOGGING=1 - -D ENV_INCLUDE_AHTX0=0 - -D ENV_INCLUDE_BME280=0 - -D ENV_INCLUDE_BMP280=0 - -D ENV_INCLUDE_INA3221=0 - -D ENV_INCLUDE_INA219=0 - -D ENV_INCLUDE_MLX90614=0 - -D ENV_INCLUDE_VL53L0X=0 - -D ENV_INCLUDE_GPS=0 -; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v3.build_src_filter} - +<../examples/simple_bridge/main.cpp> - + -lib_deps = - ${Heltec_lora32_v3.lib_deps} - bakercp/CRC32 @ ^2.0.0 - -[env:Heltec_WSL3_serial_bridge] -extends = Heltec_lora32_v3 -build_flags = - ${Heltec_lora32_v3.build_flags} -; -D LORA_FREQ=915.8 - -D MESH_PACKET_LOGGING=1 - -D SERIAL_BRIDGE_RX=47 - -D SERIAL_BRIDGE_TX=48 - -D ENV_INCLUDE_AHTX0=0 - -D ENV_INCLUDE_BME280=0 - -D ENV_INCLUDE_BMP280=0 - -D ENV_INCLUDE_INA3221=0 - -D ENV_INCLUDE_INA219=0 - -D ENV_INCLUDE_MLX90614=0 - -D ENV_INCLUDE_VL53L0X=0 - -D ENV_INCLUDE_GPS=0 -; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v3.build_src_filter} - +<../examples/simple_bridge/main.cpp> - +<../examples/simple_bridge/SerialBridgeRadio.cpp> -lib_deps = - ${Heltec_lora32_v3.lib_deps} - bakercp/CRC32 @ ^2.0.0 diff --git a/variants/t114/platformio.ini b/variants/t114/platformio.ini index 815ef173..e08da3ac 100644 --- a/variants/t114/platformio.ini +++ b/variants/t114/platformio.ini @@ -73,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 @@ -83,6 +84,7 @@ build_flags = build_src_filter = ${Heltec_t114.build_src_filter} + +<../examples/companion_radio> + +<../examples/companion_radio/ui-new> lib_deps = ${Heltec_t114.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -91,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 @@ -100,6 +103,7 @@ build_flags = build_src_filter = ${Heltec_t114.build_src_filter} + +<../examples/companion_radio> + +<../examples/companion_radio/ui-new> lib_deps = ${Heltec_t114.lib_deps} densaugeo/base64 @ ~1.4.0 \ No newline at end of file From e14b022a7cbd51923e1187c46290a3853f6ba0ae Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 16 Aug 2025 21:09:35 +1000 Subject: [PATCH 55/77] * original UITask now in /ui-orig folder --- examples/companion_radio/ui-new/UITask.h | 2 +- .../companion_radio/{ => ui-orig}/Button.cpp | 0 .../companion_radio/{ => ui-orig}/Button.h | 0 examples/companion_radio/ui-orig/UITask.cpp | 431 ++++++++++++++++++ examples/companion_radio/ui-orig/UITask.h | 73 +++ variants/heltec_v3/platformio.ini | 8 +- variants/nano_g2_ultra/platformio.ini | 9 +- variants/promicro/platformio.ini | 8 +- variants/t1000-e/platformio.ini | 2 + variants/t114/platformio.ini | 8 +- variants/techo/platformio.ini | 4 +- variants/thinknode_m1/platformio.ini | 4 +- variants/wio-tracker-l1/platformio.ini | 4 +- 13 files changed, 538 insertions(+), 15 deletions(-) rename examples/companion_radio/{ => ui-orig}/Button.cpp (100%) rename examples/companion_radio/{ => ui-orig}/Button.h (100%) create mode 100644 examples/companion_radio/ui-orig/UITask.cpp create mode 100644 examples/companion_radio/ui-orig/UITask.h diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index c8b211b6..f9e01550 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -55,7 +55,7 @@ public: bool hasDisplay() const { return _display != NULL; } bool isButtonPressed() const; - // from AbsractUITask + // 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; 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/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp new file mode 100644 index 00000000..29d995a7 --- /dev/null +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -0,0 +1,431 @@ +#include "UITask.h" +#include +#include +#include "../MyMesh.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 + +#ifndef USER_BTN_PRESSED +#define USER_BTN_PRESSED LOW +#endif + +// 'meshcore', 128x13px +static const uint8_t meshcore_logo [] PROGMEM = { + 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, +}; + +void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs) { + _display = display; + _sensors = sensors; + _auto_off = millis() + AUTO_OFF_MILLIS; + clearMsgPreview(); + _node_prefs = node_prefs; + if (_display != NULL) { + _display->turnOn(); + } + + // strip off dash and commit hash by changing dash to null terminator + // e.g: v1.2.3-abcdef -> v1.2.3 + char *version = strdup(FIRMWARE_VERSION); + char *dash = strchr(version, '-'); + if (dash) { + *dash = 0; + } + + // v1.2.3 (1 Jan 2025) + sprintf(_version_info, "%s (%s)", version, FIRMWARE_BUILD_DATE); + +#ifdef PIN_BUZZER + buzzer.begin(); +#endif + + // Initialize digital button if available +#ifdef PIN_USER_BTN + _userButton = new Button(PIN_USER_BTN, USER_BTN_PRESSED); + _userButton->begin(); + + // Set up digital button callbacks + _userButton->onShortPress([this]() { handleButtonShortPress(); }); + _userButton->onDoublePress([this]() { handleButtonDoublePress(); }); + _userButton->onTriplePress([this]() { handleButtonTriplePress(); }); + _userButton->onQuadruplePress([this]() { handleButtonQuadruplePress(); }); + _userButton->onLongPress([this]() { handleButtonLongPress(); }); + _userButton->onAnyPress([this]() { handleButtonAnyPress(); }); +#endif + + // Initialize analog button if available +#ifdef PIN_USER_BTN_ANA + _userButtonAnalog = new Button(PIN_USER_BTN_ANA, USER_BTN_PRESSED, true, 20); + _userButtonAnalog->begin(); + + // Set up analog button callbacks + _userButtonAnalog->onShortPress([this]() { handleButtonShortPress(); }); + _userButtonAnalog->onDoublePress([this]() { handleButtonDoublePress(); }); + _userButtonAnalog->onTriplePress([this]() { handleButtonTriplePress(); }); + _userButtonAnalog->onQuadruplePress([this]() { handleButtonQuadruplePress(); }); + _userButtonAnalog->onLongPress([this]() { handleButtonLongPress(); }); + _userButtonAnalog->onAnyPress([this]() { handleButtonAnyPress(); }); +#endif + ui_started_at = 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 +// Serial.print("DBG: Buzzzzzz -> "); +// Serial.println((int) bet); +} + +void UITask::msgRead(int msgcount) { + _msgcount = msgcount; + if (msgcount == 0) { + clearMsgPreview(); + } +} + +void UITask::clearMsgPreview() { + _origin[0] = 0; + _msg[0] = 0; + _need_refresh = true; +} + +void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) { + _msgcount = msgcount; + + if (path_len == 0xFF) { + sprintf(_origin, "(F) %s", from_name); + } else { + sprintf(_origin, "(%d) %s", (uint32_t) path_len, from_name); + } + StrHelper::strncpy(_msg, text, sizeof(_msg)); + + if (_display != NULL) { + if (!_display->isOn()) _display->turnOn(); + _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer + _need_refresh = true; + } +} + +void UITask::renderBatteryIndicator(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 = 12; + 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); +} + +void UITask::renderCurrScreen() { + if (_display == NULL) return; // assert() ?? + + char tmp[80]; + if (_alert[0]) { + _display->setTextSize(1.4); + uint16_t textWidth = _display->getTextWidth(_alert); + _display->setCursor((_display->width() - textWidth) / 2, 22); + _display->setColor(DisplayDriver::GREEN); + _display->print(_alert); + _alert[0] = 0; + _need_refresh = true; + return; + } else if (_origin[0] && _msg[0]) { // message preview + // render message preview + _display->setCursor(0, 0); + _display->setTextSize(1); + _display->setColor(DisplayDriver::GREEN); + _display->print(_node_prefs->node_name); + + _display->setCursor(0, 12); + _display->setColor(DisplayDriver::YELLOW); + _display->print(_origin); + _display->setCursor(0, 24); + _display->setColor(DisplayDriver::LIGHT); + _display->print(_msg); + + _display->setCursor(_display->width() - 28, 9); + _display->setTextSize(2); + _display->setColor(DisplayDriver::ORANGE); + sprintf(tmp, "%d", _msgcount); + _display->print(tmp); + _display->setColor(DisplayDriver::YELLOW); // last color will be kept on T114 + } else if ((millis() - ui_started_at) < BOOT_SCREEN_MILLIS) { // boot screen + // 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(1); + uint16_t textWidth = _display->getTextWidth(_version_info); + _display->setCursor((_display->width() - textWidth) / 2, 22); + _display->print(_version_info); + } else { // home screen + // node name + _display->setCursor(0, 0); + _display->setTextSize(1); + _display->setColor(DisplayDriver::GREEN); + _display->print(_node_prefs->node_name); + + // battery voltage + renderBatteryIndicator(_board->getBattMilliVolts()); + + // freq / sf + _display->setCursor(0, 20); + _display->setColor(DisplayDriver::YELLOW); + sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf); + _display->print(tmp); + + // bw / cr + _display->setCursor(0, 30); + sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr); + _display->print(tmp); + + // BT pin + if (!_connected && the_mesh.getBLEPin() != 0) { + _display->setColor(DisplayDriver::RED); + _display->setTextSize(2); + _display->setCursor(0, 43); + sprintf(tmp, "Pin:%d", the_mesh.getBLEPin()); + _display->print(tmp); + _display->setColor(DisplayDriver::GREEN); + } else { + _display->setColor(DisplayDriver::LIGHT); + } + } + _need_refresh = false; +} + +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 +} + +/* + 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 + _board->powerOff(); +} + +void UITask::loop() { + #ifdef PIN_USER_BTN + if (_userButton) { + _userButton->update(); + } + #endif + #ifdef PIN_USER_BTN_ANA + if (_userButtonAnalog) { + _userButtonAnalog->update(); + } + #endif + userLedHandler(); + +#ifdef PIN_BUZZER + if (buzzer.isPlaying()) buzzer.loop(); +#endif + + if (_display != NULL && _display->isOn()) { + static bool _firstBoot = true; + if(_firstBoot && (millis() - ui_started_at) >= BOOT_SCREEN_MILLIS) { + _need_refresh = true; + _firstBoot = false; + } + if (millis() >= _next_refresh && _need_refresh) { + _display->startFrame(); + renderCurrScreen(); + _display->endFrame(); + + _next_refresh = millis() + 1000; // refresh every second + } + if (millis() > _auto_off) { + _display->turnOff(); + } + } +} + +void UITask::handleButtonAnyPress() { + MESH_DEBUG_PRINTLN("UITask: any press triggered"); + // called on any button press before other events, to wake up the display quickly + // do not refresh the display here, as it may block the button handler + if (_display != NULL) { + _displayWasOn = _display->isOn(); // Track display state before any action + if (!_displayWasOn) { + _display->turnOn(); + } + _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer + } +} + +void UITask::handleButtonShortPress() { + MESH_DEBUG_PRINTLN("UITask: short press triggered"); + if (_display != NULL) { + // Only clear message preview if display was already on before button press + if (_displayWasOn) { + // If display was on and showing message preview, clear it + if (_origin[0] && _msg[0]) { + clearMsgPreview(); + } else { + // Otherwise, refresh the display + _need_refresh = true; + } + } else { + _need_refresh = true; // display just turned on, so we need to refresh + } + // Note: Display turn-on and auto-off timer extension are handled by handleButtonAnyPress + } +} + +void UITask::handleButtonDoublePress() { + MESH_DEBUG_PRINTLN("UITask: double press triggered, sending advert"); + // ADVERT + #ifdef PIN_BUZZER + soundBuzzer(UIEventType::ack); + #endif + if (the_mesh.advert()) { + MESH_DEBUG_PRINTLN("Advert sent!"); + sprintf(_alert, "Advert sent!"); + } else { + MESH_DEBUG_PRINTLN("Advert failed!"); + sprintf(_alert, "Advert failed.."); + } + _need_refresh = true; +} + +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); + sprintf(_alert, "Buzzer: ON"); + } else { + buzzer.quiet(true); + sprintf(_alert, "Buzzer: OFF"); + } + _need_refresh = true; + #endif +} + +void UITask::handleButtonQuadruplePress() { + MESH_DEBUG_PRINTLN("UITask: quad press triggered"); + if (_sensors != NULL) { + // toggle GPS onn/off + int num = _sensors->getNumSettings(); + for (int i = 0; i < num; i++) { + if (strcmp(_sensors->getSettingName(i), "gps") == 0) { + if (strcmp(_sensors->getSettingValue(i), "1") == 0) { + _sensors->setSettingValue("gps", "0"); + soundBuzzer(UIEventType::ack); + sprintf(_alert, "GPS: Disabled"); + } else { + _sensors->setSettingValue("gps", "1"); + soundBuzzer(UIEventType::ack); + sprintf(_alert, "GPS: Enabled"); + } + break; + } + } + } + _need_refresh = true; +} + +void UITask::handleButtonLongPress() { + MESH_DEBUG_PRINTLN("UITask: long press triggered"); + if (millis() - ui_started_at < 8000) { // long press in first 8 seconds since startup -> CLI/rescue + the_mesh.enterCLIRescue(); + } else { + shutdown(); + } +} \ No newline at end of file diff --git a/examples/companion_radio/ui-orig/UITask.h b/examples/companion_radio/ui-orig/UITask.h new file mode 100644 index 00000000..a59ddc41 --- /dev/null +++ b/examples/companion_radio/ui-orig/UITask.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include + +#ifdef PIN_BUZZER + #include +#endif + +#include "../AbstractUITask.h" +#include "../NodePrefs.h" + +#include "Button.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 _version_info[32]; + char _origin[62]; + char _msg[80]; + char _alert[80]; + int _msgcount; + bool _need_refresh = true; + bool _displayWasOn = false; // Track display state before button press + unsigned long ui_started_at; + + // Button handlers +#ifdef PIN_USER_BTN + Button* _userButton = nullptr; +#endif +#ifdef PIN_USER_BTN_ANA + Button* _userButtonAnalog = nullptr; +#endif + + void renderCurrScreen(); + void userLedHandler(); + void renderBatteryIndicator(uint16_t batteryMilliVolts); + + // Button action handlers + void handleButtonAnyPress(); + void handleButtonShortPress(); + void handleButtonDoublePress(); + void handleButtonTriplePress(); + void handleButtonQuadruplePress(); + void handleButtonLongPress(); + + +public: + + UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : AbstractUITask(board, serial), _display(NULL), _sensors(NULL) { + _next_refresh = 0; + ui_started_at = 0; + } + void begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs); + + bool hasDisplay() const { return _display != NULL; } + void clearMsgPreview(); + + // 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/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index c65bbec5..4968d357 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -86,6 +86,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 @@ -94,7 +95,8 @@ build_flags = 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 @@ -117,8 +119,8 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + + - +<../examples/companion_radio> - +<../examples/companion_radio/ui-new> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Heltec_lora32_v3.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/nano_g2_ultra/platformio.ini b/variants/nano_g2_ultra/platformio.ini index 511813a8..7fb2de28 100644 --- a/variants/nano_g2_ultra/platformio.ini +++ b/variants/nano_g2_ultra/platformio.ini @@ -34,6 +34,7 @@ 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 @@ -48,7 +49,8 @@ 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 @@ -62,6 +64,7 @@ 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 @@ -72,7 +75,9 @@ build_flags = 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 diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 0ec88070..6b77bdcf 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -87,15 +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 @@ -103,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 @@ -113,9 +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 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 e08da3ac..e48ee121 100644 --- a/variants/t114/platformio.ini +++ b/variants/t114/platformio.ini @@ -83,8 +83,8 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_t114.build_src_filter} + - +<../examples/companion_radio> - +<../examples/companion_radio/ui-new> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Heltec_t114.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -102,8 +102,8 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_t114.build_src_filter} + - +<../examples/companion_radio> - +<../examples/companion_radio/ui-new> + +<../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/techo/platformio.ini b/variants/techo/platformio.ini index 45eba9fc..2c82f38a 100644 --- a/variants/techo/platformio.ini +++ b/variants/techo/platformio.ini @@ -61,6 +61,7 @@ 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 @@ -74,7 +75,8 @@ 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/thinknode_m1/platformio.ini b/variants/thinknode_m1/platformio.ini index 972444ba..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 @@ -84,7 +85,8 @@ 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/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index cefe9649..0cb5a1ad 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -73,6 +73,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,9 +85,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 From 37d7257f04c21f51044b68b62c5ec6fb9bfae4b4 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 16 Aug 2025 21:53:36 +1000 Subject: [PATCH 56/77] * Heltec tracker, now using /ui-new * variants with no DISPLAY_CLASS .ini fixes --- variants/heltec_ct62/platformio.ini | 4 ++-- variants/heltec_mesh_solar/platformio.ini | 4 ++-- variants/heltec_tracker/platformio.ini | 5 ++++- variants/heltec_tracker/target.cpp | 1 + variants/heltec_tracker/target.h | 2 ++ variants/lilygo_tbeam_SX1262/platformio.ini | 2 +- variants/lilygo_tbeam_SX1276/platformio.ini | 2 +- variants/lilygo_tbeam_supreme_SX1262/platformio.ini | 2 +- variants/lilygo_tlora_c6/platformio.ini | 2 +- variants/lilygo_tlora_v2_1/platformio.ini | 4 ++-- variants/meshadventurer/platformio.ini | 8 ++++---- variants/picow/platformio.ini | 6 +++--- variants/sensecap_solar/platformio.ini | 4 ++-- variants/waveshare_rp2040_lora/platformio.ini | 6 +++--- variants/xiao_c3/platformio.ini | 4 ++-- variants/xiao_c6/platformio.ini | 4 ++-- 16 files changed, 33 insertions(+), 27 deletions(-) diff --git a/variants/heltec_ct62/platformio.ini b/variants/heltec_ct62/platformio.ini index ec24cdcd..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,7 +80,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} diff --git a/variants/heltec_mesh_solar/platformio.ini b/variants/heltec_mesh_solar/platformio.ini index 5cd8af86..9fd3edd5 100644 --- a/variants/heltec_mesh_solar/platformio.ini +++ b/variants/heltec_mesh_solar/platformio.ini @@ -68,7 +68,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_mesh_solar.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${Heltec_mesh_solar.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -85,7 +85,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_mesh_solar.build_src_filter} + - +<../examples/companion_radio> + +<../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_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/lilygo_tbeam_SX1262/platformio.ini b/variants/lilygo_tbeam_SX1262/platformio.ini index eac899f0..a0432b00 100644 --- a/variants/lilygo_tbeam_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_SX1262/platformio.ini @@ -49,7 +49,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${LilyGo_TBeam_SX1262.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/lilygo_tbeam_SX1276/platformio.ini b/variants/lilygo_tbeam_SX1276/platformio.ini index 8febe7b3..21c164dd 100644 --- a/variants/lilygo_tbeam_SX1276/platformio.ini +++ b/variants/lilygo_tbeam_SX1276/platformio.ini @@ -46,7 +46,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TBeam_SX1276.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${LilyGo_TBeam_SX1276.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index 5c2d6e86..1e8bacd8 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -82,7 +82,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${T_Beam_S3_Supreme_SX1262.lib_deps} densaugeo/base64 @ ~1.4.0 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 71b49aab..dfd83b5e 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -89,7 +89,7 @@ build_flags = ; 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> lib_deps = ${LilyGo_TLora_V2_1_1_6.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -108,7 +108,7 @@ build_flags = build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} + + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${LilyGo_TLora_V2_1_1_6.lib_deps} densaugeo/base64 @ ~1.4.0 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/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/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/waveshare_rp2040_lora/platformio.ini b/variants/waveshare_rp2040_lora/platformio.ini index 2730734d..933c7661 100644 --- a/variants/waveshare_rp2040_lora/platformio.ini +++ b/variants/waveshare_rp2040_lora/platformio.ini @@ -61,7 +61,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 @@ -75,7 +75,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 @@ -90,7 +90,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/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index 59f198e0..659313db 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -66,7 +66,7 @@ lib_deps = [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} @@ -89,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} diff --git a/variants/xiao_c6/platformio.ini b/variants/xiao_c6/platformio.ini index 95dede08..fdf0f337 100644 --- a/variants/xiao_c6/platformio.ini +++ b/variants/xiao_c6/platformio.ini @@ -62,7 +62,7 @@ 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 @@ -117,7 +117,7 @@ build_flags = ${Meshimi.build_flags} build_src_filter = ${Meshimi.build_src_filter} + - - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${Meshimi.lib_deps} densaugeo/base64 @ ~1.4.0 From 022bfc4f4bd9ea3807889ee4e092d4a89828ae82 Mon Sep 17 00:00:00 2001 From: kelsey hudson Date: Sat, 16 Aug 2025 15:09:42 -0700 Subject: [PATCH 57/77] Ikoka Stick: Use new UI, make repeater & room server work * Enable the new UI on the BLE and USB Companion roles. * Fix compilation issues with the repeater and room server roles. * Remove ESP32-related alternate pinout cruft from the NRF build tree. * build.sh: add build-matching-firmwares command to allow e.g. building all roles for a given variant by passing the variant name. --- build.sh | 11 +++++++++++ variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h | 4 ++-- variants/ikoka_stick_nrf/platformio.ini | 13 +++++++++---- variants/ikoka_stick_nrf/target.cpp | 1 + variants/ikoka_stick_nrf/target.h | 2 ++ 5 files changed, 25 insertions(+), 6 deletions(-) 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/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h b/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h index a5f33b01..1bd8b31d 100644 --- a/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h +++ b/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h @@ -29,10 +29,10 @@ public: #if defined(P_LORA_TX_LED) void onBeforeTransmit() override { - digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on } void onAfterTransmit() override { - digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off } #endif diff --git a/variants/ikoka_stick_nrf/platformio.ini b/variants/ikoka_stick_nrf/platformio.ini index b883e8f7..e78bc058 100644 --- a/variants/ikoka_stick_nrf/platformio.ini +++ b/variants/ikoka_stick_nrf/platformio.ini @@ -55,6 +55,7 @@ build_flags = ${nrf52840_xiao.build_flags} build_src_filter = ${nrf52840_xiao.build_src_filter} + + + + + +<../variants/ikoka_stick_nrf> debug_tool = jlink @@ -68,12 +69,14 @@ build_flags = -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> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${ikoka_stick_nrf.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -84,11 +87,13 @@ 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> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${ikoka_stick_nrf.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -106,7 +111,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${ikoka_stick_nrf.build_src_filter} + - +<../examples/simple_repeater> + +<../examples/simple_repeater/*.cpp> [env:ikoka_stick_nrf_room_server] extends = ikoka_stick_nrf @@ -119,4 +124,4 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${ikoka_stick_nrf.build_src_filter} - +<../examples/simple_room_server> + +<../examples/simple_room_server/*.cpp> diff --git a/variants/ikoka_stick_nrf/target.cpp b/variants/ikoka_stick_nrf/target.cpp index e50150eb..c2712761 100644 --- a/variants/ikoka_stick_nrf/target.cpp +++ b/variants/ikoka_stick_nrf/target.cpp @@ -6,6 +6,7 @@ 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); diff --git a/variants/ikoka_stick_nrf/target.h b/variants/ikoka_stick_nrf/target.h index 758cd019..8311503a 100644 --- a/variants/ikoka_stick_nrf/target.h +++ b/variants/ikoka_stick_nrf/target.h @@ -11,7 +11,9 @@ #ifdef DISPLAY_CLASS #include + #include extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif extern ikoka_stick_nrf_board board; From c30a103bafcd43163d99a060c1178645e83a352b Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 17 Aug 2025 15:50:25 +1000 Subject: [PATCH 58/77] * WSL3 fixes. Heltec V2 ui-new --- variants/heltec_v2/platformio.ini | 8 ++++++-- variants/heltec_v3/platformio.ini | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/variants/heltec_v2/platformio.ini b/variants/heltec_v2/platformio.ini index 04768df4..ea41f845 100644 --- a/variants/heltec_v2/platformio.ini +++ b/variants/heltec_v2/platformio.ini @@ -78,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 @@ -87,7 +88,8 @@ 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 @@ -96,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 @@ -108,7 +111,8 @@ 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_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 4968d357..b9193431 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -214,7 +214,7 @@ 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 @@ -228,7 +228,7 @@ build_flags = ; 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> + +<../examples/companion_radio/*.cpp> lib_deps = ${Heltec_lora32_v3.lib_deps} densaugeo/base64 @ ~1.4.0 From 8f8830047b2651bef38d1e7ecf0983e38d733693 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 17 Aug 2025 16:06:26 +1000 Subject: [PATCH 59/77] * T3S3 variants, ui-new --- variants/generic_espnow/platformio.ini | 2 +- variants/lilygo_t3s3/platformio.ini | 10 ++++++++-- variants/lilygo_t3s3/target.cpp | 1 + variants/lilygo_t3s3/target.h | 2 ++ variants/lilygo_t3s3_sx1276/platformio.ini | 10 ++++++++-- variants/lilygo_t3s3_sx1276/target.cpp | 1 + variants/lilygo_t3s3_sx1276/target.h | 2 ++ 7 files changed, 23 insertions(+), 5 deletions(-) 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/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(); From 2477d60fae3414db51e27c53f784a4101af529fe Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 17 Aug 2025 16:18:39 +1000 Subject: [PATCH 60/77] * TBeam variants: ui-new --- variants/lilygo_tbeam_SX1262/platformio.ini | 3 +++ variants/lilygo_tbeam_SX1262/target.cpp | 1 + variants/lilygo_tbeam_SX1262/target.h | 2 ++ variants/lilygo_tbeam_SX1276/platformio.ini | 3 +++ variants/lilygo_tbeam_SX1276/target.cpp | 1 + variants/lilygo_tbeam_SX1276/target.h | 2 ++ variants/lilygo_tbeam_supreme_SX1262/platformio.ini | 3 +++ variants/lilygo_tbeam_supreme_SX1262/target.cpp | 1 + variants/lilygo_tbeam_supreme_SX1262/target.h | 2 ++ 9 files changed, 18 insertions(+) diff --git a/variants/lilygo_tbeam_SX1262/platformio.ini b/variants/lilygo_tbeam_SX1262/platformio.ini index a0432b00..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/*.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 21c164dd..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/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_TBeam_SX1276.lib_deps} densaugeo/base64 @ ~1.4.0 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 1e8bacd8..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/*.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; From 276a0576934f2bf0359fb5ef4cb9a329404da073 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 17 Aug 2025 16:31:50 +1000 Subject: [PATCH 61/77] * lilygo_tlora_v2_1: ui-new --- examples/companion_radio/AbstractUITask.h | 2 +- variants/lilygo_tlora_v2_1/platformio.ini | 6 ++++++ variants/lilygo_tlora_v2_1/target.cpp | 1 + variants/lilygo_tlora_v2_1/target.h | 2 ++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/AbstractUITask.h b/examples/companion_radio/AbstractUITask.h index 706254af..1277bba9 100644 --- a/examples/companion_radio/AbstractUITask.h +++ b/examples/companion_radio/AbstractUITask.h @@ -42,5 +42,5 @@ public: 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(); + virtual void loop() = 0; }; diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index dfd83b5e..0ed06856 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -83,13 +83,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/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_TLora_V2_1_1_6.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -98,6 +101,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 @@ -108,7 +112,9 @@ build_flags = build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} + + + + +<../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(); From f5ad1df1034c7353caa251b24f1ae2968837e9bc Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 17 Aug 2025 16:35:39 +1000 Subject: [PATCH 62/77] * Minewsemi: ui-orig --- variants/minewsemi_me25ls01/platformio.ini | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 From fe376e8c35fd8ff19667f36190ee5b6d791426a3 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 17 Aug 2025 16:49:08 +1000 Subject: [PATCH 63/77] * RAK_4631: ui-new --- src/helpers/nrf52/SerialBLEInterface.cpp | 4 ++++ variants/rak4631/platformio.ini | 10 ++++++++-- variants/rak4631/target.cpp | 1 + variants/rak4631/target.h | 2 ++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index 8049f5c0..170a7331 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -116,10 +116,14 @@ 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(); diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 8fb4d1bb..f0dea837 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -64,6 +64,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 @@ -73,7 +74,9 @@ build_flags = ; 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 @@ -82,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 @@ -95,7 +99,9 @@ build_flags = 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 diff --git a/variants/rak4631/target.cpp b/variants/rak4631/target.cpp index 4e9d3cce..71d4e88c 100644 --- a/variants/rak4631/target.cpp +++ b/variants/rak4631/target.cpp @@ -6,6 +6,7 @@ RAK4631Board 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); 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; From b332b06304787c2048c65a2aeeb9b7d965ebdec0 Mon Sep 17 00:00:00 2001 From: Florent Date: Sat, 16 Aug 2025 18:13:50 +0200 Subject: [PATCH 64/77] techo_ui: some tweaks --- examples/companion_radio/ui-new/UITask.cpp | 10 +++++++--- src/helpers/ui/GxEPDDisplay.cpp | 4 ++-- variants/techo/platformio.ini | 1 + 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 64b788b2..9f34d913 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -14,6 +14,10 @@ #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" @@ -79,7 +83,7 @@ class HomeScreen : public UIScreen { NodePrefs* _node_prefs; uint8_t _page; bool _shutdown_init; - AdvertPath recent[4]; + AdvertPath recent[UI_RECENT_LIST_SIZE]; void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) { // Convert millivolts to percentage @@ -156,10 +160,10 @@ public: display.drawTextCentered(display.width() / 2, 43, tmp); } } else if (_page == HomePage::RECENT) { - the_mesh.getRecentlyHeard(recent, 4); + the_mesh.getRecentlyHeard(recent, UI_RECENT_LIST_SIZE); display.setColor(DisplayDriver::GREEN); int y = 20; - for (int i = 0; i < 4; i++, y += 11) { + 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); diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index c3d75bbd..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() { @@ -132,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/variants/techo/platformio.ini b/variants/techo/platformio.ini index 2c82f38a..2e6ad3b0 100644 --- a/variants/techo/platformio.ini +++ b/variants/techo/platformio.ini @@ -68,6 +68,7 @@ build_flags = -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} From 76a53bf84d034927455d369699ea189dd6d9705f Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 17 Aug 2025 17:23:28 +1000 Subject: [PATCH 65/77] * Wio-e5-mini: ui-orig * WioTrackerL1: ui-new --- variants/wio-e5-mini/platformio.ini | 2 ++ variants/wio-tracker-l1/platformio.ini | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) 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/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 0cb5a1ad..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} From 7613b9455d93176a575efe1cc4f9e62f5c42aa3b Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 17 Aug 2025 17:36:35 +1000 Subject: [PATCH 66/77] * Xiao_nRF and Xiao_rp2040 build fixes --- variants/xiao_nrf52/platformio.ini | 4 ++-- variants/xiao_rp2040/platformio.ini | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) 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 From e6152f9d6c4fdd30ba0d50ec083f5d72f3beee7e Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 17 Aug 2025 17:52:05 +1000 Subject: [PATCH 67/77] * Xiao_s3_wio: ui-new --- variants/xiao_s3_wio/platformio.ini | 7 +++++-- variants/xiao_s3_wio/target.cpp | 1 + variants/xiao_s3_wio/target.h | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) 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(); From 8f1ccb65aeadf62b6f9fddd2eb29edf64e1a73c9 Mon Sep 17 00:00:00 2001 From: taco Date: Sun, 17 Aug 2025 18:10:58 +1000 Subject: [PATCH 68/77] WioTrackerL1: add poweroff support --- variants/wio-tracker-l1/WioTrackerL1Board.h | 4 ++++ 1 file changed, 4 insertions(+) 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; }; From cb8ca91d27ece5bbfc9116ca3d6cf26e7896b8d8 Mon Sep 17 00:00:00 2001 From: taco Date: Sun, 17 Aug 2025 19:01:28 +1000 Subject: [PATCH 69/77] WioTrackerL1: add joystick left and right for new UI --- examples/companion_radio/ui-new/UITask.cpp | 14 ++++++++++++++ variants/wio-tracker-l1/target.cpp | 2 ++ variants/wio-tracker-l1/target.h | 2 ++ 3 files changed, 18 insertions(+) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 9f34d913..f3ad28c6 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -494,6 +494,20 @@ void UITask::loop() { 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); diff --git a/variants/wio-tracker-l1/target.cpp b/variants/wio-tracker-l1/target.cpp index dc484546..349d73b4 100644 --- a/variants/wio-tracker-l1/target.cpp +++ b/variants/wio-tracker-l1/target.cpp @@ -17,6 +17,8 @@ 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 d5fe4d70..6f5da7c6 100644 --- a/variants/wio-tracker-l1/target.h +++ b/variants/wio-tracker-l1/target.h @@ -40,6 +40,8 @@ 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(); From a4916f81eb9c241964d3bb31a601786f605a0b59 Mon Sep 17 00:00:00 2001 From: recrof Date: Sun, 17 Aug 2025 11:57:38 +0200 Subject: [PATCH 70/77] station g2: switch to new_ui --- variants/station_g2/platformio.ini | 12 ++++++++---- variants/station_g2/target.cpp | 5 +---- variants/station_g2/target.h | 2 ++ 3 files changed, 11 insertions(+), 8 deletions(-) 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(); From b8f80afee95137b76251e2f4e225d3cf60eb8b7f Mon Sep 17 00:00:00 2001 From: Florent Date: Sun, 17 Aug 2025 16:28:02 +0200 Subject: [PATCH 71/77] t1000: wait for button release before powering off --- variants/t1000-e/T1000eBoard.h | 9 +++++++++ 1 file changed, 9 insertions(+) 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(); } From d145d5936dcfd19fda2b72de51daaa69cf6463bd Mon Sep 17 00:00:00 2001 From: recrof Date: Mon, 18 Aug 2025 12:53:44 +0200 Subject: [PATCH 72/77] fix RAK build errors --- variants/rak4631/platformio.ini | 6 ++---- variants/rak4631/target.cpp | 4 ++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index f0dea837..c9091878 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -22,6 +22,8 @@ build_flags = ${nrf52_base.build_flags} build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631> + + + + + lib_deps = ${nrf52_base.lib_deps} ${sensor_base.lib_deps} @@ -73,8 +75,6 @@ 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/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = @@ -97,9 +97,7 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${rak4631.build_src_filter} - + + - + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = diff --git a/variants/rak4631/target.cpp b/variants/rak4631/target.cpp index 71d4e88c..618c9fc5 100644 --- a/variants/rak4631/target.cpp +++ b/variants/rak4631/target.cpp @@ -4,6 +4,10 @@ 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); From ff03b041d0f0dafe3e976152812151d43e11d73f Mon Sep 17 00:00:00 2001 From: Florent Date: Tue, 19 Aug 2025 09:03:34 +0200 Subject: [PATCH 73/77] techo_ui: implement poweroff + led fixes --- src/helpers/nrf52/TechoBoard.h | 19 +++++++++++++++++++ variants/techo/platformio.ini | 1 + variants/techo/variant.cpp | 2 ++ variants/techo/variant.h | 12 ++++-------- 4 files changed, 26 insertions(+), 8 deletions(-) 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/variants/techo/platformio.ini b/variants/techo/platformio.ini index 2e6ad3b0..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} + + 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 From a9d4cf1d21784fb1d63b134be739e287ebc432da Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 19 Aug 2025 23:14:11 +1000 Subject: [PATCH 74/77] * various repeaters: fix for missing MomentaryButton module --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index 02190102..2e350f81 100644 --- a/platformio.ini +++ b/platformio.ini @@ -47,6 +47,7 @@ build_src_filter = +<*.cpp> + + + + ; ----------------- ESP32 --------------------- From 5dc930410cb6517f5e8b8d55a99fc1e3845977ed Mon Sep 17 00:00:00 2001 From: recrof Date: Wed, 20 Aug 2025 13:44:41 +0200 Subject: [PATCH 75/77] set default flood advert interval to 12 hours --- examples/simple_repeater/main.cpp | 8 ++++---- examples/simple_room_server/main.cpp | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 7ad0e64a..622a0554 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -159,7 +159,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]; @@ -589,7 +589,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 } @@ -611,8 +611,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 { diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 34b94ad2..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; @@ -746,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 @@ -778,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 { From c63653659922a0f27b02e5cbd08218546ffb8e84 Mon Sep 17 00:00:00 2001 From: Alex Wolden Date: Wed, 20 Aug 2025 22:01:38 -0700 Subject: [PATCH 76/77] Add INA226 to rak --- platformio.ini | 2 ++ .../sensors/EnvironmentSensorManager.cpp | 26 +++++++++++++++++++ .../sensors/EnvironmentSensorManager.h | 1 + 3 files changed, 29 insertions(+) diff --git a/platformio.ini b/platformio.ini index 2e350f81..e935d77e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -115,12 +115,14 @@ build_flags = -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 diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 0f3289b8..1a14349d 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -65,6 +65,12 @@ static Adafruit_INA219 INA219(TELEM_INA219_ADDRESS); static Adafruit_INA260 INA260; #endif +#if ENV_INCLUDE_INA226 +#define TELEM_INA226_ADDRESS 0x44 +#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 +208,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(1.0, 0.002); // 1A max, 2 milliohm shunt + 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 +340,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; From 0c37eafd01e7041ae0cc8b32d6d5fcbdc39243d4 Mon Sep 17 00:00:00 2001 From: Alex Wolden Date: Thu, 21 Aug 2025 13:21:37 -0700 Subject: [PATCH 77/77] Fixed shunt value for ina226 --- src/helpers/sensors/EnvironmentSensorManager.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 1a14349d..f444b67b 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -67,6 +67,8 @@ static Adafruit_INA260 INA260; #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 @@ -211,7 +213,7 @@ bool EnvironmentSensorManager::begin() { #if ENV_INCLUDE_INA226 if (INA226.begin()) { MESH_DEBUG_PRINTLN("Found INA226 at address: %02X", TELEM_INA226_ADDRESS); - INA226.setMaxCurrentShunt(1.0, 0.002); // 1A max, 2 milliohm shunt + INA226.setMaxCurrentShunt(TELEM_INA226_MAX_AMP, TELEM_INA226_SHUNT_VALUE); INA226_initialized = true; } else { INA226_initialized = false;