Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b01079a281 | ||
|
|
b9a0100c0d | ||
|
|
65c7112a39 | ||
|
|
6c23fcddf0 | ||
|
|
4d7d021ef5 | ||
|
|
025c6854e8 |
@@ -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') }}
|
||||
|
||||
@@ -5,7 +5,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 = [
|
||||
`})`,
|
||||
`, '2C2D3C')})`,
|
||||
`})`,
|
||||
];
|
||||
if (expiresAt) badges.push(`})`);
|
||||
|
||||
// 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',
|
||||
'',
|
||||
`[](${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,
|
||||
});
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
checks: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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" \
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -18,4 +18,4 @@ Lora:
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
spidev: spidev0.0
|
||||
# CS: 8
|
||||
# CS: 8
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
Vendored
-12
@@ -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
|
||||
|
||||
Vendored
+1
-1
@@ -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
@@ -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]
|
||||
|
||||
+1
-1
Submodule protobufs updated: 6b1ded4396...59cb394dcf
+28
-102
@@ -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
@@ -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();
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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
|
||||
@@ -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 */);
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -37,8 +37,6 @@ template <class T> class LR11x0Interface : public RadioLibInterface
|
||||
*/
|
||||
T lora;
|
||||
|
||||
int16_t getCurrentRSSI() override;
|
||||
|
||||
/**
|
||||
* Glue functions called from ISR land
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>();
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -342,10 +342,4 @@ bool RF95Interface::sleep()
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int16_t RF95Interface::getCurrentRSSI()
|
||||
{
|
||||
float rssi = lora->getRSSI(false);
|
||||
return (int16_t)round(rssi);
|
||||
}
|
||||
#endif
|
||||
@@ -37,8 +37,6 @@ class RF95Interface : public RadioLibInterface
|
||||
*/
|
||||
virtual void disableInterrupt() override;
|
||||
|
||||
int16_t getCurrentRSSI() override;
|
||||
|
||||
/**
|
||||
* Enable a particular ISR callback glue function
|
||||
*/
|
||||
|
||||
@@ -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, \
|
||||
|
||||
@@ -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...
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -41,8 +41,6 @@ template <class T> class SX126xInterface : public RadioLibInterface
|
||||
*/
|
||||
T lora;
|
||||
|
||||
int16_t getCurrentRSSI() override;
|
||||
|
||||
/**
|
||||
* Glue functions called from ISR land
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
@@ -35,8 +35,6 @@ template <class T> class SX128xInterface : public RadioLibInterface
|
||||
*/
|
||||
T lora;
|
||||
|
||||
int16_t getCurrentRSSI() override;
|
||||
|
||||
/**
|
||||
* Glue functions called from ISR land
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
------------------------------------------------------------------------------------------------------------------------------------------ */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 };
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user