Compare commits

..
Author SHA1 Message Date
Manuel b01079a281 update commit references 2026-05-31 23:03:13 +02:00
Manuel b9a0100c0d add ES8311, GT911, AW35615, LP5814 to I2C scanner 2026-05-31 22:56:07 +02:00
Manuel 65c7112a39 add ADS1115+AW35615 for wio tracker L2 2026-05-31 22:55:07 +02:00
Manuel 6c23fcddf0 implement mesh LED 2026-05-31 13:55:44 +02:00
Manuel 4d7d021ef5 enable power save 2026-05-31 13:55:05 +02:00
Manuel 025c6854e8 initial commit 2026-05-31 01:17:11 +02:00
137 changed files with 1212 additions and 3382 deletions
+1 -1
View File
@@ -76,7 +76,7 @@ runs:
done
- name: PlatformIO ${{ inputs.arch }} download cache
uses: actions/cache@v6
uses: actions/cache@v5
with:
path: ~/.platformio/.cache
key: pio-cache-${{ inputs.arch }}-${{ hashFiles('.github/actions/**', '**.ini') }}
+1 -1
View File
@@ -5,7 +5,7 @@ runs:
using: composite
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
path: meshtasticd
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
outputs:
artifact-id: ${{ steps.upload-firmware.outputs.artifact-id }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
submodules: recursive
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
runs-on: macos-${{ inputs.macos_ver }}
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
+3 -3
View File
@@ -43,7 +43,7 @@ jobs:
- stm32
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: 3.x
@@ -64,7 +64,7 @@ jobs:
if: ${{ inputs.target != '' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Get release version string
run: |
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
@@ -93,7 +93,7 @@ jobs:
needs: [version, build]
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
+1 -1
View File
@@ -47,7 +47,7 @@ jobs:
runs-on: ${{ inputs.runs-on }}
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
+1 -1
View File
@@ -103,7 +103,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
-190
View File
@@ -1,190 +0,0 @@
name: Post Web Flasher Link Comment
on:
workflow_run:
workflows: [CI]
types: [completed]
permissions:
pull-requests: write
actions: read
jobs:
post-flasher-link:
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion != 'cancelled' &&
github.repository == 'meshtastic/firmware'
continue-on-error: true
runs-on: ubuntu-latest
steps:
# Per-board manifests carry the firmware's own metadata (activelySupported,
# displayName, ...) generated from each target's custom_meshtastic_* config.
- name: Download board manifests
uses: actions/download-artifact@v8
continue-on-error: true
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
pattern: manifest-*
path: ./manifests
merge-multiple: true
- name: Post or update web flasher link comment
uses: actions/github-script@v9
with:
script: |
const marker = '<!-- web-flasher-link -->';
const run = context.payload.workflow_run;
const { owner, repo } = context.repo;
// Resolve the PR number (run.pull_requests is empty for fork PRs)
let prNumber = run.pull_requests?.[0]?.number;
if (!prNumber) {
const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner, repo, commit_sha: run.head_sha,
});
prNumber = (prs.find((pr) => pr.head.sha === run.head_sha) ?? prs[0])?.number;
}
if (!prNumber) {
core.info('No pull request associated with this run; skipping.');
return;
}
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
// Only comment on PRs authored by members of the organization.
// author_association MEMBER is computed by GitHub and reflects org
// membership (including concealed members); OWNER covers a repo owner.
const allowedAssociations = ['OWNER', 'MEMBER'];
if (!allowedAssociations.includes(pr.author_association)) {
core.info(`Author association ${pr.author_association} is not an org member; skipping.`);
return;
}
if (pr.state !== 'open') {
core.info('Pull request is not open; skipping.');
return;
}
if (pr.head.sha !== run.head_sha) {
core.info('Run is for an outdated commit; skipping.');
return;
}
// Require at least one per-arch firmware artifact from gather-artifacts
const artifacts = await github.paginate(github.rest.actions.listWorkflowRunArtifacts, {
owner, repo, run_id: run.id, per_page: 100,
});
const archRe = /^firmware-(esp32|esp32s3|esp32c3|esp32c6|nrf52840|rp2040|rp2350|stm32)-(\d+\.\d+\.\d+\.[0-9a-f]+)$/;
const archArtifacts = artifacts.filter((a) => archRe.test(a.name) && !a.expired);
if (archArtifacts.length === 0) {
core.info('No per-arch firmware artifacts found; skipping.');
return;
}
const version = archRe.exec(archArtifacts[0].name)[2];
const expiresAt = archArtifacts[0].expires_at
? new Date(archArtifacts[0].expires_at).toISOString().slice(0, 10)
: null;
// Read each built board's manifest (.mt.json). activelySupported,
// displayName and architecture come straight from the board's
// custom_meshtastic_* platformio config, so the list is in sync with
// the firmware itself — no external device database needed.
const fs = require('fs');
let boards = [];
try {
boards = fs.readdirSync('./manifests')
.filter((f) => f.endsWith('.mt.json'))
.map((f) => {
try { return JSON.parse(fs.readFileSync(`./manifests/${f}`, 'utf8')); }
catch { return null; }
})
.filter((m) => m && m.activelySupported === true && m.platformioTarget)
.map((m) => ({
board: m.platformioTarget,
platform: m.architecture || '',
// displayName is maintainer-authored text; escape table-breaking pipes
displayName: String(m.displayName || m.platformioTarget).replace(/\|/g, '\\|'),
image: Array.isArray(m.images) && m.images[0] ? String(m.images[0]) : '',
}))
.sort((a, b) => a.board.localeCompare(b.board));
} catch (e) {
core.warning(`Could not read board manifests: ${e.message}`);
}
const flasherUrl = `https://flasher.meshtastic.org/?pr=${prNumber}`;
// Device illustrations are served by the flasher from the same image
// names the manifest declares (custom_meshtastic_images). The flasher
// serves its SPA shell (HTML, 200) for unknown paths, so confirm each
// image really resolves to an image before linking it.
const imageBase = 'https://flasher.meshtastic.org/img/devices/';
await Promise.all(boards.map(async (b) => {
if (!b.image) return;
try {
const res = await fetch(`${imageBase}${encodeURIComponent(b.image)}`);
const type = res.headers.get('content-type') || '';
if (!res.ok || !type.startsWith('image/')) b.image = '';
} catch { b.image = ''; }
}));
const boardLines = boards
.map((b) => {
const img = b.image ? `<img src="${imageBase}${encodeURIComponent(b.image)}" alt="" height="34">` : '';
return `| ${img} | ${b.displayName} | [\`${b.board}\`](${flasherUrl}&device=${encodeURIComponent(b.board)}) | ${b.platform} |`;
})
.join('\n');
// Shields.io badges. Only non-user-controlled, charset-constrained values
// (version, commit sha, counts, dates) go into badge URLs — never board
// names or the PR title — so the rendered comment cannot be spoofed.
const shieldText = (s) =>
encodeURIComponent(String(s).replace(/-/g, '--').replace(/_/g, '__').replace(/ /g, '_'));
const shield = (label, message, color) =>
`https://img.shields.io/badge/${shieldText(label)}-${shieldText(message)}-${color}`;
const buttonUrl =
`https://img.shields.io/badge/${shieldText('Flash this PR in the Web Flasher')}-2C2D3C?style=for-the-badge`;
const badges = [
`![firmware](${shield('firmware', version, '67EA94')})`,
`![commit](${shield('commit', run.head_sha.slice(0, 7), '2C2D3C')})`,
`![boards](${shield('boards', boards.length, '5C6BC0')})`,
];
if (expiresAt) badges.push(`![expires](${shield('expires', expiresAt, '9A4E00')})`);
// Only render the board table when there are supported boards to list
const boardTable = boards.length > 0 ? [
`<details><summary>Supported boards built by this PR (${boards.length})</summary>`,
'',
'| | Device | Board | Platform |',
'| --- | --- | --- | --- |',
boardLines,
'',
'</details>',
'',
] : [];
const body = [
marker,
'## ⚡ Try this PR in the Web Flasher',
'',
`[![Flash this PR in the Web Flasher](${buttonUrl})](${flasherUrl})`,
'',
badges.join(' '),
'',
'> [!WARNING]',
'> This is an automated, unreviewed CI test build. Back up your device configuration',
'> before flashing, and only flash devices you are able to recover.',
'',
...boardTable,
`*Build artifacts expire${expiresAt ? ` on ${expiresAt}` : ' after 30 days'}. Updated for \`${run.head_sha.slice(0, 7)}\`.*`,
].join('\n');
// Sticky comment: update in place when the marker is found
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number: prNumber, per_page: 100,
});
const existing = comments.find((c) => c.body?.includes(marker));
if (existing) {
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
} else {
await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body });
}
@@ -1,60 +0,0 @@
name: Post Web Flasher Build Placeholder
# Drops an immediate "build in progress" comment when a PR opens, so the web
# flasher entry shows up right away. The real CI-driven workflow
# (flasher-link-comment.yml) later replaces it in place via the shared marker.
#
# SECURITY: this uses pull_request_target (write token, runs for fork PRs) but is
# safe because it never checks out or runs PR code and posts a fully static body
# — no PR title, branch name, or other untrusted input is used anywhere.
on:
pull_request_target:
types: [opened, reopened]
permissions:
pull-requests: write
jobs:
post-placeholder:
if: github.repository == 'meshtastic/firmware'
continue-on-error: true
runs-on: ubuntu-latest
steps:
- name: Post web flasher build-in-progress placeholder
uses: actions/github-script@v9
with:
script: |
const marker = '<!-- web-flasher-link -->';
const { owner, repo } = context.repo;
const pr = context.payload.pull_request;
// Only org members get the flasher comment (matches the real workflow)
const allowedAssociations = ['OWNER', 'MEMBER'];
if (!allowedAssociations.includes(pr.author_association)) {
core.info(`Author association ${pr.author_association} is not an org member; skipping.`);
return;
}
// Only seed a placeholder when no flasher comment exists yet — never
// overwrite a real (or existing placeholder) comment.
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number: pr.number, per_page: 100,
});
if (comments.some((c) => c.body?.includes(marker))) {
core.info('Flasher comment already exists; nothing to do.');
return;
}
const body = [
marker,
'## ⚡ Try this PR in the Web Flasher',
'',
'> [!NOTE]',
'> Building this pull request… the flash button, badges and supported-board',
'> list will appear here automatically once CI finishes.',
].join('\n');
await github.rest.issues.createComment({
owner, repo, issue_number: pr.number, body,
});
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
+49 -7
View File
@@ -40,7 +40,7 @@ jobs:
- check
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: 3.x
@@ -64,7 +64,7 @@ jobs:
version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Get release version string
run: |
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
@@ -86,7 +86,7 @@ jobs:
runs-on: ${{ github.repository_owner == 'meshtastic' && 'arctastic' || 'ubuntu-latest' }}
if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Check ${{ matrix.check.board }}
@@ -189,7 +189,7 @@ jobs:
needs: [version, build]
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
- uses: actions/download-artifact@v8
with:
@@ -245,6 +245,48 @@ jobs:
path: ./*.elf
retention-days: 30
shame:
if: github.repository == 'meshtastic/firmware'
continue-on-error: true
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: actions/checkout@v6
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@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@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'
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'
# 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'
# run: python3 bin/shame.py ${{ github.event.pull_request.number }} manifests-old/ manifests-new/
release-artifacts:
permissions: # Needed for 'gh release upload'.
contents: write
@@ -261,7 +303,7 @@ jobs:
# - MacOS
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
fetch-depth: 0
@@ -362,7 +404,7 @@ jobs:
needs: [release-artifacts, version]
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
@@ -417,7 +459,7 @@ jobs:
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
fetch-depth: 0
+5 -5
View File
@@ -14,16 +14,16 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Trunk Check
uses: trunk-io/trunk-action@v1.3.1
uses: trunk-io/trunk-action@v1
with:
trunk-token: ${{ secrets.TRUNK_TOKEN }}
trunk_upgrade:
if: github.repository == 'meshtastic/firmware'
# See: https://github.com/trunk-io/trunk-action/blob/main/readme.md#automatic-upgrades
# See: https://github.com/trunk-io/trunk-action/blob/v1/readme.md#automatic-upgrades
name: Trunk Upgrade (PR)
runs-on: ubuntu-24.04
permissions:
@@ -31,9 +31,9 @@ jobs:
pull-requests: write # For trunk to create PRs
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Trunk Upgrade
uses: trunk-io/trunk-action/upgrade@v1.3.1
uses: trunk-io/trunk-action/upgrade@v1
with:
base: master
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
needs: build-debian-src
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
path: meshtasticd
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
needs: build-debian-src
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
path: meshtasticd
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
checks: write
pull-requests: write
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
submodules: recursive
+1 -1
View File
@@ -91,7 +91,7 @@ jobs:
shell: bash
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
# Always use master branch for version bumps
ref: master
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
steps:
# step 1
- name: clone application source code
uses: actions/checkout@v7
uses: actions/checkout@v6
# step 2
- name: full scan
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
steps:
# step 1
- name: clone application source code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
fetch-depth: 0
+3 -3
View File
@@ -14,7 +14,7 @@ jobs:
name: Native Simulator Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
submodules: recursive
@@ -68,7 +68,7 @@ jobs:
name: Native PlatformIO Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
submodules: recursive
@@ -123,7 +123,7 @@ jobs:
- platformio-tests
if: always()
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Get release version string
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: test-runner
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
# - uses: actions/setup-python@v6
# with:
+3 -3
View File
@@ -1,5 +1,5 @@
name: Annotate PR with trunk issues
# See: https://github.com/trunk-io/trunk-action/blob/main/readme.md#getting-inline-annotations-for-fork-prs
# See: https://github.com/trunk-io/trunk-action/blob/v1/readme.md#getting-inline-annotations-for-fork-prs
on:
workflow_run:
@@ -18,9 +18,9 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Trunk Check
uses: trunk-io/trunk-action@v1.3.1
uses: trunk-io/trunk-action@v1
with:
post-annotations: true
+2 -2
View File
@@ -16,9 +16,9 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Trunk Check
uses: trunk-io/trunk-action@v1.3.1
uses: trunk-io/trunk-action@v1
with:
save-annotations: true
+5 -10
View File
@@ -11,23 +11,18 @@ jobs:
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: true
- name: Update submodule
if: ${{ github.ref_name == 'master' || github.ref_name == 'develop' }}
working-directory: protobufs
env:
# Use the branch that triggered the workflow as the protobuf branch.
GIT_BRANCH: ${{ github.ref_name }}
if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' }}
run: |
git fetch --prune origin $GIT_BRANCH
git checkout origin/$GIT_BRANCH
git submodule update --remote protobufs
- name: Download nanopb
run: |
wget https://github.com/nanopb/nanopb/releases/download/nanopb-0.4.9.1/nanopb-0.4.9.1-linux-x86.tar.gz
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.9.1-linux-x86.tar.gz
tar xvzf nanopb-0.4.9.1-linux-x86.tar.gz
mv nanopb-0.4.9.1-linux-x86 nanopb-0.4.9
@@ -38,7 +33,7 @@ jobs:
- name: Create pull request
uses: peter-evans/create-pull-request@v8
with:
branch: create-pull-request/update-protobufs-${{ github.ref_name }}
branch: create-pull-request/update-protobufs
labels: submodules
title: Update protobufs and classes
commit-message: Update protobufs
+8 -8
View File
@@ -4,21 +4,21 @@ cli:
plugins:
sources:
- id: trunk
ref: v1.10.2
ref: v1.10.0
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.3.1
- renovate@43.236.0
- prettier@3.8.4
- trufflehog@3.95.6
- checkov@3.2.529
- renovate@43.150.0
- prettier@3.8.3
- trufflehog@3.95.3
- yamllint@1.38.0
- bandit@1.9.4
- trivy@0.71.2
- trivy@0.70.0
- taplo@0.10.0
- ruff@0.15.18
- ruff@0.15.14
- isort@8.0.1
- markdownlint@0.49.0
- markdownlint@0.48.0
- oxipng@10.1.1
- svgo@4.0.1
- actionlint@1.7.12
+2 -2
View File
@@ -4,7 +4,7 @@
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
# Ensure the Alpine version is updated in both stages of the container!
FROM alpine:3.24 AS builder
FROM alpine:3.23 AS builder
ARG PIO_ENV=native
# Enable Alpine community repository (for 'py3-grpcio-tools')
@@ -35,7 +35,7 @@ RUN bash ./bin/build-native.sh "$PIO_ENV" && \
# ##### PRODUCTION BUILD #############
FROM alpine:3.24
FROM alpine:3.23
LABEL org.opencontainers.image.title="Meshtastic" \
org.opencontainers.image.description="Alpine Meshtastic daemon" \
org.opencontainers.image.url="https://meshtastic.org" \
-19
View File
@@ -1,19 +0,0 @@
# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/NebraHat
# Use for 1 watt hat
Meta:
name: NebraHat 1W
support: community
compatible:
- raspberry-pi
Lora:
Module: sx1262 # Nebra SX1262 Pi Hat - 1W
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
# CS: 8 # Newer version of MeshtasticD do not need this? If issues uncomment this line
IRQ: 22
Busy: 4
Reset: 18
RXen: 25
I2C:
I2CDevice: /dev/i2c-1
-20
View File
@@ -1,20 +0,0 @@
# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/NebraHat
# Use for 2 watt hat
Meta:
name: NebraHat 2W
support: community
compatible:
- raspberry-pi
Lora:
Module: sx1262 # Nebra SX1262 Pi Hat - 2W
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
SX126X_MAX_POWER: 8
# CS: 8 # Newer version of MeshtasticD do not need this? If issues uncomment this line
IRQ: 22
Busy: 4
Reset: 18
RXen: 25
I2C:
I2CDevice: /dev/i2c-1
+1 -1
View File
@@ -18,4 +18,4 @@ Lora:
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
# CS: 8
# CS: 8
+2 -6
View File
@@ -5,9 +5,7 @@ Meta:
- raspberry-pi
Lora:
### RAK13300 in Slot 2
Module: sx1262
### RAK13300 in Slot 2 pins
IRQ: 18 #IO6
Reset: 24 # IO4
Busy: 19 # IO5
@@ -15,7 +13,5 @@ Lora:
Enable_Pins:
- 26
- 23
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: 7
# CS: 7
+2 -6
View File
@@ -5,18 +5,14 @@ Meta:
- raspberry-pi
Lora:
### RAK13302 in Slot 2
Module: sx1262
### RAK13302 in Slot 2 pins
IRQ: 18 #IO6
Reset: 24 # IO4
Busy: 19 # IO5
# Ant_sw: 23 # IO3
# Ant_sw: 23 # IO3
Enable_Pins:
- 26
- 23
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
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]
-19
View File
@@ -1,19 +0,0 @@
# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/ZebraHAT
# Use for 1 watt hat
Meta:
name: ZebraHat 1W
support: community
compatible:
- raspberry-pi
Lora:
Module: sx1262 # Zebra SX1262 Pi Hat - 1W
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
SX126X_MAX_POWER: 18
CS: 24
IRQ: 22
Busy: 27
Reset: 17
I2C:
I2CDevice: /dev/i2c-1
-20
View File
@@ -1,20 +0,0 @@
# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/ZebraHAT
# Use for 2 watt hat
Meta:
name: ZebraHat 2W
support: community
compatible:
- raspberry-pi
Lora:
Module: sx1262 # Zebra SX1262 Pi Hat - 2W
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
SX126X_MAX_POWER: 8
CS: 24
IRQ: 22
Busy: 27
Reset: 17
RXen: 25
I2C:
I2CDevice: /dev/i2c-1
@@ -29,8 +29,6 @@ Lora:
- pin: 50 # GPIO1_C2 (physical 16)
gpiochip: 1
line: 18
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: # GPIO0_A7 (SPI1_CSN1, physical 26)
# pin: 7
@@ -29,8 +29,6 @@ Lora:
- pin: 50 # GPIO1_C2 (physical 16)
gpiochip: 1
line: 18
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: # GPIO0_A7 (SPI1_CSN1, physical 26)
# pin: 7
@@ -1,29 +0,0 @@
# Station G3 + BQ35LORA900V1M Primary Slot
# Board Doc: https://wiki.bqvoy.com/en/devkits/station-g3
Meta:
name: B&Q Station G3 + BQ35LORA900V1M Primary Slot
support: official
compatible:
- luckfox-lyra-zero-w # Armbian
Lora:
Module: sx1262
IRQ: # GPIO0_A5 (physical 15)
pin: 5
gpiochip: 0
line: 5
Reset: # GPIO1_D1 (physical 36)
pin: 57
gpiochip: 1
line: 25
Busy: # GPIO0_B4 (physical 18)
pin: 12
gpiochip: 0
line: 12
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
# CS: # GPIO0_B2 (physical 24)
# pin: 10
# gpiochip: 0
# line: 10
@@ -29,8 +29,6 @@ Lora:
- pin: 13 # GPIO0_B5
gpiochip: 0
line: 13
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: # GPIO0_B1
# pin: 9
@@ -29,8 +29,6 @@ Lora:
- pin: 13 # GPIO0_B5
gpiochip: 0
line: 13
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: # GPIO0_B1
# pin: 9
@@ -31,8 +31,6 @@ Lora:
- pin: 103 # GPIO3_A7 (physical 16)
gpiochip: 3
line: 7
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: # GPIO0_B7 (SPI0_CSN1, physical 26)
# pin: 15
@@ -31,8 +31,6 @@ Lora:
- pin: 103 # GPIO3_A7 (physical 16)
gpiochip: 3
line: 7
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: # GPIO0_B7 (SPI0_CSN1, physical 26)
# pin: 15
@@ -1,17 +0,0 @@
# Station G3 + BQ35LORA900V1M Primary Slot
# Board Doc: https://wiki.bqvoy.com/en/devkits/station-g3
Meta:
name: B&Q Station G3 + BQ35LORA900V1M Primary Slot
support: official
compatible:
- raspberry-pi
Lora:
Module: sx1262
IRQ: 22
Reset: 16
Busy: 24
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
#CS: 8
-2
View File
@@ -1,5 +1,3 @@
# This config works with all revisions of the Meshtoad USB radio.
Meta:
name: meshtoad-e22
support: official
@@ -87,12 +87,6 @@
</screenshots>
<releases>
<release version="2.7.27" date="2026-06-24">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.27</url>
</release>
<release version="2.7.26" date="2026-06-10">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.26</url>
</release>
<release version="2.7.25" date="2026-05-23">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.25</url>
</release>
-54
View File
@@ -1,54 +0,0 @@
{
"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-n5262",
"mcu": "nrf52840",
"variant": "heltec_mesh_node_t1",
"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 Mesh Node T1",
"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"
}
+42
View File
@@ -0,0 +1,42 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi"
},
"core": "esp32",
"extra_flags": [
"-D BOARD_HAS_PSRAM",
"-D ARDUINO_USB_CDC_ON_BOOT=1",
"-D ARDUINO_USB_MODE=1",
"-D ARDUINO_RUNNING_CORE=1",
"-D ARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"psram_type": "opi",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "esp32s3"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": ["esp-builtin"],
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "seeed_wio_tracker_L2 (16 MB FLASH, 8 MB PSRAM)",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
"url": "https://www.seeedstudio.com/",
"vendor": "Seeed Studio"
}
-12
View File
@@ -1,15 +1,3 @@
meshtasticd (2.7.27.0) unstable; urgency=medium
* Version 2.7.27
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Wed, 24 Jun 2026 11:20:05 +0000
meshtasticd (2.7.26.0) unstable; urgency=medium
* Version 2.7.26
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Wed, 10 Jun 2026 00:19:23 +0000
meshtasticd (2.7.25.0) unstable; urgency=medium
* Version 2.7.25
+1 -1
View File
@@ -33,5 +33,5 @@ if [[ -n $GPG_KEY_ID ]]; then
debuild -S -nc -k"$GPG_KEY_ID"
else
# Build the source deb without signing (forks)
debuild -S -nc -us -uc
debuild -S -nc
fi
+5 -9
View File
@@ -126,7 +126,7 @@ lib_deps =
[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/1c45ebc7433acb8ba3fe96a6f7deca9c43fa54cf.zip
https://github.com/meshtastic/device-ui/archive/c23fdc22f2ba949829661fdf851846fcf7433d89.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]
@@ -140,7 +140,7 @@ lib_deps =
# renovate: datasource=github-tags depName=NeoPixel packageName=adafruit/Adafruit_NeoPixel
https://github.com/adafruit/Adafruit_NeoPixel/archive/1.15.5.zip
# renovate: datasource=github-tags depName=Adafruit SSD1306 packageName=adafruit/Adafruit_SSD1306
https://github.com/adafruit/Adafruit_SSD1306/archive/2.5.17.zip
https://github.com/adafruit/Adafruit_SSD1306/archive/refs/tags/2.5.16.zip
# renovate: datasource=github-tags depName=Adafruit BMP280 packageName=adafruit/Adafruit_BMP280_Library
https://github.com/adafruit/Adafruit_BMP280_Library/archive/refs/tags/3.0.0.zip
# renovate: datasource=github-tags depName=Adafruit BMP085 packageName=adafruit/Adafruit-BMP085-Library
@@ -185,22 +185,16 @@ lib_deps =
https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library/archive/refs/tags/v1.1.2.zip
# renovate: datasource=github-tags depName=SparkFun 9DoF IMU Breakout ICM 20948 packageName=sparkfun/SparkFun_ICM-20948_ArduinoLibrary
https://github.com/sparkfun/SparkFun_ICM-20948_ArduinoLibrary/archive/refs/tags/v1.3.2.zip
# renovate: datasource=github-tags depName=TDK InvenSense ICM42670P packageName=tdk-invn-oss/motion.arduino.ICM42670P
https://github.com/tdk-invn-oss/motion.arduino.ICM42670P/archive/refs/tags/1.0.8.zip
# renovate: datasource=github-tags depName=Adafruit LTR390 Library packageName=adafruit/Adafruit_LTR390
https://github.com/adafruit/Adafruit_LTR390/archive/refs/tags/1.1.2.zip
# renovate: datasource=github-tags depName=Adafruit PCT2075 packageName=adafruit/Adafruit_PCT2075
https://github.com/adafruit/Adafruit_PCT2075/archive/refs/tags/1.0.6.zip
# renovate: datasource=github-tags depName=DFRobot_BMM150 packageName=dfrobot/DFRobot_BMM150
https://github.com/DFRobot/DFRobot_BMM150/archive/refs/tags/V1.0.0.zip
# renovate: datasource=github-tags depName=SparkFun MMC5983MA Magnetometer packageName=sparkfun/SparkFun_MMC5983MA_Magnetometer_Arduino_Library
https://github.com/sparkfun/SparkFun_MMC5983MA_Magnetometer_Arduino_Library/archive/v1.1.5.zip
# renovate: datasource=github-tags depName=Adafruit_TSL2561 packageName=adafruit/Adafruit_TSL2561
https://github.com/adafruit/Adafruit_TSL2561/archive/refs/tags/1.1.3.zip
# renovate: datasource=github-tags depName=BH1750_WE packageName=wollewald/BH1750_WE
https://github.com/wollewald/BH1750_WE/archive/refs/tags/1.1.10.zip
# renovate: datasource=github-tags depName=Adafruit SHTC3 packageName=adafruit/Adafruit_SHTC3
https://github.com/adafruit/Adafruit_SHTC3/archive/refs/tags/1.0.2.zip
; Common environmental sensor libraries (not included in native / portduino)
[environmental_extra_common]
@@ -209,6 +203,8 @@ lib_deps =
https://github.com/adafruit/Adafruit_BMP3XX/archive/refs/tags/2.1.6.zip
# renovate: datasource=github-tags depName=Adafruit MAX1704X packageName=adafruit/Adafruit_MAX1704X
https://github.com/adafruit/Adafruit_MAX1704X/archive/refs/tags/1.0.3.zip
# renovate: datasource=github-tags depName=Adafruit SHTC3 packageName=adafruit/Adafruit_SHTC3
https://github.com/adafruit/Adafruit_SHTC3/archive/refs/tags/1.0.2.zip
# renovate: datasource=github-tags depName=Adafruit LPS2X packageName=adafruit/Adafruit_LPS2X
https://github.com/adafruit/Adafruit_LPS2X/archive/refs/tags/2.0.6.zip
# renovate: datasource=github-tags depName=Adafruit SHT31 packageName=adafruit/Adafruit_SHT31
@@ -230,7 +226,7 @@ lib_deps =
# renovate: datasource=github-tags depName=Sensirion I2C SFA3x packageName=sensirion/arduino-i2c-sfa3x
https://github.com/Sensirion/arduino-i2c-sfa3x/archive/refs/tags/1.0.0.zip
# renovate: datasource=github-tags depName=Sensirion I2C SCD30 packageName=sensirion/arduino-i2c-scd30
https://github.com/Sensirion/arduino-i2c-scd30/archive/1.1.1.zip
https://github.com/Sensirion/arduino-i2c-scd30/archive/refs/tags/1.0.0.zip
; Environmental sensors with BSEC2 (Bosch proprietary IAQ)
[environmental_extra]
+28 -102
View File
@@ -99,87 +99,48 @@ bool renameFile(const char *pathFrom, const char *pathTo)
#endif
}
#include <cstring>
#include <new>
#include <stdexcept>
#include <vector>
/**
* @brief Get the list of files in a directory.
*
* This function returns a list of files in a directory. The list includes the full path of each file.
* We can't use SPILOCK here because of recursion. Callers of this function should use SPILOCK.
*
* @param dirname The name of the directory.
* @param levels The number of levels of subdirectories to list.
* @return A vector of strings containing the full path of each file in the directory.
*/
std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels)
{
std::vector<meshtastic_FileInfo> filenames = {};
#ifdef FSCom
namespace
{
bool pathEndsWithDot(const char *path)
{
if (!path)
return false;
size_t length = strlen(path);
return length > 0 && path[length - 1] == '.';
}
bool copyFilePath(char *dest, size_t destSize, const char *path, bool *wasLimited)
{
if (!path || destSize == 0) {
if (wasLimited)
*wasLimited = true;
return false;
}
if (strlcpy(dest, path, destSize) >= destSize) {
if (wasLimited)
*wasLimited = true;
return false;
}
return true;
}
void collectFiles(const char *dirname, uint8_t levels, size_t maxCount, std::vector<meshtastic_FileInfo> &filenames,
bool *wasLimited)
{
if (!dirname)
return;
File root = FSCom.open(dirname, FILE_O_READ);
if (!root)
return;
if (!root.isDirectory()) {
root.close();
return;
}
return filenames;
if (!root.isDirectory())
return filenames;
File file = root.openNextFile();
// file.name()[0] check is a workaround for a bug in the Adafruit LittleFS nrf52 glue (see issue 4395)
while (file && file.name()[0]) {
if (filenames.size() >= maxCount) {
if (wasLimited)
*wasLimited = true;
file.close();
break;
}
const char *fileName = file.name();
if (file.isDirectory() && !pathEndsWithDot(fileName)) {
char pathBuffer[sizeof(((meshtastic_FileInfo *)nullptr)->file_name)] = {};
while (file) {
if (file.isDirectory() && !String(file.name()).endsWith(".")) {
if (levels) {
#ifdef ARCH_ESP32
const char *subDirPath = file.path();
std::vector<meshtastic_FileInfo> subDirFilenames = getFiles(file.path(), levels - 1);
#else
const char *subDirPath = fileName;
std::vector<meshtastic_FileInfo> subDirFilenames = getFiles(file.name(), levels - 1);
#endif
bool hasSubDirPath = copyFilePath(pathBuffer, sizeof(pathBuffer), subDirPath, wasLimited);
file.close();
if (levels && hasSubDirPath) {
collectFiles(pathBuffer, levels - 1, maxCount, filenames, wasLimited);
} else if (wasLimited) {
*wasLimited = true;
filenames.insert(filenames.end(), subDirFilenames.begin(), subDirFilenames.end());
file.close();
}
} else {
meshtastic_FileInfo fileInfo = {"", static_cast<uint32_t>(file.size())};
#ifdef ARCH_ESP32
bool hasFilePath = copyFilePath(fileInfo.file_name, sizeof(fileInfo.file_name), file.path(), wasLimited);
strcpy(fileInfo.file_name, file.path());
#else
bool hasFilePath = copyFilePath(fileInfo.file_name, sizeof(fileInfo.file_name), file.name(), wasLimited);
strcpy(fileInfo.file_name, file.name());
#endif
if (hasFilePath && !pathEndsWithDot(fileInfo.file_name)) {
if (!String(fileInfo.file_name).endsWith(".")) {
filenames.push_back(fileInfo);
}
file.close();
@@ -187,41 +148,6 @@ void collectFiles(const char *dirname, uint8_t levels, size_t maxCount, std::vec
file = root.openNextFile();
}
root.close();
}
} // namespace
#endif
// Callers must hold the SPI lock; recursion prevents taking it here.
std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels, size_t maxCount, bool *wasLimited)
{
std::vector<meshtastic_FileInfo> filenames = {};
if (wasLimited)
*wasLimited = false;
#ifdef FSCom
#if defined(__cpp_exceptions) || defined(__EXCEPTIONS)
size_t reservedCount = maxCount;
while (reservedCount > 0) {
try {
filenames.reserve(reservedCount);
break;
} catch (const std::bad_alloc &) {
reservedCount /= 2;
} catch (const std::length_error &) {
reservedCount /= 2;
}
}
if (reservedCount == 0) {
if (wasLimited)
*wasLimited = true;
return filenames;
}
if (reservedCount < maxCount) {
if (wasLimited)
*wasLimited = true;
maxCount = reservedCount;
}
#endif
collectFiles(dirname, levels, maxCount, filenames, wasLimited);
#endif
return filenames;
}
@@ -381,7 +307,7 @@ void fsInit()
*/
void setupSDCard()
{
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI) && !defined(HAS_SD_MMC)
concurrency::LockGuard g(spiLock);
SDHandler.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
if (!SD.begin(SDCARD_CS, SDHandler, SD_SPI_FREQUENCY)) {
@@ -409,4 +335,4 @@ void setupSDCard()
LOG_DEBUG("Total space: %lu MB", (uint32_t)(SD.totalBytes() / (1024 * 1024)));
LOG_DEBUG("Used space: %lu MB", (uint32_t)(SD.usedBytes() / (1024 * 1024)));
#endif
}
}
+2 -2
View File
@@ -52,7 +52,7 @@ void fsInit();
void fsListFiles();
bool copyFile(const char *from, const char *to);
bool renameFile(const char *pathFrom, const char *pathTo);
std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels, size_t maxCount = 64, bool *wasLimited = nullptr);
std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels);
void listDir(const char *dirname, uint8_t levels, bool del = false);
void rmDir(const char *dirname);
void setupSDCard();
void setupSDCard();
-1
View File
@@ -360,7 +360,6 @@ void MessageStore::clearAllMessages()
resetMessagePool();
#ifdef FSCom
concurrency::LockGuard guard(spiLock);
SafeFile f(filename.c_str(), false);
uint8_t count = 0;
f.write(&count, 1); // write "0 messages"
+166
View File
@@ -31,6 +31,10 @@
#include "input/LinuxInputImpl.h"
#endif
#ifdef HAS_ADS1115
#include <Adafruit_ADS1X15.h>
#endif
// Working USB detection for powered/charging states on the RAK platform
#ifdef NRF_APM
#include "nrfx_power.h"
@@ -616,6 +620,164 @@ class AnalogBatteryLevel : public HasBatteryLevel
static AnalogBatteryLevel analogLevel;
#ifdef HAS_ADS1115
#include "SPILock.h"
// AW35615 USB-C CC controller register constants (Arduino Wire, no ESP-IDF dependency)
static constexpr uint8_t AW35615_ADDR = 0x22;
static constexpr uint8_t AW35615_REG_DEVID = 0x01; // Device ID (probe)
static constexpr uint8_t AW35615_REG_ST1A = 0x3D; // STATUS1A: TOGSS[5:3]
static constexpr uint8_t AW35615_REG_ST0 = 0x40; // STATUS0: VBUSOK[7]
static constexpr uint8_t AW35615_VBUSOK = (1 << 7);
static constexpr uint8_t AW35615_TOGSS_MASK = 0x38;
static constexpr uint8_t AW35615_TOGSS_SHIFT = 3;
static constexpr uint8_t AW35615_TOGSS_SNK_CC1 = 5; // device is sink on CC1
static constexpr uint8_t AW35615_TOGSS_SNK_CC2 = 6; // device is sink on CC2
/**
* Battery level sensor using an ADS1115 16-bit ADC on I2C (address 0x48).
* Channel 0 is battery voltage through a ×2 resistive divider, so the
* measured voltage is multiplied by 2.0 to recover the true cell voltage.
* USB/charging state is read from the AW35615 USB-C CC controller (0x22)
* when present; falls back to a voltage threshold when not.
*/
class ADS1115BatteryLevel : public AnalogBatteryLevel
{
public:
bool init()
{
concurrency::LockGuard guard(spiLock);
// Wire is already configured (SDA=47, SCL=48) and the ADC-enable
// expander pin (pin 15) is already HIGH from initVariant().
if (!_ads.begin(ADS1115_ADDR, &Wire)) {
LOG_WARN("ADS1115 not found on I2C bus — battery sensor unavailable");
return false;
}
_ads.setGain(GAIN_TWO); // ±2.048 V FSR matches the voltage-divider ratio
initialized = true;
LOG_INFO("[ADS1115] battery sensor initialized");
// Probe and configure AW35615 USB-C CC controller for VBUS / charging detection
uint8_t devId = 0;
if (aw35615Read(AW35615_REG_DEVID, devId)) {
aw35615_initialized = true;
LOG_INFO("[AW35615] found at 0x22, DEVICE_ID=0x%02x", devId);
// Enable all internal power blocks
aw35615Write(0x0B, 0x0F); // POWER = 0x0F
// Put Rd (pull-down) resistors on both CC pins → device acts as USB-C sink
aw35615Write(0x02, 0x03); // SWITCHES0: PDWN1|PDWN2
// Enable sink toggle so the chip detects CC attachment and sets VBUSOK
aw35615Write(0x08, 0x05); // CONTROL2: MODE=SINK (2<<1) | TOGGLE=1
} else {
LOG_WARN("[AW35615] not found at 0x22, falling back to voltage threshold for USB detection");
}
return true;
}
virtual uint16_t getBattVoltage() override
{
if (!initialized) {
return 0;
}
concurrency::LockGuard guard(spiLock);
static constexpr uint32_t MIN_READ_INTERVAL_MS = 30000;
if (!initial_read_done || !Throttle::isWithinTimespanMs(last_read_ms, MIN_READ_INTERVAL_MS)) {
last_read_ms = millis();
float sum = 0;
for (int i = 0; i < SAMPLE_COUNT; i++) {
int16_t raw = _ads.readADC_SingleEnded(0);
float v = _ads.computeVolts(raw);
sum += v;
}
// Channel 0 sees battery/2 (resistive divider); multiply back, convert to mV
float v = (sum / SAMPLE_COUNT) * 2.0f * 1000.0f;
if (!initial_read_done) {
cached_mv = (uint16_t)v;
initial_read_done = true;
} else {
cached_mv = (uint16_t)(cached_mv + (v - cached_mv) * 0.5f);
}
}
return cached_mv;
}
virtual bool isVbusIn() override
{
concurrency::LockGuard guard(spiLock);
if (aw35615_initialized) {
uint8_t st0 = 0;
if (aw35615Read(AW35615_REG_ST0, st0)) {
bool vbus = (st0 & AW35615_VBUSOK) != 0;
//LOG_DEBUG("[AW35615] STATUS0=0x%02x VBUSOK=%d", st0, (int)vbus);
return vbus;
}
}
// Fallback: CV-phase voltage threshold
static constexpr uint16_t CHARGING_THRESH_MV = (4190 + 10) * NUM_CELLS;
return getBattVoltage() > CHARGING_THRESH_MV;
}
virtual bool isCharging() override
{
if (!isBatteryConnect())
return false;
if (aw35615_initialized) {
concurrency::LockGuard guard(spiLock);
uint8_t st1a = 0;
if (aw35615Read(AW35615_REG_ST1A, st1a)) {
uint8_t togss = (st1a & AW35615_TOGSS_MASK) >> AW35615_TOGSS_SHIFT;
bool charging = (togss == AW35615_TOGSS_SNK_CC1 || togss == AW35615_TOGSS_SNK_CC2);
//LOG_DEBUG("[AW35615] STATUS1A=0x%02x TOGSS=%d charging=%d", st1a, (int)togss, (int)charging);
return charging;
}
}
return isVbusIn();
}
private:
// arduino Wire helpers for AW35615 register access
bool aw35615Read(uint8_t reg, uint8_t &val)
{
Wire.beginTransmission(AW35615_ADDR);
Wire.write(reg);
if (Wire.endTransmission(false) != 0)
return false;
if (Wire.requestFrom(AW35615_ADDR, (uint8_t)1) != 1)
return false;
val = Wire.read();
return true;
}
bool aw35615Write(uint8_t reg, uint8_t val)
{
Wire.beginTransmission(AW35615_ADDR);
Wire.write(reg);
Wire.write(val);
return Wire.endTransmission() == 0;
}
static constexpr uint8_t SAMPLE_COUNT = 3;
Adafruit_ADS1115 _ads;
bool initialized = false;
bool aw35615_initialized = false;
bool initial_read_done = false;
uint16_t cached_mv = 0;
uint32_t last_read_ms = 0;
};
static ADS1115BatteryLevel ads1115BattLevel;
bool Power::ads1115Init()
{
if (ads1115BattLevel.init()) {
batteryLevel = &ads1115BattLevel;
return true;
}
else
return false;
}
#endif // HAS_ADS1115
Power::Power() : OSThread("Power")
{
statusHandler = {};
@@ -725,6 +887,10 @@ bool Power::setup()
found = true;
} else if (meshSolarInit()) {
found = true;
#ifdef HAS_ADS1115
} else if (ads1115Init()) {
found = true;
#endif
} else if (analogInit()) {
found = true;
} else {
+9 -9
View File
@@ -31,11 +31,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#endif
#if __has_include("SensorRtcHelper.hpp")
#include "SensorRtcHelper.hpp"
// SensorLib defines isBitSet as a macro; undefine it here to avoid conflicts
// with the SparkFun MMC5983MA library, which has a class method of the same name.
#ifdef isBitSet
#undef isBitSet
#endif
#endif
/* Offer chance for variant-specific defines */
@@ -243,7 +238,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define QMI8658_ADDR 0x6B
#define QMC5883L_ADDR 0x0D
#define HMC5883L_ADDR 0x1E
#define MMC5983MA_ADDR 0x30
#define SHTC3_ADDR 0x70
#define LPS22HB_ADDR 0x5C
#define LPS22HB_ADDR_ALT 0x5D
@@ -293,14 +287,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define DA217_ADDR 0x26
#define BMI270_ADDR 0x68
#define BMI270_ADDR_ALT 0x69
#define ICM42607P_ADDR 0x68
#define ICM42607P_ADDR_ALT 0x69
// -----------------------------------------------------------------------------
// LED
// -----------------------------------------------------------------------------
#define NCP5623_ADDR 0x38
#define LP5562_ADDR 0x30
#define LP5814_ADDR 0x2C
// -----------------------------------------------------------------------------
// Audio Codec
// -----------------------------------------------------------------------------
#define ES8311_ADDR 0x18 // same address as MCP9808_ADDR / STK8BXX_ADDR / LIS3DH_ADDR
#define ES7243E_ADDR 0x14
// -----------------------------------------------------------------------------
// Security
@@ -315,10 +314,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// -----------------------------------------------------------------------------
// Touchscreen
// -----------------------------------------------------------------------------
#define FT6336U_ADDR 0x48
#define FT6336U_ADDR 0x48 // same address as ADS1115
#define CST328_ADDR 0x1A // same address as CST226SE
#define CHSC6X_ADDR 0x2E
#define CST226SE_ADDR_ALT 0x5A
#define GT911_ADDR 0x5D // same address as SFA30_ADDR / LPS22HB_ADDR_ALT
// -----------------------------------------------------------------------------
// RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected)
+2 -9
View File
@@ -37,15 +37,8 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
{
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX,
ICM20948, QMA6100P, BMM150, BMI270, ICM42607P};
return firstOfOrNONE(11, types);
}
ScanI2C::FoundDevice ScanI2C::firstMagnetometer() const
{
ScanI2C::DeviceType types[] = {MMC5983MA};
return firstOfOrNONE(1, types);
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948, QMA6100P, BMM150, BMI270};
return firstOfOrNONE(10, types);
}
ScanI2C::FoundDevice ScanI2C::firstAQI() const
+5 -4
View File
@@ -41,7 +41,6 @@ class ScanI2C
QMI8658,
QMC5883L,
HMC5883L,
MMC5983MA,
PMSA003I,
QMA6100P,
MPU6050,
@@ -49,6 +48,7 @@ class ScanI2C
BMA423,
BQ24295,
LSM6DS3,
AW35615,
TCA9535,
TCA9555,
VEML7700,
@@ -66,7 +66,6 @@ class ScanI2C
FT6336U,
STK8BAXX,
ICM20948,
ICM42607P,
SCD4X,
MAX30102,
TPS65233,
@@ -99,6 +98,10 @@ class ScanI2C
CW2015,
SCD30,
ADS1115,
GT911,
LP5814,
ES8311,
ES7243E,
} DeviceType;
// typedef uint8_t DeviceAddress;
@@ -151,8 +154,6 @@ class ScanI2C
FoundDevice firstAccelerometer() const;
FoundDevice firstMagnetometer() const;
FoundDevice firstAQI() const;
FoundDevice firstRGBLED() const;
+58 -29
View File
@@ -359,7 +359,17 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
#ifdef HAS_NCP5623
SCAN_SIMPLE_CASE(NCP5623_ADDR, NCP5623, "NCP5623", (uint8_t)addr.address);
#endif
case XPOWERS_AXP192_AXP2101_ADDRESS:
#ifdef HAS_LP5562
SCAN_SIMPLE_CASE(LP5562_ADDR, LP5562, "LP5562", (uint8_t)addr.address);
#endif
#ifdef HAS_LP5814
SCAN_SIMPLE_CASE(LP5814_ADDR, LP5814, "LP5814", (uint8_t)addr.address);
#endif
#ifdef HAS_ES7243E
SCAN_SIMPLE_CASE(ES7243E_ADDR, ES7243E, "ES7243E", (uint8_t)addr.address);
#endif
case XPOWERS_AXP192_AXP2101_ADDRESS:
#ifndef SEEED_WIO_TRACKER_L2 // false positive
// Do we have the axp2101/192 or the TCA8418
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x90), 1);
if (registerValue == 0x0) {
@@ -369,6 +379,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
logFoundDevice("AXP192/AXP2101", (uint8_t)addr.address);
type = PMU_AXP192_AXP2101;
}
#endif
break;
case BME_ADDR:
case BME_ADDR_ALTERNATE:
@@ -476,6 +487,13 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
// We need to check for STK8BAXX first, since register 0x07 is new data flag for the z-axis and can produce some
// weird result. and register 0x00 doesn't seems to be colliding with MCP9808 and LIS3DH chips.
{
// Check register 0xFD for 0x83 to ID ES8311 audio codec.
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFD), 1);
if (registerValue == 0x83) {
type = ES8311;
logFoundDevice("ES8311", (uint8_t)addr.address);
break;
}
#ifdef HAS_STK8XXX
// Check register 0x00 for 0x8700 response to ID STK8BA53 chip.
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 2);
@@ -532,6 +550,22 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
break;
case LPS22HB_ADDR_ALT:
// GT911 touchscreen: product ID register 0x8140 returns "911"
{
uint8_t gt911_reg[] = {0x81, 0x40};
uint8_t gt911_buf[4] = {0};
i2cBus->beginTransmission(addr.address);
i2cBus->write(gt911_reg, 2);
if (i2cBus->endTransmission() == 0) {
i2cBus->requestFrom((int)addr.address, 4);
i2cBus->readBytes(gt911_buf, 4);
if (gt911_buf[0] == '9' && gt911_buf[1] == '1' && gt911_buf[2] == '1') {
type = GT911;
logFoundDevice("GT911", (uint8_t)addr.address);
break;
}
}
}
// SFA30 detection: send 2-byte command 0xD060 (Get Device Marking) and check for 48-byte response
if (i2cCommandResponseLength(addr, 0xD060, 48)) {
type = SFA30;
@@ -578,18 +612,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
#else
SCAN_SIMPLE_CASE(PMSA003I_ADDR, PMSA003I, "PMSA003I", (uint8_t)addr.address)
#endif
case MMC5983MA_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x2F), 1);
if (registerValue == 0x30) {
type = MMC5983MA;
logFoundDevice("MMC5983MA", (uint8_t)addr.address);
#ifdef HAS_LP5562
} else {
type = LP5562;
logFoundDevice("LP5562", (uint8_t)addr.address);
#endif
}
break;
case BMA423_ADDR: // this can also be LIS3DH_ADDR_ALT
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 2);
if (registerValue == 0x3300 || registerValue == 0x3333) { // RAK4631 WisBlock has LIS3DH register at 0x3333
@@ -600,9 +622,18 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
logFoundDevice("BMA423", (uint8_t)addr.address);
}
break;
case RAK120353_ADDR: { // AW35615 USB-C CC controller — must be checked before
// RAK120353_ADDR which shares 0x22 but is a TCA9535 variant
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 1);
if ((registerValue & 0xF0) == 0x90) { // DEVICE_ID upper nibble = 0x9 for AW35615
type = AW35615;
logFoundDevice("AW35615", (uint8_t)addr.address);
break;
}
// Fall through to TCA9535/RAK check
}
case TCA9535_ADDR:
case RAK120352_ADDR:
case RAK120353_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x02), 1);
if (registerValue == addr.address) { // RAK12035 returns its I2C address at 0x02 (eg 0x20)
type = RAK12035;
@@ -744,8 +775,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
break;
case ICM20948_ADDR: // same as BMX160_ADDR, BMI270_ADDR_ALT, ICM42607P_ADDR_ALT, and SEN5X_ADDR
case ICM20948_ADDR_ALT: // same as MPU6050_ADDR, BMI270_ADDR, and ICM42607P_ADDR
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;
@@ -765,12 +796,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
logFoundDevice("BMX160", (uint8_t)addr.address);
break;
} else {
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x75), 1); // WHO_AM_I
if (registerValue == 0x60) {
type = ICM42607P;
logFoundDevice("ICM-42607-P", (uint8_t)addr.address);
break;
}
String prod = "";
prod = readSEN5xProductName(i2cBus, addr.address);
if (prod.startsWith("SEN55")) {
@@ -812,6 +837,17 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
break;
case 0x48: {
// Check for ADS1115 FIRST — the SE050 probe below writes 5 bytes
// to the device, which corrupts the ADS1115 Lo_thresh register and
// leaves its pointer off register 0x01, making the later config-
// register read return 0xC000 instead of the expected 0x8583.
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 2);
if (registerValue == 0x8583 || registerValue == 0x8580) {
type = ADS1115;
logFoundDevice("ADS1115 ADC", (uint8_t)addr.address);
break;
}
i2cBus->beginTransmission(addr.address);
uint8_t getInfo[] = {0x5A, 0xC0, 0x00, 0xFF, 0xFC};
uint8_t expectedInfo[] = {0xa5, 0xE0, 0x00, 0x3F, 0x19};
@@ -826,13 +862,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
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;
+3 -5
View File
@@ -832,10 +832,7 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
switch (newState) {
case GPS_ACTIVE:
case GPS_IDLE:
if (oldState == GPS_ACTIVE)
break;
gotTime = false;
if (oldState == GPS_IDLE) // If hardware already awake, no changes needed
if (oldState == GPS_ACTIVE || oldState == GPS_IDLE) // If hardware already awake, no changes needed
break;
if (oldState != GPS_ACTIVE && oldState != GPS_IDLE) // If hardware just waking now, clear buffer
clearBuffer();
@@ -1145,7 +1142,8 @@ int32_t GPS::runOnce()
// if gps_update_interval is <=10s, GPS never goes off, so we treat that differently
uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval);
// 1. Got a time for the first time this cycle
// 1. Got a time for the first time
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time
gotTime = true;
}
-1
View File
@@ -163,7 +163,6 @@ class GPS : private concurrency::OSThread
uint32_t lastChecksumFailCount = 0;
uint8_t currentStep = 0;
int32_t currentDelay = 2000;
bool gotTime = false;
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
// (20210908) TinyGps++ can only read the GPGSA "FIX TYPE" field
+14 -89
View File
@@ -31,63 +31,13 @@ static uint32_t
timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time
static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock
#ifdef PIO_UNIT_TESTING
// Test seam: unit tests can inject a fake system clock (e.g. the uptime seconds that
// gettimeofday() returns on boards without a real RTC, like RP2040) and force readFromRTC()
// down the no-hardware-RTC fallback even when a hardware-RTC branch is compiled in.
static bool hasMockSystemTime = false;
static bool forceSystemTimeFallback = false;
static struct timeval mockSystemTime = {};
#endif
// Reads the platform system clock (or the injected mock during unit tests). Used only by the
// no-hardware-RTC fallback below, so it may be unused on builds with a hardware RTC.
[[maybe_unused]] static bool readSystemTime(struct timeval *tv)
{
#ifdef PIO_UNIT_TESTING
if (hasMockSystemTime) {
*tv = mockSystemTime;
return true;
}
#endif
return gettimeofday(tv, NULL) == 0;
}
// Seeds the clock from the system time on boards without a hardware RTC. gettimeofday() can
// return uptime rather than wall-clock time there (e.g. RP2040), so only adopt it when we have
// nothing better yet -- never clobber a higher-quality GPS/NTP/phone source (issue #9828).
[[maybe_unused]] static RTCSetResult readFromSystemTimeFallback()
{
struct timeval tv;
if (readSystemTime(&tv)) {
uint32_t now = millis();
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
if (currentQuality == RTCQualityNone) {
LOG_DEBUG("Seed time from system clock: %lu", (unsigned long)printableEpoch);
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
} else {
LOG_DEBUG("Ignore system clock fallback (%lu); current RTC quality is %s", (unsigned long)printableEpoch,
RtcName(currentQuality));
}
return RTCSetResultSuccess;
}
return RTCSetResultNotSet;
}
/**
* Reads date/time from the RTC module (or system-time fallback) and seeds internal timekeeping.
* @return RTCSetResultSuccess if a time source was read successfully (even if an existing higher-quality time is retained).
* Reads the current date and time from the RTC module and updates the system time.
* @return True if the RTC was successfully read and the system time was updated, false otherwise.
*/
RTCSetResult readFromRTC()
{
#ifdef PIO_UNIT_TESTING
if (forceSystemTimeFallback) {
return readFromSystemTimeFallback();
}
#endif
[[maybe_unused]] struct timeval tv; /* btw settimeofday() is helpful here too*/
struct timeval tv; /* btw settimeofday() is helpful here too*/
#ifdef RV3028_RTC
if (rtc_found.address == RV3028_RTC) {
uint32_t now = millis();
@@ -212,7 +162,14 @@ RTCSetResult readFromRTC()
}
}
#else
return readFromSystemTimeFallback();
if (!gettimeofday(&tv, NULL)) {
uint32_t now = millis();
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
LOG_DEBUG("Read RTC time as %ld", printableEpoch);
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
return RTCSetResultSuccess;
}
#endif
return RTCSetResultNotSet;
}
@@ -262,8 +219,8 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
} else if (q == RTCQualityGPS) {
shouldSet = true;
LOG_DEBUG("Reapply GPS time: %ld secs", printableEpoch);
} else if (q == RTCQualityNTP && !Throttle::isWithinTimespanMs(lastSetMsec, (30 * 60 * 1000UL))) {
// Every 30 minutes we will slam in a new NTP or Phone GPS / NTP time, to correct for local RTC clock drift
} else if (q == RTCQualityNTP && !Throttle::isWithinTimespanMs(lastSetMsec, (12 * 60 * 60 * 1000UL))) {
// Every 12 hrs we will slam in a new NTP or Phone GPS / NTP time, to correct for local RTC clock drift
shouldSet = true;
LOG_DEBUG("Reapply external time to correct clock drift %ld secs", printableEpoch);
} else {
@@ -335,7 +292,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
LOG_WARN("Failed to set time for RX8130CE");
}
}
#elif defined(ARCH_ESP32) || defined(ARCH_RP2040)
#elif defined(ARCH_ESP32)
settimeofday(tv, NULL);
#endif
@@ -466,38 +423,6 @@ void setBootRelativeTimeForUnitTest(uint32_t secondsSinceBoot)
lastSetFromPhoneNtpOrGps = 0;
lastTimeValidationWarning = 0;
}
void clearRTCSystemTimeForTests()
{
hasMockSystemTime = false;
mockSystemTime = {};
}
void setRTCSystemTimeForTests(const struct timeval *tv)
{
if (tv == NULL) {
clearRTCSystemTimeForTests();
return;
}
mockSystemTime = *tv;
hasMockSystemTime = true;
}
void setReadFromRTCUseSystemTimeForTests(bool enabled)
{
forceSystemTimeFallback = enabled;
}
void resetRTCStateForTests()
{
currentQuality = RTCQualityNone;
timeStartMsec = 0;
zeroOffsetSecs = 0;
lastSetFromPhoneNtpOrGps = 0;
lastTimeValidationWarning = 0;
setReadFromRTCUseSystemTimeForTests(false);
clearRTCSystemTimeForTests();
}
#endif
time_t gm_mktime(const struct tm *tm)
-4
View File
@@ -56,10 +56,6 @@ RTCSetResult readFromRTC();
#ifdef PIO_UNIT_TESTING
void setBootRelativeTimeForUnitTest(uint32_t secondsSinceBoot);
void resetRTCStateForTests();
void setRTCSystemTimeForTests(const struct timeval *tv);
void clearRTCSystemTimeForTests();
void setReadFromRTCUseSystemTimeForTests(bool enabled);
#endif
time_t gm_mktime(const struct tm *tm);
+5 -97
View File
@@ -60,7 +60,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "main.h"
#include "mesh-pb-constants.h"
#include "mesh/Channels.h"
#include "mesh/Default.h"
#include "mesh/generated/meshtastic/deviceonly.pb.h"
#include "modules/ExternalNotificationModule.h"
#include "modules/TextMessageModule.h"
@@ -99,7 +98,6 @@ namespace graphics
// This means the *visible* area (sh1106 can address 132, but shows 128 for example)
#define IDLE_FRAMERATE 1 // in fps
#define COMPASS_ACTIVE_FRAMERATE 20
// DEBUG
#define NUM_EXTRA_FRAMES 3 // text message and debug frame
@@ -137,60 +135,6 @@ static bool heartbeat = false;
extern bool hasUnreadMessage;
static inline float wrapHeading360(float heading)
{
if (heading < 0.0f) {
heading += 360.0f;
} else if (heading >= 360.0f) {
heading -= 360.0f;
}
return heading;
}
void Screen::setHeading(float heading)
{
const float wrappedHeading = wrapHeading360(heading);
if (!hasCompass) {
hasCompass = true;
compassHeading = wrappedHeading;
return;
}
// Interpolate using shortest-path angular delta to avoid jumps around 0/360.
float delta = wrappedHeading - compassHeading;
if (delta > 180.0f) {
delta -= 360.0f;
} else if (delta < -180.0f) {
delta += 360.0f;
}
// Adaptive filtering:
// - Strong damping for tiny deltas (jitter)
// - Faster response for larger turns
const float absDelta = (delta >= 0.0f) ? delta : -delta;
if (absDelta < 1.0f) {
return;
}
float alpha = 0.35f;
if (absDelta > 25.0f) {
alpha = 0.85f;
} else if (absDelta > 10.0f) {
alpha = 0.65f;
}
float step = delta * alpha;
const float maxStep = 12.0f;
if (step > maxStep) {
step = maxStep;
} else if (step < -maxStep) {
step = -maxStep;
}
compassHeading = wrapHeading360(compassHeading + step);
}
// ==============================
// Overlay Alert Banner Renderer
// ==============================
@@ -328,25 +272,10 @@ static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int
float Screen::estimatedHeading(double lat, double lon)
{
static double oldLat, oldLon;
static float b = -1.0f;
static uint32_t lastHeadingAtMs = 0;
const uint32_t now = millis();
const uint32_t gpsUpdateIntervalSecs =
Default::getConfiguredOrDefault(config.position.gps_update_interval, default_gps_update_interval);
uint32_t effectiveUpdateIntervalSecs = gpsUpdateIntervalSecs;
if (config.position.position_broadcast_smart_enabled) {
const uint32_t smartMinIntervalSecs = Default::getConfiguredOrDefault(
config.position.broadcast_smart_minimum_interval_secs, default_broadcast_smart_minimum_interval_secs);
if (smartMinIntervalSecs > effectiveUpdateIntervalSecs) {
effectiveUpdateIntervalSecs = smartMinIntervalSecs;
}
}
// Two expected update windows; keep arithmetic 32-bit to avoid pulling in larger 64-bit helpers.
const uint32_t headingStaleMs =
(effectiveUpdateIntervalSecs > (UINT32_MAX / 2000U)) ? UINT32_MAX : (effectiveUpdateIntervalSecs * 2000U);
static float b;
if (oldLat == 0) {
// Need at least two position points before we can infer heading.
// just prepare for next time
oldLat = lat;
oldLon = lon;
@@ -354,20 +283,12 @@ float Screen::estimatedHeading(double lat, double lon)
}
float d = GeoCoord::latLongToMeter(oldLat, oldLon, lat, lon);
if (d < 10) { // haven't moved enough, keep previous heading (invalid until first real movement)
if (lastHeadingAtMs != 0 && (now - lastHeadingAtMs) >= headingStaleMs) {
// Heading is stale after prolonged no-movement; force reacquire.
b = -1.0f;
oldLat = lat;
oldLon = lon;
}
if (d < 10) // haven't moved enough, just keep current bearing
return b;
}
b = GeoCoord::bearing(oldLat, oldLon, lat, lon) * RAD_TO_DEG;
oldLat = lat;
oldLon = lon;
lastHeadingAtMs = now;
return b;
}
@@ -1007,22 +928,9 @@ int32_t Screen::runOnce()
// but we should only call setTargetFPS when framestate changes, because
// otherwise that breaks animations.
uint32_t desiredFramerate = IDLE_FRAMERATE;
#if HAS_GPS && !defined(USE_EINK)
if (showingNormalScreen && hasCompass) {
const uint8_t currentFrame = ui->getUiState()->currentFrame;
if ((framesetInfo.positions.gps != 255 && currentFrame == framesetInfo.positions.gps) ||
(framesetInfo.positions.waypoint != 255 && currentFrame == framesetInfo.positions.waypoint) ||
(framesetInfo.positions.firstFavorite != 255 && currentFrame >= framesetInfo.positions.firstFavorite &&
currentFrame <= framesetInfo.positions.lastFavorite)) {
desiredFramerate = COMPASS_ACTIVE_FRAMERATE;
}
}
#endif
if (targetFramerate != desiredFramerate && ui->getUiState()->frameState == FIXED) {
if (targetFramerate != IDLE_FRAMERATE && ui->getUiState()->frameState == FIXED) {
// oldFrameState = ui->getUiState()->frameState;
targetFramerate = desiredFramerate;
targetFramerate = IDLE_FRAMERATE;
ui->setTargetFPS(targetFramerate);
forceDisplay();
+7 -3
View File
@@ -330,11 +330,15 @@ class Screen : public concurrency::OSThread
// Function to allow the AccelerometerThread to set the heading if a sensor provides it
// Mutex needed?
void setHeading(float heading);
void setHeading(long _heading)
{
hasCompass = true;
compassHeading = fmod(_heading, 360);
}
bool hasHeading() { return hasCompass; }
float getHeading() { return compassHeading; }
long getHeading() { return compassHeading; }
void setEndCalibration(uint32_t _endCalibrationAt) { endCalibrationAt = _endCalibrationAt; }
uint32_t getEndCalibration() { return endCalibrationAt; }
@@ -788,4 +792,4 @@ extern std::vector<std::string> functionSymbol;
extern std::string functionSymbolString;
extern graphics::Screen *screen;
#endif
#endif
+8 -10
View File
@@ -1359,8 +1359,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) && !defined(HELTEC_MESH_NODE_T096) && \
!defined(HELTEC_MESH_NODE_T1)
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) && !defined(HELTEC_MESH_NODE_T096)
tft->wakeup();
tft->powerSaveOff();
#endif
@@ -1371,7 +1370,7 @@ void TFTDisplay::sendCommand(uint8_t com)
#ifdef UNPHONE
unphone.backlight(true); // using unPhone library
#endif
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096) || defined(HELTEC_MESH_NODE_T1)
#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);
@@ -1387,8 +1386,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) && !defined(HELTEC_MESH_NODE_T096) && \
!defined(HELTEC_MESH_NODE_T1)
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) && !defined(HELTEC_MESH_NODE_T096)
tft->sleep();
tft->powerSaveOn();
#endif
@@ -1399,7 +1397,7 @@ void TFTDisplay::sendCommand(uint8_t com)
#ifdef UNPHONE
unphone.backlight(false); // using unPhone library
#endif
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096) || defined(HELTEC_MESH_NODE_T1)
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096)
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR)
tft->setBrightness(0);
#endif
@@ -1414,7 +1412,7 @@ void TFTDisplay::sendCommand(uint8_t com)
void TFTDisplay::setDisplayBrightness(uint8_t _brightness)
{
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096) || defined(HELTEC_MESH_NODE_T1)
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096)
// todo
#elif !defined(HACKADAY_COMMUNICATOR)
tft->setBrightness(_brightness);
@@ -1434,7 +1432,7 @@ bool TFTDisplay::hasTouch(void)
{
#ifdef RAK14014
return true;
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096) && !defined(HELTEC_MESH_NODE_T1)
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096)
return tft->touch() != nullptr;
#else
return false;
@@ -1453,7 +1451,7 @@ bool TFTDisplay::getTouch(int16_t *x, int16_t *y)
} else {
return false;
}
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096) && !defined(HELTEC_MESH_NODE_T1)
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096)
return tft->getTouch(x, y);
#else
return false;
@@ -1470,7 +1468,7 @@ bool TFTDisplay::connect()
{
concurrency::LockGuard g(spiLock);
LOG_INFO("Do TFT init");
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096) || defined(HELTEC_MESH_NODE_T1)
#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 */);
+18 -58
View File
@@ -1,6 +1,10 @@
#include "configuration.h"
#if HAS_SCREEN
#include "CompassRenderer.h"
#include "NodeDB.h"
#include "UIRenderer.h"
#include "configuration.h"
#include "gps/GeoCoord.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
#include <cmath>
@@ -17,8 +21,8 @@ struct Point {
void rotate(float angle)
{
float cos_a = cosf(angle);
float sin_a = sinf(angle);
float cos_a = cos(angle);
float sin_a = sin(angle);
float new_x = x * cos_a - y * sin_a;
float new_y = x * sin_a + y * cos_a;
x = new_x;
@@ -47,30 +51,21 @@ void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY,
if (currentResolution == ScreenResolution::High) {
radius += 4;
}
float northX = 0.0f;
float northY = -radius;
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) {
const float c = cosf(-myHeading);
const float s = sinf(-myHeading);
const float rx = northX * c - northY * s;
const float ry = northX * s + northY * c;
northX = rx;
northY = ry;
}
northX += compassX;
northY += compassY;
Point north(0, -radius);
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING)
north.rotate(-myHeading);
north.translate(compassX, compassY);
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setColor(BLACK);
const int16_t nLabelWidth = display->getStringWidth("N");
if (currentResolution == ScreenResolution::High) {
display->fillRect(northX - 8, northY - 1, nLabelWidth + 3, FONT_HEIGHT_SMALL - 6);
display->fillRect(north.x - 8, north.y - 1, display->getStringWidth("N") + 3, FONT_HEIGHT_SMALL - 6);
} else {
display->fillRect(northX - 4, northY - 1, nLabelWidth + 2, FONT_HEIGHT_SMALL - 6);
display->fillRect(north.x - 4, north.y - 1, display->getStringWidth("N") + 2, FONT_HEIGHT_SMALL - 6);
}
display->setColor(WHITE);
display->drawString(northX, northY - 3, "N");
display->drawString(north.x, north.y - 3, "N");
}
void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian)
@@ -118,46 +113,11 @@ void drawArrowToNode(OLEDDisplay *display, int16_t x, int16_t y, int16_t size, f
display->fillTriangle(tip.x, tip.y, right.x, right.y, tail.x, tail.y);
}
bool getHeadingRadians(double lat, double lon, float &headingRadian)
float estimatedHeading(double lat, double lon)
{
headingRadian = 0.0f;
if (uiconfig.compass_mode == meshtastic_CompassMode_FREEZE_HEADING)
return true;
if (!screen)
return false;
if (screen->hasHeading()) {
headingRadian = screen->getHeading() * DEG_TO_RAD;
return true;
}
const float estimatedHeadingDeg = screen->estimatedHeading(lat, lon);
if (!(estimatedHeadingDeg >= 0.0f))
return false;
headingRadian = estimatedHeadingDeg * DEG_TO_RAD;
return true;
}
float adjustBearingForCompassMode(float bearingRadian, float headingRadian)
{
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING)
return bearingRadian - headingRadian;
return bearingRadian;
}
float radiansToDegrees360(float angleRadian)
{
constexpr float fullTurnDeg = 360.0f;
float degrees = angleRadian * RAD_TO_DEG;
if (degrees < 0.0f)
degrees += fullTurnDeg;
else if (degrees >= fullTurnDeg)
degrees -= fullTurnDeg;
return degrees;
// Simple magnetic declination estimation
// This is a very basic implementation - the original might be more sophisticated
return 0.0f; // Return 0 for now, indicating no heading available
}
uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight)
@@ -177,4 +137,4 @@ uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight)
} // namespace CompassRenderer
} // namespace graphics
#endif
#endif
+2 -3
View File
@@ -1,6 +1,7 @@
#pragma once
#include "graphics/Screen.h"
#include "mesh/generated/meshtastic/mesh.pb.h"
#include <OLEDDisplay.h>
#include <OLEDDisplayUi.h>
@@ -24,9 +25,7 @@ void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, u
void drawArrowToNode(OLEDDisplay *display, int16_t x, int16_t y, int16_t size, float bearing);
// Navigation and location functions
bool getHeadingRadians(double lat, double lon, float &headingRadian);
float adjustBearingForCompassMode(float bearingRadian, float headingRadian);
float radiansToDegrees360(float angleRadian);
float estimatedHeading(double lat, double lon);
uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight);
} // namespace CompassRenderer
-5
View File
@@ -1256,11 +1256,6 @@ void menuHandler::positionBaseMenu()
if (accelerometerThread) {
accelerometerThread->calibrate(30);
}
#endif
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && !MESHTASTIC_EXCLUDE_MAGNETOMETER
if (magnetometerThread) {
magnetometerThread->calibrate(30);
}
#endif
break;
case PositionAction::GPSSmartPosition:
+31 -40
View File
@@ -373,13 +373,14 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
}
}
const char *distanceLabel = (strlen(distStr) > 0) ? distStr : "?";
int offset = (currentResolution == ScreenResolution::High)
? (isLeftCol ? 7 : 10) // Offset for Wide Screens (Left Column:Right Column)
: (isLeftCol ? 4 : 7); // Offset for Narrow Screens (Left Column:Right Column)
int rightEdge = x + columnWidth - offset;
int textWidth = display->getStringWidth(distanceLabel);
display->drawString(rightEdge - textWidth, y, distanceLabel);
if (strlen(distStr) > 0) {
int offset = (currentResolution == ScreenResolution::High)
? (isLeftCol ? 7 : 10) // Offset for Wide Screens (Left Column:Right Column)
: (isLeftCol ? 4 : 7); // Offset for Narrow Screens (Left Column:Right Column)
int rightEdge = x + columnWidth - offset;
int textWidth = display->getStringWidth(distStr);
display->drawString(rightEdge - textWidth, y, distStr);
}
}
void drawEntryDynamic_Nodes(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
@@ -430,8 +431,8 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
}
}
void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth,
float myHeadingRadian, double userLat, double userLon)
void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth, float myHeading,
double userLat, double userLon)
{
if (!nodeDB->hasValidPosition(node))
return;
@@ -445,11 +446,11 @@ void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
double nodeLat = node->position.latitude_i * 1e-7;
double nodeLon = node->position.longitude_i * 1e-7;
float bearing = GeoCoord::bearing(userLat, userLon, nodeLat, nodeLon);
float relativeBearing = CompassRenderer::adjustBearingForCompassMode(bearing, myHeadingRadian);
float relativeBearingDeg = CompassRenderer::radiansToDegrees360(relativeBearing);
float bearingToNode = RAD_TO_DEG * bearing;
float relativeBearing = fmod((bearingToNode - myHeading + 360), 360);
// Shrink size by 2px
int size = FONT_HEIGHT_SMALL - 5;
CompassRenderer::drawArrowToNode(display, centerX, centerY, size, relativeBearingDeg);
CompassRenderer::drawArrowToNode(display, centerX, centerY, size, relativeBearing);
/*
float angle = relativeBearing * DEG_TO_RAD;
float halfSize = size / 2.0;
@@ -479,27 +480,12 @@ void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
*/
}
void drawCompassUnknown(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth, float, double,
double)
{
if (!nodeDB->hasValidPosition(node))
return;
bool isLeftCol = (x < SCREEN_WIDTH / 2);
int arrowXOffset = (currentResolution == ScreenResolution::High) ? (isLeftCol ? 22 : 24) : (isLeftCol ? 12 : 18);
int centerX = x + columnWidth - arrowXOffset;
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->drawString(centerX, y, "?");
}
// =============================
// Main Screen Functions
// =============================
void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *title,
EntryRenderer renderer, NodeExtrasRenderer extras, float headingRadian, double lat, double lon)
EntryRenderer renderer, NodeExtrasRenderer extras, float heading, double lat, double lon)
{
const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1;
const int rowYOffset = FONT_HEIGHT_SMALL - 3;
@@ -584,7 +570,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
renderer(display, node, xPos, yPos, columnWidth);
if (extras)
extras(display, node, xPos, yPos, columnWidth, headingRadian, lat, lon);
extras(display, node, xPos, yPos, columnWidth, heading, lat, lon);
lastNodeY = max(lastNodeY, yPos + FONT_HEIGHT_SMALL);
yOffset += rowYOffset;
@@ -779,13 +765,9 @@ void drawDistanceScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
#endif
void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
float headingRadian = 0.0f;
float heading = 0;
bool validHeading = false;
auto ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
if (!ourNode || !nodeDB->hasValidPosition(ourNode)) {
drawNodeListScreen(display, state, x, y, "Bearings", drawEntryCompass, drawCompassUnknown, headingRadian, 0.0, 0.0);
return;
}
double lat = DegD(ourNode->position.latitude_i);
double lon = DegD(ourNode->position.longitude_i);
@@ -797,12 +779,21 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state,
lastSwitchTime = now;
}
#endif
if (!CompassRenderer::getHeadingRadians(lat, lon, headingRadian)) {
drawNodeListScreen(display, state, x, y, "Bearings", drawEntryCompass, drawCompassUnknown, headingRadian, lat, lon);
return;
}
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) {
#if HAS_GPS
if (screen->hasHeading()) {
heading = screen->getHeading(); // degrees
validHeading = true;
} else {
heading = screen->estimatedHeading(lat, lon);
validHeading = !isnan(heading);
}
#endif
drawNodeListScreen(display, state, x, y, "Bearings", drawEntryCompass, drawCompassArrow, headingRadian, lat, lon);
if (!validHeading)
return;
}
drawNodeListScreen(display, state, x, y, "Bearings", drawEntryCompass, drawCompassArrow, heading, lat, lon);
}
/// Draw a series of fields in a column, wrapping to multiple columns if needed
+3 -3
View File
@@ -32,7 +32,7 @@ enum ListMode_Location { MODE_DISTANCE = 0, MODE_BEARING = 1, MODE_COUNT_LOCATIO
// Main node list screen function
void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *title,
EntryRenderer renderer, NodeExtrasRenderer extras = nullptr, float headingRadian = 0, double lat = 0,
EntryRenderer renderer, NodeExtrasRenderer extras = nullptr, float heading = 0, double lat = 0,
double lon = 0);
// Entry renderers
@@ -43,8 +43,8 @@ void drawEntryDynamic_Nodes(OLEDDisplay *display, meshtastic_NodeInfoLite *node,
void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth);
// Extras renderers
void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth,
float myHeadingRadian, double userLat, double userLon);
void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth, float myHeading,
double userLat, double userLon);
// Screen frame functions
void drawLastHeardScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
+102 -129
View File
@@ -38,15 +38,6 @@ static inline void drawSatelliteIcon(OLEDDisplay *display, int16_t x, int16_t y)
}
}
static void drawCompassStatusText(OLEDDisplay *display, int16_t compassX, int16_t compassY, const char *statusLine1,
const char *statusLine2)
{
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->drawString(compassX, compassY - FONT_HEIGHT_SMALL, statusLine1);
display->drawString(compassX, compassY, statusLine2);
display->setTextAlignment(TEXT_ALIGN_LEFT);
}
void graphics::UIRenderer::rebuildFavoritedNodes()
{
favoritedNodes.clear();
@@ -647,54 +638,51 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, i
display->drawString(x, getTextPositions(display)[line++], batLine);
}
bool showCompass = false;
float myHeading = 0.0f;
float bearing = 0.0f;
const bool hasOwnPositionFix = (ourNode && nodeDB->hasValidPosition(ourNode));
const bool hasNodePositionFix = nodeDB->hasValidPosition(node);
const char *statusLine1 = nullptr;
const char *statusLine2 = nullptr;
if (hasOwnPositionFix && hasNodePositionFix) {
const auto &op = ourNode->position;
showCompass = CompassRenderer::getHeadingRadians(DegD(op.latitude_i), DegD(op.longitude_i), myHeading);
if (showCompass) {
const auto &p = node->position;
bearing = GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i));
bearing = CompassRenderer::adjustBearingForCompassMode(bearing, myHeading);
} else {
statusLine1 = "No";
statusLine2 = "Heading";
}
} else if (!hasOwnPositionFix || !hasNodePositionFix) {
statusLine1 = "No";
statusLine2 = "Fix";
}
// --- Compass Rendering: landscape (wide) screens use the original side-aligned logic ---
if (SCREEN_WIDTH > SCREEN_HEIGHT) {
if (showCompass || statusLine1) {
bool showCompass = false;
if (ourNode && (nodeDB->hasValidPosition(ourNode) || screen->hasHeading()) && nodeDB->hasValidPosition(node)) {
showCompass = true;
}
if (showCompass) {
const int16_t topY = getTextPositions(display)[1];
const int16_t bottomY = SCREEN_HEIGHT - (FONT_HEIGHT_SMALL - 1);
const int16_t usableHeight = bottomY - topY - 5;
int16_t compassRadius = usableHeight / 2;
if (compassRadius < 8)
compassRadius = 8;
const int16_t compassDiam = compassRadius * 2;
const int16_t compassX = x + SCREEN_WIDTH - compassRadius - 8;
const int16_t compassY = topY + (usableHeight / 2) + ((FONT_HEIGHT_SMALL - 1) / 2) + 2;
const int16_t compassDiam = compassRadius * 2;
const auto &op = ourNode->position;
float myHeading = screen->hasHeading() ? screen->getHeading() * PI / 180
: screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
const auto &p = node->position;
/* unused
float d =
GeoCoord::latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
*/
float bearing = GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i));
if (uiconfig.compass_mode == meshtastic_CompassMode_FREEZE_HEADING) {
myHeading = 0;
} else {
bearing -= myHeading;
}
display->drawCircle(compassX, compassY, compassRadius);
if (showCompass) {
CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, compassRadius);
CompassRenderer::drawNodeHeading(display, compassX, compassY, compassDiam, bearing);
} else {
drawCompassStatusText(display, compassX, compassY, statusLine1, statusLine2);
}
CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, compassRadius);
CompassRenderer::drawNodeHeading(display, compassX, compassY, compassDiam, bearing);
}
// else show nothing
} else {
// Portrait or square: put compass at the bottom and centered, scaled to fit available space
if (showCompass || statusLine1) {
bool showCompass = false;
if (ourNode && (nodeDB->hasValidPosition(ourNode) || screen->hasHeading()) && nodeDB->hasValidPosition(node)) {
showCompass = true;
}
if (showCompass) {
int yBelowContent = (line > 0 && line <= 5) ? (getTextPositions(display)[line - 1] + FONT_HEIGHT_SMALL + 2)
: getTextPositions(display)[1];
const int margin = 4;
@@ -705,8 +693,8 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, i
#else
const int navBarHeight = 0;
#endif
// --------- END PATCH FOR EINK NAV BAR -----------
int availableHeight = SCREEN_HEIGHT - yBelowContent - navBarHeight - margin;
// --------- END PATCH FOR EINK NAV BAR -----------
if (availableHeight < FONT_HEIGHT_SMALL * 2)
return;
@@ -720,13 +708,25 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, i
int compassX = x + SCREEN_WIDTH / 2;
int compassY = yBelowContent + availableHeight / 2;
display->drawCircle(compassX, compassY, compassRadius);
if (showCompass) {
graphics::CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, compassRadius);
graphics::CompassRenderer::drawNodeHeading(display, compassX, compassY, compassRadius * 2, bearing);
} else {
drawCompassStatusText(display, compassX, compassY, statusLine1, statusLine2);
const auto &op = ourNode->position;
float myHeading = 0;
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) {
myHeading = screen->hasHeading() ? screen->getHeading() * PI / 180
: screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
}
graphics::CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, compassRadius);
const auto &p = node->position;
/* unused
float d =
GeoCoord::latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
*/
float bearing = GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i));
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING)
bearing -= myHeading;
graphics::CompassRenderer::drawNodeHeading(display, compassX, compassY, compassRadius * 2, bearing);
display->drawCircle(compassX, compassY, compassRadius);
}
// else show nothing
}
@@ -1162,7 +1162,6 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
// === Header ===
graphics::drawCommonHeader(display, x, y, titleStr);
const int *textPos = getTextPositions(display);
// === First Row: My Location ===
#if HAS_GPS
@@ -1177,12 +1176,12 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
} else {
displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "No GPS" : "GPS off";
}
drawSatelliteIcon(display, x, textPos[line]);
drawSatelliteIcon(display, x, getTextPositions(display)[line]);
int xOffset = (currentResolution == ScreenResolution::High) ? 6 : 0;
display->drawString(x + 11 + xOffset, textPos[line++], displayLine);
display->drawString(x + 11 + xOffset, getTextPositions(display)[line++], displayLine);
} else {
// Onboard GPS
UIRenderer::drawGps(display, 0, textPos[line++], gpsStatus);
UIRenderer::drawGps(display, 0, getTextPositions(display)[line++], gpsStatus);
}
config.display.heading_bold = origBold;
@@ -1191,36 +1190,18 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
geoCoord.updateCoords(int32_t(gpsStatus->getLatitude()), int32_t(gpsStatus->getLongitude()),
int32_t(gpsStatus->getAltitude()));
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
const bool hasOwnPositionFix = (ourNode && nodeDB->hasValidPosition(ourNode));
const bool hasLiveGpsFix =
(gpsStatus && gpsStatus->getHasLock() && (gpsStatus->getLatitude() != 0 || gpsStatus->getLongitude() != 0));
const bool hasSensorHeading = screen->hasHeading();
float heading = 0.0f;
// === Determine Compass Heading ===
float heading = 0;
bool validHeading = false;
const char *statusLine1 = nullptr;
const char *statusLine2 = nullptr;
if (hasSensorHeading || hasLiveGpsFix || hasOwnPositionFix) {
double headingLat = 0.0;
double headingLon = 0.0;
if (hasLiveGpsFix) {
headingLat = DegD(gpsStatus->getLatitude());
headingLon = DegD(gpsStatus->getLongitude());
} else if (hasOwnPositionFix) {
const auto &op = ourNode->position;
headingLat = DegD(op.latitude_i);
headingLon = DegD(op.longitude_i);
}
validHeading = CompassRenderer::getHeadingRadians(headingLat, headingLon, heading);
}
if (!validHeading) {
if (hasSensorHeading || hasLiveGpsFix || hasOwnPositionFix) {
statusLine1 = "No";
statusLine2 = "Heading";
if (uiconfig.compass_mode == meshtastic_CompassMode_FREEZE_HEADING) {
validHeading = true;
} else {
if (screen->hasHeading()) {
heading = radians(screen->getHeading());
validHeading = true;
} else {
statusLine1 = "No";
statusLine2 = "Fix";
heading = screen->estimatedHeading(geoCoord.getLatitude() * 1e-7, geoCoord.getLongitude() * 1e-7);
validHeading = !isnan(heading);
}
}
@@ -1238,18 +1219,18 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
getUptimeStr(delta, "Last: ", uptimeStr, sizeof(uptimeStr), true);
#endif
display->drawString(0, textPos[line++], uptimeStr);
display->drawString(0, getTextPositions(display)[line++], uptimeStr);
} else {
display->drawString(0, textPos[line++], "Last: ?");
display->drawString(0, getTextPositions(display)[line++], "Last: ?");
}
// === Third Row: Line 1 GPS Info ===
UIRenderer::drawGpsCoordinates(display, x, textPos[line++], gpsStatus, "line1");
UIRenderer::drawGpsCoordinates(display, x, getTextPositions(display)[line++], gpsStatus, "line1");
if (uiconfig.gps_format != meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC &&
uiconfig.gps_format != meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS) {
// === Fourth Row: Line 2 GPS Info ===
UIRenderer::drawGpsCoordinates(display, x, textPos[line++], gpsStatus, "line2");
UIRenderer::drawGpsCoordinates(display, x, getTextPositions(display)[line++], gpsStatus, "line2");
}
// === Final Row: Altitude ===
@@ -1260,14 +1241,14 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
} else {
snprintf(altitudeLine, sizeof(altitudeLine), "Alt: %.0im", alt);
}
display->drawString(x, textPos[line++], altitudeLine);
display->drawString(x, getTextPositions(display)[line++], altitudeLine);
}
#if !defined(OLED_TINY)
// === Draw Compass ===
if (validHeading || statusLine1) {
// === Draw Compass if heading is valid ===
if (validHeading) {
// --- Compass Rendering: landscape (wide) screens use original side-aligned logic ---
if (SCREEN_WIDTH > SCREEN_HEIGHT) {
const int16_t topY = textPos[1];
const int16_t topY = getTextPositions(display)[1];
const int16_t bottomY = SCREEN_HEIGHT - (FONT_HEIGHT_SMALL - 1); // nav row height
const int16_t usableHeight = bottomY - topY - 5;
@@ -1280,33 +1261,29 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
// Center vertically and nudge down slightly to keep "N" clear of header
const int16_t compassY = topY + (usableHeight / 2) + ((FONT_HEIGHT_SMALL - 1) / 2) + 2;
CompassRenderer::drawNodeHeading(display, compassX, compassY, compassDiam, -heading);
display->drawCircle(compassX, compassY, compassRadius);
if (validHeading) {
CompassRenderer::drawNodeHeading(display, compassX, compassY, compassDiam, -heading);
// "N" label
float northAngle = 0;
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING)
northAngle = -heading;
float radius = compassRadius;
int16_t nX = compassX + (radius - 1) * sin(northAngle);
int16_t nY = compassY - (radius - 1) * cos(northAngle);
int16_t nLabelWidth = display->getStringWidth("N") + 2;
int16_t nLabelHeightBox = FONT_HEIGHT_SMALL + 1;
// "N" label
float northAngle = 0;
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING)
northAngle = -heading;
float radius = compassRadius;
int16_t nX = compassX + (radius - 1) * sin(northAngle);
int16_t nY = compassY - (radius - 1) * cos(northAngle);
int16_t nLabelWidth = display->getStringWidth("N") + 2;
int16_t nLabelHeightBox = FONT_HEIGHT_SMALL + 1;
display->setColor(BLACK);
display->fillRect(nX - nLabelWidth / 2, nY - nLabelHeightBox / 2, nLabelWidth, nLabelHeightBox);
display->setColor(WHITE);
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->drawString(nX, nY - FONT_HEIGHT_SMALL / 2, "N");
} else {
drawCompassStatusText(display, compassX, compassY, statusLine1, statusLine2);
}
display->setColor(BLACK);
display->fillRect(nX - nLabelWidth / 2, nY - nLabelHeightBox / 2, nLabelWidth, nLabelHeightBox);
display->setColor(WHITE);
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->drawString(nX, nY - FONT_HEIGHT_SMALL / 2, "N");
} else {
// Portrait or square: put compass at the bottom and centered, scaled to fit available space
// For E-Ink screens, account for navigation bar at the bottom!
int yBelowContent = textPos[5] + FONT_HEIGHT_SMALL + 2;
int yBelowContent = getTextPositions(display)[5] + FONT_HEIGHT_SMALL + 2;
const int margin = 4;
int availableHeight =
#if defined(USE_EINK)
@@ -1327,29 +1304,25 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
int compassX = x + SCREEN_WIDTH / 2;
int compassY = yBelowContent + availableHeight / 2;
CompassRenderer::drawNodeHeading(display, compassX, compassY, compassRadius * 2, -heading);
display->drawCircle(compassX, compassY, compassRadius);
if (validHeading) {
CompassRenderer::drawNodeHeading(display, compassX, compassY, compassRadius * 2, -heading);
// "N" label
float northAngle = 0;
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING)
northAngle = -heading;
float radius = compassRadius;
int16_t nX = compassX + (radius - 1) * sin(northAngle);
int16_t nY = compassY - (radius - 1) * cos(northAngle);
int16_t nLabelWidth = display->getStringWidth("N") + 2;
int16_t nLabelHeightBox = FONT_HEIGHT_SMALL + 1;
// "N" label
float northAngle = 0;
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING)
northAngle = -heading;
float radius = compassRadius;
int16_t nX = compassX + (radius - 1) * sin(northAngle);
int16_t nY = compassY - (radius - 1) * cos(northAngle);
int16_t nLabelWidth = display->getStringWidth("N") + 2;
int16_t nLabelHeightBox = FONT_HEIGHT_SMALL + 1;
display->setColor(BLACK);
display->fillRect(nX - nLabelWidth / 2, nY - nLabelHeightBox / 2, nLabelWidth, nLabelHeightBox);
display->setColor(WHITE);
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->drawString(nX, nY - FONT_HEIGHT_SMALL / 2, "N");
} else {
drawCompassStatusText(display, compassX, compassY, statusLine1, statusLine2);
}
display->setColor(BLACK);
display->fillRect(nX - nLabelWidth / 2, nY - nLabelHeightBox / 2, nLabelWidth, nLabelHeightBox);
display->setColor(WHITE);
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->drawString(nX, nY - FONT_HEIGHT_SMALL / 2, "N");
}
}
#endif
-23
View File
@@ -127,10 +127,6 @@ void printPartitionTable()
#include "motion/AccelerometerThread.h"
AccelerometerThread *accelerometerThread = nullptr;
#endif
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && !MESHTASTIC_EXCLUDE_MAGNETOMETER
#include "motion/MagnetometerThread.h"
MagnetometerThread *magnetometerThread = nullptr;
#endif
#ifdef HAS_I2S
#include "AudioThread.h"
@@ -201,8 +197,6 @@ bool osk_found = false;
ScanI2C::DeviceAddress rtc_found = ScanI2C::ADDRESS_NONE;
// The I2C address of the Accelerometer (if found)
ScanI2C::DeviceAddress accelerometer_found = ScanI2C::ADDRESS_NONE;
// The I2C address of the Magnetometer (if found)
ScanI2C::DeviceAddress magnetometer_found = ScanI2C::ADDRESS_NONE;
// The I2C address of the RGB LED (if found)
ScanI2C::FoundDevice rgb_found = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE, ScanI2C::ADDRESS_NONE);
/// The I2C address of our Air Quality Indicator (if found)
@@ -428,11 +422,6 @@ void setup()
digitalWrite(VEXT_ENABLE, VEXT_ON_VALUE); // turn on the display power
#endif
#if defined(PIN_SENSOR_EN)
pinMode(PIN_SENSOR_EN, OUTPUT);
digitalWrite(PIN_SENSOR_EN, PIN_SENSOR_EN_ACTIVE); // turn on sensor power
#endif
#if defined(BIAS_T_ENABLE)
pinMode(BIAS_T_ENABLE, OUTPUT);
digitalWrite(BIAS_T_ENABLE, BIAS_T_VALUE); // turn on 5V for GPS Antenna
@@ -673,11 +662,6 @@ void setup()
accelerometer_found = acc_info.type != ScanI2C::DeviceType::NONE ? acc_info.address : accelerometer_found;
LOG_DEBUG("acc_info = %i", acc_info.type);
#endif
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_MAGNETOMETER
auto mag_info = i2cScanner->firstMagnetometer();
magnetometer_found = mag_info.type != ScanI2C::DeviceType::NONE ? mag_info.address : magnetometer_found;
LOG_DEBUG("mag_info = %i", mag_info.type);
#endif
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA226, meshtastic_TelemetrySensorType_INA226);
@@ -690,8 +674,6 @@ void setup()
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::HMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MMC5983MA, meshtastic_TelemetrySensorType_MMC5983MA);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM42607P, meshtastic_TelemetrySensorType_ICM42607P);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102);
@@ -772,11 +754,6 @@ void setup()
accelerometerThread = new AccelerometerThread(acc_info.type);
}
#endif
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_MAGNETOMETER
if (mag_info.type != ScanI2C::DeviceType::NONE) {
magnetometerThread = new MagnetometerThread(mag_info.type);
}
#endif
#if defined(HAS_NEOPIXEL) || defined(UNPHONE) || defined(RGBLED_RED)
ambientLightingThread = new AmbientLightingThread(ScanI2C::DeviceType::NONE);
-5
View File
@@ -35,7 +35,6 @@ extern bool kb_found;
extern bool osk_found;
extern ScanI2C::DeviceAddress rtc_found;
extern ScanI2C::DeviceAddress accelerometer_found;
extern ScanI2C::DeviceAddress magnetometer_found;
extern ScanI2C::FoundDevice rgb_found;
extern ScanI2C::DeviceAddress aqi_found;
@@ -70,10 +69,6 @@ extern graphics::Screen *screen;
#include "motion/AccelerometerThread.h"
extern AccelerometerThread *accelerometerThread;
#endif
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && !MESHTASTIC_EXCLUDE_MAGNETOMETER
#include "motion/MagnetometerThread.h"
extern MagnetometerThread *magnetometerThread;
#endif
extern bool isVibrating;
-6
View File
@@ -350,10 +350,4 @@ template <typename T> bool LR11x0Interface<T>::sleep()
return true;
}
template <typename T> int16_t LR11x0Interface<T>::getCurrentRSSI()
{
float rssi = lora.getRSSI();
return (int16_t)round(rssi);
}
#endif
-2
View File
@@ -37,8 +37,6 @@ template <class T> class LR11x0Interface : public RadioLibInterface
*/
T lora;
int16_t getCurrentRSSI() override;
/**
* Glue functions called from ISR land
*/
-383
View File
@@ -1,383 +0,0 @@
#include "configuration.h"
#if defined(USE_LR2021) && RADIOLIB_EXCLUDE_LR2021 != 1
#include "LR20x0Interface.h"
#include "error.h"
#include "mesh/NodeDB.h"
// Keep LR20x0 naming while RadioLib exposes LR2021 symbols.
#ifndef LR20x0
#define LR20x0 LR2021
#endif
#ifdef LR2021_DIO_AS_RF_SWITCH
#include "rfswitch.h"
#elif ARCH_PORTDUINO
#include "PortduinoGlue.h"
#define lr20x0_rfswitch_dio_pins portduino_config.rfswitch_dio_pins
#define lr20x0_rfswitch_table portduino_config.rfswitch_table
#else
static const uint32_t lr20x0_rfswitch_dio_pins[] = {RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
static const Module::RfSwitchMode_t lr20x0_rfswitch_table[] = {
{LR20x0::MODE_STBY, {}}, {LR20x0::MODE_RX, {}}, {LR20x0::MODE_TX, {}},
{LR20x0::MODE_RX_HF, {}}, {LR20x0::MODE_TX_HF, {}}, END_OF_MODE_TABLE,
};
#endif
// Particular boards might define a different max power based on what their hardware can do, default to max power output if not
// specified (may be dangerous if using external PA and LR20x0 power config forgotten)
#if ARCH_PORTDUINO
#define LR2021_MAX_POWER portduino_config.lr2021_max_power
#endif
#ifndef LR2021_MAX_POWER
#define LR2021_MAX_POWER 22
#endif
// the 2.4G part maxes at 12dBm
#if ARCH_PORTDUINO
#define LR2021_MAX_POWER_HF portduino_config.lr2021_max_power_hf
#endif
#ifndef LR2021_MAX_POWER_HF
#define LR2021_MAX_POWER_HF 12
#endif
template <typename T>
LR20x0Interface<T>::LR20x0Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
RADIOLIB_PIN_TYPE busy)
: RadioLibInterface(hal, cs, irq, rst, busy, &lora), lora(&module)
{
LOG_WARN("LR20x0Interface(cs=%d, irq=%d, rst=%d, busy=%d)", cs, irq, rst, busy);
}
/// Initialise the Driver transport hardware and software.
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
template <typename T> bool LR20x0Interface<T>::init()
{
#ifdef LR2021_POWER_EN
pinMode(LR2021_POWER_EN, OUTPUT);
digitalWrite(LR2021_POWER_EN, HIGH);
#endif
#if ARCH_PORTDUINO
float tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000;
// FIXME: correct logic to default to not using TCXO if no voltage is specified for LR20x0_DIO3_TCXO_VOLTAGE
#elif defined(LR2021_DIO3_TCXO_VOLTAGE)
float tcxoVoltage = LR2021_DIO3_TCXO_VOLTAGE;
LOG_DEBUG("LR2021_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V", LR2021_DIO3_TCXO_VOLTAGE);
// (DIO3 is not free to be used as an IRQ)
#elif defined(TCXO_OPTIONAL)
float tcxoVoltage = 1.6f; // TCXO_OPTIONAL: try default 1.6 V first, fall back to XTAL on failure
LOG_DEBUG("TCXO_OPTIONAL: no LR2021_DIO3_TCXO_VOLTAGE defined, trying default TCXO Vref 1.6 V first");
#else
float tcxoVoltage =
0; // "TCXO reference voltage to be set on DIO3. Defaults to 1.6 V, set to 0 to skip." per
// https://github.com/jgromes/RadioLib/blob/690a050ebb46e6097c5d00c371e961c1caa3b52e/src/modules/LR11x0/LR11x0.h#L471C26-L471C104
// (DIO3 is free to be used as an IRQ)
LOG_DEBUG("LR2021_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage");
#endif
RadioLibInterface::init();
#ifdef LR2021_IRQ_DIO_NUM
lora.irqDioNum = LR2021_IRQ_DIO_NUM;
LOG_DEBUG("Set irqDioNum %d", lora.irqDioNum);
#elif defined(IRQ_DIO_NUM)
lora.irqDioNum = IRQ_DIO_NUM;
LOG_DEBUG("Set irqDioNum %d", lora.irqDioNum);
#else
LOG_DEBUG("Use default irqDioNum %d", lora.irqDioNum);
#endif
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) { // clamp if wide freq range
limitPower(LR2021_MAX_POWER_HF);
} else {
limitPower(LR2021_MAX_POWER); // default clamp for non-wide freq range
}
#ifdef LR2021_RF_SWITCH_SUBGHZ
pinMode(LR2021_RF_SWITCH_SUBGHZ, OUTPUT);
digitalWrite(LR2021_RF_SWITCH_SUBGHZ, getFreq() < 1e9 ? HIGH : LOW);
LOG_DEBUG("Set RF0 switch to %s", getFreq() < 1e9 ? "SubGHz" : "2.4GHz");
#endif
#ifdef LR2021_RF_SWITCH_2_4GHZ
pinMode(LR2021_RF_SWITCH_2_4GHZ, OUTPUT);
digitalWrite(LR2021_RF_SWITCH_2_4GHZ, getFreq() < 1e9 ? LOW : HIGH);
LOG_DEBUG("Set RF1 switch to %s", getFreq() < 1e9 ? "SubGHz" : "2.4GHz");
#endif
// Allow extra time for TCXO to stabilize after power-on
delay(10);
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage);
// Retry if we get SPI command failed - some units need extra TCXO stabilization time
if (res == RADIOLIB_ERR_SPI_CMD_FAILED) {
LOG_WARN("LR20x0 init failed with %d (SPI_CMD_FAILED), retrying after delay...", res);
delay(100);
res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage);
}
#if defined(TCXO_OPTIONAL)
// If init failed for any reason other than chip not found, retry without TCXO (XTAL mode)
if (res != RADIOLIB_ERR_NONE && res != RADIOLIB_ERR_CHIP_NOT_FOUND && tcxoVoltage > 0) {
LOG_WARN("LR20x0 init failed with TCXO Vref %f V (err %d), retrying without TCXO", tcxoVoltage, res);
tcxoVoltage = 0;
res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage);
if (res == RADIOLIB_ERR_NONE)
LOG_INFO("LR20x0 init success without TCXO (XTAL mode)");
}
#endif
// \todo Display actual typename of the adapter, not just `LR20x0`
LOG_INFO("LR20x0 init result %d", res);
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
return false;
LOG_INFO("Frequency set to %f", getFreq());
LOG_INFO("Bandwidth set to %f", bw);
LOG_INFO("Power output set to %d", power);
if (res == RADIOLIB_ERR_NONE)
res = lora.setCRC(2);
#ifdef LR2021_DIO_AS_RF_SWITCH
bool dioAsRfSwitch = true;
#elif defined(ARCH_PORTDUINO)
bool dioAsRfSwitch = portduino_config.has_rfswitch_table;
#else
bool dioAsRfSwitch = false;
#endif
if (dioAsRfSwitch) {
lora.setRfSwitchTable(lr20x0_rfswitch_dio_pins, lr20x0_rfswitch_table);
LOG_DEBUG("Set DIO RF switch");
}
if (res == RADIOLIB_ERR_NONE) {
if (config.lora.sx126x_rx_boosted_gain) { // the name is unfortunate but historically accurate
res = lora.setRxBoostedGainMode(true);
LOG_INFO("Set RX gain to boosted mode; result: %d", res);
} else {
res = lora.setRxBoostedGainMode(false);
LOG_INFO("Set RX gain to power saving mode (boosted mode off); result: %d", res);
}
}
if (res == RADIOLIB_ERR_NONE)
startReceive(); // start receiving
return res == RADIOLIB_ERR_NONE;
}
template <typename T> bool LR20x0Interface<T>::reconfigure()
{
RadioLibInterface::reconfigure();
// set mode to standby
setStandby();
// configure publicly accessible settings
int err = lora.setSpreadingFactor(sf);
if (err != RADIOLIB_ERR_NONE)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
err = lora.setBandwidth(bw); // different form than LR11xx
if (err != RADIOLIB_ERR_NONE)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
err = lora.setCodingRate(cr, cr != 7); // use long interleaving except if CR is 4/7 which doesn't support it
if (err != RADIOLIB_ERR_NONE)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
err = lora.setSyncWord(syncWord);
assert(err == RADIOLIB_ERR_NONE);
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) { // clamp if wide freq range
limitPower(LR2021_MAX_POWER_HF);
} else {
limitPower(LR2021_MAX_POWER); // default clamp for non-wide freq range
}
err = lora.setPreambleLength(preambleLength);
assert(err == RADIOLIB_ERR_NONE);
err = lora.setFrequency(getFreq());
if (err != RADIOLIB_ERR_NONE)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
err = lora.setOutputPower(power);
assert(err == RADIOLIB_ERR_NONE);
// Apply RX gain mode — valid in STDBY, matches resetAGC() pattern
err = lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain);
if (err != RADIOLIB_ERR_NONE)
LOG_WARN("LR20x0 setRxBoostedGainMode %s%d", radioLibErr, err);
startReceive(); // restart receiving
return true;
}
template <typename T> void LR20x0Interface<T>::disableInterrupt()
{
lora.clearIrqAction();
}
template <typename T> void LR20x0Interface<T>::setStandby()
{
checkNotification(); // handle any pending interrupts before we force standby
int err = lora.standby();
if (err != RADIOLIB_ERR_NONE) {
LOG_DEBUG("LR20x0 standby failed with error %d", err);
}
assert(err == RADIOLIB_ERR_NONE);
isReceiving = false; // If we were receiving, not any more
activeReceiveStart = 0;
disableInterrupt();
completeSending(); // If we were sending, not anymore
RadioLibInterface::setStandby();
}
/**
* Add SNR data to received messages
*/
template <typename T> void LR20x0Interface<T>::addReceiveMetadata(meshtastic_MeshPacket *mp)
{
// LOG_DEBUG("PacketStatus %x", lora.getPacketStatus());
mp->rx_snr = lora.getSNR();
mp->rx_rssi = lround(lora.getRSSI());
// LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError()); // not implemented for LR20x0, but noop for LR11x0
// too(!)
}
/** We override to turn on transmitter power as needed.
*/
template <typename T> void LR20x0Interface<T>::configHardwareForSend()
{
RadioLibInterface::configHardwareForSend();
}
// For power draw measurements, helpful to force radio to stay sleeping
// #define SLEEP_ONLY
template <typename T> void LR20x0Interface<T>::startReceive()
{
#ifdef SLEEP_ONLY
sleep();
#else
setStandby();
lora.setPreambleLength(preambleLength); // Solve RX ack fail after direct message sent. Not sure why this is needed.
// We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly.
int err =
lora.startReceive(RADIOLIB_LR2021_RX_TIMEOUT_INF, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
if (err)
LOG_ERROR("StartReceive error: %d", err);
assert(err == RADIOLIB_ERR_NONE);
RadioLibInterface::startReceive();
// Must be done AFTER starting receive, because startReceive clears (possibly stale) interrupt pending register bits
enableInterrupt(isrRxLevel0);
checkRxDoneIrqFlag();
#endif
}
/** Is the channel currently active? */
template <typename T> bool LR20x0Interface<T>::isChannelActive()
{
// check if we can detect a LoRa preamble on the current channel
ChannelScanConfig_t cfg = {.cad = {.symNum = NUM_SYM_CAD,
.detPeak = RADIOLIB_LR2021_CAD_PARAM_DEFAULT,
.detMin = RADIOLIB_LR2021_CAD_PARAM_DEFAULT,
.exitMode = RADIOLIB_LR2021_CAD_PARAM_DEFAULT,
.timeout = 0,
.irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS,
.irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK}};
int16_t result;
setStandby();
result = lora.scanChannel(cfg);
if (result == RADIOLIB_LORA_DETECTED)
return true;
assert(result != RADIOLIB_ERR_WRONG_MODEM);
return false;
}
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
template <typename T> bool LR20x0Interface<T>::isActivelyReceiving()
{
// The IRQ status will be cleared when we start our read operation. Check if we've started a header, but haven't yet
// received and handled the interrupt for reading the packet/handling errors.
return receiveDetected(lora.getIrqStatus(), RADIOLIB_LR2021_IRQ_LORA_HEADER_VALID, RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED);
}
#ifdef LR20X0_AGC_RESET
template <typename T> void LR20x0Interface<T>::resetAGC()
{
// Safety: don't reset mid-packet
if (sendingPacket != NULL || (isReceiving && isActivelyReceiving()))
return;
LOG_DEBUG("LR20x0 AGC reset: warm sleep + Calibrate(0x3F)");
// 1. Warm sleep — powers down the analog frontend, resetting AGC state
lora.sleep(true, 0);
// 2. Wake to RC standby for stable calibration
lora.standby(RADIOLIB_LR20X0_STANDBY_RC, true);
// 3. Calibrate all blocks (PLL, ADC, image, RC oscillators)
// calibrate() is protected on LR20x0, so use raw SPI (same as internal implementation)
uint8_t calData = RADIOLIB_LR20X0_CALIBRATE_ALL;
module.SPIwriteStream(RADIOLIB_LR20X0_CMD_CALIBRATE, &calData, 1, true, true);
// 4. Re-calibrate image rejection for actual operating frequency
// Calibrate(0x3F) defaults to 902-928 MHz which is wrong for other regions.
lora.calibrateImageRejection(getFreq() - 4.0f, getFreq() + 4.0f);
// 5. Re-apply RX boosted gain mode
lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain);
// 6. Resume receiving
startReceive();
}
#endif
template <typename T> bool LR20x0Interface<T>::sleep()
{
// \todo Display actual typename of the adapter, not just `LR20x0`
LOG_DEBUG("LR20x0 entering sleep mode");
setStandby(); // Stop any pending operations
// turn off TCXO if it was powered
lora.setTCXO(0);
// put chipset into sleep mode (we've already disabled interrupts by now)
bool keepConfig = false;
lora.sleep(keepConfig, 0); // Note: we do not keep the config, full reinit will be needed
#ifdef LR2021_POWER_EN
digitalWrite(LR2021_POWER_EN, LOW);
#endif
return true;
}
template <typename T> int16_t LR20x0Interface<T>::getCurrentRSSI()
{
float rssi = lora.getRSSI();
return (int16_t)round(rssi);
}
#endif
-77
View File
@@ -1,77 +0,0 @@
#pragma once
#if RADIOLIB_EXCLUDE_LR2021 != 1
#include "RadioLibInterface.h"
/**
* \brief Adapter for LR20x0 radio family. Implements common logic for child classes.
* \tparam T RadioLib module type for LR20x0, e.g. LR2021.
*/
template <class T> class LR20x0Interface : public RadioLibInterface
{
public:
LR20x0Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
RADIOLIB_PIN_TYPE busy);
/// Initialise the Driver transport hardware and software.
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
virtual bool init() override;
/// Apply any radio provisioning changes
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
virtual bool reconfigure() override;
/// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep.
virtual bool sleep() override;
bool isIRQPending() override { return lora.getIrqFlags() != 0; }
#ifdef LR20X0_AGC_RESET
void resetAGC() override;
#endif
protected:
/**
* Specific module instance
*/
T lora;
int16_t getCurrentRSSI() override;
/**
* Glue functions called from ISR land
*/
virtual void disableInterrupt() override;
/**
* Enable a particular ISR callback glue function
*/
virtual void enableInterrupt(void (*callback)()) { lora.setIrqAction(callback); }
/** can we detect a LoRa preamble on the current channel? */
virtual bool isChannelActive() override;
/** are we actively receiving a packet (only called during receiving state) */
virtual bool isActivelyReceiving() override;
/**
* Start waiting to receive a message
*/
virtual void startReceive() override;
/**
* We override to turn on transmitter power as needed.
*/
virtual void configHardwareForSend() override;
/**
* Add SNR data to received messages
*/
virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
virtual void setStandby() override;
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
};
#endif
+4
View File
@@ -0,0 +1,4 @@
#include "MeshLED.h"
// Default global instance: no-op dummy. Replace in variant initVariant() for device-specific behaviour.
std::shared_ptr<MeshLED> meshLED = std::make_shared<MeshLED>();
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include <memory>
/**
* Interface for a "mesh activity" LED that lights while the LoRa radio is transmitting.
* The default implementation is a no-op; device variants may replace the global instance
* with a concrete subclass.
*/
class MeshLED
{
public:
virtual ~MeshLED() = default;
virtual void init() {}
virtual void on() {}
virtual void off() {}
};
extern std::shared_ptr<MeshLED> meshLED;
+1 -5
View File
@@ -9,7 +9,6 @@
#include "FSCommon.h"
#include "MeshRadio.h"
#include "MeshService.h"
#include "MessageStore.h"
#include "NodeDB.h"
#include "PacketHistory.h"
#include "PowerFSM.h"
@@ -525,9 +524,6 @@ bool NodeDB::factoryReset(bool eraseBleBonds)
if (transmitHistory) {
transmitHistory->clear();
}
#if HAS_SCREEN
messageStore.clearAllMessages();
#endif
// second, install default state (this will deal with the duplicate mac address issue)
installDefaultNodeDatabase();
installDefaultDeviceState();
@@ -700,7 +696,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32);
#if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) || \
defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT) || defined(HELTEC_V4_R8_TFT)) && \
defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT) || defined(HELTEC_V4_R8_TFT) || defined(SEEED_WIO_TRACKER_L2)) && \
HAS_TFT
// switch BT off by default; use TFT programming mode or hotkey to enable
config.bluetooth.enabled = false;
+7 -30
View File
@@ -36,17 +36,6 @@
// Flag to indicate a heartbeat was received and we should send queue status
bool heartbeatReceived = false;
namespace
{
constexpr uint8_t FILES_MANIFEST_LEVELS = 3;
constexpr size_t FILES_MANIFEST_MAX_COUNT = 64;
void releaseFilesManifest(std::vector<meshtastic_FileInfo> &filesManifest)
{
std::vector<meshtastic_FileInfo>().swap(filesManifest);
}
} // namespace
PhoneAPI::PhoneAPI()
{
lastContactMsec = millis();
@@ -81,23 +70,10 @@ void PhoneAPI::handleStartConfig()
state = STATE_SEND_MY_INFO;
}
pauseBluetoothLogging = true;
// Manifest is never read on the node-info-only path (STATE_SEND_FILEMANIFEST
// short-circuits to sendConfigComplete), so skip the SPI lock + FS walk.
if (config_nonce != SPECIAL_NONCE_ONLY_NODES) {
bool filesManifestLimited = false;
{
concurrency::LockGuard guard(spiLock);
filesManifest = getFiles("/", FILES_MANIFEST_LEVELS, FILES_MANIFEST_MAX_COUNT, &filesManifestLimited);
}
if (filesManifestLimited) {
LOG_WARN("Got %zu files in manifest (limited to %zu entries/depth %u)", filesManifest.size(),
FILES_MANIFEST_MAX_COUNT, static_cast<unsigned>(FILES_MANIFEST_LEVELS));
} else {
LOG_DEBUG("Got %zu files in manifest", filesManifest.size());
}
} else {
releaseFilesManifest(filesManifest);
}
spiLock->lock();
filesManifest = getFiles("/", 10);
spiLock->unlock();
LOG_DEBUG("Got %d files in manifest", filesManifest.size());
LOG_INFO("Start API client config millis=%u", millis());
// Protect against concurrent BLE callbacks: they run in NimBLE's FreeRTOS task and also touch nodeInfoQueue.
@@ -146,7 +122,8 @@ void PhoneAPI::close()
nodeInfoQueue.clear();
}
packetForPhone = NULL;
releaseFilesManifest(filesManifest);
filesManifest.clear();
filesManifest.shrink_to_fit();
lastPortNumToRadio.clear();
fromRadioNum = 0;
config_nonce = 0;
@@ -555,7 +532,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
if (config_state == filesManifest.size() ||
config_nonce == SPECIAL_NONCE_ONLY_NODES) { // also handles an empty filesManifest
config_state = 0;
releaseFilesManifest(filesManifest);
filesManifest.clear();
// Skip to complete packet
sendConfigComplete();
} else {
-6
View File
@@ -342,10 +342,4 @@ bool RF95Interface::sleep()
return true;
}
int16_t RF95Interface::getCurrentRSSI()
{
float rssi = lora->getRSSI(false);
return (int16_t)round(rssi);
}
#endif
-2
View File
@@ -37,8 +37,6 @@ class RF95Interface : public RadioLibInterface
*/
virtual void disableInterrupt() override;
int16_t getCurrentRSSI() override;
/**
* Enable a particular ISR callback glue function
*/
-2
View File
@@ -32,8 +32,6 @@
#include "STM32WLE5JCInterface.h"
#endif
Observable<uint32_t> RadioInterface::loraRxPacketObservable;
#define RDEF(name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, frequency_switching, wide_lora) \
{ \
meshtastic_Config_LoRaConfig_RegionCode_##name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, \
-9
View File
@@ -123,9 +123,6 @@ class RadioInterface
virtual ~RadioInterface() {}
/// Fires once per valid received LoRa packet (arg = sender NodeNum). Used e.g. to flash LED_LORA.
static Observable<uint32_t> loraRxPacketObservable;
/**
* Coerce LoRa config fields (bandwidth/spread_factor) derived from presets.
* This is used during early bootstrapping so UIs that display these fields directly remain consistent.
@@ -267,12 +264,6 @@ class RadioInterface
*/
virtual void saveChannelNum(uint32_t savedChannelNum);
/**
* Get current RSSI reading from the radio.
* Returns 0 if not available.
*/
virtual int16_t getCurrentRSSI() { return 0; }
private:
/**
* Convert our modemConfig enum into wf, sf, etc...
+8 -100
View File
@@ -1,4 +1,5 @@
#include "RadioLibInterface.h"
#include "MeshLED.h"
#include "MeshTypes.h"
#include "NodeDB.h"
#include "PowerMon.h"
@@ -15,7 +16,6 @@
#include "PortduinoGlue.h"
#include "meshUtils.h"
#endif
void LockingArduinoHal::spiBeginTransaction()
{
spiLock->lock();
@@ -29,7 +29,6 @@ void LockingArduinoHal::spiEndTransaction()
spiLock->unlock();
}
#if ARCH_PORTDUINO
void LockingArduinoHal::spiTransfer(uint8_t *out, size_t len, uint8_t *in)
{
@@ -42,12 +41,6 @@ RadioLibInterface::RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE c
: NotifiedWorkerThread("RadioIf"), module(hal, cs, irq, rst, busy), iface(_iface)
{
instance = this;
// Initialize unused sample slots to a sane default; sample count controls averaging.
for (uint8_t i = 0; i < NOISE_FLOOR_SAMPLES; i++) {
noiseFloorSamples[i] = NOISE_FLOOR_DEFAULT;
}
#if defined(ARCH_STM32WL) && defined(USE_SX1262)
module.setCb_digitalWrite(stm32wl_emulate_digitalWrite);
module.setCb_digitalRead(stm32wl_emulate_digitalRead);
@@ -254,87 +247,6 @@ bool RadioLibInterface::findInTxQueue(NodeNum from, PacketId id)
return txQueue.find(from, id);
}
void RadioLibInterface::updateNoiseFloor()
{
// Only sample from idle receive mode. TX/RX-critical paths must return to radio work quickly.
if (!isReceiving || sendingPacket != NULL || isActivelyReceiving() || isIRQPending()) {
return;
}
uint32_t now = millis();
if (now - lastNoiseFloorUpdate < NOISE_FLOOR_UPDATE_INTERVAL_MS) {
return;
}
lastNoiseFloorUpdate = now;
int16_t rssi = getCurrentRSSI();
if (rssi == NOISE_FLOOR_INVALID || rssi >= 0 || rssi < NOISE_FLOOR_VALID_MIN) {
LOG_DEBUG("Skipping invalid RSSI reading: %d", rssi);
return;
}
noiseFloorSamples[currentSampleIndex] = (int32_t)rssi;
currentSampleIndex++;
if (currentSampleIndex >= NOISE_FLOOR_SAMPLES) {
currentSampleIndex = 0;
isNoiseFloorBufferFull = true;
}
currentNoiseFloor = getAverageNoiseFloorInternal();
LOG_DEBUG("Noise floor: %d dBm (samples: %d, latest: %d dBm)", currentNoiseFloor, getNoiseFloorSampleCountInternal(), rssi);
}
uint8_t RadioLibInterface::getNoiseFloorSampleCountInternal() const
{
return isNoiseFloorBufferFull ? NOISE_FLOOR_SAMPLES : currentSampleIndex;
}
int32_t RadioLibInterface::getAverageNoiseFloorInternal() const
{
uint8_t sampleCount = getNoiseFloorSampleCountInternal();
if (sampleCount == 0) {
return NOISE_FLOOR_DEFAULT;
}
int32_t sum = 0;
for (uint8_t i = 0; i < sampleCount; i++) {
sum += noiseFloorSamples[i];
}
return sum / sampleCount;
}
int32_t RadioLibInterface::getAverageNoiseFloor()
{
return getAverageNoiseFloorInternal();
}
int32_t RadioLibInterface::getNoiseFloor()
{
return currentNoiseFloor;
}
bool RadioLibInterface::hasNoiseFloorSamples()
{
return getNoiseFloorSampleCountInternal() > 0;
}
uint8_t RadioLibInterface::getNoiseFloorSampleCount()
{
return getNoiseFloorSampleCountInternal();
}
void RadioLibInterface::resetNoiseFloor()
{
currentSampleIndex = 0;
isNoiseFloorBufferFull = false;
currentNoiseFloor = NOISE_FLOOR_DEFAULT;
LOG_INFO("Noise floor reset - rolling window collection will restart");
}
bool RadioLibInterface::randomBytes(uint8_t *buffer, size_t length)
{
if (!buffer || length == 0 || !iface) {
@@ -362,7 +274,6 @@ currently active.
*/
void RadioLibInterface::onNotify(uint32_t notification)
{
switch (notification) {
case ISR_TX:
handleTransmitInterrupt();
@@ -494,6 +405,11 @@ bool RadioLibInterface::removePendingTXPacket(NodeNum from, PacketId id, uint32_
return false;
}
/**
* Remove a packet that is eligible for replacement from the TX queue
*/
// void RadioLibInterface::removePending
void RadioLibInterface::handleTransmitInterrupt()
{
// This can be null if we forced the device to enter standby mode. In that case
@@ -509,10 +425,8 @@ void RadioLibInterface::completeSending()
// that can take a long time
auto p = sendingPacket;
sendingPacket = NULL;
#ifdef LED_LORA
digitalWrite(LED_LORA, LED_STATE_OFF);
#endif
meshLED->off();
if (p) {
// Packet has been sent, count it toward our TX airtime utilization.
uint32_t xmitMsec = getPacketTime(p);
@@ -614,10 +528,6 @@ void RadioLibInterface::handleReceiveInterrupt()
printPacket("Lora RX", mp);
#ifdef LED_LORA
loraRxPacketObservable.notifyObservers(mp->from);
#endif
airTime->logAirtime(RX_LOG, rxMsec);
deliverToReceiver(mp);
@@ -688,14 +598,12 @@ bool RadioLibInterface::startSend(meshtastic_MeshPacket *txp)
powerMon->clearState(meshtastic_PowerMon_State_Lora_TXOn); // Transmitter off now
startReceive(); // Restart receive mode (because startTransmit failed to put us in xmit mode)
} else {
meshLED->on();
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register
// bits
enableInterrupt(isrTxLevel0);
lastTxStart = millis();
printPacket("Started Tx", txp);
#ifdef LED_LORA
digitalWrite(LED_LORA, LED_STATE_ON);
#endif
}
return res == RADIOLIB_ERR_NONE;
-56
View File
@@ -99,42 +99,11 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
/// are _trying_ to receive a packet currently (note - we might just be waiting for one)
bool isReceiving = false;
protected:
// Noise floor tracking - rolling window of samples.
static const uint8_t NOISE_FLOOR_SAMPLES = 20;
static const int32_t NOISE_FLOOR_DEFAULT = -120;
static const int32_t NOISE_FLOOR_VALID_MIN = -127;
static const int32_t NOISE_FLOOR_INVALID = -128;
int32_t noiseFloorSamples[NOISE_FLOOR_SAMPLES];
uint8_t currentSampleIndex = 0;
bool isNoiseFloorBufferFull = false;
uint32_t lastNoiseFloorUpdate = 0;
static const uint32_t NOISE_FLOOR_UPDATE_INTERVAL_MS = 5000;
int32_t currentNoiseFloor = NOISE_FLOOR_DEFAULT;
/**
* Pure virtual hook for derived radio interfaces to provide instantaneous RSSI.
* Implementations should return dBm, or an invalid value that updateNoiseFloor()
* can reject.
*/
virtual int16_t getCurrentRSSI() = 0;
public:
/** Our ISR code currently needs this to find our active instance
*/
static RadioLibInterface *instance;
/**
* Get the current calculated noise floor in dBm
* Returns -120 dBm if not yet calibrated
*/
int32_t getNoiseFloor();
/**
* Calculate the average noise floor from collected samples
*/
int32_t getAverageNoiseFloor();
/**
* Glue functions called from ISR land
*/
@@ -203,28 +172,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
virtual bool findInTxQueue(NodeNum from, PacketId id) override;
/**
* Update the noise floor measurement by sampling RSSI from a slow path.
* This should not be called from radio interrupt or TX/RX critical paths.
*/
void updateNoiseFloor();
/**
* Check if we have collected any noise floor samples
*/
bool hasNoiseFloorSamples();
/**
* Get the number of samples in the rolling window
*/
uint8_t getNoiseFloorSampleCount();
/**
* Reset the noise floor calibration
* Will automatically restart collection
*/
void resetNoiseFloor();
/**
* Request randomness sourced from the LoRa modem, if supported by the active RadioLib interface.
* @return true if len bytes were produced, false otherwise.
@@ -232,9 +179,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
bool randomBytes(uint8_t *buffer, size_t length);
private:
uint8_t getNoiseFloorSampleCountInternal() const;
int32_t getAverageNoiseFloorInternal() const;
/** if we have something waiting to send, start a short (random) timer so we can come check for collision before actually
* doing the transmit */
void setTransmitDelay();
-6
View File
@@ -258,12 +258,6 @@ template <typename T> bool SX126xInterface<T>::reconfigure()
return RADIOLIB_ERR_NONE;
}
template <typename T> int16_t SX126xInterface<T>::getCurrentRSSI()
{
float rssi = lora.getRSSI(false);
return (int16_t)round(rssi);
}
template <typename T> void SX126xInterface<T>::disableInterrupt()
{
lora.clearDio1Action();
-2
View File
@@ -41,8 +41,6 @@ template <class T> class SX126xInterface : public RadioLibInterface
*/
T lora;
int16_t getCurrentRSSI() override;
/**
* Glue functions called from ISR land
*/
-6
View File
@@ -326,10 +326,4 @@ template <typename T> bool SX128xInterface<T>::sleep()
return true;
}
template <typename T> int16_t SX128xInterface<T>::getCurrentRSSI()
{
float rssi = lora.getRSSI(false);
return (int16_t)round(rssi);
}
#endif
-2
View File
@@ -35,8 +35,6 @@ template <class T> class SX128xInterface : public RadioLibInterface
*/
T lora;
int16_t getCurrentRSSI() override;
/**
* Glue functions called from ISR land
*/
+1 -10
View File
@@ -30,7 +30,7 @@ PB_BIND(meshtastic_AircraftTrack, meshtastic_AircraftTrack, AUTO)
PB_BIND(meshtastic_CotGeoPoint, meshtastic_CotGeoPoint, AUTO)
PB_BIND(meshtastic_DrawnShape, meshtastic_DrawnShape, AUTO)
PB_BIND(meshtastic_DrawnShape, meshtastic_DrawnShape, 2)
PB_BIND(meshtastic_Marker, meshtastic_Marker, AUTO)
@@ -63,15 +63,6 @@ PB_BIND(meshtastic_TAKEnvironment, meshtastic_TAKEnvironment, AUTO)
PB_BIND(meshtastic_SensorFov, meshtastic_SensorFov, AUTO)
PB_BIND(meshtastic_TakTalkMessage, meshtastic_TakTalkMessage, AUTO)
PB_BIND(meshtastic_TakTalkRoomData, meshtastic_TakTalkRoomData, AUTO)
PB_BIND(meshtastic_Marti, meshtastic_Marti, AUTO)
PB_BIND(meshtastic_TAKPacketV2, meshtastic_TAKPacketV2, 2)
+31 -214
View File
@@ -325,15 +325,7 @@ typedef enum _meshtastic_CotType {
meshtastic_CotType_CotType_b_a_o_c = 123,
/* t-s: Task / engage request. Structured payload carried via the new
TaskRequest typed variant. */
meshtastic_CotType_CotType_t_s = 124,
/* m-t-t: TAKTALK voice/text chat message. Payload carried via the
TakTalkMessage typed variant (text, chatroom_id, lang, from_voice). */
meshtastic_CotType_CotType_m_t_t = 125,
/* y-: TAKTALK room/membership broadcast. Payload carried via the
TakTalkRoomData typed variant (sender_callsign, room_id, room_name,
participants). The CoT type literally has a trailing dash and no
second atom not a typo. */
meshtastic_CotType_CotType_y = 126
meshtastic_CotType_CotType_t_s = 124
} meshtastic_CotType;
/* Geopoint and altitude source */
@@ -561,18 +553,6 @@ typedef struct _meshtastic_GeoChat {
/* Receipt kind discriminator. See ReceiptType doc. Default ReceiptType_None
means this is a regular chat message, not a receipt. */
meshtastic_GeoChat_ReceiptType receipt_type;
/* BCP-47-ish language tag or human-readable name (e.g. "en", "English")
that the originator's TAKTALK plugin recorded for the message. */
pb_callback_t lang;
/* TAKTALK chatroom UUID (e.g. "30b2755c-c547-44ef-a0cc-cdbd8a15616f") that
the receiver's TAKTALK plugin uses to thread the message under the
right room. Resolved to a friendly name via TakTalkRoomData broadcasts. */
pb_callback_t room_id;
/* TAKTALK voice profile pointer. Often empty in practice (the empty
marker `<voice_profile_id/>` still signals TAKTALK origination), so
receivers should treat empty-but-present as the equivalent of the
marker rather than a missing field. */
pb_callback_t voice_profile_id;
} meshtastic_GeoChat;
/* ATAK Group
@@ -735,7 +715,12 @@ typedef struct _meshtastic_DrawnShape {
uint32_t fill_argb;
/* Whether labels are rendered on this shape. */
bool labels_on;
/* True if the sender truncated the vertex columns to fit the pool. */
/* Vertex list for polyline/polygon/rectangle shapes. Capped at 32 by
the nanopb pool; senders MUST truncate longer inputs and set
`truncated = true`. */
pb_size_t vertices_count;
meshtastic_CotGeoPoint vertices[32];
/* True if the sender truncated `vertices` to fit the pool. */
bool truncated; /* --- Bullseye-only fields. All ignored unless kind == Kind_Bullseye. --- */
/* Bullseye distance in meters * 10 (e.g. 3285 = 328.5 m). 0 = unset. */
uint32_t bullseye_distance_dm;
@@ -749,8 +734,6 @@ typedef struct _meshtastic_DrawnShape {
uint8_t bullseye_flags;
/* Bullseye reference UID (anchor marker). Empty = anchor is self. */
char bullseye_uid_ref[48];
pb_callback_t vertex_lat_deltas;
pb_callback_t vertex_lon_deltas;
} meshtastic_DrawnShape;
/* Fixed point of interest: spot marker, waypoint, checkpoint, 2525 symbol,
@@ -1085,87 +1068,6 @@ typedef struct _meshtastic_SensorFov {
pb_callback_t model;
} meshtastic_SensorFov;
/* TAKTALK chat message payload (CoT type m-t-t).
TAKTALK is an ATAK plugin for voice + text team messaging. The voice
audio stream goes over UDP/RTP and is NOT carried by the mesh only
the text envelope (this message) is. `from_voice` marks messages sent
via push-to-talk speech-to-text so receivers can render a mic icon
next to the text.
Wire shape inside <event type="m-t-t">/<detail>:
<callsign>...</callsign> - mapped to TAKPacketV2.callsign
<lang>English</lang> - lang
<text>...</text> - text
<chatroom-id>1</chatroom-id> - chatroom_id
<voice/> - presence sets from_voice = true */
typedef struct _meshtastic_TakTalkMessage {
/* The text body of the TAKTALK message (speech-to-text transcript when
from_voice = true, typed message otherwise). */
pb_callback_t text;
/* TAKTALK chatroom identifier. May be a short id like "1" for the
default room or a UUID like "30b2755c-c547-44ef-a0cc-cdbd8a15616f"
for custom rooms (resolved by TakTalkRoomData broadcasts).
Empty = broadcast room. */
pb_callback_t chatroom_id;
/* BCP-47-ish language tag or human-readable name (e.g. "en", "English").
Empty = unspecified. */
pb_callback_t lang;
/* True when the source CoT carried a <voice/> marker, i.e. the message
originated as push-to-talk speech-to-text. Lets receivers show a mic
icon. Proto3 only encodes when true so empty payload cost is 0 bytes. */
bool from_voice;
} meshtastic_TakTalkMessage;
/* TAKTALK room/membership broadcast (CoT type y-).
Announces a TAKTALK chatroom's friendly name and roster so peers can
resolve room UUIDs (used in TakTalkMessage.chatroom_id and
GeoChat.room_id) to a display name and participant list. Not a chat
message itself these events are emitted by TAKTALK when rooms are
created or memberships change. */
typedef struct _meshtastic_TakTalkRoomData {
/* Callsign of the device broadcasting the room state (typically the
room owner / latest writer).
DEPRECATED in v0.3.2: always equals TAKPacketV2.callsign, so the wire
byte was redundant. Builders stop emitting this field in v0.3.2;
parsers still read it for one release so v0.3.1-encoded packets decode
cleanly. To be removed entirely in v0.4.x. */
pb_callback_t sender_callsign;
/* Room UUID, matches TakTalkMessage.chatroom_id / GeoChat.room_id on
messages routed into this room. */
pb_callback_t room_id;
/* Friendly display name for the room (e.g. "test", "Alpha Team"). */
pb_callback_t room_name;
/* Member callsigns. Wire-encoded as repeated strings; the underlying
CoT carries them as a single <chatroom-participants>A,B,C</> element
which parsers split / builders join on ','. */
pb_callback_t participants;
} meshtastic_TakTalkRoomData;
/* ATAK directed-routing recipient list (CoT <marti><dest callsign='X'/>…</marti>).
Present when an event is addressed to specific TAK users rather than the
broadcast group. TAKTALK gates voice TTS on this element matching the
receiver's callsign; directed b-t-f chats use it for the same purpose. A
missing <marti> means "broadcast to all peers", which is the default for
PLI, alerts, drawings, and most situational-awareness events.
Carried as repeated strings (not indexes into a per-packet table) because
the typical event has 1-2 destinations and table overhead would erase the
savings. Receivers that need the original XML element rebuild it from
dest_callsign on emit. */
typedef struct _meshtastic_Marti {
/* Recipient callsigns. Order is preserved end-to-end so receivers can show
primary-vs-cc distinction the same way ATAK does.
If dest_callsign is [TAKPacketV2.callsign] (self-addressed, unusual but
legal e.g. ATAK echoing back to its own room), the builder still emits
the element so loopback shapes round-trip cleanly. */
pb_callback_t dest_callsign;
} meshtastic_Marti;
typedef PB_BYTES_ARRAY_T(220) meshtastic_TAKPacketV2_raw_detail_t;
/* ATAK v2 packet with expanded CoT field support and zstd dictionary compression.
Sent on ATAK_PLUGIN_V2 port. The wire payload is:
@@ -1187,14 +1089,7 @@ typedef struct _meshtastic_TAKPacketV2 {
int32_t latitude_i;
/* Longitude, multiply by 1e-7 to get degrees in floating point */
int32_t longitude_i;
/* Altitude in meters (HAE). ATAK's "no altitude" sentinel is hae=9999999.0.
NOTE: an earlier v0.4.0 attempt made this `optional` to omit the 9999999
sentinel from the wire, but measurement showed it was net-negative: the
zstd dictionary already compresses the literal 9999999 to ~nothing, while
proto3 `optional` forces a genuine 0 m HAE (common on routes/drawings that
carry hae="0.0" or omit hae parsed as 0) to encode explicitly (+2 bytes),
which REGRESSED the worst-case route fixture. Kept as a plain field. */
/* Altitude in meters (HAE) */
int32_t altitude;
/* Speed in cm/s */
uint32_t speed;
@@ -1240,18 +1135,10 @@ typedef struct _meshtastic_TAKPacketV2 {
/* Sensor field-of-view cone (camera, FLIR, laser, etc.). From <sensor>. */
bool has_sensor_fov;
meshtastic_SensorFov sensor_fov;
/* Directed-routing recipient list (CoT <marti><dest callsign='X'/>…</marti>).
Empty / unset = broadcast to all peers (the default for situational-awareness
events). Populated for TAKTALK m-t-t, directed b-t-f DMs, and any other CoT
shape that ATAK addresses to specific recipients. TAKTALK gates voice TTS
playback on this element matching the receiver's callsign, so dropping it
silently breaks voice messaging end-to-end.
See Marti. */
bool has_marti;
meshtastic_Marti marti;
pb_size_t which_payload_variant;
union {
/* Position report (true = PLI, no extra fields beyond the common ones above) */
bool pli;
/* ATAK GeoChat message */
meshtastic_GeoChat chat;
/* Aircraft track data (ADS-B, military air) */
@@ -1276,14 +1163,6 @@ typedef struct _meshtastic_TAKPacketV2 {
meshtastic_EmergencyAlert emergency;
/* Task / engage request. See TaskRequest. */
meshtastic_TaskRequest task;
/* TAKTALK chat message (CoT type m-t-t). See TakTalkMessage.
Voice audio itself rides UDP/RTP outside the mesh; this carries the
text envelope plus a from_voice marker for receiver UX. */
meshtastic_TakTalkMessage taktalk;
/* TAKTALK room/membership broadcast (CoT type y-). See TakTalkRoomData.
Resolves room UUIDs (used in TakTalkMessage.chatroom_id and
GeoChat.room_id) to display name + roster on receivers. */
meshtastic_TakTalkRoomData taktalk_room;
} payload_variant;
} meshtastic_TAKPacketV2;
@@ -1306,8 +1185,8 @@ extern "C" {
#define _meshtastic_CotHow_ARRAYSIZE ((meshtastic_CotHow)(meshtastic_CotHow_CotHow_m_s+1))
#define _meshtastic_CotType_MIN meshtastic_CotType_CotType_Other
#define _meshtastic_CotType_MAX meshtastic_CotType_CotType_y
#define _meshtastic_CotType_ARRAYSIZE ((meshtastic_CotType)(meshtastic_CotType_CotType_y+1))
#define _meshtastic_CotType_MAX meshtastic_CotType_CotType_t_s
#define _meshtastic_CotType_ARRAYSIZE ((meshtastic_CotType)(meshtastic_CotType_CotType_t_s+1))
#define _meshtastic_GeoPointSource_MIN meshtastic_GeoPointSource_GeoPointSource_Unspecified
#define _meshtastic_GeoPointSource_MAX meshtastic_GeoPointSource_GeoPointSource_NETWORK
@@ -1403,9 +1282,6 @@ extern "C" {
#define meshtastic_SensorFov_type_ENUMTYPE meshtastic_SensorFov_SensorType
#define meshtastic_TAKPacketV2_cot_type_id_ENUMTYPE meshtastic_CotType
#define meshtastic_TAKPacketV2_how_ENUMTYPE meshtastic_CotHow
#define meshtastic_TAKPacketV2_team_ENUMTYPE meshtastic_Team
@@ -1416,14 +1292,14 @@ extern "C" {
/* Initializer values for message structs */
#define meshtastic_TAKPacket_init_default {0, false, meshtastic_Contact_init_default, false, meshtastic_Group_init_default, false, meshtastic_Status_init_default, 0, {meshtastic_PLI_init_default}}
#define meshtastic_GeoChat_init_default {"", false, "", false, "", "", _meshtastic_GeoChat_ReceiptType_MIN, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}}
#define meshtastic_GeoChat_init_default {"", false, "", false, "", "", _meshtastic_GeoChat_ReceiptType_MIN}
#define meshtastic_Group_init_default {_meshtastic_MemberRole_MIN, _meshtastic_Team_MIN}
#define meshtastic_Status_init_default {0}
#define meshtastic_Contact_init_default {"", ""}
#define meshtastic_PLI_init_default {0, 0, 0, 0, 0}
#define meshtastic_AircraftTrack_init_default {"", "", "", "", 0, "", 0, 0, ""}
#define meshtastic_CotGeoPoint_init_default {0, 0}
#define meshtastic_DrawnShape_init_default {_meshtastic_DrawnShape_Kind_MIN, _meshtastic_DrawnShape_StyleMode_MIN, 0, 0, 0, _meshtastic_Team_MIN, 0, 0, _meshtastic_Team_MIN, 0, 0, 0, 0, 0, 0, "", {{NULL}, NULL}, {{NULL}, NULL}}
#define meshtastic_DrawnShape_init_default {_meshtastic_DrawnShape_Kind_MIN, _meshtastic_DrawnShape_StyleMode_MIN, 0, 0, 0, _meshtastic_Team_MIN, 0, 0, _meshtastic_Team_MIN, 0, 0, 0, {meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default}, 0, 0, 0, 0, ""}
#define meshtastic_Marker_init_default {_meshtastic_Marker_Kind_MIN, _meshtastic_Team_MIN, 0, 0, "", "", "", ""}
#define meshtastic_RangeAndBearing_init_default {false, meshtastic_CotGeoPoint_init_default, "", 0, 0, _meshtastic_Team_MIN, 0, 0}
#define meshtastic_Route_init_default {_meshtastic_Route_Method_MIN, _meshtastic_Route_Direction_MIN, "", 0, 0, {meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default}, 0}
@@ -1434,19 +1310,16 @@ extern "C" {
#define meshtastic_TaskRequest_init_default {"", "", "", _meshtastic_TaskRequest_Priority_MIN, _meshtastic_TaskRequest_Status_MIN, ""}
#define meshtastic_TAKEnvironment_init_default {0, 0, 0}
#define meshtastic_SensorFov_init_default {_meshtastic_SensorFov_SensorType_MIN, 0, false, 0, 0, 0, 0, 0, {{NULL}, NULL}}
#define meshtastic_TakTalkMessage_init_default {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, 0}
#define meshtastic_TakTalkRoomData_init_default {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}}
#define meshtastic_Marti_init_default {{{NULL}, NULL}}
#define meshtastic_TAKPacketV2_init_default {_meshtastic_CotType_MIN, _meshtastic_CotHow_MIN, "", _meshtastic_Team_MIN, _meshtastic_MemberRole_MIN, 0, 0, 0, 0, 0, 0, _meshtastic_GeoPointSource_MIN, _meshtastic_GeoPointSource_MIN, "", "", 0, "", "", "", "", "", "", "", {{NULL}, NULL}, false, meshtastic_TAKEnvironment_init_default, false, meshtastic_SensorFov_init_default, false, meshtastic_Marti_init_default, 0, {meshtastic_GeoChat_init_default}}
#define meshtastic_TAKPacketV2_init_default {_meshtastic_CotType_MIN, _meshtastic_CotHow_MIN, "", _meshtastic_Team_MIN, _meshtastic_MemberRole_MIN, 0, 0, 0, 0, 0, 0, _meshtastic_GeoPointSource_MIN, _meshtastic_GeoPointSource_MIN, "", "", 0, "", "", "", "", "", "", "", {{NULL}, NULL}, false, meshtastic_TAKEnvironment_init_default, false, meshtastic_SensorFov_init_default, 0, {0}}
#define meshtastic_TAKPacket_init_zero {0, false, meshtastic_Contact_init_zero, false, meshtastic_Group_init_zero, false, meshtastic_Status_init_zero, 0, {meshtastic_PLI_init_zero}}
#define meshtastic_GeoChat_init_zero {"", false, "", false, "", "", _meshtastic_GeoChat_ReceiptType_MIN, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}}
#define meshtastic_GeoChat_init_zero {"", false, "", false, "", "", _meshtastic_GeoChat_ReceiptType_MIN}
#define meshtastic_Group_init_zero {_meshtastic_MemberRole_MIN, _meshtastic_Team_MIN}
#define meshtastic_Status_init_zero {0}
#define meshtastic_Contact_init_zero {"", ""}
#define meshtastic_PLI_init_zero {0, 0, 0, 0, 0}
#define meshtastic_AircraftTrack_init_zero {"", "", "", "", 0, "", 0, 0, ""}
#define meshtastic_CotGeoPoint_init_zero {0, 0}
#define meshtastic_DrawnShape_init_zero {_meshtastic_DrawnShape_Kind_MIN, _meshtastic_DrawnShape_StyleMode_MIN, 0, 0, 0, _meshtastic_Team_MIN, 0, 0, _meshtastic_Team_MIN, 0, 0, 0, 0, 0, 0, "", {{NULL}, NULL}, {{NULL}, NULL}}
#define meshtastic_DrawnShape_init_zero {_meshtastic_DrawnShape_Kind_MIN, _meshtastic_DrawnShape_StyleMode_MIN, 0, 0, 0, _meshtastic_Team_MIN, 0, 0, _meshtastic_Team_MIN, 0, 0, 0, {meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero}, 0, 0, 0, 0, ""}
#define meshtastic_Marker_init_zero {_meshtastic_Marker_Kind_MIN, _meshtastic_Team_MIN, 0, 0, "", "", "", ""}
#define meshtastic_RangeAndBearing_init_zero {false, meshtastic_CotGeoPoint_init_zero, "", 0, 0, _meshtastic_Team_MIN, 0, 0}
#define meshtastic_Route_init_zero {_meshtastic_Route_Method_MIN, _meshtastic_Route_Direction_MIN, "", 0, 0, {meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero}, 0}
@@ -1457,10 +1330,7 @@ extern "C" {
#define meshtastic_TaskRequest_init_zero {"", "", "", _meshtastic_TaskRequest_Priority_MIN, _meshtastic_TaskRequest_Status_MIN, ""}
#define meshtastic_TAKEnvironment_init_zero {0, 0, 0}
#define meshtastic_SensorFov_init_zero {_meshtastic_SensorFov_SensorType_MIN, 0, false, 0, 0, 0, 0, 0, {{NULL}, NULL}}
#define meshtastic_TakTalkMessage_init_zero {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, 0}
#define meshtastic_TakTalkRoomData_init_zero {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}}
#define meshtastic_Marti_init_zero {{{NULL}, NULL}}
#define meshtastic_TAKPacketV2_init_zero {_meshtastic_CotType_MIN, _meshtastic_CotHow_MIN, "", _meshtastic_Team_MIN, _meshtastic_MemberRole_MIN, 0, 0, 0, 0, 0, 0, _meshtastic_GeoPointSource_MIN, _meshtastic_GeoPointSource_MIN, "", "", 0, "", "", "", "", "", "", "", {{NULL}, NULL}, false, meshtastic_TAKEnvironment_init_zero, false, meshtastic_SensorFov_init_zero, false, meshtastic_Marti_init_zero, 0, {meshtastic_GeoChat_init_zero}}
#define meshtastic_TAKPacketV2_init_zero {_meshtastic_CotType_MIN, _meshtastic_CotHow_MIN, "", _meshtastic_Team_MIN, _meshtastic_MemberRole_MIN, 0, 0, 0, 0, 0, 0, _meshtastic_GeoPointSource_MIN, _meshtastic_GeoPointSource_MIN, "", "", 0, "", "", "", "", "", "", "", {{NULL}, NULL}, false, meshtastic_TAKEnvironment_init_zero, false, meshtastic_SensorFov_init_zero, 0, {0}}
/* Field tags (for use in manual encoding/decoding) */
#define meshtastic_GeoChat_message_tag 1
@@ -1468,9 +1338,6 @@ extern "C" {
#define meshtastic_GeoChat_to_callsign_tag 3
#define meshtastic_GeoChat_receipt_for_uid_tag 4
#define meshtastic_GeoChat_receipt_type_tag 5
#define meshtastic_GeoChat_lang_tag 6
#define meshtastic_GeoChat_room_id_tag 7
#define meshtastic_GeoChat_voice_profile_id_tag 8
#define meshtastic_Group_role_tag 1
#define meshtastic_Group_team_tag 2
#define meshtastic_Status_battery_tag 1
@@ -1510,13 +1377,12 @@ extern "C" {
#define meshtastic_DrawnShape_fill_color_tag 9
#define meshtastic_DrawnShape_fill_argb_tag 10
#define meshtastic_DrawnShape_labels_on_tag 11
#define meshtastic_DrawnShape_vertices_tag 12
#define meshtastic_DrawnShape_truncated_tag 13
#define meshtastic_DrawnShape_bullseye_distance_dm_tag 14
#define meshtastic_DrawnShape_bullseye_bearing_ref_tag 15
#define meshtastic_DrawnShape_bullseye_flags_tag 16
#define meshtastic_DrawnShape_bullseye_uid_ref_tag 17
#define meshtastic_DrawnShape_vertex_lat_deltas_tag 18
#define meshtastic_DrawnShape_vertex_lon_deltas_tag 19
#define meshtastic_Marker_kind_tag 1
#define meshtastic_Marker_color_tag 2
#define meshtastic_Marker_color_argb_tag 3
@@ -1601,15 +1467,6 @@ extern "C" {
#define meshtastic_SensorFov_elevation_deg_tag 6
#define meshtastic_SensorFov_roll_deg_tag 7
#define meshtastic_SensorFov_model_tag 8
#define meshtastic_TakTalkMessage_text_tag 1
#define meshtastic_TakTalkMessage_chatroom_id_tag 2
#define meshtastic_TakTalkMessage_lang_tag 3
#define meshtastic_TakTalkMessage_from_voice_tag 4
#define meshtastic_TakTalkRoomData_sender_callsign_tag 1
#define meshtastic_TakTalkRoomData_room_id_tag 2
#define meshtastic_TakTalkRoomData_room_name_tag 3
#define meshtastic_TakTalkRoomData_participants_tag 4
#define meshtastic_Marti_dest_callsign_tag 1
#define meshtastic_TAKPacketV2_cot_type_id_tag 1
#define meshtastic_TAKPacketV2_how_tag 2
#define meshtastic_TAKPacketV2_callsign_tag 3
@@ -1636,7 +1493,7 @@ extern "C" {
#define meshtastic_TAKPacketV2_remarks_tag 24
#define meshtastic_TAKPacketV2_environment_tag 25
#define meshtastic_TAKPacketV2_sensor_fov_tag 26
#define meshtastic_TAKPacketV2_marti_tag 29
#define meshtastic_TAKPacketV2_pli_tag 30
#define meshtastic_TAKPacketV2_chat_tag 31
#define meshtastic_TAKPacketV2_aircraft_tag 32
#define meshtastic_TAKPacketV2_raw_detail_tag 33
@@ -1647,8 +1504,6 @@ extern "C" {
#define meshtastic_TAKPacketV2_casevac_tag 38
#define meshtastic_TAKPacketV2_emergency_tag 39
#define meshtastic_TAKPacketV2_task_tag 40
#define meshtastic_TAKPacketV2_taktalk_tag 41
#define meshtastic_TAKPacketV2_taktalk_room_tag 42
/* Struct field encoding specification for nanopb */
#define meshtastic_TAKPacket_FIELDLIST(X, a) \
@@ -1672,11 +1527,8 @@ X(a, STATIC, SINGULAR, STRING, message, 1) \
X(a, STATIC, OPTIONAL, STRING, to, 2) \
X(a, STATIC, OPTIONAL, STRING, to_callsign, 3) \
X(a, STATIC, SINGULAR, STRING, receipt_for_uid, 4) \
X(a, STATIC, SINGULAR, UENUM, receipt_type, 5) \
X(a, CALLBACK, OPTIONAL, STRING, lang, 6) \
X(a, CALLBACK, OPTIONAL, STRING, room_id, 7) \
X(a, CALLBACK, OPTIONAL, STRING, voice_profile_id, 8)
#define meshtastic_GeoChat_CALLBACK pb_default_field_callback
X(a, STATIC, SINGULAR, UENUM, receipt_type, 5)
#define meshtastic_GeoChat_CALLBACK NULL
#define meshtastic_GeoChat_DEFAULT NULL
#define meshtastic_Group_FIELDLIST(X, a) \
@@ -1736,15 +1588,15 @@ X(a, STATIC, SINGULAR, UINT32, stroke_weight_x10, 8) \
X(a, STATIC, SINGULAR, UENUM, fill_color, 9) \
X(a, STATIC, SINGULAR, FIXED32, fill_argb, 10) \
X(a, STATIC, SINGULAR, BOOL, labels_on, 11) \
X(a, STATIC, REPEATED, MESSAGE, vertices, 12) \
X(a, STATIC, SINGULAR, BOOL, truncated, 13) \
X(a, STATIC, SINGULAR, UINT32, bullseye_distance_dm, 14) \
X(a, STATIC, SINGULAR, UINT32, bullseye_bearing_ref, 15) \
X(a, STATIC, SINGULAR, UINT32, bullseye_flags, 16) \
X(a, STATIC, SINGULAR, STRING, bullseye_uid_ref, 17) \
X(a, CALLBACK, REPEATED, SINT32, vertex_lat_deltas, 18) \
X(a, CALLBACK, REPEATED, SINT32, vertex_lon_deltas, 19)
#define meshtastic_DrawnShape_CALLBACK pb_default_field_callback
X(a, STATIC, SINGULAR, STRING, bullseye_uid_ref, 17)
#define meshtastic_DrawnShape_CALLBACK NULL
#define meshtastic_DrawnShape_DEFAULT NULL
#define meshtastic_DrawnShape_vertices_MSGTYPE meshtastic_CotGeoPoint
#define meshtastic_Marker_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UENUM, kind, 1) \
@@ -1874,27 +1726,6 @@ X(a, CALLBACK, SINGULAR, STRING, model, 8)
#define meshtastic_SensorFov_CALLBACK pb_default_field_callback
#define meshtastic_SensorFov_DEFAULT NULL
#define meshtastic_TakTalkMessage_FIELDLIST(X, a) \
X(a, CALLBACK, SINGULAR, STRING, text, 1) \
X(a, CALLBACK, SINGULAR, STRING, chatroom_id, 2) \
X(a, CALLBACK, SINGULAR, STRING, lang, 3) \
X(a, STATIC, SINGULAR, BOOL, from_voice, 4)
#define meshtastic_TakTalkMessage_CALLBACK pb_default_field_callback
#define meshtastic_TakTalkMessage_DEFAULT NULL
#define meshtastic_TakTalkRoomData_FIELDLIST(X, a) \
X(a, CALLBACK, SINGULAR, STRING, sender_callsign, 1) \
X(a, CALLBACK, SINGULAR, STRING, room_id, 2) \
X(a, CALLBACK, SINGULAR, STRING, room_name, 3) \
X(a, CALLBACK, REPEATED, STRING, participants, 4)
#define meshtastic_TakTalkRoomData_CALLBACK pb_default_field_callback
#define meshtastic_TakTalkRoomData_DEFAULT NULL
#define meshtastic_Marti_FIELDLIST(X, a) \
X(a, CALLBACK, REPEATED, STRING, dest_callsign, 1)
#define meshtastic_Marti_CALLBACK pb_default_field_callback
#define meshtastic_Marti_DEFAULT NULL
#define meshtastic_TAKPacketV2_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UENUM, cot_type_id, 1) \
X(a, STATIC, SINGULAR, UENUM, how, 2) \
@@ -1922,7 +1753,7 @@ X(a, STATIC, SINGULAR, STRING, cot_type_str, 23) \
X(a, CALLBACK, SINGULAR, STRING, remarks, 24) \
X(a, STATIC, OPTIONAL, MESSAGE, environment, 25) \
X(a, STATIC, OPTIONAL, MESSAGE, sensor_fov, 26) \
X(a, STATIC, OPTIONAL, MESSAGE, marti, 29) \
X(a, STATIC, ONEOF, BOOL, (payload_variant,pli,payload_variant.pli), 30) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,chat,payload_variant.chat), 31) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,aircraft,payload_variant.aircraft), 32) \
X(a, STATIC, ONEOF, BYTES, (payload_variant,raw_detail,payload_variant.raw_detail), 33) \
@@ -1932,14 +1763,11 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,rab,payload_variant.rab), 3
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,route,payload_variant.route), 37) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,casevac,payload_variant.casevac), 38) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,emergency,payload_variant.emergency), 39) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,task,payload_variant.task), 40) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,taktalk,payload_variant.taktalk), 41) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,taktalk_room,payload_variant.taktalk_room), 42)
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,task,payload_variant.task), 40)
#define meshtastic_TAKPacketV2_CALLBACK pb_default_field_callback
#define meshtastic_TAKPacketV2_DEFAULT NULL
#define meshtastic_TAKPacketV2_environment_MSGTYPE meshtastic_TAKEnvironment
#define meshtastic_TAKPacketV2_sensor_fov_MSGTYPE meshtastic_SensorFov
#define meshtastic_TAKPacketV2_marti_MSGTYPE meshtastic_Marti
#define meshtastic_TAKPacketV2_payload_variant_chat_MSGTYPE meshtastic_GeoChat
#define meshtastic_TAKPacketV2_payload_variant_aircraft_MSGTYPE meshtastic_AircraftTrack
#define meshtastic_TAKPacketV2_payload_variant_shape_MSGTYPE meshtastic_DrawnShape
@@ -1949,8 +1777,6 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,taktalk_room,payload_variant
#define meshtastic_TAKPacketV2_payload_variant_casevac_MSGTYPE meshtastic_CasevacReport
#define meshtastic_TAKPacketV2_payload_variant_emergency_MSGTYPE meshtastic_EmergencyAlert
#define meshtastic_TAKPacketV2_payload_variant_task_MSGTYPE meshtastic_TaskRequest
#define meshtastic_TAKPacketV2_payload_variant_taktalk_MSGTYPE meshtastic_TakTalkMessage
#define meshtastic_TAKPacketV2_payload_variant_taktalk_room_MSGTYPE meshtastic_TakTalkRoomData
extern const pb_msgdesc_t meshtastic_TAKPacket_msg;
extern const pb_msgdesc_t meshtastic_GeoChat_msg;
@@ -1971,9 +1797,6 @@ extern const pb_msgdesc_t meshtastic_EmergencyAlert_msg;
extern const pb_msgdesc_t meshtastic_TaskRequest_msg;
extern const pb_msgdesc_t meshtastic_TAKEnvironment_msg;
extern const pb_msgdesc_t meshtastic_SensorFov_msg;
extern const pb_msgdesc_t meshtastic_TakTalkMessage_msg;
extern const pb_msgdesc_t meshtastic_TakTalkRoomData_msg;
extern const pb_msgdesc_t meshtastic_Marti_msg;
extern const pb_msgdesc_t meshtastic_TAKPacketV2_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
@@ -1996,27 +1819,20 @@ extern const pb_msgdesc_t meshtastic_TAKPacketV2_msg;
#define meshtastic_TaskRequest_fields &meshtastic_TaskRequest_msg
#define meshtastic_TAKEnvironment_fields &meshtastic_TAKEnvironment_msg
#define meshtastic_SensorFov_fields &meshtastic_SensorFov_msg
#define meshtastic_TakTalkMessage_fields &meshtastic_TakTalkMessage_msg
#define meshtastic_TakTalkRoomData_fields &meshtastic_TakTalkRoomData_msg
#define meshtastic_Marti_fields &meshtastic_Marti_msg
#define meshtastic_TAKPacketV2_fields &meshtastic_TAKPacketV2_msg
/* Maximum encoded size of messages (where known) */
/* meshtastic_TAKPacket_size depends on runtime parameters */
/* meshtastic_GeoChat_size depends on runtime parameters */
/* meshtastic_DrawnShape_size depends on runtime parameters */
/* meshtastic_CasevacReport_size depends on runtime parameters */
/* meshtastic_ZMistEntry_size depends on runtime parameters */
/* meshtastic_SensorFov_size depends on runtime parameters */
/* meshtastic_TakTalkMessage_size depends on runtime parameters */
/* meshtastic_TakTalkRoomData_size depends on runtime parameters */
/* meshtastic_Marti_size depends on runtime parameters */
/* meshtastic_TAKPacketV2_size depends on runtime parameters */
#define MESHTASTIC_MESHTASTIC_ATAK_PB_H_MAX_SIZE meshtastic_Route_size
#define meshtastic_AircraftTrack_size 134
#define meshtastic_Contact_size 242
#define meshtastic_CotGeoPoint_size 12
#define meshtastic_DrawnShape_size 553
#define meshtastic_EmergencyAlert_size 100
#define meshtastic_GeoChat_size 495
#define meshtastic_Group_size 4
#define meshtastic_Marker_size 191
#define meshtastic_PLI_size 31
@@ -2025,6 +1841,7 @@ extern const pb_msgdesc_t meshtastic_TAKPacketV2_msg;
#define meshtastic_Route_size 1379
#define meshtastic_Status_size 3
#define meshtastic_TAKEnvironment_size 18
#define meshtastic_TAKPacket_size 756
#define meshtastic_TaskRequest_size 132
#ifdef __cplusplus
+5 -7
View File
@@ -290,17 +290,15 @@ typedef enum _meshtastic_Config_LoRaConfig_RegionCode {
meshtastic_Config_LoRaConfig_RegionCode_BR_902 = 26,
/* ITU Region 1 Amateur Radio 2m band (144-146 MHz) */
meshtastic_Config_LoRaConfig_RegionCode_ITU1_2M = 27,
/* ITU Region 2 Amateur Radio 2m band (144-148 MHz) */
meshtastic_Config_LoRaConfig_RegionCode_ITU2_2M = 28,
/* ITU Region 2 / 3 Amateur Radio 2m band (144-148 MHz) */
meshtastic_Config_LoRaConfig_RegionCode_ITU23_2M = 28,
/* EU 866MHz band (Band no. 47b of 2006/771/EC and subsequent amendments) for Non-specific short-range devices (SRD) */
meshtastic_Config_LoRaConfig_RegionCode_EU_866 = 29,
/* EU 874MHz and 917MHz bands (Band no. 1 and 4 of 2022/172/EC and subsequent amendments) for Non-specific short-range devices (SRD) */
meshtastic_Config_LoRaConfig_RegionCode_EU_874 = 30,
meshtastic_Config_LoRaConfig_RegionCode_EU_917 = 31,
/* EU 868MHz band, with narrow presets */
meshtastic_Config_LoRaConfig_RegionCode_EU_N_868 = 32,
/* ITU Region 3 Amateur Radio 2m band (144-148 MHz) */
meshtastic_Config_LoRaConfig_RegionCode_ITU3_2M = 33
meshtastic_Config_LoRaConfig_RegionCode_EU_N_868 = 32
} meshtastic_Config_LoRaConfig_RegionCode;
/* Standard predefined channel settings
@@ -736,8 +734,8 @@ extern "C" {
#define _meshtastic_Config_DisplayConfig_CompassOrientation_ARRAYSIZE ((meshtastic_Config_DisplayConfig_CompassOrientation)(meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED+1))
#define _meshtastic_Config_LoRaConfig_RegionCode_MIN meshtastic_Config_LoRaConfig_RegionCode_UNSET
#define _meshtastic_Config_LoRaConfig_RegionCode_MAX meshtastic_Config_LoRaConfig_RegionCode_ITU3_2M
#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_ITU3_2M+1))
#define _meshtastic_Config_LoRaConfig_RegionCode_MAX meshtastic_Config_LoRaConfig_RegionCode_EU_N_868
#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_EU_N_868+1))
#define _meshtastic_Config_LoRaConfig_ModemPreset_MIN meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST
#define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_NARROW_SLOW
-4
View File
@@ -325,10 +325,6 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_T_IMPULSE_PLUS = 135,
/* Lilygo T-Echo Card */
meshtastic_HardwareModel_T_ECHO_CARD = 136,
/* Seeed Tracker L2 */
meshtastic_HardwareModel_SEEED_WIO_TRACKER_L2 = 137,
/* Elecrow CrowPanel Advance P4 models, ESP32-P4 and TFT with SX1262 radio plugin */
meshtastic_HardwareModel_CROWPANEL_P4 = 138,
/* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */
+2 -10
View File
@@ -83,11 +83,7 @@ void CannedMessageModule::LaunchWithDestination(NodeNum newDest, uint8_t newChan
// Do NOT override explicit broadcast replies
// Only reuse lastDest in LaunchRepeatDestination()
if (newDest == 0) {
dest = NODENUM_BROADCAST;
} else {
dest = newDest;
}
dest = newDest;
channel = newChannel;
lastDest = dest;
@@ -127,11 +123,7 @@ void CannedMessageModule::LaunchFreetextWithDestination(NodeNum newDest, uint8_t
// Do NOT override explicit broadcast replies
// Only reuse lastDest in LaunchRepeatDestination()
if (newDest == 0) {
dest = NODENUM_BROADCAST;
} else {
dest = newDest;
}
dest = newDest;
channel = newChannel;
lastDest = dest;
-38
View File
@@ -1,7 +1,6 @@
#include "StatusLEDModule.h"
#include "MeshService.h"
#include "configuration.h"
#include "mesh/RadioInterface.h"
#include <Arduino.h>
/*
@@ -18,9 +17,6 @@ StatusLEDModule::StatusLEDModule() : concurrency::OSThread("StatusLEDModule")
if (inputBroker)
inputObserver.observe(inputBroker);
#endif
#ifdef LED_LORA
loraRxObserver.observe(&RadioInterface::loraRxPacketObservable);
#endif
#ifdef NEOPIXEL_STATUS_POWER_PIN
powerPixel.begin();
powerPixel.clear();
@@ -94,18 +90,6 @@ int StatusLEDModule::handleInputEvent(const InputEvent *event)
return 0;
}
#endif
#ifdef LED_LORA
int StatusLEDModule::handleLoRaRx(uint32_t)
{
// Briefly flash LED_LORA on each received packet. Turn it on now (we share the main thread with
// the radio's receive handler, so this is safe) and wake runOnce() at flash end to turn it off.
digitalWrite(LED_LORA, LED_STATE_ON);
LORA_LED_state = LED_STATE_ON;
LORA_LED_starttime = millis();
setIntervalFromNow(LORA_RX_LED_FLASH_MS);
return 0;
}
#endif
int32_t StatusLEDModule::runOnce()
{
@@ -131,12 +115,6 @@ int32_t StatusLEDModule::runOnce()
CHARGE_LED_state = LED_STATE_OFF;
}
}
} else {
#if defined(LED_HEARTBEAT)
// If we are using the heartbeat, as in the Thinknode M4, we need to explicitly turn off the charge LED
// This probably implies that in the future we need to stop re-using this bool for multiple purposes.
CHARGE_LED_state = LED_STATE_OFF;
#endif
}
// If we want a LED to be dedicated to the simple hearbeat, we can use that instead of the charge LED
#if defined(LED_HEARTBEAT)
@@ -164,7 +142,6 @@ int32_t StatusLEDModule::runOnce()
}
}
#endif
#ifdef LED_PAIRING
if (!config.bluetooth.enabled || PAIRING_LED_starttime + 30 * 1000 < millis() || doing_fast_blink) {
PAIRING_LED_state = LED_STATE_OFF;
} else if (ble_state == unpaired) {
@@ -179,7 +156,6 @@ int32_t StatusLEDModule::runOnce()
} else {
PAIRING_LED_state = LED_STATE_ON;
}
#endif
// Override if disabled in config
if (config.device.led_heartbeat_disabled) {
@@ -251,20 +227,6 @@ int32_t StatusLEDModule::runOnce()
digitalWrite(Battery_LED_4, chargeIndicatorLED4);
#endif
#ifdef LED_LORA
// End the LoRa-RX flash once its duration has elapsed; otherwise make sure we come back
// exactly at flash end (only ever clamp my_interval down, so other LED timing is preserved).
if (LORA_LED_state == LED_STATE_ON) {
uint32_t elapsed = millis() - LORA_LED_starttime;
if (elapsed >= LORA_RX_LED_FLASH_MS) {
digitalWrite(LED_LORA, LED_STATE_OFF);
LORA_LED_state = LED_STATE_OFF;
} else if ((uint32_t)my_interval > LORA_RX_LED_FLASH_MS - elapsed) {
my_interval = LORA_RX_LED_FLASH_MS - elapsed;
}
}
#endif
return (my_interval);
}
-12
View File
@@ -43,9 +43,6 @@ class StatusLEDModule : private concurrency::OSThread
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
int handleInputEvent(const InputEvent *arg);
#endif
#ifdef LED_LORA
int handleLoRaRx(uint32_t sender);
#endif
void setPowerLED(bool);
@@ -68,10 +65,6 @@ class StatusLEDModule : private concurrency::OSThread
CallbackObserver<StatusLEDModule, const InputEvent *> inputObserver =
CallbackObserver<StatusLEDModule, const InputEvent *>(this, &StatusLEDModule::handleInputEvent);
#endif
#ifdef LED_LORA
CallbackObserver<StatusLEDModule, uint32_t> loraRxObserver =
CallbackObserver<StatusLEDModule, uint32_t>(this, &StatusLEDModule::handleLoRaRx);
#endif
private:
bool CHARGE_LED_state = LED_STATE_OFF;
@@ -84,11 +77,6 @@ class StatusLEDModule : private concurrency::OSThread
uint32_t lastUserbuttonTime = 0;
uint32_t POWER_LED_starttime = 0;
bool doing_fast_blink = false;
#ifdef LED_LORA
static constexpr uint32_t LORA_RX_LED_FLASH_MS = 100;
bool LORA_LED_state = LED_STATE_OFF;
uint32_t LORA_LED_starttime = 0;
#endif
enum PowerState { discharging, charging, charged, critical };
+3 -9
View File
@@ -20,7 +20,6 @@ static constexpr uint16_t TX_HISTORY_KEY_DEVICE_TELEMETRY = 0x8001;
int32_t DeviceTelemetryModule::runOnce()
{
refreshUptime();
uint32_t lastTelemetry = transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_DEVICE_TELEMETRY) : 0;
bool isImpoliteRole = isSensorOrRouterRole();
@@ -126,8 +125,6 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry()
telemetry.variant.local_stats.num_online_nodes = numOnlineNodes;
telemetry.variant.local_stats.num_total_nodes = nodeDB->getNumMeshNodes();
if (RadioLibInterface::instance) {
RadioLibInterface::instance->updateNoiseFloor();
telemetry.variant.local_stats.noise_floor = RadioLibInterface::instance->getAverageNoiseFloor();
telemetry.variant.local_stats.num_packets_tx = RadioLibInterface::instance->txGood;
telemetry.variant.local_stats.num_packets_rx = RadioLibInterface::instance->rxGood + RadioLibInterface::instance->rxBad;
telemetry.variant.local_stats.num_packets_rx_bad = RadioLibInterface::instance->rxBad;
@@ -136,8 +133,6 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry()
}
#ifdef ARCH_PORTDUINO
if (SimRadio::instance) {
if (!RadioLibInterface::instance)
telemetry.variant.local_stats.noise_floor = SimRadio::instance->getCurrentRSSI();
telemetry.variant.local_stats.num_packets_tx = SimRadio::instance->txGood;
telemetry.variant.local_stats.num_packets_rx = SimRadio::instance->rxGood + SimRadio::instance->rxBad;
telemetry.variant.local_stats.num_packets_rx_bad = SimRadio::instance->rxBad;
@@ -153,11 +148,10 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry()
telemetry.variant.local_stats.num_tx_relay_canceled = router->txRelayCanceled;
}
LOG_INFO("Sending local stats: uptime=%i, channel_utilization=%f, air_util_tx=%f, num_online_nodes=%i, num_total_nodes=%i, "
"noise_floor=%d",
LOG_INFO("Sending local stats: uptime=%i, channel_utilization=%f, air_util_tx=%f, num_online_nodes=%i, num_total_nodes=%i",
telemetry.variant.local_stats.uptime_seconds, telemetry.variant.local_stats.channel_utilization,
telemetry.variant.local_stats.air_util_tx, telemetry.variant.local_stats.num_online_nodes,
telemetry.variant.local_stats.num_total_nodes, telemetry.variant.local_stats.noise_floor);
telemetry.variant.local_stats.num_total_nodes);
LOG_INFO("num_packets_tx=%i, num_packets_rx=%i, num_packets_rx_bad=%i", telemetry.variant.local_stats.num_packets_tx,
telemetry.variant.local_stats.num_packets_rx, telemetry.variant.local_stats.num_packets_rx_bad);
@@ -200,4 +194,4 @@ bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
service->sendToMesh(p, RX_SRC_LOCAL, true);
}
return true;
}
}
+62 -98
View File
@@ -15,6 +15,15 @@
WaypointModule *waypointModule;
static inline float degToRad(float deg)
{
return deg * PI / 180.0f;
}
static inline float radToDeg(float rad)
{
return rad * 180.0f / PI;
}
ProcessMessage WaypointModule::handleReceived(const meshtastic_MeshPacket &mp)
{
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
@@ -82,7 +91,9 @@ void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state,
// === Header ===
graphics::drawCommonHeader(display, x, y, titleStr);
const int *textPos = graphics::getTextPositions(display);
const int w = display->getWidth();
const int h = display->getHeight();
// Decode the waypoint
const meshtastic_MeshPacket &mp = devicestate.rx_waypoint;
@@ -97,118 +108,71 @@ void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state,
getTimeAgoStr(sinceReceived(&mp), lastStr, sizeof(lastStr));
// Will contain distance information, passed as a field to drawColumns
char distStr[20] = "";
char distStr[20];
// Get our node, to use our own position
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
// Match compass sizing/placement to favorite node screen logic.
const int w = display->getWidth();
int16_t compassRadius = 8;
int16_t compassX = x + w - compassRadius - 8;
int16_t compassY = y + display->getHeight() / 2;
// Dimensions / co-ordinates for the compass/circle
const uint16_t compassDiam = graphics::CompassRenderer::getCompassDiam(w, h);
const int16_t compassX = x + w - (compassDiam / 2) - 5;
const int16_t compassY = (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT)
? y + h / 2
: y + FONT_HEIGHT_SMALL + (h - FONT_HEIGHT_SMALL) / 2;
if (SCREEN_WIDTH > SCREEN_HEIGHT) {
const int16_t topY = textPos[1];
const int16_t bottomY = SCREEN_HEIGHT - (FONT_HEIGHT_SMALL - 1);
const int16_t usableHeight = bottomY - topY - 5;
compassRadius = usableHeight / 2;
if (compassRadius < 8)
compassRadius = 8;
compassX = x + SCREEN_WIDTH - compassRadius - 8;
compassY = topY + (usableHeight / 2) + ((FONT_HEIGHT_SMALL - 1) / 2) + 2;
} else {
// Waypoint content uses rows 1..4, so place the compass below that block.
const int yBelowContent = textPos[4] + FONT_HEIGHT_SMALL + 2;
const int margin = 4;
#if defined(USE_EINK)
const int iconSize = (graphics::currentResolution == graphics::ScreenResolution::High) ? 16 : 8;
const int navBarHeight = iconSize + 6;
#else
const int navBarHeight = 0;
#endif
const int availableHeight = SCREEN_HEIGHT - yBelowContent - navBarHeight - margin;
if (availableHeight > 0) {
compassRadius = availableHeight / 2;
if (compassRadius < 8)
compassRadius = 8;
if (compassRadius * 2 > SCREEN_WIDTH - 16)
compassRadius = (SCREEN_WIDTH - 16) / 2;
if (compassRadius < 8)
compassRadius = 8;
compassX = x + SCREEN_WIDTH / 2;
compassY = yBelowContent + availableHeight / 2;
}
}
const uint16_t compassDiam = compassRadius * 2;
const bool hasOwnPositionFix = (ourNode && nodeDB->hasValidPosition(ourNode));
const char *statusLine1 = nullptr;
const char *statusLine2 = nullptr;
// Distance only needs our own position fix; compass/bearing additionally needs heading.
if (hasOwnPositionFix) {
// If our node has a position:
if (ourNode && (nodeDB->hasValidPosition(ourNode) || screen->hasHeading())) {
const meshtastic_PositionLite &op = ourNode->position;
const float d =
GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
float myHeading;
if (uiconfig.compass_mode == meshtastic_CompassMode_FREEZE_HEADING) {
myHeading = 0;
} else {
if (screen->hasHeading())
myHeading = degToRad(screen->getHeading());
else
myHeading = screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
}
graphics::CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, (compassDiam / 2));
// Always show distance once we have an own-position fix, even without heading.
// Compass bearing to waypoint
float bearingToOther =
GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(wp.latitude_i), DegD(wp.longitude_i));
// If the top of the compass is a static north then bearingToOther can be drawn on the compass directly
// If the top of the compass is not a static north we need adjust bearingToOther based on heading
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING)
bearingToOther -= myHeading;
graphics::CompassRenderer::drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther);
float bearingToOtherDegrees = (bearingToOther < 0) ? bearingToOther + 2 * PI : bearingToOther;
bearingToOtherDegrees = radToDeg(bearingToOtherDegrees);
// Distance to Waypoint
float d = GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
float feet = d * METERS_TO_FEET;
snprintf(distStr, sizeof(distStr), feet < (2 * MILES_TO_FEET) ? "%.0fft" : "%.1fmi",
feet < (2 * MILES_TO_FEET) ? feet : feet / MILES_TO_FEET);
snprintf(distStr, sizeof(distStr), feet < (2 * MILES_TO_FEET) ? "%.0fft %.0f°" : "%.1fmi %.0f°",
feet < (2 * MILES_TO_FEET) ? feet : feet / MILES_TO_FEET, bearingToOtherDegrees);
} else {
snprintf(distStr, sizeof(distStr), d < 2000 ? "%.0fm" : "%.1fkm", d < 2000 ? d : d / 1000);
snprintf(distStr, sizeof(distStr), d < 2000 ? "%.0fm %.0f°" : "%.1fkm %.0f°", d < 2000 ? d : d / 1000,
bearingToOtherDegrees);
}
float myHeading = 0.0f;
const bool hasHeading =
graphics::CompassRenderer::getHeadingRadians(DegD(op.latitude_i), DegD(op.longitude_i), myHeading);
if (hasHeading) {
// Draw compass circle
display->drawCircle(compassX, compassY, compassRadius);
graphics::CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, compassRadius);
// Compass bearing to waypoint
float bearingToOther =
GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(wp.latitude_i), DegD(wp.longitude_i));
bearingToOther = graphics::CompassRenderer::adjustBearingForCompassMode(bearingToOther, myHeading);
graphics::CompassRenderer::drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther);
const float bearingToOtherDegrees = graphics::CompassRenderer::radiansToDegrees360(bearingToOther);
// Distance to waypoint with relative bearing when heading is available.
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
float feet = d * METERS_TO_FEET;
snprintf(distStr, sizeof(distStr), feet < (2 * MILES_TO_FEET) ? "%.0fft %.0f°" : "%.1fmi %.0f°",
feet < (2 * MILES_TO_FEET) ? feet : feet / MILES_TO_FEET, bearingToOtherDegrees);
} else {
snprintf(distStr, sizeof(distStr), d < 2000 ? "%.0fm %.0f°" : "%.1fkm %.0f°", d < 2000 ? d : d / 1000,
bearingToOtherDegrees);
}
} else {
statusLine1 = "No";
statusLine2 = "Heading";
}
} else {
// No own fix yet, so compass/bearing data would be misleading.
statusLine1 = "No";
statusLine2 = "Fix";
}
if (statusLine1) {
display->drawCircle(compassX, compassY, compassRadius);
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->drawString(compassX, compassY - FONT_HEIGHT_SMALL, statusLine1);
display->drawString(compassX, compassY, statusLine2);
else {
display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?");
// ? in the distance field
snprintf(distStr, sizeof(distStr), "? %s ?°",
(config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) ? "mi" : "km");
}
// Draw compass circle
display->drawCircle(compassX, compassY, compassDiam / 2);
display->setTextAlignment(TEXT_ALIGN_LEFT); // Something above me changes to a different alignment, forcing a fix here!
display->drawString(0, textPos[line++], lastStr);
display->drawString(0, textPos[line++], wp.name);
display->drawString(0, textPos[line++], wp.description);
if (distStr[0])
display->drawString(0, textPos[line++], distStr);
display->drawString(0, graphics::getTextPositions(display)[line++], lastStr);
display->drawString(0, graphics::getTextPositions(display)[line++], wp.name);
display->drawString(0, graphics::getTextPositions(display)[line++], wp.description);
display->drawString(0, graphics::getTextPositions(display)[line++], distStr);
}
#endif

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