Compare commits

...
Author SHA1 Message Date
Ben Meadors d4b3e0616b Use const references and local context 2026-04-03 06:18:24 -05:00
Ben Meadors d4b627a60c Implement batched NodeInfo encoding for more efficient large NodeDB transfer 2026-04-02 14:19:50 -05:00
Tom efd2613bd7 feat: add new configuration files for LR11xx variants (#9761)
* feat: add new configuration files for LR11xx variants

* style: reformat RF switch mode table for improved readability
2026-04-01 10:46:27 +11:00
TomandBen Meadors f88bc732cc Improved manual build flow to make it easier (#8839)
* Improved flow to make easier

The emojis are intentional! I had minimal LLM input!!!

* try and fix input variable sanitisation

* and again

* again

* Copilot fixed it for me

* copilot didn't fix it for me

* copilot might have fixed it and I broke it by copypasting

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-31 07:53:59 -05:00
Ben Meadors 0746f8cfff Merge remote-tracking branch 'origin/master' into develop 2026-03-30 14:37:29 -05:00
Austin 4e05f20c58 Renovate: Don't update branches outside the schedule (daily) (#10039)
Renovate is still running after each merge... maybe this will fix it
2026-03-30 14:36:59 -05:00
Ben Meadors 0ac95e370b Merge remote-tracking branch 'origin/master' into develop 2026-03-30 14:10:25 -05:00
Austin 33d526f6f9 Set t5s3_epaper_inkhud to extra (#10037)
t5s3_epaper_inkhud is incomplete (missing macros). Disable it for now.

Also fixes the board_check evaluation so that `false` actually works.
2026-03-30 13:30:54 -05:00
Austin 0abd3cdde8 ESP32: Take away "tbeam" boards PSRAM to reclaim iram (#10036)
You read that right! Disable PSRAM on the ttgo-tbeam board to reclaim *iram*.

ttgo-tbeam.json is a copy of https://github.com/platformio/platform-espressif32/blob/v6.13.0/boards/ttgo-t-beam.json with HAS_PSRAM removed.

PSRAM support on OG-ESP32 involves a lot of iram usage thanks to iram-heavy workarounds that have been added to address hardware bugs.

See https://docs.espressif.com/projects/esp-idf/en/v4.4/esp32/api-guides/performance/ram-usage.html#optimizing-iram-usage
2026-03-30 13:20:19 -05:00
Austin 870ba80092 wio-sdk-wm1110: inherit build_unflags (#10034) 2026-03-30 10:14:43 -05:00
TomandBen Meadors 2955c12d2a Configure NFC pins as GPIO for older bootloaders (#10016)
* Configure NFC pins as GPIO for older bootloaders

Should hopefully solve the issue in https://github.com/meshtastic/firmware/issues/9986

* Fix formatting of CONFIG_NFCT_PINS_AS_GPIOS flag in platformio.ini

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-30 09:06:09 -05:00
TomandBen Meadors 283ccb83d1 Configure NFC pins as GPIO for older bootloaders (#10016)
* Configure NFC pins as GPIO for older bootloaders

Should hopefully solve the issue in https://github.com/meshtastic/firmware/issues/9986

* Fix formatting of CONFIG_NFCT_PINS_AS_GPIOS flag in platformio.ini

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-30 09:03:10 -05:00
Philip LykovandBen Meadors 5319bc7c2c Fix W5100S socket exhaustion blocking MQTT and additional TCP clients (#9770)
The W5100S Ethernet chip has only 4 hardware sockets. On RAK4631
Ethernet gateways with syslog and NTP enabled, all 4 sockets were
permanently consumed (NTP UDP + Syslog UDP + TCP API listener + TCP
API client), leaving none for MQTT, DHCP lease renewal, or additional
TCP connections.

- NTP: Remove permanent timeClient.begin() at startup; NTPClient::update()
  auto-initializes when needed. Add timeClient.end() after each query to
  release the UDP socket immediately.
- Syslog: Remove socket allocation from Syslog::enable(). Open and close
  the UDP socket on-demand in _sendLog() around each message send.
- MQTT: Fix socket leak in isValidConfig() where a successful test
  connection was never closed (PubSubClient destructor does not call
  disconnect). Add explicit pubSub->disconnect() before returning.

Made-with: Cursor

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-30 07:12:23 -05:00
github-actions[bot]andvidplace7 198339873a Upgrade trunk (#10033)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-03-30 06:29:40 -05:00
Ben Meadors db694f2f24 Merge pull request #10031 from meshtastic/master
Backmerge master -> develop
2026-03-29 15:01:39 -05:00
Austin Lane 29d70853e6 Renovate: Run once daily (not upon EVERY change)
Cut down on the spammin! Give the PIO Registry a break!
2026-03-29 14:45:52 -04:00
Austin Lane 976d201fc1 Hack: Use re-uploaded framework-arduinoespressif32 on ESP32c6
Dirty hack to keep ESP32c6 building.

Remove when updating to pioarduino
2026-03-29 13:47:59 -04:00
Austin Lane 80763ca235 Move core lib_deps to github zips
PlatformIO registry is (probably) rate limiting us. Mitigate by switching to GitHub source zips (based upon tags).

This change does not include any updates, simply swapping to a new download location.
2026-03-29 12:35:04 -04:00
renovate[bot] 844f5b46d0 Update meshtastic/device-ui digest to 7b1485b (#10023)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-29 10:55:47 -04:00
Ben Meadors 814f1289f6 Merge remote-tracking branch 'origin/master' into develop 2026-03-28 07:26:46 -05:00
ManuelandBen Meadors aec98b61b9 MUI: WiFi map tile download: heltec V4 adaptations (#10011)
* rotated MUI

* mui-maps heltec-v4 adaptations

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-27 15:40:07 -05:00
ManuelandBen Meadors 0b7556b590 MUI: WiFi map tile download: heltec V4 adaptations (#10011)
* rotated MUI

* mui-maps heltec-v4 adaptations

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-27 15:39:26 -05:00
Ben Meadors 99abfebc4a Fix TransmitHistory to improve epoch handling (#10017)
* Fix TransmitHistory to improve epoch handling

* Enable epoch handling in unit tests

* Improve comments and test handling for epoch persistence in TransmitHistory

* Add boot-relative timestamp handling and unit tests for TransmitHistory

* loadFromDisk should handle legacy entries and clean up old v1 files after migration

* Revert "loadFromDisk should handle legacy entries and clean up old v1 files after migration"

This reverts commit eb7e5c7acfa4ac077fe50980be752e4b42a739b8.

* Add NodeInfoModule integration for RTC quality changes and trigger immediate checks

* Update test conditions for RTC quality checks
2026-03-27 15:38:41 -05:00
Austin 068f5af4d8 Add meshtasticd config metadata (#10001)
To be consumed by config-picker utils, and hopefully meshtasticd itself in the future.
2026-03-27 11:38:49 -05:00
Ben Meadors f7e4ac3e43 Merge remote-tracking branch 'origin/master' into develop 2026-03-27 08:43:19 -05:00
Ethac.chenandBen Meadors c36ae159ed Fix rak_wismeshtag low‑voltage reboot hang after App configuration (#9897)
* Fix TAG low‑voltage reboot hang after App configuration

* nRF52: Move low-VDD System OFF logic to variant hook

* Addressed review

* serialize SAADC access with shared mutex for VDD and battery reads

* raise LPCOMP wake threshold to ensure rising-edge wake

* Trunk fmt

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-27 06:56:19 -05:00
Chloe Bethel 993c242202 Supporting STM32WL is like squeezing blood from a stone (#10015) 2026-03-27 06:53:49 -05:00
Chloe Bethel 33e7f16c05 Supporting STM32WL is like squeezing blood from a stone (#10015) 2026-03-27 06:52:00 -05:00
github-actions[bot]andvidplace7 3d4f0b895b Upgrade trunk (#9978)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-03-27 06:35:56 -05:00
renovate[bot] 3b29eea577 Update Adafruit_BME680 to v2.0.6 (#10009)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-26 06:45:48 -05:00
Austin e7e34e86d5 Dependencies: Remove all fuzzy-matches, spot-add renovate (#10008) 2026-03-26 06:16:08 -05:00
Austin 5a009889cc Deps: Cleanup LewisHe library references (#10007)
We cache and dedupe our dependencies, referring to them with multiple methods/urls is just noise.

```
lewisxhe/XPowersLib@0.3.3
lewisxhe/SensorLib@0.3.4
```

This does *not* include any updates, just a cleanup.
2026-03-25 19:27:48 -04:00
Ben MeadorsandCopilot c4bac04be3 Exclude web server, paxcounter and few others from original ESP32 generation to fix IRAM overflow (#10005)
* Exclude web server, paxcounter and few others from original ESP32 generation to fix IRAM overflow

* Update variants/esp32/esp32.ini

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

* Update variants/esp32/esp32.ini

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

* Update build source filter in esp32.ini

Removed WiFiAPClient.cpp from build source filter.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-25 17:52:02 -05:00
Jason P b9bdc87367 Update External Notifications with a full redo of logic gates (#10006)
* Update External Notifications with a full redo of pathways

* Correct comments.

* Fix TWatch S3 and use HAS_DRV2605
2026-03-25 14:47:34 -05:00
3b079c91bf T5-4.7-S3 Epaper Pro support (#6625)
* preliminary io pin definitions

* Update product link

* Move to new variant structure and refactor inkHUD - Display does not work and SX1262 init fails on HT752-02

* update variant definitions

* add EPD driver

* fix lora, add v1/v2 variant targets

* adapt pins for v1/v2

* alt button

* add compile guards

* use lilygo epd47 lib

* workaround for INT ERR_NOT_FOUND

* USE_EPD (e-ink parallel display)

* use FastEPD driver

* create screen

* EInkParallelDisplay definition

* setup screen

* dispaly() implementation

* enable touchscreen

* rotate touch screen

* refactor display buffer processing

* provide local copy of TwoWire instance as the touch driver calls end()

* use larger fonts

* replace touch driver; enable debugging

* replace touch driver; enable debugging

* consider bitsremain == 0

* tryfix crash

* fix button

* update touch driver

* set lora_cs pin

* update touch driver and lib reference

* add locks

* limit Ghosting similar to EInkDynamicDisplay

* workaround for FastEPD partial update bug (artifacts)

* display() code cleanup

* fix a few platformio definitions

* more EPD display cleanup

* set rotation

* use FastEPD arduino I2C by default

* touch rotation

* update screen for EPD

* increase swipe distance for larger screen

* EPD UIRenderer

* trunk fmt

* further #ifdef USE_EPD

* disable rotation which messes up w/h; more cleanup

* switch off ghosting algo

* releease build; V1 buttons

* swap V1 buttons

* rearrange USE_EINK/EPD macros, use large font

* cleanup (revert modified files)

* more cleanup

* revert

* revert file

* revert file

Removed redundant line continuation in preprocessor directives.

* Temporary gate off PSRam calculations until we can fix them

* move variant.cpp and update commit references

* revert wrong merge

* add earlyInitVariant()

* initialize all port 0 pins (0-7) as outputs / HIGH

---------

Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: Jason P <applewiz@mac.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-25 10:52:18 -05:00
Ben Meadors d693fd4232 Exclude accelerometer on new MESHTASTIC_EXCLUDE_ACCELEROMETER flag (#10004) 2026-03-25 08:57:29 -05:00
ManuelandJonathan Bennett 163c54877c fix: Cardputer-Adv I2S sound (#9963)
* fix cardputer sound

* Add I2S BLEEBLEs

* MenuHandler ifdefs

* add generic tone -> I2S RTTTL conversion

* not needed

---------

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2026-03-24 12:39:21 -05:00
Ben Meadors 2e2993f180 Add gnu++17 standard to nRF52840 build flags 2026-03-24 08:39:20 -05:00
Austin Lane 450f2adab0 Remove unneeded GH perms
Reduce perms to least-necessary
Remove merge_queue.yml since it's never been used and is now stale
Remove comment-artifact, it hasn't worked in ages.
2026-03-24 08:14:05 -04:00
renovate[bot] 0ad1b66387 Update dorny/test-reporter action to v3 (#9981)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-23 15:10:58 -04:00
Austin Lane 5716aeba3b Cleanup GH Actions 2026-03-23 11:27:09 -04:00
stmka_playgoundandBen Meadors 723209b198 Fixes #9850: Double space issue with Cyrillic OLED font (#9971)
* Fixes double space OLEDDisplayFontsRU.cpp

* Fixes double space OLEDDisplayFontsUA.cpp

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-23 07:07:20 -05:00
5a8ce60d68 add heltec_mesh_node_t096 board. (#9960)
* add heltec_mesh_node_t096 board.

* Fixed the GPS reset pin comments.

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

* Added compiles if NUM_PA_POINTS is not defined.

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

* Correct the pin description.

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

* Specify the version of the dependency library TFT_eSPI.

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

* Adding fields missing from the .ini file.

* Modify the screen SPI frequency to 40 MHz.

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-22 09:55:09 -05:00
Robert Sasak c77b10a317 Add LED_BUILTIN for variant tlora_v1 (#9973) 2026-03-22 09:37:41 -05:00
Okan ErturanandBen Meadors 8fa5b4f7ce Fix: Enable touch-to-backlight on T-Echo (not just T-Echo Plus) (#9953)
The touch-to-backlight feature was gated behind TTGO_T_ECHO_PLUS, but
the regular T-Echo has the same backlight pin (PIN_EINK_EN, P1.11).
This changes the guard to use PIN_EINK_EN only, so any device with an
e-ink backlight pin gets the feature.

Fixes #7630

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-22 09:37:33 -05:00
e30294b6da Fixes #9792 : Hop with Meshtastic ffff and ?dB is added to missing hop in traceroute (#9945)
* Fix issue 9792, decode packet for TR test

* Fix 9792: Assure packet id decoded for TR test

* Potential fix for pull request finding

Log improvement for failure to decode packet.

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

* trunk fmt

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
2026-03-22 09:37:22 -05:00
rcatal01andBen Meadors 948c28afff fix: MQTT settings silently fail to persist when broker is unreachable (#9934)
* fix: MQTT settings silently fail to persist when broker is unreachable

isValidConfig() was testing broker connectivity via connectPubSub() as
part of config validation. When the broker was unreachable (network not
ready, DNS failure, server down), the function returned false, causing
AdminModule to skip saving settings entirely — silently.

This removes the connectivity test from isValidConfig(), which now only
validates configuration correctness (TLS support, default server port).
Connectivity is handled by the MQTT module's existing reconnect loop.

Fixes #9107

* Add client warning notification when MQTT broker is unreachable

Per maintainer feedback: instead of silently saving when the broker
can't be reached, send a WARNING notification to the client saying
"MQTT settings saved, but could not reach the MQTT server."

Settings still always persist regardless of connectivity — the core
fix from the previous commit is preserved. The notification is purely
advisory so users know to double-check their server address and
credentials if the connection test fails.

When the network is not available at all, the connectivity check is
skipped entirely with a log message.

* Address Copilot review feedback

- Fix warning message wording: "Settings will be saved" instead of
  "Settings saved" (notification fires before AdminModule persists)
- Add null check on clientNotificationPool.allocZeroed() to prevent
  crash if pool is exhausted (matches AdminModule::sendWarning pattern)
- Fix test comments to accurately describe conditional connectivity
  check behavior and IS_RUNNING_TESTS compile-out

* Remove connectivity check from isValidConfig entirely

Reverts the advisory connectivity check added in the previous commit.
While the intent was to warn users about unreachable brokers,
connectPubSub() mutates the isConnected state of the running MQTT
module and performs synchronous network operations that can block
the config-save path.

The cleanest approach: isValidConfig() validates config correctness
only (TLS support, default server port). The MQTT reconnect loop
handles connectivity after settings are persisted and the device
reboots. If the broker is unreachable, the user will see it in the
MQTT connection status — no special notification needed.

This returns to the simpler design from the first commit, which was
tested on hardware and confirmed working.

* Use lightweight TCP check instead of connectPubSub for validation

Per maintainer feedback: users need connectivity feedback, but
connectPubSub() mutates the module's isConnected state.

This uses a standalone MQTTClient TCP connection test that:
- Checks if the server IP/port is reachable
- Sends a WARNING notification if unreachable
- Does NOT establish an MQTT session or mutate any module state
- Does NOT block saving — isValidConfig always returns true

The TCP test client is created locally, used, and destroyed within
the function scope. No side effects on the running MQTT module.

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-22 09:37:11 -05:00
107 changed files with 2269 additions and 278 deletions
+39 -25
View File
@@ -4,9 +4,14 @@ on:
workflow_dispatch:
inputs:
# trunk-ignore(checkov/CKV_GHA_7)
target:
type: string
required: false
description: Choose the target board, e.g. nrf52_promicro_diy_tcxo. If blank, will find available targets.
arch:
type: choice
options:
- all
- esp32
- esp32s3
- esp32c3
@@ -15,32 +20,18 @@ on:
- rp2040
- rp2350
- stm32
target:
type: string
required: false
description: Choose the target board, e.g. nrf52_promicro_diy_tcxo. If blank, will find available targets.
# find-target:
# type: boolean
# default: true
# description: 'Find the available targets'
description: Choose an arch to limit the search, or 'all' to search all architectures.
default: all
permissions: read-all
jobs:
find-targets:
if: ${{ inputs.target == '' }}
strategy:
fail-fast: false
matrix:
arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
- all
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
@@ -51,14 +42,37 @@ jobs:
- run: pip install -U platformio
- name: Generate matrix
id: jsonStep
env:
BUILDTARGET: ${{ inputs.target }}
MATRIXARCH: ${{ inputs.arch }}
run: |
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level extra)
echo "Name: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY
echo "Base: $GITHUB_BASE_REF" >> $GITHUB_STEP_SUMMARY
echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY
echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY
echo "Targets:" >> $GITHUB_STEP_SUMMARY
echo $TARGETS | jq -r 'sort_by(.board) |.[] | "- " + .board' >> $GITHUB_STEP_SUMMARY
if [ "$BUILDTARGET" = "" ]; then
echo "Name: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY
echo "Base: $GITHUB_BASE_REF" >> $GITHUB_STEP_SUMMARY
echo "Arch: $MATRIXARCH" >> $GITHUB_STEP_SUMMARY
echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY
echo "## 🎯 The following target boards are available to build:" >> $GITHUB_STEP_SUMMARY
echo "| Platform | Board |" >> $GITHUB_STEP_SUMMARY
echo "| -------- | ----- |" >> $GITHUB_STEP_SUMMARY
echo $TARGETS | jq -r 'sort_by(.board) | sort_by(.platform) |.[] | "| " + .platform + " | " + .board + " |" ' >> $GITHUB_STEP_SUMMARY
else
echo "We build this one:" >> $GITHUB_STEP_SUMMARY
ARCH=$(echo "$TARGETS" | jq --arg BUILDTARGET "$BUILDTARGET" -r '.[] | select(.board==$BUILDTARGET) | .platform')
echo "| Platform | Board |" >> $GITHUB_STEP_SUMMARY
echo "| -------- | ----- |" >> $GITHUB_STEP_SUMMARY
echo "| $ARCH | "$BUILDTARGET" |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [[ "$ARCH" == "" ]]; then
echo "## ❌ Error: Target "$BUILDTARGET" not found!" >> $GITHUB_STEP_SUMMARY
else
echo "## ✅ Target "$BUILDTARGET" found, proceeding to build." >> $GITHUB_STEP_SUMMARY
fi
echo "You may need to refresh this page to make the built firmware appear below." >> $GITHUB_STEP_SUMMARY
echo "arch=$ARCH" >> $GITHUB_OUTPUT
fi
outputs:
arch: ${{ steps.jsonStep.outputs.arch }}
version:
if: ${{ inputs.target != '' }}
@@ -78,12 +92,12 @@ jobs:
build:
if: ${{ inputs.target != '' && inputs.arch != 'native' }}
needs: [version]
needs: [version, find-targets]
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ inputs.target }}
platform: ${{ inputs.arch }}
platform: ${{ needs.find-targets.outputs.arch }}
gather-artifacts:
permissions:
+1 -1
View File
@@ -136,7 +136,7 @@ jobs:
merge-multiple: true
- name: Test Report
uses: dorny/test-reporter@v2.6.0
uses: dorny/test-reporter@v3.0.0
with:
name: PlatformIO Tests
path: testreport.xml
+5 -5
View File
@@ -8,15 +8,15 @@ plugins:
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.510
- renovate@43.84.0
- checkov@3.2.513
- renovate@43.100.0
- prettier@3.8.1
- trufflehog@3.93.8
- trufflehog@3.94.1
- yamllint@1.38.0
- bandit@1.9.4
- trivy@0.69.3
- taplo@0.10.0
- ruff@0.15.7
- ruff@0.15.8
- isort@8.0.1
- markdownlint@0.48.0
- oxipng@10.1.0
@@ -28,7 +28,7 @@ lint:
- shellcheck@0.11.0
- black@26.3.1
- git-diff-check
- gitleaks@8.30.0
- gitleaks@8.30.1
- clang-format@16.0.3
ignore:
- linters: [ALL]
@@ -1,3 +1,9 @@
Meta:
name: BananaPi-BPI-R4-sx1262
support: community
compatible:
- bananapi_bpi-r4 # OpenWrt target
Lora:
Module: sx1262 # BananaPi-BPI-R4 SPI via 26p GPIO Header
## CS: 28
@@ -1,4 +1,10 @@
## https://www.mikroe.com/lr-iot-click
Meta:
name: OpenWRT One mikroBUS LR-IOT-CLICK
support: community
compatible:
- openwrt_one # OpenWrt target
Lora:
Module: lr1110 # OpenWRT ONE mikroBUS with LR-IOT-CLICK
# CS: 25
@@ -1,3 +1,9 @@
Meta:
name: OpenWRT One mikroBUS sx1262
support: community
compatible:
- openwrt_one # OpenWrt target
Lora:
Module: sx1262
IRQ: 10
+28
View File
@@ -0,0 +1,28 @@
# meshtasticd configuration files
This directory contains YAML configuration files for meshtasticd. Each file describes a specific hardware configuration, including the LoRa module and pin assignments. These configurations are used by meshtasticd to correctly interface with the hardware.
## Metadata structure
Each configuration file includes a `Meta` section that provides information about the configuration.
This configuration is consumed by configuration-selection tools.
```yaml
Meta:
name: MeshAdv-Pi E22-900M30S # A unique identifier for this configuration.
support: community # community, official, or deprecated; determined by Meshtastic Leads.
compatible: # A list of compatible products or platforms.
- raspberry-pi
```
`name`: A unique identifier for the configuration, typically reflecting the hardware it supports.
`support`: Indicates the level of support for this configuration. It can be one of the following:
- `community`: Supported by the Meshtastic community. Meshtastic Members may not possess, or have not tested this configuration.
- `official`: Fully supported by Meshtastic. Meshtastic Members have tested and verified this configuration.
- `deprecated`: No longer recommended for deployment by Meshtastic.
`compatible`: A list of compatible products or platforms that can use this configuration.
This will vary depending on the intended use case / platform.
Multiple compatible entries can be included. E.g. Armbian `BOARD` value or OpenWrt `TARGET` value.
These tags can be consumed by different configuration-selection tools, filtering based upon their platform/etc.
+6
View File
@@ -1,3 +1,9 @@
Meta:
name: Waveshare 1.44inch LCD HAT
support: community
compatible:
- raspberry-pi
### Waveshare 1.44inch LCD HAT
Display:
Panel: ST7735S
+6
View File
@@ -1,3 +1,9 @@
Meta:
name: Waveshare 2.8inch LCD HAT
support: community
compatible:
- raspberry-pi
Display:
### Waveshare 2.8inch RPi LCD
@@ -0,0 +1,30 @@
---
Lora:
## Ebyte E80-900M22S
## This is a bit experimental
##
##
Module: lr1121
gpiochip: 1 # subtract 32 from the gpio numbers
DIO3_TCXO_VOLTAGE: 1.8
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
spiSpeed: 2000000
rfswitch_table:
pins: [DIO5, DIO6, DIO7]
MODE_STBY: [LOW, LOW, LOW]
MODE_RX: [LOW, HIGH, LOW]
MODE_TX: [HIGH, HIGH, LOW]
MODE_TX_HP: [HIGH, LOW, LOW]
MODE_TX_HF: [LOW, LOW, LOW]
MODE_GNSS: [LOW, LOW, HIGH]
MODE_WIFI: [LOW, LOW, LOW]
General:
MACAddressSource: eth0
@@ -0,0 +1,46 @@
---
Lora:
## Ebyte E80-900M22S
## This is a bit experimental
##
##
Module: lr1121
gpiochip: 1 # subtract 32 from the gpio numbers
DIO3_TCXO_VOLTAGE: 1.8
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
spiSpeed: 2000000
rfswitch_table:
pins:
- DIO5
- DIO6
MODE_STBY:
- LOW
- LOW
MODE_RX:
- HIGH
- LOW
MODE_TX:
- HIGH
- HIGH
MODE_TX_HP:
- LOW
- HIGH
MODE_TX_HF:
- LOW
- LOW
MODE_GNSS:
- LOW
- LOW
MODE_WIFI:
- LOW
- LOW
General:
MACAddressSource: eth0
@@ -0,0 +1,30 @@
---
Lora:
## Ebyte E80-900M22S
## This is a bit experimental
##
##
Module: lr1121
gpiochip: 1 # subtract 32 from the gpio numbers
DIO3_TCXO_VOLTAGE: 1.8
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
spiSpeed: 2000000
rfswitch_table:
pins: [DIO5, DIO6, DIO7]
MODE_STBY: [LOW, LOW, LOW]
MODE_RX: [LOW, LOW, LOW]
MODE_TX: [LOW, HIGH, LOW]
MODE_TX_HP: [HIGH, LOW, LOW]
# MODE_TX_HF: []
# MODE_GNSS: []
MODE_WIFI: [LOW, LOW, LOW]
General:
MACAddressSource: eth0
+6
View File
@@ -1,3 +1,9 @@
Meta:
name: Adafruit RFM9x
support: deprecated
compatible:
- raspberry-pi
Lora:
Module: RF95 # Adafruit RFM9x
Reset: 25
+6
View File
@@ -1,5 +1,11 @@
# MeshAdv-Pi E22-900M30S
# https://github.com/chrismyers2000/MeshAdv-Pi-Hat
Meta:
name: MeshAdv-Pi E22-900M30S
support: community
compatible:
- raspberry-pi
Lora:
Module: sx1262
CS: 21
@@ -1,5 +1,11 @@
# MeshAdv Mini E22-900M22S
# https://github.com/chrismyers2000/MeshAdv-Mini
Meta:
name: MeshAdv Mini E22-900M22S
support: community
compatible:
- raspberry-pi
Lora:
Module: sx1262 # Ebyte E22-900M22S
CS: 8
+7 -1
View File
@@ -1,6 +1,12 @@
Meta:
name: RAK6421 + RAK13300 Slot 1
support: official
compatible:
- raspberry-pi
Lora:
### RAK13300in Slot 1
### RAK13300 in Slot 1
Module: sx1262
IRQ: 22 #IO6
Reset: 16 # IO4
+7 -1
View File
@@ -1,5 +1,11 @@
Meta:
name: RAK6421 + RAK13300 Slot 2
support: official
compatible:
- raspberry-pi
Lora:
### RAK13300in Slot 2 pins
### RAK13300 in Slot 2 pins
IRQ: 18 #IO6
Reset: 24 # IO4
Busy: 19 # IO5
+7 -1
View File
@@ -1,6 +1,12 @@
Meta:
name: RAK6421 + RAK13302 Slot 1
support: official
compatible:
- raspberry-pi
Lora:
### RAK13300in Slot 1
### RAK13302 in Slot 1
Module: sx1262
IRQ: 22 #IO6
Reset: 16 # IO4
+7 -1
View File
@@ -1,5 +1,11 @@
Meta:
name: RAK6421 + RAK13302 Slot 2
support: official
compatible:
- raspberry-pi
Lora:
### RAK13300in Slot 2 pins
### RAK13302 in Slot 2 pins
IRQ: 18 #IO6
Reset: 24 # IO4
Busy: 19 # IO5
@@ -1,20 +1,22 @@
---
Lora:
## Ebyte E80-900M22S
## This is a bit experimental
##
##
Module: lr1121
gpiochip: 1 # subtract 32 from the gpio numbers
DIO3_TCXO_VOLTAGE: 1.8
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
spiSpeed: 2000000
General:
MACAddressSource: eth0
---
Meta:
name: Femtofox Ebyte E80-900M22S with TCXO
support: community
compatible:
- luckfox-pico-mini # Armbian
Lora:
## Ebyte E80-900M22S
## This is a bit experimental
##
##
Module: lr1121
gpiochip: 1 # subtract 32 from the gpio numbers
DIO3_TCXO_VOLTAGE: 1.8
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
spiSpeed: 2000000
@@ -1,21 +1,24 @@
---
Lora:
## Ebyte E22-900M30S, E22-900M22S with or without external RF switching setup
## HT-RA62 (Has internal switching, but whatever)
## Seeed WIO SX1262 (already has TXEN-DIO2 link, but needs RXEN)
## Will work with any module with or without RF switching, and with TCXO
Module: sx1262
gpiochip: 1 # subtract 32 from the gpio numbers
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
RXen: 24 #pin12 / GPIO56 1D0 # Not strictly needed for auto-switching, but why complicate things?
# TXen: bridge to DIO2 on E22 module
spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
spiSpeed: 2000000
General:
MACAddressSource: eth0
---
Meta:
name: Femtofox SX1262 TCXO
support: community
compatible:
- luckfox-pico-mini # Armbian
Lora:
## Ebyte E22-900M30S, E22-900M22S with or without external RF switching setup
## HT-RA62 (Has internal switching, but whatever)
## Seeed WIO SX1262 (already has TXEN-DIO2 link, but needs RXEN)
## Will work with any module with or without RF switching, and with TCXO
Module: sx1262
gpiochip: 1 # subtract 32 from the gpio numbers
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
RXen: 24 #pin12 / GPIO56 1D0 # Not strictly needed for auto-switching, but why complicate things?
# TXen: bridge to DIO2 on E22 module
spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
spiSpeed: 2000000
@@ -1,21 +1,24 @@
---
Lora:
## Ebyte E22-900MM22S with no external RF switching setup
## Waveshare SX126X XXXM, AI Thinker RA-01SH
## Will work with any module with or without RF switching and no TCXO
Module: sx1262
gpiochip: 1 # subtract 32 from the gpio numbers
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: false
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
RXen: 24 #pin12 / GPIO56 1D0 # Not strictly needed for auto-switching, but why complicate things?
# TXen: bridge to DIO2 on E22 module
spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
spiSpeed: 2000000
General:
MACAddressSource: eth0
---
Meta:
name: Femtofox SX1262 XTAL
support: community
compatible:
- luckfox-pico-mini # Armbian
Lora:
## Ebyte E22-900MM22S with no external RF switching setup
## Waveshare SX126X XXXM, AI Thinker RA-01SH
## Will work with any module with or without RF switching and no TCXO
Module: sx1262
gpiochip: 1 # subtract 32 from the gpio numbers
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: false
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
RXen: 24 #pin12 / GPIO56 1D0 # Not strictly needed for auto-switching, but why complicate things?
# TXen: bridge to DIO2 on E22 module
spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
spiSpeed: 2000000
+7 -1
View File
@@ -1,6 +1,12 @@
Meta:
name: RAK6421 + RAK13300 Slot 1 (Autoconf default)
support: official
compatible:
- raspberry-pi
Lora:
### RAK13300in Slot 1
### RAK13300 in Slot 1
Module: sx1262
IRQ: 22 #IO6
Reset: 16 # IO4
@@ -1,3 +1,9 @@
Meta:
name: Luckfox Lyra PicoCalc Wio LoRa SX1262
support: official
compatible:
- luckfox-lyra-plus # Armbian
Lora:
Module: sx1262
DIO2_AS_RF_SWITCH: true
+7
View File
@@ -2,6 +2,13 @@
# Enable overlay 'luckfox-lyra-ultra-w-spi0-cs0-spidev' with armbian-config
# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/Luckfox%20Ultra%20Hat
# 1 Watt Lyra Ultra hat
Meta:
name: wehooper4 Luckfox Ultra 1W
support: community
compatible:
- luckfox-pico-ultra # Armbian
- luckfox-lyra-ultra # Armbian
Lora:
Module: sx1262
DIO2_AS_RF_SWITCH: true
+7
View File
@@ -2,6 +2,13 @@
# Enable overlay 'luckfox-lyra-ultra-w-spi0-cs0-spidev' with armbian-config
# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/Luckfox%20Ultra%20Hat
# 2 Watt Lyra Ultra hat
Meta:
name: wehooper4 Luckfox Ultra 2W
support: community
compatible:
- luckfox-pico-ultra # Armbian
- luckfox-lyra-ultra # Armbian
Lora:
Module: sx1262
DIO2_AS_RF_SWITCH: true
@@ -2,6 +2,12 @@
# Enable overlay 'luckfox-lyra-plus-spi0-cs0_rmio13-spidev' with armbian-config
# Waveshare LoRa HAT for Raspberry Pi Pico
# https://www.waveshare.com/wiki/Pico-LoRa-SX1262
Meta:
name: Waveshare LoRa HAT for Raspberry Pi Pico
support: community
compatible:
- luckfox-lyra-plus # Armbian
Lora:
Module: sx1262
DIO2_AS_RF_SWITCH: true
+6
View File
@@ -1,3 +1,9 @@
Meta:
name: Lora Meshstick SX1262
support: official
compatible:
- usb
Lora:
Module: sx1262
CS: 0
+6
View File
@@ -1,3 +1,9 @@
Meta:
name: Lora Meshstick SX1262
support: community
compatible:
- usb
Lora:
Module: lr1121
CS: 0
+7 -1
View File
@@ -1,5 +1,11 @@
Meta:
name: Pinedio USB SX1262
support: deprecated
compatible:
- usb
Lora:
Module: sx1262
CS: 0
IRQ: 10
spidev: ch341
spidev: ch341
@@ -1,3 +1,9 @@
Meta:
name: raxda-rock2f-starter-edition-hat
support: community
compatible:
- rock-2f # Armbian
Lora:
### Raxda Rock 2F running Armbian Linux 6.1.99-vendor-rk35xx
@@ -1,5 +1,11 @@
# https://www.waveshare.com/core1262-868m.htm
# https://github.com/markbirss/lora-starter-edition-sx1262-i2c
Meta:
name: lora-starter-edition-sx1262-i2c
support: community
compatible:
- raspberry-pi
Lora:
Module: sx1262 # Starter Edition SX1262 I2C Raspberry Pi HAT
DIO2_AS_RF_SWITCH: true
@@ -1,3 +1,9 @@
Meta:
name: meshstick-1262
support: official
compatible:
- usb
Lora:
Module: sx1262
CS: 0
+6
View File
@@ -1,3 +1,9 @@
Meta:
name: meshtoad-e22
support: official
compatible:
- usb
Lora:
Module: sx1262
CS: 0
@@ -1,3 +1,9 @@
Meta:
name: umesh-1262-30dbm
support: community
compatible:
- clockwork-uconsole
Lora:
Module: sx1262
CS: 0
@@ -1,3 +1,9 @@
Meta:
name: umesh-1268-30dbm
support: community
compatible:
- clockwork-uconsole
Lora:
Module: sx1268
CS: 0
+6
View File
@@ -1,3 +1,9 @@
Meta:
name: Waveshare SX1262
support: deprecated
compatible:
- raspberry-pi
Lora:
Module: sx1262 # Waveshare SX126X XXXM
DIO2_AS_RF_SWITCH: true
@@ -1,5 +1,12 @@
# https://www.waveshare.com/pico-lora-sx1262-868m.htm
# https://github.com/markbirss/lora-ws-raspberry-pi-pico-to-rpi-adapter
Meta:
name: ws-raspberry-pico-to-rpi-adapter
support: community
compatible:
- raspberry-pi
Lora:
Module: sx1262 # Waveshare Raspberry Pi Pico to Raspberry Pi HAT Adapter
DIO2_AS_RF_SWITCH: true
@@ -15,6 +15,12 @@
# 5 CS 24
# 26 DIO1/IRQ 26
Meta:
name: ws-raspberry-pico-to-orangepi-03
support: community
compatible:
- orange-pi-zero-3 # Armbian
Lora:
Module: sx1262 # Waveshare Raspberry Pico Lora module
DIO2_AS_RF_SWITCH: true
+1 -1
View File
@@ -43,7 +43,7 @@ for pio_env in pio_envs:
env = {
"ci": {"board": pio_env, "platform": env_platform},
"board_level": cfg.get(f"env:{pio_env}", "board_level", default=None),
"board_check": bool(cfg.get(f"env:{pio_env}", "board_check", default=False)),
"board_check": cfg.get(f"env:{pio_env}", "board_check", default="false").strip().lower() == "true",
}
all_envs.append(env)
+38
View File
@@ -0,0 +1,38 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi",
"partitions": "default_16MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=0",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "esp32s3"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "LilyGo T5-ePaper-S3",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"require_upload_port": true,
"speed": 921600
},
"url": "https://lilygo.cc/products/t5-e-paper-s3-pro",
"vendor": "LILYGO"
}
+29
View File
@@ -0,0 +1,29 @@
{
"build": {
"arduino": {
"ldscript": "esp32_out.ld"
},
"core": "esp32",
"extra_flags": "-DARDUINO_T_Beam",
"f_cpu": "240000000L",
"f_flash": "40000000L",
"flash_mode": "dio",
"mcu": "esp32",
"variant": "tbeam"
},
"connectivity": ["wifi", "bluetooth", "can", "ethernet"],
"debug": {
"openocd_board": "esp-wroom-32.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "TTGO T-Beam (PSRAM Disabled)",
"upload": {
"flash_size": "4MB",
"maximum_ram_size": 1310720,
"maximum_size": 4194304,
"require_upload_port": true,
"speed": 460800
},
"url": "https://github.com/LilyGO/TTGO-T-Beam",
"vendor": "TTGO"
}
+17 -17
View File
@@ -77,10 +77,10 @@ lib_deps =
https://github.com/meshtastic/TinyGPSPlus/archive/71a82db35f3b973440044c476d4bcdc673b104f4.zip
# renovate: datasource=git-refs depName=meshtastic-ArduinoThread packageName=https://github.com/meshtastic/ArduinoThread gitBranch=master
https://github.com/meshtastic/ArduinoThread/archive/b841b0415721f1341ea41cccfb4adccfaf951567.zip
# renovate: datasource=custom.pio depName=Nanopb packageName=nanopb/library/Nanopb
nanopb/Nanopb@0.4.91
# renovate: datasource=custom.pio depName=ErriezCRC32 packageName=erriez/library/ErriezCRC32
erriez/ErriezCRC32@1.0.1
# renovate: datasource=github-tags depName=Nanopb packageName=nanopb/nanopb
https://github.com/nanopb/nanopb/archive/refs/tags/nanopb-0.4.9.1.zip
# renovate: datasource=github-tags depName=ErriezCRC32 packageName=Erriez/ErriezCRC32
https://github.com/Erriez/ErriezCRC32/archive/refs/tags/1.0.1.zip
; Used for the code analysis in PIO Home / Inspect
check_tool = cppcheck
@@ -95,8 +95,8 @@ check_flags =
framework = arduino
lib_deps =
${env.lib_deps}
# renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=end2endzone/library/NonBlockingRTTTL
end2endzone/NonBlockingRTTTL@1.4.0
# renovate: datasource=github-tags depName=NonBlockingRTTTL packageName=end2endzone/NonBlockingRTTTL
https://github.com/end2endzone/NonBlockingRTTTL/archive/refs/tags/1.4.0.zip
build_unflags =
-std=c++11
-std=gnu++11
@@ -107,26 +107,26 @@ build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/nic
; Common libs for communicating over TCP/IP networks such as MQTT
[networking_base]
lib_deps =
# renovate: datasource=custom.pio depName=TBPubSubClient packageName=thingsboard/library/TBPubSubClient
thingsboard/TBPubSubClient@2.12.1
# renovate: datasource=custom.pio depName=NTPClient packageName=arduino-libraries/library/NTPClient
arduino-libraries/NTPClient@3.2.1
# renovate: datasource=github-tags depName=TBPubSubClient packageName=thingsboard/pubsubclient
https://github.com/thingsboard/pubsubclient/archive/refs/tags/v2.12.1.zip
# renovate: datasource=github-tags depName=NTPClient packageName=arduino-libraries/NTPClient
https://github.com/arduino-libraries/NTPClient/archive/refs/tags/3.2.1.zip
; Extra TCP/IP networking libs for supported devices
[networking_extra]
lib_deps =
# renovate: datasource=custom.pio depName=Syslog packageName=arcao/library/Syslog
arcao/Syslog@2.0.0
# renovate: datasource=github-tags depName=Syslog packageName=arcao/Syslog
https://github.com/arcao/Syslog/archive/refs/tags/v2.0.zip
[radiolib_base]
lib_deps =
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
jgromes/RadioLib@7.6.0
# renovate: datasource=github-tags depName=RadioLib packageName=jgromes/RadioLib
https://github.com/jgromes/RadioLib/archive/refs/tags/7.6.0.zip
[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/f36d2a953524e372b78c5b4147ec55f38716964e.zip
https://github.com/meshtastic/device-ui/archive/7b1485b86c8d55a40e5226749097767e8b87f396.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]
@@ -233,5 +233,5 @@ lib_deps =
[environmental_extra_no_bsec]
lib_deps =
${environmental_extra_common.lib_deps}
# renovate: datasource=custom.pio depName=adafruit/Adafruit BME680 Library packageName=adafruit/library/Adafruit BME680
adafruit/Adafruit BME680 Library@^2.0.5
# renovate: datasource=custom.pio depName=Adafruit_BME680 packageName=adafruit/library/Adafruit BME680 Library
adafruit/Adafruit BME680 Library@2.0.6
+2
View File
@@ -4,6 +4,8 @@
":dependencyDashboard",
":semanticCommitTypeAll(chore)",
":ignoreModulesAndTests",
":noUnscheduledUpdates",
"schedule:daily",
"group:recommended",
"replacements:all",
"workarounds:all"
+10 -2
View File
@@ -98,7 +98,6 @@ Syslog &Syslog::logMask(uint8_t priMask)
void Syslog::enable()
{
this->_client->begin(this->_port);
this->_enabled = true;
}
@@ -166,14 +165,21 @@ inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *mess
if ((pri & LOG_FACMASK) == 0)
pri = LOG_MAKEPRI(LOG_FAC(this->_priDefault), pri);
// W5100S: acquire UDP socket on-demand to avoid permanent socket consumption
if (!this->_client->begin(this->_port)) {
return false;
}
if (this->_server != NULL) {
result = this->_client->beginPacket(this->_server, this->_port);
} else {
result = this->_client->beginPacket(this->_ip, this->_port);
}
if (result != 1)
if (result != 1) {
this->_client->stop();
return false;
}
this->_client->print('<');
this->_client->print(pri);
@@ -193,6 +199,8 @@ inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *mess
this->_client->print(message);
this->_client->endPacket();
this->_client->stop(); // W5100S: release UDP socket for other services
return true;
}
+8
View File
@@ -35,6 +35,11 @@
#include "nrfx_power.h"
#endif
#if defined(ARCH_NRF52)
#include "Nrf52SaadcLock.h"
#include "concurrency/LockGuard.h"
#endif
#if defined(DEBUG_HEAP_MQTT) && !MESHTASTIC_EXCLUDE_MQTT
#include "mqtt/MQTT.h"
#include "target_specific.h"
@@ -328,6 +333,9 @@ class AnalogBatteryLevel : public HasBatteryLevel
scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs);
scaled *= operativeAdcMultiplier;
#else // block for all other platforms
#ifdef ARCH_NRF52
concurrency::LockGuard saadcGuard(concurrency::nrf52SaadcLock);
#endif
for (uint32_t i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
raw += analogRead(BATTERY_PIN);
}
+56 -1
View File
@@ -6,6 +6,11 @@
#include "Tone.h"
#endif
#if defined(HAS_I2S)
#include "main.h"
#include <unordered_map>
#endif
#if !defined(ARCH_PORTDUINO)
extern "C" void delay(uint32_t dwMs);
#endif
@@ -50,6 +55,50 @@ const int DURATION_1_2 = 500; // 1/2 note
const int DURATION_3_4 = 750; // 3/4 note
const int DURATION_1_1 = 1000; // 1/1 note
#ifdef HAS_I2S
void playTonesRTTTL(const ToneDuration *tone_durations, int size)
{
// translate ToneDuration[] to RTTTL string and play using audioThread
static std::unordered_map<int, std::string> freqToNote = {
{NOTE_C3, "c4"}, {NOTE_CS3, "c#4"}, {NOTE_D3, "d4"}, {NOTE_DS3, "d#4"}, {NOTE_E3, "e4"}, {NOTE_F3, "f4"},
{NOTE_FS3, "f#4"}, {NOTE_G3, "g4"}, {NOTE_GS3, "g#4"}, {NOTE_A3, "a4"}, {NOTE_AS3, "a#4"}, {NOTE_B3, "b4"},
{NOTE_C4, "c5"}, {NOTE_E4, "e5"}, {NOTE_G4, "g5"}, {NOTE_A4, "a5"}, {NOTE_C5, "c6"}, {NOTE_E5, "e6"},
{NOTE_G5, "g6"}, {NOTE_F5, "f6"}, {NOTE_G6, "g7"}, {NOTE_E7, "e8"}};
char rtttl[128] = "tone:d=32,o=4,b=200:"; // default duration and octave
for (int i = 0; i < size; i++) {
const auto &td = tone_durations[i];
std::string note = "b4";
if (freqToNote.find(td.frequency_khz) != freqToNote.end()) {
note = freqToNote[td.frequency_khz];
}
int dur = 32; // default duration
if (td.duration_ms >= 1000)
dur = 1;
else if (td.duration_ms >= 500)
dur = 2;
else if (td.duration_ms >= 250)
dur = 4;
else if (td.duration_ms >= 125)
dur = 8;
else if (td.duration_ms >= 62)
dur = 16;
else
dur = 32;
char noteStr[64];
snprintf(noteStr, sizeof(noteStr), "%s,%d", note.c_str(), dur);
strncat(rtttl, noteStr, sizeof(rtttl) - strlen(rtttl) - 1);
audioThread->beginRttl(rtttl, strlen(rtttl));
while (audioThread->isPlaying()) {
delay(10);
}
return;
}
}
#endif
void playTones(const ToneDuration *tone_durations, int size)
{
if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DISABLED ||
@@ -57,7 +106,13 @@ void playTones(const ToneDuration *tone_durations, int size)
// Buzzer is disabled or not set to system tones
return;
}
#ifdef PIN_BUZZER
#ifdef HAS_I2S
if (moduleConfig.external_notification.use_i2s_as_buzzer && audioThread) {
playTonesRTTTL(tone_durations, size);
return;
}
#endif
#if defined(PIN_BUZZER)
if (!config.device.buzzer_gpio)
config.device.buzzer_gpio = PIN_BUZZER;
#endif
+28
View File
@@ -2,6 +2,7 @@
#include "configuration.h"
#include "detect/ScanI2C.h"
#include "main.h"
#include "modules/NodeInfoModule.h"
#include <Throttle.h>
#include <sys/time.h>
#include <time.h>
@@ -12,6 +13,14 @@ uint32_t lastSetFromPhoneNtpOrGps = 0;
static uint32_t lastTimeValidationWarning = 0;
static const uint32_t TIME_VALIDATION_WARNING_INTERVAL_MS = 15000; // 15 seconds
static void triggerNodeInfoCheckOnTimeSource(RTCQuality oldQuality, RTCQuality newQuality)
{
if (oldQuality == RTCQualityNone && newQuality > RTCQualityNone && nodeInfoModule) {
LOG_DEBUG("Time source acquired (%s -> %s), triggering NodeInfo recheck", RtcName(oldQuality), RtcName(newQuality));
nodeInfoModule->triggerImmediateNodeInfoCheck();
}
}
RTCQuality getRTCQuality()
{
return currentQuality;
@@ -61,9 +70,11 @@ RTCSetResult readFromRTC()
LOG_DEBUG("Read RTC time from RV3028 getTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1,
t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
if (currentQuality == RTCQualityNone) {
RTCQuality oldQuality = currentQuality;
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
currentQuality = RTCQualityDevice;
triggerNodeInfoCheckOnTimeSource(oldQuality, currentQuality);
}
return RTCSetResultSuccess;
} else {
@@ -105,9 +116,11 @@ RTCSetResult readFromRTC()
LOG_DEBUG("Read RTC time from %s getDateTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", rtc.getChipName(), t.tm_year + 1900,
t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
if (currentQuality == RTCQualityNone) {
RTCQuality oldQuality = currentQuality;
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
currentQuality = RTCQualityDevice;
triggerNodeInfoCheckOnTimeSource(oldQuality, currentQuality);
}
return RTCSetResultSuccess;
} else {
@@ -139,9 +152,11 @@ RTCSetResult readFromRTC()
}
#endif
if (currentQuality == RTCQualityNone) {
RTCQuality oldQuality = currentQuality;
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
currentQuality = RTCQualityDevice;
triggerNodeInfoCheckOnTimeSource(oldQuality, currentQuality);
}
return RTCSetResultSuccess;
}
@@ -214,6 +229,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
}
if (shouldSet) {
RTCQuality oldQuality = currentQuality;
currentQuality = q;
lastSetMsec = now;
if (currentQuality >= RTCQualityNTP) {
@@ -281,6 +297,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
#endif
readFromRTC();
triggerNodeInfoCheckOnTimeSource(oldQuality, currentQuality);
return RTCSetResultSuccess;
} else {
return RTCSetResultNotSet; // RTC was already set with a higher quality time
@@ -397,6 +414,17 @@ uint32_t getValidTime(RTCQuality minQuality, bool local)
return (currentQuality >= minQuality) ? getTime(local) : 0;
}
#ifdef PIO_UNIT_TESTING
void setBootRelativeTimeForUnitTest(uint32_t secondsSinceBoot)
{
currentQuality = RTCQualityNone;
zeroOffsetSecs = 0;
timeStartMsec = millis() - (secondsSinceBoot * 1000);
lastSetFromPhoneNtpOrGps = 0;
lastTimeValidationWarning = 0;
}
#endif
time_t gm_mktime(const struct tm *tm)
{
#if !MESHTASTIC_EXCLUDE_TZ
+4
View File
@@ -54,6 +54,10 @@ uint32_t getValidTime(RTCQuality minQuality, bool local = false);
RTCSetResult readFromRTC();
#ifdef PIO_UNIT_TESTING
void setBootRelativeTimeForUnitTest(uint32_t secondsSinceBoot);
#endif
time_t gm_mktime(const struct tm *tm);
#define SEC_PER_DAY 86400
+1 -1
View File
@@ -1,6 +1,6 @@
#include "configuration.h"
#ifdef USE_EINK
#if defined(USE_EINK) && !defined(USE_EINK_PARALLELDISPLAY)
#include "EInkDisplay2.h"
#include "SPILock.h"
#include "main.h"
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_EINK
#if defined(USE_EINK) && !defined(USE_EINK_PARALLELDISPLAY)
#include "GxEPD2_BW.h"
#include <OLEDDisplay.h>
+427
View File
@@ -0,0 +1,427 @@
#include "EInkParallelDisplay.h"
#ifdef USE_EINK_PARALLELDISPLAY
#include "Wire.h"
#include "variant.h"
#include <Arduino.h>
#include <atomic>
#include <stdlib.h>
#include <string.h>
#include "FastEPD.h"
// Thresholds for choosing partial vs full update
#ifndef EPD_PARTIAL_THRESHOLD_ROWS
#define EPD_PARTIAL_THRESHOLD_ROWS 128 // if changed region <= this many rows, prefer partial
#endif
#ifndef EPD_FULLSLOW_PERIOD
#define EPD_FULLSLOW_PERIOD 100 // every N full updates do a slow (CLEAR_SLOW) full refresh
#endif
#ifndef EPD_RESPONSIVE_MIN_MS
#define EPD_RESPONSIVE_MIN_MS 1000 // simple rate-limit (ms) for responsive updates
#endif
EInkParallelDisplay::EInkParallelDisplay(uint16_t width, uint16_t height, EpdRotation rot) : epaper(nullptr), rotation(rot)
{
LOG_INFO("init EInkParallelDisplay");
// Set dimensions in OLEDDisplay base class
this->geometry = GEOMETRY_RAWMODE;
this->displayWidth = width;
this->displayHeight = height;
// Round shortest side up to nearest byte, to prevent truncation causing an undersized buffer
uint16_t shortSide = min(width, height);
uint16_t longSide = max(width, height);
if (shortSide % 8 != 0)
shortSide = (shortSide | 7) + 1;
this->displayBufferSize = longSide * (shortSide / 8);
#ifdef EINK_LIMIT_GHOSTING_PX
// allocate dirty pixel buffer same size as epaper buffers (rowBytes * height)
size_t rowBytes = (this->displayWidth + 7) / 8;
dirtyPixelsSize = rowBytes * this->displayHeight;
dirtyPixels = (uint8_t *)calloc(dirtyPixelsSize, 1);
ghostPixelCount = 0;
#endif
}
EInkParallelDisplay::~EInkParallelDisplay()
{
#ifdef EINK_LIMIT_GHOSTING_PX
if (dirtyPixels) {
free(dirtyPixels);
dirtyPixels = nullptr;
}
#endif
// If an async full update is running, wait for it to finish
if (asyncFullRunning.load()) {
// wait a short while for task to finish
for (int i = 0; i < 50 && asyncFullRunning.load(); ++i) {
delay(50);
}
if (asyncTaskHandle) {
// Let it finish or delete it
vTaskDelete(asyncTaskHandle);
asyncTaskHandle = nullptr;
}
}
delete epaper;
}
/*
* Called by the OLEDDisplay::init() path.
*/
bool EInkParallelDisplay::connect()
{
LOG_INFO("Do EPD init");
if (!epaper) {
epaper = new FASTEPD;
#if defined(T5_S3_EPAPER_PRO_V1)
epaper->initPanel(BB_PANEL_LILYGO_T5PRO, 28000000);
#elif defined(T5_S3_EPAPER_PRO_V2)
epaper->initPanel(BB_PANEL_LILYGO_T5PRO_V2, 28000000);
// initialize all port 0 pins (0-7) as outputs / HIGH
for (int i = 0; i < 8; i++) {
epaper->ioPinMode(i, OUTPUT);
epaper->ioWrite(i, HIGH);
}
#else
#error "unsupported EPD device!"
#endif
}
// epaper->setRotation(rotation); // does not work, messes up width/height
epaper->setMode(BB_MODE_1BPP);
epaper->clearWhite();
epaper->fullUpdate(true);
#ifdef EINK_LIMIT_GHOSTING_PX
// After a full/clear the dirty tracking should be reset
resetGhostPixelTracking();
#endif
return true;
}
/*
* sendCommand - simple passthrough (not required for epd_driver-based path)
*/
void EInkParallelDisplay::sendCommand(uint8_t com)
{
LOG_DEBUG("EInkParallelDisplay::sendCommand %d", (int)com);
}
/*
* Start a background task that will perform a blocking fullUpdate(). This lets
* display() return quickly while the heavy refresh runs in the background.
*/
void EInkParallelDisplay::startAsyncFullUpdate(int clearMode)
{
if (asyncFullRunning.load())
return; // already running
asyncFullRunning.store(true);
// pass 'this' as parameter
BaseType_t rc = xTaskCreatePinnedToCore(EInkParallelDisplay::asyncFullUpdateTask, "epd_full", 4096 / sizeof(StackType_t),
this, 2, &asyncTaskHandle,
#if CONFIG_FREERTOS_UNICORE
0
#else
1
#endif
);
if (rc != pdPASS) {
LOG_WARN("Failed to create async full-update task, falling back to blocking update");
epaper->fullUpdate(clearMode, false);
epaper->backupPlane();
asyncFullRunning.store(false);
asyncTaskHandle = nullptr;
}
}
/*
* FreeRTOS task entry: runs the full update and then backs up plane.
*/
void EInkParallelDisplay::asyncFullUpdateTask(void *pvParameters)
{
EInkParallelDisplay *self = static_cast<EInkParallelDisplay *>(pvParameters);
if (!self) {
vTaskDelete(nullptr);
return;
}
// choose CLEAR_SLOW occasionally
int clearMode = CLEAR_FAST;
if (self->fastRefreshCount >= EPD_FULLSLOW_PERIOD) {
clearMode = CLEAR_SLOW;
self->fastRefreshCount = 0;
} else {
// when running async full, treat it as a full so reset fast count
self->fastRefreshCount = 0;
}
self->epaper->fullUpdate(clearMode, false);
self->epaper->backupPlane();
#ifdef EINK_LIMIT_GHOSTING_PX
// A full refresh clears ghosting state
self->resetGhostPixelTracking();
#endif
self->asyncFullRunning.store(false);
self->asyncTaskHandle = nullptr;
// delete this task
vTaskDelete(nullptr);
}
/*
* Convert the OLEDDisplay buffer (vertical byte layout) into the 1bpp horizontal-bytes
* buffer used by the FASTEPD library. For performance we write directly into FASTEPD's
* currentBuffer() while comparing against previousBuffer() to detect changed rows.
* After conversion we call FASTEPD::partialUpdate() or FASTEPD::fullUpdate() according
* to a heuristic so only the minimal region is refreshed.
*/
void EInkParallelDisplay::display(void)
{
const uint16_t w = this->displayWidth;
const uint16_t h = this->displayHeight;
// Simple rate limiting: avoid very-frequent responsive updates
uint32_t nowMs = millis();
if (lastUpdateMs != 0 && (nowMs - lastUpdateMs) < EPD_RESPONSIVE_MIN_MS) {
LOG_DEBUG("rate-limited, skipping update");
return;
}
// bytes per row in epd format (one byte = 8 horizontal pixels)
const uint32_t rowBytes = (w + 7) / 8;
// Get pointers to internal buffers
uint8_t *cur = epaper->currentBuffer();
uint8_t *prev = epaper->previousBuffer(); // may be NULL on first init
// Track changed row range while converting
int newTop = h; // min changed row (initialized to out-of-range)
int newBottom = -1; // max changed row
#ifdef FAST_EPD_PARTIAL_UPDATE_BUG
// Track changed byte column range (for clipped fullUpdate fallback)
int newLeftByte = (int)rowBytes;
int newRightByte = -1;
#endif
// Compute a quick hash of the incoming OLED buffer (so we can skip identical frames)
uint32_t imageHash = 0;
uint32_t bufBytes = (w / 8) * h; // vertical-byte layout size
for (uint32_t bi = 0; bi < bufBytes; ++bi) {
imageHash ^= ((uint32_t)buffer[bi]) << (bi & 31);
}
if (imageHash == previousImageHash) {
// LOG_DEBUG("image identical to previous, skipping update");
return;
}
#ifdef EINK_LIMIT_GHOSTING_PX
// reset ghost count for this conversion pass; we'll mark bits that change
ghostPixelCount = 0;
#endif
// Convert: OLED buffer layout -> FASTEPD 1bpp horizontal-bytes layout into cur,
// comparing against prev when available to detect changes.
for (uint32_t y = 0; y < h; ++y) {
const uint32_t base = (y >> 3) * w; // (y/8) * width
const uint8_t bitMask = (uint8_t)(1u << (y & 7)); // mask for this row in vertical-byte layout
const uint32_t rowBase = y * rowBytes;
// process full 8-pixel bytes
for (uint32_t xb = 0; xb < rowBytes; ++xb) {
uint32_t x0 = xb * 8;
// read up to 8 source bytes (vertical-byte per column)
uint8_t b0 = (x0 + 0 < w) ? buffer[base + x0 + 0] : 0;
uint8_t b1 = (x0 + 1 < w) ? buffer[base + x0 + 1] : 0;
uint8_t b2 = (x0 + 2 < w) ? buffer[base + x0 + 2] : 0;
uint8_t b3 = (x0 + 3 < w) ? buffer[base + x0 + 3] : 0;
uint8_t b4 = (x0 + 4 < w) ? buffer[base + x0 + 4] : 0;
uint8_t b5 = (x0 + 5 < w) ? buffer[base + x0 + 5] : 0;
uint8_t b6 = (x0 + 6 < w) ? buffer[base + x0 + 6] : 0;
uint8_t b7 = (x0 + 7 < w) ? buffer[base + x0 + 7] : 0;
// build output byte: MSB = leftmost pixel
uint8_t out = 0;
out |= (uint8_t)((b0 & bitMask) ? 0x80 : 0x00);
out |= (uint8_t)((b1 & bitMask) ? 0x40 : 0x00);
out |= (uint8_t)((b2 & bitMask) ? 0x20 : 0x00);
out |= (uint8_t)((b3 & bitMask) ? 0x10 : 0x00);
out |= (uint8_t)((b4 & bitMask) ? 0x08 : 0x00);
out |= (uint8_t)((b5 & bitMask) ? 0x04 : 0x00);
out |= (uint8_t)((b6 & bitMask) ? 0x02 : 0x00);
out |= (uint8_t)((b7 & bitMask) ? 0x01 : 0x00);
// handle partial byte at end of row by masking off invalid bits
uint8_t mask = 0xFF;
uint32_t bitsRemain = (w > x0) ? (w - x0) : 0;
if (bitsRemain > 0 && bitsRemain < 8) {
mask = (uint8_t)(0xFF << (8 - bitsRemain));
out &= mask;
}
// invert to FASTEPD polarity
out = (~out) & mask;
uint32_t pos = rowBase + xb;
uint8_t prevVal = prev ? (prev[pos] & mask) : 0x00;
// Consider this byte changed if previous buffer differs (or prev is null)
bool changed = (prev == nullptr) || (prevVal != out);
#ifdef EINK_LIMIT_GHOSTING_PX
if (changed && prev)
markDirtyBits(prev, pos, mask, out);
#endif
// mark row changed only if the previous buffer differs
if (changed) {
if (y < (uint32_t)newTop)
newTop = y;
if ((int)y > newBottom)
newBottom = y;
#ifdef FAST_EPD_PARTIAL_UPDATE_BUG
// record changed column bytes
if ((int)xb < newLeftByte)
newLeftByte = (int)xb;
if ((int)xb > newRightByte)
newRightByte = (int)xb;
#endif
}
// Always write the computed value into the current buffer (avoid leaving stale bytes)
cur[pos] = (cur[pos] & ~mask) | out;
}
}
// If nothing changed, avoid any panel update
if (newBottom < 0) {
LOG_DEBUG("no pixel changes detected, skipping update (conv)");
previousImageHash = imageHash; // still remember that frame
return;
}
// Choose partial vs full update using heuristic
// Decide if we should force a full update after many fast updates
bool forceFull = (fastRefreshCount >= EPD_FULLSLOW_PERIOD);
#ifdef EINK_LIMIT_GHOSTING_PX
// If ghost pixels exceed limit, force a full update to clear ghosting
if (ghostPixelCount > ghostPixelLimit) {
LOG_WARN("ghost pixels %u > limit %u, forcing full refresh", ghostPixelCount, ghostPixelLimit);
forceFull = true;
}
#endif
// Compute pixel bounds from newTop/newBottom
int startRow = (newTop / 8) * 8;
int endRow = (newBottom / 8) * 8 + 7;
LOG_DEBUG("EPD update rows=%d..%d alignedRows=%d..%d rowBytes=%u", newTop, newBottom, startRow, endRow, rowBytes);
if (epaper->getMode() == BB_MODE_1BPP && !forceFull && (newBottom - newTop) <= EPD_PARTIAL_THRESHOLD_ROWS) {
// Prefer partial update path if driver is reliable; otherwise use clipped fullUpdate fallback.
#ifdef FAST_EPD_PARTIAL_UPDATE_BUG
// Workaround for FastEPD partial update bug: use clipped fullUpdate instead
// Build a pixel rectangle for a clipped fullUpdate using the changed columns
int startCol = (newLeftByte <= newRightByte) ? (newLeftByte * 8) : 0;
int endCol = (newLeftByte <= newRightByte) ? ((newRightByte + 1) * 8 - 1) : (w - 1);
BB_RECT rect{startCol, startRow, endCol - startCol + 1, endRow - startRow + 1};
// LOG_DEBUG("Using clipped fullUpdate rect x=%d y=%d w=%d h=%d", rect.x, rect.y, rect.w, rect.h);
epaper->fullUpdate(CLEAR_FAST, false, &rect);
#else
// Use rows for partial update
LOG_DEBUG("calling partialUpdate startRow=%d endRow=%d", startRow, endRow);
epaper->partialUpdate(true, startRow, endRow);
#endif
epaper->backupPlane();
fastRefreshCount++;
} else {
// Full update: run async if possible (startAsyncFullUpdate will fall back to blocking)
startAsyncFullUpdate(forceFull ? CLEAR_SLOW : CLEAR_FAST);
}
lastUpdateMs = millis();
previousImageHash = imageHash;
// Keep same behavior as before
lastDrawMsec = millis();
}
#ifdef EINK_LIMIT_GHOSTING_PX
// markDirtyBits: mark per-bit dirty flags and update ghostPixelCount
void EInkParallelDisplay::markDirtyBits(const uint8_t *prevBuf, uint32_t pos, uint8_t mask, uint8_t out)
{
// defensive: need dirtyPixels allocated and prevBuf valid
if (!dirtyPixels || !prevBuf)
return;
// 'out' is in FASTEPD polarity (1 = black, 0 = white)
uint8_t newBlack = out & mask; // bits that will be black now
uint8_t newWhite = (~out) & mask; // bits that will be white now
// previously recorded dirty bits for this byte
uint8_t before = dirtyPixels[pos];
// Ghost bits: bits that were previously marked dirty and are now being driven white
uint8_t ghostBits = before & newWhite;
if (ghostBits) {
ghostPixelCount += __builtin_popcount((unsigned)ghostBits);
}
// Only mark bits dirty when they turn black now (accumulate until a full refresh)
uint8_t newlyDirty = newBlack & (~before);
if (newlyDirty) {
dirtyPixels[pos] |= newlyDirty;
}
}
// reset ghost tracking (call after a full refresh)
void EInkParallelDisplay::resetGhostPixelTracking()
{
if (!dirtyPixels)
return;
memset(dirtyPixels, 0, dirtyPixelsSize);
ghostPixelCount = 0;
}
#endif
/*
* forceDisplay: use lastDrawMsec
*/
bool EInkParallelDisplay::forceDisplay(uint32_t msecLimit)
{
uint32_t now = millis();
if (lastDrawMsec == 0 || (now - lastDrawMsec) > msecLimit) {
display();
return true;
}
return false;
}
void EInkParallelDisplay::endUpdate()
{
{
// ensure any async full update is started/completed
if (asyncFullRunning.load()) {
// nothing to do; background task will run and call backupPlane when done
} else {
epaper->fullUpdate(CLEAR_FAST, false);
epaper->backupPlane();
#ifdef EINK_LIMIT_GHOSTING_PX
resetGhostPixelTracking();
#endif
}
}
}
#endif
+69
View File
@@ -0,0 +1,69 @@
#pragma once
#include "configuration.h"
#ifdef USE_EINK_PARALLELDISPLAY
#include <OLEDDisplay.h>
#include <atomic>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
class FASTEPD;
/**
* Adapter for E-Ink 8-bit parallel displays (EPD), specifically devices supported by FastEPD library
*/
class EInkParallelDisplay : public OLEDDisplay
{
public:
enum EpdRotation {
EPD_ROT_LANDSCAPE = 0,
EPD_ROT_PORTRAIT = 90,
EPD_ROT_INVERTED_LANDSCAPE = 180,
EPD_ROT_INVERTED_PORTRAIT = 270,
};
EInkParallelDisplay(uint16_t width, uint16_t height, EpdRotation rotation);
virtual ~EInkParallelDisplay();
// OLEDDisplay virtuals
bool connect() override;
void sendCommand(uint8_t com) override;
int getBufferOffset(void) override { return 0; }
void display(void) override;
bool forceDisplay(uint32_t msecLimit = 1000);
void endUpdate();
protected:
uint32_t lastDrawMsec = 0;
FASTEPD *epaper;
private:
// Async full-refresh support
std::atomic<bool> asyncFullRunning{false};
TaskHandle_t asyncTaskHandle = nullptr;
void startAsyncFullUpdate(int clearMode);
static void asyncFullUpdateTask(void *pvParameters);
#ifdef EINK_LIMIT_GHOSTING_PX
// helpers
void resetGhostPixelTracking();
void markDirtyBits(const uint8_t *prevBuf, uint32_t pos, uint8_t mask, uint8_t out);
void countGhostPixelsAndMaybePromote(int &newTop, int &newBottom, bool &forceFull);
// per-bit dirty buffer (same format as epaper buffers): one bit == one pixel
uint8_t *dirtyPixels = nullptr;
size_t dirtyPixelsSize = 0;
uint32_t ghostPixelCount = 0;
uint32_t ghostPixelLimit = EINK_LIMIT_GHOSTING_PX;
#endif
EpdRotation rotation;
uint32_t previousImageHash = 0;
uint32_t lastUpdateMs = 0;
int fastRefreshCount = 0;
};
#endif
+12 -3
View File
@@ -27,6 +27,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "configuration.h"
#include "meshUtils.h"
#if HAS_SCREEN
#include "EInkParallelDisplay.h"
#include <OLEDDisplay.h>
#include "DisplayFormatters.h"
@@ -364,12 +365,14 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(HACKADAY_COMMUNICATOR)
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY) && !defined(USE_EINK_PARALLELDISPLAY)
dispdev = new EInkDisplay(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY)
dispdev = new EInkDynamicDisplay(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif defined(USE_EINK_PARALLELDISPLAY)
dispdev = new EInkParallelDisplay(EPD_WIDTH, EPD_HEIGHT, EInkParallelDisplay::EPD_ROT_PORTRAIT);
#elif defined(USE_ST7567)
dispdev = new ST7567Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
@@ -759,7 +762,11 @@ void Screen::forceDisplay(bool forceUiUpdate)
}
// Tell EInk class to update the display
#if defined(USE_EINK_PARALLELDISPLAY)
static_cast<EInkParallelDisplay *>(dispdev)->forceDisplay();
#elif defined(USE_EINK)
static_cast<EInkDisplay *>(dispdev)->forceDisplay();
#endif
#else
// No delay between UI frame rendering
if (forceUiUpdate) {
@@ -998,8 +1005,10 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver)
ui->update();
} while (ui->getUiState()->lastUpdate < startUpdate);
#if defined(USE_EINK_PARALLELDISPLAY)
static_cast<EInkParallelDisplay *>(dispdev)->forceDisplay(0);
#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
// Old EInkDisplay class
#if !defined(USE_EINK_DYNAMICDISPLAY)
static_cast<EInkDisplay *>(dispdev)->forceDisplay(0); // Screen::forceDisplay(), but override rate-limit
#endif
@@ -1011,7 +1020,7 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver)
#ifdef EINK_HASQUIRK_GHOSTING
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // Really ugly to see ghosting from "screen paused"
#else
EINK_ADD_FRAMEFLAG(dispdev, RESPONSIVE); // Really nice to wake screen with a fast-refresh
EINK_ADD_FRAMEFLAG(dispdev, RESPONSIVE); // Really nice to wake screen with a fast-refresh
#endif
}
#endif
+3 -3
View File
@@ -20,7 +20,7 @@
#include "graphics/fonts/OLEDDisplayFontsGR.h"
#endif
#if defined(CROWPANEL_ESP32S3_5_EPAPER) && defined(USE_EINK)
#if (defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(T5_S3_EPAPER_PRO)) && defined(USE_EINK)
#include "graphics/fonts/EinkDisplayFonts.h"
#endif
@@ -90,7 +90,7 @@
#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) || defined(ST7796_CS) || \
defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796)) && \
defined(USE_ST7796) || defined(HACKADAY_COMMUNICATOR)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
// The screen is bigger so use bigger fonts
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
@@ -106,7 +106,7 @@
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
#endif
#if defined(CROWPANEL_ESP32S3_5_EPAPER) && defined(USE_EINK)
#if defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(T5_S3_EPAPER_PRO)
#undef FONT_SMALL
#undef FONT_MEDIUM
#undef FONT_LARGE
+9 -3
View File
@@ -544,6 +544,9 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
#ifndef T_DECK_PRO
barsOffset -= 12;
#endif
#if defined(T5_S3_EPAPER_PRO)
barsOffset += 60;
#endif
#endif
int barX = x + barsOffset;
if (currentResolution == ScreenResolution::UltraLow) {
@@ -593,11 +596,12 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
uint32_t heapUsed = memGet.getHeapSize() - memGet.getFreeHeap();
uint32_t heapTotal = memGet.getHeapSize();
uint32_t psramUsed = memGet.getPsramSize() - memGet.getFreePsram();
uint32_t psramTotal = memGet.getPsramSize();
uint32_t flashUsed = 0, flashTotal = 0;
#ifdef ESP32
#ifndef T5_S3_EPAPER_PRO
uint32_t psramUsed = memGet.getPsramSize() - memGet.getFreePsram();
uint32_t psramTotal = memGet.getPsramSize();
#endif
flashUsed = FSCom.usedBytes();
flashTotal = FSCom.totalBytes();
#endif
@@ -616,10 +620,12 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
// === Draw memory rows
drawUsageRow("Heap:", heapUsed, heapTotal, true);
#ifdef ESP32
#ifndef T5_S3_EPAPER_PRO
if (psramUsed > 0) {
line += 1;
drawUsageRow("PSRAM:", psramUsed, psramTotal);
}
#endif
if (flashTotal > 0) {
line += 1;
drawUsageRow("Flash:", flashUsed, flashTotal);
+20 -4
View File
@@ -1221,9 +1221,11 @@ void menuHandler::positionBaseMenu()
};
constexpr size_t baseCount = sizeof(baseOptions) / sizeof(baseOptions[0]);
constexpr size_t calibrateCount = sizeof(calibrateOptions) / sizeof(calibrateOptions[0]);
static std::array<const char *, baseCount> baseLabels{};
#if !MESHTASTIC_EXCLUDE_ACCELEROMETER
constexpr size_t calibrateCount = sizeof(calibrateOptions) / sizeof(calibrateOptions[0]);
static std::array<const char *, calibrateCount> calibrateLabels{};
#endif
auto onSelection = [](const PositionMenuOption &option, int) -> void {
if (option.action == OptionsAction::Back) {
@@ -1249,9 +1251,11 @@ void menuHandler::positionBaseMenu()
screen->runNow();
break;
case PositionAction::CompassCalibrate:
#if !MESHTASTIC_EXCLUDE_ACCELEROMETER
if (accelerometerThread) {
accelerometerThread->calibrate(30);
}
#endif
break;
case PositionAction::GPSSmartPosition:
menuQueue = GpsSmartPositionMenu;
@@ -1269,11 +1273,15 @@ void menuHandler::positionBaseMenu()
};
BannerOverlayOptions bannerOptions;
#if !MESHTASTIC_EXCLUDE_ACCELEROMETER
if (accelerometerThread) {
bannerOptions = createStaticBannerOptions("GPS Action", calibrateOptions, calibrateLabels, onSelection);
} else {
bannerOptions = createStaticBannerOptions("GPS Action", baseOptions, baseLabels, onSelection);
}
#else
bannerOptions = createStaticBannerOptions("GPS Action", baseOptions, baseLabels, onSelection);
#endif
screen->showOverlayBanner(bannerOptions);
}
@@ -2203,9 +2211,9 @@ void menuHandler::traceRouteMenu()
void menuHandler::testMenu()
{
enum optionsNumbers { Back, NumberPicker, ShowChirpy };
static const char *optionsArray[4] = {"Back"};
static int optionsEnumArray[4] = {Back};
enum optionsNumbers { Back, NumberPicker, ShowChirpy, TestAnnounce };
static const char *optionsArray[5] = {"Back"};
static int optionsEnumArray[5] = {Back};
int options = 1;
optionsArray[options] = "Number Picker";
@@ -2213,6 +2221,10 @@ void menuHandler::testMenu()
optionsArray[options] = screen->isFrameHidden("chirpy") ? "Show Chirpy" : "Hide Chirpy";
optionsEnumArray[options++] = ShowChirpy;
#ifdef HAS_I2S
optionsArray[options] = "Test Announce";
optionsEnumArray[options++] = TestAnnounce;
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Hidden Test Menu";
@@ -2227,6 +2239,10 @@ void menuHandler::testMenu()
screen->toggleFrameVisibility("chirpy");
screen->setFrames(Screen::FOCUS_SYSTEM);
} else if (selected == TestAnnounce) {
#ifdef HAS_I2S
audioThread->readAloud("This is a test of the emergency broadcast system. This is only a test.");
#endif
} else {
menuQueue = SystemBaseMenu;
screen->runNow();
+1
View File
@@ -15,6 +15,7 @@
#include <GFX.h> // GFXRoot drawing lib
#include "mesh/MeshModule.h"
#include "mesh/MeshTypes.h"
#include "./AppletFont.h"
+5 -1
View File
@@ -16,6 +16,10 @@
DeviceScreen *deviceScreen = nullptr;
#ifndef TFT_TASK_STACK_SIZE
#define TFT_TASK_STACK_SIZE 16384
#endif
#ifdef ARCH_ESP32
// Get notified when the system is entering light sleep
CallbackObserver<DeviceScreen, void *> tftSleepObserver =
@@ -127,7 +131,7 @@ void tftSetup(void)
#ifdef ARCH_ESP32
tftSleepObserver.observe(&notifyLightSleep);
endSleepObserver.observe(&notifyLightSleepEnd);
xTaskCreatePinnedToCore(tft_task_handler, "tft", 10240, NULL, 1, NULL, 0);
xTaskCreatePinnedToCore(tft_task_handler, "tft", TFT_TASK_STACK_SIZE, NULL, 1, NULL, 0);
#elif defined(ARCH_PORTDUINO)
std::thread *tft_task = new std::thread([] { tft_task_handler(); });
#endif
+3 -3
View File
@@ -123,7 +123,7 @@ void printPartitionTable()
#include "AmbientLightingThread.h"
#include "PowerFSMThread.h"
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && !MESHTASTIC_EXCLUDE_ACCELEROMETER
#include "motion/AccelerometerThread.h"
AccelerometerThread *accelerometerThread = nullptr;
#endif
@@ -657,7 +657,7 @@ void setup()
}
#endif
#if !defined(ARCH_STM32WL)
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ACCELEROMETER
auto acc_info = i2cScanner->firstAccelerometer();
accelerometer_found = acc_info.type != ScanI2C::DeviceType::NONE ? acc_info.address : accelerometer_found;
LOG_DEBUG("acc_info = %i", acc_info.type);
@@ -737,7 +737,7 @@ void setup()
#endif
#if !MESHTASTIC_EXCLUDE_I2C
#if !defined(ARCH_STM32WL)
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ACCELEROMETER
if (acc_info.type != ScanI2C::DeviceType::NONE) {
accelerometerThread = new AccelerometerThread(acc_info.type);
}
+1 -1
View File
@@ -65,7 +65,7 @@ extern UdpMulticastHandler *udpHandler;
// Global Screen singleton.
extern graphics::Screen *screen;
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && !MESHTASTIC_EXCLUDE_ACCELEROMETER
#include "motion/AccelerometerThread.h"
extern AccelerometerThread *accelerometerThread;
#endif
+125 -15
View File
@@ -18,10 +18,7 @@
#include "concurrency/LockGuard.h"
#include "main.h"
#include "xmodem.h"
#if FromRadio_size > MAX_TO_FROM_RADIO_SIZE
#error FromRadio is too big
#endif
#include <pb_encode.h>
#if ToRadio_size > MAX_TO_FROM_RADIO_SIZE
#error ToRadio is too big
@@ -61,13 +58,14 @@ void PhoneAPI::handleStartConfig()
onConfigStart();
// even if we were already connected - restart our state machine
if (config_nonce == SPECIAL_NONCE_ONLY_NODES) {
if (config_nonce == SPECIAL_NONCE_ONLY_NODES || config_nonce == SPECIAL_NONCE_BATCH_ONLY_NODES) {
// If client only wants node info, jump directly to sending nodes
state = STATE_SEND_OWN_NODEINFO;
LOG_INFO("Client only wants node info, skipping other config");
} else {
state = STATE_SEND_MY_INFO;
}
batchNodeInfo = (config_nonce == SPECIAL_NONCE_BATCH || config_nonce == SPECIAL_NONCE_BATCH_ONLY_NODES);
pauseBluetoothLogging = true;
spiLock->lock();
filesManifest = getFiles("/", 10);
@@ -223,6 +221,48 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
STATE_SEND_PACKETS // send packets or debug strings
*/
/// Context passed to the nanopb encoding callback for batched NodeInfo
struct NodeInfoBatchEncodeContext {
std::deque<meshtastic_NodeInfo> *items;
size_t itemCount; // How many items from the front of the deque to encode
};
/// Nanopb encoding callback for repeated NodeInfo items in a NodeInfoBatch.
/// Called by pb_encode during both sizing and actual encoding passes.
static bool nodeInfoBatchEncodeCallback(pb_ostream_t *stream, const pb_field_iter_t *field, void *const *arg)
{
auto *ctx = static_cast<NodeInfoBatchEncodeContext *>(*arg);
for (size_t i = 0; i < ctx->itemCount && i < ctx->items->size(); i++) {
if (!pb_encode_tag_for_field(stream, field))
return false;
if (!pb_encode_submessage(stream, meshtastic_NodeInfo_fields, &(*ctx->items)[i]))
return false;
}
return true;
}
/// Calculate how many NodeInfos from the front of the deque fit within maxPayloadBytes.
/// maxPayloadBytes is the budget for the inner NodeInfoBatch content (repeated field entries).
static size_t calculateBatchCount(std::deque<meshtastic_NodeInfo> &items, size_t maxPayloadBytes)
{
size_t totalSize = 0;
size_t count = 0;
for (const auto &info : items) {
pb_ostream_t sizestream = PB_OSTREAM_SIZING;
if (!pb_encode(&sizestream, meshtastic_NodeInfo_fields, &info))
break;
size_t itemSize = sizestream.bytes_written;
// Per-item overhead: 1 byte tag (field 1, wire type 2) + varint length prefix
size_t overhead = 1 + (itemSize < 128 ? 1 : 2);
if (totalSize + overhead + itemSize > maxPayloadBytes)
break;
totalSize += overhead + itemSize;
count++;
}
// Always include at least 1 item if available (even if it slightly exceeds budget)
return count > 0 ? count : (items.empty() ? 0 : 1);
}
size_t PhoneAPI::getFromRadio(uint8_t *buf)
{
// Respond to heartbeat by sending queue status
@@ -231,7 +271,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_queueStatus_tag;
fromRadioScratch.queueStatus = router->getQueueStatus();
heartbeatReceived = false;
size_t numbytes = pb_encode_to_bytes(buf, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch);
size_t numbytes = pb_encode_to_bytes(buf, MAX_TO_FROM_RADIO_SIZE, &meshtastic_FromRadio_msg, &fromRadioScratch);
LOG_DEBUG("FromRadio=STATE_SEND_QUEUE_STATUS, numbytes=%u", numbytes);
return numbytes;
}
@@ -286,7 +326,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
nodeInfoForPhone.num = 0;
}
}
if (config_nonce == SPECIAL_NONCE_ONLY_NODES) {
if (config_nonce == SPECIAL_NONCE_ONLY_NODES || config_nonce == SPECIAL_NONCE_BATCH_ONLY_NODES) {
// If client only wants node info, jump directly to sending nodes
state = STATE_SEND_OTHER_NODEINFOS;
onNowHasData(0);
@@ -479,6 +519,81 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
LOG_INFO("Start sending nodeinfos millis=%u", millis());
}
if (batchNodeInfo) {
// Batched path: pack multiple NodeInfos into a single FromRadio packet.
// Consolidate any pending nodeInfoForPhone into the queue, then top it up.
{
concurrency::LockGuard guard(&nodeInfoMutex);
if (nodeInfoForPhone.num != 0) {
nodeInfoQueue.push_front(nodeInfoForPhone);
nodeInfoForPhone = {};
}
}
prefetchNodeInfos();
bool queueEmpty;
{
concurrency::LockGuard guard(&nodeInfoMutex);
queueEmpty = nodeInfoQueue.empty();
}
if (queueEmpty) {
LOG_DEBUG("Done sending %d of %d nodeinfos (batched) millis=%u", readIndex, nodeDB->getNumMeshNodes(), millis());
concurrency::LockGuard guard(&nodeInfoMutex);
nodeInfoQueue.clear();
state = STATE_SEND_FILEMANIFEST;
return getFromRadio(buf);
}
// Fix up user IDs and log progress
{
concurrency::LockGuard guard(&nodeInfoMutex);
for (auto &info : nodeInfoQueue) {
sprintf(info.user.id, "!%08x", info.num);
}
}
// Estimate overhead: FromRadio id (~2B) + oneof tag 18 (~2B) + NodeInfoBatch length varint (~2B) = ~6 bytes
constexpr size_t kFromRadioOverhead = 10; // conservative
size_t batchCount;
{
concurrency::LockGuard guard(&nodeInfoMutex);
batchCount = calculateBatchCount(nodeInfoQueue, MAX_TO_FROM_RADIO_SIZE - kFromRadioOverhead);
}
if (readIndex == 2 || readIndex % 20 == 0) {
concurrency::LockGuard guard(&nodeInfoMutex);
LOG_DEBUG("nodeinfo batch: %d items, %d/%d total", batchCount, readIndex, nodeDB->getNumMeshNodes());
}
// Use a local FromRadio so the callback context pointer doesn't outlive its scope.
meshtastic_FromRadio batchRadio = {};
NodeInfoBatchEncodeContext ctx;
{
concurrency::LockGuard guard(&nodeInfoMutex);
ctx.items = &nodeInfoQueue;
ctx.itemCount = batchCount;
}
batchRadio.which_payload_variant = meshtastic_FromRadio_node_info_batch_tag;
batchRadio.node_info_batch.items.funcs.encode = nodeInfoBatchEncodeCallback;
batchRadio.node_info_batch.items.arg = &ctx;
size_t numbytes = pb_encode_to_bytes(buf, MAX_TO_FROM_RADIO_SIZE, &meshtastic_FromRadio_msg, &batchRadio);
// Remove consumed items from the queue
{
concurrency::LockGuard guard(&nodeInfoMutex);
for (size_t i = 0; i < batchCount && !nodeInfoQueue.empty(); i++) {
nodeInfoQueue.pop_front();
}
}
prefetchNodeInfos();
return numbytes;
}
// Non-batched path: send one NodeInfo per FromRadio packet (legacy behavior)
meshtastic_NodeInfo infoToSend = {};
{
concurrency::LockGuard guard(&nodeInfoMutex);
@@ -496,11 +611,6 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
// Just in case we stored a different user.id in the past, but should never happen going forward
sprintf(infoToSend.user.id, "!%08x", infoToSend.num);
// Logging this really slows down sending nodes on initial connection because the serial console is so slow, so only
// uncomment if you really need to:
// LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", nodeInfoForPhone.num, nodeInfoForPhone.last_heard,
// nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name);
// Occasional progress logging. (readIndex==2 will be true for the first non-us node)
if (readIndex == 2 || readIndex % 20 == 0) {
LOG_DEBUG("nodeinfo: %d/%d", readIndex, nodeDB->getNumMeshNodes());
@@ -523,8 +633,8 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
case STATE_SEND_FILEMANIFEST: {
LOG_DEBUG("FromRadio=STATE_SEND_FILEMANIFEST");
// last element
if (config_state == filesManifest.size() ||
config_nonce == SPECIAL_NONCE_ONLY_NODES) { // also handles an empty filesManifest
if (config_state == filesManifest.size() || config_nonce == SPECIAL_NONCE_ONLY_NODES ||
config_nonce == SPECIAL_NONCE_BATCH_ONLY_NODES) { // also handles an empty filesManifest
config_state = 0;
filesManifest.clear();
// Skip to complete packet
@@ -579,7 +689,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
// Do we have a message from the mesh?
if (fromRadioScratch.which_payload_variant != 0) {
// Encapsulate as a FromRadio packet
size_t numbytes = pb_encode_to_bytes(buf, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch);
size_t numbytes = pb_encode_to_bytes(buf, MAX_TO_FROM_RADIO_SIZE, &meshtastic_FromRadio_msg, &fromRadioScratch);
// VERY IMPORTANT to not print debug messages while writing to fromRadioScratch - because we use that same buffer
// for logging (when we are encapsulating with protobufs)
+5 -3
View File
@@ -13,15 +13,14 @@
// Make sure that we never let our packets grow too large for one BLE packet
#define MAX_TO_FROM_RADIO_SIZE 512
#if meshtastic_FromRadio_size > MAX_TO_FROM_RADIO_SIZE
#error "meshtastic_FromRadio_size is too large for our BLE packets"
#endif
#if meshtastic_ToRadio_size > MAX_TO_FROM_RADIO_SIZE
#error "meshtastic_ToRadio_size is too large for our BLE packets"
#endif
#define SPECIAL_NONCE_ONLY_CONFIG 69420
#define SPECIAL_NONCE_ONLY_NODES 69421 // ( ͡° ͜ʖ ͡°)
#define SPECIAL_NONCE_BATCH 69422
#define SPECIAL_NONCE_BATCH_ONLY_NODES 69423
/**
* Provides our protobuf based API which phone/PC clients can use to talk to our device
@@ -88,6 +87,9 @@ class PhoneAPI
// Protect nodeInfoForPhone + nodeInfoQueue because NimBLE callbacks run in a separate FreeRTOS task.
concurrency::Lock nodeInfoMutex;
// When true, bundle multiple NodeInfos per FromRadio packet (opt-in via batch nonce)
bool batchNodeInfo = false;
meshtastic_ToRadio toRadioScratch = {
0}; // this is a static scratch object, any data must be copied elsewhere before returning
+2 -2
View File
@@ -190,7 +190,7 @@ void StreamAPI::emitRebooted()
fromRadioScratch.rebooted = true;
// LOG_DEBUG("Emitting reboot packet for serial shell");
emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch));
emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, MAX_TO_FROM_RADIO_SIZE, &meshtastic_FromRadio_msg, &fromRadioScratch));
}
void StreamAPI::emitLogRecord(meshtastic_LogRecord_Level level, const char *src, const char *format, va_list arg)
@@ -209,7 +209,7 @@ void StreamAPI::emitLogRecord(meshtastic_LogRecord_Level level, const char *src,
if (num_printed > 0 && fromRadioScratch.log_record.message[num_printed - 1] ==
'\n') // Strip any ending newline, because we have records for framing instead.
fromRadioScratch.log_record.message[num_printed - 1] = '\0';
emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch));
emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, MAX_TO_FROM_RADIO_SIZE, &meshtastic_FromRadio_msg, &fromRadioScratch));
}
/// Hookable to find out when connection changes
+2 -1
View File
@@ -31,5 +31,6 @@ bool Throttle::execute(uint32_t *lastExecutionMs, uint32_t minumumIntervalMs, vo
/// @param timeSpanMs The interval in milliseconds of the timespan
bool Throttle::isWithinTimespanMs(uint32_t lastExecutionMs, uint32_t timeSpanMs)
{
return (millis() - lastExecutionMs) < timeSpanMs;
uint32_t now = millis();
return (now - lastExecutionMs) < timeSpanMs;
}
+127 -36
View File
@@ -16,6 +16,20 @@ TransmitHistory *TransmitHistory::getInstance()
return transmitHistory;
}
TransmitHistory::StoredTimestamp TransmitHistory::makeStoredTimestamp(uint32_t seconds, uint8_t flags)
{
StoredTimestamp stored;
stored.seconds = seconds;
stored.flags = flags;
return stored;
}
TransmitHistory::StoredTimestamp TransmitHistory::decodeLegacyTimestamp(uint32_t seconds)
{
const bool isProbablyBootRelative = seconds > 0 && seconds <= LEGACY_BOOT_RELATIVE_MAX_SEC;
return makeStoredTimestamp(seconds, isProbablyBootRelative ? ENTRY_FLAG_BOOT_RELATIVE : ENTRY_FLAG_NONE);
}
void TransmitHistory::loadFromDisk()
{
spiLock->lock();
@@ -23,16 +37,33 @@ void TransmitHistory::loadFromDisk()
if (file) {
FileHeader header{};
if (file.read((uint8_t *)&header, sizeof(header)) == sizeof(header) && header.magic == MAGIC &&
header.version == VERSION && header.count <= MAX_ENTRIES) {
(header.version == 1 || header.version == VERSION) && header.count <= MAX_ENTRIES) {
for (uint8_t i = 0; i < header.count; i++) {
Entry entry{};
if (file.read((uint8_t *)&entry, sizeof(entry)) == sizeof(entry)) {
if (entry.epochSeconds > 0) {
history[entry.key] = entry.epochSeconds;
// Seed in-memory millis so throttle works even without RTC/GPS.
// Treating stored entries as "just sent" is safe — worst case the
// node waits one full interval before its first broadcast.
lastMillis[entry.key] = millis();
if (header.version == 1) {
LegacyEntry entry{};
if (file.read((uint8_t *)&entry, sizeof(entry)) == sizeof(entry) && entry.epochSeconds > 0) {
history[entry.key] = decodeLegacyTimestamp(entry.epochSeconds);
}
} else {
Entry entry{};
if (file.read((uint8_t *)&entry, sizeof(entry)) == sizeof(entry) && entry.epochSeconds > 0) {
history[entry.key] = makeStoredTimestamp(entry.epochSeconds, entry.flags);
// Do NOT seed lastMillis here.
//
// getLastSentToMeshMillis() reconstructs a millis()-relative value
// from the stored epoch, and Throttle::isWithinTimespanMs() uses
// the same unsigned subtraction pattern. Once getTime() has a valid
// wall-clock epoch comparable to stored values, recent reboots still
// throttle correctly while long power-off periods no longer look like
// "just sent" and incorrectly suppress the first send.
//
// Before RTC/NTP/GPS time is valid, persisted absolute epochs do not
// contribute, but boot-relative entries still suppress near-term reboot
// chatter via a narrow recovery window.
//
// If we seeded lastMillis to millis() here, every loaded entry would
// appear to have been sent at boot time, regardless of the true age
// of the last transmission. That was the regression behind #9901.
}
}
}
@@ -53,7 +84,8 @@ void TransmitHistory::setLastSentToMesh(uint16_t key)
lastMillis[key] = millis();
uint32_t now = getTime();
if (now >= 2) {
history[key] = now;
const uint8_t flags = (getRTCQuality() == RTCQualityNone) ? ENTRY_FLAG_BOOT_RELATIVE : ENTRY_FLAG_NONE;
history[key] = makeStoredTimestamp(now, flags);
dirty = true;
// Don't flush to disk on every transmit — flash has limited write endurance.
// The in-memory lastMillis map handles throttle during normal operation.
@@ -68,15 +100,84 @@ void TransmitHistory::setLastSentToMesh(uint16_t key)
}
}
#ifdef PIO_UNIT_TESTING
void TransmitHistory::setLastSentAtEpoch(uint16_t key, uint32_t epochSeconds)
{
if (epochSeconds > 0) {
history[key] = makeStoredTimestamp(epochSeconds, ENTRY_FLAG_NONE);
dirty = true;
} else {
history.erase(key);
lastMillis.erase(key);
}
}
void TransmitHistory::setLastSentAtBootRelative(uint16_t key, uint32_t secondsSinceBoot)
{
if (secondsSinceBoot > 0) {
history[key] = makeStoredTimestamp(secondsSinceBoot, ENTRY_FLAG_BOOT_RELATIVE);
dirty = true;
} else {
history.erase(key);
lastMillis.erase(key);
}
}
#endif
uint32_t TransmitHistory::getLastSentToMeshEpoch(uint16_t key) const
{
auto it = history.find(key);
if (it != history.end()) {
return it->second;
return it->second.seconds;
}
return 0;
}
uint32_t TransmitHistory::getLastSentAbsoluteMillis(uint32_t storedEpoch) const
{
uint32_t now = getTime();
if (now < 2) {
return 0;
}
if (storedEpoch > now) {
return 0;
}
uint32_t secondsAgo = now - storedEpoch;
uint32_t msAgo = secondsAgo * 1000;
if (secondsAgo > 86400 || msAgo / 1000 != secondsAgo) {
return 0;
}
return millis() - msAgo;
}
uint32_t TransmitHistory::getLastSentBootRelativeMillis(uint32_t storedSeconds) const
{
if (getRTCQuality() != RTCQualityNone) {
return 0;
}
uint32_t now = getTime();
if (storedSeconds <= now) {
uint32_t secondsAgo = now - storedSeconds;
if (secondsAgo > BOOT_RELATIVE_RECOVERY_WINDOW_SEC) {
return 0;
}
return millis() - (secondsAgo * 1000);
}
uint32_t secondsAhead = storedSeconds - now;
if (secondsAhead > BOOT_RELATIVE_RECOVERY_WINDOW_SEC) {
return 0;
}
return millis();
}
uint32_t TransmitHistory::getLastSentToMeshMillis(uint16_t key) const
{
// Prefer runtime millis value (accurate within this boot)
@@ -86,34 +187,23 @@ uint32_t TransmitHistory::getLastSentToMeshMillis(uint16_t key) const
}
// Fall back to epoch conversion (loaded from disk after reboot)
uint32_t storedEpoch = getLastSentToMeshEpoch(key);
if (storedEpoch == 0) {
auto it = history.find(key);
if (it == history.end() || it->second.seconds == 0) {
return 0; // No stored time — module has never sent
}
uint32_t now = getTime();
if (now < 2) {
// No valid RTC time yet — can't convert to millis. Return 0 so throttle doesn't block.
return 0;
// Convert to a millis()-relative timestamp: millis() - msAgo.
//
// The result may wrap if msAgo is larger than the current uptime, and that is
// intentional. Throttle::isWithinTimespanMs() also uses unsigned subtraction,
// so the reconstructed age is preserved across wraparound:
// - recent reboot, 5 min ago -> (millis() - lastMs) == 300000, still throttled
// - long reboot, 30 min ago -> (millis() - lastMs) == 1800000, allowed
if ((it->second.flags & ENTRY_FLAG_BOOT_RELATIVE) != 0) {
return getLastSentBootRelativeMillis(it->second.seconds);
}
if (storedEpoch > now) {
// Stored time is in the future (clock went backwards?) — treat as stale
return 0;
}
uint32_t secondsAgo = now - storedEpoch;
uint32_t msAgo = secondsAgo * 1000;
// Guard against overflow: if the transmit was very long ago, just return 0 (won't throttle)
if (secondsAgo > 86400 || msAgo / 1000 != secondsAgo) {
return 0;
}
// Convert to a millis()-relative timestamp: millis() - msAgo
// This gives a value that, when passed to Throttle::isWithinTimespanMs(value, interval),
// correctly reports whether the transmit was within interval ms.
return millis() - msAgo;
return getLastSentAbsoluteMillis(it->second.seconds);
}
bool TransmitHistory::saveToDisk()
@@ -141,12 +231,13 @@ bool TransmitHistory::saveToDisk()
file.write((uint8_t *)&header, sizeof(header));
uint8_t written = 0;
for (const auto &[key, epochSeconds] : history) {
for (const auto &[key, stored] : history) {
if (written >= MAX_ENTRIES)
break;
Entry entry{};
entry.key = key;
entry.epochSeconds = epochSeconds;
entry.epochSeconds = stored.seconds;
entry.flags = stored.flags;
file.write((uint8_t *)&entry, sizeof(entry));
written++;
}
+44 -4
View File
@@ -35,8 +35,25 @@ class TransmitHistory
*/
void setLastSentToMesh(uint16_t key);
#ifdef PIO_UNIT_TESTING
/**
* Get the last transmit epoch seconds for a given key, or 0 if unknown.
* Directly set the stored epoch for a key without touching the runtime lastMillis map.
* Intended for testing purposes: lets tests simulate "the last broadcast happened N
* seconds ago" without needing to fake the system clock.
*/
void setLastSentAtEpoch(uint16_t key, uint32_t epochSeconds);
/**
* Directly set a boot-relative timestamp (seconds since boot) for testing.
*/
void setLastSentAtBootRelative(uint16_t key, uint32_t secondsSinceBoot);
#endif
/**
* Get the raw persisted timestamp seconds for a given key, or 0 if unknown.
*
* The returned value is an absolute epoch when persisted with valid RTC/NTP/GPS time,
* or boot-relative seconds when ENTRY_FLAG_BOOT_RELATIVE is set.
*/
uint32_t getLastSentToMeshEpoch(uint16_t key) const;
@@ -64,13 +81,31 @@ class TransmitHistory
static constexpr const char *FILENAME = "/prefs/transmit_history.dat";
static constexpr uint32_t MAGIC = 0x54485354; // "THST"
static constexpr uint8_t VERSION = 1;
static constexpr uint8_t VERSION = 2;
static constexpr uint8_t MAX_ENTRIES = 16;
static constexpr uint32_t SAVE_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
static constexpr uint32_t BOOT_RELATIVE_RECOVERY_WINDOW_SEC = 2 * 60;
static constexpr uint32_t LEGACY_BOOT_RELATIVE_MAX_SEC = 365UL * 24 * 60 * 60;
enum EntryFlags : uint8_t {
ENTRY_FLAG_NONE = 0,
ENTRY_FLAG_BOOT_RELATIVE = 0x01,
};
struct StoredTimestamp {
uint32_t seconds = 0;
uint8_t flags = ENTRY_FLAG_NONE;
};
struct __attribute__((packed)) Entry {
uint16_t key;
uint32_t epochSeconds;
uint8_t flags;
};
struct __attribute__((packed)) LegacyEntry {
uint16_t key;
uint32_t epochSeconds;
};
struct __attribute__((packed)) FileHeader {
@@ -79,8 +114,13 @@ class TransmitHistory
uint8_t count;
};
std::map<uint16_t, uint32_t> history; // key -> epoch seconds (for disk persistence)
std::map<uint16_t, uint32_t> lastMillis; // key -> millis() value (for runtime throttle)
uint32_t getLastSentAbsoluteMillis(uint32_t storedEpoch) const;
uint32_t getLastSentBootRelativeMillis(uint32_t storedSeconds) const;
static StoredTimestamp makeStoredTimestamp(uint32_t seconds, uint8_t flags = ENTRY_FLAG_NONE);
static StoredTimestamp decodeLegacyTimestamp(uint32_t seconds);
std::map<uint16_t, StoredTimestamp> history; // key -> persisted transmit time
std::map<uint16_t, uint32_t> lastMillis; // key -> millis() value (for runtime throttle)
bool dirty = false;
uint32_t lastDiskSave = 0; // millis() of last disk flush
};
+1 -1
View File
@@ -102,7 +102,6 @@ static int32_t reconnectETH()
#ifndef DISABLE_NTP
LOG_INFO("Start NTP time client");
timeClient.begin();
timeClient.setUpdateInterval(60 * 60); // Update once an hour
#endif
@@ -159,6 +158,7 @@ static int32_t reconnectETH()
LOG_ERROR("NTP Update failed");
ntp_renew = millis() + 300 * 1000; // failure, retry every 5 minutes
}
timeClient.end(); // W5100S: release UDP socket for other services
}
#endif
@@ -42,6 +42,9 @@ PB_BIND(meshtastic_MeshPacket, meshtastic_MeshPacket, 2)
PB_BIND(meshtastic_NodeInfo, meshtastic_NodeInfo, 2)
PB_BIND(meshtastic_NodeInfoBatch, meshtastic_NodeInfoBatch, AUTO)
PB_BIND(meshtastic_MyNodeInfo, meshtastic_MyNodeInfo, AUTO)
+28 -3
View File
@@ -1037,6 +1037,12 @@ typedef struct _meshtastic_NodeInfo {
bool is_muted;
} meshtastic_NodeInfo;
/* Batched NodeInfo wrapper for efficient bulk transfer during want_config flow.
Allows multiple NodeInfo messages to be packed into a single FromRadio packet. */
typedef struct _meshtastic_NodeInfoBatch {
pb_callback_t items;
} meshtastic_NodeInfoBatch;
typedef PB_BYTES_ARRAY_T(16) meshtastic_MyNodeInfo_device_id_t;
/* Unique local debugging info for this node
Note: we don't include position or the user info, because that will come in the
@@ -1262,6 +1268,9 @@ typedef struct _meshtastic_FromRadio {
meshtastic_ClientNotification clientNotification;
/* Persistent data for device-ui */
meshtastic_DeviceUIConfig deviceuiConfig;
/* Batched NodeInfo messages for efficient bulk transfer.
Used when client opts in via special nonce value. */
meshtastic_NodeInfoBatch node_info_batch;
};
} meshtastic_FromRadio;
@@ -1421,6 +1430,7 @@ extern "C" {
#define meshtastic_MeshPacket_transport_mechanism_ENUMTYPE meshtastic_MeshPacket_TransportMechanism
#define meshtastic_MyNodeInfo_firmware_edition_ENUMTYPE meshtastic_FirmwareEdition
#define meshtastic_LogRecord_level_ENUMTYPE meshtastic_LogRecord_Level
@@ -1462,6 +1472,7 @@ extern "C" {
#define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0}
#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN}
#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0}
#define meshtastic_NodeInfoBatch_init_default {{{NULL}, NULL}}
#define meshtastic_MyNodeInfo_init_default {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN, 0}
#define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN}
#define meshtastic_QueueStatus_init_default {0, 0, 0, 0}
@@ -1495,6 +1506,7 @@ extern "C" {
#define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0}
#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN}
#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0}
#define meshtastic_NodeInfoBatch_init_zero {{{NULL}, NULL}}
#define meshtastic_MyNodeInfo_init_zero {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN, 0}
#define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN}
#define meshtastic_QueueStatus_init_zero {0, 0, 0, 0}
@@ -1626,6 +1638,7 @@ extern "C" {
#define meshtastic_NodeInfo_is_ignored_tag 11
#define meshtastic_NodeInfo_is_key_manually_verified_tag 12
#define meshtastic_NodeInfo_is_muted_tag 13
#define meshtastic_NodeInfoBatch_items_tag 1
#define meshtastic_MyNodeInfo_my_node_num_tag 1
#define meshtastic_MyNodeInfo_reboot_count_tag 8
#define meshtastic_MyNodeInfo_min_app_version_tag 11
@@ -1700,6 +1713,7 @@ extern "C" {
#define meshtastic_FromRadio_fileInfo_tag 15
#define meshtastic_FromRadio_clientNotification_tag 16
#define meshtastic_FromRadio_deviceuiConfig_tag 17
#define meshtastic_FromRadio_node_info_batch_tag 18
#define meshtastic_Heartbeat_nonce_tag 1
#define meshtastic_ToRadio_packet_tag 1
#define meshtastic_ToRadio_want_config_id_tag 3
@@ -1882,6 +1896,12 @@ X(a, STATIC, SINGULAR, BOOL, is_muted, 13)
#define meshtastic_NodeInfo_position_MSGTYPE meshtastic_Position
#define meshtastic_NodeInfo_device_metrics_MSGTYPE meshtastic_DeviceMetrics
#define meshtastic_NodeInfoBatch_FIELDLIST(X, a) \
X(a, CALLBACK, REPEATED, MESSAGE, items, 1)
#define meshtastic_NodeInfoBatch_CALLBACK pb_default_field_callback
#define meshtastic_NodeInfoBatch_DEFAULT NULL
#define meshtastic_NodeInfoBatch_items_MSGTYPE meshtastic_NodeInfo
#define meshtastic_MyNodeInfo_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, my_node_num, 1) \
X(a, STATIC, SINGULAR, UINT32, reboot_count, 8) \
@@ -1926,7 +1946,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,metadata,metadata), 13) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 14) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,fileInfo,fileInfo), 15) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,clientNotification,clientNotification), 16) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,deviceuiConfig,deviceuiConfig), 17)
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,deviceuiConfig,deviceuiConfig), 17) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,node_info_batch,node_info_batch), 18)
#define meshtastic_FromRadio_CALLBACK NULL
#define meshtastic_FromRadio_DEFAULT NULL
#define meshtastic_FromRadio_payload_variant_packet_MSGTYPE meshtastic_MeshPacket
@@ -1943,6 +1964,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,deviceuiConfig,deviceuiConfi
#define meshtastic_FromRadio_payload_variant_fileInfo_MSGTYPE meshtastic_FileInfo
#define meshtastic_FromRadio_payload_variant_clientNotification_MSGTYPE meshtastic_ClientNotification
#define meshtastic_FromRadio_payload_variant_deviceuiConfig_MSGTYPE meshtastic_DeviceUIConfig
#define meshtastic_FromRadio_payload_variant_node_info_batch_MSGTYPE meshtastic_NodeInfoBatch
#define meshtastic_ClientNotification_FIELDLIST(X, a) \
X(a, STATIC, OPTIONAL, UINT32, reply_id, 1) \
@@ -2098,6 +2120,7 @@ extern const pb_msgdesc_t meshtastic_StatusMessage_msg;
extern const pb_msgdesc_t meshtastic_MqttClientProxyMessage_msg;
extern const pb_msgdesc_t meshtastic_MeshPacket_msg;
extern const pb_msgdesc_t meshtastic_NodeInfo_msg;
extern const pb_msgdesc_t meshtastic_NodeInfoBatch_msg;
extern const pb_msgdesc_t meshtastic_MyNodeInfo_msg;
extern const pb_msgdesc_t meshtastic_LogRecord_msg;
extern const pb_msgdesc_t meshtastic_QueueStatus_msg;
@@ -2133,6 +2156,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
#define meshtastic_MqttClientProxyMessage_fields &meshtastic_MqttClientProxyMessage_msg
#define meshtastic_MeshPacket_fields &meshtastic_MeshPacket_msg
#define meshtastic_NodeInfo_fields &meshtastic_NodeInfo_msg
#define meshtastic_NodeInfoBatch_fields &meshtastic_NodeInfoBatch_msg
#define meshtastic_MyNodeInfo_fields &meshtastic_MyNodeInfo_msg
#define meshtastic_LogRecord_fields &meshtastic_LogRecord_msg
#define meshtastic_QueueStatus_fields &meshtastic_QueueStatus_msg
@@ -2156,9 +2180,11 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
#define meshtastic_ChunkedPayloadResponse_fields &meshtastic_ChunkedPayloadResponse_msg
/* Maximum encoded size of messages (where known) */
/* meshtastic_NodeInfoBatch_size depends on runtime parameters */
/* meshtastic_FromRadio_size depends on runtime parameters */
/* meshtastic_resend_chunks_size depends on runtime parameters */
/* meshtastic_ChunkedPayloadResponse_size depends on runtime parameters */
#define MESHTASTIC_MESHTASTIC_MESH_PB_H_MAX_SIZE meshtastic_FromRadio_size
#define MESHTASTIC_MESHTASTIC_MESH_PB_H_MAX_SIZE meshtastic_ToRadio_size
#define meshtastic_ChunkedPayload_size 245
#define meshtastic_ClientNotification_size 482
#define meshtastic_Compressed_size 239
@@ -2166,7 +2192,6 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
#define meshtastic_DeviceMetadata_size 54
#define meshtastic_DuplicatedPublicKey_size 0
#define meshtastic_FileInfo_size 236
#define meshtastic_FromRadio_size 510
#define meshtastic_Heartbeat_size 6
#define meshtastic_KeyVerificationFinal_size 65
#define meshtastic_KeyVerificationNumberInform_size 58
+19
View File
@@ -5,6 +5,8 @@
#include <Arduino.h>
#include <functional>
#if !MESHTASTIC_EXCLUDE_WEBSERVER
void initWebServer();
void createSSLCert();
@@ -24,3 +26,20 @@ class WebServerThread : private concurrency::OSThread
};
extern WebServerThread *webServerThread;
#else
// Stub implementations when web server is excluded
inline void initWebServer() {}
inline void createSSLCert() {}
class WebServerThread
{
public:
WebServerThread() {}
uint32_t requestRestart = 0;
void markActivity() {}
};
inline WebServerThread *webServerThread = nullptr;
#endif
+5 -3
View File
@@ -40,7 +40,7 @@
#include "modules/PositionModule.h"
#endif
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && !MESHTASTIC_EXCLUDE_ACCELEROMETER
#include "motion/AccelerometerThread.h"
#endif
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \
@@ -664,7 +664,8 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c, bool fromOthers)
case meshtastic_Config_device_tag:
LOG_INFO("Set config: Device");
config.has_device = true;
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && \
!MESHTASTIC_EXCLUDE_ACCELEROMETER
if (config.device.double_tap_as_button_press == false && c.payload_variant.device.double_tap_as_button_press == true &&
accelerometerThread->enabled == false) {
config.device.double_tap_as_button_press = c.payload_variant.device.double_tap_as_button_press;
@@ -766,7 +767,8 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c, bool fromOthers)
c.payload_variant.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
config.bluetooth.enabled = false;
}
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && \
!MESHTASTIC_EXCLUDE_ACCELEROMETER
if (config.display.wake_on_tap_or_motion == false && c.payload_variant.display.wake_on_tap_or_motion == true &&
accelerometerThread->enabled == false) {
config.display.wake_on_tap_or_motion = c.payload_variant.display.wake_on_tap_or_motion;
+6
View File
@@ -118,6 +118,12 @@ void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t cha
}
}
void NodeInfoModule::triggerImmediateNodeInfoCheck()
{
LOG_DEBUG("NodeInfo: scheduling immediate periodic check");
setIntervalFromNow(0);
}
meshtastic_MeshPacket *NodeInfoModule::allocReply()
{
// Only apply suppression when actually replying to someone else's request, not for periodic broadcasts.
+6
View File
@@ -24,6 +24,12 @@ class NodeInfoModule : public ProtobufModule<meshtastic_User>, private concurren
void sendOurNodeInfo(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false, uint8_t channel = 0,
bool _shorterTimeout = false);
/**
* Schedule an immediate NodeInfo periodic check.
* Used when external conditions change (for example time source quality).
*/
void triggerImmediateNodeInfoCheck();
protected:
/** Called to handle a particular incoming message
+1 -1
View File
@@ -4,7 +4,7 @@
#include "configuration.h"
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && !MESHTASTIC_EXCLUDE_ACCELEROMETER
#include "../concurrency/OSThread.h"
#ifdef HAS_BMA423
+3 -3
View File
@@ -145,7 +145,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
std::mutex toPhoneMutex;
std::atomic<size_t> toPhoneQueueSize{0};
// We use array here (and pay the cost of memcpy) to avoid dynamic memory allocations and frees across FreeRTOS tasks.
std::array<std::array<uint8_t, meshtastic_FromRadio_size>, NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE> toPhoneQueue{};
std::array<std::array<uint8_t, MAX_TO_FROM_RADIO_SIZE>, NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE> toPhoneQueue{};
std::array<size_t, NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE> toPhoneQueueByteSizes{};
// The onReadCallbackIsWaitingForData flag provides synchronization between the NimBLE task's onRead callback and our main
// task's runOnce. It's only set by onRead, and only cleared by runOnce.
@@ -245,7 +245,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
void runOnceHandleToPhoneQueue()
{
// Stack buffer for getFromRadio packet
uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0};
uint8_t fromRadioBytes[MAX_TO_FROM_RADIO_SIZE] = {0};
size_t numBytes = 0;
if (onReadCallbackIsWaitingForData || runOnceToPhoneCanPreloadNextPacket()) {
@@ -524,7 +524,7 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
}
// Pop from toPhoneQueue, protected by toPhoneMutex. Hold the mutex as briefly as possible.
uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0}; // Stack buffer for getFromRadio packet
uint8_t fromRadioBytes[MAX_TO_FROM_RADIO_SIZE] = {0}; // Stack buffer for getFromRadio packet
size_t numBytes = 0;
{ // scope for toPhoneMutex mutex
std::lock_guard<std::mutex> guard(bluetoothPhoneAPI->toPhoneMutex);
+1 -1
View File
@@ -25,7 +25,7 @@ static BLEDfuSecure bledfusecure; //
// This scratch buffer is used for various bluetooth reads/writes - but it is safe because only one bt operation can be in
// process at once
// static uint8_t trBytes[_max(_max(_max(_max(ToRadio_size, RadioConfig_size), User_size), MyNodeInfo_size), FromRadio_size)];
static uint8_t fromRadioBytes[meshtastic_FromRadio_size];
static uint8_t fromRadioBytes[MAX_TO_FROM_RADIO_SIZE];
static uint8_t toRadioBytes[meshtastic_ToRadio_size];
// Last ToRadio value received from the phone
+13
View File
@@ -0,0 +1,13 @@
#include "Nrf52SaadcLock.h"
#include "concurrency/Lock.h"
#include "configuration.h"
#ifdef ARCH_NRF52
namespace concurrency
{
static Lock nrf52SaadcLockInstance;
Lock *nrf52SaadcLock = &nrf52SaadcLockInstance;
} // namespace concurrency
#endif
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#ifdef ARCH_NRF52
namespace concurrency
{
class Lock;
/** Shared mutex for SAADC configuration and reads (VDD + battery analog path). */
extern Lock *nrf52SaadcLock;
} // namespace concurrency
#endif
+21 -5
View File
@@ -25,6 +25,8 @@
#include "power.h"
#include <power/PowerHAL.h>
#include "Nrf52SaadcLock.h"
#include "concurrency/LockGuard.h"
#include <hal/nrf_lpcomp.h>
#ifdef BQ25703A_ADDR
@@ -51,6 +53,10 @@ uint16_t getVDDVoltage();
void variant_shutdown() __attribute__((weak));
void variant_shutdown() {}
// Optional variant hook called each nrf52Loop(); e.g. for low-VDD System OFF.
void variant_nrf52LoopHook(void) __attribute__((weak));
void variant_nrf52LoopHook(void) {}
static nrfx_wdt_t nrfx_wdt = NRFX_WDT_INSTANCE(0);
static nrfx_wdt_channel_id nrfx_wdt_channel_id_nrf52_main;
@@ -74,11 +80,18 @@ bool powerHAL_isVBUSConnected()
bool powerHAL_isPowerLevelSafe()
{
static bool powerLevelSafe = true;
uint16_t threshold = SAFE_VDD_VOLTAGE_THRESHOLD * 1000; // convert V to mV
uint16_t hysteresis = SAFE_VDD_VOLTAGE_THRESHOLD_HYST * 1000;
#ifdef SAFE_VDD_VOLTAGE_THRESHOLD_MV
uint16_t threshold = SAFE_VDD_VOLTAGE_THRESHOLD_MV;
#else
uint16_t threshold = (uint16_t)(SAFE_VDD_VOLTAGE_THRESHOLD * 1000.0f + 0.5f); // convert V to mV
#endif
#ifdef SAFE_VDD_VOLTAGE_THRESHOLD_HYST_MV
uint16_t hysteresis = SAFE_VDD_VOLTAGE_THRESHOLD_HYST_MV;
#else
uint16_t hysteresis = (uint16_t)(SAFE_VDD_VOLTAGE_THRESHOLD_HYST * 1000.0f + 0.5f);
#endif
if (powerLevelSafe) {
if (getVDDVoltage() < threshold) {
@@ -125,11 +138,12 @@ void powerHAL_platformInit()
// get VDD voltage (in millivolts)
uint16_t getVDDVoltage()
{
// we use the same values as regular battery read so there is no conflict on SAADC
concurrency::LockGuard guard(concurrency::nrf52SaadcLock);
// Match battery read resolution; SAADC is shared with AnalogBatteryLevel in Power.cpp.
analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS);
// VDD range on NRF52840 is 1.8-3.3V so we need to remap analog reference to 3.6V
// let's hope battery reading runs in same task and we don't have race condition
analogReference(AR_INTERNAL);
uint16_t vddADCRead = analogReadVDD();
@@ -326,6 +340,8 @@ void nrf52Loop()
checkSDEvents();
reportLittleFSCorruptionOnce();
variant_nrf52LoopHook(); // Optional variant hook called each nrf52Loop();
}
#ifdef USE_SEMIHOSTING
+127 -21
View File
@@ -1,5 +1,6 @@
#include "TestUtil.h"
#include "TransmitHistory.h"
#include "gps/RTC.h"
#include <Throttle.h>
#include <unity.h>
@@ -161,44 +162,141 @@ static void test_save_and_load_round_trip()
// After loadFromDisk, millis should be seeded (non-zero) for stored entries
uint32_t restoredMillis = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP);
if (restoredNodeInfo > 0) {
// If epoch was stored, millis should be seeded from load
// If epoch was stored (set seconds ago), epoch-conversion gives elapsed ≈ 0 s,
// so getLastSentToMeshMillis() should return a non-zero value.
TEST_ASSERT_NOT_EQUAL(0, restoredMillis);
}
}
// --- Boot without RTC scenario ---
static void test_load_seeds_millis_even_without_rtc()
// Crash-reboot protection: a send that happened moments before the reboot must still
// throttle after reload. This works because getLastSentToMeshMillis() reconstructs
// a millis()-relative timestamp from the stored epoch, and Throttle uses unsigned
// subtraction so the age survives wraparound even when uptime is near zero.
static void test_boot_after_recent_send_still_throttles()
{
// This tests the critical crash-reboot scenario:
// After loadFromDisk(), even if getTime() returns 0 (no RTC),
// lastMillis should be seeded so throttle blocks immediate re-broadcast.
transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP);
transmitHistory->saveToDisk();
// Simulate reboot: destroy and recreate
// Simulate reboot
delete transmitHistory;
transmitHistory = nullptr;
transmitHistory = TransmitHistory::getInstance();
transmitHistory->loadFromDisk();
// The key insight: after load, getLastSentToMeshMillis should return non-zero
// because loadFromDisk seeds lastMillis[key] = millis() for every loaded entry.
// This ensures throttle works even without RTC.
// Epoch was set seconds ago; reconstructed age is still within the 10-min window.
uint32_t result = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP);
uint32_t epoch = transmitHistory->getLastSentToMeshEpoch(meshtastic_PortNum_NODEINFO_APP);
if (epoch > 0) {
// Data was persisted — millis must be seeded
TEST_ASSERT_NOT_EQUAL(0, result);
// And it should cause throttle to block (treating as "just sent")
bool withinInterval = Throttle::isWithinTimespanMs(result, 10 * 60 * 1000);
TEST_ASSERT_TRUE(withinInterval);
if (epoch == 0) {
TEST_IGNORE_MESSAGE("Epoch not persisted; skipping");
return;
}
// If epoch == 0, RTC wasn't available — no data was saved, so nothing to restore.
// This is expected on platforms without RTC during the very first boot.
TEST_ASSERT_NOT_EQUAL(0, result);
bool withinInterval = Throttle::isWithinTimespanMs(result, 10 * 60 * 1000);
TEST_ASSERT_TRUE(withinInterval);
}
// Regression test for issue #9901:
// A device powered off for longer than the throttle window must broadcast NodeInfo
// on its next boot — it must not be silenced because loadFromDisk() once treated
// every loaded entry as "just sent" by seeding lastMillis to millis() at boot.
static void test_boot_after_long_gap_allows_nodeinfo()
{
if (getRTCQuality() <= RTCQualityNone) {
TEST_IGNORE_MESSAGE("No RTC available; skipping epoch-dependent test");
return;
}
uint32_t now = getTime();
// Simulate: last NodeInfo sent 30 minutes ago (outside the 10-min throttle window)
transmitHistory->setLastSentAtEpoch(meshtastic_PortNum_NODEINFO_APP, now - (30 * 60));
transmitHistory->saveToDisk();
// Simulate reboot
delete transmitHistory;
transmitHistory = nullptr;
transmitHistory = TransmitHistory::getInstance();
transmitHistory->loadFromDisk();
uint32_t restoredEpoch = transmitHistory->getLastSentToMeshEpoch(meshtastic_PortNum_NODEINFO_APP);
if (restoredEpoch == 0) {
TEST_IGNORE_MESSAGE("Epoch not persisted; skipping");
return;
}
uint32_t restoredMs = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP);
bool throttled = (restoredMs != 0) && Throttle::isWithinTimespanMs(restoredMs, 10 * 60 * 1000);
TEST_ASSERT_FALSE_MESSAGE(throttled, "NodeInfo must not be throttled after a 30-min gap (#9901)");
}
// Complementary: a rapid reboot must still throttle (crash-loop protection), even
// though the reconstructed lastMs may wrap because current uptime is small.
static void test_boot_within_throttle_window_still_throttles()
{
if (getRTCQuality() <= RTCQualityNone) {
TEST_IGNORE_MESSAGE("No RTC available; skipping epoch-dependent test");
return;
}
uint32_t now = getTime();
// Simulate: last NodeInfo sent 5 minutes ago (inside the 10-min throttle window)
transmitHistory->setLastSentAtEpoch(meshtastic_PortNum_NODEINFO_APP, now - (5 * 60));
transmitHistory->saveToDisk();
// Simulate reboot
delete transmitHistory;
transmitHistory = nullptr;
transmitHistory = TransmitHistory::getInstance();
transmitHistory->loadFromDisk();
uint32_t restoredEpoch = transmitHistory->getLastSentToMeshEpoch(meshtastic_PortNum_NODEINFO_APP);
if (restoredEpoch == 0) {
TEST_IGNORE_MESSAGE("Epoch not persisted; skipping");
return;
}
uint32_t restoredMs = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP);
bool throttled = (restoredMs != 0) && Throttle::isWithinTimespanMs(restoredMs, 10 * 60 * 1000);
TEST_ASSERT_TRUE_MESSAGE(throttled, "NodeInfo must still be throttled when last send was within the 10-min window");
}
static void test_boot_without_time_source_still_throttles_recent_restart()
{
setBootRelativeTimeForUnitTest(32);
transmitHistory->setLastSentAtBootRelative(meshtastic_PortNum_NODEINFO_APP, 32);
transmitHistory->saveToDisk();
delete transmitHistory;
transmitHistory = nullptr;
transmitHistory = TransmitHistory::getInstance();
setBootRelativeTimeForUnitTest(31);
transmitHistory->loadFromDisk();
uint32_t restoredMs = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP);
bool throttled = (restoredMs != 0) && Throttle::isWithinTimespanMs(restoredMs, 10 * 60 * 1000);
TEST_ASSERT_TRUE_MESSAGE(throttled, "Recent no-RTC reboots should still suppress duplicate NodeInfo");
}
static void test_boot_without_time_source_expires_boot_relative_history()
{
setBootRelativeTimeForUnitTest(32);
transmitHistory->setLastSentAtBootRelative(meshtastic_PortNum_NODEINFO_APP, 32);
transmitHistory->saveToDisk();
delete transmitHistory;
transmitHistory = nullptr;
transmitHistory = TransmitHistory::getInstance();
setBootRelativeTimeForUnitTest(400);
transmitHistory->loadFromDisk();
uint32_t restoredMs = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP);
TEST_ASSERT_EQUAL_UINT32_MESSAGE(0, restoredMs, "Boot-relative history should only suppress near-term restarts");
}
void setup()
@@ -222,7 +320,15 @@ void setup()
// Persistence
RUN_TEST(test_save_and_load_round_trip);
RUN_TEST(test_load_seeds_millis_even_without_rtc);
RUN_TEST(test_boot_after_recent_send_still_throttles);
// Issue #9901 regression tests
RUN_TEST(test_boot_after_long_gap_allows_nodeinfo);
RUN_TEST(test_boot_within_throttle_window_still_throttles);
// No-RTC regression tests
RUN_TEST(test_boot_without_time_source_still_throttles_recent_restart);
RUN_TEST(test_boot_without_time_source_expires_boot_relative_history);
exit(UNITY_END());
}
+4 -4
View File
@@ -8,7 +8,7 @@ platform =
platformio/espressif32@6.13.0
platform_packages =
# renovate: datasource=custom.pio depName=platformio/tool-mklittlefs packageName=platformio/tool/tool-mklittlefs
platformio/tool-mklittlefs@^1.203.210628
platformio/tool-mklittlefs@1.203.210628
extra_scripts =
${env.extra_scripts}
@@ -70,11 +70,11 @@ lib_deps =
# renovate: datasource=git-refs depName=meshtastic-esp32_https_server packageName=https://github.com/meshtastic/esp32_https_server gitBranch=master
https://github.com/meshtastic/esp32_https_server/archive/b78f12c86ea65c3ca08968840ff554ff7ed69b60.zip
# renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino
h2zero/NimBLE-Arduino@^1.4.3
h2zero/NimBLE-Arduino@1.4.3
# renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master
https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip
# renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib
https://github.com/lewisxhe/XPowersLib/archive/v0.3.3.zip
# renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
lewisxhe/XPowersLib@0.3.3
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
rweather/Crypto@0.4.0
+15 -11
View File
@@ -4,25 +4,29 @@
extends = esp32_common
custom_esp32_kind = esp32
build_src_filter =
${esp32_common.build_src_filter}
-<modules/esp32/PaxcounterModule.cpp>
-<mesh/http/>
build_flags =
${esp32_common.build_flags}
-DMESHTASTIC_EXCLUDE_AUDIO=1
; Override lib_deps to use environmental_extra_no_bsec instead of environmental_extra
; BSEC library uses ~3.5KB DRAM which causes overflow on original ESP32 targets
-DMESHTASTIC_EXCLUDE_ACCELEROMETER=1
-DMESHTASTIC_EXCLUDE_PAXCOUNTER=1
-DMESHTASTIC_EXCLUDE_WEBSERVER=1
-DMESHTASTIC_EXCLUDE_RANGETEST=1
lib_deps =
${arduino_base.lib_deps}
${networking_base.lib_deps}
${networking_extra.lib_deps}
${radiolib_base.lib_deps}
${environmental_base.lib_deps}
${environmental_extra_no_bsec.lib_deps}
${radiolib_base.lib_deps}
# renovate: datasource=git-refs depName=meshtastic-esp32_https_server packageName=https://github.com/meshtastic/esp32_https_server gitBranch=master
https://github.com/meshtastic/esp32_https_server/archive/b78f12c86ea65c3ca08968840ff554ff7ed69b60.zip
# renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino
h2zero/NimBLE-Arduino@^1.4.3
# renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master
https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip
# renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib
https://github.com/lewisxhe/XPowersLib/archive/v0.3.3.zip
h2zero/NimBLE-Arduino@1.4.3
# renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
lewisxhe/XPowersLib@0.3.3
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
rweather/Crypto@0.4.0
rweather/Crypto@0.4.0
@@ -9,7 +9,7 @@ custom_meshtastic_display_name = Nano G1 Explorer
custom_meshtastic_tags = B&Q
extends = esp32_base
board = ttgo-t-beam
board = ttgo-tbeam
build_flags =
${esp32_base.build_flags}
-D NANO_G1_EXPLORER
+1 -1
View File
@@ -9,7 +9,7 @@ custom_meshtastic_display_name = Nano G1
custom_meshtastic_tags = B&Q
extends = esp32_base
board = ttgo-t-beam
board = ttgo-tbeam
build_flags =
${esp32_base.build_flags}
-D NANO_G1
+1 -1
View File
@@ -9,7 +9,7 @@ custom_meshtastic_display_name = Station G1
custom_meshtastic_tags = B&Q
extends = esp32_base
board = ttgo-t-beam
board = ttgo-tbeam
build_flags =
${esp32_base.build_flags}
-D STATION_G1
+2 -4
View File
@@ -10,14 +10,12 @@ custom_meshtastic_images = tbeam.svg
custom_meshtastic_tags = LilyGo
extends = esp32_base
board = ttgo-t-beam
board = ttgo-tbeam
board_check = true
build_flags = ${esp32_base.build_flags}
-D TBEAM_V10
-I variants/esp32/tbeam
-DBOARD_HAS_PSRAM
-mfix-esp32-psram-cache-issue
-ULED_BUILTIN
upload_speed = 921600
@@ -32,5 +30,5 @@ lib_deps =
${env:tbeam.lib_deps}
# renovate: datasource=github-tags depName=meshtastic-st7796 packageName=meshtastic/st7796
https://github.com/meshtastic/st7796/archive/1.0.5.zip
# renovate: datasource=custom.pio depName=lewisxhe-SensorLib packageName=lewisxhe/library/SensorLib
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
lewisxhe/SensorLib@0.3.4
+1 -1
View File
@@ -9,7 +9,7 @@ custom_meshtastic_tags = LilyGo
board_level = extra
extends = esp32_base
board = ttgo-t-beam
board = ttgo-tbeam
build_flags =
${esp32_base.build_flags}
-D TBEAM_V07
+3
View File
@@ -3,6 +3,9 @@ extends = esp32_common
platform =
# Do not renovate until we have switched to pioarduino tagged builds
https://github.com/Jason2866/platform-espressif32/archive/22faa566df8c789000f8136cd8d0aca49617af55.zip
platform_packages =
# HACK: This release was automatically removed upstream
framework-arduinoespressif32 @ https://github.com/vidplace7/platform-espressif32/releases/download/meshtastic-esp32c6/framework-arduinoespressif32-all-release_v5.1-124d64e.zip
build_flags =
${arduino_base.build_flags}
-Wall
+4 -1
View File
@@ -68,7 +68,10 @@ build_flags =
-D INPUTDRIVER_BUTTON_TYPE=0
-D HAS_SCREEN=1
-D HAS_TFT=1
-D RAM_SIZE=1860
-D MAP_TILES_GREY ; required for 2MB PSRAM
-D RAM_SIZE=1432
-D STBI_ARENA_SIZE=450000
-D LV_CACHE_DEF_SIZE=0
-D LV_LVGL_H_INCLUDE_SIMPLE
-D LV_CONF_INCLUDE_SIMPLE
-D LV_COMP_CONF_INCLUDE_SIMPLE
@@ -8,15 +8,17 @@ upload_protocol = esptool
build_flags =
${esp32s3_base.build_flags}
-D M5STACK_CARDPUTER_ADV
-D BOARD_HAS_PSRAM
-D ARDUINO_USB_CDC_ON_BOOT=1
-I variants/esp32s3/m5stack_cardputer_adv
build_src_filter =
${esp32s3_base.build_src_filter}
+<../variants/esp32s3/m5stack_cardputer_adv>
lib_deps =
${esp32s3_base.lib_deps}
# renovate: datasource=git-refs depName=meshtastic-st7789 packageName=https://github.com/meshtastic/st7789 gitBranch=main
https://github.com/meshtastic/st7789/archive/9ee76d6b18b9a8f45a2c5cae06b1134a587691eb.zip
# # renovate: datasource=github-tags depName=pschatzmann_arduino-audio-driver packageName=pschatzmann/arduino-audio-driver
# https://github.com/pschatzmann/arduino-audio-driver/archive/v0.2.1.zip
# renovate: datasource=github-tags depName=pschatzmann_arduino-audio-driver packageName=pschatzmann/arduino-audio-driver
https://github.com/pschatzmann/arduino-audio-driver/archive/v0.2.1.zip
# renovate: datasource=git-refs depName=ESP8266Audio packageName=https://github.com/meshtastic/ESP8266Audio gitBranch=meshtastic-2.0.0-dacfix
https://github.com/meshtastic/ESP8266Audio/archive/343024632ee78d6216907b2353fc943a62422d80.zip
# renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM
@@ -0,0 +1,40 @@
#include "AudioBoard.h"
#include "configuration.h"
DriverPins PinsAudioBoardES8311;
AudioBoard board(AudioDriverES8311, PinsAudioBoardES8311);
// M5stack Cardputer ADV specific init
void lateInitVariant()
{
// AudioDriverLogger.begin(Serial, AudioDriverLogLevel::Debug);
// I2C: function, scl, sda
PinsAudioBoardES8311.addI2C(PinFunction::CODEC, Wire);
// I2S: function, mclk, bck, ws, data_out, data_in
PinsAudioBoardES8311.addI2S(PinFunction::CODEC, DAC_I2S_MCLK, DAC_I2S_BCK, DAC_I2S_WS, DAC_I2S_DOUT, DAC_I2S_DIN);
// configure codec
CodecConfig cfg;
cfg.input_device = ADC_INPUT_LINE1;
cfg.output_device = DAC_OUTPUT_ALL;
cfg.i2s.bits = BIT_LENGTH_16BITS;
cfg.i2s.rate = RATE_44K;
board.begin(cfg);
// extra ES8311 init
auto es8311_write_reg = [](uint8_t reg, uint8_t val) {
Wire.beginTransmission(0x18); // ES8311 i2c address
Wire.write(reg);
Wire.write(val);
Wire.endTransmission();
};
es8311_write_reg(0x00, 0x80); // reset, power on
es8311_write_reg(0x01, 0xB5); // MCLK = BCLK
es8311_write_reg(0x02, 0x18); // CLOCK_MANAGER/ MULT_PRE=3
es8311_write_reg(0x0D, 0x01); // analog power up
es8311_write_reg(0x12, 0x00); // DAC power up
es8311_write_reg(0x13, 0x10); // enable HP drive
es8311_write_reg(0x32, 0xBF); // DAC volume (0dB)
es8311_write_reg(0x37, 0x08); // EQ bypass
}
@@ -0,0 +1,123 @@
/*
Most of the Meshtastic firmware uses preprocessor macros throughout the code to support different hardware variants.
NicheGraphics attempts a different approach:
Per-device config takes place in this setupNicheGraphics() method
(And a small amount in platformio.ini)
This file sets up InkHUD for Heltec VM-E290.
Different NicheGraphics UIs and different hardware variants will each have their own setup procedure.
*/
#pragma once
#include "configuration.h"
#include "mesh/MeshModule.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
// InkHUD-specific components
// ---------------------------
// #include "graphics/niche/InkHUD/InkHUD.h"
#include "graphics/niche/InkHUD/WindowManager.h"
// Applets
#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h"
#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h"
#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h"
#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h"
#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h"
#include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
// Shared NicheGraphics components
// --------------------------------
#include "graphics/niche/Drivers/Backlight/LatchingBacklight.h"
#include "graphics/niche/Drivers/EInk/DEPG0290BNS800.h"
#include "graphics/niche/Inputs/TwoButton.h"
void setupNicheGraphics()
{
using namespace NicheGraphics;
// SPI
// -----------------------------
// Display is connected to HSPI
SPIClass *hspi = new SPIClass(HSPI);
hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
// E-Ink Driver
// -----------------------------
// Use E-Ink driver
Drivers::EInk *driver = new Drivers::DEPG0290BNS800;
driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY);
// InkHUD
// ----------------------------
InkHUD::InkHUD *inkhud = InkHUD::InkHUD::getInstance();
// Set the driver
inkhud->setDriver(driver);
// Set how many FAST updates per FULL update
// Set how unhealthy additional FAST updates beyond this number are
inkhud->setDisplayResilience(7, 1.5);
// Prepare fonts
InkHUD::Applet::fontLarge = FREESANS_9PT_WIN1252;
InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
// Init settings, and customize defaults
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.batteryIcon = true; // Device definitely has a battery
// Setup backlight
// Note: AUX button behavior configured further down
Drivers::LatchingBacklight *backlight = Drivers::LatchingBacklight::getInstance();
backlight->setPin(PIN_EINK_EN);
// Pick applets
// Note: order of applets determines priority of "auto-show" feature
// Optional arguments for defaults:
// - is activated?
// - is autoshown?
// - is foreground on a specific tile (index)?
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
// inkhud->addApplet("Basic", new InkHUD::BasicExampleApplet);
// inkhud->addApplet("NewMsg", new InkHUD::NewMsgExampleApplet);
// Start running InkHUD
inkhud->begin();
// Buttons
// --------------------------
Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance(); // A shared NicheGraphics component
// Setup the main user button (0)
buttons->setWiring(0, BUTTON_PIN);
buttons->setHandlerShortPress(0, []() { InkHUD::InkHUD::getInstance()->shortpress(); });
buttons->setHandlerLongPress(0, []() { InkHUD::InkHUD::getInstance()->longpress(); });
// Setup the aux button (1)
// Bonus feature of VME290
buttons->setWiring(1, BUTTON_PIN_SECONDARY);
buttons->setHandlerShortPress(1, []() { InkHUD::InkHUD::getInstance()->nextTile(); });
buttons->start();
}
#endif
@@ -0,0 +1,43 @@
#ifndef Pins_Arduino_h
#define Pins_Arduino_h
#include <stdint.h>
#define USB_VID 0x303a
#define USB_PID 0x1001
#if defined(T5_S3_EPAPER_PRO_V1)
// The default Wire will be mapped to RTC, Touch, BQ25896, and BQ27220
static const uint8_t SDA = 6;
static const uint8_t SCL = 5;
// Default SPI will be mapped to Radio
static const uint8_t SS = 46;
static const uint8_t MOSI = 17;
static const uint8_t MISO = 8;
static const uint8_t SCK = 18;
#define SPI_MOSI (17)
#define SPI_SCK (18)
#define SPI_MISO (8)
#define SPI_CS (16)
#else // T5_S3_EPAPER_PRO_V2
// The default Wire will be mapped to RTC, Touch, PCA9535, BQ25896, and BQ27220
static const uint8_t SDA = 39;
static const uint8_t SCL = 40;
// Default SPI will be mapped to Radio
static const uint8_t SS = 46;
static const uint8_t MOSI = 13;
static const uint8_t MISO = 21;
static const uint8_t SCK = 14;
#define SPI_MOSI (13)
#define SPI_SCK (14)
#define SPI_MISO (21)
#define SPI_CS (12)
#endif
#endif /* Pins_Arduino_h */
@@ -0,0 +1,61 @@
[t5s3_epaper_base]
extends = esp32s3_base
board = t5-epaper-s3
board_build.partition = default_16MB.csv
board_check = true
upload_protocol = esptool
build_flags = -fno-strict-aliasing
${esp32_base.build_flags}
-I variants/esp32s3/t5s3_epaper
-D T5_S3_EPAPER_PRO
-D USE_EINK
-D USE_EINK_PARALLELDISPLAY
-D PRIVATE_HW
-D TOUCH_THRESHOLD_X=60
-D TOUCH_THRESHOLD_Y=40
-D TIME_LONG_PRESS=500
; -D EINK_LIMIT_GHOSTING_PX=5000
-D EPD_FULLSLOW_PERIOD=100
-D FAST_EPD_PARTIAL_UPDATE_BUG ; use rect area update instead of partial
build_src_filter =
${esp32s3_base.build_src_filter}
+<../variants/esp32s3/t5s3_epaper>
lib_deps =
${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
lewisxhe/SensorLib@0.3.4
https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip
https://github.com/mverch67/FastEPD/archive/0df1bff329b6fc782e062f611758880762340647.zip
[env:t5s3_epaper_inkhud]
extends = t5s3_epaper_base, inkhud
board_level = extra # inkhud port is incomplete
board_check = false
build_flags =
${t5s3_epaper_base.build_flags}
${inkhud.build_flags}
-D SDCARD_USE_SPI1
-D T5_S3_EPAPER_PRO_V2
build_src_filter =
${t5s3_epaper_base.build_src_filter}
${inkhud.build_src_filter}
lib_deps =
${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
${t5s3_epaper_base.lib_deps}
[env:t5s3-epaper-v1] ; H752
extends = t5s3_epaper_base
build_flags =
${t5s3_epaper_base.build_flags}
-D T5_S3_EPAPER_PRO_V1
-D GPS_DEFAULT_NOT_PRESENT=1
[env:t5s3-epaper-v2] ; H752-01
extends = t5s3_epaper_base
build_flags =
${t5s3_epaper_base.build_flags}
-D T5_S3_EPAPER_PRO_V2
-D SDCARD_USE_SPI1
-D GPS_POWER_TOGGLE
+47
View File
@@ -0,0 +1,47 @@
#include "configuration.h"
#ifdef T5_S3_EPAPER_PRO
#include "TouchDrvGT911.hpp"
#include "Wire.h"
#include "input/TouchScreenImpl1.h"
TouchDrvGT911 touch;
bool readTouch(int16_t *x, int16_t *y)
{
if (!digitalRead(GT911_PIN_INT)) {
int16_t raw_x;
int16_t raw_y;
if (touch.getPoint(&raw_x, &raw_y)) {
// rotate 90° for landscape
*x = raw_y;
*y = EPD_WIDTH - 1 - raw_x;
LOG_DEBUG("touched(%d/%d)", *x, *y);
return true;
}
}
return false;
}
void earlyInitVariant()
{
pinMode(LORA_CS, OUTPUT);
digitalWrite(LORA_CS, HIGH);
pinMode(SDCARD_CS, OUTPUT);
digitalWrite(SDCARD_CS, HIGH);
pinMode(BOARD_BL_EN, OUTPUT);
}
// T5-S3-ePaper Pro specific (late-) init
void lateInitVariant(void)
{
touch.setPins(GT911_PIN_RST, GT911_PIN_INT);
if (touch.begin(Wire, GT911_SLAVE_ADDRESS_L, GT911_PIN_SDA, GT911_PIN_SCL)) {
touchScreenImpl1 = new TouchScreenImpl1(EPD_WIDTH, EPD_HEIGHT, readTouch);
touchScreenImpl1->init();
} else {
LOG_ERROR("Failed to find touch controller!");
}
}
#endif
+92
View File
@@ -0,0 +1,92 @@
// Display (E-Ink) ED047TC1 - 8bit parallel
#define EPD_WIDTH 960
#define EPD_HEIGHT 540
#define CANNED_MESSAGE_MODULE_ENABLE 1
#define USE_VIRTUAL_KEYBOARD 1
#if defined(T5_S3_EPAPER_PRO_V1)
#define BOARD_BL_EN 40
#else
#define BOARD_BL_EN 11
#endif
#define I2C_SDA SDA
#define I2C_SCL SCL
#define HAS_TOUCHSCREEN 1
#define GT911_PIN_SDA SDA
#define GT911_PIN_SCL SCL
#if defined(T5_S3_EPAPER_PRO_V1)
#define GT911_PIN_INT 15
#define GT911_PIN_RST 41
#else
#define GT911_PIN_INT 3
#define GT911_PIN_RST 9
#endif
#define PCF85063_RTC 0x51
#define HAS_RTC 1
#define PCF85063_INT 2
#define USE_POWERSAVE
#define SLEEP_TIME 120
// GPS
#if !defined(T5_S3_EPAPER_PRO_V1)
#define GPS_RX_PIN 44
#define GPS_TX_PIN 43
#endif
#if defined(T5_S3_EPAPER_PRO_V1)
#define BUTTON_PIN 48
#define PIN_BUTTON2 0
#define ALT_BUTTON_PIN PIN_BUTTON2
#else
#define BUTTON_PIN 0
#endif
// SD card
#define HAS_SDCARD
#define SDCARD_CS SPI_CS
#define SD_SPI_FREQUENCY 75000000U
// battery charger BQ25896
#define HAS_PPM 1
#define XPOWERS_CHIP_BQ25896
// battery quality management BQ27220
#define HAS_BQ27220 1
#define BQ27220_I2C_SDA SDA
#define BQ27220_I2C_SCL SCL
#define BQ27220_DESIGN_CAPACITY 1500
// LoRa
#define USE_SX1262
#define USE_SX1268
#define LORA_SCK SCK
#define LORA_MISO MISO
#define LORA_MOSI MOSI
#define LORA_CS 46
#define LORA_DIO0 -1
#if defined(T5_S3_EPAPER_PRO_V1)
#define LORA_RESET 43
#define LORA_DIO1 3 // SX1262 IRQ
#define LORA_DIO2 44 // SX1262 BUSY
#define LORA_DIO3
#else
#define LORA_RESET 1
#define LORA_DIO1 10 // SX1262 IRQ
#define LORA_DIO2 47 // SX1262 BUSY
#define LORA_DIO3
#endif
#define SX126X_CS LORA_CS
#define SX126X_DIO1 LORA_DIO1
#define SX126X_BUSY LORA_DIO2
#define SX126X_RESET LORA_RESET
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 2.4
+2 -2
View File
@@ -34,8 +34,8 @@ lib_deps =
adafruit/Adafruit seesaw Library@1.7.9
# renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main
https://github.com/RAKWireless/RAK12034-BMX160/archive/dcead07ffa267d3c906e9ca4a1330ab989e957e2.zip
# renovate: datasource=custom.pio depName=adafruit/Adafruit BME680 Library packageName=adafruit/library/Adafruit BME680
adafruit/Adafruit BME680 Library@^2.0.5
# renovate: datasource=custom.pio depName=Adafruit_BME680 packageName=adafruit/library/Adafruit BME680 Library
adafruit/Adafruit BME680 Library@2.0.6
build_flags =
${arduino_base.build_flags}
@@ -12,4 +12,5 @@ build_flags = ${nrf52840_base.build_flags}
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW-ThinkNode-M4>
lib_deps =
${nrf52840_base.lib_deps}
lewisxhe/PCF8563_Library@^1.0.1
# renovate: datasource=custom.pio depName=PCF8563 packageName=lewisxhe/library/PCF8563_Library
lewisxhe/PCF8563_Library@1.0.1
@@ -12,9 +12,15 @@ static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11
RADIOLIB_NC};
static const Module::RfSwitchMode_t rfswitch_table[] = {
// mode DIO5 DIO6 DIO7
{LR11x0::MODE_STBY, {LOW, LOW, LOW}}, {LR11x0::MODE_RX, {LOW, HIGH, LOW}},
{LR11x0::MODE_TX, {HIGH, HIGH, LOW}}, {LR11x0::MODE_TX_HP, {HIGH, LOW, LOW}},
{LR11x0::MODE_TX_HF, {LOW, LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW, HIGH}},
{LR11x0::MODE_WIFI, {LOW, LOW, LOW}}, END_OF_MODE_TABLE,
// clang-format off
// mode DIO5 DIO6 DIO7
{LR11x0::MODE_STBY, {LOW, LOW, LOW}},
{LR11x0::MODE_RX, {LOW, HIGH, LOW}},
{LR11x0::MODE_TX, {HIGH, HIGH, LOW}},
{LR11x0::MODE_TX_HP, {HIGH, LOW, LOW}},
{LR11x0::MODE_TX_HF, {LOW, LOW, LOW}},
{LR11x0::MODE_GNSS, {LOW, LOW, HIGH}},
{LR11x0::MODE_WIFI, {LOW, LOW, LOW}},
END_OF_MODE_TABLE,
// clang-format on
};

Some files were not shown because too many files have changed in this diff Show More