Compare commits

..

80 Commits

Author SHA1 Message Date
Thomas Göttgens
d657177f0e Merge branch 'master' into store-and-forward 2025-07-13 18:18:19 +02:00
TSAO
2ecbf704d0 Improve OLED UI Responsiveness and Force Redraws for Canned message module (#7324)
* No delay between UI frame rendering for OLED

* force redraw the display

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jason P <applewiz@mac.com>
2025-07-13 08:28:05 -05:00
Styne13
5e28ee6d1e NodeDB.cpp: Fix iOS bluetooth crash by ensuring UINT32_MAX is not used (#7312)
Signed-off-by: Marcel <6253936+Styne13@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-13 06:26:35 -05:00
Neil Hanlon
622023de8b fix(device-update.sh): safely filter args without breaking parsing (#7305)
The previous method of removing `--change-mode` from the argument list used a
string (`NEW_ARGS`) and `eval set -- $NEW_ARGS` to reconstruct the positional
parameters. This was both unsafe and incorrect.

Because `NEW_ARGS` was built using quoted literal `$arg` strings instead of the
actual values, it resulted in all filtered arguments being set to the same last
value of `$arg`. This caused `getopts` to receive incorrect input and silently
fail to parse options like `-p` and `-f`, leading to broken behavior and unset
variables (e.g., `ESPTOOL_CMD` never got a port).

This patch rewrites the logic to use an array (`NEW_ARGS+=("$arg")`), and
resets positional parameters via `set -- "${NEW_ARGS[@]}"`. This preserves
argument integrity and avoids the unsafe use of `eval`.

Example of the broken behavior before this fix:

  ./device-update.sh -p /dev/ttyACM0 -f firmware.bin

Resulted in:

  set -- firmware.bin firmware.bin firmware.bin firmware.bin

Now:

  set -- -p /dev/ttyACM0 -f firmware.bin

as expected.

Signed-off-by: Neil Hanlon <neil@shrug.pw>
2025-07-13 06:19:58 -05:00
Andrew Yong
b49e59b904 xiao_ble README.md updates (#7283)
* docs(xiao_ble): Simplify building and flashing instructions

- **Update Bootloader** - deleted this section, as Meshtastic now builds-in a compatible SoftDevice
- **PlatformIO Environment Preparation** - deleted this section, as Meshtastic now builds-in a compatible SoftDevice
- **Build Meshtastic** - simplified it greatly by referring to Meshtastic documentation
- **Flash the firmware to the Xiao BLE** - simplified it greatly as Meshtastic now builds firmware.uf2; added some observations for a succesful flash

Light cleanup of Markdown and renumbering of sections.

Signed-off-by: Andrew Yong <me@ndoo.sg>

* docs(xiao_ble): Replace some HTML with Markdown, cleanup Markdown

Signed-off-by: Andrew Yong <me@ndoo.sg>

* docs(xiao_ble): Update SX126X_TXEN definition location

Signed-off-by: Andrew Yong <me@ndoo.sg>

* docs(xiao_ble): Fresher information about E22 modules

Signed-off-by: Andrew Yong <me@ndoo.sg>

* docs(xiao_ble): Instructions for E22...M33S modules

Also re-order the Build section to come after the Wiring section since the build instructions require special attention if the wiring/modules differ from the variant's expected pins/module.

Signed-off-by: Andrew Yong <me@ndoo.sg>

* docs(xiao_ble): Rename all XIAO BLE to XIAO nRF52840

Signed-off-by: Andrew Yong <me@ndoo.sg>

* docs(xiao_ble): Remove note about Linux since shell script is gone

Signed-off-by: Andrew Yong <me@ndoo.sg>

* docs(xiao_ble): trunk fmt and fix links

Signed-off-by: Andrew Yong <me@ndoo.sg>

---------

Signed-off-by: Andrew Yong <me@ndoo.sg>
2025-07-13 06:17:50 -05:00
Andrew Yong
0133c5dc9e feat: New variant esp32c3_super_mini (#7133)
https://www.espboards.dev/esp32/esp32-c3-super-mini/

DIY build by @AntonKartajaya on Meshtastic Discord and a PCB version WIP by https://github.com/NomDeTom.

- I2C
  - I2C_SDA: 1
  - I2C_SCL: 0
  - OLED: SSD1306
- GPS
  - GPS_RX_PIN: 20
  - GPS_TX_PIN: 21
- Button
  - BUTTON_PIN: 9
- SPI
  - SCK: 10
  - MISO: 6
  - MOSI: 7
  - CS: 8
- LoRa: SX1262
  - LORA_RESET: 5
  - LORA_DIO1: 3
  - LORA_RXEN: 2
  - LORA_BUSY: 4

Signed-off-by: Andrew Yong <me@ndoo.sg>
2025-07-13 06:12:24 -05:00
Andrew Yong
fd414ed149 feat: DIY Seeed XIAO nRF52840 + EBYTE E22 variants, pin-compatible with Wio-SX1262 kit (#7105)
These DIY builds are functionally similar to the legacy xiao_ble variant, but use a pinout harmonized with the officially-supported XIAO nRF52840 & Wio-SX1262 Kit for Meshtastic (SKU 102010710).

An additional E22-900M33S variant is provided to ensure SX1262 transmit power is set below the maximum PA input for that module, to avoid damaging it.

- seeed_xiao_nrf52840_e22_900m30s:
  - XIAO nRF52840 + EBYTE E22-900M30S
  - EBYTE E22 pinout matching Wio-SX1262 (SKU 113010003)
  - I2C
    - SDA: D6
    - SCL: D7
  - User button: D0
  - Gain is programmed in firmware - user Tx power setting is the desired final output power
- seeed_xiao_nrf52840_e22_900m33s:
  - XIAO nRF52840 + EBYTE E22-900M33S
  - EBYTE E22 pinout matching Wio-SX1262 (SKU 113010003)
  - I2C
    - SDA: D6
    - SCL: D7
  - User button: D0
  - Gain is programmed in firmware - user Tx power setting is the desired final output power

Signed-off-by: Andrew Yong <me@ndoo.sg>
2025-07-13 15:58:01 +08:00
Austin
77768e9023 Remove Ubuntu oracular (#7322) 2025-07-13 12:39:20 +08:00
Austin
86be2ac12f userPrefs: Set default ringtone nag time (#7314) 2025-07-12 16:26:25 -05:00
Jonathan Bennett
4342d51f5a Bump Framework-native and set version string. (#7317) 2025-07-12 14:44:58 -05:00
Austin
41f52a6566 Build: Update platformio with pkg install (#7315) 2025-07-12 14:35:57 -05:00
Jonathan Bennett
cb47325f08 Seesaw Rotary (#7310)
* Initial add of Adafruit seesaw encoder

* Fully wire up seesaw

* Trunk

* Add #include configuration.h back to unbreak logging

* Tryfix the dumb compilation error

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-12 12:36:44 -05:00
Austin
deed6cd96a STM32: Properly ignore OneButton (#7311) 2025-07-12 06:27:45 -05:00
renovate[bot]
05c32c99e4 Update meshtastic/device-ui digest to 86a09a7 (#7308)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-11 18:46:29 -05:00
Jonathan Bennett
1ca0584ba0 Add first config override for Native (#7306) 2025-07-11 16:09:59 -05:00
Ben Meadors
5ae8021aa6 I'm dumb 2025-07-11 08:28:21 -05:00
Ben Meadors
9798a91e7b Delete ringtone.proto file for factory reset (#7303) 2025-07-11 08:22:50 -05:00
Austin
e9a551ae90 Load ringtone from userPrefs (#7298)
* Load ringtone from userPrefs

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-11 08:09:46 -05:00
Mictronics
d42bde135f Support native configuration Waveshare Pico LoRa module on Orange Pi Zero3 (#7295)
* Fix LED pinout for T-Echo board marked v1.0, date 2021-6-28

* Merge PR #420

* Fixed double and missing Default class.

* Use correct format specifier and fixed typo.

* Removed duplicate code.

* Fix error: #if with no expression

* Fix warning: extra tokens at end of #endif directive.

* Fix antenna switching logic. Complementary-pin control logic is required on the rp2040-lora board.

* Fix deprecated macros.

* Set RP2040 in dormant mode when deep sleep is triggered.

* Fix array out of bounds read.

* Admin key count needs to be set otherwise the key will be zero loaded after reset.

* Don't reset the admin key size when loading defaults. Preserve an existing key in config if possible.

* Remove log spam when reading INA voltage sensor.

* Remove static declaration for admin keys from userPrefs.h. Load hard coded admin keys in case config file has empty slots.

* Removed newlines from log.

* Fix issue #5665.

* Fix build for Pico2 RP2350 platform.

* Enable Wifi client on Pico2W.

* Use correct processor on Pico2.

* Fix deprecated warning.

* Update platform and framework for RP2350.

* Added Pico2W variant including Wifi support.

* Fix typo in used variant.

* Remove obsolete define.

* Fix for native Linux build.

* Simplify RP2350 platform tag reference.

Co-authored-by: Austin <vidplace7@gmail.com>

* Cast user prefs strings.

* Update to last successfully building platform package.

* Define I2C GPIOs to ensure usage of both ports. Possibly fixes #5361

* RAK11310 support for RAK12002 RTC added.

* Update platform and framework packages to 4.4.3.

* Use RP2040 base platform and framework package. Use RAK11300 board definition in arduino-pico framework.

* Use RAK11300 board definition in arduino-pico framework.

* Fix build when MESHTASTIC_EXCLUDE_GPS is defined.

* Added configuration for Waveshare Pico LoRa module in combination with Orange Pi Zero3.

* Equal to upstream master.

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: GUVWAF <78759985+GUVWAF@users.noreply.github.com>
Co-authored-by: Austin <vidplace7@gmail.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
2025-07-11 06:54:37 -05:00
github-actions[bot]
72f3d19d5a Upgrade trunk (#7278)
Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com>
2025-07-11 06:51:33 -05:00
renovate[bot]
f7ecf141b5 Update meshtastic/device-ui digest to 404c6e0 (#7302)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-11 06:45:18 -05:00
todd-herbert
1063ef9034 Shorter audio feedback for InkHUD buttons (#7301) 2025-07-11 17:30:48 +12:00
Jonathan Bennett
13ac182142 Pick up nodedb.h in Screen.cpp regardless of HAS_SCREEN state 2025-07-10 23:19:58 -05:00
Jonathan Bennett
4bab148e3b Make the shouldWake function always available, and remove the bool (#7300) 2025-07-10 22:51:43 -05:00
Jason P
be75f11156 Update Screen Wake Default Behavior (#7282)
* feat(display): enable screen wake on received messages

* feat(menu): add Screen Wakeup option in system menu

* feat(ui): update wake on message configuration and refactor save logic

* feat(TextMessageModule): conditionally trigger screen wake on received message

* Refactoring system menu options for notification and screen.

* Fix MUI options in the system menu.

* Build out Reboot/Shutdown Menu and consolidate options within it

* Trunk fixes

* Protobuf ref

* Revert generated files

* Update plumbing for screen_wakeup_menu

* Begin work on crafting a method to stop screen wake for received messages

* SharedUIDisplay.cpp doesn't need ExternalNotificationModule.h

* Stop screen wake if External Notification is enabled

* Removing extra log lines

* Add role and battery state checks for not waking screen. Menu updates to resolve some Back options not being linked

* Resolve some additional merge conflict related issues

* Shouldn't throttle the power menu

* Finalize renames of some menus

* Flip Flop MUI Menu to avoid accidental clicks

* NULL check for powerStatus

* Remove "Wakeup" eNum

* Update src/graphics/Screen.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* CoPilot was close this should fix the builds

---------

Co-authored-by: whywilson <m.tools@qq.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-10 19:49:15 -05:00
renovate[bot]
093868f3ed Update dorny/test-reporter action to v2.1.1 (#7284)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-10 17:12:39 -05:00
renovate[bot]
fe534eae37 Update Adafruit BusIO to v1.17.2 (#7277)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-10 17:12:25 -05:00
Kongduino
1aad442ccc Update platformio.ini (#7289)
The link to the product should point to the vendor's website, not a random distributor.

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2025-07-10 17:11:19 -05:00
renovate[bot]
57c1c9286b Update RadioLib to v7.2.1 (#7287)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-10 17:11:00 -05:00
Jonathan Bennett
6030bf50e0 Unbreak the macro 2025-07-10 11:40:02 -05:00
github-actions[bot]
6d8c815558 automated bumps (#7293)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-07-10 11:31:40 -05:00
Jonathan Bennett
5f5698ccc0 Explicitly include meshUtils.h 2025-07-10 10:29:33 -05:00
Jonathan Bennett
74c735d5fb Gate screen code behind IF_SCREEN() 2025-07-10 10:20:44 -05:00
Jonathan Bennett
107dec22bd Remove bogus validation check 2025-07-10 10:12:02 -05:00
Jonathan Bennett
0795b21c2b Key verification flow on BaseUI (#7240) 2025-07-10 09:45:36 -05:00
renovate[bot]
a7e516d6f6 Update Adafruit INA260 to v1.5.3 (#7270)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-09 15:11:05 +08:00
Thomas Göttgens
d3fc155186 Merge branch 'master' into store-and-forward 2025-06-25 20:16:49 +02:00
Thomas Göttgens
3fb4ab35f1 Merge branch 'master' into store-and-forward 2025-05-24 22:06:23 +02:00
Woutvstk
6d8bedb027 Clean up unused macros and outdated comments (#6520)
* Updating comments and removing residue

* Some more unused macros
2025-04-08 08:27:47 -05:00
Ben Meadors
aca9ceebfa Merge branch 'master' into store-and-forward 2025-04-07 08:42:41 -05:00
Ben Meadors
9dee6030e4 Merge branch 'master' into store-and-forward 2025-04-07 08:33:52 -05:00
Thomas Göttgens
0d19f766e8 fix build errors 2025-04-07 13:00:28 +02:00
Thomas Göttgens
91986db1b0 Merge branch 'master' into store-and-forward 2025-04-07 09:27:53 +02:00
Thomas Göttgens
1e54d3bfc7 Merge branch 'master' into store-and-forward 2025-03-31 11:25:28 +02:00
Ben Meadors
b2401100de Merge branch 'master' into store-and-forward 2025-03-30 20:36:00 -05:00
Woutvstk
7e7792aa51 Changed SD library for nrf52 and got S&F working (#6382)
- Changed library from deprecated arduino SD library to wrapper of SdFat library
- Fixed some bugs in the S&F code for SD card storage
Now the S&F functionality works on nrf52 (tested on rak4631 with RAK15002)
2025-03-29 13:15:21 +01:00
Ben Meadors
d237d4f311 Merge branch 'master' into store-and-forward 2025-03-28 06:17:28 -05:00
Thomas Göttgens
9f2a3fd23b Merge pull request #6132 from Woutvstk/retry-fix-wrongly-removed-spi1-rak4631 2025-03-02 13:27:10 +01:00
Thomas Göttgens
61901c37bb Merge branch 'store-and-forward' into retry-fix-wrongly-removed-spi1-rak4631 2025-03-02 13:26:14 +01:00
Thomas Göttgens
64f8203abc Merge branch 'master' into store-and-forward 2025-03-02 12:05:49 +01:00
Woutvstk
f0ca0b947c trunk fmt 2025-02-25 16:04:59 +01:00
Woutvstk
db376532ad Add SPIM3/SPIM2 for SPI/SPI1 selection
The SPIM3 is faster (max 32Mhz) than SPIM2 (max 8Mhz)
Default is SPI_32MHZ_INTERFACE  0 in SPI library
2025-02-25 15:22:03 +01:00
Woutvstk
372b62aa35 remove second initSPI() (#6140) 2025-02-24 19:45:44 +08:00
Woutvstk
95fe4aed04 trunk fmt 2025-02-23 18:17:24 +01:00
Woutvstk
b19d358dcc redid the reorganisation of the SPI definitions 2025-02-23 17:23:35 +01:00
Woutvstk
4ec0134606 Revert "Rak4631 remove spi1 (#6042)"
This reverts commit 9b46cb4ef0.
2025-02-23 16:56:39 +01:00
Woutvstk
5aa4946e6f Revert "Reorganize spi definitions for use with sd cards ESP32/NRF52 (#6080)"
This reverts commit 443922b947.
2025-02-23 16:56:22 +01:00
Thomas Göttgens
e67b84ee06 Merge branch 'master' into store-and-forward 2025-02-19 13:22:11 +01:00
Woutvstk
443922b947 Reorganize spi definitions for use with sd cards ESP32/NRF52 (#6080)
Changed variant files of hardware that uses HAS_SDCARD.
Reorganised SPIClass definitions in FSCommon
2025-02-18 09:08:03 +01:00
Thomas Göttgens
35fc93752e Merge branch 'master' into store-and-forward 2025-02-17 15:57:31 +01:00
Woutvstk
a6aa84431c added quotes against linking issues in platformio.ini issue #5898 (#5966) 2025-01-31 07:08:57 -06:00
Thomas Göttgens
9ab50d5feb Merge branch 'master' into store-and-forward 2025-01-18 14:11:59 +01:00
Thomas Göttgens
45ff66e713 Merge branch 'master' into store-and-forward 2025-01-05 23:26:32 +01:00
Thomas Göttgens
f7feea63f7 Attempt to merge 2025-01-05 21:34:38 +01:00
Thomas Göttgens
7c08ff35f3 Merge branch 'master' into store-and-forward 2025-01-05 21:20:44 +01:00
Woutvstk
96277ed804 First attempt to adding hardware support for NRF52 SPI SD Card (#5561)
* First attempt to adding hardware support for NRF52 SPI SD Card using arduino SD library
My first time contributing to an open source project so not very confident in what i'm doing.

Changes to
FSCommon:
initializing SD library for NRF52. Progress: No compile error, but SD card does not get initialized properly yet
added ifdef ARCH_ESP32 conditions around esp32 SD library functions

memget: added ifdef conditional statements

StoreForwardModule.cpp: added ifdef conditional statements

Rak4631 platfromIO.ini and variant.h:
added arduino-libraries/SD@^1.3.0 library to libdeps
defined HAS_SDCARD and SPI pins

Arduino SD library. Made changes to library because using namespace SDLIB in header file caused ambiguity problems
Not sure this is the right way of adding a library, also, how do i implement changes to the library permanently to the project?

Am I going somewhat in the right direction with these changes? Tell me your thoughts, thanks

* replaced arduino SD library to custom fork.
A "using namespace" statement in the header file was to messy to work around.
NRF52 SD card initialisation added

* updated library reference
added card size and type function to SD library
added populateSDCard for NRF52

* Changed NRF52 SD object from SDFilesystem to SD

Changed NRF52 SD object from SDFilesystem to SD for more compatibility with esp32 SD library. Some functions are still different but most used open, read, write and close are the same.

* Removed duplicate ESP32/NRF52 SD card access code

Mainly made changes to the custom arduino SD library to make it compatible with the esp32 SD library already used in the store and forward code.
With these compatible function names and return, I removed some duplicate code.

* trunk fmt and pin SD library to commit hash

* print this out on ESP32 anyway

---------

Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2025-01-05 21:19:16 +01:00
Thomas Göttgens
37a03c2ef9 Merge branch 'master' into store-and-forward 2024-12-29 22:41:38 +01:00
Thomas Göttgens
ef602a7d81 Merge branch 'master' into store-and-forward 2024-12-12 16:40:27 +01:00
Thomas Göttgens
1f1d785ca5 Merge branch 'master' into store-and-forward 2024-12-09 13:53:33 +01:00
Thomas Göttgens
37da78aec2 Merge branch 'master' into store-and-forward 2024-11-24 15:52:25 +01:00
Thomas Göttgens
beb69e93d0 Merge branch 'master' into store-and-forward 2024-11-03 15:15:44 +01:00
Thomas Göttgens
5d25c304ad Merge branch 'master' into store-and-forward 2024-11-02 16:38:36 +01:00
Thomas Göttgens
fbe6a3c4d2 Merge branch 'master' into store-and-forward 2024-10-28 12:11:46 +01:00
Thomas Göttgens
91933c66f3 missed one. 2024-10-27 17:31:10 +01:00
Thomas Göttgens
a13a299b92 next one - fix compilation for Portduino 2024-10-26 14:39:12 +02:00
Thomas Göttgens
94155f170e fix compilation, double macros resolved 2024-10-26 13:58:29 +02:00
Thomas Göttgens
840996c755 fix build for portduino 2024-10-26 13:41:31 +02:00
Thomas Göttgens
1027b45ee9 Merge branch 'store-and-forward' of https://github.com/meshtastic/firmware into store-and-forward 2024-10-26 13:39:44 +02:00
Thomas Göttgens
7532052edc Use SD card as store&forward memory 2024-10-26 12:21:18 +02:00
Thomas Göttgens
b1125513f3 Use SD card as store&forward memory 2024-10-16 21:11:24 +02:00
82 changed files with 1696 additions and 676 deletions

View File

@@ -30,7 +30,7 @@ jobs:
strategy:
fail-fast: false
matrix:
series: [plucky, oracular, noble, jammy]
series: [plucky, noble, jammy]
uses: ./.github/workflows/package_ppa.yml
with:
ppa_repo: ppa:meshtastic/daily

View File

@@ -20,7 +20,7 @@ jobs:
strategy:
fail-fast: false
matrix:
series: [plucky, oracular, noble, jammy]
series: [plucky, noble, jammy]
uses: ./.github/workflows/package_ppa.yml
with:
ppa_repo: |-

View File

@@ -143,7 +143,7 @@ jobs:
merge-multiple: true
- name: Test Report
uses: dorny/test-reporter@v2.1.0
uses: dorny/test-reporter@v2.1.1
with:
name: PlatformIO Tests
path: testreport.xml

View File

@@ -8,8 +8,8 @@ plugins:
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.447
- renovate@41.23.4
- checkov@3.2.450
- renovate@41.29.1
- prettier@3.6.2
- trufflehog@3.89.2
- yamllint@1.37.1

View File

@@ -2,7 +2,7 @@
[portduino_base]
platform =
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
https://github.com/meshtastic/platform-native/archive/681ee029207e9fd040afa223df6e54074cbbe084.zip
https://github.com/meshtastic/platform-native/archive/6cb7a455b440dd0738e8ed74a18136ed5cf7ea63.zip
framework = arduino
build_src_filter =
@@ -30,6 +30,8 @@ lib_deps =
lovyan03/LovyanGFX@^1.2.0
# renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main
https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip
# renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library
adafruit/Adafruit seesaw Library@1.7.9
build_flags =
${arduino_base.build_flags}
@@ -48,4 +50,7 @@ build_flags =
-std=gnu17
-std=c++17
lib_ignore = Adafruit NeoPixel
lib_ignore =
Adafruit NeoPixel
Adafruit ST7735 and ST7789 Library
SD

View File

@@ -47,4 +47,4 @@ lib_deps =
https://github.com/caveman99/Crypto/archive/eae9c768054118a9399690f8af202853d1ae8516.zip
lib_ignore =
mathertel/OneButton@2.6.1
OneButton

View File

@@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg update -e $1
platformio pkg install -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.*

View File

@@ -25,7 +25,7 @@ mkdir -p $OUTDIR/
rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
pio pkg update --environment "$PIO_ENV" || platformioFailed
pio pkg install --environment "$PIO_ENV" || platformioFailed
pio run --environment "$PIO_ENV" || platformioFailed
cp ".pio/build/$PIO_ENV/program" "$OUTDIR/meshtasticd_linux_$(uname -m)"
cp bin/native-install.* $OUTDIR

View File

@@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg update -e $1
platformio pkg install -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.*

View File

@@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg update -e $1
platformio pkg install -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.*

View File

@@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg update -e $1
platformio pkg install -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.*

View File

@@ -199,6 +199,10 @@ HostMetrics:
# UserStringCommand: cat /sys/firmware/devicetree/base/serial-number # Command to execute, to send the results as the userString
Config:
# DisplayMode: TWOCOLOR # uncomment to force BaseUI
# DisplayMode: COLOR # uncomment to force MUI
General:
MaxNodes: 200
MaxMessageQueue: 100

View File

@@ -0,0 +1,52 @@
# https://www.waveshare.com/pico-lora-sx1262-868m.htm
# http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/details/Orange-Pi-Zero-3.html
#
# See Orange Pi Zero3 manual, chapter 3.16, page 124 for 26-pin header pinout
#
# Pin Connection
# Waveshare Orange Pi Zero3
# 36 3.3V 17
# 15 MOSI 19
# 16 MISO 21
# 14 CLK 23
# 38 GND 25
# 4 BUSY 18
# 20 RESET 22
# 5 CS 24
# 26 DIO1/IRQ 26
Lora:
Module: sx1262 # Waveshare Raspberry Pico Lora module
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
# Specify either the spidev1_1 or the CS below, not both!
# On DietPi Linux, when using the user overlay dietpi-spi1_1.dtbo, CS will be configured with spidev1.1
spidev: spidev1.1 # See Orange Pi Zero3 manual, chapter 3.18.3, page 130
# CS: # CS PIN_24 -> chip 1, line 233
# pin: 24
# gpiochip: 1
# line: 233
SCK: # SCK PIN_23 -> chip 1, line 230
pin: 23
gpiochip: 1
line: 230
Busy: # BUSY PIN_18 -> chip 1, line 78
pin: 18
gpiochip: 1
line: 78
MOSI: # MOSI PIN_19 -> chip 1, line 231
pin: 19
gpiochip: 1
line: 231
MISO: # MISO PIN_21 -> chip 1, line 232
pin: 21
gpiochip: 1
line: 232
Reset: # NRST PIN_22 -> chip 1, line 71
pin: 22
gpiochip: 1
line: 71
IRQ: # DIO1 PIN_26 -> chip 1, line 74
pin: 26
gpiochip: 1
line: 74

View File

@@ -31,17 +31,16 @@ EOF
}
# Check for --change-mode and remove it from arguments
NEW_ARGS=""
NEW_ARGS=()
for arg in "$@"; do
if [ "$arg" = "--change-mode" ]; then
CHANGE_MODE=true
else
NEW_ARGS="$NEW_ARGS \"\$arg\""
NEW_ARGS+=("$arg")
fi
done
# Reset positional parameters to filtered list
eval set -- $NEW_ARGS
set -- "${NEW_ARGS[@]}"
while getopts ":hp:P:f:" opt; do
case "${opt}" in

View File

@@ -87,6 +87,9 @@
</screenshots>
<releases>
<release version="2.7.3" date="2025-07-10">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.3</url>
</release>
<release version="2.7.2" date="2025-07-04">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.2</url>
</release>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

7
debian/changelog vendored
View File

@@ -1,4 +1,4 @@
meshtasticd (2.7.2.0) UNRELEASED; urgency=medium
meshtasticd (2.7.3.0) UNRELEASED; urgency=medium
[ Austin Lane ]
* Initial packaging
@@ -28,4 +28,7 @@ meshtasticd (2.7.2.0) UNRELEASED; urgency=medium
[ ]
* GitHub Actions Automatic version bump
-- <github-actions[bot]@users.noreply.github.com> Fri, 04 Jul 2025 11:58:01 +0000
[ Ubuntu ]
* GitHub Actions Automatic version bump
-- Ubuntu <github-actions[bot]@users.noreply.github.com> Thu, 10 Jul 2025 16:29:27 +0000

View File

@@ -104,18 +104,18 @@ lib_deps =
[radiolib_base]
lib_deps =
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
jgromes/RadioLib@7.2.0
jgromes/RadioLib@7.2.1
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/8c7092c73425adfda1aac8c6960df06cd85f6d92.zip
https://github.com/meshtastic/device-ui/archive/86a09a7360f92d10053fbbf8d74f67f85b0ceb09.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]
lib_deps =
# renovate: datasource=custom.pio depName=Adafruit BusIO packageName=adafruit/library/Adafruit BusIO
adafruit/Adafruit BusIO@1.17.1
adafruit/Adafruit BusIO@1.17.2
# renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor
adafruit/Adafruit Unified Sensor@1.1.15
# renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library
@@ -129,7 +129,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=Adafruit MCP9808 packageName=adafruit/library/Adafruit MCP9808 Library
adafruit/Adafruit MCP9808 Library@2.0.2
# renovate: datasource=custom.pio depName=Adafruit INA260 packageName=adafruit/library/Adafruit INA260 Library
adafruit/Adafruit INA260 Library@1.5.2
adafruit/Adafruit INA260 Library@1.5.3
# renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219
adafruit/Adafruit INA219@1.2.3
# renovate: datasource=custom.pio depName=Adafruit PM25 AQI Sensor packageName=adafruit/library/Adafruit PM25 AQI Sensor

View File

@@ -23,11 +23,16 @@ SPIClass SPI_HSPI(HSPI);
#else
#define SDHandler SPI
#endif
#elif defined(ARCH_NRF52)
#if defined(SDCARD_USE_SPI1)
#define SDHandler SPI1
#elif defined(SDCARD_USE_SPI)
#define SDHandler SPI
#endif // NRF52 SPI or SPI1
#endif // ESP32/NRF52
#ifndef SD_SPI_FREQUENCY
#define SD_SPI_FREQUENCY 4000000U
#endif
#endif // HAS_SDCARD
/**
@@ -309,7 +314,13 @@ void setupSDCard()
{
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
concurrency::LockGuard g(spiLock);
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52))
#if (defined(ARCH_ESP32))
SDHandler.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
#elif (defined(ARCH_NRF52))
SDHandler.begin();
#endif
if (!SD.begin(SDCARD_CS, SDHandler, SD_SPI_FREQUENCY)) {
LOG_DEBUG("No SD_MMC card detected");
return;
@@ -319,20 +330,23 @@ void setupSDCard()
LOG_DEBUG("No SD_MMC card attached");
return;
}
LOG_DEBUG("SD_MMC Card Type: ");
if (cardType == CARD_MMC) {
LOG_DEBUG("MMC");
LOG_DEBUG("SD_MMC Card Type: MMC");
} else if (cardType == CARD_SD) {
LOG_DEBUG("SDSC");
LOG_DEBUG("SD_MMC Card Type: SDSC");
} else if (cardType == CARD_SDHC) {
LOG_DEBUG("SDHC");
LOG_DEBUG("SD_MMC Card Type: SDHC");
} else {
LOG_DEBUG("UNKNOWN");
LOG_DEBUG("SD_MMC Card Type: UNKNOWN");
}
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
LOG_DEBUG("SD Card Size: %lu MB", (uint32_t)cardSize);
LOG_DEBUG("Total space: %lu MB", (uint32_t)(SD.totalBytes() / (1024 * 1024)));
LOG_DEBUG("Used space: %lu MB", (uint32_t)(SD.usedBytes() / (1024 * 1024)));
LOG_INFO("Now scanning free clusters on SD card, this may take some time...");
delay(100); // let serial print the above statement properly
LOG_DEBUG("Used space: %lu MB", (uint32_t)(SD.usedBytes() / (1024 * 1024))); // This might take some time during boot
#endif
#endif
}

View File

@@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Screen.h"
#include "NodeDB.h"
#include "PowerMon.h"
#include "Throttle.h"
#include "configuration.h"
@@ -44,7 +45,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#endif
#include "FSCommon.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "RadioLibInterface.h"
#include "error.h"
#include "gps/GeoCoord.h"
@@ -62,6 +62,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "modules/ExternalNotificationModule.h"
#include "modules/TextMessageModule.h"
#include "modules/WaypointModule.h"
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
#include "modules/StoreForwardModule.h"
#endif
#include "sleep.h"
#include "target_specific.h"
@@ -76,10 +79,13 @@ extern uint16_t TFT_MESH;
#endif
#ifdef ARCH_ESP32
<<<<<<< store-and-forward
#include "esp_task_wdt.h"
=======
>>>>>>> master
#endif
#if ARCH_PORTDUINO
#include "modules/StoreForwardModule.h"
#include "platform/portduino/PortduinoGlue.h"
#endif
@@ -171,7 +177,7 @@ void Screen::showOverlayBanner(BannerOverlayOptions banner_overlay_options)
}
// Called to trigger a banner with custom message and duration
void Screen::showNodePicker(const char *message, uint32_t durationMs, std::function<void(int)> bannerCallback)
void Screen::showNodePicker(const char *message, uint32_t durationMs, std::function<void(uint32_t)> bannerCallback)
{
#ifdef USE_EINK
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
@@ -196,7 +202,6 @@ void Screen::showNodePicker(const char *message, uint32_t durationMs, std::funct
void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits,
std::function<void(uint32_t)> bannerCallback)
{
LOG_WARN("Show Number Picker");
#ifdef USE_EINK
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
#endif
@@ -641,6 +646,11 @@ void Screen::forceDisplay(bool forceUiUpdate)
// Tell EInk class to update the display
static_cast<EInkDisplay *>(dispdev)->forceDisplay();
#else
// No delay between UI frame rendering
if (forceUiUpdate) {
setFastFramerate();
}
#endif
}
@@ -1226,6 +1236,327 @@ void Screen::setFastFramerate()
runASAP = true;
}
<<<<<<< store-and-forward
void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setFont(FONT_SMALL);
// The coordinates define the left starting point of the text
display->setTextAlignment(TEXT_ALIGN_LEFT);
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
display->setColor(BLACK);
}
char channelStr[20];
{
concurrency::LockGuard guard(&lock);
snprintf(channelStr, sizeof(channelStr), "#%s", channels.getName(channels.getPrimaryIndex()));
}
// Display power status
if (powerStatus->getHasBattery()) {
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
drawBattery(display, x, y + 2, imgBattery, powerStatus);
} else {
drawBattery(display, x + 1, y + 3, imgBattery, powerStatus);
}
} else if (powerStatus->knowsUSB()) {
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
display->drawFastImage(x, y + 2, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower);
} else {
display->drawFastImage(x + 1, y + 3, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower);
}
}
// Display nodes status
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodeStatus);
} else {
drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 3, nodeStatus);
}
#if HAS_GPS
// Display GPS status
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
drawGPSpowerstat(display, x, y + 2, gpsStatus);
} else {
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus);
} else {
drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 3, gpsStatus);
}
}
#endif
display->setColor(WHITE);
// Draw the channel name
display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr);
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
// Draw our hardware ID to assist with bluetooth pairing. Either prefix with Info or S&F Logo
if (moduleConfig.store_forward.enabled) {
if (!Throttle::isWithinTimespanMs(storeForwardModule->lastHeartbeat,
(storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
imgQuestionL1);
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8,
imgQuestionL2);
#else
display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8,
imgQuestion);
#endif
} else {
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8,
imgSFL1);
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 16, 8,
imgSFL2);
#else
display->drawFastImage(x + SCREEN_WIDTH - 13 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 11, 8,
imgSF);
#endif
}
#else
// No store and forward, show a exclamation mark
if (false) {
#endif
} else {
// TODO: Raspberry Pi supports more than just the one screen size
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
imgInfoL1);
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8,
imgInfoL2);
#else
display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, imgInfo);
#endif
}
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(ourId), y + FONT_HEIGHT_SMALL, ourId);
// Draw any log messages
display->drawLogBuffer(x, y + (FONT_HEIGHT_SMALL * 2));
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
#ifdef SHOW_REDRAWS
if (heartbeat)
display->setPixel(0, 0);
heartbeat = !heartbeat;
#endif
}
// Jm
void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
const char *wifiName = config.network.wifi_ssid;
display->setFont(FONT_SMALL);
// The coordinates define the left starting point of the text
display->setTextAlignment(TEXT_ALIGN_LEFT);
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
display->setColor(BLACK);
}
if (WiFi.status() != WL_CONNECTED) {
display->drawString(x, y, String("WiFi: Not Connected"));
if (config.display.heading_bold)
display->drawString(x + 1, y, String("WiFi: Not Connected"));
} else {
display->drawString(x, y, String("WiFi: Connected"));
if (config.display.heading_bold)
display->drawString(x + 1, y, String("WiFi: Connected"));
display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())), y,
"RSSI " + String(WiFi.RSSI()));
if (config.display.heading_bold) {
display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())) - 1, y,
"RSSI " + String(WiFi.RSSI()));
}
}
display->setColor(WHITE);
/*
- WL_CONNECTED: assigned when connected to a WiFi network;
- WL_NO_SSID_AVAIL: assigned when no SSID are available;
- WL_CONNECT_FAILED: assigned when the connection fails for all the attempts;
- WL_CONNECTION_LOST: assigned when the connection is lost;
- WL_DISCONNECTED: assigned when disconnected from a network;
- WL_IDLE_STATUS: it is a temporary status assigned when WiFi.begin() is called and remains active until the number of
attempts expires (resulting in WL_CONNECT_FAILED) or a connection is established (resulting in WL_CONNECTED);
- WL_SCAN_COMPLETED: assigned when the scan networks is completed;
- WL_NO_SHIELD: assigned when no WiFi shield is present;
*/
if (WiFi.status() == WL_CONNECTED) {
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IP: " + String(WiFi.localIP().toString().c_str()));
} else if (WiFi.status() == WL_NO_SSID_AVAIL) {
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "SSID Not Found");
} else if (WiFi.status() == WL_CONNECTION_LOST) {
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Lost");
} else if (WiFi.status() == WL_CONNECT_FAILED) {
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Failed");
} else if (WiFi.status() == WL_IDLE_STATUS) {
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Idle ... Reconnecting");
}
#ifdef ARCH_ESP32
else {
// Codes:
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-reason-code
display->drawString(x, y + FONT_HEIGHT_SMALL * 1,
WiFi.disconnectReasonName(static_cast<wifi_err_reason_t>(getWifiDisconnectReason())));
}
#else
else {
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Unkown status: " + String(WiFi.status()));
}
#endif
display->drawString(x, y + FONT_HEIGHT_SMALL * 2, "SSID: " + String(wifiName));
display->drawString(x, y + FONT_HEIGHT_SMALL * 3, "http://meshtastic.local");
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
#ifdef SHOW_REDRAWS
if (heartbeat)
display->setPixel(0, 0);
heartbeat = !heartbeat;
#endif
#endif
}
void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setFont(FONT_SMALL);
// The coordinates define the left starting point of the text
display->setTextAlignment(TEXT_ALIGN_LEFT);
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
display->setColor(BLACK);
}
char batStr[20];
if (powerStatus->getHasBattery()) {
int batV = powerStatus->getBatteryVoltageMv() / 1000;
int batCv = (powerStatus->getBatteryVoltageMv() % 1000) / 10;
snprintf(batStr, sizeof(batStr), "B %01d.%02dV %3d%% %c%c", batV, batCv, powerStatus->getBatteryChargePercent(),
powerStatus->getIsCharging() ? '+' : ' ', powerStatus->getHasUSB() ? 'U' : ' ');
// Line 1
display->drawString(x, y, batStr);
if (config.display.heading_bold)
display->drawString(x + 1, y, batStr);
} else {
// Line 1
display->drawString(x, y, String("USB"));
if (config.display.heading_bold)
display->drawString(x + 1, y, String("USB"));
}
// auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, true);
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode);
// if (config.display.heading_bold)
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode);
uint32_t currentMillis = millis();
uint32_t seconds = currentMillis / 1000;
uint32_t minutes = seconds / 60;
uint32_t hours = minutes / 60;
uint32_t days = hours / 24;
// currentMillis %= 1000;
// seconds %= 60;
// minutes %= 60;
// hours %= 24;
// Show uptime as days, hours, minutes OR seconds
std::string uptime = screen->drawTimeDelta(days, hours, minutes, seconds);
// Line 1 (Still)
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
if (config.display.heading_bold)
display->drawString(x - 1 + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
display->setColor(WHITE);
// Setup string to assemble analogClock string
std::string analogClock = "";
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
if (rtc_sec > 0) {
long hms = rtc_sec % SEC_PER_DAY;
// hms += tz.tz_dsttime * SEC_PER_HOUR;
// hms -= tz.tz_minuteswest * SEC_PER_MIN;
// mod `hms` to ensure in positive range of [0...SEC_PER_DAY)
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
// Tear apart hms into h:m:s
int hour = hms / SEC_PER_HOUR;
int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
char timebuf[12];
if (config.display.use_12h_clock) {
std::string meridiem = "am";
if (hour >= 12) {
if (hour > 12)
hour -= 12;
meridiem = "pm";
}
if (hour == 00) {
hour = 12;
}
snprintf(timebuf, sizeof(timebuf), "%d:%02d:%02d%s", hour, min, sec, meridiem.c_str());
} else {
snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", hour, min, sec);
}
analogClock += timebuf;
}
// Line 2
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, analogClock.c_str());
// Display Channel Utilization
char chUtil[13];
snprintf(chUtil, sizeof(chUtil), "ChUtil %2.0f%%", airTime->channelUtilizationPercent());
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(chUtil), y + FONT_HEIGHT_SMALL * 1, chUtil);
#if HAS_GPS
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
// Line 3
if (config.display.gps_format !=
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude
drawGPSAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
// Line 4
drawGPScoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus);
} else {
drawGPSpowerstat(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
}
#endif
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
#ifdef SHOW_REDRAWS
if (heartbeat)
display->setPixel(0, 0);
heartbeat = !heartbeat;
#endif
}
=======
>>>>>>> master
int Screen::handleStatusUpdate(const meshtastic::Status *arg)
{
// LOG_DEBUG("Screen got status update %d", arg->getStatusType());
@@ -1258,40 +1589,45 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
devicestate.has_rx_text_message = true; // Needed to include the message frame
hasUnreadMessage = true; // Enables mail icon in the header
setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view
forceDisplay(); // Forces screen redraw
// === Prepare banner content ===
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
// Only wake/force display if the configuration allows it
if (shouldWakeOnReceivedMessage()) {
setOn(true); // Wake up the screen first
forceDisplay(); // Forces screen redraw
const char *msgRaw = reinterpret_cast<const char *>(packet->decoded.payload.bytes);
// === Prepare banner content ===
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
char banner[256];
const char *msgRaw = reinterpret_cast<const char *>(packet->decoded.payload.bytes);
// Check for bell character in message to determine alert type
bool isAlert = false;
for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
if (msgRaw[i] == '\x07') {
isAlert = true;
break;
char banner[256];
// Check for bell character in message to determine alert type
bool isAlert = false;
for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
if (msgRaw[i] == '\x07') {
isAlert = true;
break;
}
}
}
if (isAlert) {
if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
if (isAlert) {
if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
} else {
strcpy(banner, "Alert Received");
}
} else {
strcpy(banner, "Alert Received");
if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
} else {
strcpy(banner, "New Message");
}
}
} else {
if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
} else {
strcpy(banner, "New Message");
}
}
screen->showSimpleBanner(banner, 3000);
screen->showSimpleBanner(banner, 3000);
}
}
}
@@ -1330,7 +1666,7 @@ int Screen::handleInputEvent(const InputEvent *event)
setFastFramerate(); // Draw ASAP
#endif
if (NotificationRenderer::isOverlayBannerShowing()) {
NotificationRenderer::inEvent = event->inputEvent;
NotificationRenderer::inEvent = *event;
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
setFastFramerate(); // Draw ASAP
@@ -1424,3 +1760,23 @@ bool Screen::isOverlayBannerShowing()
#else
graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {}
#endif // HAS_SCREEN
bool shouldWakeOnReceivedMessage()
{
/*
The goal here is to determine when we do NOT wake up the screen on message received:
- Any ext. notifications are turned on
- If role is not client / client_mute
- If the battery level is very low
*/
if (moduleConfig.external_notification.enabled) {
return false;
}
if (!meshtastic_Config_DeviceConfig_Role_CLIENT && !meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE) {
return false;
}
if (powerStatus && powerStatus->getBatteryChargePercent() < 10) {
return false;
}
return true;
}

View File

@@ -26,6 +26,8 @@ struct BannerOverlayOptions {
};
} // namespace graphics
bool shouldWakeOnReceivedMessage();
#if !HAS_SCREEN
#include "power.h"
namespace graphics
@@ -92,6 +94,7 @@ class Screen
#include "commands.h"
#include "concurrency/LockGuard.h"
#include "concurrency/OSThread.h"
#include "graphics/draw/MenuHandler.h"
#include "input/InputBroker.h"
#include "mesh/MeshModule.h"
#include "modules/AdminModule.h"
@@ -308,9 +311,15 @@ class Screen : public concurrency::OSThread
void showSimpleBanner(const char *message, uint32_t durationMs = 0);
void showOverlayBanner(BannerOverlayOptions);
void showNodePicker(const char *message, uint32_t durationMs, std::function<void(int)> bannerCallback);
void showNodePicker(const char *message, uint32_t durationMs, std::function<void(uint32_t)> bannerCallback);
void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function<void(uint32_t)> bannerCallback);
void requestMenu(graphics::menuHandler::screenMenus menuToShow)
{
graphics::menuHandler::menuQueue = menuToShow;
runNow();
}
void startFirmwareUpdateScreen()
{
ScreenCmd cmd;

View File

@@ -13,6 +13,7 @@
#include "main.h"
#include "modules/AdminModule.h"
#include "modules/CannedMessageModule.h"
#include "modules/KeyVerificationModule.h"
extern uint16_t TFT_MESH;
@@ -128,11 +129,11 @@ void menuHandler::ClockFacePicker()
screen->runNow();
} else if (selected == Digital) {
uiconfig.is_clockface_analog = false;
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
saveUIConfig();
screen->setFrames(Screen::FOCUS_CLOCK);
} else {
uiconfig.is_clockface_analog = true;
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
saveUIConfig();
screen->setFrames(Screen::FOCUS_CLOCK);
}
};
@@ -237,27 +238,25 @@ void menuHandler::clockMenu()
void menuHandler::messageResponseMenu()
{
enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3, Aloud = 4, enumEnd = 5 };
static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply via Preset"};
static int optionsEnumArray[enumEnd] = {Back, Dismiss, Preset};
int options = 3;
static const char **optionsArrayPtr;
int options;
enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3 };
if (kb_found) {
static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset", "Reply via Freetext"};
optionsArrayPtr = optionsArray;
options = 4;
} else {
static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset"};
optionsArrayPtr = optionsArray;
options = 3;
optionsArray[options] = "Reply via Freetext";
optionsEnumArray[options++] = Freetext;
}
#ifdef HAS_I2S
static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset", "Reply via Freetext", "Read Aloud"};
optionsArrayPtr = optionsArray;
options = 5;
optionsArray[options] = "Read Aloud";
optionsEnumArray[options++] = Aloud;
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Message Action";
bannerOptions.optionsArrayPtr = optionsArrayPtr;
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Dismiss) {
@@ -276,7 +275,7 @@ void menuHandler::messageResponseMenu()
}
}
#ifdef HAS_I2S
else if (selected == 4) {
else if (selected == Aloud) {
const meshtastic_MeshPacket &mp = devicestate.rx_text_message;
const char *msg = reinterpret_cast<const char *>(mp.decoded.payload.bytes);
@@ -289,10 +288,10 @@ void menuHandler::messageResponseMenu()
void menuHandler::homeBaseMenu()
{
enum optionsNumbers { Back, Backlight, Position, Preset, Freetext, Bluetooth, Sleep };
enum optionsNumbers { Back, Backlight, Position, Preset, Freetext, Bluetooth, Sleep, enumEnd };
static const char *optionsArray[6] = {"Back"};
static int optionsEnumArray[6] = {Back};
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
#ifdef PIN_EINK_EN
@@ -347,37 +346,28 @@ void menuHandler::homeBaseMenu()
void menuHandler::systemBaseMenu()
{
// Check if brightness is supported
bool hasSupportBrightness = false;
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || HAS_TFT
hasSupportBrightness = true;
#endif
enum optionsNumbers { Back, Beeps, Brightness, Reboot, Color, MUI, Test };
static const char *optionsArray[7] = {"Back"};
static int optionsEnumArray[7] = {Back};
enum optionsNumbers { Back, Notifications, ScreenOptions, PowerMenu, Test, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
optionsArray[options] = "Reboot";
optionsEnumArray[options++] = Reboot;
optionsArray[options] = "Beeps Action";
optionsEnumArray[options++] = Beeps;
if (hasSupportBrightness) {
optionsArray[options] = "Brightness";
optionsEnumArray[options++] = Brightness;
}
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
optionsArray[options] = "Screen Color";
optionsEnumArray[options++] = Color;
#endif
#if HAS_TFT
optionsArray[options] = "Switch to MUI";
optionsEnumArray[options++] = MUI;
optionsArray[options] = "Notifications";
optionsEnumArray[options++] = Notifications;
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || \
defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
optionsArray[options] = "Screen Options";
optionsEnumArray[options++] = ScreenOptions;
#endif
optionsArray[options] = "Reboot/Shutdown";
optionsEnumArray[options++] = PowerMenu;
if (test_enabled) {
optionsArray[options] = "Test Menu";
optionsEnumArray[options++] = Test;
@@ -389,20 +379,14 @@ void menuHandler::systemBaseMenu()
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Beeps) {
menuHandler::menuQueue = menuHandler::buzzermodemenupicker;
if (selected == Notifications) {
menuHandler::menuQueue = menuHandler::notifications_menu;
screen->runNow();
} else if (selected == Brightness) {
menuHandler::menuQueue = menuHandler::brightness_picker;
} else if (selected == ScreenOptions) {
menuHandler::menuQueue = menuHandler::screen_options_menu;
screen->runNow();
} else if (selected == Reboot) {
menuHandler::menuQueue = menuHandler::reboot_menu;
screen->runNow();
} else if (selected == MUI) {
menuHandler::menuQueue = menuHandler::mui_picker;
screen->runNow();
} else if (selected == Color) {
menuHandler::menuQueue = menuHandler::tftcolormenupicker;
} else if (selected == PowerMenu) {
menuHandler::menuQueue = menuHandler::power_menu;
screen->runNow();
} else if (selected == Test) {
menuHandler::menuQueue = menuHandler::test_menu;
@@ -419,21 +403,22 @@ void menuHandler::systemBaseMenu()
void menuHandler::favoriteBaseMenu()
{
int options;
static const char **optionsArrayPtr;
enum optionsNumbers { Back, Preset, Freetext, Remove, enumEnd };
static const char *optionsArray[enumEnd] = {"Back", "New Preset Msg"};
static int optionsEnumArray[enumEnd] = {Back, Preset};
int options = 2;
if (kb_found) {
static const char *optionsArray[] = {"Back", "New Preset Msg", "New Freetext Msg", "Remove Favorite"};
optionsArrayPtr = optionsArray;
options = 4;
} else {
static const char *optionsArray[] = {"Back", "New Preset Msg", "Remove Favorite"};
optionsArrayPtr = optionsArray;
options = 3;
optionsArray[options] = "New Freetext Msg";
optionsEnumArray[options++] = Freetext;
}
optionsArray[options] = "Remove Favorite";
optionsEnumArray[options++] = Remove;
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Favorites Action";
bannerOptions.optionsArrayPtr = optionsArrayPtr;
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
@@ -450,34 +435,29 @@ void menuHandler::favoriteBaseMenu()
void menuHandler::positionBaseMenu()
{
int options;
static const char **optionsArrayPtr;
static const char *optionsArray[] = {"Back", "GPS Toggle", "Compass"};
static const char *optionsArrayCalibrate[] = {"Back", "GPS Toggle", "Compass", "Compass Calibrate"};
enum optionsNumbers { Back, GPSToggle, CompassMenu, CompassCalibrate, enumEnd };
static const char *optionsArray[enumEnd] = {"Back", "GPS Toggle", "Compass"};
static int optionsEnumArray[enumEnd] = {Back, GPSToggle, CompassMenu};
int options = 3;
if (accelerometerThread) {
optionsArrayPtr = optionsArrayCalibrate;
options = 4;
} else {
optionsArrayPtr = optionsArray;
options = 3;
optionsArray[options] = "Compass Calibrate";
optionsEnumArray[options++] = CompassCalibrate;
}
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Position Action";
bannerOptions.optionsArrayPtr = optionsArrayPtr;
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
#if MESHTASTIC_EXCLUDE_GPS
menuQueue = menu_none;
#else
if (selected == GPSToggle) {
menuQueue = gps_toggle_menu;
screen->runNow();
#endif
} else if (selected == 2) {
} else if (selected == CompassMenu) {
menuQueue = compass_point_north_menu;
screen->runNow();
} else if (selected == 3) {
} else if (selected == CompassCalibrate) {
accelerometerThread->calibrate(30);
}
};
@@ -486,16 +466,20 @@ void menuHandler::positionBaseMenu()
void menuHandler::nodeListMenu()
{
static const char *optionsArray[] = {"Back", "Add Favorite", "Reset NodeDB"};
enum optionsNumbers { Back, Favorite, Verify, Reset };
static const char *optionsArray[] = {"Back", "Add Favorite", "Key Verification", "Reset NodeDB"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Node Action";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.optionsCount = 4;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
if (selected == Favorite) {
menuQueue = add_favorite;
screen->runNow();
} else if (selected == 2) {
} else if (selected == Verify) {
menuQueue = key_verification_init;
screen->runNow();
} else if (selected == Reset) {
menuQueue = reset_node_db_menu;
screen->runNow();
}
@@ -523,6 +507,7 @@ void menuHandler::resetNodeDBMenu()
void menuHandler::compassNorthMenu()
{
enum optionsNumbers { Back, Dynamic, Fixed, Freeze };
static const char *optionsArray[] = {"Back", "Dynamic", "Fixed Ring", "Freeze Heading"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "North Directions?";
@@ -530,28 +515,25 @@ void menuHandler::compassNorthMenu()
bannerOptions.optionsCount = 4;
bannerOptions.InitialSelected = uiconfig.compass_mode + 1;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
if (selected == Dynamic) {
if (uiconfig.compass_mode != meshtastic_CompassMode_DYNAMIC) {
uiconfig.compass_mode = meshtastic_CompassMode_DYNAMIC;
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg,
&uiconfig);
saveUIConfig();
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
} else if (selected == 2) {
} else if (selected == Fixed) {
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) {
uiconfig.compass_mode = meshtastic_CompassMode_FIXED_RING;
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg,
&uiconfig);
saveUIConfig();
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
} else if (selected == 3) {
} else if (selected == Freeze) {
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) {
uiconfig.compass_mode = meshtastic_CompassMode_FREEZE_HEADING;
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg,
&uiconfig);
saveUIConfig();
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
} else if (selected == 0) {
} else if (selected == Back) {
menuQueue = position_base_menu;
screen->runNow();
}
@@ -562,6 +544,7 @@ void menuHandler::compassNorthMenu()
#if !MESHTASTIC_EXCLUDE_GPS
void menuHandler::GPSToggleMenu()
{
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Toggle GPS";
@@ -609,7 +592,7 @@ void menuHandler::BuzzerModeMenu()
{
static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Beep Action";
bannerOptions.message = "Buzzer Mode";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 4;
bannerOptions.bannerCallback = [](int selected) -> void {
@@ -659,7 +642,7 @@ void menuHandler::BrightnessPickerMenu()
#endif
// Save to device
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
saveUIConfig();
LOG_INFO("Screen brightness set to %d", uiconfig.screen_brightness);
}
@@ -670,13 +653,13 @@ void menuHandler::BrightnessPickerMenu()
void menuHandler::switchToMUIMenu()
{
static const char *optionsArray[] = {"Yes", "No"};
static const char *optionsArray[] = {"No", "Yes"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Switch to MUI?";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 0) {
if (selected == 1) {
config.display.displaymode = meshtastic_Config_DisplayConfig_DisplayMode_COLOR;
config.bluetooth.enabled = false;
service->reloadConfig(SEGMENT_CONFIG);
@@ -741,6 +724,9 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
TFT_MESH_r = 255;
TFT_MESH_g = 255;
TFT_MESH_b = 255;
} else {
menuQueue = system_base_menu;
screen->runNow();
}
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
@@ -770,7 +756,7 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
uiconfig.screen_rgb_color = (TFT_MESH_r << 16) | (TFT_MESH_g << 8) | TFT_MESH_b;
}
LOG_INFO("Storing Value of %d to uiconfig.screen_rgb_color", uiconfig.screen_rgb_color);
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
saveUIConfig();
}
#endif
};
@@ -789,6 +775,29 @@ void menuHandler::rebootMenu()
IF_SCREEN(screen->showSimpleBanner("Rebooting...", 0));
nodeDB->saveToDisk();
rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000;
} else {
menuQueue = power_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::shutdownMenu()
{
static const char *optionsArray[] = {"Back", "Confirm"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Shutdown Device?";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
IF_SCREEN(screen->showSimpleBanner("Shutting Down...", 0));
nodeDB->saveToDisk();
power->shutdown();
} else {
menuQueue = power_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
@@ -796,7 +805,7 @@ void menuHandler::rebootMenu()
void menuHandler::addFavoriteMenu()
{
screen->showNodePicker("Node To Favorite", 30000, [](int nodenum) -> void {
screen->showNodePicker("Node To Favorite", 30000, [](uint32_t nodenum) -> void {
LOG_WARN("Nodenum: %u", nodenum);
nodeDB->set_favorite(true, nodenum);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
@@ -887,6 +896,148 @@ void menuHandler::wifiToggleMenu()
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::notificationsMenu()
{
enum optionsNumbers { Back, BuzzerActions };
static const char *optionsArray[] = {"Back", "Buzzer Actions"};
static int optionsEnumArray[] = {Back, BuzzerActions};
int options = 2;
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Notifications";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == BuzzerActions) {
menuHandler::menuQueue = menuHandler::buzzermodemenupicker;
screen->runNow();
} else {
menuQueue = system_base_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::screenOptionsMenu()
{
// Check if brightness is supported
bool hasSupportBrightness = false;
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || HAS_TFT
hasSupportBrightness = true;
#endif
enum optionsNumbers { Back, Brightness, ScreenColor };
static const char *optionsArray[4] = {"Back"};
static int optionsEnumArray[4] = {Back};
int options = 1;
// Only show brightness for B&W displays
if (hasSupportBrightness && !HAS_TFT) {
optionsArray[options] = "Brightness";
optionsEnumArray[options++] = Brightness;
}
// Only show screen color for TFT displays
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
optionsArray[options] = "Screen Color";
optionsEnumArray[options++] = ScreenColor;
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Screen Options";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Brightness) {
menuHandler::menuQueue = menuHandler::brightness_picker;
screen->runNow();
} else if (selected == ScreenColor) {
menuHandler::menuQueue = menuHandler::tftcolormenupicker;
screen->runNow();
} else {
menuQueue = system_base_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::powerMenu()
{
enum optionsNumbers { Back, Reboot, Shutdown, MUI };
static const char *optionsArray[4] = {"Back"};
static int optionsEnumArray[4] = {Back};
int options = 1;
optionsArray[options] = "Reboot";
optionsEnumArray[options++] = Reboot;
optionsArray[options] = "Shutdown";
optionsEnumArray[options++] = Shutdown;
#if HAS_TFT
optionsArray[options] = "Switch to MUI";
optionsEnumArray[options++] = MUI;
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Reboot / Shutdown";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Reboot) {
menuHandler::menuQueue = menuHandler::reboot_menu;
screen->runNow();
} else if (selected == Shutdown) {
menuHandler::menuQueue = menuHandler::shutdown_menu;
screen->runNow();
} else if (selected == MUI) {
menuHandler::menuQueue = menuHandler::mui_picker;
screen->runNow();
} else {
menuQueue = system_base_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::keyVerificationInitMenu()
{
screen->showNodePicker("Node to Verify", 30000,
[](uint32_t selected) -> void { keyVerificationModule->sendInitialRequest(selected); });
}
void menuHandler::keyVerificationFinalPrompt()
{
char message[40] = {0};
memset(message, 0, sizeof(message));
sprintf(message, "Verification: \n");
keyVerificationModule->generateVerificationCode(message + 15); // send the toPhone packet
if (screen) {
static const char *optionsArray[] = {"Reject", "Accept"};
graphics::BannerOverlayOptions options;
options.message = message;
options.durationMs = 30000;
options.optionsArrayPtr = optionsArray;
options.optionsCount = 2;
options.notificationType = graphics::notificationTypeEnum::selection_picker;
options.bannerCallback = [=](int selected) {
if (selected == 1) {
auto remoteNodePtr = nodeDB->getMeshNode(keyVerificationModule->getCurrentRemoteNode());
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
}
};
screen->showOverlayBanner(options);
}
}
void menuHandler::handleMenuSwitch(OLEDDisplay *display)
{
if (menuQueue != menu_none)
@@ -909,6 +1060,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case clock_menu:
clockMenu();
break;
case system_base_menu:
systemBaseMenu();
break;
case position_base_menu:
positionBaseMenu();
break;
@@ -938,6 +1092,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case reboot_menu:
rebootMenu();
break;
case shutdown_menu:
shutdownMenu();
break;
case add_favorite:
addFavoriteMenu();
break;
@@ -953,13 +1110,36 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case wifi_toggle_menu:
wifiToggleMenu();
break;
case key_verification_init:
keyVerificationInitMenu();
break;
case key_verification_final_prompt:
keyVerificationFinalPrompt();
break;
case bluetooth_toggle_menu:
BluetoothToggleMenu();
break;
case notifications_menu:
notificationsMenu();
break;
case screen_options_menu:
screenOptionsMenu();
break;
case power_menu:
powerMenu();
break;
case throttle_message:
screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000);
break;
}
menuQueue = menu_none;
}
void menuHandler::saveUIConfig()
{
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
}
} // namespace graphics
#endif

View File

@@ -1,3 +1,5 @@
#pragma once
#if HAS_SCREEN
#include "configuration.h"
namespace graphics
{
@@ -21,12 +23,20 @@ class menuHandler
tftcolormenupicker,
brightness_picker,
reboot_menu,
shutdown_menu,
add_favorite,
remove_favorite,
test_menu,
number_test,
wifi_toggle_menu,
bluetooth_toggle_menu
bluetooth_toggle_menu,
notifications_menu,
screen_options_menu,
power_menu,
system_base_menu,
key_verification_init,
key_verification_final_prompt,
throttle_message
};
static screenMenus menuQueue;
@@ -50,13 +60,23 @@ class menuHandler
static void resetNodeDBMenu();
static void BrightnessPickerMenu();
static void rebootMenu();
static void shutdownMenu();
static void addFavoriteMenu();
static void removeFavoriteMenu();
static void testMenu();
static void numberTest();
static void wifiBaseMenu();
static void wifiToggleMenu();
static void notificationsMenu();
static void screenOptionsMenu();
static void powerMenu();
private:
static void saveUIConfig();
static void keyVerificationInitMenu();
static void keyVerificationFinalPrompt();
static void BluetoothToggleMenu();
};
} // namespace graphics
} // namespace graphics
#endif

View File

@@ -26,7 +26,7 @@ extern bool hasUnreadMessage;
namespace graphics
{
char NotificationRenderer::inEvent = INPUT_BROKER_NONE;
InputEvent NotificationRenderer::inEvent;
int8_t NotificationRenderer::curSelected = 0;
char NotificationRenderer::alertBannerMessage[256] = {0};
uint32_t NotificationRenderer::alertBannerUntil = 0; // 0 is a special case meaning forever
@@ -72,11 +72,25 @@ void NotificationRenderer::resetBanner()
{
alertBannerMessage[0] = '\0';
current_notification_type = notificationTypeEnum::none;
inEvent.inputEvent = INPUT_BROKER_NONE;
inEvent.kbchar = 0;
curSelected = 0;
alertBannerOptions = 0; // last x lines are seelctable options
optionsArrayPtr = nullptr;
optionsEnumPtr = nullptr;
alertBannerCallback = NULL;
pauseBanner = false;
numDigits = 0;
currentNumber = 0;
nodeDB->pause_sort(false);
}
void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state)
{
if (!isOverlayBannerShowing() && alertBannerMessage[0] != '\0')
resetBanner();
if (!isOverlayBannerShowing() || pauseBanner)
return;
switch (current_notification_type) {
@@ -115,31 +129,40 @@ void NotificationRenderer::drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiS
// modulo to extract
uint8_t this_digit = (currentNumber % (pow_of_10(numDigits - curSelected))) / (pow_of_10(numDigits - curSelected - 1));
// Handle input
if (inEvent == INPUT_BROKER_UP || inEvent == INPUT_BROKER_ALT_PRESS) {
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
if (this_digit == 9) {
currentNumber -= 9 * (pow_of_10(numDigits - curSelected - 1));
} else {
currentNumber += (pow_of_10(numDigits - curSelected - 1));
}
} else if (inEvent == INPUT_BROKER_DOWN || inEvent == INPUT_BROKER_USER_PRESS) {
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
if (this_digit == 0) {
currentNumber += 9 * (pow_of_10(numDigits - curSelected - 1));
} else {
currentNumber -= (pow_of_10(numDigits - curSelected - 1));
}
} else if (inEvent == INPUT_BROKER_SELECT || inEvent == INPUT_BROKER_RIGHT) {
} else if (inEvent.inputEvent == INPUT_BROKER_ANYKEY) {
if (inEvent.kbchar > 47 && inEvent.kbchar < 58) { // have a digit
currentNumber -= this_digit * (pow_of_10(numDigits - curSelected - 1));
currentNumber += (inEvent.kbchar - 48) * (pow_of_10(numDigits - curSelected - 1));
curSelected++;
}
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT || inEvent.inputEvent == INPUT_BROKER_RIGHT) {
curSelected++;
} else if (inEvent == INPUT_BROKER_LEFT) {
} else if (inEvent.inputEvent == INPUT_BROKER_LEFT) {
curSelected--;
} else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) {
} else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) &&
alertBannerUntil != 0) {
resetBanner();
return;
}
if (curSelected == numDigits) {
resetBanner();
alertBannerCallback(currentNumber);
resetBanner();
return;
}
inEvent = INPUT_BROKER_NONE;
inEvent.inputEvent = INPUT_BROKER_NONE;
if (alertBannerMessage[0] == '\0')
return;
@@ -193,16 +216,18 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
}
// Handle input
if (inEvent == INPUT_BROKER_UP || inEvent == INPUT_BROKER_ALT_PRESS) {
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
curSelected--;
} else if (inEvent == INPUT_BROKER_DOWN || inEvent == INPUT_BROKER_USER_PRESS) {
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
curSelected++;
} else if (inEvent == INPUT_BROKER_SELECT) {
resetBanner();
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
alertBannerCallback(selectedNodenum);
} else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) {
resetBanner();
return;
} else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) &&
alertBannerUntil != 0) {
resetBanner();
return;
}
if (curSelected == -1)
@@ -210,7 +235,7 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
if (curSelected == alertBannerOptions)
curSelected = 0;
inEvent = INPUT_BROKER_NONE;
inEvent.inputEvent = INPUT_BROKER_NONE;
if (alertBannerMessage[0] == '\0')
return;
@@ -308,11 +333,11 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
// Handle input
if (alertBannerOptions > 0) {
if (inEvent == INPUT_BROKER_UP || inEvent == INPUT_BROKER_ALT_PRESS) {
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
curSelected--;
} else if (inEvent == INPUT_BROKER_DOWN || inEvent == INPUT_BROKER_USER_PRESS) {
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
curSelected++;
} else if (inEvent == INPUT_BROKER_SELECT) {
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
if (optionsEnumPtr != nullptr) {
alertBannerCallback(optionsEnumPtr[curSelected]);
optionsEnumPtr = nullptr;
@@ -320,8 +345,11 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
alertBannerCallback(curSelected);
}
resetBanner();
} else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) {
return;
} else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) &&
alertBannerUntil != 0) {
resetBanner();
return;
}
if (curSelected == -1)
@@ -329,12 +357,14 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
if (curSelected == alertBannerOptions)
curSelected = 0;
} else {
if (inEvent == INPUT_BROKER_SELECT || inEvent == INPUT_BROKER_ALT_LONG || inEvent == INPUT_BROKER_CANCEL) {
if (inEvent.inputEvent == INPUT_BROKER_SELECT || inEvent.inputEvent == INPUT_BROKER_ALT_LONG ||
inEvent.inputEvent == INPUT_BROKER_CANCEL) {
resetBanner();
return;
}
}
inEvent = INPUT_BROKER_NONE;
inEvent.inputEvent = INPUT_BROKER_NONE;
if (alertBannerMessage[0] == '\0')
return;

View File

@@ -11,7 +11,8 @@ namespace graphics
class NotificationRenderer
{
public:
static char inEvent;
static InputEvent inEvent;
static char inKeypress;
static int8_t curSelected;
static char alertBannerMessage[256];
static uint32_t alertBannerUntil; // 0 is a special case meaning forever

View File

@@ -39,8 +39,8 @@ void InkHUD::Events::begin()
void InkHUD::Events::onButtonShort()
{
// Audio feedback (via buzzer)
// Short low tone
playBoop();
// Short tone
playChirp();
// Cancel any beeping, buzzing, blinking
// Some button handling suppressed if we are dismissing an external notification (see below)
bool dismissedExt = dismissExternalNotification();
@@ -64,8 +64,8 @@ void InkHUD::Events::onButtonShort()
void InkHUD::Events::onButtonLong()
{
// Audio feedback (via buzzer)
// Low tone, longer than playBoop
playBeep();
// Slightly longer than playChirp
playBoop();
// Check which system applet wants to handle the button press (if any)
SystemApplet *consumer = nullptr;

View File

@@ -0,0 +1,83 @@
#ifdef ARCH_PORTDUINO
#include "SeesawRotary.h"
#include "input/InputBroker.h"
using namespace concurrency;
SeesawRotary *seesawRotary;
SeesawRotary::SeesawRotary(const char *name) : OSThread(name)
{
_originName = name;
}
bool SeesawRotary::init()
{
if (inputBroker)
inputBroker->registerSource(this);
if (!ss.begin(SEESAW_ADDR)) {
return false;
}
// attachButtonInterrupts();
uint32_t version = ((ss.getVersion() >> 16) & 0xFFFF);
if (version != 4991) {
LOG_WARN("Wrong firmware loaded? %u", version);
} else {
LOG_INFO("Found Product 4991");
}
/*
#ifdef ARCH_ESP32
// Register callbacks for before and after lightsleep
// Used to detach and reattach interrupts
lsObserver.observe(&notifyLightSleep);
lsEndObserver.observe(&notifyLightSleepEnd);
#endif
*/
ss.pinMode(SS_SWITCH, INPUT_PULLUP);
// get starting position
encoder_position = ss.getEncoderPosition();
ss.setGPIOInterrupts((uint32_t)1 << SS_SWITCH, 1);
ss.enableEncoderInterrupt();
canSleep = true; // Assume we should not keep the board awake
return true;
}
int32_t SeesawRotary::runOnce()
{
InputEvent e;
e.inputEvent = INPUT_BROKER_NONE;
bool currentlyPressed = !ss.digitalRead(SS_SWITCH);
if (currentlyPressed && !wasPressed) {
e.inputEvent = INPUT_BROKER_SELECT;
}
wasPressed = currentlyPressed;
int32_t new_position = ss.getEncoderPosition();
// did we move arounde?
if (encoder_position != new_position) {
if (encoder_position == 0 && new_position != 1) {
e.inputEvent = INPUT_BROKER_ALT_PRESS;
} else if (new_position == 0 && encoder_position != 1) {
e.inputEvent = INPUT_BROKER_USER_PRESS;
} else if (new_position > encoder_position) {
e.inputEvent = INPUT_BROKER_USER_PRESS;
} else {
e.inputEvent = INPUT_BROKER_ALT_PRESS;
}
encoder_position = new_position;
}
if (e.inputEvent != INPUT_BROKER_NONE) {
e.source = this->_originName;
e.kbchar = 0x00;
this->notifyObservers(&e);
}
return 50;
}
#endif

29
src/input/SeesawRotary.h Normal file
View File

@@ -0,0 +1,29 @@
#pragma once
#ifdef ARCH_PORTDUINO
#include "Adafruit_seesaw.h"
#include "InputBroker.h"
#include "concurrency/OSThread.h"
#include "configuration.h"
#define SS_SWITCH 24
#define SS_NEOPIX 6
#define SEESAW_ADDR 0x36
class SeesawRotary : public Observable<const InputEvent *>, public concurrency::OSThread
{
public:
const char *_originName;
bool init();
SeesawRotary(const char *name);
int32_t runOnce() override;
private:
Adafruit_seesaw ss;
int32_t encoder_position;
bool wasPressed = false;
};
extern SeesawRotary *seesawRotary;
#endif

View File

@@ -700,6 +700,8 @@ void setup()
i2cScanner.reset();
#endif
// initSPI() must have called at this point (must be before screen and lora)
#ifdef HAS_SDCARD
setupSDCard();
#endif
@@ -792,7 +794,6 @@ void setup()
drv.setMode(DRV2605_MODE_INTTRIG);
#endif
// Init our SPI controller (must be before screen and lora)
#ifdef ARCH_RP2040
#ifdef HW_SPI1_DEVICE
SPI1.setSCK(LORA_SCK);

View File

@@ -57,6 +57,8 @@ uint32_t MemGet::getFreePsram()
{
#ifdef ARCH_ESP32
return ESP.getFreePsram();
#elif (defined(HAS_SDCARD) && defined(ARCH_ESP32))
return SD.totalBytes() - SD.usedBytes();
#elif defined(ARCH_PORTDUINO)
return 4194252;
#else
@@ -73,6 +75,8 @@ uint32_t MemGet::getPsramSize()
{
#ifdef ARCH_ESP32
return ESP.getPsramSize();
#elif (defined(HAS_SDCARD) && defined(ARCH_ESP32))
return SD.totalBytes();
#elif defined(ARCH_PORTDUINO)
return 4194252;
#else

View File

@@ -24,6 +24,11 @@
#define min_node_info_broadcast_secs 60 * 60 // No regular broadcasts of more than once an hour
#define min_neighbor_info_broadcast_secs 4 * 60 * 60
#define default_map_publish_interval_secs 60 * 60
#ifdef USERPREFS_RINGTONE_NAG_SECS
#define default_ringtone_nag_secs USERPREFS_RINGTONE_NAG_SECS
#else
#define default_ringtone_nag_secs 60
#endif
#define default_mqtt_address "mqtt.meshtastic.org"
#define default_mqtt_username "meshdev"

View File

@@ -283,7 +283,6 @@ void MeshService::sendToPhone(meshtastic_MeshPacket *p)
{
perhapsDecode(p);
#ifdef ARCH_ESP32
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
if (moduleConfig.store_forward.enabled && storeForwardModule->isServer() &&
p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) {
@@ -291,7 +290,6 @@ void MeshService::sendToPhone(meshtastic_MeshPacket *p)
fromNum++; // Notify observers for packet from radio
return;
}
#endif
#endif
if (toPhoneQueue.numFree() == 0) {

View File

@@ -13,11 +13,9 @@
#if defined(ARCH_PORTDUINO)
#include "../platform/portduino/SimRadio.h"
#endif
#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
#include "modules/StoreForwardModule.h"
#endif
#endif
extern Allocator<meshtastic_QueueStatus> &queueStatusPool;
extern Allocator<meshtastic_MqttClientProxyMessage> &mqttClientProxyMessagePool;

View File

@@ -22,6 +22,9 @@
#include "mesh-pb-constants.h"
#include "meshUtils.h"
#include "modules/NeighborInfoModule.h"
// #if !MESHTASTIC_EXCLUDE_STOREFORWARD
// #include "modules/StoreForwardModule.h"
// #endif
#include <ErriezCRC32.h>
#include <algorithm>
#include <pb_decode.h>
@@ -32,8 +35,6 @@
#if HAS_WIFI
#include "mesh/wifi/WiFiAPClient.h"
#endif
#include "SPILock.h"
#include "modules/StoreForwardModule.h"
#include <Preferences.h>
#include <esp_efuse.h>
#include <esp_efuse_table.h>
@@ -42,8 +43,10 @@
#include <soc/soc.h>
#endif
#ifdef ARCH_PORTDUINO
#include "SPILock.h"
#include "modules/StoreForwardModule.h"
#ifdef ARCH_PORTDUINO
#include "platform/portduino/PortduinoGlue.h"
#endif
@@ -344,6 +347,22 @@ NodeDB::NodeDB()
config.device.node_info_broadcast_secs = MAX_INTERVAL;
if (config.position.position_broadcast_secs > MAX_INTERVAL)
config.position.position_broadcast_secs = MAX_INTERVAL;
if (config.position.gps_update_interval > MAX_INTERVAL)
config.position.gps_update_interval = MAX_INTERVAL;
if (config.position.gps_attempt_time > MAX_INTERVAL)
config.position.gps_attempt_time = MAX_INTERVAL;
if (config.position.position_flags > MAX_INTERVAL)
config.position.position_flags = MAX_INTERVAL;
if (config.position.rx_gpio > MAX_INTERVAL)
config.position.rx_gpio = MAX_INTERVAL;
if (config.position.tx_gpio > MAX_INTERVAL)
config.position.tx_gpio = MAX_INTERVAL;
if (config.position.broadcast_smart_minimum_distance > MAX_INTERVAL)
config.position.broadcast_smart_minimum_distance = MAX_INTERVAL;
if (config.position.broadcast_smart_minimum_interval_secs > MAX_INTERVAL)
config.position.broadcast_smart_minimum_interval_secs = MAX_INTERVAL;
if (config.position.gps_en_gpio > MAX_INTERVAL)
config.position.gps_en_gpio = MAX_INTERVAL;
if (moduleConfig.neighbor_info.update_interval > MAX_INTERVAL)
moduleConfig.neighbor_info.update_interval = MAX_INTERVAL;
if (moduleConfig.telemetry.device_update_interval > MAX_INTERVAL)
@@ -778,7 +797,7 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.external_notification.output_buzzer = PIN_BUZZER;
moduleConfig.external_notification.use_pwm = true;
moduleConfig.external_notification.alert_message_buzzer = true;
moduleConfig.external_notification.nag_timeout = 60;
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
#endif
#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312)
// Default to RAK led pin 2 (blue)
@@ -787,7 +806,7 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.external_notification.active = true;
moduleConfig.external_notification.alert_message = true;
moduleConfig.external_notification.output_ms = 1000;
moduleConfig.external_notification.nag_timeout = 60;
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
#endif
#ifdef HAS_I2S
@@ -796,10 +815,10 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.external_notification.use_i2s_as_buzzer = true;
moduleConfig.external_notification.alert_message_buzzer = true;
#if HAS_TFT
if (moduleConfig.external_notification.nag_timeout == 60)
if (moduleConfig.external_notification.nag_timeout == default_ringtone_nag_secs)
moduleConfig.external_notification.nag_timeout = 0;
#else
moduleConfig.external_notification.nag_timeout = 60;
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
#endif
#endif
#ifdef NANO_G2_ULTRA
@@ -1309,6 +1328,13 @@ void NodeDB::loadFromDisk()
saveToDisk(SEGMENT_MODULECONFIG);
}
#if ARCH_PORTDUINO
// set any config overrides
if (settingsMap[has_configDisplayMode]) {
config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)settingsMap[configDisplayMode];
}
#endif
}
/** Save a protobuf from a file, return true for success */

View File

@@ -185,7 +185,7 @@ bool RF95Interface::init()
#endif
if (res == RADIOLIB_ERR_NONE)
res = lora->setCRC(RADIOLIB_SX126X_LORA_CRC_ON);
res = lora->setCRC(true);
if (res == RADIOLIB_ERR_NONE)
startReceive(); // start receiving

View File

@@ -13,8 +13,9 @@ template <class T> constexpr const T &clamp(const T &v, const T &lo, const T &hi
#if HAS_SCREEN
#define IF_SCREEN(X) \
if (screen) \
X;
if (screen) { \
X; \
}
#else
#define IF_SCREEN(...)
#endif

View File

@@ -1327,12 +1327,6 @@ void AdminModule::handleSendInputEvent(const meshtastic_AdminMessage_InputEvent
LOG_DEBUG("Processing input event: event_code=%u, kb_char=%u, touch_x=%u, touch_y=%u", inputEvent.event_code,
inputEvent.kb_char, inputEvent.touch_x, inputEvent.touch_y);
// Validate input parameters
if (inputEvent.event_code > INPUT_BROKER_ANYKEY) {
LOG_WARN("Invalid input event code: %u", inputEvent.event_code);
return;
}
// Create InputEvent for injection
InputEvent event = {.inputEvent = (input_broker_event)inputEvent.event_code,
.kbchar = (unsigned char)inputEvent.kb_char,

View File

@@ -454,7 +454,7 @@ int CannedMessageModule::handleDestinationSelectionInput(const InputEvent *event
else if ((destIndex / columns) >= (scrollIndex + visibleRows))
scrollIndex = (destIndex / columns) - visibleRows + 1;
screen->forceDisplay();
screen->forceDisplay(true);
return 1;
}
@@ -469,7 +469,7 @@ int CannedMessageModule::handleDestinationSelectionInput(const InputEvent *event
if ((destIndex / columns) >= (scrollIndex + visibleRows))
scrollIndex = (destIndex / columns) - visibleRows + 1;
screen->forceDisplay();
screen->forceDisplay(true);
return 1;
}
@@ -491,7 +491,7 @@ int CannedMessageModule::handleDestinationSelectionInput(const InputEvent *event
runState = returnToCannedList ? CANNED_MESSAGE_RUN_STATE_ACTIVE : CANNED_MESSAGE_RUN_STATE_FREETEXT;
returnToCannedList = false;
screen->forceDisplay();
screen->forceDisplay(true);
return 1;
}
@@ -504,7 +504,7 @@ int CannedMessageModule::handleDestinationSelectionInput(const InputEvent *event
// UIFrameEvent e;
// e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
// notifyObservers(&e);
screen->forceDisplay();
screen->forceDisplay(true);
return 1;
}
@@ -716,7 +716,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
}
// Backspace
if (event->inputEvent == INPUT_BROKER_BACK) {
if (event->inputEvent == INPUT_BROKER_BACK && this->freetext.length() > 0) {
payload = 0x08;
lastTouchMillis = millis();
runOnce();
@@ -739,7 +739,8 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
}
// Cancel (dismiss freetext screen)
if (event->inputEvent == INPUT_BROKER_CANCEL || event->inputEvent == INPUT_BROKER_ALT_LONG) {
if (event->inputEvent == INPUT_BROKER_CANCEL || event->inputEvent == INPUT_BROKER_ALT_LONG ||
(event->inputEvent == INPUT_BROKER_BACK && this->freetext.length() == 0)) {
runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
freetext = "";
cursor = 0;
@@ -989,6 +990,7 @@ int32_t CannedMessageModule::runOnce()
}
this->cursor--;
}
} else {
}
break;
case INPUT_BROKER_MSG_TAB: // Tab key: handled by input handler
@@ -2075,4 +2077,4 @@ String CannedMessageModule::drawWithCursor(String text, int cursor)
return result;
}
#endif
#endif

View File

@@ -362,9 +362,8 @@ ExternalNotificationModule::ExternalNotificationModule()
if (nodeDB->loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig),
&meshtastic_RTTTLConfig_msg, &rtttlConfig) != LoadFileResult::LOAD_SUCCESS) {
memset(rtttlConfig.ringtone, 0, sizeof(rtttlConfig.ringtone));
strncpy(rtttlConfig.ringtone,
"24:d=32,o=5,b=565:f6,p,f6,4p,p,f6,p,f6,2p,p,b6,p,b6,p,b6,p,b6,p,b,p,b,p,b,p,b,p,b,p,b,p,b,p,b,1p.,2p.,p",
sizeof(rtttlConfig.ringtone));
// The default ringtone is always loaded from userPrefs.jsonc
strncpy(rtttlConfig.ringtone, USERPREFS_RINGTONE_RTTTL, sizeof(rtttlConfig.ringtone));
}
LOG_INFO("Init External Notification Module");

View File

@@ -2,7 +2,9 @@
#include "KeyVerificationModule.h"
#include "MeshService.h"
#include "RTC.h"
#include "graphics/draw/MenuHandler.h"
#include "main.h"
#include "meshUtils.h"
#include "modules/AdminModule.h"
#include <SHA256.h>
@@ -48,18 +50,22 @@ AdminMessageHandleResult KeyVerificationModule::handleAdminMessageForModule(cons
bool KeyVerificationModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_KeyVerification *r)
{
updateState();
if (mp.pki_encrypted == false)
if (mp.pki_encrypted == false) {
return false;
if (mp.from != currentRemoteNode) // because the inital connection request is handled in allocReply()
}
if (mp.from != currentRemoteNode) { // because the inital connection request is handled in allocReply()
return false;
}
if (currentState == KEY_VERIFICATION_IDLE) {
return false; // if we're idle, the only acceptable message is an init, which should be handled by allocReply()
}
} else if (currentState == KEY_VERIFICATION_SENDER_HAS_INITIATED && r->nonce == currentNonce && r->hash2.size == 32 &&
r->hash1.size == 0) {
if (currentState == KEY_VERIFICATION_SENDER_HAS_INITIATED && r->nonce == currentNonce && r->hash2.size == 32 &&
r->hash1.size == 0) {
memcpy(hash2, r->hash2.bytes, 32);
if (screen)
screen->showSimpleBanner("Enter Security Number", 30000);
IF_SCREEN(screen->showNumberPicker("Enter Security Number", 60000, 6, [](int number_picked) -> void {
keyVerificationModule->processSecurityNumber(number_picked);
});)
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING;
@@ -79,23 +85,20 @@ bool KeyVerificationModule::handleReceivedProtobuf(const meshtastic_MeshPacket &
memset(message, 0, sizeof(message));
sprintf(message, "Verification: \n");
generateVerificationCode(message + 15);
static const char *optionsArray[] = {"ACCEPT", "REJECT"};
LOG_INFO("Hash1 matches!");
if (screen) {
graphics::BannerOverlayOptions options;
options.message = message;
options.durationMs = 30000;
options.optionsArrayPtr = optionsArray;
options.optionsCount = 2;
options.notificationType = graphics::notificationTypeEnum::selection_picker;
options.bannerCallback = [=](int selected) {
if (selected == 0) {
auto remoteNodePtr = nodeDB->getMeshNode(currentRemoteNode);
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
}
};
screen->showOverlayBanner(options);
}
static const char *optionsArray[] = {"Reject", "Accept"};
// Don't try to put the array definition in the macro. Does not work with curly braces.
IF_SCREEN(graphics::BannerOverlayOptions options; options.message = message; options.durationMs = 30000;
options.optionsArrayPtr = optionsArray; options.optionsCount = 2;
options.notificationType = graphics::notificationTypeEnum::selection_picker;
options.bannerCallback =
[=](int selected) {
if (selected == 1) {
auto remoteNodePtr = nodeDB->getMeshNode(currentRemoteNode);
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
}
};
screen->showOverlayBanner(options);)
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING;
sprintf(cn->message, "Final confirmation for incoming manual key verification %s", message);
@@ -120,6 +123,7 @@ bool KeyVerificationModule::sendInitialRequest(NodeNum remoteNode)
// generate nonce
updateState();
if (currentState != KEY_VERIFICATION_IDLE) {
IF_SCREEN(graphics::menuHandler::menuQueue = graphics::menuHandler::throttle_message;)
return false;
}
currentNonce = random();
@@ -190,11 +194,8 @@ meshtastic_MeshPacket *KeyVerificationModule::allocReply()
responsePacket = allocDataProtobuf(response);
responsePacket->pki_encrypted = true;
if (screen) {
snprintf(message, 25, "Security Number \n%03u %03u", currentSecurityNumber / 1000, currentSecurityNumber % 1000);
screen->showSimpleBanner(message, 30000);
LOG_WARN("%s", message);
}
IF_SCREEN(snprintf(message, 25, "Security Number \n%03u %03u", currentSecurityNumber / 1000, currentSecurityNumber % 1000);
screen->showSimpleBanner(message, 30000); LOG_WARN("%s", message);)
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING;
sprintf(cn->message, "Incoming Key Verification.\nSecurity Number\n%03u %03u", currentSecurityNumber / 1000,
@@ -258,12 +259,7 @@ void KeyVerificationModule::processSecurityNumber(uint32_t incomingNumber)
p->priority = meshtastic_MeshPacket_Priority_HIGH;
service->sendToMesh(p, RX_SRC_LOCAL, true);
currentState = KEY_VERIFICATION_SENDER_AWAITING_USER;
memset(message, 0, sizeof(message));
sprintf(message, "Verification: \n");
generateVerificationCode(message + 15); // send the toPhone packet
if (screen) {
screen->showSimpleBanner(message, 30000);
}
IF_SCREEN(screen->requestMenu(graphics::menuHandler::key_verification_final_prompt);)
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING;
sprintf(cn->message, "Final confirmation for outgoing manual key verification %s", message);
@@ -282,7 +278,7 @@ void KeyVerificationModule::processSecurityNumber(uint32_t incomingNumber)
void KeyVerificationModule::updateState()
{
if (currentState != KEY_VERIFICATION_IDLE) {
// check for the 30 second timeout
// check for the 60 second timeout
if (currentNonceTimestamp < getTime() - 60) {
resetToIdle();
} else {

View File

@@ -27,6 +27,8 @@ class KeyVerificationModule : public ProtobufModule<meshtastic_KeyVerification>
}*/
virtual bool wantUIFrame() { return false; };
bool sendInitialRequest(NodeNum remoteNode);
void generateVerificationCode(char *); // fills char with the user readable verification code
uint32_t getCurrentRemoteNode() { return currentRemoteNode; }
protected:
/* Called to handle a particular incoming message
@@ -56,9 +58,8 @@ class KeyVerificationModule : public ProtobufModule<meshtastic_KeyVerification>
char message[40] = {0};
void processSecurityNumber(uint32_t);
void updateState(); // check the timeouts and maybe reset the state to idle
void resetToIdle(); // Zero out module state
void generateVerificationCode(char *); // fills char with the user readable verification code
void updateState(); // check the timeouts and maybe reset the state to idle
void resetToIdle(); // Zero out module state
};
extern KeyVerificationModule *keyVerificationModule;

View File

@@ -53,6 +53,7 @@
#endif
#if ARCH_PORTDUINO
#include "input/LinuxInputImpl.h"
#include "input/SeesawRotary.h"
#include "modules/Telemetry/HostMetrics.h"
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
#include "modules/StoreForwardModule.h"
@@ -74,7 +75,6 @@
#if !MESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE
#include "modules/GenericThreadModule.h"
#endif
#ifdef ARCH_ESP32
#if defined(USE_SX1280) && !MESHTASTIC_EXCLUDE_AUDIO
#include "modules/esp32/AudioModule.h"
@@ -82,9 +82,6 @@
#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
#include "modules/esp32/PaxcounterModule.h"
#endif
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
#include "modules/StoreForwardModule.h"
#endif
#endif
#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO)
#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
@@ -163,7 +160,6 @@ void setupModules()
// Example: Put your module here
// new ReplyModule();
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
if (!rotaryEncoderInterruptImpl1->init()) {
@@ -189,6 +185,11 @@ void setupModules()
#endif // HAS_BUTTON
#if ARCH_PORTDUINO
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
seesawRotary = new SeesawRotary("SeesawRotary");
if (!seesawRotary->init()) {
delete seesawRotary;
seesawRotary = nullptr;
}
aLinuxInputImpl = new LinuxInputImpl();
aLinuxInputImpl->init();
}
@@ -248,7 +249,7 @@ void setupModules()
paxcounterModule = new PaxcounterModule();
#endif
#endif
#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(HAS_SDCARD)
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
storeForwardModule = new StoreForwardModule();
#endif

View File

@@ -32,7 +32,7 @@ StoreForwardModule *storeForwardModule;
int32_t StoreForwardModule::runOnce()
{
#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(HAS_SDCARD)
if (moduleConfig.store_forward.enabled && is_server) {
// Send out the message queue.
if (this->busy) {
@@ -89,6 +89,32 @@ void StoreForwardModule::populatePSRAM()
LOG_DEBUG("After PSRAM init: heap %d/%d PSRAM %d/%d", memGet.getFreeHeap(), memGet.getHeapSize(), memGet.getFreePsram(),
memGet.getPsramSize());
LOG_DEBUG("numberOfPackets for packetHistory - %u", numberOfPackets);
this->storageType = StorageType::ST_PSRAM;
}
/**
* if we have an SDCARD, format it for store&forward use
*/
void StoreForwardModule::populateSDCard()
{
#if defined(HAS_SDCARD)
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52))
spiLock->lock();
if (SD.cardType() != CARD_NONE) {
if (!SD.exists("/storeforward")) {
LOG_INFO("Creating StoreForward directory");
SD.mkdir("/storeforward");
}
this->storageType = StorageType::ST_SDCARD;
uint32_t numberOfPackets = (this->records ? this->records : (((SD.totalBytes() / 3) * 2) / sizeof(PacketHistoryStruct)));
this->records = numberOfPackets;
// only allocate space for one temp copy
this->packetHistory = (PacketHistoryStruct *)malloc(sizeof(PacketHistoryStruct));
LOG_DEBUG("numberOfPackets for packetHistory - %u", numberOfPackets);
}
spiLock->unlock();
#endif // ARCH_ESP32 || ARCH_NRF52
#endif // HAS_SDCARD
}
/**
@@ -135,14 +161,42 @@ uint32_t StoreForwardModule::getNumAvailablePackets(NodeNum dest, uint32_t last_
lastRequest.emplace(dest, 0);
}
for (uint32_t i = lastRequest[dest]; i < this->packetHistoryTotalCount; i++) {
if (this->packetHistory[i].time && (this->packetHistory[i].time > last_time)) {
// Client is only interested in packets not from itself and only in broadcast packets or packets towards it.
if (this->packetHistory[i].from != dest &&
(this->packetHistory[i].to == NODENUM_BROADCAST || this->packetHistory[i].to == dest)) {
count++;
if (this->storageType == StorageType::ST_PSRAM) {
if (this->packetHistory[i].time && (this->packetHistory[i].time > last_time)) {
// Client is only interested in packets not from itself and only in broadcast packets or packets towards it.
if (this->packetHistory[i].from != dest &&
(this->packetHistory[i].to == NODENUM_BROADCAST || this->packetHistory[i].to == dest)) {
count++;
}
}
} else if (this->storageType == StorageType::ST_SDCARD) {
#if defined(HAS_SDCARD)
#if defined(ARCH_ESP32) || defined(ARCH_NRF52)
spiLock->lock();
auto handler = SD.open("/storeforward/" + String(i), FILE_READ);
if (handler) {
if (handler.read((uint8_t *)&this->packetHistory[0], sizeof(PacketHistoryStruct)) !=
sizeof(PacketHistoryStruct)) {
LOG_ERROR("SD card reading error");
}
handler.close();
if (this->packetHistory[0].time && (this->packetHistory[0].time > last_time)) {
// Client is only interested in packets not from itself and only in broadcast packets or packets towards it.
if (this->packetHistory[0].from != dest &&
(this->packetHistory[0].to == NODENUM_BROADCAST || this->packetHistory[0].to == dest)) {
count++;
}
}
}
spiLock->unlock();
#endif
#endif
} else {
LOG_ERROR("S&F: Unknown storage type");
}
}
return count;
}
@@ -187,23 +241,47 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp)
const auto &p = mp.decoded;
if (this->packetHistoryTotalCount == this->records) {
LOG_WARN("S&F - PSRAM Full. Starting overwrite");
LOG_WARN("S&F - Storage Full. Starting overwrite");
this->packetHistoryTotalCount = 0;
for (auto &i : lastRequest) {
i.second = 0; // Clear the last request index for each client device
}
}
this->packetHistory[this->packetHistoryTotalCount].time = getTime();
this->packetHistory[this->packetHistoryTotalCount].to = mp.to;
this->packetHistory[this->packetHistoryTotalCount].channel = mp.channel;
this->packetHistory[this->packetHistoryTotalCount].from = getFrom(&mp);
this->packetHistory[this->packetHistoryTotalCount].id = mp.id;
this->packetHistory[this->packetHistoryTotalCount].reply_id = p.reply_id;
this->packetHistory[this->packetHistoryTotalCount].emoji = (bool)p.emoji;
this->packetHistory[this->packetHistoryTotalCount].payload_size = p.payload.size;
memcpy(this->packetHistory[this->packetHistoryTotalCount].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
if (this->storageType == StorageType::ST_PSRAM) {
this->packetHistory[this->packetHistoryTotalCount].time = getTime();
this->packetHistory[this->packetHistoryTotalCount].to = mp.to;
this->packetHistory[this->packetHistoryTotalCount].channel = mp.channel;
this->packetHistory[this->packetHistoryTotalCount].from = getFrom(&mp);
this->packetHistory[this->packetHistoryTotalCount].id = mp.id;
this->packetHistory[this->packetHistoryTotalCount].reply_id = p.reply_id;
this->packetHistory[this->packetHistoryTotalCount].emoji = (bool)p.emoji;
this->packetHistory[this->packetHistoryTotalCount].payload_size = p.payload.size;
memcpy(this->packetHistory[this->packetHistoryTotalCount].payload, p.payload.bytes,
meshtastic_Constants_DATA_PAYLOAD_LEN);
} else if (this->storageType == StorageType::ST_SDCARD) {
// Save to SDCARD
#if defined(HAS_SDCARD)
#if defined(ARCH_ESP32) || defined(ARCH_NRF52)
this->packetHistory[0].time = getTime();
this->packetHistory[0].to = mp.to;
this->packetHistory[0].channel = mp.channel;
this->packetHistory[0].from = getFrom(&mp);
this->packetHistory[0].id = mp.id;
this->packetHistory[0].reply_id = p.reply_id;
this->packetHistory[0].emoji = (bool)p.emoji;
this->packetHistory[0].payload_size = p.payload.size;
memcpy(this->packetHistory[0].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
spiLock->lock();
auto handler = SD.open("/storeforward/" + String(this->packetHistoryTotalCount), FILE_WRITE, true);
handler.write((uint8_t *)&this->packetHistory[0], sizeof(PacketHistoryStruct));
handler.close();
spiLock->unlock();
#endif
#endif
} else {
LOG_ERROR("S&F: Unknown storage type");
}
this->packetHistoryTotalCount++;
}
@@ -236,50 +314,108 @@ bool StoreForwardModule::sendPayload(NodeNum dest, uint32_t last_time)
meshtastic_MeshPacket *StoreForwardModule::preparePayload(NodeNum dest, uint32_t last_time, bool local)
{
for (uint32_t i = lastRequest[dest]; i < this->packetHistoryTotalCount; i++) {
if (this->packetHistory[i].time && (this->packetHistory[i].time > last_time)) {
/* Copy the messages that were received by the server in the last msAgo
to the packetHistoryTXQueue structure.
Client not interested in packets from itself and only in broadcast packets or packets towards it. */
if (this->packetHistory[i].from != dest &&
(this->packetHistory[i].to == NODENUM_BROADCAST || this->packetHistory[i].to == dest)) {
if (this->storageType == StorageType::ST_PSRAM) {
meshtastic_MeshPacket *p = allocDataPacket();
if (this->packetHistory[i].time && (this->packetHistory[i].time > last_time)) {
/* Copy the messages that were received by the server in the last msAgo
to the packetHistoryTXQueue structure.
Client not interested in packets from itself and only in broadcast packets or packets towards it. */
if (this->packetHistory[i].from != dest &&
(this->packetHistory[i].to == NODENUM_BROADCAST || this->packetHistory[i].to == dest)) {
p->to = local ? this->packetHistory[i].to : dest; // PhoneAPI can handle original `to`
p->from = this->packetHistory[i].from;
p->id = this->packetHistory[i].id;
p->channel = this->packetHistory[i].channel;
p->decoded.reply_id = this->packetHistory[i].reply_id;
p->rx_time = this->packetHistory[i].time;
p->decoded.emoji = (uint32_t)this->packetHistory[i].emoji;
meshtastic_MeshPacket *p = allocDataPacket();
// Let's assume that if the server received the S&F request that the client is in range.
// TODO: Make this configurable.
p->want_ack = false;
p->to = local ? this->packetHistory[i].to : dest; // PhoneAPI can handle original `to`
p->from = this->packetHistory[i].from;
p->id = this->packetHistory[i].id;
p->channel = this->packetHistory[i].channel;
p->decoded.reply_id = this->packetHistory[i].reply_id;
p->rx_time = this->packetHistory[i].time;
p->decoded.emoji = (uint32_t)this->packetHistory[i].emoji;
// Let's assume that if the server received the S&F request that the client is in range.
// TODO: Make this configurable.
p->want_ack = false;
if (local) { // PhoneAPI gets normal TEXT_MESSAGE_APP
p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
memcpy(p->decoded.payload.bytes, this->packetHistory[i].payload, this->packetHistory[i].payload_size);
p->decoded.payload.size = this->packetHistory[i].payload_size;
if (local) { // PhoneAPI gets normal TEXT_MESSAGE_APP
p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
memcpy(p->decoded.payload.bytes, this->packetHistory[i].payload, this->packetHistory[i].payload_size);
p->decoded.payload.size = this->packetHistory[i].payload_size;
} else {
meshtastic_StoreAndForward sf = meshtastic_StoreAndForward_init_zero;
sf.which_variant = meshtastic_StoreAndForward_text_tag;
sf.variant.text.size = this->packetHistory[i].payload_size;
memcpy(sf.variant.text.bytes, this->packetHistory[i].payload, this->packetHistory[i].payload_size);
if (this->packetHistory[i].to == NODENUM_BROADCAST) {
sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_TEXT_BROADCAST;
} else {
sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_TEXT_DIRECT;
meshtastic_StoreAndForward sf = meshtastic_StoreAndForward_init_zero;
sf.which_variant = meshtastic_StoreAndForward_text_tag;
sf.variant.text.size = this->packetHistory[i].payload_size;
memcpy(sf.variant.text.bytes, this->packetHistory[i].payload, this->packetHistory[i].payload_size);
if (this->packetHistory[i].to == NODENUM_BROADCAST) {
sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_TEXT_BROADCAST;
} else {
sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_TEXT_DIRECT;
}
p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes),
&meshtastic_StoreAndForward_msg, &sf);
}
p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes),
&meshtastic_StoreAndForward_msg, &sf);
lastRequest[dest] = i + 1; // Update the last request index for the client device
return p;
}
lastRequest[dest] = i + 1; // Update the last request index for the client device
return p;
}
} else if (this->storageType == StorageType::ST_SDCARD) {
#if defined(HAS_SDCARD)
#if defined(ARCH_ESP32) || defined(ARCH_NRF52)
spiLock->lock();
auto handler = SD.open("/storeforward/" + String(i), FILE_READ);
if (handler) {
handler.read((uint8_t *)&this->packetHistory[0], sizeof(PacketHistoryStruct));
handler.close();
spiLock->unlock();
if (this->packetHistory[0].time && (this->packetHistory[0].time > last_time)) {
if (this->packetHistory[0].from != dest &&
(this->packetHistory[0].to == NODENUM_BROADCAST || this->packetHistory[0].to == dest)) {
meshtastic_MeshPacket *p = allocDataPacket();
p->to = local ? this->packetHistory[0].to : dest; // PhoneAPI can handle original `to`
p->from = this->packetHistory[0].from;
p->channel = this->packetHistory[0].channel;
p->rx_time = this->packetHistory[0].time;
// Let's assume that if the server received the S&F request that the client is in range.
p->want_ack = false;
if (local) { // PhoneAPI gets normal TEXT_MESSAGE_APP
p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
memcpy(p->decoded.payload.bytes, this->packetHistory[0].payload, this->packetHistory[0].payload_size);
p->decoded.payload.size = this->packetHistory[0].payload_size;
} else {
meshtastic_StoreAndForward sf = meshtastic_StoreAndForward_init_zero;
sf.which_variant = meshtastic_StoreAndForward_text_tag;
sf.variant.text.size = this->packetHistory[0].payload_size;
memcpy(sf.variant.text.bytes, this->packetHistory[0].payload, this->packetHistory[0].payload_size);
if (this->packetHistory[0].to == NODENUM_BROADCAST) {
sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_TEXT_BROADCAST;
} else {
sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_TEXT_DIRECT;
}
p->decoded.payload.size = pb_encode_to_bytes(
p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_StoreAndForward_msg, &sf);
}
lastRequest[dest] = i + 1; // Update the last request index for the client device
return p;
}
}
} else {
spiLock->unlock();
}
#endif
#endif
} else {
LOG_ERROR("S&F: Unknown storage type");
}
}
return nullptr;
@@ -383,7 +519,7 @@ void StoreForwardModule::statsSend(uint32_t to)
*/
ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &mp)
{
#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(HAS_SDCARD)
if (moduleConfig.store_forward.enabled) {
if ((mp.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) && is_server) {
@@ -562,7 +698,7 @@ StoreForwardModule::StoreForwardModule()
ProtobufModule("StoreForward", meshtastic_PortNum_STORE_FORWARD_APP, &meshtastic_StoreAndForward_msg)
{
#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(HAS_SDCARD)
isPromiscuous = true; // Brown chicken brown cow
@@ -613,7 +749,14 @@ StoreForwardModule::StoreForwardModule()
} else {
LOG_INFO("S&F: device doesn't have PSRAM, Disable");
}
#ifdef HAS_SDCARD
// If we have an SDCARD, format it for store&forward use
if (SD.cardType() != CARD_NONE) {
this->populateSDCard();
LOG_INFO("S&F: SDCARD initialized");
is_server = true;
}
#endif
// Client
} else {
is_client = true;

View File

@@ -9,6 +9,11 @@
#include <functional>
#include <unordered_map>
#ifdef HAS_SDCARD
#include "SPILock.h"
#include <SD.h>
#endif
struct PacketHistoryStruct {
uint32_t time;
uint32_t to;
@@ -21,6 +26,9 @@ struct PacketHistoryStruct {
pb_size_t payload_size;
};
// enum for the storage type
enum StorageType { ST_PSRAM, ST_SDCARD };
class StoreForwardModule : private concurrency::OSThread, public ProtobufModule<meshtastic_StoreAndForward>
{
bool busy = 0;
@@ -83,6 +91,10 @@ class StoreForwardModule : private concurrency::OSThread, public ProtobufModule<
private:
void populatePSRAM();
void populateSDCard();
// Storage Type
StorageType storageType = ST_PSRAM;
// S&F Defaults
uint32_t historyReturnMax = 25; // Return maximum of 25 records by default.

View File

@@ -4,6 +4,7 @@
#include "PowerFSM.h"
#include "buzz.h"
#include "configuration.h"
#include "graphics/Screen.h"
TextMessageModule *textMessageModule;
ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp)
@@ -17,7 +18,10 @@ ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp
devicestate.rx_text_message = mp;
devicestate.has_rx_text_message = true;
powerFSM.trigger(EVENT_RECEIVED_MSG);
// Only trigger screen wake if configuration allows it
if (shouldWakeOnReceivedMessage()) {
powerFSM.trigger(EVENT_RECEIVED_MSG);
}
notifyObservers(&mp);
return ProcessMessage::CONTINUE; // Let others look at this message also if they want

View File

@@ -35,6 +35,8 @@ char *configPath = nullptr;
char *optionMac = nullptr;
bool forceSimulated = false;
const char *argp_program_version = optstr(APP_VERSION);
// FIXME - move setBluetoothEnable into a HALPlatform class
void setBluetoothEnable(bool enable)
{
@@ -665,6 +667,21 @@ bool loadConfig(const char *configPath)
settingsStrings[hostMetrics_user_command] = (yamlConfig["HostMetrics"]["UserStringCommand"]).as<std::string>("");
}
if (yamlConfig["Config"]) {
if (yamlConfig["Config"]["DisplayMode"]) {
settingsMap[has_configDisplayMode] = true;
if ((yamlConfig["Config"]["DisplayMode"]).as<std::string>("") == "TWOCOLOR") {
settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR;
} else if ((yamlConfig["Config"]["DisplayMode"]).as<std::string>("") == "INVERTED") {
settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_INVERTED;
} else if ((yamlConfig["Config"]["DisplayMode"]).as<std::string>("") == "COLOR") {
settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_COLOR;
} else {
settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT;
}
}
}
if (yamlConfig["General"]) {
settingsMap[maxnodes] = (yamlConfig["General"]["MaxNodes"]).as<int>(200);
settingsMap[maxtophone] = (yamlConfig["General"]["MaxMessageQueue"]).as<int>(100);

View File

@@ -109,7 +109,9 @@ enum configNames {
mac_address,
hostMetrics_interval,
hostMetrics_channel,
hostMetrics_user_command
hostMetrics_user_command,
configDisplayMode,
has_configDisplayMode
};
enum { no_screen, x11, fb, st7789, st7735, st7735s, st7796, ili9341, ili9342, ili9486, ili9488, hx8357d };
enum { no_touchscreen, xpt2046, stmpe610, gt911, ft5x06 };

View File

@@ -61,7 +61,9 @@ class XModemAdapter
uint16_t packetno = 0;
#if defined(ARCH_NRF52) || defined(ARCH_STM32WL)
#if defined(ARCH_NRF52)
Adafruit_LittleFS_Namespace::File file = Adafruit_LittleFS_Namespace::File(FSCom);
#elif defined(ARCH_STM32WL)
File file = File(FSCom);
#else
File file;

View File

@@ -1,15 +1,15 @@
{
// "USERPREFS_BUTTON_PIN": "36",
"USERPREFS_CHANNELS_TO_WRITE": "2",
// "USERPREFS_CHANNELS_TO_WRITE": "3",
// "USERPREFS_CHANNEL_0_DOWNLINK_ENABLED": "false",
"USERPREFS_CHANNEL_0_NAME": "Sauce",
// "USERPREFS_CHANNEL_0_NAME": "REPLACEME",
// "USERPREFS_CHANNEL_0_PRECISION": "14",
"USERPREFS_CHANNEL_0_PSK": "{ 0x38, 0x4b, 0xbc, 0xc0, 0x1d, 0xc0, 0x22, 0xd1, 0x81, 0xbf, 0x36, 0xb8, 0x61, 0x21, 0xe1, 0xfb, 0x96, 0xb7, 0x2e, 0x55, 0xbf, 0x74, 0x22, 0x7e, 0x9d, 0x6a, 0xfb, 0x48, 0xd6, 0x4c, 0xb1, 0xa1 }",
"USERPREFS_CHANNEL_0_UPLINK_ENABLED": "true",
// "USERPREFS_CHANNEL_0_PSK": "{ 0x38, 0x4b, 0xbc, 0xc0, 0x1d, 0xc0, 0x22, 0xd1, 0x81, 0xbf, 0x36, 0xb8, 0x61, 0x21, 0xe1, 0xfb, 0x96, 0xb7, 0x2e, 0x55, 0xbf, 0x74, 0x22, 0x7e, 0x9d, 0x6a, 0xfb, 0x48, 0xd6, 0x4c, 0xb1, 0xa1 }",
// "USERPREFS_CHANNEL_0_UPLINK_ENABLED": "true",
// "USERPREFS_CHANNEL_1_DOWNLINK_ENABLED": "false",
"USERPREFS_CHANNEL_1_NAME": "Exhibits",
// "USERPREFS_CHANNEL_1_NAME": "NodeChat",
// "USERPREFS_CHANNEL_1_PRECISION": "14",
"USERPREFS_CHANNEL_1_PSK": "{ 0x4e, 0x22, 0x1d, 0x8b, 0xc3, 0x09, 0x1b, 0xe2, 0x11, 0x9c, 0x89, 0x12, 0xf2, 0x25, 0x19, 0x5d, 0x15, 0x3e, 0x30, 0x7b, 0x86, 0xb6, 0xec, 0xc4, 0x6a, 0xc3, 0x96, 0x5e, 0x9e, 0x10, 0x9d, 0xd5 }",
// "USERPREFS_CHANNEL_1_PSK": "{ 0x4e, 0x22, 0x1d, 0x8b, 0xc3, 0x09, 0x1b, 0xe2, 0x11, 0x9c, 0x89, 0x12, 0xf2, 0x25, 0x19, 0x5d, 0x15, 0x3e, 0x30, 0x7b, 0x86, 0xb6, 0xec, 0xc4, 0x6a, 0xc3, 0x96, 0x5e, 0x9e, 0x10, 0x9d, 0xd5 }",
// "USERPREFS_CHANNEL_1_UPLINK_ENABLED": "false",
// "USERPREFS_CHANNEL_2_DOWNLINK_ENABLED": "false",
// "USERPREFS_CHANNEL_2_NAME": "YardSale",
@@ -17,12 +17,12 @@
// "USERPREFS_CHANNEL_2_PSK": "{ 0x15, 0x6f, 0xfe, 0x46, 0xd4, 0x56, 0x63, 0x8a, 0x54, 0x43, 0x13, 0xf2, 0xef, 0x6c, 0x63, 0x89, 0xf0, 0x06, 0x30, 0x52, 0xce, 0x36, 0x5e, 0xb1, 0xe8, 0xbb, 0x86, 0xe6, 0x26, 0x5b, 0x1d, 0x58 }",
// "USERPREFS_CHANNEL_2_UPLINK_ENABLED": "false",
// "USERPREFS_CONFIG_GPS_MODE": "meshtastic_Config_PositionConfig_GpsMode_ENABLED",
"USERPREFS_CONFIG_LORA_IGNORE_MQTT": "true",
// "USERPREFS_CONFIG_LORA_IGNORE_MQTT": "true",
// "USERPREFS_CONFIG_LORA_REGION": "meshtastic_Config_LoRaConfig_RegionCode_US",
// "USERPREFS_CONFIG_OWNER_LONG_NAME": "My Long Name",
// "USERPREFS_CONFIG_OWNER_SHORT_NAME": "MLN",
// "USERPREFS_CONFIG_DEVICE_ROLE": "meshtastic_Config_DeviceConfig_Role_CLIENT", // Defaults to CLIENT. ROUTER*, LOST AND FOUND, and REPEATER roles are restricted.
"USERPREFS_EVENT_MODE": "1",
// "USERPREFS_EVENT_MODE": "1",
// "USERPREFS_FIXED_BLUETOOTH": "121212",
// "USERPREFS_FIXED_GPS": "",
// "USERPREFS_FIXED_GPS_ALT": "0",
@@ -32,16 +32,16 @@
// "USERPREFS_CONFIG_GPS_UPDATE_INTERVAL": "600",
// "USERPREFS_CONFIG_POSITION_BROADCAST_INTERVAL": "1800",
// "USERPREFS_CONFIG_DEVICE_TELEM_UPDATE_INTERVAL": "900", // Device telemetry update interval in seconds
"USERPREFS_LORACONFIG_CHANNEL_NUM": "31",
"USERPREFS_LORACONFIG_MODEM_PRESET": "meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO",
// "USERPREFS_LORACONFIG_CHANNEL_NUM": "31",
// "USERPREFS_LORACONFIG_MODEM_PRESET": "meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST",
// "USERPREFS_USE_ADMIN_KEY_0": "{ 0xcd, 0xc0, 0xb4, 0x3c, 0x53, 0x24, 0xdf, 0x13, 0xca, 0x5a, 0xa6, 0x0c, 0x0d, 0xec, 0x85, 0x5a, 0x4c, 0xf6, 0x1a, 0x96, 0x04, 0x1a, 0x3e, 0xfc, 0xbb, 0x8e, 0x33, 0x71, 0xe5, 0xfc, 0xff, 0x3c }",
// "USERPREFS_USE_ADMIN_KEY_1": "{}",
// "USERPREFS_USE_ADMIN_KEY_2": "{}",
"USERPREFS_OEM_TEXT": "OpenSauce 2025",
"USERPREFS_OEM_FONT_SIZE": "0",
"USERPREFS_OEM_IMAGE_WIDTH": "49",
"USERPREFS_OEM_IMAGE_HEIGHT": "65",
"USERPREFS_OEM_IMAGE_DATA": "{ 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0xfe, 0xc0, 0x03, 0x00, 0x00, 0x80, 0x07, 0xfe, 0x00, 0x3f, 0x00, 0x00, 0xf8, 0x01, 0xfe, 0x00, 0x38, 0x00, 0x00, 0x38, 0x00, 0xfe, 0x00, 0x30, 0x00, 0x00, 0x18, 0x00, 0xfe, 0x00, 0x70, 0x00, 0x00, 0x1c, 0x00, 0xfe, 0x00, 0xe0, 0x03, 0x81, 0x0f, 0x00, 0xfe, 0x00, 0x00, 0x8c, 0x43, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0xfe, 0x00, 0x80, 0x07, 0xc1, 0x03, 0x00, 0xfe, 0x00, 0xf0, 0x01, 0x01, 0x1f, 0x00, 0xfe, 0x00, 0x70, 0x00, 0x01, 0x1c, 0x00, 0xfe, 0x00, 0x30, 0x80, 0x03, 0x18, 0x00, 0xfe, 0x00, 0x3c, 0xf0, 0x0f, 0x78, 0x00, 0xfe, 0x80, 0x0f, 0xf8, 0x3f, 0xe0, 0x03, 0xfe, 0xe0, 0x00, 0xfc, 0x7f, 0x00, 0x0e, 0xfe, 0x00, 0x00, 0xfe, 0xff, 0x01, 0x00, 0xfe, 0x00, 0xf0, 0xff, 0xff, 0x1f, 0x00, 0xfe, 0x00, 0xff, 0xff, 0xff, 0xff, 0x03, 0xfe, 0xf8, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, 0xfc, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, 0xc1, 0xff, 0xff, 0xff, 0xff, 0x87, 0xff, 0x1f, 0xfc, 0xff, 0xff, 0x7f, 0xf0, 0xff, 0x9f, 0xc1, 0xff, 0xff, 0x07, 0x6f, 0xff, 0xbb, 0x07, 0xfc, 0x7f, 0xf0, 0xef, 0xff, 0xb3, 0xef, 0xc1, 0x0f, 0xdf, 0xfe, 0xfe, 0xb3, 0xed, 0x1b, 0xf0, 0xdb, 0x8f, 0xff, 0xb3, 0xed, 0x7b, 0xbc, 0xfb, 0xe0, 0xff, 0xb3, 0x6d, 0xf8, 0xb2, 0x7f, 0xd9, 0xff, 0xb3, 0x6d, 0xd8, 0xfe, 0x70, 0xbc, 0xff, 0xb3, 0x6d, 0xd8, 0x0e, 0x56, 0x7e, 0xff, 0xb3, 0x6f, 0xd8, 0x80, 0x76, 0x72, 0xff, 0xb3, 0x6f, 0xd8, 0xee, 0x75, 0x63, 0xff, 0xb3, 0xef, 0xd9, 0xf6, 0x73, 0x61, 0xff, 0xb3, 0xe1, 0xdb, 0x96, 0x53, 0x61, 0xff, 0xb3, 0xe1, 0xdb, 0x16, 0x77, 0x63, 0xff, 0xb3, 0x61, 0xd8, 0x0e, 0x77, 0x62, 0xff, 0xbf, 0x61, 0xd8, 0x0e, 0x77, 0xb6, 0xff, 0xbf, 0x61, 0xd8, 0x0e, 0x57, 0xbd, 0xff, 0x9c, 0x61, 0xd8, 0x16, 0x57, 0xc3, 0xff, 0x83, 0x61, 0xd8, 0x16, 0x73, 0xff, 0xff, 0x8f, 0xe0, 0xd8, 0xb6, 0x33, 0xff, 0xff, 0xef, 0xe0, 0xdb, 0xee, 0xcd, 0xff, 0xff, 0xeb, 0xc5, 0xdb, 0x1e, 0xfe, 0xc7, 0xff, 0xeb, 0x2d, 0xd8, 0xfe, 0xff, 0xb0, 0xff, 0x6b, 0x2d, 0xc3, 0xfe, 0x1f, 0xa4, 0xff, 0x63, 0xad, 0x07, 0xfe, 0x83, 0xa4, 0xff, 0x66, 0xad, 0xf7, 0x7e, 0x88, 0xa4, 0xff, 0xec, 0xad, 0xf5, 0x0e, 0x89, 0xa4, 0xff, 0xeb, 0xad, 0xb5, 0x0e, 0x89, 0xa4, 0xff, 0xeb, 0xad, 0x35, 0x1e, 0x89, 0x9c, 0xff, 0x6b, 0xad, 0x35, 0x1e, 0x89, 0xc7, 0xff, 0x6f, 0xad, 0x31, 0x1e, 0xf9, 0xf8, 0xff, 0x6f, 0xad, 0x71, 0x1e, 0x1d, 0xff, 0xfe, 0x6c, 0xad, 0xf1, 0xbe, 0xe7, 0x7f, 0xfe, 0x60, 0xad, 0xf5, 0xee, 0xfc, 0x1f, 0xfe, 0x00, 0xbd, 0x35, 0x1e, 0xff, 0x03, 0xfe, 0x00, 0xb8, 0x35, 0xfe, 0x7f, 0x00, 0xfe, 0x00, 0xb0, 0x37, 0xfe, 0x1f, 0x00, 0xfe, 0x00, 0x00, 0x37, 0xfe, 0x03, 0x00, 0xfe, 0x00, 0x00, 0x76, 0xfe, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xf0, 0x1e, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x80, 0x02, 0x00, 0x00, 0xfe }",
// "USERPREFS_OEM_TEXT": "Caterham Car Club",
// "USERPREFS_OEM_FONT_SIZE": "0",
// "USERPREFS_OEM_IMAGE_WIDTH": "50",
// "USERPREFS_OEM_IMAGE_HEIGHT": "28",
// "USERPREFS_OEM_IMAGE_DATA": "{ 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00, 0xC0, 0x07, 0x80, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xC7, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xC7, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0x67, 0x00, 0x00, 0x00, 0x18, 0x1F, 0xF0, 0x67, 0x00, 0x00, 0x00, 0x30, 0x1F, 0xF8, 0x33, 0x00, 0x00, 0x00, 0x30, 0x00, 0xFC, 0x31, 0x00, 0x00, 0x00, 0x60, 0x00, 0xFE, 0x18, 0x00, 0x00, 0x00, 0x60, 0x00, 0x7E, 0x18, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x3F, 0x0C, 0x00, 0x00, 0x00, 0xC0, 0x80, 0x1F, 0x0C, 0x00, 0x00, 0x00, 0x80, 0x81, 0x1F, 0x06, 0x00, 0x00, 0x00, 0x80, 0xC1, 0x0F, 0x06, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x0F, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x0F, 0x03, 0x00, 0x00, 0x00, 0x00, 0xE6, 0x8F, 0x01, 0x00, 0x00, 0x00, 0x00, 0xEE, 0xC7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x00}",
// "USERPREFS_NETWORK_ENABLED_PROTOCOLS": "1", // Enable UDP mesh
// "USERPREFS_NETWORK_WIFI_ENABLED": "true",
// "USERPREFS_NETWORK_WIFI_SSID": "wifi_ssid",
@@ -52,6 +52,8 @@
// "USERPREFS_MQTT_PASSWORD": "large4cats",
// "USERPREFS_MQTT_ENCRYPTION_ENABLED": "true",
// "USERPREFS_MQTT_TLS_ENABLED": "false",
"USERPREFS_MQTT_ROOT_TOPIC": "event/OpenSauce",
"USERPREFS_TZ_STRING": "PST8PDT,M3.2.0,M11.1.0"
// "USERPREFS_MQTT_ROOT_TOPIC": "event/REPLACEME",
// "USERPREFS_RINGTONE_NAG_SECS": "60",
"USERPREFS_RINGTONE_RTTTL": "24:d=32,o=5,b=565:f6,p,f6,4p,p,f6,p,f6,2p,p,b6,p,b6,p,b6,p,b6,p,b,p,b,p,b,p,b,p,b,p,b,p,b,p,b,1p.,2p.,p",
"USERPREFS_TZ_STRING": "tzplaceholder "
}

View File

@@ -6,7 +6,7 @@
// SD card - TODO: test, currently untested, copied from T3S3 variant
#define HAS_SDCARD
#define SDCARD_USE_SPI1
#define SDCARD_USE_HSPI
// TODO: rename this to make this SD-card specific
#define SPI_CS 13
#define SPI_SCK 14

View File

@@ -61,9 +61,8 @@ void setupNicheGraphics()
InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
// Customize default settings
inkhud->persistence->settings.userTiles.maxCount = 2; // Two applets side-by-side
inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
inkhud->persistence->settings.userTiles.maxCount = 2; // Two applets side-by-side
inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery
// Setup backlight controller
// Note: button is attached further down
@@ -71,16 +70,14 @@ void setupNicheGraphics()
backlight->setPin(PIN_EINK_EN);
// Pick applets
// Custom selection for OpenSauce
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
// Disabled by default
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
// Note: order of applets determines priority of "auto-show" feature
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0
// Start running InkHUD
inkhud->begin();
@@ -107,11 +104,11 @@ void setupNicheGraphics()
buttons->setHandlerDown(1, [backlight]() { backlight->peek(); });
buttons->setHandlerLongPress(1, [backlight]() {
backlight->latch();
playBeep();
playBoop();
});
buttons->setHandlerShortPress(1, [backlight]() {
backlight->off();
playBoop();
playChirp();
});
// Begin handling button events

View File

@@ -5,7 +5,7 @@
// #define HAS_SCREEN 0
// #define HAS_SDCARD
// #define SDCARD_USE_SPI1
// #define SDCARD_USE_HSPI
#define USE_SSD1306
#define I2C_SDA 12

View File

@@ -0,0 +1,24 @@
#ifndef Pins_Arduino_h
#define Pins_Arduino_h
#include <stdint.h>
static const uint8_t TX = 21;
static const uint8_t RX = 20;
static const uint8_t SDA = 1;
static const uint8_t SCL = 0;
static const uint8_t SS = 8;
static const uint8_t MOSI = 7;
static const uint8_t MISO = 6;
static const uint8_t SCK = 10;
static const uint8_t A0 = 0;
static const uint8_t A1 = 1;
static const uint8_t A2 = 2;
static const uint8_t A3 = 3;
static const uint8_t A4 = 4;
static const uint8_t A5 = 5;
#endif /* Pins_Arduino_h */

View File

@@ -0,0 +1,61 @@
#ifndef _VARIANT_ESP32C3_SUPER_MINI_
#define _VARIANT_ESP32C3_SUPER_MINI_
/*----------------------------------------------------------------------------
* Headers
*----------------------------------------------------------------------------*/
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// I2C (Wire) & OLED
#define WIRE_INTERFACES_COUNT (1)
#define I2C_SDA (1)
#define I2C_SCL (0)
#define USE_SSD1306
// GPS
#undef GPS_RX_PIN
#undef GPS_TX_PIN
#define GPS_RX_PIN (20)
#define GPS_TX_PIN (21)
// Button
#define BUTTON_PIN (9) // BOOT button
// LoRa
#define USE_LLCC68
#define USE_SX1262
// #define USE_RF95
#define USE_SX1268
#define LORA_DIO0 RADIOLIB_NC
#define LORA_RESET (5)
#define LORA_DIO1 (3)
#define LORA_RXEN (2)
#define LORA_BUSY (4)
#define LORA_SCK (10)
#define LORA_MISO (6)
#define LORA_MOSI (7)
#define LORA_CS (8)
#define SX126X_CS LORA_CS
#define SX126X_DIO1 LORA_DIO1
#define SX126X_BUSY LORA_BUSY
#define SX126X_RESET LORA_RESET
#define SX126X_RXEN LORA_RXEN
#define SX126X_DIO3_TCXO_VOLTAGE (1.8)
#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL
#ifdef __cplusplus
}
#endif
/*----------------------------------------------------------------------------
* Arduino objects - C++ only
*----------------------------------------------------------------------------*/
#endif

View File

@@ -65,19 +65,16 @@ void setupNicheGraphics()
inkhud->persistence->settings.rotation = (driver->height > driver->width ? 1 : 0); // Rotate 90deg to landscape, if needed
inkhud->persistence->settings.userTiles.maxCount = 4;
inkhud->persistence->settings.optionalFeatures.batteryIcon = true;
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
// Pick applets
// Custom selection for OpenSauce
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
// Disabled by default
// Note: order of applets determines priority of "auto-show" feature
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, false, 3); // Default on tile 3
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 2); // Default on tile 2
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1));
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true, false, 1); // Default on tile 1
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet, true, false, 0); // Default on tile 0
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true); // Background
// Start running InkHUD
inkhud->begin();

View File

@@ -96,6 +96,20 @@ board_level = extra
build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} -D PRIVATE_HW -DXIAO_BLE_LEGACY_PINOUT -DEBYTE_E22 -DEBYTE_E22_900M30S
build_unflags = -DGPS_L76K
; Seeed XIAO nRF52840 + EBYTE E22-900M30S - Pinout matching Wio-SX1262 (SKU 113010003)
[env:seeed_xiao_nrf52840_e22_900m30s]
extends = env:seeed_xiao_nrf52840_kit
board_level = extra
build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} -D PRIVATE_HW -DEBYTE_E22 -DEBYTE_E22_900M30S
build_unflags = -DGPS_L76K
; Seeed XIAO nRF52840 + EBYTE E22-900M33S - Pinout matching Wio-SX1262 (SKU 113010003)
[env:seeed_xiao_nrf52840_e22_900m33s]
extends = env:seeed_xiao_nrf52840_kit
board_level = extra
build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} -D PRIVATE_HW -DEBYTE_E22 -DEBYTE_E22_900M33S
build_unflags = -DGPS_L76K
; Seeed XIAO nRF52840 + XIAO Wio SX1262 DIY
[env:seeed-xiao-nrf52840-wio-sx1262]
board = xiao_ble_sense
@@ -127,3 +141,16 @@ build_flags =
-D ARDUINO_USB_MODE=0
-D ARDUINO_USB_CDC_ON_BOOT=1
-I variants/diy/t-energy-s3_e22
; ESP32 C3 Super Mini Development Board
; https://www.espboards.dev/esp32/esp32-c3-super-mini/
[env:esp32c3_super_mini]
extends = esp32c3_base
board = esp32-c3-devkitm-1
build_flags =
${esp32_base.build_flags}
-D PRIVATE_HW
-I variants/diy/esp32c3_super_mini
-D ARDUINO_USB_MODE=1
-D ARDUINO_USB_CDC_ON_BOOT=1
board_level = extra

View File

@@ -1,264 +1,168 @@
#
# XIAO nrf52840/nrf52840 Sense + Ebyte E22-900M30S
<p align="center" style="font-size: 28px;">
Xiao BLE/BLE Sense + Ebyte E22-900M30S
</p>
<p align="center" style="font-size: 20px;">
A step-by-step guide for macOS and Linux
</p>
_A step-by-step guide for macOS and Linux._
## Introduction
This guide will walk you through everything needed to get the Xiao BLE (or BLE Sense) running Meshtastic using an Ebyte E22-900M30S LoRa module. The combination of the E22 with an nRF52840 MCU is desirable because it allows for both very low idle (Rx) power draw <i>and</i> high transmit power. The Xiao BLE is a small but surprisingly well-appointed nRF52840 board, with enough GPIO for most Meshtastic applications and a built-in LiPo charger. The E22, on the other hand, is a famously inscrutable and mysterious beast. It is one of the more readily available LoRa modules capable of transmitting at 30 dBm, and includes an LNA to boost its Rx sensitivity a few dB beyond that of the SX1262. However, its documentation is relatively sparse overall, and seems to merely hint at (or completely omit) several key details regarding its functionality. Thus, much of what follows is a synthesis of my observations and inferences over the course of many hours of trial and error.
This guide will walk you through everything needed to get the XIAO nrf52840 (or XIAO nrf52840 Sense) running Meshtastic using an Ebyte E22-900M30S LoRa module. The combination of the E22 with an nRF52840 MCU is desirable because it allows for both very low idle (Rx) power draw _and_ high transmit power.
<h3>Acknowledgement and friendly disclaimer</h3>
The XIAO nrf52840 is a small but surprisingly well-appointed nRF52840 board, with enough GPIO for most Meshtastic applications and a built-in LiPo charger.
The E22, on the other hand, is a famously inscrutable and mysterious beast. It is one of the more readily available LoRa modules capable of transmitting at 30 dBm, and includes an LNA to boost its Rx sensitivity a few dB beyond that of the SX1262.
However, its documentation is relatively sparse overall, and seems to merely hint at (or completely omit) several key details regarding its functionality. Thus, much of what follows is a synthesis of my observations and inferences over the course of many hours of trial and error.
### Acknowledgement and Friendly Disclaimer
Huge thanks to those in the community who have forged the way with the E22, without whose hard work none of this would have been possible! (thebentern, riddick, rainer_vie, beegee-tokyo, geeksville, caveman99, Der_Bear, PlumRugOfDoom, BigCorvus, and many others.)
<br>
Please take the conclusions here as a tentative work in progress, representing my current (and fairly limited) understanding of the E22 when paired with this particular MCU. It is my hope that this guide will be helpful to others who are interested in trying a DIY Meshtastic build, and also be subject to revision by folks with more experience and better test equipment.
### Obligatory liability disclaimer
### Obligatory Liability Disclaimer
This guide and all associated content is for informational purposes only. The information presented is intended for consumption only by persons having appropriate technical skill and judgement, to be used entirely at their own discretion and risk. The authors of this guide in no way provide any warranty, express or implied, toward the content herein, nor its correctness, safety, or suitability to any particular purpose. By following the instructions in this guide in part or in full, you assume all responsibility for all potential risks, including but not limited to fire, property damage, bodily injury, and death.
### Note
## 1. Wire the board
These instructions assume you are running macOS or Linux, but it should be relatively easy to translate each command for Windows. (In this case, in step 2 below, each line of `xiao_ble.sh` would also need to be converted to the equivalent Windows CLI command and run individually.)
Connecting the E22 to the XIAO nrf52840 is straightforward, but there are a few gotchas to be mindful of.
## 1. Update Bootloader
### On the XIAO nrf52840
The first thing you will need to do is update the Xiao BLE's bootloader. The stock bootloader is functionally very similar to the Adafruit nRF52 UF2 bootloader, but apparently not quite enough so to work with Meshtastic out of the box.
- Pins D4 and D5 are currently mapped to `PIN_WIRE_SDA` and `PIN_WIRE_SCL`, respectively. If you are not using I²C and would like to free up pins D4 and D5 for use as GPIO, `PIN_WIRE_SDA` and `PIN_WIRE_SCL` can be reassigned to any two other unused pin numbers.
- Pins D6 and D7 were originally mapped to the TX and RX pins for serial interface 1 (`PIN_SERIAL1_RX` and `PIN_SERIAL1_TX`) but are currently set to -1 in `variant.h`. If you need to expose a serial interface, you can restore these pins and move e.g. `SX126X_RXEN` to pin 4 or 5 (the opposite should work too).
1. Connect the Xiao BLE to your computer via USB-C.
### On the E22
2. Install `adafruit-nrfutil` by following the instructions <a href="https://github.com/adafruit/Adafruit_nRF52_nrfutil">here</a>.
- There are two options for the E22's `TXEN` pin:
1. It can be connected to the MCU on the pin defined as `SX126X_TXEN` in `variant.h`. In this configuration, the MCU will control Tx/Rx switching "manually". As long as `SX126X_TXEN` and `SX126X_RXEN` are both defined in `variant.h` (and neither is set to `RADIOLIB_NC`), `SX126xInterface.cpp` will initialize the E22 correctly for this mode.
2. Alternately, it can be connected to the E22's `DIO2` pin only, with neither `TXEN` nor `DIO2` being connected to the MCU. In this configuration, the E22 will control Tx/Rx switching automatically. In `variant.h`, as long as `SX126X_TXEN` is defined as `RADIOLIB_NC`, and `SX126X_RXEN` is defined and connected to the E22's `RXEN` pin, and `E22_TXEN_CONNECTED_TO_DIO2` is defined, `SX126xInterface.cpp` will initialize the E22 correctly for this mode. This configuration frees up a GPIO, and presents no drawbacks that I have found.
- Note that any combination other than the two described above will likely result in unexpected behavior. In my testing, some of these other configurations appeared to "work" at first glance, but every one I tried had at least one of the following flaws: weak Tx power, extremely poor Rx sensitivity, or the E22 overheating because TXEN was never pulled low, causing its PA to stay on indefinitely.
- Along the same lines, it is a good idea to check the E22's temperature frequently by lightly touching the shield. If you feel the shield getting hot (i.e. approaching uncomfortable to touch) near pins 1, 2, and 3, something is probably misconfigured; disconnect both the XIAO nrf52840 and E22 from power and double check wiring and pin mapping.
- Whether you opt to let the E22 control Rx and Tx or handle this manually, **the E22's `RXEN` pin must always be connected to the MCU** on the pin defined as `SX126X_RXEN` in `variant.h`.
3. Open a terminal window and navigate to `firmware/variants/xiao_ble` (where `firmware` is the directory into which you have cloned the <a href="https://github.com/meshtastic/firmware">Meshtastic firmware repo</a>).
#### Note
4. Run the following command, replacing `/dev/cu.usbmodem2101` with the serial port your Xiao BLE is connected to:
The default pin mapping in `variant.h` uses "Automatic Tx/Rx switching" mode.
```bash
adafruit-nrfutil --verbose dfu serial --package xiao_nrf52840_ble_bootloader-0.7.0-22-g277a0c8_s140_7.3.0.zip --port /dev/cu.usbmodem2101 -b 115200 --singlebank --touch 1200
```
If you wire your board for Manual Tx/Rx Switching Mode, `SX126X_TXEN` must be defined (`#define #define SX126X_TXEN D6`) in `variants/seeed_xiao_nrf52840_kit/variant.h` in the code block following:
5. If all goes well, the Xiao BLE's red LED should start to pulse slowly, and you should see a new USB storage device called `XIAO-BOOT` appear under `Locations` in Finder.
```c
#ifdef XIAO_BLE_LEGACY_PINOUT
// Legacy xiao_ble variant pinout for third-party SX126x modules e.g. EBYTE E22
```
&nbsp;
### Example Wiring for Automatic Tx/Rx Switching Mode
## 2. PlatformIO Environment Preparation
#### MCU -> E22 Connections
Before building Meshtastic for the Xiao BLE + E22, it is necessary to pull in SoftDevice 7.3.0 and its associated linker script (nrf52840_s140_v7.ld) from Seeed Studio's Arduino core. The `xiao_ble.sh` script does this.
| XIAO nrf52840 pin | variant.h definition | E22 pin | Notes |
| :---------------- | :------------------- | :-------- | :------------------------------------------------------------------------------------------------------------------- |
| D0 | SX126X_CS | 19 (NSS) | |
| D1 | SX126X_DIO1 | 13 (DIO1) | |
| D2 | SX126X_BUSY | 14 (BUSY) | |
| D3 | SX126X_RESET | 15 (NRST) | |
| D7 | SX126X_RXEN | 6 (RXEN) | These pins must still be connected, and `SX126X_RXEN` defined in `variant.h`, otherwise Rx sensitivity will be poor. |
| D8 | PIN_SPI_SCK | 18 (SCK) | |
| D9 | PIN_SPI_MISO | 16 (MISO) | |
| D10 | PIN_SPI_MOSI | 17 (MOSI) | |
1. In your terminal window, run the following command:
```bash
sudo ./xiao_ble.sh
```
&nbsp;
## 3. Build Meshtastic
At this point, you should be able to build the firmware successfully.
1. In VS Code, press `Command Shift P` to bring up the command palette.
2. Search for and run the `Developer: Reload Window` command.
3. Bring up the command palette again with `Command Shift P`. Search for and run the `PlatformIO: Pick Project Environment` command.
4. In the list of environments, select `env:xiao_ble`. PlatformIO may update itself for a minute or two, and should let you know once done.
5. Return to the command palette once again (`Command Shift P`). Search for and run the `PlatformIO: Build` command.
6. PlatformIO will build the project. After a few minutes you should see a green `SUCCESS` message.
&nbsp;
## 4. Wire the board
Connecting the E22 to the Xiao BLE is straightforward, but there are a few gotchas to be mindful of.
- <strong>On the Xiao BLE:</strong>
- Pins D4 and D5 are currently mapped to `PIN_WIRE_SDA` and `PIN_WIRE_SCL`, respectively. If you are not using I²C and would like to free up pins D4 and D5 for use as GPIO, `PIN_WIRE_SDA` and `PIN_WIRE_SCL` can be reassigned to any two other unused pin numbers.
- Pins D6 and D7 were originally mapped to the TX and RX pins for serial interface 1 (`PIN_SERIAL1_RX` and `PIN_SERIAL1_TX`) but are currently set to -1 in `variant.h`. If you need to expose a serial interface, you can restore these pins and move e.g. `SX126X_RXEN` to pin 4 or 5 (the opposite should work too).
- <strong>On the E22:</strong>
- There are two options for the E22's `TXEN` pin:
1. It can be connected to the MCU on the pin defined as `SX126X_TXEN` in `variant.h`. In this configuration, the MCU will control Tx/Rx switching "manually". As long as `SX126X_TXEN` and `SX126X_RXEN` are both defined in `variant.h` (and neither is set to `RADIOLIB_NC`), `SX126xInterface.cpp` will initialize the E22 correctly for this mode.
2. Alternately, it can be connected to the E22's `DIO2` pin only, with neither `TXEN` nor `DIO2` being connected to the MCU. In this configuration, the E22 will control Tx/Rx switching automatically. In `variant.h`, as long as `SX126X_TXEN` is defined as `RADIOLIB_NC`, and `SX126X_RXEN` is defined and connected to the E22's `RXEN` pin, and `E22_TXEN_CONNECTED_TO_DIO2` is defined, `SX126xInterface.cpp` will initialize the E22 correctly for this mode. This configuration frees up a GPIO, and presents no drawbacks that I have found.
- Note that any combination other than the two described above will likely result in unexpected behavior. In my testing, some of these other configurations appeared to "work" at first glance, but every one I tried had at least one of the following flaws: weak Tx power, extremely poor Rx sensitivity, or the E22 overheating because TXEN was never pulled low, causing its PA to stay on indefinitely.
- Along the same lines, it is a good idea to check the E22's temperature frequently by lightly touching the shield. If you feel the shield getting hot (i.e. approaching uncomfortable to touch) near pins 1, 2, and 3, something is probably misconfigured; disconnect both the Xiao BLE and E22 from power and double check wiring and pin mapping.
- Whether you opt to let the E22 control Rx and Tx or handle this manually, <strong>the E22's `RXEN` pin must always be connected to the MCU</strong> on the pin defined as `SX126X_RXEN` in `variant.h`.
<h3>Note</h3>
The default pin mapping in `variant.h` uses 'automatic Tx/Rx switching' mode. If you wire your board for manual Rx/Tx switching, make sure to update `variant.h` accordingly by commenting/uncommenting the necessary lines in the 'E22 Tx/Rx control options' section.
&nbsp;
---
&nbsp;
<h3>Example wiring for "E22 automatic Tx/Rx switching" mode:</h3>
&nbsp;
<strong>MCU -> E22 connections</strong>
| Xiao BLE pin | variant.h definition | E22 pin | Notes |
| :----------- | :------------------- | :-------- | :------------------------------------------------------------------------------------------------------------------- |
| D0 | SX126X_CS | 19 (NSS) | |
| D1 | SX126X_DIO1 | 13 (DIO1) | |
| D2 | SX126X_BUSY | 14 (BUSY) | |
| D3 | SX126X_RESET | 15 (NRST) | |
| D7 | SX126X_RXEN | 6 (RXEN) | These pins must still be connected, and `SX126X_RXEN` defined in `variant.h`, otherwise Rx sensitivity will be poor. |
| D8 | PIN_SPI_SCK | 18 (SCK) | |
| D9 | PIN_SPI_MISO | 16 (MISO) | |
| D10 | PIN_SPI_MOSI | 17 (MOSI) | |
&nbsp;
&nbsp;
<strong>E22 -> E22 connections:</strong>
#### E22 -> E22 Connections
| E22 pin | E22 pin | Notes |
| :------ | :------ | :------------------------------------------------------------------------ |
| TXEN | DIO2 | These must be physically connected for automatic Tx/Rx switching to work. |
<h3>Note</h3>
#### Note
The schematic (`xiao-ble-e22-schematic.png`) in the `eagle-project` directory uses this wiring.
&nbsp;
### Example Wiring for Manual Tx/Rx Switching Mode
---
#### MCU -> E22 Connections
&nbsp;
| XIAO nrf52840 pin | variant.h definition | E22 pin | Notes |
| :---------------- | :------------------- | :-------- | :---- |
| D0 | SX126X_CS | 19 (NSS) | |
| D1 | SX126X_DIO1 | 13 (DIO1) | |
| D2 | SX126X_BUSY | 14 (BUSY) | |
| D3 | SX126X_RESET | 15 (NRST) | |
| D6 | SX126X_TXEN | 7 (TXEN) | |
| D7 | SX126X_RXEN | 6 (RXEN) | |
| D8 | PIN_SPI_SCK | 18 (SCK) | |
| D9 | PIN_SPI_MISO | 16 (MISO) | |
| D10 | PIN_SPI_MOSI | 17 (MOSI) | |
<h3>Example wiring for "Manual Tx/Rx switching" mode:</h3>
#### E22 -> E22 connections
<strong>MCU -> E22 connections</strong>
_(none)_
| Xiao BLE pin | variant.h definition | E22 pin | Notes |
| :----------- | :------------------- | :-------- | :---- |
| D0 | SX126X_CS | 19 (NSS) | |
| D1 | SX126X_DIO1 | 13 (DIO1) | |
| D2 | SX126X_BUSY | 14 (BUSY) | |
| D3 | SX126X_RESET | 15 (NRST) | |
| D6 | SX126X_TXEN | 7 (TXEN) | |
| D7 | SX126X_RXEN | 6 (RXEN) | |
| D8 | PIN_SPI_SCK | 18 (SCK) | |
| D9 | PIN_SPI_MISO | 16 (MISO) | |
| D10 | PIN_SPI_MOSI | 17 (MOSI) | |
## 2. Build Meshtastic
<strong>E22 -> E22 connections:</strong> (none)
1. Follow the [Building Meshtastic Firmware](https://meshtastic.org/docs/development/firmware/build/) documentation, stop after **Build****Step 2**
2. For **Build****Step 3**, select `xiao_ble` as your target
3. Adjust source code if you:
- Wired your board for Manual Tx/Rx Switching Mode: see [Wire the Board](#1-wire-the-board)
- Used an E22-900M33S module
(this step is important to avoid **damaging the power amplifier** in the M33S module and **transmitting power above legal limits**!):
1. Open `variants/diy/platformio.ini`
2. Search for `[env:xiao_ble]`
3. In the line starting with `build_flags` within this section, change `-DEBYTE_E22_900M30S` to `-DEBYTE_E22_900M33S`
4. Follow **Build****Step 4** to build the firmware
5. Stop here, because the **PlatformIO: Upload** step does not work for factory-fresh XIAO nrf52840 (the automatic reset to bootloader only works if Meshtastic firmware is already running)
6. The built `firmware.uf2` binary can be found in the folder `.pio/build/xiao_ble/firmware.uf2` (relative to where you cloned the Git repository to), we will need it for [flashing the firmware](#3-flash-the-firmware-to-the-xiao-nrf52840) (manually)
&nbsp;
## 3. Flash the Firmware to the XIAO nrf52840
## 5. Flash the firmware to the Xiao BLE
1. Double press the XIAO nrf52840's `reset` button to put it in bootloader mode, and a USB volume named `XIAO SENSE` will appear
2. Copy the `firmware.uf2` file to the `XIAO SENSE` volume (refer to the last step of [Build Meshtastic](#2-build-meshtastic))
3. The XIAO nrf52840's red LED will flash for several seconds as the firmware is copied
4. Once Meshtastic firmware succesfully boots, the:
1. Green LED will turn on
2. Red LED will flash several times to indicate flash memory writes during initial settings file creation
3. Green LED will blink every second once the firmware is running normally
5. If you do not see the above LED patters, proceed to [Troubleshooting](#4-troubleshooting)
1. Double press the Xiao's `reset` button to put it in bootloader mode.
2. In a terminal window, navigate to the Meshtastic firmware repo's root directory, and from there to `.pio/build/xiao_ble`.
3. Convert the generated `.hex` file into a `.uf2` file:
```bash
../../../bin/uf2conv.py firmware.hex -c -o firmware.uf2 -f 0xADA52840
```
4. Copy the new `.uf2` file to the Xiao's mass storage volume:
```bash
cp firmware.uf2 /Volumes/XIAO-BOOT
```
5. The Xiao's red LED will flash for several seconds as the firmware is copied.
6. Once the firmware is copied, to verify it is running, run the following command:
```bash
meshtastic --noproto
```
7. Then, press the Xiao's `reset` button again. You should see a lot of debug output logged in the terminal window.
&nbsp;
## 6. Troubleshooting
- If after flashing Meshtastic, the Xiao is bootlooped, look at the serial output (you can see this by running `meshtastic --noproto` with the device connected to your computer via USB).
## 4. Troubleshooting
- If after flashing Meshtastic, the XIAO is bootlooped, look at the serial output (you can see this by running `meshtastic --noproto` with the device connected to your computer via USB).
- If you see that the SX1262 init result was -2, this likely indicates a wiring problem; double check your wiring and pin mapping in `variant.h`.
- If you see an error mentioning tinyFS, this may mean you need to reformat the Xiao's storage:
1. Double press the `reset` button to put the Xiao in bootloader mode.
2. In a terminal window, navigate to the Meshtastic firmware repo's root directory, and from there to `variants/xiao_ble`.
3. Run the following command: &nbsp;`cp xiao-ble-internal-format.uf2 /Volumes/XIAO-BOOT`
4. The Xiao's red LED will flash briefly as the filesystem format firmware is copied.
5. Run the following command: &nbsp;`meshtastic --noproto`
6. In the output of the above command, you should see a message saying "Formatting...done".
7. To flash Meshtastic again, repeat the steps in section 5 above.
- If you see an error mentioning tinyFS, this may mean you need to reformat the XIAO's storage:
1. Open the [Meshtastic web flasher](https://flasher.meshtastic.org/)
2. Select the **_Seeed XIAO NRF52840 Kit_**
3. Click the **_trash can icon_** to the right of **_Flash_**
4. Follow the instructions on the screen
**Do not flash the Seeed XIAO NRF52840 Kit firmware** if you have wired the LoRa module according to this variant, as the Seeed XIAO NRF52840 Kit uses different wiring for the SX1262 LoRa chip
- If you don't see any specific error message, but the boot process is stuck or not proceeding as expected, this might also mean there is a conflict in `variant.h`. If you have made any changes to the pin mapping, ensure they do not result in a conflict. If all else fails, try reverting your changes and using the known-good configuration included here.
- The above might also mean something is wired incorrectly. Try reverting to one of the known-good example wirings in section 4.
- If the E22 gets hot to the touch:
- The power amplifier is likely running continually. Disconnect it and the Xiao from power immediately, and double check wiring and pin mapping. In my experimentation this occurred in cases where TXEN was inadvertenly high (usually due to a pin mapping conflict).
- The power amplifier is likely running continually. Disconnect it and the XIAO from power immediately, and double check wiring and pin mapping. In my experimentation this occurred in cases where TXEN was inadvertenly high (usually due to a pin mapping conflict).
&nbsp;
## 5. Notes
## 7. Notes
- **Transmit Power**
- There is a power amplifier after the SX1262's Tx, so the actual Tx power is just over 7 dB greater than the SX1262's set Tx power (the E22-900M30S actually tops out just over 29dB at 5V according to the datasheet)
- Meshtastic firmware is aware of the gain of the E22-900M30S module, so the Meshtastic clients' Tx power setting reflects the actual output power, i.e. setting 30 dBm in the Meshtastic app programs the E22 module to correctly output 30 dBm, setting 24 dBm will output 24 dBm, etc.
- **Adequate 5V Power Supply to the E22 Module**
- Have a bypass capacitor from its 5V supply to ground; 100 µF works well
- Voltage must be between 5V5.5V, lower supply voltage results in less output power; for example, with a fully charged LiPo at 4.2V, Tx power appears to max out around 26-27 dBm
- There are several anecdotal recommendations regarding the Tx power the E22's internal SX1262 should be set to in order to achieve the advertised output of 30 dBm, ranging from 4 (per <a href="https://github.com/jgromes/RadioLib/wiki/High-power-Radio-Modules-Guide">this article</a> in the RadioLib github repo) to 22 (per <a href="https://discord.com/channels/867578229534359593/871539930852130866/976472577545490482">this conversation</a> from the Meshtastic Discord). When paired with the Xiao BLE in the configurations described above, I observed that the output is at its maximum when Tx power is set to 22.
### Additional Reading
- To achieve its full output, the E22 should have a bypass capacitor from its 5V supply to ground. 100 µF works well.
- [S5NC/CDEBYTE_Modules](https://github.com/S5NC/CDEBYTE_Modules) has additional information about EBYTE E22 modules' internal workings, including photographs
- [RadioLib High power Radio Modules Guide](https://github.com/jgromes/RadioLib/wiki/High-power-Radio-Modules-Guide)
- The E22 will happily run on voltages lower than 5V, but the full output power will not be realized. For example, with a fully charged LiPo at 4.2V, Tx power appears to max out around 26-27 dBm.
&nbsp;
## 8. Testing Methodology
## 6. Testing Methodology
During what became a fairly long trial-and-error process, I did a lot of careful testing of Tx power and Rx sensitivity. My methodology in these tests was as follows:
- All tests were conducted between two nodes:
1. The Xiao BLE + E22 coupled with an <a href="https://www.digikey.com/en/products/detail/abracon-llc/ARRKP4065-S915A/8593263">Abracon ARRKP4065-S915A</a> ceramic patch antenna
2. A RAK 5005/4631 coupled with a <a href="https://www.streakwave.com/laird-technologies-ma9-5n-55dbi-900mhz-mobile-omni-select-mount">Laird MA9-5N</a> antenna via a 4" U.FL to Type N pigtail.
1. The XIAO nrf52840 + E22 coupled with an [Abracon ARRKP4065-S915A](https://www.digikey.com/en/products/detail/abracon-llc/ARRKP4065-S915A/8593263") ceramic patch antenna
2. A RAK 5005/4631 coupled with a [Laird MA9-5N](https://www.streakwave.com/laird-technologies-ma9-5n-55dbi-900mhz-mobile-omni-select-mount) antenna via a 4" U.FL to Type N pigtail.
- No other nodes were powered up onsite or nearby.
<br>
- Each node and its antenna was kept in exactly the same position and orientation throughout testing.
- Other environmental factors (e.g. the location and resting position of my body in the room while testing) were controlled as carefully as possible.
- Each test comprised at least five (and often ten) runs, after which the results were averaged.
- All testing was done by sending single-character messages between nodes and observing the received RSSI reported in the message acknowledgement. Messages were sent one by one, waiting for each to be acknowledged or time out before sending the next.
- The E22's Tx power was observed by sending messages from the RAK to the Xiao BLE + E22 and recording the received RSSI.
- The opposite was done to observe the E22's Rx sensitivity: messages were sent from the Xiao BLE + E22 to the RAK, and the received RSSI was recorded.
While this cannot match the level of accuracy achievable with actual test equipment in a lab setting, it was nonetheless sufficient to demonstrate the (sometimes very large) differences in Tx power and Rx sensitivity between various configurations.
- The E22's Tx power was observed by sending messages from the RAK to the XIAO nrf52840 + E22 and recording the received RSSI.
- The opposite was done to observe the E22's Rx sensitivity: messages were sent from the XIAO nrf52840 + E22 to the RAK, and the received RSSI was recorded.
While this cannot match the level of accuracy achievable with actual test equipment in a lab setting, it was nonetheless sufficient to demonstrate the (sometimes very large) differences in Tx power and Rx sensitivity between various configurations.

View File

@@ -70,7 +70,7 @@
#endif
#define HAS_SDCARD // Have SPI interface SD card slot
#define SDCARD_USE_SPI1
#define SDCARD_USE_HSPI
#define LORA_RESET 3
#define LORA_SCK 12

View File

@@ -66,20 +66,16 @@ void setupNicheGraphics()
inkhud->persistence->settings.rotation = (driver->height > driver->width ? 1 : 0); // Rotate 90deg to landscape, if needed
inkhud->persistence->settings.userTiles.maxCount = 4;
inkhud->persistence->settings.optionalFeatures.batteryIcon = true;
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
// Pick applets
// Custom selection for OpenSauce
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
// Disabled by default
// Note: order of applets determines priority of "auto-show" feature
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
// Background
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, false, 3); // Default on tile 3
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 2); // Default on tile 2
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1));
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true, false, 1); // Default on tile 1
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet, true, false, 0); // Default on tile 0
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true); // Background
// Start running InkHUD
inkhud->begin();

View File

@@ -59,19 +59,16 @@ void setupNicheGraphics()
inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
inkhud->persistence->settings.optionalMenuItems.nextTile = true;
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
// Pick applets
// Custom selection for OpenSauce
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
// Disabled by default
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
// Note: order of applets determines priority of "auto-show" feature
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0
// Start running InkHUD
inkhud->begin();

View File

@@ -78,20 +78,17 @@ void setupNicheGraphics()
inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle?
inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
inkhud->persistence->settings.optionalMenuItems.nextTile = false; // Behavior handled by aux button instead
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
inkhud->persistence->settings.optionalMenuItems.nextTile = false; // Behavior handled by aux button instead
// Pick applets
// Custom selection for OpenSauce
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
// Disabled by default
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
// Note: order of applets determines priority of "auto-show" feature
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0
// Start running InkHUD
inkhud->begin();
@@ -110,7 +107,7 @@ void setupNicheGraphics()
buttons->setWiring(1, PIN_BUTTON2);
buttons->setHandlerShortPress(1, [inkhud]() {
inkhud->nextTile();
playBoop();
playChirp();
});
// Begin handling button events

View File

@@ -75,20 +75,17 @@ void setupNicheGraphics()
inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle?
inkhud->persistence->settings.rotation = 1; // 90 degrees clockwise
inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
inkhud->persistence->settings.optionalMenuItems.nextTile = false; // Behavior handled by aux button instead
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
inkhud->persistence->settings.optionalMenuItems.nextTile = false; // Behavior handled by aux button instead
// Pick applets
// Custom selection for OpenSauce
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
// Disabled by default
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
// Note: order of applets determines priority of "auto-show" feature
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0
// Start running InkHUD
inkhud->begin();
@@ -107,7 +104,7 @@ void setupNicheGraphics()
buttons->setWiring(1, PIN_BUTTON2);
buttons->setHandlerShortPress(1, [inkhud]() {
inkhud->nextTile();
playBoop();
playChirp();
});
// Begin handling button events

View File

@@ -77,18 +77,16 @@ void setupNicheGraphics()
inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle?
inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
// Pick applets
// Custom selection for OpenSauce
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
// Disabled by default
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
// Note: order of applets determines priority of "auto-show" feature
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0
// Start running InkHUD
inkhud->begin();

View File

@@ -4,7 +4,7 @@
// #define HAS_SCREEN 0
// #define HAS_SDCARD
// #define SDCARD_USE_SPI1
// #define SDCARD_USE_HSPI
// #define USE_SSD1306

View File

@@ -20,6 +20,8 @@ lib_deps =
rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2
beegee-tokyo/RAK12035_SoilMoisture@^1.0.4
https://github.com/RAKWireless/RAK12034-BMX160/archive/dcead07ffa267d3c906e9ca4a1330ab989e957e2.zip
https://github.com/Woutvstk/SdFat_wrapper25.git#6f8f48d56c15cbeac753560dfeede4a487f81f4c
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds

View File

@@ -113,11 +113,14 @@ static const uint8_t AREF = PIN_AREF;
* SPI Interfaces
*/
#define SPI_INTERFACES_COUNT 2
#define SPI_32MHZ_INTERFACE 0 // 0: use SPIM3 for SPI and SPIM2 for SPI1; 1: the opposite
// SPI pins for SX1262
#define PIN_SPI_MISO (45)
#define PIN_SPI_MOSI (44)
#define PIN_SPI_SCK (43)
// SPI1 pins for external(rak4630) spi (incl. SDCard)
#define PIN_SPI1_MISO (29) // (0 + 29)
#define PIN_SPI1_MOSI (30) // (0 + 30)
#define PIN_SPI1_SCK (3) // (0 + 3)
@@ -127,6 +130,19 @@ static const uint8_t MOSI = PIN_SPI_MOSI;
static const uint8_t MISO = PIN_SPI_MISO;
static const uint8_t SCK = PIN_SPI_SCK;
// SD card SPI pin definitions
#define HAS_SDCARD 1
#define SDCARD_USE_SPI1 1
#define SDCARD_CS (26)
// Some settings for the SdFat library to optimize flash usage
#define SDFAT_FILE_TYPE 1 // only support FAT16/FAT32, not exFAT
#define CHECK_FLASH_PROGRAMMING \
0 // this reduces flash usage but may cause higher power usage when sd card is idle TODO:Check if power usage is higher
#define MAINTAIN_FREE_CLUSTER_COUNT 1 // maintain free cluster count
/*
* eink display pins
*/

View File

@@ -67,7 +67,6 @@ void setupNicheGraphics()
inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery
inkhud->persistence->settings.optionalMenuItems.backlight = true; // Until proves capacitive button works by touching it
inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
// Setup backlight controller
// Note: AUX button attached further down
@@ -75,16 +74,14 @@ void setupNicheGraphics()
backlight->setPin(PIN_EINK_EN);
// Pick applets
// Custom selection for OpenSauce
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
// Disabled by default
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
// Note: order of applets determines priority of "auto-show" feature
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0
inkhud->persistence->settings.rotation = 1;
// inkhud->persistence->printSettings(&inkhud->persistence->settings);

View File

@@ -1,4 +1,4 @@
; Seeed Xiao BLE: https://www.digikey.com/en/products/detail/seeed-technology-co-ltd/102010448/16652893
; Seeed Xiao BLE: https://wiki.seeedstudio.com/XIAO_BLE/
[env:seeed_xiao_nrf52840_kit]
extends = nrf52840_base
board = xiao_ble_sense

View File

@@ -68,6 +68,7 @@
#define TB_LEFT 1
#define TB_RIGHT 2
#define TB_PRESS 0 // BUTTON_PIN
#define TB_DIRECTION FALLING
// microphone
#define ES7210_SCK 47

View File

@@ -62,11 +62,10 @@ void setupNicheGraphics()
InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
// Customize default settings
inkhud->persistence->settings.userTiles.maxCount = 2; // Two applets side-by-side
inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery
inkhud->persistence->settings.optionalMenuItems.backlight = true; // Until proves capacitive button works by touching it
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
inkhud->persistence->settings.userTiles.maxCount = 2; // Two applets side-by-side
inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery
inkhud->persistence->settings.optionalMenuItems.backlight = true; // Until proves capacitive button works by touching it
// Setup backlight controller
// Note: AUX button attached further down
@@ -74,16 +73,14 @@ void setupNicheGraphics()
backlight->setPin(PIN_EINK_EN);
// Pick applets
// Custom selection for OpenSauce
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
// Disabled by default
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
// Note: order of applets determines priority of "auto-show" feature
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0
// Start running InkHUD
inkhud->begin();

View File

@@ -56,7 +56,7 @@
#define GPS_1PPS_PIN 6
#define HAS_SDCARD // Have SPI interface SD card slot
#define SDCARD_USE_SPI1
#define SDCARD_USE_HSPI
// PCF8563 RTC Module
// #define PCF8563_RTC 0x51 //Putting definitions in variant. h does not compile correctly

View File

@@ -59,19 +59,16 @@ void setupNicheGraphics()
inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle?
inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
// Pick applets
// Custom selection for OpenSauce
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
// Disabled by default
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
// Note: order of applets determines priority of "auto-show" feature
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0
// Start running InkHUD
inkhud->begin();

View File

@@ -1,5 +1,5 @@
#define HAS_SDCARD
#define SDCARD_USE_SPI1
#define SDCARD_USE_HSPI
// Display (E-Ink)
#define PIN_EINK_CS 15

View File

@@ -1,5 +1,5 @@
#define HAS_SDCARD
#define SDCARD_USE_SPI1
#define SDCARD_USE_HSPI
#define USE_SSD1306
@@ -76,4 +76,4 @@
#endif
#define HAS_SDCARD // Have SPI interface SD card slot
#define SDCARD_USE_SPI1
#define SDCARD_USE_HSPI

View File

@@ -3,6 +3,11 @@ extends = esp32_base
board = ttgo-lora32-v21
board_check = true
build_flags =
${esp32_base.build_flags} -D TLORA_V2_1_16 -I variants/tlora_v2_1_16
${esp32_base.build_flags}
-D TLORA_V2_1_16
-I variants/tlora_v2_1_16
-DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
upload_speed = 115200
-DRADIOLIB_EXCLUDE_SX128X=1
-DRADIOLIB_EXCLUDE_SX126X=1
-DRADIOLIB_EXCLUDE_LR11X0=1
upload_speed = 115200

View File

@@ -22,4 +22,15 @@
#define LORA_DIO1 33 // https://www.thethingsnetwork.org/forum/t/big-esp32-sx127x-topic-part-3/18436
#endif
#define LORA_DIO2 32 // Not really used
#define LORA_DIO2 32 // Not really used
/*
* Use SD Card for Store and Forward
*/
#define HAS_SDCARD
#define SDCARD_USE_HSPI
#define SPI_MOSI 15
#define SPI_MISO 2
#define SPI_SCK 14
#define SPI_CS 13
#define SDCARD_CS SPI_CS

View File

@@ -51,6 +51,9 @@
#undef GPS_RX_PIN
#undef GPS_TX_PIN
// #define HAS_SDCARD 1 // causes hang if defined
#define SDCARD_USE_HSPI
#define SD_SPI_FREQUENCY 25000000
#define SDCARD_CS 43

View File

@@ -1,4 +1,4 @@
[VERSION]
major = 2
minor = 7
build = 2
build = 3