mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-08 05:00:05 +10:00
Compare commits
173
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4b3e0616b | ||
|
|
d4b627a60c | ||
|
|
efd2613bd7 | ||
|
|
f88bc732cc | ||
|
|
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 | ||
|
|
d3a86841b9 | ||
|
|
3b079c91bf | ||
|
|
d693fd4232 | ||
|
|
163c54877c | ||
|
|
2e2993f180 | ||
|
|
450f2adab0 | ||
|
|
e14b8d385a | ||
|
|
8ce1a872eb | ||
|
|
0ad1b66387 | ||
|
|
5716aeba3b | ||
|
|
abfa346630 | ||
|
|
723209b198 | ||
|
|
2ffe2d829c | ||
|
|
bfaf6c6b20 | ||
|
|
5a8ce60d68 | ||
|
|
d293d654a0 | ||
|
|
8384659608 | ||
|
|
c77b10a317 | ||
|
|
8fa5b4f7ce | ||
|
|
e30294b6da | ||
|
|
948c28afff | ||
|
|
a632d0133f | ||
|
|
f982b58d61 | ||
|
|
efb0b299b2 | ||
|
|
d09433e453 | ||
|
|
139b970409 | ||
|
|
eb80d3141d | ||
|
|
3604c1255d | ||
|
|
22d63fa69c | ||
|
|
fc030d2d19 | ||
|
|
4a534f02a4 | ||
|
|
0ea408e12b | ||
|
|
1fe9a41fb9 | ||
|
|
644d0d4a15 | ||
|
|
2aefd386b6 | ||
|
|
f04746a928 | ||
|
|
532a63e541 | ||
|
|
e4c5bfd161 | ||
|
|
1be2529fb9 | ||
|
|
ea3386f34a | ||
|
|
959756abf1 | ||
|
|
e1d238b75f | ||
|
|
c88b802e32 | ||
|
|
286bc852b3 | ||
|
|
e282491cd8 | ||
|
|
58fee80b30 | ||
|
|
3673af46cf | ||
|
|
7404228555 | ||
|
|
76b6eaa48a | ||
|
|
344fe2f70b | ||
|
|
9f74fc11de | ||
|
|
458e46b1ea | ||
|
|
6f5448fa61 | ||
|
|
d369825c38 | ||
|
|
4fbd5c9f80 | ||
|
|
aebcb34c80 | ||
|
|
2ef09d17b9 | ||
|
|
39aefde18d | ||
|
|
19d070c284 | ||
|
|
e24db2994b | ||
|
|
b7379f1516 | ||
|
|
fe8bfa0069 | ||
|
|
52750927e3 | ||
|
|
3ef7a15a17 | ||
|
|
ac7a58cd45 | ||
|
|
53c21eb30d | ||
|
|
e51e6cad84 | ||
|
|
4890f7084f | ||
|
|
3fcbfe4370 | ||
|
|
b155a63d3b | ||
|
|
60730a73c2 | ||
|
|
421f9afead | ||
|
|
da808cb43b | ||
|
|
82580c6798 | ||
|
|
429cf51539 | ||
|
|
d9e2b12097 | ||
|
|
016e68ec53 | ||
|
|
d7d08a4725 | ||
|
|
79f469ce71 | ||
|
|
0668b82858 | ||
|
|
0797378a4d | ||
|
|
0501e177e9 | ||
|
|
a79b75a0d5 | ||
|
|
391928ed08 | ||
|
|
6cbb9ab09a | ||
|
|
f185abbcdd | ||
|
|
5d12edbe07 | ||
|
|
398d1adea1 | ||
|
|
393cd96ba2 | ||
|
|
6658ec2603 | ||
|
|
b941dab900 | ||
|
|
ffe6f706f9 | ||
|
|
0ed537a336 | ||
|
|
86dad90573 | ||
|
|
20db5fdbb7 | ||
|
|
5b1ea922f3 | ||
|
|
5b94f580dc | ||
|
|
3d524be965 | ||
|
|
4ec59ce75d | ||
|
|
cb28c38828 | ||
|
|
969aefa551 | ||
|
|
3f5828c0e9 | ||
|
|
5cf365245e | ||
|
|
fdd17ac75c | ||
|
|
9e40c8893f | ||
|
|
afd5e29bce | ||
|
|
20f838a61e | ||
|
|
58736f5184 | ||
|
|
22031c5886 | ||
|
|
fe4e75eb66 | ||
|
|
42075da09e | ||
|
|
6ba82ed5b6 | ||
|
|
935b0504d4 | ||
|
|
a8fed3256e | ||
|
|
1626836a71 | ||
|
|
66161da2a7 | ||
|
|
6bbdfe1797 | ||
|
|
7ea28d34b2 | ||
|
|
65bf1630b3 | ||
|
|
3972fe1fd0 | ||
|
|
5ced739a57 | ||
|
|
b7bf251798 | ||
|
|
90742fc87e | ||
|
|
f7f356e2e2 | ||
|
|
1fad7facd6 | ||
|
|
153ab81a9f | ||
|
|
8d73d67246 | ||
|
|
0d46943bb8 | ||
|
|
deb45e471c | ||
|
|
3601eabbf8 | ||
|
|
eb2f3cef89 | ||
|
|
85be5965b0 | ||
|
|
1e34c1ef1b | ||
|
|
5f852a1c05 | ||
|
|
1d90ea6f1d | ||
|
|
65eee22396 | ||
|
|
cb938266c7 | ||
|
|
8093e2ed5a | ||
|
|
3f5dc78744 |
@@ -100,7 +100,7 @@ runs:
|
||||
id: version
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.long }}
|
||||
overwrite: true
|
||||
|
||||
@@ -8,8 +8,6 @@ runs:
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
|
||||
- Before starting on some new big chunk of code, it it is optional but highly recommended to open an issue first
|
||||
to say "Hey, I think this idea X should be implemented and I'm starting work on it. My general plan is Y, any feedback
|
||||
is appreciated." This will allow other devs to potentially save you time by not accidentially duplicating work etc...
|
||||
is appreciated." This will allow other devs to potentially save you time by not accidentally duplicating work etc...
|
||||
- Please do not check in files that don't have real changes
|
||||
- Please do not reformat lines that you didn't have to change the code on
|
||||
- We recommend using the [Visual Studio Code](https://platformio.org/install/ide?install=vscode) editor along with the ['Trunk Check' extension](https://marketplace.visualstudio.com/items?itemName=trunk.io) (In beta for windows, WSL2 for the linux version),
|
||||
- We recommend using the [Visual Studio Code](https://platformio.org/install/ide?install=vscode) editor along with the ['Trunk Check' extension](https://marketplace.visualstudio.com/items?itemName=trunk.io) (In beta for windows, WSL2 for the Linux version),
|
||||
because it automatically follows our indentation rules and its auto reformatting will not cause spurious changes to lines.
|
||||
- If your PR fixes a bug, mention "fixes #bugnum" somewhere in your pull request description.
|
||||
- If your other co-developers have comments on your PR please tweak as needed.
|
||||
- Please also enable "Allow edits by maintainers".
|
||||
- Please do not submit untested code.
|
||||
- If you do not have the affected hardware to test your code changes adequately against regressions, please indicate this, so that contributors and commnunity members can help test your changes.
|
||||
- If you do not have the affected hardware to test your code changes adequately against regressions, please indicate this, so that contributors and community members can help test your changes.
|
||||
- If your PR gets accepted you can request a "Contributor" role in the Meshtastic Discord
|
||||
|
||||
## 🤝 Attestations
|
||||
|
||||
@@ -16,8 +16,7 @@ on:
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-debian-src:
|
||||
@@ -28,8 +27,6 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
path: meshtasticd
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
@@ -42,7 +39,8 @@ jobs:
|
||||
sudo mk-build-deps --install --remove --tool='apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes' debian/control
|
||||
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
if: github.event_name != 'pull_request' && github.event_name != 'pull_request_target'
|
||||
uses: crazy-max/ghaction-import-gpg@v7
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }}
|
||||
id: gpg
|
||||
@@ -60,11 +58,11 @@ jobs:
|
||||
run: debian/ci_pack_sdeb.sh
|
||||
env:
|
||||
SERIES: ${{ inputs.series }}
|
||||
GPG_KEY_ID: ${{ steps.gpg.outputs.keyid }}
|
||||
GPG_KEY_ID: ${{ steps.gpg.outputs.keyid || '' }}
|
||||
PKG_VERSION: ${{ steps.version.outputs.deb }}
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||
overwrite: true
|
||||
|
||||
@@ -26,8 +26,6 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Build ${{ inputs.platform }}
|
||||
id: build
|
||||
@@ -111,7 +109,7 @@ jobs:
|
||||
echo "</details>" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
id: upload-firmware
|
||||
with:
|
||||
name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}
|
||||
@@ -127,7 +125,7 @@ jobs:
|
||||
release/device-*.bat
|
||||
|
||||
- name: Store manifests as an artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
id: upload-manifest
|
||||
with:
|
||||
name: manifest-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}
|
||||
|
||||
@@ -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,16 +92,16 @@ 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:
|
||||
contents: write
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
needs: [version, build]
|
||||
@@ -98,7 +112,7 @@ jobs:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: ./
|
||||
pattern: firmware-*-*
|
||||
@@ -111,7 +125,7 @@ jobs:
|
||||
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
|
||||
|
||||
- name: Repackage in single firmware zip
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
|
||||
overwrite: true
|
||||
@@ -127,7 +141,7 @@ jobs:
|
||||
./Meshtastic_nRF52_factory_erase*.uf2
|
||||
retention-days: 30
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: firmware-*-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -146,7 +160,7 @@ jobs:
|
||||
run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
|
||||
|
||||
- name: Repackage in single elfs zip
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
|
||||
overwrite: true
|
||||
|
||||
@@ -5,7 +5,7 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop # Default branch, same as 'cron' above
|
||||
paths:
|
||||
- debian/**
|
||||
- "*.rpkg"
|
||||
@@ -16,7 +16,7 @@ on:
|
||||
- .github/workflows/hook_copr.yml
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
@@ -35,8 +35,8 @@ jobs:
|
||||
series:
|
||||
- jammy # 22.04 LTS
|
||||
- noble # 24.04 LTS
|
||||
- plucky # 25.04
|
||||
- questing # 25.10
|
||||
- resolute # 26.04 LTS
|
||||
uses: ./.github/workflows/package_ppa.yml
|
||||
with:
|
||||
ppa_repo: ppa:meshtastic/daily
|
||||
|
||||
@@ -37,7 +37,7 @@ on:
|
||||
value: ${{ jobs.docker-build.outputs.digest }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
@@ -50,8 +50,6 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Get release version string
|
||||
run: |
|
||||
@@ -60,16 +58,16 @@ jobs:
|
||||
|
||||
- name: Docker login
|
||||
if: ${{ inputs.push }}
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: meshtastic
|
||||
password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Docker setup
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Sanitize platform string
|
||||
id: sanitize_platform
|
||||
@@ -78,7 +76,7 @@ jobs:
|
||||
|
||||
- name: Docker tag
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: meshtastic/meshtasticd
|
||||
tags: |
|
||||
@@ -86,7 +84,7 @@ jobs:
|
||||
flavor: latest=false
|
||||
|
||||
- name: Docker build and push
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
id: docker_variant
|
||||
with:
|
||||
context: .
|
||||
|
||||
@@ -12,7 +12,7 @@ on:
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
@@ -86,8 +86,6 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Get release version string
|
||||
run: |
|
||||
@@ -139,14 +137,14 @@ jobs:
|
||||
id: tags
|
||||
|
||||
- name: Docker login
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: meshtastic
|
||||
password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }}
|
||||
|
||||
- name: Docker meta (Debian)
|
||||
id: meta_debian
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: meshtastic/meshtasticd
|
||||
tags: |
|
||||
@@ -167,7 +165,7 @@ jobs:
|
||||
|
||||
- name: Docker meta (Alpine)
|
||||
id: meta_alpine
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: meshtastic/meshtasticd
|
||||
tags: |
|
||||
|
||||
@@ -11,8 +11,7 @@ on:
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-copr-hook:
|
||||
@@ -22,8 +21,6 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{ github.ref }}
|
||||
repository: ${{ github.repository }}
|
||||
|
||||
- name: Trigger COPR build
|
||||
uses: vidplace7/copr-build@main
|
||||
|
||||
@@ -15,8 +15,7 @@ on:
|
||||
- "**.md"
|
||||
- version.properties
|
||||
|
||||
# Note: This is different from "pull_request". Need to specify ref when doing checkouts.
|
||||
pull_request_target:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
@@ -29,6 +28,8 @@ on:
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
strategy:
|
||||
@@ -88,8 +89,6 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
- name: Check ${{ matrix.check.board }}
|
||||
uses: meshtastic/gh-action-firmware@main
|
||||
with:
|
||||
@@ -126,9 +125,16 @@ jobs:
|
||||
|
||||
test-native:
|
||||
if: ${{ !contains(github.ref_name, 'event/') && github.repository == 'meshtastic/firmware' }}
|
||||
permissions: # Needed for dorny/test-reporter.
|
||||
contents: read
|
||||
actions: read
|
||||
checks: write
|
||||
uses: ./.github/workflows/test_native.yml
|
||||
|
||||
docker:
|
||||
permissions: # Needed for pushing to GHCR.
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -153,9 +159,6 @@ jobs:
|
||||
gather-artifacts:
|
||||
# trunk-ignore(checkov/CKV2_GHA_1)
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -173,11 +176,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: ./
|
||||
pattern: firmware-${{matrix.arch}}-*
|
||||
@@ -187,7 +187,7 @@ jobs:
|
||||
run: ls -R
|
||||
|
||||
- name: Repackage in single firmware zip
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
overwrite: true
|
||||
@@ -205,7 +205,7 @@ jobs:
|
||||
./Meshtastic_nRF52_factory_erase*.uf2
|
||||
retention-days: 30
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -224,20 +224,13 @@ jobs:
|
||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||
|
||||
- name: Repackage in single elfs zip
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
overwrite: true
|
||||
path: ./*.elf
|
||||
retention-days: 30
|
||||
|
||||
- uses: scruplelesswizard/comment-artifact@main
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
with:
|
||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
description: "Download firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
shame:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
continue-on-error: true
|
||||
@@ -245,42 +238,44 @@ jobs:
|
||||
needs: [build]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
if: github.event_name == 'pull_request_target'
|
||||
if: github.event_name == 'pull_request'
|
||||
with:
|
||||
filter: blob:none # means we download all the git history but none of the commit (except ones with checkout like the head)
|
||||
fetch-depth: 0
|
||||
- name: Download the current manifests
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: ./manifests-new/
|
||||
pattern: manifest-*
|
||||
merge-multiple: true
|
||||
- name: Upload combined manifests for later commit and global stats crunching.
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
id: upload-manifest
|
||||
with:
|
||||
name: manifests-${{ github.sha }}
|
||||
overwrite: true
|
||||
path: manifests-new/*.mt.json
|
||||
- name: Find the merge base
|
||||
if: github.event_name == 'pull_request_target'
|
||||
if: github.event_name == 'pull_request'
|
||||
run: echo "MERGE_BASE=$(git merge-base "origin/$base" "$head")" >> $GITHUB_ENV
|
||||
env:
|
||||
base: ${{ github.base_ref }}
|
||||
head: ${{ github.sha }}
|
||||
# Currently broken (for-loop through EVERY artifact -- rate limiting)
|
||||
# - name: Download the old manifests
|
||||
# if: github.event_name == 'pull_request_target'
|
||||
# if: github.event_name == 'pull_request'
|
||||
# run: gh run download -R "$repo" --name "manifests-$merge_base" --dir manifest-old/
|
||||
# env:
|
||||
# GH_TOKEN: ${{ github.token }}
|
||||
# merge_base: ${{ env.MERGE_BASE }}
|
||||
# repo: ${{ github.repository }}
|
||||
# - name: Do scan and post comment
|
||||
# if: github.event_name == 'pull_request_target'
|
||||
# if: github.event_name == 'pull_request'
|
||||
# run: python3 bin/shame.py ${{ github.event.pull_request.number }} manifests-old/ manifests-new/
|
||||
|
||||
release-artifacts:
|
||||
permissions: # Needed for 'gh release upload'.
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
|
||||
outputs:
|
||||
@@ -324,14 +319,14 @@ jobs:
|
||||
body: ${{ steps.release_notes.outputs.notes }}
|
||||
|
||||
- name: Download source deb
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
|
||||
merge-multiple: true
|
||||
path: ./output/debian-src
|
||||
|
||||
- name: Download `native-tft` pio deps
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -355,7 +350,7 @@ jobs:
|
||||
}' > firmware-${{ needs.version.outputs.long }}.json
|
||||
|
||||
- name: Save Release manifest artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: manifest-${{ needs.version.outputs.long }}
|
||||
overwrite: true
|
||||
@@ -372,6 +367,8 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
release-firmware:
|
||||
permissions: # Needed for 'gh release upload'.
|
||||
contents: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -396,7 +393,7 @@ jobs:
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -413,7 +410,7 @@ jobs:
|
||||
- name: Zip firmware
|
||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -454,14 +451,14 @@ jobs:
|
||||
python-version: 3.x
|
||||
|
||||
- name: Get firmware artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
path: ./publish
|
||||
|
||||
- name: Get manifest artifact
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: manifest-${{ needs.version.outputs.long }}
|
||||
path: ./publish
|
||||
|
||||
@@ -1,371 +0,0 @@
|
||||
name: Merge Queue
|
||||
# Not sure how concurrency works in merge_queue, removing for now.
|
||||
# concurrency:
|
||||
# group: merge-queue-${{ github.head_ref || github.run_id }}
|
||||
# cancel-in-progress: true
|
||||
on:
|
||||
# Merge group is a special trigger that is used to trigger the workflow when a merge group is created.
|
||||
merge_group:
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
arch:
|
||||
- all
|
||||
- check
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.x
|
||||
cache: pip
|
||||
- run: pip install -U platformio
|
||||
- name: Generate matrix
|
||||
id: jsonStep
|
||||
run: |
|
||||
if [[ "$GITHUB_HEAD_REF" == "" ]]; then
|
||||
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
|
||||
else
|
||||
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr)
|
||||
fi
|
||||
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
|
||||
echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT
|
||||
outputs:
|
||||
all: ${{ steps.jsonStep.outputs.all }}
|
||||
check: ${{ steps.jsonStep.outputs.check }}
|
||||
|
||||
version:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Get release version string
|
||||
run: |
|
||||
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
env:
|
||||
BUILD_LOCATION: local
|
||||
outputs:
|
||||
long: ${{ steps.version.outputs.long }}
|
||||
deb: ${{ steps.version.outputs.deb }}
|
||||
|
||||
check:
|
||||
needs: setup
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
check: ${{ fromJson(needs.setup.outputs.check) }}
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Build base
|
||||
id: base
|
||||
uses: ./.github/actions/setup-base
|
||||
- name: Check ${{ matrix.check.board }}
|
||||
run: bin/check-all.sh ${{ matrix.check.board }}
|
||||
|
||||
build:
|
||||
needs: [setup, version]
|
||||
strategy:
|
||||
matrix:
|
||||
build: ${{ fromJson(needs.setup.outputs.all) }}
|
||||
uses: ./.github/workflows/build_firmware.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.long }}
|
||||
pio_env: ${{ matrix.build.board }}
|
||||
platform: ${{ matrix.build.platform }}
|
||||
|
||||
build-debian-src:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
uses: ./.github/workflows/build_debian_src.yml
|
||||
with:
|
||||
series: UNRELEASED
|
||||
build_location: local
|
||||
secrets: inherit
|
||||
|
||||
package-pio-deps-native-tft:
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/package_pio_deps.yml
|
||||
with:
|
||||
pio_env: native-tft
|
||||
secrets: inherit
|
||||
|
||||
test-native:
|
||||
if: ${{ !contains(github.ref_name, 'event/') }}
|
||||
uses: ./.github/workflows/test_native.yml
|
||||
|
||||
docker:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
distro: [debian, alpine]
|
||||
platform: [linux/amd64, linux/arm64, linux/arm/v7]
|
||||
pio_env: [native, native-tft]
|
||||
exclude:
|
||||
- distro: alpine
|
||||
platform: linux/arm/v7
|
||||
- pio_env: native-tft
|
||||
platform: linux/arm64
|
||||
- pio_env: native-tft
|
||||
platform: linux/arm/v7
|
||||
uses: ./.github/workflows/docker_build.yml
|
||||
with:
|
||||
distro: ${{ matrix.distro }}
|
||||
platform: ${{ matrix.platform }}
|
||||
runs-on: ${{ contains(matrix.platform, 'arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||
pio_env: ${{ matrix.pio_env }}
|
||||
push: false
|
||||
|
||||
gather-artifacts:
|
||||
# trunk-ignore(checkov/CKV2_GHA_1)
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch:
|
||||
- esp32
|
||||
- esp32s3
|
||||
- esp32c3
|
||||
- esp32c6
|
||||
- nrf52840
|
||||
- rp2040
|
||||
- rp2350
|
||||
- stm32
|
||||
runs-on: ubuntu-latest
|
||||
needs: [version, build]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: ./
|
||||
pattern: firmware-${{matrix.arch}}-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R
|
||||
|
||||
- name: Move files up
|
||||
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
|
||||
|
||||
- name: Repackage in single firmware zip
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
overwrite: true
|
||||
path: |
|
||||
./firmware-*.bin
|
||||
./firmware-*.uf2
|
||||
./firmware-*.hex
|
||||
./firmware-*.zip
|
||||
./device-*.sh
|
||||
./device-*.bat
|
||||
./littlefs-*.bin
|
||||
./bleota*bin
|
||||
./Meshtastic_nRF52_factory_erase*.uf2
|
||||
retention-days: 30
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
path: ./output
|
||||
|
||||
# For diagnostics
|
||||
- name: Show artifacts
|
||||
run: ls -lR
|
||||
|
||||
- name: Device scripts permissions
|
||||
run: |
|
||||
chmod +x ./output/device-install.sh || true
|
||||
chmod +x ./output/device-update.sh || true
|
||||
|
||||
- name: Zip firmware
|
||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||
|
||||
- name: Repackage in single elfs zip
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
overwrite: true
|
||||
path: ./*.elf
|
||||
retention-days: 30
|
||||
|
||||
- uses: scruplelesswizard/comment-artifact@main
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
with:
|
||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
description: "Download firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
release-artifacts:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
outputs:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
needs:
|
||||
- version
|
||||
- gather-artifacts
|
||||
- build-debian-src
|
||||
- package-pio-deps-native-tft
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v2
|
||||
id: create_release
|
||||
with:
|
||||
draft: true
|
||||
prerelease: true
|
||||
name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
|
||||
tag_name: v${{ needs.version.outputs.long }}
|
||||
body: |
|
||||
Autogenerated by github action, developer should edit as required before publishing...
|
||||
|
||||
- name: Download source deb
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
|
||||
merge-multiple: true
|
||||
path: ./output/debian-src
|
||||
|
||||
- name: Download `native-tft` pio deps
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
path: ./output/pio-deps-native-tft
|
||||
|
||||
- name: Zip Linux sources
|
||||
working-directory: output
|
||||
run: |
|
||||
zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src
|
||||
zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft
|
||||
|
||||
# For diagnostics
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -lR
|
||||
|
||||
- name: Add Linux sources to GtiHub Release
|
||||
# Only run when targeting master branch with workflow_dispatch
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
run: |
|
||||
gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
|
||||
gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
release-firmware:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch:
|
||||
- esp32
|
||||
- esp32s3
|
||||
- esp32c3
|
||||
- esp32c6
|
||||
- nrf52840
|
||||
- rp2040
|
||||
- rp2350
|
||||
- stm32
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
needs: [release-artifacts, version]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
path: ./output
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -lR
|
||||
|
||||
- name: Device scripts permissions
|
||||
run: |
|
||||
chmod +x ./output/device-install.sh || true
|
||||
chmod +x ./output/device-update.sh || true
|
||||
|
||||
- name: Zip firmware
|
||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
path: ./elfs
|
||||
|
||||
- name: Zip debug elfs
|
||||
run: zip -j -9 -r ./debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./elfs
|
||||
|
||||
# For diagnostics
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -lR
|
||||
|
||||
- name: Add bins and debug elfs to GitHub Release
|
||||
# Only run when targeting master branch with workflow_dispatch
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
run: |
|
||||
gh release upload v${{ needs.version.outputs.long }} ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
||||
gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
publish-firmware:
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
needs: [release-firmware, version]
|
||||
env:
|
||||
targets: |-
|
||||
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
path: ./publish
|
||||
|
||||
- name: Publish firmware to meshtastic.github.io
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
env:
|
||||
# On event/* branches, use the event name as the destination prefix
|
||||
DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
|
||||
with:
|
||||
deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
|
||||
external_repository: meshtastic/meshtastic.github.io
|
||||
publish_branch: master
|
||||
publish_dir: ./publish
|
||||
destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
|
||||
keep_files: true
|
||||
user_name: github-actions[bot]
|
||||
user_email: github-actions[bot]@users.noreply.github.com
|
||||
commit_message: ${{ needs.version.outputs.long }}
|
||||
enable_jekyll: true
|
||||
@@ -18,8 +18,7 @@ on:
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-debian-src:
|
||||
@@ -58,7 +57,7 @@ jobs:
|
||||
id: version
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||
merge-multiple: true
|
||||
|
||||
@@ -16,8 +16,7 @@ on:
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
pkg-pio-libdeps:
|
||||
@@ -27,8 +26,6 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
@@ -54,9 +51,19 @@ jobs:
|
||||
PLATFORMIO_LIBDEPS_DIR: pio/libdeps
|
||||
PLATFORMIO_PACKAGES_DIR: pio/packages
|
||||
PLATFORMIO_CORE_DIR: pio/core
|
||||
PLATFORMIO_SETTING_ENABLE_TELEMETRY: 0
|
||||
PLATFORMIO_SETTING_CHECK_PLATFORMIO_INTERVAL: 3650
|
||||
PLATFORMIO_SETTING_CHECK_PRUNE_SYSTEM_THRESHOLD: 10240
|
||||
|
||||
- name: Mangle platformio cache
|
||||
# Add "1" to epoch-timestamps of all downloads in the cache.
|
||||
# This is a hack to prevent internet access at build-time.
|
||||
run: |
|
||||
cp pio/core/.cache/downloads/usage.db pio/core/.cache/downloads/usage.db.bak
|
||||
jq -c 'with_entries(.value |= (. | tostring + "1" | tonumber))' pio/core/.cache/downloads/usage.db.bak > pio/core/.cache/downloads/usage.db
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: platformio-deps-${{ inputs.pio_env }}-${{ steps.version.outputs.long }}
|
||||
overwrite: true
|
||||
|
||||
@@ -16,8 +16,7 @@ on:
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-debian-src:
|
||||
@@ -36,8 +35,6 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
path: meshtasticd
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
@@ -46,7 +43,7 @@ jobs:
|
||||
sudo apt-get install -y dput
|
||||
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
uses: crazy-max/ghaction-import-gpg@v7
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }}
|
||||
id: gpg
|
||||
@@ -60,7 +57,7 @@ jobs:
|
||||
id: version
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||
merge-multiple: true
|
||||
@@ -70,5 +67,6 @@ jobs:
|
||||
|
||||
- name: Publish with dput
|
||||
if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
|
||||
timeout-minutes: 15 # dput is terrible, sometimes runs 'forever'
|
||||
run: |
|
||||
dput ${{ inputs.ppa_repo }} meshtasticd_${{ steps.version.outputs.deb }}~${{ inputs.series }}_source.changes
|
||||
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
- name: Download test artifacts
|
||||
if: needs.native-tests.result != 'skipped'
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: platformio-test-report-${{ steps.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
|
||||
@@ -23,8 +23,8 @@ jobs:
|
||||
series:
|
||||
- jammy # 22.04 LTS
|
||||
- noble # 24.04 LTS
|
||||
- plucky # 25.04
|
||||
- questing # 25.10
|
||||
- resolute # 26.04 LTS
|
||||
uses: ./.github/workflows/package_ppa.yml
|
||||
with:
|
||||
ppa_repo: |-
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
# step 3
|
||||
- name: save report as pipeline artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: report.sarif
|
||||
overwrite: true
|
||||
|
||||
@@ -16,8 +16,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup native build
|
||||
@@ -59,7 +57,7 @@ jobs:
|
||||
id: version
|
||||
|
||||
- name: Save coverage information
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
if: always() # run this step even if previous step failed
|
||||
with:
|
||||
name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.long }}
|
||||
@@ -72,8 +70,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup native build
|
||||
@@ -94,7 +90,7 @@ jobs:
|
||||
|
||||
- name: Save test results
|
||||
if: always() # run this step even if previous step failed
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: platformio-test-report-${{ steps.version.outputs.long }}
|
||||
overwrite: true
|
||||
@@ -108,7 +104,7 @@ jobs:
|
||||
sed -i -e "s#${PWD}#.#" coverage_tests.info # Make paths relative.
|
||||
|
||||
- name: Save coverage information
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
if: always() # run this step even if previous step failed
|
||||
with:
|
||||
name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.long }}
|
||||
@@ -128,29 +124,26 @@ jobs:
|
||||
if: always()
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Download test artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: platformio-test-report-${{ steps.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
|
||||
- name: Test Report
|
||||
uses: dorny/test-reporter@v2.5.0
|
||||
uses: dorny/test-reporter@v3.0.0
|
||||
with:
|
||||
name: PlatformIO Tests
|
||||
path: testreport.xml
|
||||
reporter: java-junit
|
||||
|
||||
- name: Download coverage artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}
|
||||
path: code-coverage-report
|
||||
@@ -163,7 +156,7 @@ jobs:
|
||||
genhtml --quiet --legend --prefix "${PWD}" code-coverage-report/coverage_src.info --output-directory code-coverage-report
|
||||
|
||||
- name: Save Code Coverage Report
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: code-coverage-report-${{ steps.version.outputs.long }}
|
||||
path: code-coverage-report
|
||||
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
node-version: 24
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@v5
|
||||
with:
|
||||
version: latest
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ permissions: read-all
|
||||
jobs:
|
||||
update-protobufs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
permissions: # Needed for peter-evans/create-pull-request.
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
|
||||
+11
-11
@@ -4,31 +4,31 @@ cli:
|
||||
plugins:
|
||||
sources:
|
||||
- id: trunk
|
||||
ref: v1.7.4
|
||||
ref: v1.7.6
|
||||
uri: https://github.com/trunk-io/plugins
|
||||
lint:
|
||||
enabled:
|
||||
- checkov@3.2.506
|
||||
- renovate@43.43.2
|
||||
- checkov@3.2.513
|
||||
- renovate@43.100.0
|
||||
- prettier@3.8.1
|
||||
- trufflehog@3.93.5
|
||||
- trufflehog@3.94.1
|
||||
- yamllint@1.38.0
|
||||
- bandit@1.9.4
|
||||
- trivy@0.69.1
|
||||
- trivy@0.69.3
|
||||
- taplo@0.10.0
|
||||
- ruff@0.15.4
|
||||
- isort@8.0.0
|
||||
- markdownlint@0.47.0
|
||||
- ruff@0.15.8
|
||||
- isort@8.0.1
|
||||
- markdownlint@0.48.0
|
||||
- oxipng@10.1.0
|
||||
- svgo@4.0.0
|
||||
- svgo@4.0.1
|
||||
- actionlint@1.7.11
|
||||
- flake8@7.3.0
|
||||
- hadolint@2.14.0
|
||||
- shfmt@3.6.0
|
||||
- shellcheck@0.11.0
|
||||
- black@26.1.0
|
||||
- black@26.3.1
|
||||
- git-diff-check
|
||||
- gitleaks@8.30.0
|
||||
- gitleaks@8.30.1
|
||||
- clang-format@16.0.3
|
||||
ignore:
|
||||
- linters: [ALL]
|
||||
|
||||
@@ -187,6 +187,7 @@ Logging:
|
||||
LogLevel: info # debug, info, warn, error
|
||||
# TraceFile: /var/log/meshtasticd.json
|
||||
# JSONFile: /packets.json # File location for JSON output of decoded packets
|
||||
# JSONFileRotate: 60 # Rotate JSON file every N minutes, or 0 for no rotation
|
||||
# JSONFilter: position # filter for packets to save to JSON file
|
||||
# AsciiLogs: true # default if not specified is !isatty() on stdout
|
||||
|
||||
@@ -214,3 +215,4 @@ General:
|
||||
AvailableDirectory: /etc/meshtasticd/available.d/
|
||||
# MACAddress: AA:BB:CC:DD:EE:FF
|
||||
# MACAddressSource: eth0
|
||||
# APIPort: 4403
|
||||
|
||||
@@ -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,11 +1,20 @@
|
||||
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
|
||||
Busy: 24 # IO5
|
||||
# Ant_sw: 13 # IO3
|
||||
Enable_Pins:
|
||||
- 12
|
||||
- 13
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
spidev: spidev0.0
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
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
|
||||
# Ant_sw: 23 # IO3
|
||||
Enable_Pins:
|
||||
- 26
|
||||
- 23
|
||||
spidev: spidev0.1
|
||||
# CS: 7
|
||||
@@ -0,0 +1,22 @@
|
||||
Meta:
|
||||
name: RAK6421 + RAK13302 Slot 1
|
||||
support: official
|
||||
compatible:
|
||||
- raspberry-pi
|
||||
|
||||
Lora:
|
||||
|
||||
### RAK13302 in Slot 1
|
||||
Module: sx1262
|
||||
IRQ: 22 #IO6
|
||||
Reset: 16 # IO4
|
||||
Busy: 24 # IO5
|
||||
# Ant_sw: 13 # IO3
|
||||
Enable_Pins:
|
||||
- 12
|
||||
- 13
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
spidev: spidev0.0
|
||||
# CS: 8
|
||||
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,18 @@
|
||||
Meta:
|
||||
name: RAK6421 + RAK13302 Slot 2
|
||||
support: official
|
||||
compatible:
|
||||
- raspberry-pi
|
||||
|
||||
Lora:
|
||||
### RAK13302 in Slot 2 pins
|
||||
IRQ: 18 #IO6
|
||||
Reset: 24 # IO4
|
||||
Busy: 19 # IO5
|
||||
# Ant_sw: 23 # IO3
|
||||
Enable_Pins:
|
||||
- 26
|
||||
- 23
|
||||
spidev: spidev0.1
|
||||
# CS: 7
|
||||
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]
|
||||
+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,13 +1,21 @@
|
||||
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
|
||||
Busy: 24 # IO5
|
||||
# Ant_sw: 13 # IO3
|
||||
Enable_Pins:
|
||||
- 12
|
||||
- 13
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
spidev: spidev0.0
|
||||
GPIO_DETECT_PA: 13
|
||||
TX_GAIN_LORA: [8, 8, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7]
|
||||
# GPIO_DETECT_PA: 13
|
||||
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]
|
||||
@@ -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
|
||||
|
||||
@@ -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.21" date="2026-03-11">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.21</url>
|
||||
</release>
|
||||
<release version="2.7.20" date="2026-02-11">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.20</url>
|
||||
</release>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x239A", "0x4405"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"],
|
||||
["0x2886", "0x1667"]
|
||||
],
|
||||
"usb_product": "HT-n5262G",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "heltec_mesh_node_t096",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"onboard_tools": ["jlink"],
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Heltec nrf (Adafruit BSP)",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://heltec.org/",
|
||||
"vendor": "Heltec"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
+1
-1
@@ -12,6 +12,6 @@ Ex:
|
||||
- `logo_320x480.png`
|
||||
- `logo_320x240.png`
|
||||
|
||||
This file is copied to `data/boot/logo.png` before filesytem image compilation.
|
||||
This file is copied to `data/boot/logo.png` before filesystem image compilation.
|
||||
|
||||
For additional examples see the [`event/defcon33` branch](https://github.com/meshtastic/firmware/tree/event/defcon33).
|
||||
|
||||
Vendored
+6
@@ -1,3 +1,9 @@
|
||||
meshtasticd (2.7.21.0) unstable; urgency=medium
|
||||
|
||||
* Version 2.7.21
|
||||
|
||||
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Wed, 11 Mar 2026 11:45:36 +0000
|
||||
|
||||
meshtasticd (2.7.20.0) unstable; urgency=medium
|
||||
|
||||
* Version 2.7.20
|
||||
|
||||
Vendored
+14
-2
@@ -3,10 +3,17 @@ export DEBEMAIL="jbennett@incomsystems.biz"
|
||||
export PLATFORMIO_LIBDEPS_DIR=pio/libdeps
|
||||
export PLATFORMIO_PACKAGES_DIR=pio/packages
|
||||
export PLATFORMIO_CORE_DIR=pio/core
|
||||
export PLATFORMIO_SETTING_ENABLE_TELEMETRY=0
|
||||
export PLATFORMIO_SETTING_CHECK_PLATFORMIO_INTERVAL=3650
|
||||
export PLATFORMIO_SETTING_CHECK_PRUNE_SYSTEM_THRESHOLD=10240
|
||||
|
||||
# Download libraries to `pio`
|
||||
platformio pkg install -e native-tft
|
||||
platformio pkg install -e native-tft -t platformio/tool-scons@4.40502.0
|
||||
# Mangle PlatformIO cache to prevent internet access at build-time
|
||||
# Simply adds 1 to all expiry (epoch) timestamps, adding ~500 years to expiry date
|
||||
cp pio/core/.cache/downloads/usage.db pio/core/.cache/downloads/usage.db.bak
|
||||
jq -c 'with_entries(.value |= (. | tostring + "1" | tonumber))' pio/core/.cache/downloads/usage.db.bak >pio/core/.cache/downloads/usage.db
|
||||
# Compress `pio` directory to prevent dh_clean from sanitizing it
|
||||
tar -cf pio.tar pio/
|
||||
rm -rf pio
|
||||
@@ -20,5 +27,10 @@ rm -rf debian/changelog
|
||||
dch --create --distribution "$SERIES" --package "$package" --newversion "$PKG_VERSION~$SERIES" \
|
||||
"GitHub Actions Automatic packaging for $PKG_VERSION~$SERIES"
|
||||
|
||||
# Build the source deb
|
||||
debuild -S -nc -k"$GPG_KEY_ID"
|
||||
if [[ -n $GPG_KEY_ID ]]; then
|
||||
# Build and sign the source deb
|
||||
debuild -S -nc -k"$GPG_KEY_ID"
|
||||
else
|
||||
# Build the source deb without signing (forks)
|
||||
debuild -S -nc
|
||||
fi
|
||||
|
||||
Vendored
+4
-1
@@ -9,7 +9,10 @@
|
||||
PIO_ENV:=\
|
||||
PLATFORMIO_CORE_DIR=pio/core \
|
||||
PLATFORMIO_LIBDEPS_DIR=pio/libdeps \
|
||||
PLATFORMIO_PACKAGES_DIR=pio/packages
|
||||
PLATFORMIO_PACKAGES_DIR=pio/packages \
|
||||
PLATFORMIO_SETTING_ENABLE_TELEMETRY=0 \
|
||||
PLATFORMIO_SETTING_CHECK_PLATFORMIO_INTERVAL=3650 \
|
||||
PLATFORMIO_SETTING_CHECK_PRUNE_SYSTEM_THRESHOLD=10240
|
||||
|
||||
# Raspbian armhf builds should be compatible with armv6-hardfloat
|
||||
# https://www.valvers.com/open-software/raspberry-pi/bare-metal-programming-in-c-part-1/#rpi1-compiler-flags
|
||||
|
||||
+21
-17
@@ -77,10 +77,10 @@ lib_deps =
|
||||
https://github.com/meshtastic/TinyGPSPlus/archive/71a82db35f3b973440044c476d4bcdc673b104f4.zip
|
||||
# renovate: datasource=git-refs depName=meshtastic-ArduinoThread packageName=https://github.com/meshtastic/ArduinoThread gitBranch=master
|
||||
https://github.com/meshtastic/ArduinoThread/archive/b841b0415721f1341ea41cccfb4adccfaf951567.zip
|
||||
# renovate: datasource=custom.pio depName=Nanopb packageName=nanopb/library/Nanopb
|
||||
nanopb/Nanopb@0.4.91
|
||||
# renovate: datasource=custom.pio depName=ErriezCRC32 packageName=erriez/library/ErriezCRC32
|
||||
erriez/ErriezCRC32@1.0.1
|
||||
# renovate: datasource=github-tags depName=Nanopb packageName=nanopb/nanopb
|
||||
https://github.com/nanopb/nanopb/archive/refs/tags/nanopb-0.4.9.1.zip
|
||||
# renovate: datasource=github-tags depName=ErriezCRC32 packageName=Erriez/ErriezCRC32
|
||||
https://github.com/Erriez/ErriezCRC32/archive/refs/tags/1.0.1.zip
|
||||
|
||||
; Used for the code analysis in PIO Home / Inspect
|
||||
check_tool = cppcheck
|
||||
@@ -95,34 +95,38 @@ 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
|
||||
build_flags = ${env.build_flags} -Os
|
||||
-std=gnu++17
|
||||
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/>
|
||||
|
||||
; 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/6c75195e9987b7a49563232234f2f868dd343cae.zip
|
||||
https://github.com/meshtastic/device-ui/archive/7b1485b86c8d55a40e5226749097767e8b87f396.zip
|
||||
|
||||
; Common libs for environmental measurements in telemetry module
|
||||
[environmental_base]
|
||||
@@ -229,5 +233,5 @@ lib_deps =
|
||||
[environmental_extra_no_bsec]
|
||||
lib_deps =
|
||||
${environmental_extra_common.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=adafruit/Adafruit BME680 Library packageName=adafruit/library/Adafruit BME680
|
||||
adafruit/Adafruit BME680 Library@^2.0.5
|
||||
# renovate: datasource=custom.pio depName=Adafruit_BME680 packageName=adafruit/library/Adafruit BME680 Library
|
||||
adafruit/Adafruit BME680 Library@2.0.6
|
||||
|
||||
+1
-1
Submodule protobufs updated: f7f7c8d2e4...cb1f89372a
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaC
|
||||
bool usePreset)
|
||||
{
|
||||
|
||||
// If use_preset is false, always return "Custom"
|
||||
// If use_preset is false, always return "Custom" — callers such as RadioInterface and Channels
|
||||
// rely on this being a stable literal for channel-name hashing and default-channel detection.
|
||||
if (!usePreset) {
|
||||
return "Custom";
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+8
-6
@@ -38,7 +38,10 @@ static bool isPowered()
|
||||
return true;
|
||||
#endif
|
||||
|
||||
bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0);
|
||||
bool isRouter = ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
|
||||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)
|
||||
? 1
|
||||
: 0);
|
||||
|
||||
// If we are not a router and we already have AC power go to POWER state after init, otherwise go to ON
|
||||
// We assume routers might be powered all the time, but from a low current (solar) source
|
||||
@@ -182,11 +185,7 @@ static void darkEnter()
|
||||
static void serialEnter()
|
||||
{
|
||||
LOG_POWERFSM("State: serialEnter");
|
||||
#ifndef ARCH_NRF52
|
||||
// nRF52 runs BLE on SoftDevice independently of USB serial — no need to disable it.
|
||||
// (Same rationale as nbEnter() which already guards this with #ifdef ARCH_ESP32)
|
||||
setBluetoothEnable(false);
|
||||
#endif
|
||||
if (screen) {
|
||||
screen->setOn(true);
|
||||
}
|
||||
@@ -266,7 +265,10 @@ Fsm powerFSM(&stateBOOT);
|
||||
|
||||
void PowerFSM_setup()
|
||||
{
|
||||
bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0);
|
||||
bool isRouter = ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
|
||||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)
|
||||
? 1
|
||||
: 0);
|
||||
bool hasPower = isPowered();
|
||||
|
||||
LOG_INFO("PowerFSM init, USB power=%d", hasPower ? 1 : 0);
|
||||
|
||||
@@ -345,7 +345,7 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...)
|
||||
return;
|
||||
}
|
||||
|
||||
void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16_t len)
|
||||
void RedirectablePrint::hexDump(const char *logLevel, const unsigned char *buf, uint16_t len)
|
||||
{
|
||||
const char alphabet[17] = "0123456789abcdef";
|
||||
log(logLevel, " +------------------------------------------------+ +----------------+");
|
||||
|
||||
@@ -44,7 +44,7 @@ class RedirectablePrint : public Print
|
||||
/** like printf but va_list based */
|
||||
size_t vprintf(const char *logLevel, const char *format, va_list arg);
|
||||
|
||||
void hexDump(const char *logLevel, unsigned char *buf, uint16_t len);
|
||||
void hexDump(const char *logLevel, const unsigned char *buf, uint16_t len);
|
||||
|
||||
std::string mt_sprintf(const std::string fmt_str, ...);
|
||||
|
||||
|
||||
+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
|
||||
|
||||
+29
-64
@@ -78,6 +78,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// Configuration
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Pre-hop drop handling (compile-time flag).
|
||||
#ifndef MESHTASTIC_PREHOP_DROP
|
||||
#define MESHTASTIC_PREHOP_DROP 0
|
||||
#endif
|
||||
|
||||
/// Convert a preprocessor name into a quoted string
|
||||
#define xstr(s) ystr(s)
|
||||
#define ystr(s) #s
|
||||
@@ -149,6 +154,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
|
||||
#endif
|
||||
|
||||
#ifdef USE_KCT8103L_PA
|
||||
// Power Amps are often non-linear, so we can use an array of values for the power curve
|
||||
#if defined(HELTEC_WIRELESS_TRACKER_V2)
|
||||
#define NUM_PA_POINTS 22
|
||||
#define TX_GAIN_LORA 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 12, 12, 11, 10, 9, 8, 7
|
||||
#elif defined(HELTEC_MESH_NODE_T096)
|
||||
#define NUM_PA_POINTS 22
|
||||
#define TX_GAIN_LORA 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 12, 11, 10, 9, 8, 7
|
||||
#else
|
||||
// If a board enables USE_KCT8103L_PA but does not match a known variant and has
|
||||
// not already provided a PA curve, fail at compile time to avoid unsafe defaults.
|
||||
#if !defined(NUM_PA_POINTS) || !defined(TX_GAIN_LORA)
|
||||
#error "USE_KCT8103L_PA is defined, but no PA gain curve (NUM_PA_POINTS / TX_GAIN_LORA) is configured for this board."
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RAK13302
|
||||
#define NUM_PA_POINTS 22
|
||||
#define TX_GAIN_LORA 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8
|
||||
@@ -163,6 +185,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define TX_GAIN_LORA 0
|
||||
#endif
|
||||
|
||||
#ifndef HAS_LORA_FEM
|
||||
#define HAS_LORA_FEM 0
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Feature toggles
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -261,6 +287,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define BHI260AP_ADDR 0x28
|
||||
#define BMM150_ADDR 0x13
|
||||
#define DA217_ADDR 0x26
|
||||
#define BMI270_ADDR 0x68
|
||||
#define BMI270_ADDR_ALT 0x69
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// LED
|
||||
@@ -487,6 +515,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define MESHTASTIC_EXCLUDE_REMOTEHARDWARE 1
|
||||
#define MESHTASTIC_EXCLUDE_STOREFORWARD 1
|
||||
#define MESHTASTIC_EXCLUDE_TEXTMESSAGE 1
|
||||
#define MESHTASTIC_EXCLUDE_TRAFFIC_MANAGEMENT 1
|
||||
#define MESHTASTIC_EXCLUDE_ATAK 1
|
||||
#define MESHTASTIC_EXCLUDE_CANNEDMESSAGES 1
|
||||
#define MESHTASTIC_EXCLUDE_NEIGHBORINFO 1
|
||||
@@ -528,69 +557,5 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define HAS_SCREEN 0
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// MESHTASTIC_LOCKDOWN — combined hardened build flag
|
||||
//
|
||||
// All targets:
|
||||
// DEBUG_MUTE — suppress serial/USB log output
|
||||
// MESHTASTIC_EXCLUDE_* — strip non-essential modules
|
||||
// MESHTASTIC_PHONEAPI_ACCESS_CONTROL — redact keys from unauthenticated BLE/USB/TCP clients
|
||||
// HARDENED_DEFAULTS — set is_managed, RANDOM_PIN, TAK role at first boot
|
||||
//
|
||||
// nRF52 (CC310 hardware crypto):
|
||||
// MESHTASTIC_ENCRYPTED_STORAGE — AES-128-CTR + HMAC-SHA256 at-rest encryption
|
||||
// MESHTASTIC_ENABLE_APPROTECT — write UICR APPROTECT to block debug probe
|
||||
//
|
||||
// ESP32 (stub — mbedTLS crypto TODO):
|
||||
// MESHTASTIC_ENCRYPTED_STORAGE — same API, plaintext pass-through until ported
|
||||
// APPROTECT equivalent — burn JTAG_DISABLE + flash encryption via espsecure.py
|
||||
// -----------------------------------------------------------------------------
|
||||
#ifdef MESHTASTIC_LOCKDOWN
|
||||
|
||||
// Suppress all serial/USB-CDC log output — prevents info leakage via physical access
|
||||
#define DEBUG_MUTE
|
||||
|
||||
// Strip non-essential modules to reduce attack surface
|
||||
#define MESHTASTIC_EXCLUDE_DETECTIONSENSOR 1
|
||||
#define MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR 1
|
||||
#define MESHTASTIC_EXCLUDE_AIR_QUALITY 1
|
||||
#define MESHTASTIC_EXCLUDE_HEALTH_TELEMETRY 1
|
||||
#define MESHTASTIC_EXCLUDE_POWER_TELEMETRY 1
|
||||
#define MESHTASTIC_EXCLUDE_RANGETEST 1
|
||||
#define MESHTASTIC_EXCLUDE_REMOTEHARDWARE 1
|
||||
#define MESHTASTIC_EXCLUDE_STOREFORWARD 1
|
||||
#define MESHTASTIC_EXCLUDE_CANNEDMESSAGES 1
|
||||
#define MESHTASTIC_EXCLUDE_SERIAL 1
|
||||
#define MESHTASTIC_EXCLUDE_POWERSTRESS 1
|
||||
#define MESHTASTIC_EXCLUDE_DROPZONE 1
|
||||
#define MESHTASTIC_EXCLUDE_REPLYBOT 1
|
||||
#define MESHTASTIC_EXCLUDE_PAXCOUNTER 1
|
||||
#define MESHTASTIC_EXCLUDE_AUDIO 1
|
||||
#define MESHTASTIC_EXCLUDE_STATUS 1
|
||||
#define MESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE 1
|
||||
#define MESHTASTIC_EXCLUDE_POWERMON 1
|
||||
#define MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION 1
|
||||
|
||||
// Access control and hardened defaults are pure C++ — no platform-specific deps.
|
||||
#define MESHTASTIC_PHONEAPI_ACCESS_CONTROL 1
|
||||
#define HARDENED_DEFAULTS 1
|
||||
|
||||
// Platform-specific security features:
|
||||
#if defined(ARCH_NRF52)
|
||||
// Full encrypted storage via CC310 hardware AES-128-CTR + HMAC-SHA256.
|
||||
#define MESHTASTIC_ENCRYPTED_STORAGE 1
|
||||
// Hardware JTAG/SWD lockout: firmware writes NRF_UICR->APPROTECT = 0x00 once.
|
||||
#define MESHTASTIC_ENABLE_APPROTECT 1
|
||||
#elif defined(ARCH_ESP32)
|
||||
// Encrypted storage stub: plaintext pass-through, same API, crypto TODO (mbedTLS).
|
||||
// APPROTECT equivalent: burn JTAG_DISABLE eFuse and enable flash encryption
|
||||
// via espsecure.py at provisioning time — not firmware-controlled.
|
||||
#define MESHTASTIC_ENCRYPTED_STORAGE 1
|
||||
#else
|
||||
#warning "MESHTASTIC_LOCKDOWN: encrypted storage not available on this platform; access control and hardened defaults still active"
|
||||
#endif
|
||||
|
||||
#endif // MESHTASTIC_LOCKDOWN
|
||||
|
||||
#include "DebugConfiguration.h"
|
||||
#include "RF95Configuration.h"
|
||||
|
||||
@@ -37,8 +37,8 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
|
||||
{
|
||||
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948, QMA6100P, BMM150};
|
||||
return firstOfOrNONE(9, types);
|
||||
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948, QMA6100P, BMM150, BMI270};
|
||||
return firstOfOrNONE(10, types);
|
||||
}
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstAQI() const
|
||||
|
||||
@@ -89,10 +89,12 @@ class ScanI2C
|
||||
DA217,
|
||||
CHSC6X,
|
||||
CST226SE,
|
||||
BMI270,
|
||||
SEN5X,
|
||||
SFA30,
|
||||
CW2015,
|
||||
SCD30
|
||||
SCD30,
|
||||
ADS1115
|
||||
} DeviceType;
|
||||
|
||||
// typedef uint8_t DeviceAddress;
|
||||
|
||||
@@ -341,7 +341,9 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
type = DPS310;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
if (type == DPS310) {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); // GET_ID
|
||||
switch (registerValue) {
|
||||
@@ -652,9 +654,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
}
|
||||
break;
|
||||
|
||||
case ICM20948_ADDR: // same as BMX160_ADDR and SEN5X_ADDR
|
||||
case ICM20948_ADDR_ALT: // same as MPU6050_ADDR
|
||||
// ICM20948 Register check
|
||||
case ICM20948_ADDR: // same as BMX160_ADDR, BMI270_ADDR_ALT, and SEN5X_ADDR
|
||||
case ICM20948_ADDR_ALT: // same as MPU6050_ADDR, BMI270_ADDR
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1);
|
||||
#ifdef HAS_ICM20948
|
||||
type = ICM20948;
|
||||
@@ -665,6 +666,14 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
type = ICM20948;
|
||||
logFoundDevice("ICM20948", (uint8_t)addr.address);
|
||||
break;
|
||||
} else if (registerValue == 0x24) {
|
||||
type = BMI270;
|
||||
logFoundDevice("BMI270", (uint8_t)addr.address);
|
||||
break;
|
||||
} else if (registerValue == 0xD8) { // BMX160 chip ID at register 0x00
|
||||
type = BMX160;
|
||||
logFoundDevice("BMX160", (uint8_t)addr.address);
|
||||
break;
|
||||
} else {
|
||||
String prod = "";
|
||||
prod = readSEN5xProductName(i2cBus, addr.address);
|
||||
@@ -718,11 +727,18 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
if (len == 5 && memcmp(expectedInfo, info, len) == 0) {
|
||||
LOG_INFO("NXP SE050 crypto chip found");
|
||||
type = NXP_SE050;
|
||||
|
||||
} else {
|
||||
LOG_INFO("FT6336U touchscreen found");
|
||||
type = FT6336U;
|
||||
break;
|
||||
}
|
||||
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 2);
|
||||
if (registerValue == 0x8583 || registerValue == 0x8580) {
|
||||
type = ADS1115;
|
||||
logFoundDevice("ADS1115 ADC", (uint8_t)addr.address);
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_INFO("FT6336U touchscreen found");
|
||||
type = FT6336U;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
+14
-6
@@ -93,7 +93,7 @@ static const char *getGPSPowerStateString(GPSPowerState state)
|
||||
|
||||
#ifdef PIN_GPS_SWITCH
|
||||
// If we have a hardware switch, define a periodic watcher outside of the GPS runOnce thread, since this can be sleeping
|
||||
// idefinitely
|
||||
// indefinitely
|
||||
|
||||
int lastState = LOW;
|
||||
bool firstrun = true;
|
||||
@@ -103,6 +103,14 @@ static int32_t gpsSwitch()
|
||||
if (gps) {
|
||||
int currentState = digitalRead(PIN_GPS_SWITCH);
|
||||
|
||||
// Respect explicit NOT_PRESENT mode and do not let the hardware switch re-enable GPS.
|
||||
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
|
||||
gps->disable();
|
||||
lastState = currentState;
|
||||
firstrun = false;
|
||||
return 1000;
|
||||
}
|
||||
|
||||
// if the switch is set to zero, disable the GPS Thread
|
||||
if (firstrun)
|
||||
if (currentState == LOW)
|
||||
@@ -586,14 +594,14 @@ bool GPS::setup()
|
||||
_serial_gps->write("$PMTK301,2*2E\r\n");
|
||||
delay(250);
|
||||
} else if (gnssModel == GNSS_MODEL_ATGM336H) {
|
||||
// Set the intial configuration of the device - these _should_ work for most AT6558 devices
|
||||
// Set the initial configuration of the device - these _should_ work for most AT6558 devices
|
||||
msglen = makeCASPacket(0x06, 0x07, sizeof(_message_CAS_CFG_NAVX_CONF), _message_CAS_CFG_NAVX_CONF);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACKCas(0x06, 0x07, 250) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("ATGM336H: Could not set Config");
|
||||
}
|
||||
|
||||
// Set the update frequence to 1Hz
|
||||
// Set the update frequency to 1Hz
|
||||
msglen = makeCASPacket(0x06, 0x04, sizeof(_message_CAS_CFG_RATE_1HZ), _message_CAS_CFG_RATE_1HZ);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACKCas(0x06, 0x04, 250) != GNSS_RESPONSE_OK) {
|
||||
@@ -700,7 +708,7 @@ bool GPS::setup()
|
||||
} else { // 8,9
|
||||
LOG_INFO("GPS+SBAS+GLONASS+Galileo configured");
|
||||
}
|
||||
// Documentation say, we need wait atleast 0.5s after reconfiguration of GNSS module, before sending next
|
||||
// Documentation say, we need wait at least 0.5s after reconfiguration of GNSS module, before sending next
|
||||
// commands for the M8 it tends to be more... 1 sec should be enough ;>)
|
||||
delay(1000);
|
||||
}
|
||||
@@ -733,7 +741,7 @@ bool GPS::setup()
|
||||
SEND_UBX_PACKET(0x06, 0x86, _message_PMS, "enable powersave for GPS", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "enable powersave details for GPS", 500);
|
||||
|
||||
// For M8 we want to enable NMEA vserion 4.10 so we can see the additional sats.
|
||||
// For M8 we want to enable NMEA version 4.10 so we can see the additional sats.
|
||||
if (gnssModel == GNSS_MODEL_UBLOX8) {
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x17, _message_NMEA, "enable NMEA 4.10", 500);
|
||||
@@ -1211,7 +1219,7 @@ int32_t GPS::runOnce()
|
||||
return disable(); // This should trigger when we have a fixed position, and get that first position
|
||||
|
||||
// 9600bps is approx 1 byte per msec, so considering our buffer size we never need to wake more often than 200ms
|
||||
// if not awake we can run super infrquently (once every 5 secs?) to see if we need to wake.
|
||||
// if not awake we can run super infrequently (once every 5 secs?) to see if we need to wake.
|
||||
return (powerState == GPS_ACTIVE) ? GPS_THREAD_INTERVAL : 5000;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ GeoCoord::GeoCoord(int32_t lat, int32_t lon, int32_t alt) : _latitude(lat), _lon
|
||||
|
||||
GeoCoord::GeoCoord(float lat, float lon, int32_t alt) : _altitude(alt)
|
||||
{
|
||||
// Change decimial representation to int32_t. I.e., 12.345 becomes 123450000
|
||||
// Change decimal representation to int32_t. I.e., 12.345 becomes 123450000
|
||||
_latitude = int32_t(lat * 1e+7);
|
||||
_longitude = int32_t(lon * 1e+7);
|
||||
GeoCoord::setCoords();
|
||||
@@ -20,7 +20,7 @@ GeoCoord::GeoCoord(float lat, float lon, int32_t alt) : _altitude(alt)
|
||||
|
||||
GeoCoord::GeoCoord(double lat, double lon, int32_t alt) : _altitude(alt)
|
||||
{
|
||||
// Change decimial representation to int32_t. I.e., 12.345 becomes 123450000
|
||||
// Change decimal representation to int32_t. I.e., 12.345 becomes 123450000
|
||||
_latitude = int32_t(lat * 1e+7);
|
||||
_longitude = int32_t(lon * 1e+7);
|
||||
GeoCoord::setCoords();
|
||||
@@ -467,10 +467,10 @@ int32_t GeoCoord::bearingTo(const GeoCoord &pointB)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new point bassed on the passed in poin
|
||||
* Create a new point based on the passed-in point
|
||||
* Ported from http://www.edwilliams.org/avform147.htm#LL
|
||||
* @param bearing
|
||||
* The bearing in raidans
|
||||
* The bearing in radians
|
||||
* @param range_meters
|
||||
* range in meters
|
||||
* @return GeoCoord object of point at bearing and range from initial point
|
||||
@@ -593,4 +593,4 @@ double GeoCoord::toRadians(double deg)
|
||||
double GeoCoord::toDegrees(double r)
|
||||
{
|
||||
return r * 180 / PI;
|
||||
}
|
||||
}
|
||||
|
||||
+30
-2
@@ -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) {
|
||||
@@ -223,7 +239,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
|
||||
// This delta value works on all platforms
|
||||
timeStartMsec = now;
|
||||
zeroOffsetSecs = tv->tv_sec;
|
||||
// If this platform has a setable RTC, set it
|
||||
// If this platform has a settable RTC, set it
|
||||
#ifdef RV3028_RTC
|
||||
if (rtc_found.address == RV3028_RTC) {
|
||||
Melopero_RV3028 rtc;
|
||||
@@ -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,12 +414,23 @@ 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
|
||||
time_t result = 0;
|
||||
|
||||
// First, get us to the start of tm->year, by calcuating the number of days since the Unix epoch.
|
||||
// First, get us to the start of tm->year, by calculating the number of days since the Unix epoch.
|
||||
int year = 1900 + tm->tm_year; // tm_year is years since 1900
|
||||
int year_minus_one = year - 1;
|
||||
int days_before_this_year = 0;
|
||||
|
||||
@@ -54,6 +54,10 @@ uint32_t getValidTime(RTCQuality minQuality, bool local = false);
|
||||
|
||||
RTCSetResult readFromRTC();
|
||||
|
||||
#ifdef PIO_UNIT_TESTING
|
||||
void setBootRelativeTimeForUnitTest(uint32_t secondsSinceBoot);
|
||||
#endif
|
||||
|
||||
time_t gm_mktime(const struct tm *tm);
|
||||
|
||||
#define SEC_PER_DAY 86400
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ static const uint8_t _message_CAS_CFG_RATE_1HZ[] = {
|
||||
|
||||
// CFG-NAVX (0x06, 0x07)
|
||||
// Initial ATGM33H-5N configuration, Updates for Dynamic Mode, Fix Mode, and SV system
|
||||
// Qwirk: The ATGM33H-5N-31 should only support GPS+BDS, however it will happily enable
|
||||
// Quirk: The ATGM33H-5N-31 should only support GPS+BDS, however it will happily enable
|
||||
// and use GPS+BDS+GLONASS iff the correct CFG_NAVX command is used.
|
||||
static const uint8_t _message_CAS_CFG_NAVX_CONF[] = {
|
||||
0x03, 0x01, 0x00, 0x00, // Update Mask: Dynamic Mode, Fix Mode, Nav Settings
|
||||
|
||||
+6
-6
@@ -57,7 +57,7 @@ static const uint8_t _message_CFG_PM2[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x00 // 0x64, 0x40, 0x01, 0x00 // reserved 11
|
||||
};
|
||||
|
||||
// Constallation setup, none required for Neo-6
|
||||
// Constellation setup, none required for Neo-6
|
||||
|
||||
// For Neo-7 GPS & SBAS
|
||||
static const uint8_t _message_GNSS_7[] = {
|
||||
@@ -157,7 +157,7 @@ static const uint8_t _message_NAVX5[] = {
|
||||
0x00, 0x00, 0x00, 0x00, // Reserved 9
|
||||
0x00, // Reserved 10
|
||||
0x00, // Reserved 11
|
||||
0x00, // usePPP (Precice Point Positioning) (0 = false, 1 = true)
|
||||
0x00, // usePPP (Precise Point Positioning) (0 = false, 1 = true)
|
||||
0x01, // useAOP (AssistNow Autonomous configuration) = 1 (enabled)
|
||||
0x00, // Reserved 12
|
||||
0x00, // Reserved 13
|
||||
@@ -185,7 +185,7 @@ static const uint8_t _message_NAVX5_8[] = {
|
||||
0x00, // Reserved 4
|
||||
0x00, 0x00, // Reserved 5
|
||||
0x00, 0x00, // Reserved 6
|
||||
0x00, // usePPP (Precice Point Positioning) (0 = false, 1 = true)
|
||||
0x00, // usePPP (Precise Point Positioning) (0 = false, 1 = true)
|
||||
0x01, // aopCfg (AssistNow Autonomous configuration) = 1 (enabled)
|
||||
0x00, 0x00, // Reserved 7
|
||||
0x00, 0x00, // aopOrbMaxErr = 0 to reset to firmware default
|
||||
@@ -314,7 +314,7 @@ static const uint8_t _message_DISABLE_TXT_INFO[] = {
|
||||
// This command applies to M8 products
|
||||
static const uint8_t _message_PMS[] = {
|
||||
0x00, // Version (0)
|
||||
0x03, // Power setup value 3 = Agresssive 1Hz
|
||||
0x03, // Power setup value 3 = Agressive 1Hz
|
||||
0x00, 0x00, // period: not applicable, set to 0
|
||||
0x00, 0x00, // onTime: not applicable, set to 0
|
||||
0x00, 0x00 // reserved, generated by u-center
|
||||
@@ -337,7 +337,7 @@ static const uint8_t _message_SAVE_10[] = {
|
||||
// As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR.
|
||||
// BBR will survive a restart, and power off for a while, but modules with small backup
|
||||
// batteries or super caps will not retain the config for a long power off time.
|
||||
// for all configurations using sleep / low power modes, V_BCKP needs to be hooked to permanent power for fast aquisition after
|
||||
// for all configurations using sleep / low power modes, V_BCKP needs to be hooked to permanent power for fast acquisition after
|
||||
// sleep
|
||||
|
||||
// VALSET Commands for M10
|
||||
@@ -462,7 +462,7 @@ Default GNSS configuration is: GPS, Galileo, BDS B1l, with QZSS and SBAS enabled
|
||||
The PMREQ puts the receiver to sleep and wakeup re-acquires really fast and seems to not need
|
||||
the PM config. Lets try without it.
|
||||
PMREQ sort of works with SBAS, but the awake time is too short to re-acquire any SBAS sats.
|
||||
The defination of "Got Fix" doesn't seem to include SBAS. Much more too this...
|
||||
The definition of "Got Fix" doesn't seem to include SBAS. Much more too this...
|
||||
Even if it was, it can take minutes (up to 12.5),
|
||||
even under good sat visibility conditions to re-acquire the SBAS data.
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -101,7 +101,7 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
|
||||
return true;
|
||||
}
|
||||
|
||||
// End the update process - virtual method, overriden in derived class
|
||||
// End the update process - virtual method, overridden in derived class
|
||||
void EInkDisplay::endUpdate()
|
||||
{
|
||||
// Power off display hardware, then deep-sleep (Except Wireless Paper V1.1, no deep-sleep)
|
||||
@@ -143,6 +143,10 @@ bool EInkDisplay::connect()
|
||||
#ifdef ELECROW_ThinkNode_M1
|
||||
// ThinkNode M1 has a hardware dimmable backlight. Start enabled
|
||||
digitalWrite(PIN_EINK_EN, HIGH);
|
||||
#elif defined(MINI_EPAPER_S3)
|
||||
// T-Mini Epaper S3 requires panel power rail enabled before SPI transfer.
|
||||
digitalWrite(PIN_EINK_EN, HIGH);
|
||||
delay(10);
|
||||
#else
|
||||
digitalWrite(PIN_EINK_EN, LOW);
|
||||
#endif
|
||||
@@ -202,7 +206,8 @@ bool EInkDisplay::connect()
|
||||
}
|
||||
|
||||
#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || \
|
||||
defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER)
|
||||
defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER) || \
|
||||
defined(MINI_EPAPER_S3)
|
||||
{
|
||||
// Start HSPI
|
||||
hspi = new SPIClass(HSPI);
|
||||
@@ -216,9 +221,13 @@ bool EInkDisplay::connect()
|
||||
|
||||
// Init GxEPD2
|
||||
adafruitDisplay->init();
|
||||
#if defined(MINI_EPAPER_S3)
|
||||
adafruitDisplay->setRotation(3);
|
||||
#else
|
||||
adafruitDisplay->setRotation(3);
|
||||
#if defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(CROWPANEL_ESP32S3_4_EPAPER)
|
||||
adafruitDisplay->setRotation(0);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#elif defined(PCA10059) || defined(ME25LS01)
|
||||
@@ -259,17 +268,6 @@ bool EInkDisplay::connect()
|
||||
adafruitDisplay->setRotation(3);
|
||||
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
||||
}
|
||||
#elif defined(MINI_EPAPER_S3)
|
||||
spi1 = new SPIClass(HSPI);
|
||||
spi1->begin(PIN_SPI1_SCK, PIN_SPI1_MISO, PIN_SPI1_MOSI, PIN_EINK_CS);
|
||||
|
||||
// Create GxEPD2 objects
|
||||
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *spi1);
|
||||
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
|
||||
// Init GxEPD2
|
||||
adafruitDisplay->init();
|
||||
adafruitDisplay->setRotation(1);
|
||||
#elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213)
|
||||
|
||||
// Detect display model, before starting SPI
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_EINK
|
||||
#if defined(USE_EINK) && !defined(USE_EINK_PARALLELDISPLAY)
|
||||
|
||||
#include "GxEPD2_BW.h"
|
||||
#include <OLEDDisplay.h>
|
||||
@@ -89,12 +89,12 @@ class EInkDisplay : public OLEDDisplay
|
||||
// If display uses HSPI
|
||||
#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \
|
||||
defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) || \
|
||||
defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER) || defined(ELECROW_ThinkNode_M5)
|
||||
defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER) || defined(ELECROW_ThinkNode_M5) || \
|
||||
defined(MINI_EPAPER_S3)
|
||||
SPIClass *hspi = NULL;
|
||||
#endif
|
||||
|
||||
#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK) || \
|
||||
defined(MINI_EPAPER_S3)
|
||||
#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK)
|
||||
SPIClass *spi1 = NULL;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ void EInkDynamicDisplay::adjustRefreshCounters()
|
||||
// Trigger the display update by calling base class
|
||||
bool EInkDynamicDisplay::update()
|
||||
{
|
||||
// Detemine the refresh mode to use, and start the update
|
||||
// Determine the refresh mode to use, and start the update
|
||||
bool refreshApproved = determineMode();
|
||||
if (refreshApproved) {
|
||||
EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system
|
||||
@@ -317,7 +317,7 @@ void EInkDynamicDisplay::checkFrameMatchesPrevious()
|
||||
LOG_DEBUG("refresh=SKIPPED, reason=FRAME_MATCHED_PREVIOUS, frameFlags=0x%x", frameFlags);
|
||||
}
|
||||
|
||||
// Have too many fast-refreshes occured consecutively, since last full refresh?
|
||||
// Have too many fast-refreshes occurred consecutively, since last full refresh?
|
||||
void EInkDynamicDisplay::checkConsecutiveFastRefreshes()
|
||||
{
|
||||
// If a decision was already reached, don't run the check
|
||||
@@ -561,4 +561,4 @@ void EInkDynamicDisplay::awaitRefresh()
|
||||
}
|
||||
#endif // HAS_EINK_ASYNCFULL
|
||||
|
||||
#endif // USE_EINK_DYNAMICDISPLAY
|
||||
#endif // USE_EINK_DYNAMICDISPLAY
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,434 @@
|
||||
#include "configuration.h"
|
||||
#if HAS_SCREEN
|
||||
|
||||
#include "graphics/EmoteRenderer.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
namespace EmoteRenderer
|
||||
{
|
||||
|
||||
static inline int getStringWidth(OLEDDisplay *display, const char *text, size_t len)
|
||||
{
|
||||
#if defined(OLED_UA) || defined(OLED_RU)
|
||||
return display->getStringWidth(text, len, true);
|
||||
#else
|
||||
(void)len;
|
||||
return display->getStringWidth(text);
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t utf8CharLen(uint8_t c)
|
||||
{
|
||||
if ((c & 0xE0) == 0xC0)
|
||||
return 2;
|
||||
if ((c & 0xF0) == 0xE0)
|
||||
return 3;
|
||||
if ((c & 0xF8) == 0xF0)
|
||||
return 4;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline bool isPossibleEmoteLead(uint8_t c)
|
||||
{
|
||||
// All supported emoji labels in emotes.cpp are currently in these UTF-8 lead ranges.
|
||||
return c == 0xE2 || c == 0xF0;
|
||||
}
|
||||
|
||||
static inline int getUtf8ChunkWidth(OLEDDisplay *display, const char *text, size_t len)
|
||||
{
|
||||
char chunk[5] = {0, 0, 0, 0, 0};
|
||||
if (len > 4)
|
||||
len = 4;
|
||||
memcpy(chunk, text, len);
|
||||
return getStringWidth(display, chunk, len);
|
||||
}
|
||||
|
||||
static inline bool isFE0FAt(const char *s, size_t pos, size_t len)
|
||||
{
|
||||
return pos + 2 < len && static_cast<uint8_t>(s[pos]) == 0xEF && static_cast<uint8_t>(s[pos + 1]) == 0xB8 &&
|
||||
static_cast<uint8_t>(s[pos + 2]) == 0x8F;
|
||||
}
|
||||
|
||||
static inline bool isSkinToneAt(const char *s, size_t pos, size_t len)
|
||||
{
|
||||
return pos + 3 < len && static_cast<uint8_t>(s[pos]) == 0xF0 && static_cast<uint8_t>(s[pos + 1]) == 0x9F &&
|
||||
static_cast<uint8_t>(s[pos + 2]) == 0x8F &&
|
||||
(static_cast<uint8_t>(s[pos + 3]) >= 0xBB && static_cast<uint8_t>(s[pos + 3]) <= 0xBF);
|
||||
}
|
||||
|
||||
static inline size_t ignorableModifierLenAt(const char *s, size_t pos, size_t len)
|
||||
{
|
||||
// Skip modifiers that do not change which bitmap we render.
|
||||
if (isFE0FAt(s, pos, len))
|
||||
return 3;
|
||||
if (isSkinToneAt(s, pos, len))
|
||||
return 4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const Emote *findEmoteByLabel(const char *label, const Emote *emoteSet, int emoteCount)
|
||||
{
|
||||
if (!label || !*label)
|
||||
return nullptr;
|
||||
|
||||
for (int i = 0; i < emoteCount; ++i) {
|
||||
if (emoteSet[i].label && strcmp(label, emoteSet[i].label) == 0)
|
||||
return &emoteSet[i];
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool matchAtIgnoringModifiers(const char *text, size_t textLen, size_t pos, const char *label, size_t &textConsumed,
|
||||
size_t &matchScore)
|
||||
{
|
||||
// Treat FE0F and skin-tone modifiers as optional while matching.
|
||||
textConsumed = 0;
|
||||
matchScore = 0;
|
||||
if (!label || !*label || pos >= textLen)
|
||||
return false;
|
||||
|
||||
const size_t labelLen = strlen(label);
|
||||
size_t ti = pos;
|
||||
size_t li = 0;
|
||||
|
||||
while (true) {
|
||||
while (ti < textLen) {
|
||||
const size_t skipLen = ignorableModifierLenAt(text, ti, textLen);
|
||||
if (!skipLen)
|
||||
break;
|
||||
ti += skipLen;
|
||||
}
|
||||
while (li < labelLen) {
|
||||
const size_t skipLen = ignorableModifierLenAt(label, li, labelLen);
|
||||
if (!skipLen)
|
||||
break;
|
||||
li += skipLen;
|
||||
}
|
||||
|
||||
if (li >= labelLen) {
|
||||
while (ti < textLen) {
|
||||
const size_t skipLen = ignorableModifierLenAt(text, ti, textLen);
|
||||
if (!skipLen)
|
||||
break;
|
||||
ti += skipLen;
|
||||
}
|
||||
textConsumed = ti - pos;
|
||||
return textConsumed > 0;
|
||||
}
|
||||
|
||||
if (ti >= textLen)
|
||||
return false;
|
||||
|
||||
const uint8_t tc = static_cast<uint8_t>(text[ti]);
|
||||
const uint8_t lc = static_cast<uint8_t>(label[li]);
|
||||
const size_t tlen = utf8CharLen(tc);
|
||||
const size_t llen = utf8CharLen(lc);
|
||||
|
||||
if (tlen != llen || ti + tlen > textLen || li + llen > labelLen)
|
||||
return false;
|
||||
if (memcmp(text + ti, label + li, tlen) != 0)
|
||||
return false;
|
||||
|
||||
ti += tlen;
|
||||
li += llen;
|
||||
matchScore += llen;
|
||||
}
|
||||
}
|
||||
|
||||
const Emote *findEmoteAt(const char *text, size_t textLen, size_t pos, size_t &matchLen, const Emote *emoteSet, int emoteCount)
|
||||
{
|
||||
// Prefer the longest matching label at this byte offset.
|
||||
const Emote *matched = nullptr;
|
||||
matchLen = 0;
|
||||
size_t bestScore = 0;
|
||||
if (!text || pos >= textLen)
|
||||
return nullptr;
|
||||
|
||||
if (!isPossibleEmoteLead(static_cast<uint8_t>(text[pos])))
|
||||
return nullptr;
|
||||
|
||||
for (int i = 0; i < emoteCount; ++i) {
|
||||
const char *label = emoteSet[i].label;
|
||||
if (!label || !*label)
|
||||
continue;
|
||||
if (static_cast<uint8_t>(label[0]) != static_cast<uint8_t>(text[pos]))
|
||||
continue;
|
||||
|
||||
const size_t labelLen = strlen(label);
|
||||
if (labelLen == 0)
|
||||
continue;
|
||||
|
||||
size_t candidateLen = 0;
|
||||
size_t candidateScore = 0;
|
||||
if (pos + labelLen <= textLen && memcmp(text + pos, label, labelLen) == 0) {
|
||||
candidateLen = labelLen;
|
||||
candidateScore = labelLen;
|
||||
} else if (matchAtIgnoringModifiers(text, textLen, pos, label, candidateLen, candidateScore)) {
|
||||
// Matched with FE0F/skin tone modifiers treated as optional.
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (candidateScore > bestScore) {
|
||||
matched = &emoteSet[i];
|
||||
matchLen = candidateLen;
|
||||
bestScore = candidateScore;
|
||||
}
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
static LineMetrics analyzeLineInternal(OLEDDisplay *display, const char *line, size_t lineLen, int fallbackHeight,
|
||||
const Emote *emoteSet, int emoteCount, int emoteSpacing)
|
||||
{
|
||||
// Scan once to collect width and tallest emote for this line.
|
||||
LineMetrics metrics{0, fallbackHeight, false};
|
||||
if (!line)
|
||||
return metrics;
|
||||
|
||||
for (size_t i = 0; i < lineLen;) {
|
||||
size_t matchLen = 0;
|
||||
const Emote *matched = findEmoteAt(line, lineLen, i, matchLen, emoteSet, emoteCount);
|
||||
if (matched) {
|
||||
metrics.hasEmote = true;
|
||||
metrics.tallestHeight = std::max(metrics.tallestHeight, matched->height);
|
||||
if (display)
|
||||
metrics.width += matched->width + emoteSpacing;
|
||||
i += matchLen;
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t skipLen = ignorableModifierLenAt(line, i, lineLen);
|
||||
if (skipLen) {
|
||||
i += skipLen;
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t charLen = utf8CharLen(static_cast<uint8_t>(line[i]));
|
||||
if (display)
|
||||
metrics.width += getUtf8ChunkWidth(display, line + i, charLen);
|
||||
i += charLen;
|
||||
}
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
LineMetrics analyzeLine(OLEDDisplay *display, const char *line, int fallbackHeight, const Emote *emoteSet, int emoteCount,
|
||||
int emoteSpacing)
|
||||
{
|
||||
return analyzeLineInternal(display, line, line ? strlen(line) : 0, fallbackHeight, emoteSet, emoteCount, emoteSpacing);
|
||||
}
|
||||
|
||||
int maxEmoteHeight(const Emote *emoteSet, int emoteCount)
|
||||
{
|
||||
int tallest = 0;
|
||||
for (int i = 0; i < emoteCount; ++i) {
|
||||
if (emoteSet[i].label && *emoteSet[i].label)
|
||||
tallest = std::max(tallest, emoteSet[i].height);
|
||||
}
|
||||
return tallest;
|
||||
}
|
||||
|
||||
int measureStringWithEmotes(OLEDDisplay *display, const char *line, const Emote *emoteSet, int emoteCount, int emoteSpacing)
|
||||
{
|
||||
if (!display)
|
||||
return 0;
|
||||
|
||||
if (!line || !*line)
|
||||
return 0;
|
||||
|
||||
return analyzeLine(display, line, 0, emoteSet, emoteCount, emoteSpacing).width;
|
||||
}
|
||||
|
||||
static int appendTextSpanAndMeasure(OLEDDisplay *display, int cursorX, int fontY, const char *text, size_t len, bool draw,
|
||||
bool fauxBold)
|
||||
{
|
||||
// Draw plain-text runs in chunks so UTF-8 stays intact.
|
||||
if (!text || len == 0)
|
||||
return cursorX;
|
||||
|
||||
char chunk[33];
|
||||
size_t pos = 0;
|
||||
while (pos < len) {
|
||||
size_t chunkLen = 0;
|
||||
while (pos + chunkLen < len) {
|
||||
const size_t charLen = utf8CharLen(static_cast<uint8_t>(text[pos + chunkLen]));
|
||||
if (chunkLen + charLen >= sizeof(chunk))
|
||||
break;
|
||||
chunkLen += charLen;
|
||||
}
|
||||
|
||||
if (chunkLen == 0) {
|
||||
chunkLen = std::min(len - pos, sizeof(chunk) - 1);
|
||||
}
|
||||
|
||||
memcpy(chunk, text + pos, chunkLen);
|
||||
chunk[chunkLen] = '\0';
|
||||
if (draw) {
|
||||
if (fauxBold)
|
||||
display->drawString(cursorX + 1, fontY, chunk);
|
||||
display->drawString(cursorX, fontY, chunk);
|
||||
}
|
||||
cursorX += getStringWidth(display, chunk, chunkLen);
|
||||
pos += chunkLen;
|
||||
}
|
||||
|
||||
return cursorX;
|
||||
}
|
||||
|
||||
size_t truncateToWidth(OLEDDisplay *display, const char *line, char *out, size_t outSize, int maxWidth, const char *ellipsis,
|
||||
const Emote *emoteSet, int emoteCount, int emoteSpacing)
|
||||
{
|
||||
if (!out || outSize == 0)
|
||||
return 0;
|
||||
|
||||
out[0] = '\0';
|
||||
if (!display || !line || maxWidth <= 0)
|
||||
return 0;
|
||||
|
||||
const size_t lineLen = strlen(line);
|
||||
const int suffixWidth =
|
||||
(ellipsis && *ellipsis) ? measureStringWithEmotes(display, ellipsis, emoteSet, emoteCount, emoteSpacing) : 0;
|
||||
const char *suffix = (ellipsis && suffixWidth <= maxWidth) ? ellipsis : "";
|
||||
const size_t suffixLen = strlen(suffix);
|
||||
const int availableWidth = maxWidth - (*suffix ? suffixWidth : 0);
|
||||
|
||||
if (measureStringWithEmotes(display, line, emoteSet, emoteCount, emoteSpacing) <= maxWidth) {
|
||||
strncpy(out, line, outSize - 1);
|
||||
out[outSize - 1] = '\0';
|
||||
return strlen(out);
|
||||
}
|
||||
|
||||
int used = 0;
|
||||
size_t cut = 0;
|
||||
for (size_t i = 0; i < lineLen;) {
|
||||
// Keep whole emotes together when deciding where to cut.
|
||||
int tokenWidth = 0;
|
||||
size_t advance = 0;
|
||||
|
||||
if (isPossibleEmoteLead(static_cast<uint8_t>(line[i]))) {
|
||||
size_t matchLen = 0;
|
||||
const Emote *matched = findEmoteAt(line, lineLen, i, matchLen, emoteSet, emoteCount);
|
||||
if (matched) {
|
||||
tokenWidth = matched->width + emoteSpacing;
|
||||
advance = matchLen;
|
||||
}
|
||||
}
|
||||
|
||||
if (advance == 0) {
|
||||
const size_t skipLen = ignorableModifierLenAt(line, i, lineLen);
|
||||
if (skipLen) {
|
||||
i += skipLen;
|
||||
cut = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t charLen = utf8CharLen(static_cast<uint8_t>(line[i]));
|
||||
tokenWidth = getUtf8ChunkWidth(display, line + i, charLen);
|
||||
advance = charLen;
|
||||
}
|
||||
|
||||
if (used + tokenWidth > availableWidth)
|
||||
break;
|
||||
|
||||
used += tokenWidth;
|
||||
i += advance;
|
||||
cut = i;
|
||||
}
|
||||
|
||||
if (cut == 0) {
|
||||
strncpy(out, suffix, outSize - 1);
|
||||
out[outSize - 1] = '\0';
|
||||
return strlen(out);
|
||||
}
|
||||
|
||||
size_t copyLen = cut;
|
||||
if (copyLen > outSize - 1)
|
||||
copyLen = outSize - 1;
|
||||
if (suffixLen > 0 && copyLen + suffixLen > outSize - 1) {
|
||||
copyLen = (outSize - 1 > suffixLen) ? (outSize - 1 - suffixLen) : 0;
|
||||
}
|
||||
|
||||
memcpy(out, line, copyLen);
|
||||
size_t totalLen = copyLen;
|
||||
if (suffixLen > 0 && totalLen < outSize - 1) {
|
||||
memcpy(out + totalLen, suffix, std::min(suffixLen, outSize - 1 - totalLen));
|
||||
totalLen += std::min(suffixLen, outSize - 1 - totalLen);
|
||||
}
|
||||
out[totalLen] = '\0';
|
||||
return totalLen;
|
||||
}
|
||||
|
||||
void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const char *line, int fontHeight, const Emote *emoteSet,
|
||||
int emoteCount, int emoteSpacing, bool fauxBold)
|
||||
{
|
||||
if (!line)
|
||||
return;
|
||||
|
||||
const size_t lineLen = strlen(line);
|
||||
// Center text vertically when any emote is taller than the font.
|
||||
const int maxIconHeight =
|
||||
analyzeLineInternal(nullptr, line, lineLen, fontHeight, emoteSet, emoteCount, emoteSpacing).tallestHeight;
|
||||
const int lineHeight = std::max(fontHeight, maxIconHeight);
|
||||
const int fontY = y + (lineHeight - fontHeight) / 2;
|
||||
|
||||
int cursorX = x;
|
||||
bool inBold = false;
|
||||
|
||||
for (size_t i = 0; i < lineLen;) {
|
||||
// Toggle faux bold.
|
||||
if (fauxBold && i + 1 < lineLen && line[i] == '*' && line[i + 1] == '*') {
|
||||
inBold = !inBold;
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t skipLen = ignorableModifierLenAt(line, i, lineLen);
|
||||
if (skipLen) {
|
||||
i += skipLen;
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t matchLen = 0;
|
||||
const Emote *matched = findEmoteAt(line, lineLen, i, matchLen, emoteSet, emoteCount);
|
||||
if (matched) {
|
||||
const int iconY = y + (lineHeight - matched->height) / 2;
|
||||
display->drawXbm(cursorX, iconY, matched->width, matched->height, matched->bitmap);
|
||||
cursorX += matched->width + emoteSpacing;
|
||||
i += matchLen;
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t next = i;
|
||||
while (next < lineLen) {
|
||||
// Stop the text run before the next emote or bold marker.
|
||||
if (fauxBold && next + 1 < lineLen && line[next] == '*' && line[next + 1] == '*')
|
||||
break;
|
||||
|
||||
if (ignorableModifierLenAt(line, next, lineLen))
|
||||
break;
|
||||
|
||||
size_t nextMatchLen = 0;
|
||||
if (findEmoteAt(line, lineLen, next, nextMatchLen, emoteSet, emoteCount) != nullptr)
|
||||
break;
|
||||
|
||||
next += utf8CharLen(static_cast<uint8_t>(line[next]));
|
||||
}
|
||||
|
||||
if (next == i)
|
||||
next += utf8CharLen(static_cast<uint8_t>(line[i]));
|
||||
|
||||
cursorX = appendTextSpanAndMeasure(display, cursorX, fontY, line + i, next - i, true, fauxBold && inBold);
|
||||
i = next;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace EmoteRenderer
|
||||
} // namespace graphics
|
||||
|
||||
#endif // HAS_SCREEN
|
||||
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
#include "configuration.h"
|
||||
|
||||
#if HAS_SCREEN
|
||||
#include "graphics/emotes.h"
|
||||
#include <Arduino.h>
|
||||
#include <OLEDDisplay.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
namespace EmoteRenderer
|
||||
{
|
||||
|
||||
struct LineMetrics {
|
||||
int width;
|
||||
int tallestHeight;
|
||||
bool hasEmote;
|
||||
};
|
||||
|
||||
size_t utf8CharLen(uint8_t c);
|
||||
|
||||
const Emote *findEmoteByLabel(const char *label, const Emote *emoteSet = emotes, int emoteCount = numEmotes);
|
||||
const Emote *findEmoteAt(const char *text, size_t textLen, size_t pos, size_t &matchLen, const Emote *emoteSet = emotes,
|
||||
int emoteCount = numEmotes);
|
||||
inline const Emote *findEmoteAt(const std::string &text, size_t pos, size_t &matchLen, const Emote *emoteSet = emotes,
|
||||
int emoteCount = numEmotes)
|
||||
{
|
||||
return findEmoteAt(text.c_str(), text.length(), pos, matchLen, emoteSet, emoteCount);
|
||||
}
|
||||
|
||||
LineMetrics analyzeLine(OLEDDisplay *display, const char *line, int fallbackHeight = 0, const Emote *emoteSet = emotes,
|
||||
int emoteCount = numEmotes, int emoteSpacing = 1);
|
||||
inline LineMetrics analyzeLine(OLEDDisplay *display, const std::string &line, int fallbackHeight = 0,
|
||||
const Emote *emoteSet = emotes, int emoteCount = numEmotes, int emoteSpacing = 1)
|
||||
{
|
||||
return analyzeLine(display, line.c_str(), fallbackHeight, emoteSet, emoteCount, emoteSpacing);
|
||||
}
|
||||
int maxEmoteHeight(const Emote *emoteSet = emotes, int emoteCount = numEmotes);
|
||||
|
||||
int measureStringWithEmotes(OLEDDisplay *display, const char *line, const Emote *emoteSet = emotes, int emoteCount = numEmotes,
|
||||
int emoteSpacing = 1);
|
||||
inline int measureStringWithEmotes(OLEDDisplay *display, const std::string &line, const Emote *emoteSet = emotes,
|
||||
int emoteCount = numEmotes, int emoteSpacing = 1)
|
||||
{
|
||||
return measureStringWithEmotes(display, line.c_str(), emoteSet, emoteCount, emoteSpacing);
|
||||
}
|
||||
size_t truncateToWidth(OLEDDisplay *display, const char *line, char *out, size_t outSize, int maxWidth,
|
||||
const char *ellipsis = "...", const Emote *emoteSet = emotes, int emoteCount = numEmotes,
|
||||
int emoteSpacing = 1);
|
||||
inline std::string truncateToWidth(OLEDDisplay *display, const std::string &line, int maxWidth,
|
||||
const std::string &ellipsis = "...", const Emote *emoteSet = emotes,
|
||||
int emoteCount = numEmotes, int emoteSpacing = 1)
|
||||
{
|
||||
if (!display || maxWidth <= 0)
|
||||
return "";
|
||||
if (measureStringWithEmotes(display, line.c_str(), emoteSet, emoteCount, emoteSpacing) <= maxWidth)
|
||||
return line;
|
||||
|
||||
std::vector<char> out(line.length() + ellipsis.length() + 1, '\0');
|
||||
truncateToWidth(display, line.c_str(), out.data(), out.size(), maxWidth, ellipsis.c_str(), emoteSet, emoteCount,
|
||||
emoteSpacing);
|
||||
return std::string(out.data());
|
||||
}
|
||||
|
||||
void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const char *line, int fontHeight, const Emote *emoteSet = emotes,
|
||||
int emoteCount = numEmotes, int emoteSpacing = 1, bool fauxBold = true);
|
||||
inline void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string &line, int fontHeight,
|
||||
const Emote *emoteSet = emotes, int emoteCount = numEmotes, int emoteSpacing = 1,
|
||||
bool fauxBold = true)
|
||||
{
|
||||
drawStringWithEmotes(display, x, y, line.c_str(), fontHeight, emoteSet, emoteCount, emoteSpacing, fauxBold);
|
||||
}
|
||||
|
||||
} // namespace EmoteRenderer
|
||||
} // namespace graphics
|
||||
|
||||
#endif // HAS_SCREEN
|
||||
+23
-5
@@ -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);
|
||||
@@ -433,12 +436,15 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
||||
PMU->enablePowerOutput(XPOWERS_ALDO2);
|
||||
#endif
|
||||
|
||||
#if defined(MUZI_BASE)
|
||||
// some screens seem to need a kick in the pants to turn back on
|
||||
#if defined(MUZI_BASE) || defined(M5STACK_CARDPUTER_ADV)
|
||||
dispdev->init();
|
||||
dispdev->setBrightness(brightness);
|
||||
dispdev->flipScreenVertically();
|
||||
dispdev->resetDisplay();
|
||||
#ifdef SCREEN_12V_ENABLE
|
||||
digitalWrite(SCREEN_12V_ENABLE, HIGH);
|
||||
#endif
|
||||
delay(100);
|
||||
#endif
|
||||
#if !ARCH_PORTDUINO
|
||||
@@ -462,9 +468,11 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
||||
#if defined(HELTEC_TRACKER_V1_X) || defined(HELTEC_WIRELESS_TRACKER_V2)
|
||||
ui->init();
|
||||
#endif
|
||||
#ifdef USE_ST7789
|
||||
#if defined(USE_ST7789) && defined(VTFT_LEDA)
|
||||
#ifdef VTFT_CTRL
|
||||
pinMode(VTFT_CTRL, OUTPUT);
|
||||
digitalWrite(VTFT_CTRL, LOW);
|
||||
#endif
|
||||
ui->init();
|
||||
#ifdef ESP_PLATFORM
|
||||
analogWrite(VTFT_LEDA, BRIGHTNESS_DEFAULT);
|
||||
@@ -506,8 +514,12 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
||||
#ifdef USE_ST7789
|
||||
SPI1.end();
|
||||
#if defined(ARCH_ESP32)
|
||||
#ifdef VTFT_LEDA
|
||||
pinMode(VTFT_LEDA, ANALOG);
|
||||
#endif
|
||||
#ifdef VTFT_CTRL
|
||||
pinMode(VTFT_CTRL, ANALOG);
|
||||
#endif
|
||||
pinMode(ST7789_RESET, ANALOG);
|
||||
pinMode(ST7789_RS, ANALOG);
|
||||
pinMode(ST7789_NSS, ANALOG);
|
||||
@@ -750,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) {
|
||||
@@ -989,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
|
||||
|
||||
@@ -1002,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
|
||||
|
||||
@@ -765,7 +765,11 @@ class Screen : public concurrency::OSThread
|
||||
DebugInfo debugInfo;
|
||||
|
||||
/// Display device
|
||||
#ifdef USE_ST7789
|
||||
ST7789Spi *dispdev;
|
||||
#else
|
||||
OLEDDisplay *dispdev;
|
||||
#endif
|
||||
|
||||
/// UI helper for rendering to frames and switching between them
|
||||
OLEDDisplayUi *ui;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -121,11 +121,10 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
||||
}
|
||||
|
||||
// === Screen Title ===
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->drawString(SCREEN_WIDTH / 2, y, titleStr);
|
||||
if (config.display.heading_bold) {
|
||||
display->drawString((SCREEN_WIDTH / 2) + 1, y, titleStr);
|
||||
}
|
||||
const char *headerTitle = titleStr ? titleStr : "";
|
||||
const int titleWidth = UIRenderer::measureStringWithEmotes(display, headerTitle);
|
||||
const int titleX = (SCREEN_WIDTH - titleWidth) / 2;
|
||||
UIRenderer::drawStringWithEmotes(display, titleX, y, headerTitle, FONT_HEIGHT_SMALL, 1, config.display.heading_bold);
|
||||
}
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
@@ -515,4 +514,4 @@ std::string sanitizeString(const std::string &input)
|
||||
}
|
||||
|
||||
} // namespace graphics
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -1348,7 +1348,7 @@ void TFTDisplay::sendCommand(uint8_t com)
|
||||
digitalWrite(portduino_config.displayBacklight.pin, TFT_BACKLIGHT_ON);
|
||||
#elif defined(HACKADAY_COMMUNICATOR)
|
||||
tft->displayOn();
|
||||
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
|
||||
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) && !defined(HELTEC_MESH_NODE_T096)
|
||||
tft->wakeup();
|
||||
tft->powerSaveOff();
|
||||
#endif
|
||||
@@ -1359,7 +1359,7 @@ void TFTDisplay::sendCommand(uint8_t com)
|
||||
#ifdef UNPHONE
|
||||
unphone.backlight(true); // using unPhone library
|
||||
#endif
|
||||
#ifdef RAK14014
|
||||
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096)
|
||||
#elif !defined(M5STACK) && !defined(ST7789_CS) && \
|
||||
!defined(HACKADAY_COMMUNICATOR) // T-Deck gets brightness set in Screen.cpp in the handleSetOn function
|
||||
tft->setBrightness(172);
|
||||
@@ -1375,7 +1375,7 @@ void TFTDisplay::sendCommand(uint8_t com)
|
||||
digitalWrite(portduino_config.displayBacklight.pin, !TFT_BACKLIGHT_ON);
|
||||
#elif defined(HACKADAY_COMMUNICATOR)
|
||||
tft->displayOff();
|
||||
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
|
||||
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) && !defined(HELTEC_MESH_NODE_T096)
|
||||
tft->sleep();
|
||||
tft->powerSaveOn();
|
||||
#endif
|
||||
@@ -1386,7 +1386,7 @@ void TFTDisplay::sendCommand(uint8_t com)
|
||||
#ifdef UNPHONE
|
||||
unphone.backlight(false); // using unPhone library
|
||||
#endif
|
||||
#ifdef RAK14014
|
||||
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096)
|
||||
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR)
|
||||
tft->setBrightness(0);
|
||||
#endif
|
||||
@@ -1401,7 +1401,7 @@ void TFTDisplay::sendCommand(uint8_t com)
|
||||
|
||||
void TFTDisplay::setDisplayBrightness(uint8_t _brightness)
|
||||
{
|
||||
#ifdef RAK14014
|
||||
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096)
|
||||
// todo
|
||||
#elif !defined(HACKADAY_COMMUNICATOR)
|
||||
tft->setBrightness(_brightness);
|
||||
@@ -1421,7 +1421,7 @@ bool TFTDisplay::hasTouch(void)
|
||||
{
|
||||
#ifdef RAK14014
|
||||
return true;
|
||||
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR)
|
||||
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096)
|
||||
return tft->touch() != nullptr;
|
||||
#else
|
||||
return false;
|
||||
@@ -1440,7 +1440,7 @@ bool TFTDisplay::getTouch(int16_t *x, int16_t *y)
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR)
|
||||
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096)
|
||||
return tft->getTouch(x, y);
|
||||
#else
|
||||
return false;
|
||||
@@ -1457,7 +1457,7 @@ bool TFTDisplay::connect()
|
||||
{
|
||||
concurrency::LockGuard g(spiLock);
|
||||
LOG_INFO("Do TFT init");
|
||||
#ifdef RAK14014
|
||||
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096)
|
||||
tft = new TFT_eSPI;
|
||||
#elif defined(HACKADAY_COMMUNICATOR)
|
||||
bus = new Arduino_ESP32SPI(TFT_DC, TFT_CS, 38 /* SCK */, 21 /* MOSI */, GFX_NOT_DEFINED /* MISO */, HSPI /* spi_num */);
|
||||
@@ -1494,7 +1494,7 @@ bool TFTDisplay::connect()
|
||||
ft6336u.begin();
|
||||
pinMode(SCREEN_TOUCH_INT, INPUT_PULLUP);
|
||||
attachInterrupt(digitalPinToInterrupt(SCREEN_TOUCH_INT), rak14014_tpIntHandle, FALLING);
|
||||
#elif defined(T_DECK) || defined(PICOMPUTER_S3) || defined(CHATTER_2)
|
||||
#elif defined(T_DECK) || defined(PICOMPUTER_S3) || defined(CHATTER_2) || defined(HELTEC_MESH_NODE_T096)
|
||||
tft->setRotation(1); // T-Deck has the TFT in landscape
|
||||
#elif defined(T_WATCH_S3)
|
||||
tft->setRotation(2); // T-Watch S3 left-handed orientation
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user