mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-08 13:10:03 +10:00
Compare commits
87
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b9e06b05f | ||
|
|
288d7a3e7b | ||
|
|
4990bf51e3 | ||
|
|
b2c8cbb78d | ||
|
|
8061f83704 | ||
|
|
a3b49b9028 | ||
|
|
17945884a5 | ||
|
|
7fdee353b5 | ||
|
|
1116f06139 | ||
|
|
16cf962351 | ||
|
|
12f0a74557 | ||
|
|
2dd9c5eef2 | ||
|
|
98963218ad | ||
|
|
6628c9e66e | ||
|
|
c728cfdaf4 | ||
|
|
d96770007d | ||
|
|
9322bcdb21 | ||
|
|
1370b234eb | ||
|
|
9ba44bfbfb | ||
|
|
2f19a1d7a4 | ||
|
|
71c8143f72 | ||
|
|
222d3e6b62 | ||
|
|
2e6519bb98 | ||
|
|
934c3fa8be | ||
|
|
726d539174 | ||
|
|
e7ee4bea18 | ||
|
|
7f0cd70c07 | ||
|
|
d5fe7dc9e6 | ||
|
|
98efb25a0c | ||
|
|
8dc3c0541b | ||
|
|
efd2613bd7 | ||
|
|
fc6c89abeb | ||
|
|
c7feef00c0 | ||
|
|
f88bc732cc | ||
|
|
8c905427a1 | ||
|
|
90befeeeb1 | ||
|
|
9e61c44629 | ||
|
|
3ad80b80ee | ||
|
|
8fe12e9fda | ||
|
|
0746f8cfff | ||
|
|
4e05f20c58 | ||
|
|
0ac95e370b | ||
|
|
33d526f6f9 | ||
|
|
0abd3cdde8 | ||
|
|
870ba80092 | ||
|
|
2955c12d2a | ||
|
|
283ccb83d1 | ||
|
|
5319bc7c2c | ||
|
|
198339873a | ||
|
|
db694f2f24 | ||
|
|
29d70853e6 | ||
|
|
976d201fc1 | ||
|
|
80763ca235 | ||
|
|
844f5b46d0 | ||
|
|
814f1289f6 | ||
|
|
aec98b61b9 | ||
|
|
0b7556b590 | ||
|
|
99abfebc4a | ||
|
|
068f5af4d8 | ||
|
|
f7e4ac3e43 | ||
|
|
c36ae159ed | ||
|
|
993c242202 | ||
|
|
33e7f16c05 | ||
|
|
3d4f0b895b | ||
|
|
3b29eea577 | ||
|
|
e7e34e86d5 | ||
|
|
5a009889cc | ||
|
|
c4bac04be3 | ||
|
|
b9bdc87367 | ||
|
|
3b079c91bf | ||
|
|
d693fd4232 | ||
|
|
163c54877c | ||
|
|
2e2993f180 | ||
|
|
450f2adab0 | ||
|
|
0ad1b66387 | ||
|
|
5716aeba3b | ||
|
|
723209b198 | ||
|
|
5a8ce60d68 | ||
|
|
c77b10a317 | ||
|
|
8fa5b4f7ce | ||
|
|
e30294b6da | ||
|
|
948c28afff | ||
|
|
b0060b61fe | ||
|
|
a7435b85a1 | ||
|
|
729d6c576f | ||
|
|
15b84fca01 | ||
|
|
285b30dff0 |
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
+6
-6
@@ -8,27 +8,27 @@ plugins:
|
||||
uri: https://github.com/trunk-io/plugins
|
||||
lint:
|
||||
enabled:
|
||||
- checkov@3.2.510
|
||||
- renovate@43.84.0
|
||||
- checkov@3.2.513
|
||||
- renovate@43.104.6
|
||||
- prettier@3.8.1
|
||||
- trufflehog@3.93.8
|
||||
- trufflehog@3.94.2
|
||||
- yamllint@1.38.0
|
||||
- bandit@1.9.4
|
||||
- trivy@0.69.3
|
||||
- taplo@0.10.0
|
||||
- ruff@0.15.7
|
||||
- ruff@0.15.9
|
||||
- isort@8.0.1
|
||||
- markdownlint@0.48.0
|
||||
- oxipng@10.1.0
|
||||
- svgo@4.0.1
|
||||
- actionlint@1.7.11
|
||||
- actionlint@1.7.12
|
||||
- flake8@7.3.0
|
||||
- hadolint@2.14.0
|
||||
- shfmt@3.6.0
|
||||
- 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
|
||||
|
||||
@@ -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.
|
||||
@@ -1,3 +1,9 @@
|
||||
Meta:
|
||||
name: Waveshare 1.44inch LCD HAT
|
||||
support: community
|
||||
compatible:
|
||||
- raspberry-pi
|
||||
|
||||
### Waveshare 1.44inch LCD HAT
|
||||
Display:
|
||||
Panel: ST7735S
|
||||
|
||||
@@ -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
|
||||
@@ -1,3 +1,9 @@
|
||||
Meta:
|
||||
name: Adafruit RFM9x
|
||||
support: deprecated
|
||||
compatible:
|
||||
- raspberry-pi
|
||||
|
||||
Lora:
|
||||
Module: RF95 # Adafruit RFM9x
|
||||
Reset: 25
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+22
-20
@@ -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
|
||||
+24
-21
@@ -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
|
||||
+24
-21
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# MeshAdv-Pi E22-900M30S
|
||||
# https://github.com/chrismyers2000/MeshAdv-Pi-Hat
|
||||
Meta:
|
||||
name: MeshAdv-Pi E22-900M30S
|
||||
support: community
|
||||
compatible:
|
||||
- luckfox-lyra-zero-w # Armbian
|
||||
|
||||
Lora:
|
||||
Module: sx1262
|
||||
CS: # GPIO0_C2 (physical 40)
|
||||
pin: 18
|
||||
gpiochip: 0
|
||||
line: 18
|
||||
IRQ: # GPIO1_D1 (physical 36)
|
||||
pin: 57
|
||||
gpiochip: 1
|
||||
line: 25
|
||||
Busy: # GPIO0_C1 (physical 38)
|
||||
pin: 17
|
||||
gpiochip: 0
|
||||
line: 17
|
||||
Reset: # GPIO0_B6 (physical 12)
|
||||
pin: 14
|
||||
gpiochip: 0
|
||||
line: 14
|
||||
TXen: # GPIO1_C2 (physical 33)
|
||||
pin: 50
|
||||
gpiochip: 1
|
||||
line: 18
|
||||
RXen: # GPIO1_D2 (physical 32)
|
||||
pin: 58
|
||||
gpiochip: 1
|
||||
line: 26
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
# Only for E22-900M33S:
|
||||
# Limit the output power to 8 dBm
|
||||
# SX126X_MAX_POWER: 8
|
||||
spidev: spidev0.0
|
||||
@@ -0,0 +1,33 @@
|
||||
# MeshAdv Mini E22-900M22S
|
||||
# https://github.com/chrismyers2000/MeshAdv-Mini
|
||||
Meta:
|
||||
name: MeshAdv Mini E22-900M22S
|
||||
support: community
|
||||
compatible:
|
||||
- luckfox-lyra-zero-w # Armbian
|
||||
|
||||
Lora:
|
||||
Module: sx1262 # Ebyte E22-900M22S
|
||||
CS: # GPIO0_B2_d (phys 24, RPi CE0)
|
||||
pin: 10
|
||||
gpiochip: 0
|
||||
line: 10
|
||||
IRQ: # GPIO1_D1_d (phys 36, RPi GPIO16)
|
||||
pin: 57
|
||||
gpiochip: 1
|
||||
line: 25
|
||||
Busy: # GPIO0_C1_d (phys 38, RPi GPIO20)
|
||||
pin: 17
|
||||
gpiochip: 0
|
||||
line: 17
|
||||
Reset: # GPIO0_B4_d (phys 18, RPi GPIO24)
|
||||
pin: 12
|
||||
gpiochip: 0
|
||||
line: 12
|
||||
RXen: # GPIO1_D2_d (phys 32, RPi GPIO12)
|
||||
pin: 58
|
||||
gpiochip: 1
|
||||
line: 26
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
spidev: spidev0.0
|
||||
@@ -0,0 +1,38 @@
|
||||
Meta:
|
||||
name: RAK6421 + RAK13300 Slot 1
|
||||
support: community # Promote when tested
|
||||
compatible:
|
||||
- luckfox-lyra-zero-w # Armbian
|
||||
|
||||
Lora:
|
||||
Module: sx1262
|
||||
IRQ: # GPIO0_A5 (IO6)
|
||||
pin: 5
|
||||
gpiochip: 0
|
||||
line: 5
|
||||
Reset: # GPIO1_D1 (IO4)
|
||||
pin: 57
|
||||
gpiochip: 1
|
||||
line: 25
|
||||
Busy: # GPIO0_B4 (IO5)
|
||||
pin: 12
|
||||
gpiochip: 0
|
||||
line: 12
|
||||
# Ant_sw: # GPIO1_C2 (IO3)
|
||||
# pin: 50
|
||||
# gpiochip: 1
|
||||
# line: 18
|
||||
Enable_Pins:
|
||||
- pin: 58 # GPIO1_D2
|
||||
gpiochip: 1
|
||||
line: 26
|
||||
- pin: 50 # GPIO1_C2
|
||||
gpiochip: 1
|
||||
line: 18
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
spidev: spidev0.0
|
||||
# CS: # GPIO0_B2
|
||||
# pin: 10
|
||||
# gpiochip: 0
|
||||
# line: 10
|
||||
@@ -0,0 +1,36 @@
|
||||
Meta:
|
||||
name: RAK6421 + RAK13300 Slot 2
|
||||
support: community # Promote when tested
|
||||
compatible:
|
||||
- luckfox-lyra-zero-w # Armbian
|
||||
|
||||
Lora:
|
||||
Module: sx1262
|
||||
IRQ: # GPIO0_B6 (IO6)
|
||||
pin: 14
|
||||
gpiochip: 0
|
||||
line: 14
|
||||
Reset: # GPIO0_B4 (IO4)
|
||||
pin: 12
|
||||
gpiochip: 0
|
||||
line: 12
|
||||
Busy: # GPIO1_C0 (IO5)
|
||||
pin: 48
|
||||
gpiochip: 1
|
||||
line: 16
|
||||
# Ant_sw: # GPIO0_B5 (IO3)
|
||||
# pin: 13
|
||||
# gpiochip: 0
|
||||
# line: 13
|
||||
Enable_Pins:
|
||||
- pin: 51 # GPIO1_C3
|
||||
gpiochip: 1
|
||||
line: 19
|
||||
- pin: 13 # GPIO0_B5
|
||||
gpiochip: 0
|
||||
line: 13
|
||||
spidev: spidev0.1
|
||||
# CS: # GPIO0_B1
|
||||
# pin: 9
|
||||
# gpiochip: 0
|
||||
# line: 9
|
||||
@@ -0,0 +1,39 @@
|
||||
Meta:
|
||||
name: RAK6421 + RAK13300 Slot 1
|
||||
support: community # Promote when tested
|
||||
compatible:
|
||||
- luckfox-lyra-zero-w # Armbian
|
||||
|
||||
Lora:
|
||||
Module: sx1262
|
||||
IRQ: # GPIO0_A5 (IO6)
|
||||
pin: 5
|
||||
gpiochip: 0
|
||||
line: 5
|
||||
Reset: # GPIO1_D1 (IO4)
|
||||
pin: 57
|
||||
gpiochip: 1
|
||||
line: 25
|
||||
Busy: # GPIO0_B4 (IO5)
|
||||
pin: 12
|
||||
gpiochip: 0
|
||||
line: 12
|
||||
# Ant_sw: # GPIO1_C2 (IO3)
|
||||
# pin: 50
|
||||
# gpiochip: 1
|
||||
# line: 18
|
||||
Enable_Pins:
|
||||
- pin: 58 # GPIO1_D2
|
||||
gpiochip: 1
|
||||
line: 26
|
||||
- pin: 50 # GPIO1_C2
|
||||
gpiochip: 1
|
||||
line: 18
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
spidev: spidev0.0
|
||||
# CS: # GPIO0_B2
|
||||
# pin: 10
|
||||
# gpiochip: 0
|
||||
# line: 10
|
||||
TX_GAIN_LORA: [9, 9, 10, 11, 9, 8, 9, 10, 10, 10, 11, 12, 12, 12, 12, 12, 12, 12, 12, 10, 9, 8]
|
||||
@@ -0,0 +1,37 @@
|
||||
Meta:
|
||||
name: RAK6421 + RAK13300 Slot 2
|
||||
support: community # Promote when tested
|
||||
compatible:
|
||||
- luckfox-lyra-zero-w # Armbian
|
||||
|
||||
Lora:
|
||||
Module: sx1262
|
||||
IRQ: # GPIO0_B6 (IO6)
|
||||
pin: 14
|
||||
gpiochip: 0
|
||||
line: 14
|
||||
Reset: # GPIO0_B4 (IO4)
|
||||
pin: 12
|
||||
gpiochip: 0
|
||||
line: 12
|
||||
Busy: # GPIO1_C0 (IO5)
|
||||
pin: 48
|
||||
gpiochip: 1
|
||||
line: 16
|
||||
# Ant_sw: # GPIO0_B5 (IO3)
|
||||
# pin: 13
|
||||
# gpiochip: 0
|
||||
# line: 13
|
||||
Enable_Pins:
|
||||
- pin: 51 # GPIO1_C3
|
||||
gpiochip: 1
|
||||
line: 19
|
||||
- pin: 13 # GPIO0_B5
|
||||
gpiochip: 0
|
||||
line: 13
|
||||
spidev: spidev0.1
|
||||
# CS: # GPIO0_B1
|
||||
# pin: 9
|
||||
# gpiochip: 0
|
||||
# line: 9
|
||||
TX_GAIN_LORA: [9, 9, 10, 11, 9, 8, 9, 10, 10, 10, 11, 12, 12, 12, 12, 12, 12, 12, 12, 10, 9, 8]
|
||||
@@ -0,0 +1,30 @@
|
||||
Meta:
|
||||
name: Waveshare SX1262
|
||||
support: deprecated
|
||||
compatible:
|
||||
- luckfox-lyra-zero-w # Armbian
|
||||
|
||||
Lora:
|
||||
Module: sx1262 # Waveshare SX126X XXXM
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
CS: # GPIO0_C2 (physical 40)
|
||||
pin: 18
|
||||
gpiochip: 0
|
||||
line: 18
|
||||
IRQ: # GPIO1_D1 (physical 36)
|
||||
pin: 57
|
||||
gpiochip: 1
|
||||
line: 25
|
||||
Busy: # GPIO0_C1 (physical 38)
|
||||
pin: 17
|
||||
gpiochip: 0
|
||||
line: 17
|
||||
Reset: # GPIO0_B6 (physical 12)
|
||||
pin: 14
|
||||
gpiochip: 0
|
||||
line: 14
|
||||
SX126X_ANT_SW: # GPIO1_B3 (physical 31)
|
||||
pin: 43
|
||||
gpiochip: 1
|
||||
line: 11
|
||||
spidev: spidev0.0
|
||||
@@ -1,3 +1,9 @@
|
||||
Meta:
|
||||
name: Lora Meshstick SX1262
|
||||
support: official
|
||||
compatible:
|
||||
- usb
|
||||
|
||||
Lora:
|
||||
Module: sx1262
|
||||
CS: 0
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
Meta:
|
||||
name: Lora Meshstick SX1262
|
||||
support: community
|
||||
compatible:
|
||||
- usb
|
||||
|
||||
Lora:
|
||||
Module: lr1121
|
||||
CS: 0
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -87,6 +87,9 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="2.7.22" date="2026-04-06">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.22</url>
|
||||
</release>
|
||||
<release version="2.7.21" date="2026-03-11">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.21</url>
|
||||
</release>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
Vendored
+6
@@ -1,3 +1,9 @@
|
||||
meshtasticd (2.7.22.0) unstable; urgency=medium
|
||||
|
||||
* Version 2.7.22
|
||||
|
||||
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Mon, 06 Apr 2026 11:34:12 +0000
|
||||
|
||||
meshtasticd (2.7.21.0) unstable; urgency=medium
|
||||
|
||||
* Version 2.7.21
|
||||
|
||||
+20
-23
@@ -50,6 +50,7 @@ build_flags = -Wno-missing-field-initializers
|
||||
-DRADIOLIB_EXCLUDE_APRS=1
|
||||
-DRADIOLIB_EXCLUDE_LORAWAN=1
|
||||
-DMESHTASTIC_EXCLUDE_DROPZONE=1
|
||||
-DMESHTASTIC_EXCLUDE_ZPS=1
|
||||
-DMESHTASTIC_EXCLUDE_REPLYBOT=1
|
||||
-DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1
|
||||
-DMESHTASTIC_EXCLUDE_HEALTH_TELEMETRY=1
|
||||
@@ -77,10 +78,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 +96,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 +108,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/1897dd17fceb1f101bb1a3245680aa3439edcfdd.zip
|
||||
|
||||
; Common libs for environmental measurements in telemetry module
|
||||
[environmental_base]
|
||||
@@ -195,16 +196,10 @@ lib_deps =
|
||||
adafruit/Adafruit BMP3XX Library@2.1.6
|
||||
# renovate: datasource=custom.pio depName=Adafruit MAX1704X packageName=adafruit/library/Adafruit MAX1704X
|
||||
adafruit/Adafruit MAX1704X@1.0.3
|
||||
# renovate: datasource=custom.pio depName=Adafruit SHTC3 packageName=adafruit/library/Adafruit SHTC3 Library
|
||||
adafruit/Adafruit SHTC3 Library@1.0.2
|
||||
# renovate: datasource=custom.pio depName=Adafruit LPS2X packageName=adafruit/library/Adafruit LPS2X
|
||||
adafruit/Adafruit LPS2X@2.0.6
|
||||
# renovate: datasource=custom.pio depName=Adafruit SHT31 packageName=adafruit/library/Adafruit SHT31 Library
|
||||
adafruit/Adafruit SHT31 Library@2.2.2
|
||||
# renovate: datasource=custom.pio depName=Adafruit VEML7700 packageName=adafruit/library/Adafruit VEML7700 Library
|
||||
adafruit/Adafruit VEML7700 Library@2.1.6
|
||||
# renovate: datasource=custom.pio depName=Adafruit SHT4x packageName=adafruit/library/Adafruit SHT4x Library
|
||||
adafruit/Adafruit SHT4x Library@1.0.5
|
||||
# renovate: datasource=custom.pio depName=SparkFun Qwiic Scale NAU7802 packageName=sparkfun/library/SparkFun Qwiic Scale NAU7802 Arduino Library
|
||||
sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@1.0.6
|
||||
# renovate: datasource=custom.pio depName=ClosedCube OPT3001 packageName=closedcube/library/ClosedCube OPT3001
|
||||
@@ -219,6 +214,8 @@ lib_deps =
|
||||
sensirion/Sensirion I2C SFA3x@1.0.0
|
||||
# renovate: datasource=custom.pio depName=Sensirion I2C SCD30 packageName=sensirion/library/Sensirion I2C SCD30
|
||||
sensirion/Sensirion I2C SCD30@1.0.0
|
||||
# renovate: datasource=custom.pio depName=arduino-sht packageName=sensirion/library/arduino-sht
|
||||
sensirion/arduino-sht@1.2.6
|
||||
|
||||
; Environmental sensors with BSEC2 (Bosch proprietary IAQ)
|
||||
[environmental_extra]
|
||||
@@ -233,5 +230,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
|
||||
|
||||
+1
-1
Submodule protobufs updated: cb1f89372a...e30092e616
@@ -4,6 +4,8 @@
|
||||
":dependencyDashboard",
|
||||
":semanticCommitTypeAll(chore)",
|
||||
":ignoreModulesAndTests",
|
||||
":noUnscheduledUpdates",
|
||||
"schedule:daily",
|
||||
"group:recommended",
|
||||
"replacements:all",
|
||||
"workarounds:all"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
|
||||
if (color) {
|
||||
::printf("\u001b[0m");
|
||||
}
|
||||
::printf("| %02d:%02d:%02d %u ", hour, min, sec, millis() / 1000);
|
||||
::printf("| %02d:%02d:%02d %u.%03u ", hour, min, sec, millis() / 1000, millis() % 1000);
|
||||
#else
|
||||
printf("%s ", logLevel);
|
||||
if (color) {
|
||||
@@ -151,7 +151,7 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
|
||||
if (color) {
|
||||
::printf("\u001b[0m");
|
||||
}
|
||||
::printf("| ??:??:?? %u ", millis() / 1000);
|
||||
::printf("| ??:??:?? %u.%03u ", millis() / 1000, millis() % 1000);
|
||||
#else
|
||||
printf("%s ", logLevel);
|
||||
if (color) {
|
||||
|
||||
+56
-1
@@ -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
|
||||
|
||||
+3
-3
@@ -231,7 +231,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define BME_ADDR 0x76
|
||||
#define BME_ADDR_ALTERNATE 0x77
|
||||
#define MCP9808_ADDR 0x18
|
||||
#define INA_ADDR 0x40
|
||||
#define INA_ADDR 0x40 // same as SHT2X
|
||||
#define INA_ADDR_ALTERNATE 0x41
|
||||
#define INA_ADDR_WAVESHARE_UPS 0x43
|
||||
#define INA3221_ADDR 0x42
|
||||
@@ -244,8 +244,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define LPS22HB_ADDR 0x5C
|
||||
#define LPS22HB_ADDR_ALT 0x5D
|
||||
#define SFA30_ADDR 0x5D
|
||||
#define SHT31_4x_ADDR 0x44
|
||||
#define SHT31_4x_ADDR_ALT 0x45
|
||||
#define SHTXX_ADDR 0x44
|
||||
#define SHTXX_ADDR_ALT 0x45
|
||||
#define PMSA003I_ADDR 0x12
|
||||
#define QMA6100P_ADDR 0x12
|
||||
#define AHT10_ADDR 0x38
|
||||
|
||||
@@ -31,9 +31,6 @@ class ScanI2C
|
||||
INA3221,
|
||||
MAX17048,
|
||||
MCP9808,
|
||||
SHT31,
|
||||
SHT4X,
|
||||
SHTC3,
|
||||
LPS22HB,
|
||||
QMC6310U,
|
||||
QMC6310N,
|
||||
@@ -94,7 +91,8 @@ class ScanI2C
|
||||
SFA30,
|
||||
CW2015,
|
||||
SCD30,
|
||||
ADS1115
|
||||
ADS1115,
|
||||
SHTXX
|
||||
} DeviceType;
|
||||
|
||||
// typedef uint8_t DeviceAddress;
|
||||
|
||||
@@ -136,7 +136,9 @@ bool ScanI2CTwoWire::i2cCommandResponseLength(ScanI2C::DeviceAddress addr, uint1
|
||||
return match;
|
||||
}
|
||||
|
||||
/// for SEN5X detection
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
|
||||
// FIXME Move to a separate file for detection of sensors that require more complex interactions?
|
||||
// For SEN5X detection
|
||||
// Note, this code needs to be called before setting the I2C bus speed
|
||||
// for the screen at high speed. The speed needs to be at 100kHz, otherwise
|
||||
// detection will not work
|
||||
@@ -174,6 +176,46 @@ String readSEN5xProductName(TwoWire *i2cBus, uint8_t address)
|
||||
|
||||
return String(productName);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
bool detectSHT21SerialNumber(TwoWire *i2cBus, uint8_t address)
|
||||
{
|
||||
|
||||
i2cBus->beginTransmission(address);
|
||||
i2cBus->write(0xFA);
|
||||
i2cBus->write(0x0F);
|
||||
|
||||
if (i2cBus->endTransmission() != 0)
|
||||
return false;
|
||||
|
||||
if (i2cBus->requestFrom(address, (uint8_t)8) != 8)
|
||||
return false;
|
||||
|
||||
// Just flush the data
|
||||
while (i2cBus->available() < 8) {
|
||||
i2cBus->read();
|
||||
}
|
||||
|
||||
i2cBus->beginTransmission(address);
|
||||
i2cBus->write(0xFC);
|
||||
i2cBus->write(0xC9);
|
||||
|
||||
if (i2cBus->endTransmission() != 0)
|
||||
return false;
|
||||
|
||||
if (i2cBus->requestFrom(address, (uint8_t)6) != 6)
|
||||
return false;
|
||||
|
||||
// Just flush the data
|
||||
while (i2cBus->available() < 6) {
|
||||
i2cBus->read();
|
||||
}
|
||||
|
||||
// Assume we detect the SHT21 if something came back from the request
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define SCAN_SIMPLE_CASE(ADDR, T, ...) \
|
||||
case ADDR: \
|
||||
@@ -371,7 +413,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
break;
|
||||
#endif
|
||||
#if !defined(M5STACK_UNITC6L)
|
||||
case INA_ADDR:
|
||||
case INA_ADDR: // Same as SHT2X
|
||||
case INA_ADDR_ALTERNATE:
|
||||
case INA_ADDR_WAVESHARE_UPS:
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFE), 2);
|
||||
@@ -387,7 +429,12 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
logFoundDevice("INA260", (uint8_t)addr.address);
|
||||
type = INA260;
|
||||
}
|
||||
} else { // Assume INA219 if INA260 ID is not found
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
} else if (detectSHT21SerialNumber(i2cBus, (uint8_t)addr.address)) {
|
||||
logFoundDevice("SHTXX (SHT2X)", (uint8_t)addr.address);
|
||||
type = SHTXX;
|
||||
#endif
|
||||
} else { // Assume INA219 if none of the above ones are found
|
||||
logFoundDevice("INA219", (uint8_t)addr.address);
|
||||
type = INA219;
|
||||
}
|
||||
@@ -448,22 +495,19 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT
|
||||
case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
|
||||
case SHTXX_ADDR: // same as OPT3001_ADDR_ALT
|
||||
case SHTXX_ADDR_ALT: // same as OPT3001_ADDR
|
||||
if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
|
||||
type = OPT3001;
|
||||
logFoundDevice("OPT3001", (uint8_t)addr.address);
|
||||
} else if (i2cCommandResponseLength(addr, 0x89, 6)) { // SHT4x serial number (6 bytes inc. CRC)
|
||||
type = SHT4X;
|
||||
logFoundDevice("SHT4X", (uint8_t)addr.address);
|
||||
} else {
|
||||
type = SHT31;
|
||||
logFoundDevice("SHT31", (uint8_t)addr.address);
|
||||
} else { // SHTXX
|
||||
type = SHTXX;
|
||||
logFoundDevice("SHTXX", (uint8_t)addr.address);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
SCAN_SIMPLE_CASE(SHTC3_ADDR, SHTC3, "SHTC3", (uint8_t)addr.address)
|
||||
SCAN_SIMPLE_CASE(SHTC3_ADDR, SHTXX, "SHTXX", (uint8_t)addr.address)
|
||||
case RCWL9620_ADDR:
|
||||
// get MAX30102 PARTID
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFF), 1);
|
||||
@@ -675,6 +719,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
logFoundDevice("BMX160", (uint8_t)addr.address);
|
||||
break;
|
||||
} else {
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
|
||||
String prod = "";
|
||||
prod = readSEN5xProductName(i2cBus, addr.address);
|
||||
if (prod.startsWith("SEN55")) {
|
||||
@@ -690,6 +735,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
logFoundDevice("Sensirion SEN50", addr.address);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
if (addr.address == BMX160_ADDR) {
|
||||
type = BMX160;
|
||||
logFoundDevice("BMX160", (uint8_t)addr.address);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_EINK
|
||||
#if defined(USE_EINK) && !defined(USE_EINK_PARALLELDISPLAY)
|
||||
|
||||
#include "GxEPD2_BW.h"
|
||||
#include <OLEDDisplay.h>
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1254,14 +1254,14 @@ void TFTDisplay::display(bool fromBlank)
|
||||
|
||||
// Did we find a pixel that needs updating on this row?
|
||||
if (x_FirstPixelUpdate < displayWidth) {
|
||||
// Align the first pixel for update to an even number so the total alignment of
|
||||
// the data will be at 32-bit boundary, which is required by GDMA SPI transfers.
|
||||
x_FirstPixelUpdate &= ~1;
|
||||
|
||||
// Quickly write out the first changed pixel (saves another array lookup)
|
||||
linePixelBuffer[x_FirstPixelUpdate] = isset ? colorTftMesh : colorTftBlack;
|
||||
x_LastPixelUpdate = x_FirstPixelUpdate;
|
||||
|
||||
// Step 3: copy all remaining pixels in this row into the pixel line buffer,
|
||||
// while also recording the last pixel in the row that needs updating
|
||||
for (x = x_FirstPixelUpdate + 1; x < displayWidth; x++) {
|
||||
// Step 3a: copy rest of the pixels in this row into the pixel line buffer,
|
||||
// while also recording the last pixel in the row that needs updating.
|
||||
// Since the first changed pixel will be looked up, the x_LastPixelUpdate will be set.
|
||||
for (x = x_FirstPixelUpdate; x < displayWidth; x++) {
|
||||
isset = buffer[x + y_byteIndex] & y_byteMask;
|
||||
linePixelBuffer[x] = isset ? colorTftMesh : colorTftBlack;
|
||||
|
||||
@@ -1274,6 +1274,14 @@ void TFTDisplay::display(bool fromBlank)
|
||||
x_LastPixelUpdate = x;
|
||||
}
|
||||
}
|
||||
// Step 3b: Round up the last pixel to odd number to maintain 32-bit alignment for SPIs.
|
||||
// Most displays will have even number of pixels in a row -- this will be in bounds
|
||||
// of the displayWidth. (Hopefully odd displays will just ignore that extra pixel.)
|
||||
x_LastPixelUpdate |= 1;
|
||||
// Ensure the last pixel index does not exceed the display width.
|
||||
if (x_LastPixelUpdate >= displayWidth) {
|
||||
x_LastPixelUpdate = displayWidth - 1;
|
||||
}
|
||||
#if defined(HACKADAY_COMMUNICATOR)
|
||||
tft->draw16bitBeRGBBitmap(x_FirstPixelUpdate, y, &linePixelBuffer[x_FirstPixelUpdate],
|
||||
(x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -163,9 +163,11 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
|
||||
|
||||
// Guard: without a reboot, reconfigure() applies the region directly.
|
||||
// Reject LORA_24 on sub-GHz-only hardware — getRadio() used to catch this post-reboot.
|
||||
// TODO: change this to either use the validateLoraConfig() logic or at least check the region for wideLora
|
||||
// rather than a hardcoded check for LORA_24.
|
||||
if (selectedRegion == meshtastic_Config_LoRaConfig_RegionCode_LORA_24 &&
|
||||
!(RadioLibInterface::instance && RadioLibInterface::instance->wideLora())) {
|
||||
LOG_WARN("Radio hardware does not support 2.4 GHz; ignoring LORA_24 selection");
|
||||
LOG_WARN("Radio hardware does not support 2.4 GHz; ignoring region selection");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1221,9 +1223,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 +1253,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 +1275,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 +2213,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 +2223,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 +2241,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();
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include <GFX.h> // GFXRoot drawing lib
|
||||
|
||||
#include "mesh/MeshModule.h"
|
||||
#include "mesh/MeshTypes.h"
|
||||
|
||||
#include "./AppletFont.h"
|
||||
|
||||
@@ -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(¬ifyLightSleep);
|
||||
endSleepObserver.observe(¬ifyLightSleepEnd);
|
||||
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
|
||||
|
||||
@@ -299,6 +299,7 @@ void InputBroker::Init()
|
||||
// Buttons. Moved here cause we need NodeDB to be initialized
|
||||
// If your variant.h has a BUTTON_PIN defined, go ahead and define BUTTON_ACTIVE_LOW and BUTTON_ACTIVE_PULLUP
|
||||
UserButtonThread = new ButtonThread("UserButton");
|
||||
#if !MESHTASTIC_EXCLUDE_SCREEN
|
||||
if (screen) {
|
||||
ButtonConfig userConfig;
|
||||
userConfig.pinNumber = (uint8_t)_pinNum;
|
||||
@@ -317,7 +318,9 @@ void InputBroker::Init()
|
||||
userConfig.longPressTime = 500;
|
||||
userConfig.longLongPress = INPUT_BROKER_SHUTDOWN;
|
||||
UserButtonThread->initButton(userConfig);
|
||||
} else {
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
ButtonConfig userConfigNoScreen;
|
||||
userConfigNoScreen.pinNumber = (uint8_t)_pinNum;
|
||||
userConfigNoScreen.activeLow = BUTTON_ACTIVE_LOW;
|
||||
@@ -379,11 +382,13 @@ void InputBroker::Init()
|
||||
}
|
||||
#endif // HAS_BUTTON
|
||||
#if ARCH_PORTDUINO
|
||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR && portduino_config.i2cdev != "") {
|
||||
seesawRotary = new SeesawRotary("SeesawRotary");
|
||||
if (!seesawRotary->init()) {
|
||||
delete seesawRotary;
|
||||
seesawRotary = nullptr;
|
||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
||||
if (portduino_config.i2cdev != "") {
|
||||
seesawRotary = new SeesawRotary("SeesawRotary");
|
||||
if (!seesawRotary->init()) {
|
||||
delete seesawRotary;
|
||||
seesawRotary = nullptr;
|
||||
}
|
||||
}
|
||||
aLinuxInputImpl = new LinuxInputImpl();
|
||||
aLinuxInputImpl->init();
|
||||
|
||||
+3
-3
@@ -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
@@ -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
|
||||
|
||||
+2
-2
@@ -32,8 +32,8 @@
|
||||
#define default_map_publish_interval_secs 60 * 60
|
||||
|
||||
// Traffic management defaults
|
||||
#define default_traffic_mgmt_position_precision_bits 24 // ~10m grid cells
|
||||
#define default_traffic_mgmt_position_min_interval_secs ONE_DAY // 1 day between identical positions
|
||||
#define default_traffic_mgmt_position_precision_bits 24 // ~10m grid cells
|
||||
#define default_traffic_mgmt_position_min_interval_secs (ONE_DAY / 2) // 12 hours between identical positions
|
||||
|
||||
#ifdef USERPREFS_RINGTONE_NAG_SECS
|
||||
#define default_ringtone_nag_secs USERPREFS_RINGTONE_NAG_SECS
|
||||
|
||||
@@ -71,12 +71,10 @@ template <typename T> bool LR11x0Interface<T>::init()
|
||||
|
||||
RadioLibInterface::init();
|
||||
|
||||
limitPower(LR1110_MAX_POWER);
|
||||
|
||||
if ((power > LR1120_MAX_POWER) &&
|
||||
(config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { // clamp again if wide freq range
|
||||
power = LR1120_MAX_POWER;
|
||||
preambleLength = 12; // 12 is the default for operation above 2GHz
|
||||
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) { // clamp if wide freq range
|
||||
limitPower(LR1120_MAX_POWER);
|
||||
} else {
|
||||
limitPower(LR1110_MAX_POWER); // default clamp for non-wide freq range
|
||||
}
|
||||
|
||||
#ifdef LR11X0_RF_SWITCH_SUBGHZ
|
||||
@@ -177,6 +175,12 @@ template <typename T> bool LR11x0Interface<T>::reconfigure()
|
||||
err = lora.setSyncWord(syncWord);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) { // clamp if wide freq range
|
||||
limitPower(LR1120_MAX_POWER);
|
||||
} else {
|
||||
limitPower(LR1110_MAX_POWER); // default clamp for non-wide freq range
|
||||
}
|
||||
|
||||
err = lora.setPreambleLength(preambleLength);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
@@ -184,11 +188,6 @@ template <typename T> bool LR11x0Interface<T>::reconfigure()
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
|
||||
if (power > LR1110_MAX_POWER) // This chip has lower power limits than some
|
||||
power = LR1110_MAX_POWER;
|
||||
if ((power > LR1120_MAX_POWER) && (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) // 2.4G power limit
|
||||
power = LR1120_MAX_POWER;
|
||||
|
||||
err = lora.setOutputPower(power);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
|
||||
+1
-1
@@ -819,7 +819,7 @@ void NodeDB::installDefaultModuleConfig()
|
||||
moduleConfig.has_store_forward = true;
|
||||
moduleConfig.has_telemetry = true;
|
||||
moduleConfig.has_external_notification = true;
|
||||
#if defined(PIN_BUZZER) || defined(PIN_VIBRATION) || defined(LED_NOTIFICATION)
|
||||
#if defined(PIN_BUZZER) || defined(PIN_VIBRATION) || defined(LED_NOTIFICATION) || defined(PCA_LED_NOTIFICATION)
|
||||
moduleConfig.external_notification.enabled = true;
|
||||
#endif
|
||||
#if defined(PIN_BUZZER)
|
||||
|
||||
@@ -17,14 +17,4 @@ template <class T> class PointerQueue : public TypedQueue<T *>
|
||||
|
||||
return this->dequeue(&p, maxWait) ? p : nullptr;
|
||||
}
|
||||
|
||||
#ifdef HAS_FREE_RTOS
|
||||
// returns a ptr or null if the queue was empty
|
||||
T *dequeuePtrFromISR(BaseType_t *higherPriWoken)
|
||||
{
|
||||
T *p;
|
||||
|
||||
return this->dequeueFromISR(&p, higherPriWoken) ? p : nullptr;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -240,8 +240,7 @@ bool RF95Interface::reconfigure()
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
|
||||
if (power > RF95_MAX_POWER) // This chip has lower power limits than some
|
||||
power = RF95_MAX_POWER;
|
||||
limitPower(RF95_MAX_POWER);
|
||||
|
||||
#ifdef USE_RF95_RFO
|
||||
err = lora->setOutputPower(power, true);
|
||||
|
||||
@@ -152,8 +152,10 @@ const RegionInfo regions[] = {
|
||||
|
||||
/*
|
||||
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
|
||||
https://standard.nbtc.go.th/getattachment/Standards/%E0%B8%A1%E0%B8%B2%E0%B8%95%E0%B8%A3%E0%B8%90%E0%B8%B2%E0%B8%99%E0%B8%97%E0%B8%B2%E0%B8%87%E0%B9%80%E0%B8%97%E0%B8%84%E0%B8%99%E0%B8%B4%E0%B8%84%E0%B8%82%E0%B8%AD%E0%B8%87%E0%B9%80%E0%B8%84%E0%B8%A3%E0%B8%B7%E0%B9%88%E0%B8%AD%E0%B8%87%E0%B9%82%E0%B8%97%E0%B8%A3%E0%B8%84%E0%B8%A1%E0%B8%99%E0%B8%B2%E0%B8%84%E0%B8%A1/1033-2565.pdf.aspx?lang=th-TH
|
||||
Thailand 920–925 MHz set max TX power to 27 dBm and enforce 10% duty cycle, aligned with NBTC regulations.
|
||||
*/
|
||||
RDEF(TH, 920.0f, 925.0f, 100, 16, false, false, PROFILE_STD),
|
||||
RDEF(TH, 920.0f, 925.0f, 10, 27, false, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
433,05-434,7 Mhz 10 mW
|
||||
@@ -961,7 +963,7 @@ void RadioInterface::applyModemConfig()
|
||||
cr = loraConfig.coding_rate;
|
||||
LOG_INFO("Using custom Coding Rate %u", cr);
|
||||
} else {
|
||||
cr = loraConfig.coding_rate;
|
||||
cr = newcr;
|
||||
}
|
||||
|
||||
} else { // if not using preset, then just use the custom settings
|
||||
@@ -1036,6 +1038,13 @@ void RadioInterface::applyModemConfig()
|
||||
saveFreq(freq + loraConfig.frequency_offset);
|
||||
const char *channelName = channels.getName(channels.getPrimaryIndex());
|
||||
|
||||
if (newRegion->wideLora) { // clamp if wide freq range
|
||||
preambleLength = wideLoraPreambleLengthDefault; // 12 is the default for operation above 2GHz
|
||||
} else {
|
||||
preambleLength =
|
||||
preambleLengthDefault; // 8 is default, but we use longer to increase the amount of sleep time when receiving
|
||||
}
|
||||
|
||||
slotTimeMsec = computeSlotTimeMsec();
|
||||
preambleTimeMsec = preambleLength * (pow_of_2(sf) / bw);
|
||||
|
||||
|
||||
@@ -92,15 +92,20 @@ class RadioInterface
|
||||
uint8_t sf = 9;
|
||||
uint8_t cr = 5;
|
||||
|
||||
const uint8_t NUM_SYM_CAD = 2; // Number of symbols used for CAD, 2 is the default since RadioLib 6.3.0 as per AN1200.48
|
||||
const uint8_t NUM_SYM_CAD_24GHZ = 4; // Number of symbols used for CAD in 2.4 GHz, 4 is recommended in AN1200.22 of SX1280
|
||||
static constexpr uint8_t NUM_SYM_CAD =
|
||||
2; // Number of symbols used for CAD, 2 is the default since RadioLib 6.3.0 as per AN1200.48
|
||||
static constexpr uint8_t NUM_SYM_CAD_24GHZ =
|
||||
4; // Number of symbols used for CAD in 2.4 GHz, 4 is recommended in AN1200.22 of SX1280
|
||||
uint32_t slotTimeMsec = computeSlotTimeMsec();
|
||||
uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving
|
||||
uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast
|
||||
const uint32_t PROCESSING_TIME_MSEC =
|
||||
4500; // time to construct, process and construct a packet again (empirically determined)
|
||||
const uint8_t CWmin = 3; // minimum CWsize
|
||||
const uint8_t CWmax = 8; // maximum CWsize
|
||||
uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving
|
||||
static constexpr uint16_t preambleLengthDefault =
|
||||
16; // 8 is default, but we use longer to increase the amount of sleep time when receiving
|
||||
static constexpr uint16_t wideLoraPreambleLengthDefault = 12; // 12 is default for wide Lora
|
||||
uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast
|
||||
static constexpr uint32_t PROCESSING_TIME_MSEC =
|
||||
4500; // time to construct, process and construct a packet again (empirically determined)
|
||||
static constexpr uint8_t CWmin = 3; // minimum CWsize
|
||||
static constexpr uint8_t CWmax = 8; // maximum CWsize
|
||||
|
||||
meshtastic_MeshPacket *sendingPacket = NULL; // The packet we are currently sending
|
||||
uint32_t lastTxStart = 0L;
|
||||
|
||||
@@ -245,8 +245,10 @@ template <typename T> bool SX126xInterface<T>::reconfigure()
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
|
||||
if (power > SX126X_MAX_POWER) // This chip has lower power limits than some
|
||||
power = SX126X_MAX_POWER;
|
||||
limitPower(SX126X_MAX_POWER);
|
||||
// Make sure we reach the minimum power supported to turn the chip on (-9dBm)
|
||||
if (power < -9)
|
||||
power = -9;
|
||||
|
||||
err = lora.setOutputPower(power);
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
|
||||
@@ -144,8 +144,7 @@ template <typename T> bool SX128xInterface<T>::reconfigure()
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
|
||||
if (power > SX128X_MAX_POWER) // This chip has lower power limits than some
|
||||
power = SX128X_MAX_POWER;
|
||||
limitPower(SX128X_MAX_POWER);
|
||||
|
||||
err = lora.setOutputPower(power);
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
|
||||
@@ -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
@@ -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++;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -24,6 +24,18 @@ PB_BIND(meshtastic_Contact, meshtastic_Contact, AUTO)
|
||||
PB_BIND(meshtastic_PLI, meshtastic_PLI, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_AircraftTrack, meshtastic_AircraftTrack, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_TAKPacketV2, meshtastic_TAKPacketV2, 2)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -65,6 +65,197 @@ typedef enum _meshtastic_MemberRole {
|
||||
meshtastic_MemberRole_K9 = 8
|
||||
} meshtastic_MemberRole;
|
||||
|
||||
/* CoT how field values.
|
||||
Represents how the coordinates were generated. */
|
||||
typedef enum _meshtastic_CotHow {
|
||||
/* Unspecified */
|
||||
meshtastic_CotHow_CotHow_Unspecified = 0,
|
||||
/* Human entered */
|
||||
meshtastic_CotHow_CotHow_h_e = 1,
|
||||
/* Machine generated */
|
||||
meshtastic_CotHow_CotHow_m_g = 2,
|
||||
/* Human GPS/INS derived */
|
||||
meshtastic_CotHow_CotHow_h_g_i_g_o = 3,
|
||||
/* Machine relayed (imported from another system/gateway) */
|
||||
meshtastic_CotHow_CotHow_m_r = 4,
|
||||
/* Machine fused (corroborated from multiple sources) */
|
||||
meshtastic_CotHow_CotHow_m_f = 5,
|
||||
/* Machine predicted */
|
||||
meshtastic_CotHow_CotHow_m_p = 6,
|
||||
/* Machine simulated */
|
||||
meshtastic_CotHow_CotHow_m_s = 7
|
||||
} meshtastic_CotHow;
|
||||
|
||||
/* Well-known CoT event types.
|
||||
When the type is known, use the enum value for efficient encoding.
|
||||
For unknown types, set cot_type_id to CotType_Other and populate cot_type_str. */
|
||||
typedef enum _meshtastic_CotType {
|
||||
/* Unknown or unmapped type, use cot_type_str */
|
||||
meshtastic_CotType_CotType_Other = 0,
|
||||
/* a-f-G-U-C: Friendly ground unit combat */
|
||||
meshtastic_CotType_CotType_a_f_G_U_C = 1,
|
||||
/* a-f-G-U-C-I: Friendly ground unit combat infantry */
|
||||
meshtastic_CotType_CotType_a_f_G_U_C_I = 2,
|
||||
/* a-n-A-C-F: Neutral aircraft civilian fixed-wing */
|
||||
meshtastic_CotType_CotType_a_n_A_C_F = 3,
|
||||
/* a-n-A-C-H: Neutral aircraft civilian helicopter */
|
||||
meshtastic_CotType_CotType_a_n_A_C_H = 4,
|
||||
/* a-n-A-C: Neutral aircraft civilian */
|
||||
meshtastic_CotType_CotType_a_n_A_C = 5,
|
||||
/* a-f-A-M-H: Friendly aircraft military helicopter */
|
||||
meshtastic_CotType_CotType_a_f_A_M_H = 6,
|
||||
/* a-f-A-M: Friendly aircraft military */
|
||||
meshtastic_CotType_CotType_a_f_A_M = 7,
|
||||
/* a-f-A-M-F-F: Friendly aircraft military fixed-wing fighter */
|
||||
meshtastic_CotType_CotType_a_f_A_M_F_F = 8,
|
||||
/* a-f-A-M-H-A: Friendly aircraft military helicopter attack */
|
||||
meshtastic_CotType_CotType_a_f_A_M_H_A = 9,
|
||||
/* a-f-A-M-H-U-M: Friendly aircraft military helicopter utility medium */
|
||||
meshtastic_CotType_CotType_a_f_A_M_H_U_M = 10,
|
||||
/* a-h-A-M-F-F: Hostile aircraft military fixed-wing fighter */
|
||||
meshtastic_CotType_CotType_a_h_A_M_F_F = 11,
|
||||
/* a-h-A-M-H-A: Hostile aircraft military helicopter attack */
|
||||
meshtastic_CotType_CotType_a_h_A_M_H_A = 12,
|
||||
/* a-u-A-C: Unknown aircraft civilian */
|
||||
meshtastic_CotType_CotType_a_u_A_C = 13,
|
||||
/* t-x-d-d: Tasking delete/disconnect */
|
||||
meshtastic_CotType_CotType_t_x_d_d = 14,
|
||||
/* a-f-G-E-S-E: Friendly ground equipment sensor */
|
||||
meshtastic_CotType_CotType_a_f_G_E_S_E = 15,
|
||||
/* a-f-G-E-V-C: Friendly ground equipment vehicle */
|
||||
meshtastic_CotType_CotType_a_f_G_E_V_C = 16,
|
||||
/* a-f-S: Friendly sea */
|
||||
meshtastic_CotType_CotType_a_f_S = 17,
|
||||
/* a-f-A-M-F: Friendly aircraft military fixed-wing */
|
||||
meshtastic_CotType_CotType_a_f_A_M_F = 18,
|
||||
/* a-f-A-M-F-C-H: Friendly aircraft military fixed-wing cargo heavy */
|
||||
meshtastic_CotType_CotType_a_f_A_M_F_C_H = 19,
|
||||
/* a-f-A-M-F-U-L: Friendly aircraft military fixed-wing utility light */
|
||||
meshtastic_CotType_CotType_a_f_A_M_F_U_L = 20,
|
||||
/* a-f-A-M-F-L: Friendly aircraft military fixed-wing liaison */
|
||||
meshtastic_CotType_CotType_a_f_A_M_F_L = 21,
|
||||
/* a-f-A-M-F-P: Friendly aircraft military fixed-wing patrol */
|
||||
meshtastic_CotType_CotType_a_f_A_M_F_P = 22,
|
||||
/* a-f-A-C-H: Friendly aircraft civilian helicopter */
|
||||
meshtastic_CotType_CotType_a_f_A_C_H = 23,
|
||||
/* a-n-A-M-F-Q: Neutral aircraft military fixed-wing drone */
|
||||
meshtastic_CotType_CotType_a_n_A_M_F_Q = 24,
|
||||
/* b-t-f: GeoChat message */
|
||||
meshtastic_CotType_CotType_b_t_f = 25,
|
||||
/* b-r-f-h-c: CASEVAC/MEDEVAC report */
|
||||
meshtastic_CotType_CotType_b_r_f_h_c = 26,
|
||||
/* b-a-o-pan: Ring the bell / alert all */
|
||||
meshtastic_CotType_CotType_b_a_o_pan = 27,
|
||||
/* b-a-o-opn: Troops in contact */
|
||||
meshtastic_CotType_CotType_b_a_o_opn = 28,
|
||||
/* b-a-o-can: Cancel alert */
|
||||
meshtastic_CotType_CotType_b_a_o_can = 29,
|
||||
/* b-a-o-tbl: 911 alert */
|
||||
meshtastic_CotType_CotType_b_a_o_tbl = 30,
|
||||
/* b-a-g: Geofence breach alert */
|
||||
meshtastic_CotType_CotType_b_a_g = 31,
|
||||
/* a-f-G: Friendly ground (generic) */
|
||||
meshtastic_CotType_CotType_a_f_G = 32,
|
||||
/* a-f-G-U: Friendly ground unit (generic) */
|
||||
meshtastic_CotType_CotType_a_f_G_U = 33,
|
||||
/* a-h-G: Hostile ground (generic) */
|
||||
meshtastic_CotType_CotType_a_h_G = 34,
|
||||
/* a-u-G: Unknown ground (generic) */
|
||||
meshtastic_CotType_CotType_a_u_G = 35,
|
||||
/* a-n-G: Neutral ground (generic) */
|
||||
meshtastic_CotType_CotType_a_n_G = 36,
|
||||
/* b-m-r: Route */
|
||||
meshtastic_CotType_CotType_b_m_r = 37,
|
||||
/* b-m-p-w: Route waypoint */
|
||||
meshtastic_CotType_CotType_b_m_p_w = 38,
|
||||
/* b-m-p-s-p-i: Self-position marker */
|
||||
meshtastic_CotType_CotType_b_m_p_s_p_i = 39,
|
||||
/* u-d-f: Freeform shape (line/polygon) */
|
||||
meshtastic_CotType_CotType_u_d_f = 40,
|
||||
/* u-d-r: Rectangle */
|
||||
meshtastic_CotType_CotType_u_d_r = 41,
|
||||
/* u-d-c-c: Circle */
|
||||
meshtastic_CotType_CotType_u_d_c_c = 42,
|
||||
/* u-rb-a: Range/bearing line */
|
||||
meshtastic_CotType_CotType_u_rb_a = 43,
|
||||
/* a-h-A: Hostile aircraft (generic) */
|
||||
meshtastic_CotType_CotType_a_h_A = 44,
|
||||
/* a-u-A: Unknown aircraft (generic) */
|
||||
meshtastic_CotType_CotType_a_u_A = 45,
|
||||
/* a-f-A-M-H-Q: Friendly aircraft military helicopter observation */
|
||||
meshtastic_CotType_CotType_a_f_A_M_H_Q = 46,
|
||||
/* a-f-A-C-F: Friendly aircraft civilian fixed-wing */
|
||||
meshtastic_CotType_CotType_a_f_A_C_F = 47,
|
||||
/* a-f-A-C: Friendly aircraft civilian (generic) */
|
||||
meshtastic_CotType_CotType_a_f_A_C = 48,
|
||||
/* a-f-A-C-L: Friendly aircraft civilian lighter-than-air */
|
||||
meshtastic_CotType_CotType_a_f_A_C_L = 49,
|
||||
/* a-f-A: Friendly aircraft (generic) */
|
||||
meshtastic_CotType_CotType_a_f_A = 50,
|
||||
/* a-f-A-M-H-C: Friendly aircraft military helicopter cargo */
|
||||
meshtastic_CotType_CotType_a_f_A_M_H_C = 51,
|
||||
/* a-n-A-M-F-F: Neutral aircraft military fixed-wing fighter */
|
||||
meshtastic_CotType_CotType_a_n_A_M_F_F = 52,
|
||||
/* a-u-A-C-F: Unknown aircraft civilian fixed-wing */
|
||||
meshtastic_CotType_CotType_a_u_A_C_F = 53,
|
||||
/* a-f-G-U-C-F-T-A: Friendly ground unit combat forces theater aviation */
|
||||
meshtastic_CotType_CotType_a_f_G_U_C_F_T_A = 54,
|
||||
/* a-f-G-U-C-V-S: Friendly ground unit combat vehicle support */
|
||||
meshtastic_CotType_CotType_a_f_G_U_C_V_S = 55,
|
||||
/* a-f-G-U-C-R-X: Friendly ground unit combat reconnaissance exploitation */
|
||||
meshtastic_CotType_CotType_a_f_G_U_C_R_X = 56,
|
||||
/* a-f-G-U-C-I-Z: Friendly ground unit combat infantry mechanized */
|
||||
meshtastic_CotType_CotType_a_f_G_U_C_I_Z = 57,
|
||||
/* a-f-G-U-C-E-C-W: Friendly ground unit combat engineer construction wheeled */
|
||||
meshtastic_CotType_CotType_a_f_G_U_C_E_C_W = 58,
|
||||
/* a-f-G-U-C-I-L: Friendly ground unit combat infantry light */
|
||||
meshtastic_CotType_CotType_a_f_G_U_C_I_L = 59,
|
||||
/* a-f-G-U-C-R-O: Friendly ground unit combat reconnaissance other */
|
||||
meshtastic_CotType_CotType_a_f_G_U_C_R_O = 60,
|
||||
/* a-f-G-U-C-R-V: Friendly ground unit combat reconnaissance cavalry */
|
||||
meshtastic_CotType_CotType_a_f_G_U_C_R_V = 61,
|
||||
/* a-f-G-U-H: Friendly ground unit headquarters */
|
||||
meshtastic_CotType_CotType_a_f_G_U_H = 62,
|
||||
/* a-f-G-U-U-M-S-E: Friendly ground unit support medical surgical evacuation */
|
||||
meshtastic_CotType_CotType_a_f_G_U_U_M_S_E = 63,
|
||||
/* a-f-G-U-S-M-C: Friendly ground unit support maintenance collection */
|
||||
meshtastic_CotType_CotType_a_f_G_U_S_M_C = 64,
|
||||
/* a-f-G-E-S: Friendly ground equipment sensor (generic) */
|
||||
meshtastic_CotType_CotType_a_f_G_E_S = 65,
|
||||
/* a-f-G-E: Friendly ground equipment (generic) */
|
||||
meshtastic_CotType_CotType_a_f_G_E = 66,
|
||||
/* a-f-G-E-V-C-U: Friendly ground equipment vehicle utility */
|
||||
meshtastic_CotType_CotType_a_f_G_E_V_C_U = 67,
|
||||
/* a-f-G-E-V-C-ps: Friendly ground equipment vehicle public safety */
|
||||
meshtastic_CotType_CotType_a_f_G_E_V_C_ps = 68,
|
||||
/* a-u-G-E-V: Unknown ground equipment vehicle */
|
||||
meshtastic_CotType_CotType_a_u_G_E_V = 69,
|
||||
/* a-f-S-N-N-R: Friendly sea surface non-naval rescue */
|
||||
meshtastic_CotType_CotType_a_f_S_N_N_R = 70,
|
||||
/* a-f-F-B: Friendly force boundary */
|
||||
meshtastic_CotType_CotType_a_f_F_B = 71,
|
||||
/* b-m-p-s-p-loc: Self-position location marker */
|
||||
meshtastic_CotType_CotType_b_m_p_s_p_loc = 72,
|
||||
/* b-i-v: Imagery/video */
|
||||
meshtastic_CotType_CotType_b_i_v = 73,
|
||||
/* b-f-t-r: File transfer request */
|
||||
meshtastic_CotType_CotType_b_f_t_r = 74,
|
||||
/* b-f-t-a: File transfer acknowledgment */
|
||||
meshtastic_CotType_CotType_b_f_t_a = 75
|
||||
} meshtastic_CotType;
|
||||
|
||||
/* Geopoint and altitude source */
|
||||
typedef enum _meshtastic_GeoPointSource {
|
||||
/* Unspecified */
|
||||
meshtastic_GeoPointSource_GeoPointSource_Unspecified = 0,
|
||||
/* GPS derived */
|
||||
meshtastic_GeoPointSource_GeoPointSource_GPS = 1,
|
||||
/* User entered */
|
||||
meshtastic_GeoPointSource_GeoPointSource_USER = 2,
|
||||
/* Network/external */
|
||||
meshtastic_GeoPointSource_GeoPointSource_NETWORK = 3
|
||||
} meshtastic_GeoPointSource;
|
||||
|
||||
/* Struct definitions */
|
||||
/* ATAK GeoChat message */
|
||||
typedef struct _meshtastic_GeoChat {
|
||||
@@ -146,6 +337,95 @@ typedef struct _meshtastic_TAKPacket {
|
||||
} payload_variant;
|
||||
} meshtastic_TAKPacket;
|
||||
|
||||
/* Aircraft track information from ADS-B or military air tracking.
|
||||
Covers the majority of observed real-world CoT traffic. */
|
||||
typedef struct _meshtastic_AircraftTrack {
|
||||
/* ICAO hex identifier (e.g. "AD237C") */
|
||||
char icao[8];
|
||||
/* Aircraft registration (e.g. "N946AK") */
|
||||
char registration[16];
|
||||
/* Flight number/callsign (e.g. "ASA864") */
|
||||
char flight[16];
|
||||
/* ICAO aircraft type designator (e.g. "B39M") */
|
||||
char aircraft_type[8];
|
||||
/* Transponder squawk code (0-7777 octal) */
|
||||
uint16_t squawk;
|
||||
/* ADS-B emitter category (e.g. "A3") */
|
||||
char category[4];
|
||||
/* Received signal strength * 10 (e.g. -194 for -19.4 dBm) */
|
||||
int32_t rssi_x10;
|
||||
/* Whether receiver has GPS fix */
|
||||
bool gps;
|
||||
/* CoT host ID for source attribution */
|
||||
char cot_host_id[64];
|
||||
} meshtastic_AircraftTrack;
|
||||
|
||||
typedef PB_BYTES_ARRAY_T(220) meshtastic_TAKPacketV2_raw_detail_t;
|
||||
/* ATAK v2 packet with expanded CoT field support and zstd dictionary compression.
|
||||
Sent on ATAK_PLUGIN_V2 port. The wire payload is:
|
||||
[1 byte flags][zstd-compressed TAKPacketV2 protobuf]
|
||||
Flags byte: bits 0-5 = dictionary ID, bits 6-7 = reserved. */
|
||||
typedef struct _meshtastic_TAKPacketV2 {
|
||||
/* Well-known CoT event type enum.
|
||||
Use CotType_Other with cot_type_str for unknown types. */
|
||||
meshtastic_CotType cot_type_id;
|
||||
/* How the coordinates were generated */
|
||||
meshtastic_CotHow how;
|
||||
/* Callsign */
|
||||
char callsign[120];
|
||||
/* Team color assignment */
|
||||
meshtastic_Team team;
|
||||
/* Role of the group member */
|
||||
meshtastic_MemberRole role;
|
||||
/* Latitude, multiply by 1e-7 to get degrees in floating point */
|
||||
int32_t latitude_i;
|
||||
/* Longitude, multiply by 1e-7 to get degrees in floating point */
|
||||
int32_t longitude_i;
|
||||
/* Altitude in meters (HAE) */
|
||||
int32_t altitude;
|
||||
/* Speed in cm/s */
|
||||
uint32_t speed;
|
||||
/* Course in degrees * 100 (0-36000) */
|
||||
uint16_t course;
|
||||
/* Battery level 0-100 */
|
||||
uint8_t battery;
|
||||
/* Geopoint source */
|
||||
meshtastic_GeoPointSource geo_src;
|
||||
/* Altitude source */
|
||||
meshtastic_GeoPointSource alt_src;
|
||||
/* Device UID (UUID string or device ID like "ANDROID-xxxx") */
|
||||
char uid[48];
|
||||
/* Device callsign */
|
||||
char device_callsign[120];
|
||||
/* Stale time as seconds offset from event time */
|
||||
uint16_t stale_seconds;
|
||||
/* TAK client version string */
|
||||
char tak_version[64];
|
||||
/* TAK device model */
|
||||
char tak_device[32];
|
||||
/* TAK platform (ATAK-CIV, WebTAK, etc.) */
|
||||
char tak_platform[32];
|
||||
/* TAK OS version */
|
||||
char tak_os[16];
|
||||
/* Connection endpoint */
|
||||
char endpoint[32];
|
||||
/* Phone number */
|
||||
char phone[20];
|
||||
/* CoT event type string, only populated when cot_type_id is CotType_Other */
|
||||
char cot_type_str[32];
|
||||
pb_size_t which_payload_variant;
|
||||
union {
|
||||
/* Position report (true = PLI, no extra fields beyond the common ones above) */
|
||||
bool pli;
|
||||
/* ATAK GeoChat message */
|
||||
meshtastic_GeoChat chat;
|
||||
/* Aircraft track data (ADS-B, military air) */
|
||||
meshtastic_AircraftTrack aircraft;
|
||||
/* Generic CoT detail XML for unmapped types */
|
||||
meshtastic_TAKPacketV2_raw_detail_t raw_detail;
|
||||
} payload_variant;
|
||||
} meshtastic_TAKPacketV2;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -160,6 +440,18 @@ extern "C" {
|
||||
#define _meshtastic_MemberRole_MAX meshtastic_MemberRole_K9
|
||||
#define _meshtastic_MemberRole_ARRAYSIZE ((meshtastic_MemberRole)(meshtastic_MemberRole_K9+1))
|
||||
|
||||
#define _meshtastic_CotHow_MIN meshtastic_CotHow_CotHow_Unspecified
|
||||
#define _meshtastic_CotHow_MAX meshtastic_CotHow_CotHow_m_s
|
||||
#define _meshtastic_CotHow_ARRAYSIZE ((meshtastic_CotHow)(meshtastic_CotHow_CotHow_m_s+1))
|
||||
|
||||
#define _meshtastic_CotType_MIN meshtastic_CotType_CotType_Other
|
||||
#define _meshtastic_CotType_MAX meshtastic_CotType_CotType_b_f_t_a
|
||||
#define _meshtastic_CotType_ARRAYSIZE ((meshtastic_CotType)(meshtastic_CotType_CotType_b_f_t_a+1))
|
||||
|
||||
#define _meshtastic_GeoPointSource_MIN meshtastic_GeoPointSource_GeoPointSource_Unspecified
|
||||
#define _meshtastic_GeoPointSource_MAX meshtastic_GeoPointSource_GeoPointSource_NETWORK
|
||||
#define _meshtastic_GeoPointSource_ARRAYSIZE ((meshtastic_GeoPointSource)(meshtastic_GeoPointSource_GeoPointSource_NETWORK+1))
|
||||
|
||||
|
||||
|
||||
#define meshtastic_Group_role_ENUMTYPE meshtastic_MemberRole
|
||||
@@ -169,6 +461,14 @@ extern "C" {
|
||||
|
||||
|
||||
|
||||
#define meshtastic_TAKPacketV2_cot_type_id_ENUMTYPE meshtastic_CotType
|
||||
#define meshtastic_TAKPacketV2_how_ENUMTYPE meshtastic_CotHow
|
||||
#define meshtastic_TAKPacketV2_team_ENUMTYPE meshtastic_Team
|
||||
#define meshtastic_TAKPacketV2_role_ENUMTYPE meshtastic_MemberRole
|
||||
#define meshtastic_TAKPacketV2_geo_src_ENUMTYPE meshtastic_GeoPointSource
|
||||
#define meshtastic_TAKPacketV2_alt_src_ENUMTYPE meshtastic_GeoPointSource
|
||||
|
||||
|
||||
/* Initializer values for message structs */
|
||||
#define meshtastic_TAKPacket_init_default {0, false, meshtastic_Contact_init_default, false, meshtastic_Group_init_default, false, meshtastic_Status_init_default, 0, {meshtastic_PLI_init_default}}
|
||||
#define meshtastic_GeoChat_init_default {"", false, "", false, ""}
|
||||
@@ -176,12 +476,16 @@ extern "C" {
|
||||
#define meshtastic_Status_init_default {0}
|
||||
#define meshtastic_Contact_init_default {"", ""}
|
||||
#define meshtastic_PLI_init_default {0, 0, 0, 0, 0}
|
||||
#define meshtastic_AircraftTrack_init_default {"", "", "", "", 0, "", 0, 0, ""}
|
||||
#define meshtastic_TAKPacketV2_init_default {_meshtastic_CotType_MIN, _meshtastic_CotHow_MIN, "", _meshtastic_Team_MIN, _meshtastic_MemberRole_MIN, 0, 0, 0, 0, 0, 0, _meshtastic_GeoPointSource_MIN, _meshtastic_GeoPointSource_MIN, "", "", 0, "", "", "", "", "", "", "", 0, {0}}
|
||||
#define meshtastic_TAKPacket_init_zero {0, false, meshtastic_Contact_init_zero, false, meshtastic_Group_init_zero, false, meshtastic_Status_init_zero, 0, {meshtastic_PLI_init_zero}}
|
||||
#define meshtastic_GeoChat_init_zero {"", false, "", false, ""}
|
||||
#define meshtastic_Group_init_zero {_meshtastic_MemberRole_MIN, _meshtastic_Team_MIN}
|
||||
#define meshtastic_Status_init_zero {0}
|
||||
#define meshtastic_Contact_init_zero {"", ""}
|
||||
#define meshtastic_PLI_init_zero {0, 0, 0, 0, 0}
|
||||
#define meshtastic_AircraftTrack_init_zero {"", "", "", "", 0, "", 0, 0, ""}
|
||||
#define meshtastic_TAKPacketV2_init_zero {_meshtastic_CotType_MIN, _meshtastic_CotHow_MIN, "", _meshtastic_Team_MIN, _meshtastic_MemberRole_MIN, 0, 0, 0, 0, 0, 0, _meshtastic_GeoPointSource_MIN, _meshtastic_GeoPointSource_MIN, "", "", 0, "", "", "", "", "", "", "", 0, {0}}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
#define meshtastic_GeoChat_message_tag 1
|
||||
@@ -204,6 +508,42 @@ extern "C" {
|
||||
#define meshtastic_TAKPacket_pli_tag 5
|
||||
#define meshtastic_TAKPacket_chat_tag 6
|
||||
#define meshtastic_TAKPacket_detail_tag 7
|
||||
#define meshtastic_AircraftTrack_icao_tag 1
|
||||
#define meshtastic_AircraftTrack_registration_tag 2
|
||||
#define meshtastic_AircraftTrack_flight_tag 3
|
||||
#define meshtastic_AircraftTrack_aircraft_type_tag 4
|
||||
#define meshtastic_AircraftTrack_squawk_tag 5
|
||||
#define meshtastic_AircraftTrack_category_tag 6
|
||||
#define meshtastic_AircraftTrack_rssi_x10_tag 7
|
||||
#define meshtastic_AircraftTrack_gps_tag 8
|
||||
#define meshtastic_AircraftTrack_cot_host_id_tag 9
|
||||
#define meshtastic_TAKPacketV2_cot_type_id_tag 1
|
||||
#define meshtastic_TAKPacketV2_how_tag 2
|
||||
#define meshtastic_TAKPacketV2_callsign_tag 3
|
||||
#define meshtastic_TAKPacketV2_team_tag 4
|
||||
#define meshtastic_TAKPacketV2_role_tag 5
|
||||
#define meshtastic_TAKPacketV2_latitude_i_tag 6
|
||||
#define meshtastic_TAKPacketV2_longitude_i_tag 7
|
||||
#define meshtastic_TAKPacketV2_altitude_tag 8
|
||||
#define meshtastic_TAKPacketV2_speed_tag 9
|
||||
#define meshtastic_TAKPacketV2_course_tag 10
|
||||
#define meshtastic_TAKPacketV2_battery_tag 11
|
||||
#define meshtastic_TAKPacketV2_geo_src_tag 12
|
||||
#define meshtastic_TAKPacketV2_alt_src_tag 13
|
||||
#define meshtastic_TAKPacketV2_uid_tag 14
|
||||
#define meshtastic_TAKPacketV2_device_callsign_tag 15
|
||||
#define meshtastic_TAKPacketV2_stale_seconds_tag 16
|
||||
#define meshtastic_TAKPacketV2_tak_version_tag 17
|
||||
#define meshtastic_TAKPacketV2_tak_device_tag 18
|
||||
#define meshtastic_TAKPacketV2_tak_platform_tag 19
|
||||
#define meshtastic_TAKPacketV2_tak_os_tag 20
|
||||
#define meshtastic_TAKPacketV2_endpoint_tag 21
|
||||
#define meshtastic_TAKPacketV2_phone_tag 22
|
||||
#define meshtastic_TAKPacketV2_cot_type_str_tag 23
|
||||
#define meshtastic_TAKPacketV2_pli_tag 30
|
||||
#define meshtastic_TAKPacketV2_chat_tag 31
|
||||
#define meshtastic_TAKPacketV2_aircraft_tag 32
|
||||
#define meshtastic_TAKPacketV2_raw_detail_tag 33
|
||||
|
||||
/* Struct field encoding specification for nanopb */
|
||||
#define meshtastic_TAKPacket_FIELDLIST(X, a) \
|
||||
@@ -255,12 +595,60 @@ X(a, STATIC, SINGULAR, UINT32, course, 5)
|
||||
#define meshtastic_PLI_CALLBACK NULL
|
||||
#define meshtastic_PLI_DEFAULT NULL
|
||||
|
||||
#define meshtastic_AircraftTrack_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, STRING, icao, 1) \
|
||||
X(a, STATIC, SINGULAR, STRING, registration, 2) \
|
||||
X(a, STATIC, SINGULAR, STRING, flight, 3) \
|
||||
X(a, STATIC, SINGULAR, STRING, aircraft_type, 4) \
|
||||
X(a, STATIC, SINGULAR, UINT32, squawk, 5) \
|
||||
X(a, STATIC, SINGULAR, STRING, category, 6) \
|
||||
X(a, STATIC, SINGULAR, SINT32, rssi_x10, 7) \
|
||||
X(a, STATIC, SINGULAR, BOOL, gps, 8) \
|
||||
X(a, STATIC, SINGULAR, STRING, cot_host_id, 9)
|
||||
#define meshtastic_AircraftTrack_CALLBACK NULL
|
||||
#define meshtastic_AircraftTrack_DEFAULT NULL
|
||||
|
||||
#define meshtastic_TAKPacketV2_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, UENUM, cot_type_id, 1) \
|
||||
X(a, STATIC, SINGULAR, UENUM, how, 2) \
|
||||
X(a, STATIC, SINGULAR, STRING, callsign, 3) \
|
||||
X(a, STATIC, SINGULAR, UENUM, team, 4) \
|
||||
X(a, STATIC, SINGULAR, UENUM, role, 5) \
|
||||
X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 6) \
|
||||
X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 7) \
|
||||
X(a, STATIC, SINGULAR, SINT32, altitude, 8) \
|
||||
X(a, STATIC, SINGULAR, UINT32, speed, 9) \
|
||||
X(a, STATIC, SINGULAR, UINT32, course, 10) \
|
||||
X(a, STATIC, SINGULAR, UINT32, battery, 11) \
|
||||
X(a, STATIC, SINGULAR, UENUM, geo_src, 12) \
|
||||
X(a, STATIC, SINGULAR, UENUM, alt_src, 13) \
|
||||
X(a, STATIC, SINGULAR, STRING, uid, 14) \
|
||||
X(a, STATIC, SINGULAR, STRING, device_callsign, 15) \
|
||||
X(a, STATIC, SINGULAR, UINT32, stale_seconds, 16) \
|
||||
X(a, STATIC, SINGULAR, STRING, tak_version, 17) \
|
||||
X(a, STATIC, SINGULAR, STRING, tak_device, 18) \
|
||||
X(a, STATIC, SINGULAR, STRING, tak_platform, 19) \
|
||||
X(a, STATIC, SINGULAR, STRING, tak_os, 20) \
|
||||
X(a, STATIC, SINGULAR, STRING, endpoint, 21) \
|
||||
X(a, STATIC, SINGULAR, STRING, phone, 22) \
|
||||
X(a, STATIC, SINGULAR, STRING, cot_type_str, 23) \
|
||||
X(a, STATIC, ONEOF, BOOL, (payload_variant,pli,payload_variant.pli), 30) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,chat,payload_variant.chat), 31) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,aircraft,payload_variant.aircraft), 32) \
|
||||
X(a, STATIC, ONEOF, BYTES, (payload_variant,raw_detail,payload_variant.raw_detail), 33)
|
||||
#define meshtastic_TAKPacketV2_CALLBACK NULL
|
||||
#define meshtastic_TAKPacketV2_DEFAULT NULL
|
||||
#define meshtastic_TAKPacketV2_payload_variant_chat_MSGTYPE meshtastic_GeoChat
|
||||
#define meshtastic_TAKPacketV2_payload_variant_aircraft_MSGTYPE meshtastic_AircraftTrack
|
||||
|
||||
extern const pb_msgdesc_t meshtastic_TAKPacket_msg;
|
||||
extern const pb_msgdesc_t meshtastic_GeoChat_msg;
|
||||
extern const pb_msgdesc_t meshtastic_Group_msg;
|
||||
extern const pb_msgdesc_t meshtastic_Status_msg;
|
||||
extern const pb_msgdesc_t meshtastic_Contact_msg;
|
||||
extern const pb_msgdesc_t meshtastic_PLI_msg;
|
||||
extern const pb_msgdesc_t meshtastic_AircraftTrack_msg;
|
||||
extern const pb_msgdesc_t meshtastic_TAKPacketV2_msg;
|
||||
|
||||
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
|
||||
#define meshtastic_TAKPacket_fields &meshtastic_TAKPacket_msg
|
||||
@@ -269,14 +657,18 @@ extern const pb_msgdesc_t meshtastic_PLI_msg;
|
||||
#define meshtastic_Status_fields &meshtastic_Status_msg
|
||||
#define meshtastic_Contact_fields &meshtastic_Contact_msg
|
||||
#define meshtastic_PLI_fields &meshtastic_PLI_msg
|
||||
#define meshtastic_AircraftTrack_fields &meshtastic_AircraftTrack_msg
|
||||
#define meshtastic_TAKPacketV2_fields &meshtastic_TAKPacketV2_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define MESHTASTIC_MESHTASTIC_ATAK_PB_H_MAX_SIZE meshtastic_TAKPacket_size
|
||||
#define MESHTASTIC_MESHTASTIC_ATAK_PB_H_MAX_SIZE meshtastic_TAKPacketV2_size
|
||||
#define meshtastic_AircraftTrack_size 134
|
||||
#define meshtastic_Contact_size 242
|
||||
#define meshtastic_GeoChat_size 444
|
||||
#define meshtastic_Group_size 4
|
||||
#define meshtastic_PLI_size 31
|
||||
#define meshtastic_Status_size 3
|
||||
#define meshtastic_TAKPacketV2_size 1027
|
||||
#define meshtastic_TAKPacket_size 705
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -308,6 +308,8 @@ typedef enum _meshtastic_HardwareModel {
|
||||
meshtastic_HardwareModel_TDISPLAY_S3_PRO = 126,
|
||||
/* Heltec Mesh Node T096 board features an nRF52840 CPU and a TFT screen. */
|
||||
meshtastic_HardwareModel_HELTEC_MESH_NODE_T096 = 127,
|
||||
/* Seeed studio T1000-E Pro tracker card. NRF52840 w/ LR2021 radio, GPS, button, buzzer, and sensors. */
|
||||
meshtastic_HardwareModel_TRACKER_T1000_E_PRO = 128,
|
||||
/* ------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
|
||||
------------------------------------------------------------------------------------------------------------------------------------------ */
|
||||
|
||||
@@ -150,6 +150,10 @@ typedef enum _meshtastic_PortNum {
|
||||
arbitrary telemetry over meshtastic that is not covered by telemetry.proto
|
||||
ENCODING: CayenneLLP */
|
||||
meshtastic_PortNum_CAYENNE_APP = 77,
|
||||
/* ATAK Plugin V2
|
||||
Portnum for payloads from the official Meshtastic ATAK plugin using
|
||||
TAKPacketV2 with zstd dictionary compression. */
|
||||
meshtastic_PortNum_ATAK_PLUGIN_V2 = 78,
|
||||
/* GroupAlarm integration
|
||||
Used for transporting GroupAlarm-related messages between Meshtastic nodes
|
||||
and companion applications/services. */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -203,6 +203,10 @@ void ExternalNotificationModule::setExternalState(uint8_t index, bool on)
|
||||
default:
|
||||
if (output > 0)
|
||||
digitalWrite(output, (moduleConfig.external_notification.active ? on : !on));
|
||||
#ifdef PCA_LED_NOTIFICATION
|
||||
io.digitalWrite(PCA_LED_NOTIFICATION, on);
|
||||
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,10 @@
|
||||
#include "modules/StatusMessageModule.h"
|
||||
#endif
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_ZPS
|
||||
#include "modules/esp32/ZPSModule.h"
|
||||
#endif
|
||||
|
||||
#if defined(HAS_HARDWARE_WATCHDOG)
|
||||
#include "watchdog/watchdogThread.h"
|
||||
#endif
|
||||
@@ -174,6 +178,9 @@ void setupModules()
|
||||
#if !MESHTASTIC_EXCLUDE_STATUS
|
||||
statusMessageModule = new StatusMessageModule();
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_ZPS
|
||||
zpsModule = new ZPSModule();
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE
|
||||
new GenericThreadModule();
|
||||
#endif
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user