mirror of
https://github.com/meshtastic/firmware.git
synced 2026-01-30 21:51:51 +00:00
Compare commits
86 Commits
thinknode-
...
no-outgoin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0512493c4 | ||
|
|
ee3f20467c | ||
|
|
e2f0472e2e | ||
|
|
5dd06edd00 | ||
|
|
eeb7373043 | ||
|
|
dbded86dcb | ||
|
|
45fbc0f9d3 | ||
|
|
61b39acc7d | ||
|
|
8af9e7fbdc | ||
|
|
1f7ed6888a | ||
|
|
31bf51b3f2 | ||
|
|
334a4f04cd | ||
|
|
415686dd06 | ||
|
|
b2f2f6b305 | ||
|
|
df400850c1 | ||
|
|
6ab2f02dbc | ||
|
|
d7d6fe7f0f | ||
|
|
4fd0a8276b | ||
|
|
1d219a93ab | ||
|
|
f710cd6ecb | ||
|
|
571c1ac34c | ||
|
|
fb635987d1 | ||
|
|
a922751afc | ||
|
|
c1e3f56324 | ||
|
|
d0562e1ee6 | ||
|
|
4eb4c4b584 | ||
|
|
69a42e1fd2 | ||
|
|
fd498bebad | ||
|
|
23a8b5a66f | ||
|
|
e1e8d6124d | ||
|
|
10b2eae70c | ||
|
|
cfda9bb8ef | ||
|
|
d1edd386b6 | ||
|
|
b6a1020fc5 | ||
|
|
91ad861b26 | ||
|
|
d54ae5dad8 | ||
|
|
c8079d4115 | ||
|
|
90778a4e78 | ||
|
|
63a97a54e1 | ||
|
|
a2e8e232f1 | ||
|
|
7efc3e3770 | ||
|
|
3d58c6e916 | ||
|
|
c038cfe69a | ||
|
|
0770f25e79 | ||
|
|
8a9830282a | ||
|
|
8894a0b711 | ||
|
|
57a3ff8dfc | ||
|
|
6cff13623f | ||
|
|
b312f226b4 | ||
|
|
b627fa720b | ||
|
|
9faf178bdc | ||
|
|
c98f134b40 | ||
|
|
5838b26d90 | ||
|
|
7221fc4d4b | ||
|
|
a417760887 | ||
|
|
04d2dd3b1c | ||
|
|
6b88d37b73 | ||
|
|
d407ec1975 | ||
|
|
6d6a0734b0 | ||
|
|
0157a769c3 | ||
|
|
73932dd1c3 | ||
|
|
bc2abf3db4 | ||
|
|
073eb2c672 | ||
|
|
4744010295 | ||
|
|
d8d02cd6ec | ||
|
|
3e3299f549 | ||
|
|
eefc08087d | ||
|
|
fb6d199d36 | ||
|
|
fb3bf783dd | ||
|
|
fc268d43d0 | ||
|
|
c38aff7e52 | ||
|
|
7d4600f8c2 | ||
|
|
ff50ba4002 | ||
|
|
5c401b8e34 | ||
|
|
c96ebf15fd | ||
|
|
3e4239daf8 | ||
|
|
ab97c0126f | ||
|
|
d34d694731 | ||
|
|
e545897d4e | ||
|
|
caa6ec0e8a | ||
|
|
49accefd8b | ||
|
|
02f24b9015 | ||
|
|
33ae3777a3 | ||
|
|
021106dfe5 | ||
|
|
afbd9e2180 | ||
|
|
64116cd0d3 |
204
.github/workflows/models_issue_triage.yml
vendored
Normal file
204
.github/workflows/models_issue_triage.yml
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
name: Issue Triage (Models)
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
models: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.issue.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
if: ${{ github.repository == 'meshtastic/firmware' && github.event.issue.user.type != 'Bot' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Step 1: Quality check (spam/AI-slop detection) - runs first, exits early if spam
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
- name: Detect spam or low-quality content
|
||||
uses: actions/ai-inference@v2
|
||||
id: quality
|
||||
continue-on-error: true
|
||||
with:
|
||||
max-tokens: 20
|
||||
prompt: |
|
||||
Is this GitHub issue spam, AI-generated slop, or low quality?
|
||||
|
||||
Title: ${{ github.event.issue.title }}
|
||||
Body: ${{ github.event.issue.body }}
|
||||
|
||||
Respond with exactly one of: spam, ai-generated, needs-review, ok
|
||||
system-prompt: You detect spam and low-quality contributions. Be conservative - only flag obvious spam or AI slop.
|
||||
model: openai/gpt-4o-mini
|
||||
|
||||
- name: Apply quality label if needed
|
||||
if: steps.quality.outputs.response != '' && steps.quality.outputs.response != 'ok'
|
||||
uses: actions/github-script@v8
|
||||
env:
|
||||
QUALITY_LABEL: ${{ steps.quality.outputs.response }}
|
||||
with:
|
||||
script: |
|
||||
const label = (process.env.QUALITY_LABEL || '').trim().toLowerCase();
|
||||
const labelMeta = {
|
||||
'spam': { color: 'd73a4a', description: 'Possible spam' },
|
||||
'ai-generated': { color: 'fbca04', description: 'Possible AI-generated low-quality content' },
|
||||
'needs-review': { color: 'f9d0c4', description: 'Needs human review' },
|
||||
};
|
||||
const meta = labelMeta[label];
|
||||
if (!meta) return;
|
||||
|
||||
// Ensure label exists
|
||||
try {
|
||||
await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label });
|
||||
} catch (e) {
|
||||
if (e.status !== 404) throw e;
|
||||
await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description });
|
||||
}
|
||||
|
||||
// Apply label
|
||||
await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.issue.number, labels: [label] });
|
||||
|
||||
// Set output to skip remaining steps
|
||||
core.setOutput('is_spam', 'true');
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Step 2: Duplicate detection - only if not spam
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
- name: Detect duplicate issues
|
||||
if: steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == ''
|
||||
uses: pelikhan/action-genai-issue-dedup@bdb3b5d9451c1090ffcdf123d7447a5e7c7a2528 # v0.0.19
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Step 3: Completeness check + auto-labeling (combined into one AI call)
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
- name: Determine if completeness check should be skipped
|
||||
if: steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == ''
|
||||
uses: actions/github-script@v8
|
||||
id: check-skip
|
||||
with:
|
||||
script: |
|
||||
const title = (context.payload.issue.title || '').toLowerCase();
|
||||
const labels = (context.payload.issue.labels || []).map(label => label.name);
|
||||
const hasFeatureRequest = title.includes('feature request');
|
||||
const hasEnhancement = labels.includes('enhancement');
|
||||
const shouldSkip = hasFeatureRequest && hasEnhancement;
|
||||
core.setOutput('should_skip', shouldSkip ? 'true' : 'false');
|
||||
|
||||
- name: Analyze issue completeness and determine labels
|
||||
if: (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '') && steps.check-skip.outputs.should_skip != 'true'
|
||||
uses: actions/ai-inference@v2
|
||||
id: analysis
|
||||
continue-on-error: true
|
||||
with:
|
||||
prompt: |
|
||||
Analyze this GitHub issue for completeness and determine if it needs labels.
|
||||
|
||||
If this looks like a bug on the device/firmware (crash, reboot, lockup, radio issues, GPS issues, display issues, power/sleep issues), request device logs and explain how to get them:
|
||||
|
||||
Web Flasher logs:
|
||||
- Go to https://flasher.meshtastic.org
|
||||
- Connect the device via USB and click Connect
|
||||
- Open the device console/log output, reproduce the problem, then copy/download and attach/paste the logs
|
||||
|
||||
Meshtastic CLI logs:
|
||||
- Run: meshtastic --port <serial-port> --noproto
|
||||
- Reproduce the problem, then copy/paste the terminal output
|
||||
|
||||
Also request key context if missing: device model/variant, firmware version, region, steps to reproduce, expected vs actual.
|
||||
|
||||
Respond ONLY with JSON:
|
||||
{
|
||||
"complete": true|false,
|
||||
"comment": "Your helpful comment requesting missing info, or empty string if complete",
|
||||
"label": "needs-logs" | "needs-info" | "none"
|
||||
}
|
||||
|
||||
Use "needs-logs" if this is a device bug AND no logs are attached.
|
||||
Use "needs-info" if basic info like firmware version or steps to reproduce are missing.
|
||||
Use "none" if the issue is complete or is a feature request.
|
||||
|
||||
Title: ${{ github.event.issue.title }}
|
||||
Body: ${{ github.event.issue.body }}
|
||||
system-prompt: You are a helpful assistant that triages GitHub issues. Be conservative with labels.
|
||||
model: openai/gpt-4o-mini
|
||||
|
||||
- name: Process analysis result
|
||||
if: (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '') && steps.check-skip.outputs.should_skip != 'true' && steps.analysis.outputs.response != ''
|
||||
uses: actions/github-script@v8
|
||||
id: process
|
||||
env:
|
||||
AI_RESPONSE: ${{ steps.analysis.outputs.response }}
|
||||
with:
|
||||
script: |
|
||||
const raw = (process.env.AI_RESPONSE || '').trim();
|
||||
|
||||
let complete = false;
|
||||
let comment = '';
|
||||
let label = 'none';
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
complete = !!parsed.complete;
|
||||
comment = (parsed.comment ?? '').toString().trim();
|
||||
label = (parsed.label ?? 'none').toString().trim().toLowerCase();
|
||||
} catch {
|
||||
// If JSON parse fails, treat as incomplete with raw response as comment
|
||||
complete = false;
|
||||
comment = raw;
|
||||
label = 'none';
|
||||
}
|
||||
|
||||
// Validate label
|
||||
const allowedLabels = new Set(['needs-logs', 'needs-info', 'none']);
|
||||
if (!allowedLabels.has(label)) label = 'none';
|
||||
|
||||
core.setOutput('should_comment', (!complete && comment.length > 0) ? 'true' : 'false');
|
||||
core.setOutput('comment_body', comment);
|
||||
core.setOutput('label', label);
|
||||
|
||||
- name: Apply triage label
|
||||
if: steps.process.outputs.label != '' && steps.process.outputs.label != 'none'
|
||||
uses: actions/github-script@v8
|
||||
env:
|
||||
LABEL_NAME: ${{ steps.process.outputs.label }}
|
||||
with:
|
||||
script: |
|
||||
const label = process.env.LABEL_NAME;
|
||||
const labelMeta = {
|
||||
'needs-logs': { color: 'cfd3d7', description: 'Device logs requested for triage' },
|
||||
'needs-info': { color: 'f9d0c4', description: 'More information requested for triage' },
|
||||
};
|
||||
const meta = labelMeta[label];
|
||||
if (!meta) return;
|
||||
|
||||
// Ensure label exists
|
||||
try {
|
||||
await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label });
|
||||
} catch (e) {
|
||||
if (e.status !== 404) throw e;
|
||||
await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description });
|
||||
}
|
||||
|
||||
// Apply label
|
||||
await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.issue.number, labels: [label] });
|
||||
|
||||
- name: Comment on issue
|
||||
if: steps.process.outputs.should_comment == 'true'
|
||||
uses: actions/github-script@v8
|
||||
env:
|
||||
COMMENT_BODY: ${{ steps.process.outputs.comment_body }}
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.issue.number,
|
||||
body: process.env.COMMENT_BODY
|
||||
});
|
||||
138
.github/workflows/models_pr_triage.yml
vendored
Normal file
138
.github/workflows/models_pr_triage.yml
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
name: PR Triage (Models)
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
issues: write
|
||||
models: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
if: ${{ github.repository == 'meshtastic/firmware' && github.event.pull_request.user.type != 'Bot' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Step 1: Check if PR already has automation/type labels (skip if so)
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
- name: Check existing labels
|
||||
uses: actions/github-script@v8
|
||||
id: check-labels
|
||||
with:
|
||||
script: |
|
||||
const skipLabels = new Set(['automation']);
|
||||
const typeLabels = new Set(['bugfix', 'hardware-support', 'enhancement', 'dependencies', 'submodules', 'github_actions', 'trunk', 'cleanup']);
|
||||
const prLabels = context.payload.pull_request.labels.map(l => l.name);
|
||||
|
||||
const shouldSkipAll = prLabels.some(l => skipLabels.has(l));
|
||||
const hasTypeLabel = prLabels.some(l => typeLabels.has(l));
|
||||
|
||||
core.setOutput('skip_all', shouldSkipAll ? 'true' : 'false');
|
||||
core.setOutput('has_type_label', hasTypeLabel ? 'true' : 'false');
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Step 2: Quality check (spam/AI-slop detection)
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
- name: Detect spam or low-quality content
|
||||
if: steps.check-labels.outputs.skip_all != 'true'
|
||||
uses: actions/ai-inference@v2
|
||||
id: quality
|
||||
continue-on-error: true
|
||||
with:
|
||||
max-tokens: 20
|
||||
prompt: |
|
||||
Is this GitHub pull request spam, AI-generated slop, or low quality?
|
||||
|
||||
Title: ${{ github.event.pull_request.title }}
|
||||
Body: ${{ github.event.pull_request.body }}
|
||||
|
||||
Respond with exactly one of: spam, ai-generated, needs-review, ok
|
||||
system-prompt: You detect spam and low-quality contributions. Be conservative - only flag obvious spam or AI slop.
|
||||
model: openai/gpt-4o-mini
|
||||
|
||||
- name: Apply quality label if needed
|
||||
if: steps.check-labels.outputs.skip_all != 'true' && steps.quality.outputs.response != '' && steps.quality.outputs.response != 'ok'
|
||||
uses: actions/github-script@v8
|
||||
id: quality-label
|
||||
env:
|
||||
QUALITY_LABEL: ${{ steps.quality.outputs.response }}
|
||||
with:
|
||||
script: |
|
||||
const label = (process.env.QUALITY_LABEL || '').trim().toLowerCase();
|
||||
const labelMeta = {
|
||||
'spam': { color: 'd73a4a', description: 'Possible spam' },
|
||||
'ai-generated': { color: 'fbca04', description: 'Possible AI-generated low-quality content' },
|
||||
'needs-review': { color: 'f9d0c4', description: 'Needs human review' },
|
||||
};
|
||||
const meta = labelMeta[label];
|
||||
if (!meta) return;
|
||||
|
||||
// Ensure label exists
|
||||
try {
|
||||
await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label });
|
||||
} catch (e) {
|
||||
if (e.status !== 404) throw e;
|
||||
await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description });
|
||||
}
|
||||
|
||||
// Apply label
|
||||
await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, labels: [label] });
|
||||
|
||||
core.setOutput('is_spam', 'true');
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Step 3: Auto-label PR type (bugfix/hardware-support/enhancement)
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
- name: Classify PR for labeling
|
||||
if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '')
|
||||
uses: actions/ai-inference@v2
|
||||
id: classify
|
||||
continue-on-error: true
|
||||
with:
|
||||
max-tokens: 30
|
||||
prompt: |
|
||||
Classify this pull request into exactly one category.
|
||||
|
||||
Return exactly one of: bugfix, hardware-support, enhancement
|
||||
|
||||
Use bugfix if it fixes a bug, crash, or incorrect behavior.
|
||||
Use hardware-support if it adds or improves support for a specific hardware device/variant.
|
||||
Use enhancement if it adds a new feature, improves performance, or refactors code.
|
||||
|
||||
Title: ${{ github.event.pull_request.title }}
|
||||
Body: ${{ github.event.pull_request.body }}
|
||||
system-prompt: You classify pull requests into categories. Be conservative and pick the most appropriate single label.
|
||||
model: openai/gpt-4o-mini
|
||||
|
||||
- name: Apply type label
|
||||
if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && steps.classify.outputs.response != ''
|
||||
uses: actions/github-script@v8
|
||||
env:
|
||||
TYPE_LABEL: ${{ steps.classify.outputs.response }}
|
||||
with:
|
||||
script: |
|
||||
const label = (process.env.TYPE_LABEL || '').trim().toLowerCase();
|
||||
const labelMeta = {
|
||||
'bugfix': { color: 'd73a4a', description: 'Bug fix' },
|
||||
'hardware-support': { color: '0e8a16', description: 'Hardware support addition or improvement' },
|
||||
'enhancement': { color: 'a2eeef', description: 'New feature or enhancement' },
|
||||
};
|
||||
const meta = labelMeta[label];
|
||||
if (!meta) return;
|
||||
|
||||
// Ensure label exists
|
||||
try {
|
||||
await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label });
|
||||
} catch (e) {
|
||||
if (e.status !== 404) throw e;
|
||||
await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description });
|
||||
}
|
||||
|
||||
// Apply label
|
||||
await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, labels: [label] });
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -50,3 +50,6 @@ idf_component.yml
|
||||
CMakeLists.txt
|
||||
/sdkconfig.*
|
||||
.dummy/*
|
||||
|
||||
# PYTHONPATH used by the Nix shell
|
||||
.python3
|
||||
|
||||
@@ -9,14 +9,14 @@ plugins:
|
||||
lint:
|
||||
enabled:
|
||||
- checkov@3.2.497
|
||||
- renovate@42.81.8
|
||||
- renovate@42.84.2
|
||||
- prettier@3.8.0
|
||||
- trufflehog@3.92.4
|
||||
- trufflehog@3.92.5
|
||||
- yamllint@1.38.0
|
||||
- bandit@1.9.2
|
||||
- bandit@1.9.3
|
||||
- trivy@0.68.2
|
||||
- taplo@0.10.0
|
||||
- ruff@0.14.11
|
||||
- ruff@0.14.13
|
||||
- isort@7.0.0
|
||||
- markdownlint@0.47.0
|
||||
- oxipng@10.0.0
|
||||
@@ -26,7 +26,7 @@ lint:
|
||||
- hadolint@2.14.0
|
||||
- shfmt@3.6.0
|
||||
- shellcheck@0.11.0
|
||||
- black@25.12.0
|
||||
- black@26.1.0
|
||||
- git-diff-check
|
||||
- gitleaks@8.30.0
|
||||
- clang-format@16.0.3
|
||||
|
||||
23
bin/config.d/lora-usb-umesh-1262-30dbm.yaml
Normal file
23
bin/config.d/lora-usb-umesh-1262-30dbm.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
Lora:
|
||||
Module: sx1262
|
||||
CS: 0
|
||||
IRQ: 6
|
||||
Reset: 1
|
||||
Busy: 4
|
||||
RXen: 2
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
spidev: ch341
|
||||
USB_PID: 0x5512
|
||||
USB_VID: 0x1A86
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
# USB_Serialnum: 12345678
|
||||
SX126X_MAX_POWER: 22
|
||||
# Reduce output power to improve EMI
|
||||
NUM_PA_POINTS: 22
|
||||
TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7
|
||||
# Note: This module integrates an additional PA to achieve higher output power.
|
||||
# The 'power' parameter here does not represent the actual RF output.
|
||||
# TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (1–22 dBm).
|
||||
# Each array element corresponds to the additional gain when that input level is set,
|
||||
# The effective RF output is: Pout ≈ Pset + TX_GAIN_LORA[index].
|
||||
# Please refer to https://github.com/linser233/uMesh/blob/main/RF_Power.md for detailed information.
|
||||
@@ -1,15 +0,0 @@
|
||||
Lora:
|
||||
Module: sx1262
|
||||
CS: 0
|
||||
IRQ: 6
|
||||
Reset: 1
|
||||
Busy: 4
|
||||
RXen: 2
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
spidev: ch341
|
||||
USB_PID: 0x5512
|
||||
USB_VID: 0x1A86
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
# USB_Serialnum: 12345678
|
||||
SX126X_MAX_POWER: 30
|
||||
# Reduce output power to improve EMI
|
||||
23
bin/config.d/lora-usb-umesh-1268-30dbm.yaml
Normal file
23
bin/config.d/lora-usb-umesh-1268-30dbm.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
Lora:
|
||||
Module: sx1268
|
||||
CS: 0
|
||||
IRQ: 6
|
||||
Reset: 1
|
||||
Busy: 4
|
||||
RXen: 2
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
spidev: ch341
|
||||
USB_PID: 0x5512
|
||||
USB_VID: 0x1A86
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
# USB_Serialnum: 12345678
|
||||
SX126X_MAX_POWER: 22
|
||||
# Reduce output power to improve EMI
|
||||
NUM_PA_POINTS: 22
|
||||
TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7
|
||||
# Note: This module integrates an additional PA to achieve higher output power.
|
||||
# The 'power' parameter here does not represent the actual RF output.
|
||||
# TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (1–22 dBm).
|
||||
# Each array element corresponds to the additional gain when that input level is set,
|
||||
# The effective RF output is: Pout ≈ Pset + TX_GAIN_LORA[index].
|
||||
# Please refer to https://github.com/linser233/uMesh/blob/main/RF_Power.md for detailed information.
|
||||
@@ -1,15 +0,0 @@
|
||||
Lora:
|
||||
Module: sx1268
|
||||
CS: 0
|
||||
IRQ: 6
|
||||
Reset: 1
|
||||
Busy: 4
|
||||
RXen: 2
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
spidev: ch341
|
||||
USB_PID: 0x5512
|
||||
USB_VID: 0x1A86
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
# USB_Serialnum: 12345678
|
||||
SX126X_MAX_POWER: 30
|
||||
# Reduce output power to improve EMI
|
||||
@@ -32,6 +32,19 @@ if ! command -v jq >/dev/null 2>&1; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# esptool v5 supports commands with dashes and deprecates commands with
|
||||
# underscores. Prior versions only support commands with underscores
|
||||
if ${ESPTOOL_CMD} | grep --quiet write-flash
|
||||
then
|
||||
ESPTOOL_WRITE_FLASH=write-flash
|
||||
ESPTOOL_ERASE_FLASH=erase-flash
|
||||
ESPTOOL_READ_FLASH_STATUS=read-flash-status
|
||||
else
|
||||
ESPTOOL_WRITE_FLASH=write_flash
|
||||
ESPTOOL_ERASE_FLASH=erase_flash
|
||||
ESPTOOL_READ_FLASH_STATUS=read_flash_status
|
||||
fi
|
||||
|
||||
set -e
|
||||
|
||||
# Usage info
|
||||
@@ -83,8 +96,8 @@ while [ $# -gt 0 ]; do
|
||||
done
|
||||
|
||||
if [[ $BPS_RESET == true ]]; then
|
||||
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
|
||||
exit 0
|
||||
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset ${ESPTOOL_READ_FLASH_STATUS}
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[ -z "$FILENAME" ] && [ -n "$1" ] && {
|
||||
@@ -144,12 +157,12 @@ if [[ -f "$FILENAME" && "$FILENAME" == *.factory.bin ]]; then
|
||||
fi
|
||||
|
||||
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
|
||||
$ESPTOOL_CMD erase-flash
|
||||
$ESPTOOL_CMD write-flash $FIRMWARE_OFFSET "${FILENAME}"
|
||||
$ESPTOOL_CMD ${ESPTOOL_ERASE_FLASH}
|
||||
$ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $FIRMWARE_OFFSET "${FILENAME}"
|
||||
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
|
||||
$ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}"
|
||||
$ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $OTA_OFFSET "${OTAFILE}"
|
||||
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
|
||||
$ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}"
|
||||
$ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $OFFSET "${SPIFFSFILE}"
|
||||
|
||||
else
|
||||
show_help
|
||||
|
||||
@@ -20,6 +20,17 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# esptool v5 supports commands with dashes and deprecates commands with
|
||||
# underscores. Prior versions only support commands with underscores
|
||||
if ${ESPTOOL_CMD} | grep --quiet write-flash
|
||||
then
|
||||
ESPTOOL_WRITE_FLASH=write-flash
|
||||
ESPTOOL_READ_FLASH_STATUS=read-flash-status
|
||||
else
|
||||
ESPTOOL_WRITE_FLASH=write_flash
|
||||
ESPTOOL_READ_FLASH_STATUS=read_flash_status
|
||||
fi
|
||||
|
||||
# Usage info
|
||||
show_help() {
|
||||
cat << EOF
|
||||
@@ -69,7 +80,7 @@ done
|
||||
shift "$((OPTIND-1))"
|
||||
|
||||
if [ "$CHANGE_MODE" = true ]; then
|
||||
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
|
||||
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset ${ESPTOOL_READ_FLASH_STATUS}
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -80,7 +91,7 @@ fi
|
||||
|
||||
if [[ -f "$FILENAME" && "$FILENAME" != *.factory.bin ]]; then
|
||||
echo "Trying to flash update ${FILENAME}"
|
||||
$ESPTOOL_CMD --baud $FLASH_BAUD write-flash $UPDATE_OFFSET "${FILENAME}"
|
||||
$ESPTOOL_CMD --baud $FLASH_BAUD ${ESPTOOL_WRITE_FLASH} $UPDATE_OFFSET "${FILENAME}"
|
||||
else
|
||||
show_help
|
||||
echo "Invalid file: ${FILENAME}"
|
||||
|
||||
@@ -87,6 +87,9 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="2.7.19" date="2026-01-22">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.19</url>
|
||||
</release>
|
||||
<release version="2.7.18" date="2026-01-02">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.18</url>
|
||||
</release>
|
||||
|
||||
50
boards/minimesh_lite.json
Normal file
50
boards/minimesh_lite.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DMINIMESH_LITE -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x239A", "0x8029"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"]
|
||||
],
|
||||
"usb_product": "Minimesh Lite",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "dls_Minimesh_Lite",
|
||||
"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",
|
||||
"svd_path": "nrf52840.svd"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Minimesh Lite",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": ["nrfutil", "jlink", "nrfjprog", "stlink"],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://deeplabstudio.com",
|
||||
"vendor": "Deeplab Studio"
|
||||
}
|
||||
6
debian/changelog
vendored
6
debian/changelog
vendored
@@ -1,3 +1,9 @@
|
||||
meshtasticd (2.7.19.0) unstable; urgency=medium
|
||||
|
||||
* Version 2.7.19
|
||||
|
||||
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Thu, 22 Jan 2026 22:17:40 +0000
|
||||
|
||||
meshtasticd (2.7.18.0) unstable; urgency=medium
|
||||
|
||||
* Version 2.7.18
|
||||
|
||||
44
flake.lock
generated
Normal file
44
flake.lock
generated
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1767039857,
|
||||
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
||||
"owner": "NixOS",
|
||||
"repo": "flake-compat",
|
||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1766314097,
|
||||
"narHash": "sha256-laJftWbghBehazn/zxVJ8NdENVgjccsWAdAqKXhErrM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "306ea70f9eb0fb4e040f8540e2deab32ed7e2055",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
66
flake.nix
Normal file
66
flake.nix
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
description = "Nix flake to compile Meshtastic firmware";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
|
||||
# Shim to make flake.nix work with stable Nix.
|
||||
flake-compat = {
|
||||
url = "github:NixOS/flake-compat";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
inputs:
|
||||
let
|
||||
lib = inputs.nixpkgs.lib;
|
||||
|
||||
forAllSystems =
|
||||
fn:
|
||||
lib.genAttrs lib.systems.flakeExposed (
|
||||
system:
|
||||
fn {
|
||||
pkgs = import inputs.nixpkgs {
|
||||
inherit system;
|
||||
};
|
||||
inherit system;
|
||||
}
|
||||
);
|
||||
in
|
||||
{
|
||||
devShells = forAllSystems (
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
python3 = pkgs.python312.withPackages (
|
||||
ps: with ps; [
|
||||
google
|
||||
]
|
||||
);
|
||||
in
|
||||
{
|
||||
default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
python3
|
||||
platformio
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
# Set up PlatformIO to use a local core directory.
|
||||
export PLATFORMIO_CORE_DIR=$PWD/.platformio
|
||||
# Tell pip to put packages into $PIP_PREFIX instead of the usual
|
||||
# location. This is especially necessary under NixOS to avoid having
|
||||
# pip trying to write to the read-only Nix store. For more info,
|
||||
# see https://wiki.nixos.org/wiki/Python
|
||||
export PIP_PREFIX=$PWD/.python3
|
||||
export PYTHONPATH="$PIP_PREFIX/${python3.sitePackages}"
|
||||
export PATH="$PIP_PREFIX/bin:$PATH"
|
||||
# Avoids reproducibility issues with some Python packages
|
||||
# See https://nixos.org/manual/nixpkgs/stable/#python-setup.py-bdist_wheel-cannot-create-.whl
|
||||
unset SOURCE_DATE_EPOCH
|
||||
'';
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -43,13 +43,11 @@ class Esp32C3ExceptionDecoder(DeviceMonitorFilterBase):
|
||||
self.enabled = self.setup_paths()
|
||||
|
||||
if self.config.get("env:" + self.environment, "build_type") != "debug":
|
||||
print(
|
||||
"""
|
||||
print("""
|
||||
Please build project in debug configuration to get more details about an exception.
|
||||
See https://docs.platformio.org/page/projectconf/build_configurations.html
|
||||
|
||||
"""
|
||||
)
|
||||
""")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ build_flags = -Wno-missing-field-initializers
|
||||
-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
|
||||
-DMESHTASTIC_EXCLUDE_POWERMON=1
|
||||
-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
|
||||
-DLED_BUILTIN=-1
|
||||
#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
|
||||
#-D OLED_PL=1
|
||||
#-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs
|
||||
@@ -119,7 +120,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/5a870c623a4e9ab7a7abe3d02950536f107d1a31.zip
|
||||
https://github.com/meshtastic/device-ui/archive/69739b84f87a91568d3c421498bc89977937a141.zip
|
||||
|
||||
; Common libs for environmental measurements in telemetry module
|
||||
[environmental_base]
|
||||
@@ -212,3 +213,30 @@ lib_deps =
|
||||
sensirion/Sensirion Core@0.7.2
|
||||
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
|
||||
sensirion/Sensirion I2C SCD4x@1.1.0
|
||||
; Same as environmental_extra but without BSEC (saves ~3.5KB DRAM for original ESP32 targets)
|
||||
[environmental_extra_no_bsec]
|
||||
lib_deps =
|
||||
# renovate: datasource=custom.pio depName=Adafruit BMP3XX packageName=adafruit/library/Adafruit BMP3XX Library
|
||||
adafruit/Adafruit BMP3XX Library@2.1.6
|
||||
# renovate: datasource=custom.pio depName=Adafruit MAX1704X packageName=adafruit/library/Adafruit MAX1704X
|
||||
adafruit/Adafruit MAX1704X@1.0.3
|
||||
# renovate: datasource=custom.pio depName=Adafruit SHTC3 packageName=adafruit/library/Adafruit SHTC3 Library
|
||||
adafruit/Adafruit SHTC3 Library@1.0.2
|
||||
# renovate: datasource=custom.pio depName=Adafruit LPS2X packageName=adafruit/library/Adafruit LPS2X
|
||||
adafruit/Adafruit LPS2X@2.0.6
|
||||
# renovate: datasource=custom.pio depName=Adafruit SHT31 packageName=adafruit/library/Adafruit SHT31 Library
|
||||
adafruit/Adafruit SHT31 Library@2.2.2
|
||||
# renovate: datasource=custom.pio depName=Adafruit VEML7700 packageName=adafruit/library/Adafruit VEML7700 Library
|
||||
adafruit/Adafruit VEML7700 Library@2.1.6
|
||||
# renovate: datasource=custom.pio depName=Adafruit SHT4x packageName=adafruit/library/Adafruit SHT4x Library
|
||||
adafruit/Adafruit SHT4x Library@1.0.5
|
||||
# renovate: datasource=custom.pio depName=SparkFun Qwiic Scale NAU7802 packageName=sparkfun/library/SparkFun Qwiic Scale NAU7802 Arduino Library
|
||||
sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@1.0.6
|
||||
# renovate: datasource=custom.pio depName=ClosedCube OPT3001 packageName=closedcube/library/ClosedCube OPT3001
|
||||
closedcube/ClosedCube OPT3001@1.1.2
|
||||
# renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master
|
||||
https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip
|
||||
# renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core
|
||||
sensirion/Sensirion Core@0.7.2
|
||||
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
|
||||
sensirion/Sensirion I2C SCD4x@1.1.0
|
||||
Submodule protobufs updated: 4b9f104a18...bc63a57f9e
12
shell.nix
Normal file
12
shell.nix
Normal file
@@ -0,0 +1,12 @@
|
||||
(import (
|
||||
let
|
||||
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
||||
nodeName = lock.nodes.root.inputs.flake-compat;
|
||||
in
|
||||
fetchTarball {
|
||||
url =
|
||||
lock.nodes.${nodeName}.locked.url
|
||||
or "https://github.com/NixOS/flake-compat/archive/${lock.nodes.${nodeName}.locked.rev}.tar.gz";
|
||||
sha256 = lock.nodes.${nodeName}.locked.narHash;
|
||||
}
|
||||
) { src = ./.; }).shellNix
|
||||
@@ -13,11 +13,6 @@ extern MemGet memGet;
|
||||
#define LED_STATE_ON 1
|
||||
#endif
|
||||
|
||||
// WIFI LED
|
||||
#ifndef WIFI_STATE_ON
|
||||
#define WIFI_STATE_ON 1
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// DEBUG
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -152,9 +147,7 @@ extern "C" void logLegacy(const char *level, const char *fmt, ...);
|
||||
// Default Bluetooth PIN
|
||||
#define defaultBLEPin 123456
|
||||
|
||||
#if HAS_ETHERNET && defined(USE_CH390D)
|
||||
#include <ESP32_CH390.h>
|
||||
#elif HAS_ETHERNET && !defined(USE_WS5500)
|
||||
#if HAS_ETHERNET && !defined(USE_WS5500)
|
||||
#include <RAK13800_W5100S.h>
|
||||
#endif // HAS_ETHERNET
|
||||
|
||||
|
||||
207
src/Power.cpp
207
src/Power.cpp
@@ -1,11 +1,14 @@
|
||||
/**
|
||||
* @file Power.cpp
|
||||
* @brief This file contains the implementation of the Power class, which is responsible for managing power-related functionality
|
||||
* of the device. It includes battery level sensing, power management unit (PMU) control, and power state machine management. The
|
||||
* Power class is used by the main device class to manage power-related functionality.
|
||||
* @brief This file contains the implementation of the Power class, which is
|
||||
* responsible for managing power-related functionality of the device. It
|
||||
* includes battery level sensing, power management unit (PMU) control, and
|
||||
* power state machine management. The Power class is used by the main device
|
||||
* class to manage power-related functionality.
|
||||
*
|
||||
* The file also includes implementations of various battery level sensors, such as the AnalogBatteryLevel class, which assumes
|
||||
* the battery voltage is attached via a voltage-divider to an analog input.
|
||||
* The file also includes implementations of various battery level sensors, such
|
||||
* as the AnalogBatteryLevel class, which assumes the battery voltage is
|
||||
* attached via a voltage-divider to an analog input.
|
||||
*
|
||||
* This file is part of the Meshtastic project.
|
||||
* For more information, see: https://meshtastic.org/
|
||||
@@ -19,6 +22,7 @@
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include "meshUtils.h"
|
||||
#include "power/PowerHAL.h"
|
||||
#include "sleep.h"
|
||||
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
@@ -171,22 +175,12 @@ Power *power;
|
||||
|
||||
using namespace meshtastic;
|
||||
|
||||
#ifndef AREF_VOLTAGE
|
||||
#if defined(ARCH_NRF52)
|
||||
/*
|
||||
* Internal Reference is +/-0.6V, with an adjustable gain of 1/6, 1/5, 1/4,
|
||||
* 1/3, 1/2 or 1, meaning 3.6, 3.0, 2.4, 1.8, 1.2 or 0.6V for the ADC levels.
|
||||
*
|
||||
* External Reference is VDD/4, with an adjustable gain of 1, 2 or 4, meaning
|
||||
* VDD/4, VDD/2 or VDD for the ADC levels.
|
||||
*
|
||||
* Default settings are internal reference with 1/6 gain (GND..3.6V ADC range)
|
||||
*/
|
||||
#define AREF_VOLTAGE 3.6
|
||||
#else
|
||||
// NRF52 has AREF_VOLTAGE defined in architecture.h but
|
||||
// make sure it's included. If something is wrong with NRF52
|
||||
// definition - compilation will fail on missing definition
|
||||
#if !defined(AREF_VOLTAGE) && !defined(ARCH_NRF52)
|
||||
#define AREF_VOLTAGE 3.3
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* If this board has a battery level sensor, set this to a valid implementation
|
||||
@@ -233,7 +227,8 @@ static void battery_adcDisable()
|
||||
#endif
|
||||
|
||||
/**
|
||||
* A simple battery level sensor that assumes the battery voltage is attached via a voltage-divider to an analog input
|
||||
* A simple battery level sensor that assumes the battery voltage is attached
|
||||
* via a voltage-divider to an analog input
|
||||
*/
|
||||
class AnalogBatteryLevel : public HasBatteryLevel
|
||||
{
|
||||
@@ -311,7 +306,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
|
||||
#ifndef BATTERY_SENSE_SAMPLES
|
||||
#define BATTERY_SENSE_SAMPLES \
|
||||
15 // Set the number of samples, it has an effect of increasing sensitivity in complex electromagnetic environment.
|
||||
15 // Set the number of samples, it has an effect of increasing sensitivity in
|
||||
// complex electromagnetic environment.
|
||||
#endif
|
||||
|
||||
#ifdef BATTERY_PIN
|
||||
@@ -341,7 +337,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
battery_adcDisable();
|
||||
|
||||
if (!initial_read_done) {
|
||||
// Flush the smoothing filter with an ADC reading, if the reading is plausibly correct
|
||||
// Flush the smoothing filter with an ADC reading, if the reading is
|
||||
// plausibly correct
|
||||
if (scaled > last_read_value)
|
||||
last_read_value = scaled;
|
||||
initial_read_done = true;
|
||||
@@ -350,8 +347,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF
|
||||
}
|
||||
|
||||
// LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u", BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t)
|
||||
// (last_read_value));
|
||||
// LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u",
|
||||
// BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t) (last_read_value));
|
||||
}
|
||||
return last_read_value;
|
||||
#endif // BATTERY_PIN
|
||||
@@ -420,7 +417,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
/**
|
||||
* return true if there is a battery installed in this unit
|
||||
*/
|
||||
// if we have a integrated device with a battery, we can assume that the battery is always connected
|
||||
// if we have a integrated device with a battery, we can assume that the
|
||||
// battery is always connected
|
||||
#ifdef BATTERY_IMMUTABLE
|
||||
virtual bool isBatteryConnect() override { return true; }
|
||||
#elif defined(ADC_V)
|
||||
@@ -441,10 +439,10 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
virtual bool isBatteryConnect() override { return getBatteryPercent() != -1; }
|
||||
#endif
|
||||
|
||||
/// If we see a battery voltage higher than physics allows - assume charger is pumping
|
||||
/// in power
|
||||
/// On some boards we don't have the power management chip (like AXPxxxx)
|
||||
/// so we use EXT_PWR_DETECT GPIO pin to detect external power source
|
||||
/// If we see a battery voltage higher than physics allows - assume charger is
|
||||
/// pumping in power On some boards we don't have the power management chip
|
||||
/// (like AXPxxxx) so we use EXT_PWR_DETECT GPIO pin to detect external power
|
||||
/// source
|
||||
virtual bool isVbusIn() override
|
||||
{
|
||||
#ifdef EXT_PWR_DETECT
|
||||
@@ -461,8 +459,12 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
}
|
||||
// if it's not HIGH - check the battery
|
||||
#endif
|
||||
#elif defined(MUZI_BASE)
|
||||
return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk;
|
||||
|
||||
// technically speaking this should work for all(?) NRF52 boards
|
||||
// but needs testing across multiple devices. NRF52 USB would not even work if
|
||||
// VBUS was not properly connected and detected by the CPU
|
||||
#elif defined(MUZI_BASE) || defined(PROMICRO_DIY_TCXO)
|
||||
return powerHAL_isVBUSConnected();
|
||||
#endif
|
||||
return getBattVoltage() > chargingVolt;
|
||||
}
|
||||
@@ -485,8 +487,9 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
#else
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION)
|
||||
if (hasINA()) {
|
||||
// get current flow from INA sensor - negative value means power flowing into the battery
|
||||
// default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT RESISTOR <--> INA_VIN- <--> LOAD
|
||||
// get current flow from INA sensor - negative value means power flowing
|
||||
// into the battery default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT
|
||||
// RESISTOR <--> INA_VIN- <--> LOAD
|
||||
LOG_DEBUG("Using INA on I2C addr 0x%x for charging detection", config.power.device_battery_ina_address);
|
||||
#if defined(INA_CHARGING_DETECTION_INVERT)
|
||||
return getINACurrent() > 0;
|
||||
@@ -502,8 +505,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
}
|
||||
|
||||
private:
|
||||
/// If we see a battery voltage higher than physics allows - assume charger is pumping
|
||||
/// in power
|
||||
/// If we see a battery voltage higher than physics allows - assume charger is
|
||||
/// pumping in power
|
||||
|
||||
/// For heltecs with no battery connected, the measured voltage is 2204, so
|
||||
// need to be higher than that, in this case is 2500mV (3000-500)
|
||||
@@ -512,7 +515,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
const float noBatVolt = (OCV[NUM_OCV_POINTS - 1] - 500) * NUM_CELLS;
|
||||
// Start value from minimum voltage for the filter to not start from 0
|
||||
// that could trigger some events.
|
||||
// This value is over-written by the first ADC reading, it the voltage seems reasonable.
|
||||
// This value is over-written by the first ADC reading, it the voltage seems
|
||||
// reasonable.
|
||||
bool initial_read_done = false;
|
||||
float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS);
|
||||
uint32_t last_read_time_ms = 0;
|
||||
@@ -654,7 +658,8 @@ bool Power::analogInit()
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32S3
|
||||
// ESP32S3
|
||||
else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) {
|
||||
LOG_INFO("ADC config based on Two Point values and fitting curve coefficients stored in eFuse");
|
||||
LOG_INFO("ADC config based on Two Point values and fitting curve "
|
||||
"coefficients stored in eFuse");
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
@@ -662,13 +667,7 @@ bool Power::analogInit()
|
||||
}
|
||||
#endif // ARCH_ESP32
|
||||
|
||||
#ifdef ARCH_NRF52
|
||||
#ifdef VBAT_AR_INTERNAL
|
||||
analogReference(VBAT_AR_INTERNAL);
|
||||
#else
|
||||
analogReference(AR_INTERNAL); // 3.6V
|
||||
#endif
|
||||
#endif // ARCH_NRF52
|
||||
// NRF52 ADC init moved to powerHAL_init in nrf52 platform
|
||||
|
||||
#ifndef ARCH_ESP32
|
||||
analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS);
|
||||
@@ -723,6 +722,16 @@ bool Power::setup()
|
||||
runASAP = true;
|
||||
},
|
||||
CHANGE);
|
||||
#endif
|
||||
#ifdef EXT_CHRG_DETECT
|
||||
attachInterrupt(
|
||||
EXT_CHRG_DETECT,
|
||||
[]() {
|
||||
power->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
},
|
||||
CHANGE);
|
||||
#endif
|
||||
enabled = found;
|
||||
low_voltage_counter = 0;
|
||||
@@ -769,7 +778,8 @@ void Power::reboot()
|
||||
HAL_NVIC_SystemReset();
|
||||
#else
|
||||
rebootAtMsec = -1;
|
||||
LOG_WARN("FIXME implement reboot for this platform. Note that some settings require a restart to be applied");
|
||||
LOG_WARN("FIXME implement reboot for this platform. Note that some settings "
|
||||
"require a restart to be applied");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -779,9 +789,12 @@ void Power::shutdown()
|
||||
#if HAS_SCREEN
|
||||
if (screen) {
|
||||
#ifdef T_DECK_PRO
|
||||
screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button
|
||||
screen->showSimpleBanner("Device is powered off.\nConnect USB to start!",
|
||||
0); // T-Deck Pro has no power button
|
||||
#elif defined(USE_EINK)
|
||||
screen->showSimpleBanner("Shutting Down...", 2250); // dismiss after 3 seconds to avoid the banner on the sleep screen
|
||||
screen->showSimpleBanner("Shutting Down...",
|
||||
2250); // dismiss after 3 seconds to avoid the
|
||||
// banner on the sleep screen
|
||||
#else
|
||||
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
|
||||
#endif
|
||||
@@ -803,6 +816,9 @@ void Power::shutdown()
|
||||
#endif
|
||||
#ifdef PIN_LED3
|
||||
ledOff(PIN_LED3);
|
||||
#endif
|
||||
#ifdef LED_NOTIFICATION
|
||||
ledOff(LED_NOTIFICATION);
|
||||
#endif
|
||||
doDeepSleep(DELAY_FOREVER, true, true);
|
||||
#elif defined(ARCH_PORTDUINO)
|
||||
@@ -820,7 +836,8 @@ void Power::readPowerStatus()
|
||||
int32_t batteryVoltageMv = -1; // Assume unknown
|
||||
int8_t batteryChargePercent = -1;
|
||||
OptionalBool usbPowered = OptUnknown;
|
||||
OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM code doesn't run every time
|
||||
OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM
|
||||
// code doesn't run every time
|
||||
OptionalBool isChargingNow = OptUnknown;
|
||||
|
||||
if (batteryLevel) {
|
||||
@@ -833,9 +850,10 @@ void Power::readPowerStatus()
|
||||
if (batteryLevel->getBatteryPercent() >= 0) {
|
||||
batteryChargePercent = batteryLevel->getBatteryPercent();
|
||||
} else {
|
||||
// If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error
|
||||
// In that case, we compute an estimate of the charge percent based on open circuit voltage table defined
|
||||
// in power.h
|
||||
// If the AXP192 returns a percentage less than 0, the feature is either
|
||||
// not supported or there is an error In that case, we compute an
|
||||
// estimate of the charge percent based on open circuit voltage table
|
||||
// defined in power.h
|
||||
batteryChargePercent = clamp((int)(((batteryVoltageMv - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS)) * 1e2) /
|
||||
((OCV[0] * NUM_CELLS) - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS))),
|
||||
0, 100);
|
||||
@@ -843,12 +861,12 @@ void Power::readPowerStatus()
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way better instead to make a Nrf52IsUsbPowered subclass
|
||||
// (which shares a superclass with the BatteryLevel stuff)
|
||||
// that just provides a few methods. But in the interest of fixing this bug I'm going to follow current
|
||||
// practice.
|
||||
#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates the power states. Takes 20 seconds or so to detect
|
||||
// changes.
|
||||
// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way
|
||||
// better instead to make a Nrf52IsUsbPowered subclass (which shares a
|
||||
// superclass with the BatteryLevel stuff) that just provides a few methods. But
|
||||
// in the interest of fixing this bug I'm going to follow current practice.
|
||||
#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates
|
||||
// the power states. Takes 20 seconds or so to detect changes.
|
||||
|
||||
nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get();
|
||||
// LOG_DEBUG("NRF Power %d", nrf_usb_state);
|
||||
@@ -922,8 +940,9 @@ void Power::readPowerStatus()
|
||||
|
||||
#endif
|
||||
|
||||
// If we have a battery at all and it is less than 0%, force deep sleep if we have more than 10 low readings in
|
||||
// a row. NOTE: min LiIon/LiPo voltage is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough.
|
||||
// If we have a battery at all and it is less than 0%, force deep sleep if we
|
||||
// have more than 10 low readings in a row. NOTE: min LiIon/LiPo voltage
|
||||
// is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough.
|
||||
//
|
||||
|
||||
if (batteryLevel && powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) {
|
||||
@@ -945,8 +964,8 @@ int32_t Power::runOnce()
|
||||
readPowerStatus();
|
||||
|
||||
#ifdef HAS_PMU
|
||||
// WE no longer use the IRQ line to wake the CPU (due to false wakes from sleep), but we do poll
|
||||
// the IRQ status by reading the registers over I2C
|
||||
// WE no longer use the IRQ line to wake the CPU (due to false wakes from
|
||||
// sleep), but we do poll the IRQ status by reading the registers over I2C
|
||||
if (PMU) {
|
||||
|
||||
PMU->getIrqStatus();
|
||||
@@ -988,7 +1007,8 @@ int32_t Power::runOnce()
|
||||
PMU->clearIrqStatus();
|
||||
}
|
||||
#endif
|
||||
// Only read once every 20 seconds once the power status for the app has been initialized
|
||||
// Only read once every 20 seconds once the power status for the app has been
|
||||
// initialized
|
||||
return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME;
|
||||
}
|
||||
|
||||
@@ -996,10 +1016,12 @@ int32_t Power::runOnce()
|
||||
* Init the power manager chip
|
||||
*
|
||||
* axp192 power
|
||||
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the
|
||||
axp192 share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this
|
||||
on!) LDO1 30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of
|
||||
days), can not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
|
||||
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose
|
||||
comms to the axp192 because the OLED and the axp192 share the same i2c bus,
|
||||
instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max ->
|
||||
ESP32 (keep this on!) LDO1 30mA -> charges GPS backup battery // charges the
|
||||
tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can
|
||||
not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
|
||||
*
|
||||
*/
|
||||
bool Power::axpChipInit()
|
||||
@@ -1044,9 +1066,10 @@ bool Power::axpChipInit()
|
||||
|
||||
if (!PMU) {
|
||||
/*
|
||||
* In XPowersLib, if the XPowersAXPxxx object is released, Wire.end() will be called at the same time.
|
||||
* In order not to affect other devices, if the initialization of the PMU fails, Wire needs to be re-initialized once,
|
||||
* if there are multiple devices sharing the bus.
|
||||
* In XPowersLib, if the XPowersAXPxxx object is released, Wire.end() will
|
||||
* be called at the same time. In order not to affect other devices, if the
|
||||
* initialization of the PMU fails, Wire needs to be re-initialized once, if
|
||||
* there are multiple devices sharing the bus.
|
||||
* * */
|
||||
#ifndef PMU_USE_WIRE1
|
||||
w->begin(I2C_SDA, I2C_SCL);
|
||||
@@ -1063,8 +1086,8 @@ bool Power::axpChipInit()
|
||||
PMU->enablePowerOutput(XPOWERS_LDO2);
|
||||
|
||||
// oled module power channel,
|
||||
// disable it will cause abnormal communication between boot and AXP power supply,
|
||||
// do not turn it off
|
||||
// disable it will cause abnormal communication between boot and AXP power
|
||||
// supply, do not turn it off
|
||||
PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300);
|
||||
// enable oled power
|
||||
PMU->enablePowerOutput(XPOWERS_DCDC1);
|
||||
@@ -1091,7 +1114,8 @@ bool Power::axpChipInit()
|
||||
PMU->setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2);
|
||||
} else if (PMU->getChipModel() == XPOWERS_AXP2101) {
|
||||
|
||||
/*The alternative version of T-Beam 1.1 differs from T-Beam V1.1 in that it uses an AXP2101 power chip*/
|
||||
/*The alternative version of T-Beam 1.1 differs from T-Beam V1.1 in that it
|
||||
* uses an AXP2101 power chip*/
|
||||
if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) {
|
||||
// Unuse power channel
|
||||
PMU->disablePowerOutput(XPOWERS_DCDC2);
|
||||
@@ -1126,8 +1150,8 @@ bool Power::axpChipInit()
|
||||
// t-beam s3 core
|
||||
/**
|
||||
* gnss module power channel
|
||||
* The default ALDO4 is off, you need to turn on the GNSS power first, otherwise it will be invalid during
|
||||
* initialization
|
||||
* The default ALDO4 is off, you need to turn on the GNSS power first,
|
||||
* otherwise it will be invalid during initialization
|
||||
*/
|
||||
PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300);
|
||||
PMU->enablePowerOutput(XPOWERS_ALDO4);
|
||||
@@ -1177,7 +1201,8 @@ bool Power::axpChipInit()
|
||||
// disable all axp chip interrupt
|
||||
PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
|
||||
|
||||
// Set the constant current charging current of AXP2101, temporarily use 500mA by default
|
||||
// Set the constant current charging current of AXP2101, temporarily use
|
||||
// 500mA by default
|
||||
PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA);
|
||||
|
||||
// Set up the charging voltage
|
||||
@@ -1243,11 +1268,12 @@ bool Power::axpChipInit()
|
||||
PMU->getPowerChannelVoltage(XPOWERS_BLDO2));
|
||||
}
|
||||
|
||||
// We can safely ignore this approach for most (or all) boards because MCU turned off
|
||||
// earlier than battery discharged to 2.6V.
|
||||
// We can safely ignore this approach for most (or all) boards because MCU
|
||||
// turned off earlier than battery discharged to 2.6V.
|
||||
//
|
||||
// Unfortanly for now we can't use this killswitch for RAK4630-based boards because they have a bug with
|
||||
// battery voltage measurement. Probably it sometimes drops to low values.
|
||||
// Unfortunately for now we can't use this killswitch for RAK4630-based boards
|
||||
// because they have a bug with battery voltage measurement. Probably it
|
||||
// sometimes drops to low values.
|
||||
#ifndef RAK4630
|
||||
// Set PMU shutdown voltage at 2.6V to maximize battery utilization
|
||||
PMU->setSysPowerDownVoltage(2600);
|
||||
@@ -1266,10 +1292,12 @@ bool Power::axpChipInit()
|
||||
attachInterrupt(
|
||||
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
||||
|
||||
// we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ because it occurs repeatedly while there is
|
||||
// no battery also it could cause inadvertent waking from light sleep just because the battery filled
|
||||
// we don't look for AXPXXX_BATT_REMOVED_IRQ because it occurs repeatedly while no battery installed
|
||||
// we don't look at AXPXXX_VBUS_REMOVED_IRQ because we don't have anything hooked to vbus
|
||||
// we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ
|
||||
// because it occurs repeatedly while there is no battery also it could cause
|
||||
// inadvertent waking from light sleep just because the battery filled we
|
||||
// don't look for AXPXXX_BATT_REMOVED_IRQ because it occurs repeatedly while
|
||||
// no battery installed we don't look at AXPXXX_VBUS_REMOVED_IRQ because we
|
||||
// don't have anything hooked to vbus
|
||||
PMU->enableIRQ(pmuIrqMask);
|
||||
|
||||
PMU->clearIrqStatus();
|
||||
@@ -1385,8 +1413,8 @@ class LipoCharger : public HasBatteryLevel
|
||||
bool result = PPM->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR);
|
||||
if (result) {
|
||||
LOG_INFO("PPM BQ25896 init succeeded");
|
||||
// Set the minimum operating voltage. Below this voltage, the PPM will protect
|
||||
// PPM->setSysPowerDownVoltage(3100);
|
||||
// Set the minimum operating voltage. Below this voltage, the PPM will
|
||||
// protect PPM->setSysPowerDownVoltage(3100);
|
||||
|
||||
// Set input current limit, default is 500mA
|
||||
// PPM->setInputCurrentLimit(800);
|
||||
@@ -1409,7 +1437,8 @@ class LipoCharger : public HasBatteryLevel
|
||||
PPM->enableMeasure();
|
||||
|
||||
// Turn on charging function
|
||||
// If there is no battery connected, do not turn on the charging function
|
||||
// If there is no battery connected, do not turn on the charging
|
||||
// function
|
||||
PPM->enableCharge();
|
||||
} else {
|
||||
LOG_WARN("PPM BQ25896 init failed");
|
||||
@@ -1444,7 +1473,8 @@ class LipoCharger : public HasBatteryLevel
|
||||
virtual int getBatteryPercent() override
|
||||
{
|
||||
return -1;
|
||||
// return bq->getChargePercent(); // don't use BQ27220 for battery percent, it is not calibrated
|
||||
// return bq->getChargePercent(); // don't use BQ27220 for battery percent,
|
||||
// it is not calibrated
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1566,7 +1596,8 @@ bool Power::meshSolarInit()
|
||||
|
||||
#else
|
||||
/**
|
||||
* The meshSolar battery level sensor is unavailable - default to AnalogBatteryLevel
|
||||
* The meshSolar battery level sensor is unavailable - default to
|
||||
* AnalogBatteryLevel
|
||||
*/
|
||||
bool Power::meshSolarInit()
|
||||
{
|
||||
|
||||
@@ -155,6 +155,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#endif
|
||||
|
||||
// Default system gain to 0 if not defined
|
||||
#ifndef NUM_PA_POINTS
|
||||
#define NUM_PA_POINTS 1
|
||||
#endif
|
||||
|
||||
#ifndef TX_GAIN_LORA
|
||||
#define TX_GAIN_LORA 0
|
||||
#endif
|
||||
@@ -424,12 +428,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define HAS_RGB_LED
|
||||
#endif
|
||||
|
||||
#ifndef LED_STATE_OFF
|
||||
#define LED_STATE_OFF 0
|
||||
#endif
|
||||
#ifndef LED_STATE_ON
|
||||
#define LED_STATE_ON 1
|
||||
#endif
|
||||
#ifndef LED_STATE_OFF
|
||||
#define LED_STATE_OFF (LED_STATE_ON ^ 1)
|
||||
#endif
|
||||
|
||||
#ifndef ledOff
|
||||
#define ledOff(pin) pinMode(pin, INPUT)
|
||||
#endif
|
||||
|
||||
// default mapping of pins
|
||||
#if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN)
|
||||
@@ -445,18 +453,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// BME680 BSEC2 support detection
|
||||
#if !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED)
|
||||
#if defined(RAK_4631) || defined(TBEAM_V10)
|
||||
|
||||
#define MESHTASTIC_BME680_BSEC2_SUPPORTED 1
|
||||
#define MESHTASTIC_BME680_HEADER <bsec2.h>
|
||||
#else
|
||||
#define MESHTASTIC_BME680_BSEC2_SUPPORTED 0
|
||||
#define MESHTASTIC_BME680_HEADER <Adafruit_BME680.h>
|
||||
#endif // defined(RAK_4631)
|
||||
#endif // !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED)
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Global switches to turn off features for a minimized build
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -905,6 +905,12 @@ void GPS::writePinStandby(bool standby)
|
||||
// Write and log
|
||||
pinMode(PIN_GPS_STANDBY, OUTPUT);
|
||||
digitalWrite(PIN_GPS_STANDBY, val);
|
||||
|
||||
// Enter backup mode on PA1010D; TODO: may be applicable to other MTK GPS too
|
||||
if (IS_ONE_OF(gnssModel, GNSS_MODEL_MTK_PA1010D)) {
|
||||
_serial_gps->write("$PMTK225,4*2F\r\n");
|
||||
}
|
||||
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_DEBUG("Pin STANDBY %s", val == HIGH ? "HI" : "LOW");
|
||||
#endif
|
||||
|
||||
@@ -397,7 +397,7 @@ uint32_t getValidTime(RTCQuality minQuality, bool local)
|
||||
return (currentQuality >= minQuality) ? getTime(local) : 0;
|
||||
}
|
||||
|
||||
time_t gm_mktime(struct tm *tm)
|
||||
time_t gm_mktime(const struct tm *tm)
|
||||
{
|
||||
#if !MESHTASTIC_EXCLUDE_TZ
|
||||
time_t result = 0;
|
||||
@@ -413,8 +413,8 @@ time_t gm_mktime(struct tm *tm)
|
||||
days_before_this_year -= 719162; // (1969 * 365 + 1969 / 4 - 1969 / 100 + 1969 / 400);
|
||||
|
||||
// Now, within this tm->year, compute the days *before* this tm->month starts.
|
||||
int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year
|
||||
int days_this_year_before_this_month = days_before_month[tm->tm_mon]; // tm->tm_mon is 0..11
|
||||
static const int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year
|
||||
int days_this_year_before_this_month = days_before_month[tm->tm_mon]; // tm->tm_mon is 0..11
|
||||
|
||||
// If this is a leap year, and we're past February, add a day:
|
||||
if (tm->tm_mon >= 2 && (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) {
|
||||
@@ -435,6 +435,7 @@ time_t gm_mktime(struct tm *tm)
|
||||
|
||||
return result;
|
||||
#else
|
||||
return mktime(tm);
|
||||
struct tm tmCopy = *tm;
|
||||
return mktime(&tmCopy);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ uint32_t getValidTime(RTCQuality minQuality, bool local = false);
|
||||
|
||||
RTCSetResult readFromRTC();
|
||||
|
||||
time_t gm_mktime(struct tm *tm);
|
||||
time_t gm_mktime(const struct tm *tm);
|
||||
|
||||
#define SEC_PER_DAY 86400
|
||||
#define SEC_PER_HOUR 3600
|
||||
|
||||
@@ -1731,6 +1731,26 @@ int Screen::handleInputEvent(const InputEvent *event)
|
||||
showFrame(FrameDirection::PREVIOUS);
|
||||
} else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) {
|
||||
showFrame(FrameDirection::NEXT);
|
||||
} else if (event->inputEvent == INPUT_BROKER_FN_F1) {
|
||||
this->ui->switchToFrame(0);
|
||||
lastScreenTransition = millis();
|
||||
setFastFramerate();
|
||||
} else if (event->inputEvent == INPUT_BROKER_FN_F2) {
|
||||
this->ui->switchToFrame(1);
|
||||
lastScreenTransition = millis();
|
||||
setFastFramerate();
|
||||
} else if (event->inputEvent == INPUT_BROKER_FN_F3) {
|
||||
this->ui->switchToFrame(2);
|
||||
lastScreenTransition = millis();
|
||||
setFastFramerate();
|
||||
} else if (event->inputEvent == INPUT_BROKER_FN_F4) {
|
||||
this->ui->switchToFrame(3);
|
||||
lastScreenTransition = millis();
|
||||
setFastFramerate();
|
||||
} else if (event->inputEvent == INPUT_BROKER_FN_F5) {
|
||||
this->ui->switchToFrame(4);
|
||||
lastScreenTransition = millis();
|
||||
setFastFramerate();
|
||||
} else if (event->inputEvent == INPUT_BROKER_UP_LONG) {
|
||||
// Long press up button for fast frame switching
|
||||
showPrevFrame();
|
||||
|
||||
@@ -438,7 +438,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
||||
if (currentResolution == ScreenResolution::UltraLow) {
|
||||
snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz (%d)", freqStr, config.lora.channel_num);
|
||||
} else {
|
||||
snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %sMHz (%d)", freqStr, config.lora.channel_num);
|
||||
snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz (%d)", freqStr, config.lora.channel_num);
|
||||
}
|
||||
}
|
||||
size_t len = strlen(frequencyslot);
|
||||
|
||||
@@ -65,12 +65,12 @@ uint8_t test_count = 0;
|
||||
|
||||
void menuHandler::loraMenu()
|
||||
{
|
||||
static const char *optionsArray[] = {"Back", "Device Role", "Radio Preset", "LoRa Region"};
|
||||
enum optionsNumbers { Back = 0, device_role_picker = 1, radio_preset_picker = 2, lora_picker = 3 };
|
||||
static const char *optionsArray[] = {"Back", "Device Role", "Radio Preset", "Frequency Slot", "LoRa Region"};
|
||||
enum optionsNumbers { Back = 0, device_role_picker = 1, radio_preset_picker = 2, frequency_slot = 3, lora_picker = 4 };
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "LoRa Actions";
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = 4;
|
||||
bannerOptions.optionsCount = 5;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == Back) {
|
||||
// No action
|
||||
@@ -78,6 +78,8 @@ void menuHandler::loraMenu()
|
||||
menuHandler::menuQueue = menuHandler::device_role_picker;
|
||||
} else if (selected == radio_preset_picker) {
|
||||
menuHandler::menuQueue = menuHandler::radio_preset_picker;
|
||||
} else if (selected == frequency_slot) {
|
||||
menuHandler::menuQueue = menuHandler::frequency_slot;
|
||||
} else if (selected == lora_picker) {
|
||||
menuHandler::menuQueue = menuHandler::lora_picker;
|
||||
}
|
||||
@@ -248,6 +250,69 @@ void menuHandler::DeviceRolePicker()
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
}
|
||||
|
||||
void menuHandler::FrequencySlotPicker()
|
||||
{
|
||||
|
||||
enum ReplyOptions : int { Back = -1 };
|
||||
constexpr int MAX_CHANNEL_OPTIONS = 202;
|
||||
static const char *optionsArray[MAX_CHANNEL_OPTIONS];
|
||||
static int optionsEnumArray[MAX_CHANNEL_OPTIONS];
|
||||
static char channelText[MAX_CHANNEL_OPTIONS - 1][12];
|
||||
int options = 0;
|
||||
optionsArray[options] = "Back";
|
||||
optionsEnumArray[options++] = Back;
|
||||
optionsArray[options] = "Slot 0 (Auto)";
|
||||
optionsEnumArray[options++] = 0;
|
||||
|
||||
// Calculate number of channels (copied from RadioInterface::applyModemConfig())
|
||||
meshtastic_Config_LoRaConfig &loraConfig = config.lora;
|
||||
double bw = loraConfig.use_preset ? modemPresetToBwKHz(loraConfig.modem_preset, myRegion->wideLora)
|
||||
: bwCodeToKHz(loraConfig.bandwidth);
|
||||
|
||||
uint32_t numChannels = 0;
|
||||
if (myRegion) {
|
||||
numChannels = (uint32_t)floor((myRegion->freqEnd - myRegion->freqStart) / (myRegion->spacing + (bw / 1000.0)));
|
||||
} else {
|
||||
LOG_WARN("Region not set, cannot calculate number of channels");
|
||||
return;
|
||||
}
|
||||
|
||||
if (numChannels > (uint32_t)(MAX_CHANNEL_OPTIONS - 2))
|
||||
numChannels = (uint32_t)(MAX_CHANNEL_OPTIONS - 2);
|
||||
|
||||
for (uint32_t ch = 1; ch <= numChannels; ch++) {
|
||||
snprintf(channelText[ch - 1], sizeof(channelText[ch - 1]), "Slot %lu", (unsigned long)ch);
|
||||
optionsArray[options] = channelText[ch - 1];
|
||||
optionsEnumArray[options++] = (int)ch;
|
||||
}
|
||||
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "Frequency Slot";
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
||||
bannerOptions.optionsCount = options;
|
||||
|
||||
// Start highlight on current channel if possible, otherwise on "1"
|
||||
int initial = (int)config.lora.channel_num + 1;
|
||||
if (initial < 2 || initial > (int)numChannels + 1)
|
||||
initial = 1;
|
||||
bannerOptions.InitialSelected = initial;
|
||||
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == Back) {
|
||||
menuHandler::menuQueue = menuHandler::lora_Menu;
|
||||
screen->runNow();
|
||||
return;
|
||||
}
|
||||
|
||||
config.lora.channel_num = selected;
|
||||
service->reloadConfig(SEGMENT_CONFIG);
|
||||
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
|
||||
};
|
||||
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
}
|
||||
|
||||
void menuHandler::RadioPresetPicker()
|
||||
{
|
||||
static const RadioPresetOption presetOptions[] = {
|
||||
@@ -278,6 +343,8 @@ void menuHandler::RadioPresetPicker()
|
||||
}
|
||||
|
||||
config.lora.modem_preset = option.value;
|
||||
config.lora.channel_num = 0; // Reset to default channel for the preset
|
||||
config.lora.override_frequency = 0; // Clear any custom frequency
|
||||
service->reloadConfig(SEGMENT_CONFIG);
|
||||
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
|
||||
});
|
||||
@@ -2551,6 +2618,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
|
||||
case radio_preset_picker:
|
||||
RadioPresetPicker();
|
||||
break;
|
||||
case frequency_slot:
|
||||
FrequencySlotPicker();
|
||||
break;
|
||||
case no_timeout_lora_picker:
|
||||
LoraRegionPicker(0);
|
||||
break;
|
||||
|
||||
@@ -13,6 +13,7 @@ class menuHandler
|
||||
lora_picker,
|
||||
device_role_picker,
|
||||
radio_preset_picker,
|
||||
frequency_slot,
|
||||
no_timeout_lora_picker,
|
||||
TZ_picker,
|
||||
twelve_hour_picker,
|
||||
@@ -63,6 +64,7 @@ class menuHandler
|
||||
static void loraMenu();
|
||||
static void DeviceRolePicker();
|
||||
static void RadioPresetPicker();
|
||||
static void FrequencySlotPicker();
|
||||
static void handleMenuSwitch(OLEDDisplay *display);
|
||||
static void showConfirmationBanner(const char *message, std::function<void()> onConfirm);
|
||||
static void clockMenu();
|
||||
@@ -138,7 +140,7 @@ struct ScreenColor {
|
||||
uint8_t b;
|
||||
bool useVariant;
|
||||
|
||||
ScreenColor(uint8_t rIn = 0, uint8_t gIn = 0, uint8_t bIn = 0, bool variantIn = false)
|
||||
explicit ScreenColor(uint8_t rIn = 0, uint8_t gIn = 0, uint8_t bIn = 0, bool variantIn = false)
|
||||
: r(rIn), g(gIn), b(bIn), useVariant(variantIn)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "MessageStore.h"
|
||||
#include "NodeDB.h"
|
||||
#include "UIRenderer.h"
|
||||
#include "configuration.h"
|
||||
#include "gps/RTC.h"
|
||||
#include "graphics/Screen.h"
|
||||
#include "graphics/ScreenFonts.h"
|
||||
@@ -20,7 +19,6 @@
|
||||
|
||||
// External declarations
|
||||
extern bool hasUnreadMessage;
|
||||
extern meshtastic_DeviceState devicestate;
|
||||
extern graphics::Screen *screen;
|
||||
|
||||
using graphics::Emote;
|
||||
@@ -49,7 +47,7 @@ static inline size_t utf8CharLen(uint8_t c)
|
||||
}
|
||||
|
||||
// Remove variation selectors (FE0F) and skin tone modifiers from emoji so they match your labels
|
||||
std::string normalizeEmoji(const std::string &s)
|
||||
static std::string normalizeEmoji(const std::string &s)
|
||||
{
|
||||
std::string out;
|
||||
for (size_t i = 0; i < s.size();) {
|
||||
@@ -82,6 +80,7 @@ uint32_t pauseStart = 0;
|
||||
bool waitingToReset = false;
|
||||
bool scrollStarted = false;
|
||||
static bool didReset = false;
|
||||
static constexpr int MESSAGE_BLOCK_GAP = 6;
|
||||
|
||||
void scrollUp()
|
||||
{
|
||||
@@ -111,22 +110,6 @@ void scrollDown()
|
||||
|
||||
void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string &line, const Emote *emotes, int emoteCount)
|
||||
{
|
||||
std::string renderLine;
|
||||
for (size_t i = 0; i < line.size();) {
|
||||
uint8_t c = (uint8_t)line[i];
|
||||
size_t len = utf8CharLen(c);
|
||||
if (c == 0xEF && i + 2 < line.size() && (uint8_t)line[i + 1] == 0xB8 && (uint8_t)line[i + 2] == 0x8F) {
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
if (c == 0xF0 && i + 3 < line.size() && (uint8_t)line[i + 1] == 0x9F && (uint8_t)line[i + 2] == 0x8F &&
|
||||
((uint8_t)line[i + 3] >= 0xBB && (uint8_t)line[i + 3] <= 0xBF)) {
|
||||
i += 4;
|
||||
continue;
|
||||
}
|
||||
renderLine.append(line, i, len);
|
||||
i += len;
|
||||
}
|
||||
int cursorX = x;
|
||||
const int fontHeight = FONT_HEIGHT_SMALL;
|
||||
|
||||
@@ -203,8 +186,7 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string
|
||||
|
||||
// Render the emote (if found)
|
||||
if (matchedEmote && i == nextEmotePos) {
|
||||
// Vertically center emote relative to font baseline (not just midline)
|
||||
int iconY = fontY + (fontHeight - matchedEmote->height) / 2;
|
||||
int iconY = y + (lineHeight - matchedEmote->height) / 2;
|
||||
display->drawXbm(cursorX, iconY, matchedEmote->width, matchedEmote->height, matchedEmote->bitmap);
|
||||
cursorX += matchedEmote->width + 1;
|
||||
i += emojiLen;
|
||||
@@ -423,6 +405,63 @@ static inline int getRenderedLineWidth(OLEDDisplay *display, const std::string &
|
||||
return totalWidth;
|
||||
}
|
||||
|
||||
struct MessageBlock {
|
||||
size_t start;
|
||||
size_t end;
|
||||
bool mine;
|
||||
};
|
||||
|
||||
static int getDrawnLinePixelBottom(int lineTopY, const std::string &line, bool isHeaderLine)
|
||||
{
|
||||
if (isHeaderLine) {
|
||||
return lineTopY + (FONT_HEIGHT_SMALL - 1);
|
||||
}
|
||||
|
||||
int tallest = FONT_HEIGHT_SMALL;
|
||||
for (int e = 0; e < numEmotes; ++e) {
|
||||
if (line.find(emotes[e].label) != std::string::npos) {
|
||||
if (emotes[e].height > tallest)
|
||||
tallest = emotes[e].height;
|
||||
}
|
||||
}
|
||||
|
||||
const int lineHeight = std::max(FONT_HEIGHT_SMALL, tallest);
|
||||
const int iconTop = lineTopY + (lineHeight - tallest) / 2;
|
||||
|
||||
return iconTop + tallest - 1;
|
||||
}
|
||||
|
||||
static std::vector<MessageBlock> buildMessageBlocks(const std::vector<bool> &isHeaderVec, const std::vector<bool> &isMineVec)
|
||||
{
|
||||
std::vector<MessageBlock> blocks;
|
||||
if (isHeaderVec.empty())
|
||||
return blocks;
|
||||
|
||||
size_t start = 0;
|
||||
bool mine = isMineVec[0];
|
||||
|
||||
for (size_t i = 1; i < isHeaderVec.size(); ++i) {
|
||||
if (isHeaderVec[i]) {
|
||||
MessageBlock b;
|
||||
b.start = start;
|
||||
b.end = i - 1;
|
||||
b.mine = mine;
|
||||
blocks.push_back(b);
|
||||
|
||||
start = i;
|
||||
mine = isMineVec[i];
|
||||
}
|
||||
}
|
||||
|
||||
MessageBlock last;
|
||||
last.start = start;
|
||||
last.end = isHeaderVec.size() - 1;
|
||||
last.mine = mine;
|
||||
blocks.push_back(last);
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
static void drawMessageScrollbar(OLEDDisplay *display, int visibleHeight, int totalHeight, int scrollOffset, int startY)
|
||||
{
|
||||
if (totalHeight <= visibleHeight)
|
||||
@@ -482,9 +521,14 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
constexpr int LEFT_MARGIN = 2;
|
||||
constexpr int RIGHT_MARGIN = 2;
|
||||
constexpr int SCROLLBAR_WIDTH = 3;
|
||||
constexpr int BUBBLE_PAD_X = 3;
|
||||
constexpr int BUBBLE_PAD_Y = 4;
|
||||
constexpr int BUBBLE_RADIUS = 4;
|
||||
constexpr int BUBBLE_MIN_W = 24;
|
||||
constexpr int BUBBLE_TEXT_INDENT = 2;
|
||||
|
||||
const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN;
|
||||
|
||||
// Derived widths
|
||||
const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - (BUBBLE_PAD_X * 2);
|
||||
const int rightTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - SCROLLBAR_WIDTH;
|
||||
|
||||
// Title string depending on mode
|
||||
@@ -547,7 +591,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
char chanType[32] = "";
|
||||
if (currentMode == ThreadMode::ALL) {
|
||||
if (m.dest == NODENUM_BROADCAST) {
|
||||
snprintf(chanType, sizeof(chanType), "#%s", channels.getName(m.channelIndex));
|
||||
const char *name = channels.getName(m.channelIndex);
|
||||
if (currentResolution == ScreenResolution::Low || currentResolution == ScreenResolution::UltraLow) {
|
||||
if (strcmp(name, "ShortTurbo") == 0)
|
||||
name = "ShortT";
|
||||
else if (strcmp(name, "ShortSlow") == 0)
|
||||
name = "ShortS";
|
||||
else if (strcmp(name, "ShortFast") == 0)
|
||||
name = "ShortF";
|
||||
else if (strcmp(name, "MediumSlow") == 0)
|
||||
name = "MedS";
|
||||
else if (strcmp(name, "MediumFast") == 0)
|
||||
name = "MedF";
|
||||
else if (strcmp(name, "LongSlow") == 0)
|
||||
name = "LongS";
|
||||
else if (strcmp(name, "LongFast") == 0)
|
||||
name = "LongF";
|
||||
else if (strcmp(name, "LongTurbo") == 0)
|
||||
name = "LongT";
|
||||
else if (strcmp(name, "LongMod") == 0)
|
||||
name = "LongM";
|
||||
}
|
||||
snprintf(chanType, sizeof(chanType), "#%s", name);
|
||||
} else {
|
||||
snprintf(chanType, sizeof(chanType), "(DM)");
|
||||
}
|
||||
@@ -614,8 +679,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
}
|
||||
|
||||
// Shrink Sender name if needed
|
||||
int availWidth = SCREEN_WIDTH - display->getStringWidth(timeBuf) - display->getStringWidth(chanType) -
|
||||
display->getStringWidth(" @...") - 10;
|
||||
int availWidth = (mine ? rightTextWidth : leftTextWidth) - display->getStringWidth(timeBuf) -
|
||||
display->getStringWidth(chanType) - display->getStringWidth(" @...");
|
||||
if (availWidth < 0)
|
||||
availWidth = 0;
|
||||
|
||||
@@ -667,6 +732,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
cachedLines = allLines;
|
||||
cachedHeights = calculateLineHeights(cachedLines, emotes, isHeader);
|
||||
|
||||
std::vector<MessageBlock> blocks = buildMessageBlocks(isHeader, isMine);
|
||||
|
||||
// Scrolling logic (unchanged)
|
||||
int totalHeight = 0;
|
||||
for (size_t i = 0; i < cachedHeights.size(); ++i)
|
||||
@@ -714,12 +781,133 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
|
||||
int finalScroll = (int)scrollY;
|
||||
int yOffset = -finalScroll + getTextPositions(display)[1];
|
||||
const int contentTop = getTextPositions(display)[1];
|
||||
const int contentBottom = scrollBottom; // already excludes nav line
|
||||
const int rightEdge = SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN;
|
||||
const int bubbleGapY = std::max(1, MESSAGE_BLOCK_GAP / 2);
|
||||
|
||||
std::vector<int> lineTop;
|
||||
lineTop.resize(cachedLines.size());
|
||||
{
|
||||
int acc = 0;
|
||||
for (size_t i = 0; i < cachedLines.size(); ++i) {
|
||||
lineTop[i] = yOffset + acc;
|
||||
acc += cachedHeights[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Draw bubbles
|
||||
for (size_t bi = 0; bi < blocks.size(); ++bi) {
|
||||
const auto &b = blocks[bi];
|
||||
if (b.start >= cachedLines.size() || b.end >= cachedLines.size() || b.start > b.end)
|
||||
continue;
|
||||
|
||||
int visualTop = lineTop[b.start];
|
||||
|
||||
int topY;
|
||||
if (isHeader[b.start]) {
|
||||
// Header start
|
||||
constexpr int BUBBLE_PAD_TOP_HEADER = 1; // try 1 or 2
|
||||
topY = visualTop - BUBBLE_PAD_TOP_HEADER;
|
||||
} else {
|
||||
// Body start
|
||||
bool thisLineHasEmote = false;
|
||||
for (int e = 0; e < numEmotes; ++e) {
|
||||
if (cachedLines[b.start].find(emotes[e].label) != std::string::npos) {
|
||||
thisLineHasEmote = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (thisLineHasEmote) {
|
||||
constexpr int EMOTE_PADDING_ABOVE = 4;
|
||||
visualTop -= EMOTE_PADDING_ABOVE;
|
||||
}
|
||||
topY = visualTop - BUBBLE_PAD_Y;
|
||||
}
|
||||
int visualBottom = getDrawnLinePixelBottom(lineTop[b.end], cachedLines[b.end], isHeader[b.end]);
|
||||
int bottomY = visualBottom + BUBBLE_PAD_Y;
|
||||
|
||||
if (bi + 1 < blocks.size()) {
|
||||
int nextHeaderIndex = (int)blocks[bi + 1].start;
|
||||
int nextTop = lineTop[nextHeaderIndex];
|
||||
int maxBottom = nextTop - 1 - bubbleGapY;
|
||||
if (bottomY > maxBottom)
|
||||
bottomY = maxBottom;
|
||||
}
|
||||
|
||||
if (bottomY <= topY + 2)
|
||||
continue;
|
||||
|
||||
if (bottomY < contentTop || topY > contentBottom - 1)
|
||||
continue;
|
||||
|
||||
int maxLineW = 0;
|
||||
|
||||
for (size_t i = b.start; i <= b.end; ++i) {
|
||||
int w = 0;
|
||||
if (isHeader[i]) {
|
||||
w = display->getStringWidth(cachedLines[i].c_str());
|
||||
if (b.mine)
|
||||
w += 12; // room for ACK/NACK/relay mark
|
||||
} else {
|
||||
w = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes);
|
||||
}
|
||||
if (w > maxLineW)
|
||||
maxLineW = w;
|
||||
}
|
||||
|
||||
int bubbleW = std::max(BUBBLE_MIN_W, maxLineW + (BUBBLE_PAD_X * 2));
|
||||
int bubbleH = (bottomY - topY) + 1;
|
||||
int bubbleX = 0;
|
||||
if (b.mine) {
|
||||
bubbleX = rightEdge - bubbleW;
|
||||
} else {
|
||||
bubbleX = x;
|
||||
}
|
||||
if (bubbleX < x)
|
||||
bubbleX = x;
|
||||
if (bubbleX + bubbleW > rightEdge)
|
||||
bubbleW = std::max(1, rightEdge - bubbleX);
|
||||
|
||||
if (bubbleW > 1 && bubbleH > 1) {
|
||||
int x1 = bubbleX + bubbleW - 1;
|
||||
int y1 = topY + bubbleH - 1;
|
||||
|
||||
if (b.mine) {
|
||||
// Send Message (Right side)
|
||||
display->drawRect(x1 + 2 - bubbleW, y1 - bubbleH, bubbleW, bubbleH);
|
||||
// Top Right Corner
|
||||
display->drawRect(x1, topY, 2, 1);
|
||||
display->drawRect(x1, topY, 1, 2);
|
||||
// Bottom Right Corner
|
||||
display->drawRect(x1 - 1, bottomY - 2, 2, 1);
|
||||
display->drawRect(x1, bottomY - 3, 1, 2);
|
||||
// Knock the corners off to make a bubble
|
||||
display->setColor(BLACK);
|
||||
display->drawRect(x1 - bubbleW, topY - 1, 1, 1);
|
||||
display->drawRect(x1 - bubbleW, bottomY - 1, 1, 1);
|
||||
display->setColor(WHITE);
|
||||
} else {
|
||||
// Received Message (Left Side)
|
||||
display->drawRect(bubbleX, topY, bubbleW + 1, bubbleH);
|
||||
// Top Left Corner
|
||||
display->drawRect(bubbleX + 1, topY + 1, 2, 1);
|
||||
display->drawRect(bubbleX + 1, topY + 1, 1, 2);
|
||||
// Bottom Left Corner
|
||||
display->drawRect(bubbleX + 1, bottomY - 1, 2, 1);
|
||||
display->drawRect(bubbleX + 1, bottomY - 2, 1, 2);
|
||||
// Knock the corners off to make a bubble
|
||||
display->setColor(BLACK);
|
||||
display->drawRect(bubbleX + bubbleW, topY, 1, 1);
|
||||
display->drawRect(bubbleX + bubbleW, bottomY, 1, 1);
|
||||
display->setColor(WHITE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render visible lines
|
||||
int lineY = yOffset;
|
||||
for (size_t i = 0; i < cachedLines.size(); ++i) {
|
||||
int lineY = yOffset;
|
||||
for (size_t j = 0; j < i; ++j)
|
||||
lineY += cachedHeights[j];
|
||||
|
||||
if (lineY > -cachedHeights[i] && lineY < scrollBottom) {
|
||||
if (isHeader[i]) {
|
||||
@@ -728,14 +916,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
int headerX;
|
||||
if (isMine[i]) {
|
||||
// push header left to avoid overlap with scrollbar
|
||||
headerX = SCREEN_WIDTH - w - SCROLLBAR_WIDTH - RIGHT_MARGIN;
|
||||
headerX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - w - BUBBLE_TEXT_INDENT;
|
||||
if (headerX < LEFT_MARGIN)
|
||||
headerX = LEFT_MARGIN;
|
||||
} else {
|
||||
headerX = x;
|
||||
headerX = x + BUBBLE_PAD_X + BUBBLE_TEXT_INDENT;
|
||||
}
|
||||
display->drawString(headerX, lineY, cachedLines[i].c_str());
|
||||
|
||||
// Draw underline just under header text
|
||||
int underlineY = lineY + FONT_HEIGHT_SMALL;
|
||||
|
||||
int underlineW = w;
|
||||
int maxW = rightEdge - headerX;
|
||||
if (maxW < 0)
|
||||
maxW = 0;
|
||||
if (underlineW > maxW)
|
||||
underlineW = maxW;
|
||||
|
||||
for (int px = 0; px < underlineW; ++px) {
|
||||
display->setPixel(headerX + px, underlineY);
|
||||
}
|
||||
|
||||
// Draw ACK/NACK mark for our own messages
|
||||
if (isMine[i]) {
|
||||
int markX = headerX - 10;
|
||||
@@ -753,32 +955,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
// AckStatus::NONE → show nothing
|
||||
}
|
||||
|
||||
// Draw underline just under header text
|
||||
int underlineY = lineY + FONT_HEIGHT_SMALL;
|
||||
for (int px = 0; px < w; ++px) {
|
||||
display->setPixel(headerX + px, underlineY);
|
||||
}
|
||||
} else {
|
||||
// Render message line
|
||||
if (isMine[i]) {
|
||||
// Calculate actual rendered width including emotes
|
||||
int renderedWidth = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes);
|
||||
int rightX = SCREEN_WIDTH - renderedWidth - SCROLLBAR_WIDTH - RIGHT_MARGIN;
|
||||
int rightX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - renderedWidth - BUBBLE_TEXT_INDENT;
|
||||
if (rightX < LEFT_MARGIN)
|
||||
rightX = LEFT_MARGIN;
|
||||
|
||||
drawStringWithEmotes(display, rightX, lineY, cachedLines[i], emotes, numEmotes);
|
||||
} else {
|
||||
drawStringWithEmotes(display, x, lineY, cachedLines[i], emotes, numEmotes);
|
||||
drawStringWithEmotes(display, x + BUBBLE_PAD_X + BUBBLE_TEXT_INDENT, lineY, cachedLines[i], emotes,
|
||||
numEmotes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lineY += cachedHeights[i];
|
||||
}
|
||||
int totalContentHeight = totalHeight;
|
||||
int visibleHeight = usableHeight;
|
||||
|
||||
// Draw scrollbar
|
||||
drawMessageScrollbar(display, visibleHeight, totalContentHeight, finalScroll, getTextPositions(display)[1]);
|
||||
drawMessageScrollbar(display, usableHeight, totalHeight, finalScroll, getTextPositions(display)[1]);
|
||||
graphics::drawCommonHeader(display, x, y, titleStr);
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
@@ -841,7 +1039,6 @@ std::vector<int> calculateLineHeights(const std::vector<std::string> &lines, con
|
||||
constexpr int HEADER_UNDERLINE_GAP = 0; // space between underline and first body line
|
||||
constexpr int HEADER_UNDERLINE_PIX = 1; // underline thickness (1px row drawn)
|
||||
constexpr int BODY_LINE_LEADING = -4; // default vertical leading for normal body lines
|
||||
constexpr int MESSAGE_BLOCK_GAP = 4; // gap after a message block before a new header
|
||||
constexpr int EMOTE_PADDING_ABOVE = 4; // space above emote line (added to line above)
|
||||
constexpr int EMOTE_PADDING_BELOW = 3; // space below emote line (added to emote line)
|
||||
|
||||
@@ -851,6 +1048,7 @@ std::vector<int> calculateLineHeights(const std::vector<std::string> &lines, con
|
||||
for (size_t idx = 0; idx < lines.size(); ++idx) {
|
||||
const auto &line = lines[idx];
|
||||
const int baseHeight = FONT_HEIGHT_SMALL;
|
||||
int lineHeight = baseHeight;
|
||||
|
||||
// Detect if THIS line or NEXT line contains an emote
|
||||
bool hasEmote = false;
|
||||
@@ -872,8 +1070,6 @@ std::vector<int> calculateLineHeights(const std::vector<std::string> &lines, con
|
||||
}
|
||||
}
|
||||
|
||||
int lineHeight = baseHeight;
|
||||
|
||||
if (isHeaderVec[idx]) {
|
||||
// Header line spacing
|
||||
lineHeight = baseHeight + HEADER_UNDERLINE_PIX + HEADER_UNDERLINE_GAP;
|
||||
@@ -922,7 +1118,7 @@ void handleNewMessage(OLEDDisplay *display, const StoredMessage &sm, const mesht
|
||||
|
||||
// Banner logic
|
||||
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet.from);
|
||||
char longName[48] = "???";
|
||||
char longName[48] = "?";
|
||||
if (node && node->user.long_name) {
|
||||
strncpy(longName, node->user.long_name, sizeof(longName) - 1);
|
||||
longName[sizeof(longName) - 1] = '\0';
|
||||
|
||||
@@ -155,6 +155,18 @@ void InkHUD::LogoApplet::onShutdown()
|
||||
// This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update, after InkHUD's flash write is complete
|
||||
}
|
||||
|
||||
void InkHUD::LogoApplet::onApplyingChanges()
|
||||
{
|
||||
bringToForeground();
|
||||
|
||||
textLeft = "";
|
||||
textRight = "";
|
||||
textTitle = "Applying changes";
|
||||
fontTitle = fontSmall;
|
||||
|
||||
inkhud->forceUpdate(Drivers::EInk::FAST, false);
|
||||
}
|
||||
|
||||
void InkHUD::LogoApplet::onReboot()
|
||||
{
|
||||
bringToForeground();
|
||||
|
||||
@@ -26,6 +26,7 @@ class LogoApplet : public SystemApplet, public concurrency::OSThread
|
||||
void onBackground() override;
|
||||
void onShutdown() override;
|
||||
void onReboot() override;
|
||||
void onApplyingChanges();
|
||||
|
||||
protected:
|
||||
int32_t runOnce() override;
|
||||
|
||||
@@ -22,6 +22,7 @@ enum MenuAction {
|
||||
STORE_CANNEDMESSAGE_SELECTION,
|
||||
SEND_CANNEDMESSAGE,
|
||||
SHUTDOWN,
|
||||
BACK,
|
||||
NEXT_TILE,
|
||||
TOGGLE_BACKLIGHT,
|
||||
TOGGLE_GPS,
|
||||
@@ -36,6 +37,84 @@ enum MenuAction {
|
||||
TOGGLE_NOTIFICATIONS,
|
||||
TOGGLE_INVERT_COLOR,
|
||||
TOGGLE_12H_CLOCK,
|
||||
// Regions
|
||||
SET_REGION_US,
|
||||
SET_REGION_EU_868,
|
||||
SET_REGION_EU_433,
|
||||
SET_REGION_CN,
|
||||
SET_REGION_JP,
|
||||
SET_REGION_ANZ,
|
||||
SET_REGION_KR,
|
||||
SET_REGION_TW,
|
||||
SET_REGION_RU,
|
||||
SET_REGION_IN,
|
||||
SET_REGION_NZ_865,
|
||||
SET_REGION_TH,
|
||||
SET_REGION_LORA_24,
|
||||
SET_REGION_UA_433,
|
||||
SET_REGION_UA_868,
|
||||
SET_REGION_MY_433,
|
||||
SET_REGION_MY_919,
|
||||
SET_REGION_SG_923,
|
||||
SET_REGION_PH_433,
|
||||
SET_REGION_PH_868,
|
||||
SET_REGION_PH_915,
|
||||
SET_REGION_ANZ_433,
|
||||
SET_REGION_KZ_433,
|
||||
SET_REGION_KZ_863,
|
||||
SET_REGION_NP_865,
|
||||
SET_REGION_BR_902,
|
||||
// Device Roles
|
||||
SET_ROLE_CLIENT,
|
||||
SET_ROLE_CLIENT_MUTE,
|
||||
SET_ROLE_ROUTER,
|
||||
SET_ROLE_REPEATER,
|
||||
// Presets
|
||||
SET_PRESET_LONG_SLOW,
|
||||
SET_PRESET_LONG_MODERATE,
|
||||
SET_PRESET_LONG_FAST,
|
||||
SET_PRESET_MEDIUM_SLOW,
|
||||
SET_PRESET_MEDIUM_FAST,
|
||||
SET_PRESET_SHORT_SLOW,
|
||||
SET_PRESET_SHORT_FAST,
|
||||
SET_PRESET_SHORT_TURBO,
|
||||
// Timezones
|
||||
SET_TZ_US_HAWAII,
|
||||
SET_TZ_US_ALASKA,
|
||||
SET_TZ_US_PACIFIC,
|
||||
SET_TZ_US_ARIZONA,
|
||||
SET_TZ_US_MOUNTAIN,
|
||||
SET_TZ_US_CENTRAL,
|
||||
SET_TZ_US_EASTERN,
|
||||
SET_TZ_BR_BRAZILIA,
|
||||
SET_TZ_UTC,
|
||||
SET_TZ_EU_WESTERN,
|
||||
SET_TZ_EU_CENTRAL,
|
||||
SET_TZ_EU_EASTERN,
|
||||
SET_TZ_ASIA_KOLKATA,
|
||||
SET_TZ_ASIA_HONG_KONG,
|
||||
SET_TZ_AU_AWST,
|
||||
SET_TZ_AU_ACST,
|
||||
SET_TZ_AU_AEST,
|
||||
SET_TZ_PACIFIC_NZ,
|
||||
// Power
|
||||
TOGGLE_POWER_SAVE,
|
||||
CALIBRATE_ADC,
|
||||
// Bluetooth
|
||||
TOGGLE_BLUETOOTH,
|
||||
TOGGLE_BLUETOOTH_PAIR_MODE,
|
||||
// Channel
|
||||
TOGGLE_CHANNEL_UPLINK,
|
||||
TOGGLE_CHANNEL_DOWNLINK,
|
||||
TOGGLE_CHANNEL_POSITION,
|
||||
SET_CHANNEL_PRECISION,
|
||||
// Display
|
||||
TOGGLE_DISPLAY_UNITS,
|
||||
// Network
|
||||
TOGGLE_WIFI,
|
||||
// Administration
|
||||
RESET_NODEDB_ALL,
|
||||
RESET_NODEDB_KEEP_FAVORITES,
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::InkHUD
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,6 +35,7 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
||||
void onRender() override;
|
||||
|
||||
void show(Tile *t); // Open the menu, onto a user tile
|
||||
void setStartPage(MenuPage page);
|
||||
|
||||
protected:
|
||||
Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton
|
||||
@@ -56,6 +57,7 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
||||
void sendText(NodeNum dest, ChannelIndex channel, const char *message); // Send a text message to mesh
|
||||
void freeCannedMessageResources(); // Clear MenuApplet's canned message processing data
|
||||
|
||||
MenuPage startPageOverride = MenuPage::ROOT;
|
||||
MenuPage currentPage = MenuPage::ROOT;
|
||||
MenuPage previousPage = MenuPage::EXIT;
|
||||
uint8_t cursor = 0; // Which menu item is currently highlighted
|
||||
@@ -63,7 +65,15 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
||||
|
||||
uint16_t systemInfoPanelHeight = 0; // Need to know before we render
|
||||
|
||||
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
|
||||
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
|
||||
std::vector<std::string> nodeConfigLabels; // Persistent labels for Node Config pages
|
||||
uint8_t selectedChannelIndex = 0; // Currently selected LoRa channel (Node Config → Radio → Channel)
|
||||
bool channelPositionEnabled = false;
|
||||
bool gpsEnabled = false;
|
||||
|
||||
// Recents menu checkbox state (derived from settings.recentlyActiveSeconds)
|
||||
static constexpr uint8_t RECENTS_COUNT = 6;
|
||||
bool recentsSelected[RECENTS_COUNT] = {};
|
||||
|
||||
// Data for selecting and sending canned messages via the menu
|
||||
// Placed into a sub-class for organization only
|
||||
|
||||
@@ -30,6 +30,7 @@ class MenuItem
|
||||
MenuAction action = NO_ACTION;
|
||||
MenuPage nextPage = EXIT;
|
||||
bool *checkState = nullptr;
|
||||
bool isHeader = false; // Non-selectable section label
|
||||
|
||||
// Various constructors, depending on the intended function of the item
|
||||
|
||||
@@ -40,6 +41,12 @@ class MenuItem
|
||||
: label(label), action(action), nextPage(nextPage), checkState(checkState)
|
||||
{
|
||||
}
|
||||
static MenuItem Header(const char *label)
|
||||
{
|
||||
MenuItem item(label, NO_ACTION, EXIT);
|
||||
item.isHeader = true;
|
||||
return item;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::InkHUD
|
||||
|
||||
@@ -20,10 +20,27 @@ enum MenuPage : uint8_t {
|
||||
SEND,
|
||||
CANNEDMESSAGE_RECIPIENT, // Select destination for a canned message
|
||||
OPTIONS,
|
||||
NODE_CONFIG,
|
||||
NODE_CONFIG_LORA,
|
||||
NODE_CONFIG_CHANNELS, // List of channels
|
||||
NODE_CONFIG_CHANNEL_DETAIL, // Per-channel options
|
||||
NODE_CONFIG_CHANNEL_PRECISION,
|
||||
NODE_CONFIG_PRESET,
|
||||
NODE_CONFIG_DEVICE,
|
||||
NODE_CONFIG_DEVICE_ROLE,
|
||||
NODE_CONFIG_POWER,
|
||||
NODE_CONFIG_POWER_ADC_CAL,
|
||||
NODE_CONFIG_NETWORK,
|
||||
NODE_CONFIG_DISPLAY,
|
||||
NODE_CONFIG_BLUETOOTH,
|
||||
NODE_CONFIG_POSITION,
|
||||
NODE_CONFIG_ADMIN_RESET,
|
||||
TIMEZONE,
|
||||
APPLETS,
|
||||
AUTOSHOW,
|
||||
RECENTS, // Select length of "recentlyActiveSeconds"
|
||||
EXIT, // Dismiss the menu applet
|
||||
REGION,
|
||||
EXIT, // Dismiss the menu applet
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::InkHUD
|
||||
|
||||
@@ -10,34 +10,37 @@ using namespace NicheGraphics;
|
||||
|
||||
InkHUD::TipsApplet::TipsApplet()
|
||||
{
|
||||
// Decide which tips (if any) should be shown to user after the boot screen
|
||||
bool needsRegion = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET);
|
||||
|
||||
bool showTutorialTips = (settings->tips.firstBoot || needsRegion);
|
||||
|
||||
// Welcome screen
|
||||
if (settings->tips.firstBoot)
|
||||
if (showTutorialTips)
|
||||
tipQueue.push_back(Tip::WELCOME);
|
||||
|
||||
// Antenna, region, timezone
|
||||
// Shown at boot if region not yet set
|
||||
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET)
|
||||
// Finish setup
|
||||
if (needsRegion)
|
||||
tipQueue.push_back(Tip::FINISH_SETUP);
|
||||
|
||||
// Using the UI
|
||||
if (showTutorialTips) {
|
||||
tipQueue.push_back(Tip::CUSTOMIZATION);
|
||||
tipQueue.push_back(Tip::BUTTONS);
|
||||
}
|
||||
|
||||
// Shutdown info
|
||||
// Shown until user performs one valid shutdown
|
||||
if (!settings->tips.safeShutdownSeen)
|
||||
tipQueue.push_back(Tip::SAFE_SHUTDOWN);
|
||||
|
||||
// Using the UI
|
||||
if (settings->tips.firstBoot) {
|
||||
tipQueue.push_back(Tip::CUSTOMIZATION);
|
||||
tipQueue.push_back(Tip::BUTTONS);
|
||||
}
|
||||
|
||||
// Catch an incorrect attempt at rotating display
|
||||
if (config.display.flip_screen)
|
||||
tipQueue.push_back(Tip::ROTATION);
|
||||
|
||||
// Applet is foreground immediately at boot, but is obscured by LogoApplet, which is also foreground
|
||||
// LogoApplet can be considered to have a higher Z-index, because it is placed before TipsApplet in the systemApplets vector
|
||||
// Region picker
|
||||
if (needsRegion)
|
||||
tipQueue.push_back(Tip::PICK_REGION);
|
||||
|
||||
if (!tipQueue.empty())
|
||||
bringToForeground();
|
||||
}
|
||||
@@ -51,81 +54,109 @@ void InkHUD::TipsApplet::onRender()
|
||||
|
||||
case Tip::FINISH_SETUP: {
|
||||
setFont(fontMedium);
|
||||
printAt(0, 0, "Tip: Finish Setup");
|
||||
const char *title = "Tip: Finish Setup";
|
||||
uint16_t h = getWrappedTextHeight(0, width(), title);
|
||||
printWrapped(0, 0, width(), title);
|
||||
|
||||
setFont(fontSmall);
|
||||
int16_t cursorY = fontMedium.lineHeight() * 1.5;
|
||||
printAt(0, cursorY, "- connect antenna");
|
||||
int16_t cursorY = h + fontSmall.lineHeight();
|
||||
|
||||
cursorY += fontSmall.lineHeight() * 1.2;
|
||||
printAt(0, cursorY, "- connect a client app");
|
||||
auto drawBullet = [&](const char *text) {
|
||||
uint16_t bh = getWrappedTextHeight(0, width(), text);
|
||||
printWrapped(0, cursorY, width(), text);
|
||||
cursorY += bh + (fontSmall.lineHeight() / 3);
|
||||
};
|
||||
|
||||
// Only if region not set
|
||||
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
|
||||
cursorY += fontSmall.lineHeight() * 1.2;
|
||||
printAt(0, cursorY, "- set region");
|
||||
}
|
||||
drawBullet("- connect antenna");
|
||||
drawBullet("- connect a client app");
|
||||
|
||||
// Only if tz not set
|
||||
if (!(*config.device.tzdef && config.device.tzdef[0] != 0)) {
|
||||
cursorY += fontSmall.lineHeight() * 1.2;
|
||||
printAt(0, cursorY, "- set timezone");
|
||||
}
|
||||
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET)
|
||||
drawBullet("- set region");
|
||||
|
||||
cursorY += fontSmall.lineHeight() * 1.5;
|
||||
printAt(0, cursorY, "More info at meshtastic.org");
|
||||
if (!(*config.device.tzdef && config.device.tzdef[0] != 0))
|
||||
drawBullet("- set timezone");
|
||||
|
||||
cursorY += fontSmall.lineHeight() / 2;
|
||||
drawBullet("More info at meshtastic.org");
|
||||
|
||||
setFont(fontSmall);
|
||||
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
||||
} break;
|
||||
|
||||
case Tip::PICK_REGION: {
|
||||
setFont(fontMedium);
|
||||
printAt(0, 0, "Set Region");
|
||||
|
||||
setFont(fontSmall);
|
||||
printWrapped(0, fontMedium.lineHeight() * 1.5, width(), "Please select your LoRa region to complete setup.");
|
||||
|
||||
printAt(0, Y(1.0), "Press button to choose", LEFT, BOTTOM);
|
||||
} break;
|
||||
|
||||
case Tip::SAFE_SHUTDOWN: {
|
||||
setFont(fontMedium);
|
||||
printAt(0, 0, "Tip: Shutdown");
|
||||
|
||||
const char *title = "Tip: Shutdown";
|
||||
uint16_t h = getWrappedTextHeight(0, width(), title);
|
||||
printWrapped(0, 0, width(), title);
|
||||
|
||||
setFont(fontSmall);
|
||||
std::string shutdown;
|
||||
shutdown += "Before removing power, please shut down from InkHUD menu, or a client app. \n";
|
||||
shutdown += "\n";
|
||||
shutdown += "This ensures data is saved.";
|
||||
printWrapped(0, fontMedium.lineHeight() * 1.5, width(), shutdown);
|
||||
int16_t cursorY = h + fontSmall.lineHeight();
|
||||
|
||||
const char *body = "Before removing power, please shut down from InkHUD menu, or a client app.\n\n"
|
||||
"This ensures data is saved.";
|
||||
|
||||
uint16_t bodyH = getWrappedTextHeight(0, width(), body);
|
||||
printWrapped(0, cursorY, width(), body);
|
||||
cursorY += bodyH + (fontSmall.lineHeight() / 2);
|
||||
|
||||
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
||||
|
||||
} break;
|
||||
|
||||
case Tip::CUSTOMIZATION: {
|
||||
setFont(fontMedium);
|
||||
printAt(0, 0, "Tip: Customization");
|
||||
|
||||
const char *title = "Tip: Customization";
|
||||
uint16_t h = getWrappedTextHeight(0, width(), title);
|
||||
printWrapped(0, 0, width(), title);
|
||||
|
||||
setFont(fontSmall);
|
||||
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
|
||||
"Configure & control display with the InkHUD menu. Optional features, layout, rotation, and more.");
|
||||
int16_t cursorY = h + fontSmall.lineHeight();
|
||||
|
||||
const char *body = "Configure & control display with the InkHUD menu. "
|
||||
"Optional features, layout, rotation, and more.";
|
||||
|
||||
uint16_t bodyH = getWrappedTextHeight(0, width(), body);
|
||||
printWrapped(0, cursorY, width(), body);
|
||||
cursorY += bodyH + (fontSmall.lineHeight() / 2);
|
||||
|
||||
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
||||
} break;
|
||||
|
||||
case Tip::BUTTONS: {
|
||||
setFont(fontMedium);
|
||||
printAt(0, 0, "Tip: Buttons");
|
||||
|
||||
const char *title = "Tip: Buttons";
|
||||
uint16_t h = getWrappedTextHeight(0, width(), title);
|
||||
printWrapped(0, 0, width(), title);
|
||||
|
||||
setFont(fontSmall);
|
||||
int16_t cursorY = fontMedium.lineHeight() * 1.5;
|
||||
int16_t cursorY = h + fontSmall.lineHeight();
|
||||
|
||||
auto drawBullet = [&](const char *text) {
|
||||
uint16_t bh = getWrappedTextHeight(0, width(), text);
|
||||
printWrapped(0, cursorY, width(), text);
|
||||
cursorY += bh + (fontSmall.lineHeight() / 3);
|
||||
};
|
||||
|
||||
if (!settings->joystick.enabled) {
|
||||
printAt(0, cursorY, "User Button");
|
||||
cursorY += fontSmall.lineHeight() * 1.2;
|
||||
printAt(0, cursorY, "- short press: next");
|
||||
cursorY += fontSmall.lineHeight() * 1.2;
|
||||
printAt(0, cursorY, "- long press: select / open menu");
|
||||
drawBullet("User Button");
|
||||
drawBullet("- short press: next");
|
||||
drawBullet("- long press: select or open menu");
|
||||
} else {
|
||||
printAt(0, cursorY, "Joystick");
|
||||
cursorY += fontSmall.lineHeight() * 1.2;
|
||||
printAt(0, cursorY, "- open menu / select");
|
||||
cursorY += fontSmall.lineHeight() * 1.5;
|
||||
printAt(0, cursorY, "Exit Button");
|
||||
cursorY += fontSmall.lineHeight() * 1.2;
|
||||
printAt(0, cursorY, "- switch tile / close menu");
|
||||
drawBullet("Joystick");
|
||||
drawBullet("- press: open menu or select");
|
||||
drawBullet("Exit Button");
|
||||
drawBullet("- press: switch tile or close menu");
|
||||
}
|
||||
|
||||
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
||||
@@ -133,12 +164,21 @@ void InkHUD::TipsApplet::onRender()
|
||||
|
||||
case Tip::ROTATION: {
|
||||
setFont(fontMedium);
|
||||
printAt(0, 0, "Tip: Rotation");
|
||||
|
||||
const char *title = "Tip: Rotation";
|
||||
uint16_t h = getWrappedTextHeight(0, width(), title);
|
||||
printWrapped(0, 0, width(), title);
|
||||
|
||||
setFont(fontSmall);
|
||||
if (!settings->joystick.enabled) {
|
||||
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
|
||||
"To rotate the display, use the InkHUD menu. Long-press the user button > Options > Rotate.");
|
||||
int16_t cursorY = h + fontSmall.lineHeight();
|
||||
|
||||
const char *body = "To rotate the display, use the InkHUD menu. "
|
||||
"Long-press the user button > Options > Rotate.";
|
||||
|
||||
uint16_t bh = getWrappedTextHeight(0, width(), body);
|
||||
printWrapped(0, cursorY, width(), body);
|
||||
cursorY += bh + (fontSmall.lineHeight() / 2);
|
||||
} else {
|
||||
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
|
||||
"To rotate the display, use the InkHUD menu. Press the user button > Options > Rotate.");
|
||||
@@ -159,12 +199,15 @@ void InkHUD::TipsApplet::renderWelcome()
|
||||
{
|
||||
uint16_t padW = X(0.05);
|
||||
|
||||
// Detect portrait orientation
|
||||
bool portrait = height() > width();
|
||||
|
||||
// Block 1 - logo & title
|
||||
// ========================
|
||||
|
||||
// Logo size
|
||||
uint16_t logoWLimit = X(0.3);
|
||||
uint16_t logoHLimit = Y(0.3);
|
||||
uint16_t logoWLimit = portrait ? X(0.5) : X(0.3);
|
||||
uint16_t logoHLimit = portrait ? Y(0.25) : Y(0.3);
|
||||
uint16_t logoW = getLogoWidth(logoWLimit, logoHLimit);
|
||||
uint16_t logoH = getLogoHeight(logoWLimit, logoHLimit);
|
||||
|
||||
@@ -177,7 +220,7 @@ void InkHUD::TipsApplet::renderWelcome()
|
||||
|
||||
// Center the block
|
||||
// Desired effect: equal margin from display edge for logo left and title right
|
||||
int16_t block1Y = Y(0.3);
|
||||
int16_t block1Y = portrait ? Y(0.2) : Y(0.3);
|
||||
int16_t block1CX = X(0.5) + (logoW / 2) - (titleW / 2);
|
||||
int16_t logoCX = block1CX - (logoW / 2) - (padW / 2);
|
||||
int16_t titleCX = block1CX + (titleW / 2) + (padW / 2);
|
||||
@@ -192,7 +235,7 @@ void InkHUD::TipsApplet::renderWelcome()
|
||||
std::string subtitle = "InkHUD";
|
||||
if (width() >= 200)
|
||||
subtitle += " - A Heads-Up Display"; // Future proofing: narrower for tiny display
|
||||
printAt(X(0.5), Y(0.6), subtitle, CENTER, MIDDLE);
|
||||
printAt(X(0.5), portrait ? Y(0.45) : Y(0.6), subtitle, CENTER, MIDDLE);
|
||||
|
||||
// Block 3 - press to continue
|
||||
// ============================
|
||||
@@ -224,26 +267,37 @@ void InkHUD::TipsApplet::onBackground()
|
||||
// While our SystemApplet::handleInput flag is true
|
||||
void InkHUD::TipsApplet::onButtonShortPress()
|
||||
{
|
||||
bool needsRegion = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET);
|
||||
// If we're prompting the user to pick a region, hand off to the menu
|
||||
if (!tipQueue.empty() && tipQueue.front() == Tip::PICK_REGION) {
|
||||
tipQueue.pop_front();
|
||||
|
||||
// Signal InkHUD to open the menu on Region page
|
||||
inkhud->forceRegionMenu = true;
|
||||
|
||||
// Close tips and open menu
|
||||
sendToBackground();
|
||||
inkhud->openMenu();
|
||||
return;
|
||||
}
|
||||
// Consume current tip
|
||||
tipQueue.pop_front();
|
||||
|
||||
// All tips done
|
||||
if (tipQueue.empty()) {
|
||||
// Record that user has now seen the "tutorial" set of tips
|
||||
// Don't show them on subsequent boots
|
||||
if (settings->tips.firstBoot) {
|
||||
if (settings->tips.firstBoot && !needsRegion) {
|
||||
settings->tips.firstBoot = false;
|
||||
inkhud->persistence->saveSettings();
|
||||
}
|
||||
|
||||
// Close applet, and full refresh to clean the screen
|
||||
// Need to force update, because our request would be ignored otherwise, as we are now background
|
||||
// Close applet and clean the screen
|
||||
sendToBackground();
|
||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||
}
|
||||
|
||||
// More tips left
|
||||
else
|
||||
} else {
|
||||
requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
// Functions the same as the user button in this instance
|
||||
|
||||
@@ -23,6 +23,7 @@ class TipsApplet : public SystemApplet
|
||||
enum class Tip {
|
||||
WELCOME,
|
||||
FINISH_SETUP,
|
||||
PICK_REGION,
|
||||
SAFE_SHUTDOWN,
|
||||
CUSTOMIZATION,
|
||||
BUTTONS,
|
||||
|
||||
@@ -276,6 +276,15 @@ int InkHUD::Events::beforeDeepSleep(void *unused)
|
||||
return 0; // We agree: deep sleep now
|
||||
}
|
||||
|
||||
// Display an intermediate screen while configuration changes are applied
|
||||
void InkHUD::Events::applyingChanges()
|
||||
{
|
||||
// Bring the logo applet forward with a temporary message
|
||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
||||
sa->onApplyingChanges();
|
||||
}
|
||||
}
|
||||
|
||||
// Callback for rebootObserver
|
||||
// Same as shutdown, without drawing the logoApplet
|
||||
// Makes sure we don't lose message history / InkHUD config
|
||||
|
||||
@@ -29,12 +29,13 @@ class Events
|
||||
|
||||
void onButtonShort(); // User button: short press
|
||||
void onButtonLong(); // User button: long press
|
||||
void onExitShort(); // Exit button: short press
|
||||
void onExitLong(); // Exit button: long press
|
||||
void onNavUp(); // Navigate up
|
||||
void onNavDown(); // Navigate down
|
||||
void onNavLeft(); // Navigate left
|
||||
void onNavRight(); // Navigate right
|
||||
void applyingChanges();
|
||||
void onExitShort(); // Exit button: short press
|
||||
void onExitLong(); // Exit button: long press
|
||||
void onNavUp(); // Navigate up
|
||||
void onNavDown(); // Navigate down
|
||||
void onNavLeft(); // Navigate left
|
||||
void onNavRight(); // Navigate right
|
||||
|
||||
int beforeDeepSleep(void *unused); // Prepare for shutdown
|
||||
int beforeReboot(void *unused); // Prepare for reboot
|
||||
|
||||
@@ -53,6 +53,13 @@ void InkHUD::InkHUD::addApplet(const char *name, Applet *a, bool defaultActive,
|
||||
windowManager->addApplet(name, a, defaultActive, defaultAutoshow, onTile);
|
||||
}
|
||||
|
||||
void InkHUD::InkHUD::notifyApplyingChanges()
|
||||
{
|
||||
if (events) {
|
||||
events->applyingChanges();
|
||||
}
|
||||
}
|
||||
|
||||
// Start InkHUD!
|
||||
// Call this only after you have configured InkHUD
|
||||
void InkHUD::InkHUD::begin()
|
||||
|
||||
@@ -47,6 +47,7 @@ class InkHUD
|
||||
void setDriver(Drivers::EInk *driver);
|
||||
void setDisplayResilience(uint8_t fastPerFull = 5, float stressMultiplier = 2.0);
|
||||
void addApplet(const char *name, Applet *a, bool defaultActive = false, bool defaultAutoshow = false, uint8_t onTile = -1);
|
||||
void notifyApplyingChanges();
|
||||
|
||||
void begin();
|
||||
|
||||
@@ -76,6 +77,9 @@ class InkHUD
|
||||
void rotateJoystick(uint8_t angle = 1); // rotate 90 deg by default
|
||||
void toggleBatteryIcon();
|
||||
|
||||
// Used by TipsApplet to force menu to start on Region selection
|
||||
bool forceRegionMenu = false;
|
||||
|
||||
// Updating the display
|
||||
// - called by various InkHUD components
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ class SystemApplet : public Applet
|
||||
bool lockRequests = false; // - prevent other applets from triggering display updates
|
||||
|
||||
virtual void onReboot() { onShutdown(); } // - handle reboot specially
|
||||
virtual void onApplyingChanges() {}
|
||||
|
||||
// Other system applets may take precedence over our own system applet though
|
||||
// The order an applet is passed to WindowManager::addSystemApplet determines this hierarchy (added earlier = higher rank)
|
||||
|
||||
@@ -20,20 +20,20 @@ constexpr uint8_t modifierLeftShift = 0b0001;
|
||||
|
||||
// Num chars per key, Modulus for rotating through characters
|
||||
static uint8_t HackadayCommunicatorTapMod[_TCA8418_NUM_KEYS] = {
|
||||
0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 0, 0, 0, 1, 2, 2, 2, 1, 2, 2, 0, 0, 0, 2, 1, 2, 2, 0, 1, 1, 0,
|
||||
};
|
||||
|
||||
static unsigned char HackadayCommunicatorTapMap[_TCA8418_NUM_KEYS][2] = {{},
|
||||
{},
|
||||
{Key::FUNCTION_F1},
|
||||
{'+'},
|
||||
{'9'},
|
||||
{'8'},
|
||||
{'7'},
|
||||
{'2'},
|
||||
{'3'},
|
||||
{'4'},
|
||||
{'5'},
|
||||
{Key::FUNCTION_F2},
|
||||
{Key::FUNCTION_F3},
|
||||
{Key::FUNCTION_F4},
|
||||
{Key::FUNCTION_F5},
|
||||
{Key::ESC},
|
||||
{'q', 'Q'},
|
||||
{'w', 'W'},
|
||||
@@ -141,6 +141,7 @@ void HackadayCommunicatorKeyboard::pressed(uint8_t key)
|
||||
if (state == Init || state == Busy) {
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG("Key pressed: %u", key);
|
||||
|
||||
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
|
||||
modifierFlag = 0;
|
||||
|
||||
@@ -1,8 +1,59 @@
|
||||
#include "InputBroker.h"
|
||||
#include "PowerFSM.h" // needed for event trigger
|
||||
#include "configuration.h"
|
||||
#include "graphics/Screen.h"
|
||||
#include "modules/ExternalNotificationModule.h"
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
#include "input/LinuxInputImpl.h"
|
||||
#include "input/SeesawRotary.h"
|
||||
#include "platform/portduino/PortduinoGlue.h"
|
||||
#endif
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
|
||||
#include "input/ExpressLRSFiveWay.h"
|
||||
#include "input/RotaryEncoderImpl.h"
|
||||
#include "input/RotaryEncoderInterruptImpl1.h"
|
||||
#include "input/SerialKeyboardImpl.h"
|
||||
#include "input/UpDownInterruptImpl1.h"
|
||||
#include "input/i2cButton.h"
|
||||
#if HAS_TRACKBALL
|
||||
#include "input/TrackballInterruptImpl1.h"
|
||||
#endif
|
||||
|
||||
#include "modules/StatusLEDModule.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_I2C
|
||||
#include "input/cardKbI2cImpl.h"
|
||||
#endif
|
||||
#include "input/kbMatrixImpl.h"
|
||||
#endif
|
||||
|
||||
#if HAS_BUTTON || defined(ARCH_PORTDUINO)
|
||||
#include "input/ButtonThread.h"
|
||||
|
||||
#if defined(BUTTON_PIN_TOUCH)
|
||||
ButtonThread *TouchButtonThread = nullptr;
|
||||
#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN)
|
||||
static bool touchBacklightWasOn = false;
|
||||
static bool touchBacklightActive = false;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
|
||||
ButtonThread *UserButtonThread = nullptr;
|
||||
#endif
|
||||
|
||||
#if defined(ALT_BUTTON_PIN)
|
||||
ButtonThread *BackButtonThread = nullptr;
|
||||
#endif
|
||||
|
||||
#if defined(CANCEL_BUTTON_PIN)
|
||||
ButtonThread *CancelButtonThread = nullptr;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
InputBroker *inputBroker = nullptr;
|
||||
|
||||
InputBroker::InputBroker()
|
||||
@@ -74,3 +125,262 @@ void InputBroker::pollSoonWorker(void *p)
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
void InputBroker::Init()
|
||||
{
|
||||
|
||||
#ifdef BUTTON_PIN
|
||||
#ifdef ARCH_ESP32
|
||||
|
||||
#if ESP_ARDUINO_VERSION_MAJOR >= 3
|
||||
#ifdef BUTTON_NEED_PULLUP
|
||||
pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT_PULLUP);
|
||||
#else
|
||||
pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN
|
||||
#endif
|
||||
#else
|
||||
pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN
|
||||
#ifdef BUTTON_NEED_PULLUP
|
||||
gpio_pullup_en((gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN));
|
||||
delay(10);
|
||||
#endif
|
||||
#ifdef BUTTON_NEED_PULLUP2
|
||||
gpio_pullup_en((gpio_num_t)BUTTON_NEED_PULLUP2);
|
||||
delay(10);
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// buttons are now inputBroker, so have to come after setupModules
|
||||
#if HAS_BUTTON
|
||||
int pullup_sense = 0;
|
||||
#ifdef INPUT_PULLUP_SENSE
|
||||
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
|
||||
#ifdef BUTTON_SENSE_TYPE
|
||||
pullup_sense = BUTTON_SENSE_TYPE;
|
||||
#else
|
||||
pullup_sense = INPUT_PULLUP_SENSE;
|
||||
#endif
|
||||
#endif
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
|
||||
if (portduino_config.userButtonPin.enabled) {
|
||||
|
||||
LOG_DEBUG("Use GPIO%02d for button", portduino_config.userButtonPin.pin);
|
||||
UserButtonThread = new ButtonThread("UserButton");
|
||||
if (screen) {
|
||||
ButtonConfig config;
|
||||
config.pinNumber = (uint8_t)portduino_config.userButtonPin.pin;
|
||||
config.activeLow = true;
|
||||
config.activePullup = true;
|
||||
config.pullupSense = INPUT_PULLUP;
|
||||
config.intRoutine = []() {
|
||||
UserButtonThread->userButton.tick();
|
||||
UserButtonThread->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
concurrency::mainDelay.interruptFromISR(&higherWake);
|
||||
};
|
||||
config.singlePress = INPUT_BROKER_USER_PRESS;
|
||||
config.longPress = INPUT_BROKER_SELECT;
|
||||
UserButtonThread->initButton(config);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef BUTTON_PIN_TOUCH
|
||||
TouchButtonThread = new ButtonThread("BackButton");
|
||||
ButtonConfig touchConfig;
|
||||
touchConfig.pinNumber = BUTTON_PIN_TOUCH;
|
||||
touchConfig.activeLow = true;
|
||||
touchConfig.activePullup = true;
|
||||
touchConfig.pullupSense = pullup_sense;
|
||||
touchConfig.intRoutine = []() {
|
||||
TouchButtonThread->userButton.tick();
|
||||
TouchButtonThread->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
concurrency::mainDelay.interruptFromISR(&higherWake);
|
||||
};
|
||||
touchConfig.singlePress = INPUT_BROKER_NONE;
|
||||
touchConfig.longPress = INPUT_BROKER_BACK;
|
||||
#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN)
|
||||
// On T-Echo Plus the touch pad should only drive the backlight, not UI navigation/sounds
|
||||
touchConfig.longPress = INPUT_BROKER_NONE;
|
||||
touchConfig.suppressLeadUpSound = true;
|
||||
touchConfig.onPress = []() {
|
||||
touchBacklightWasOn = uiconfig.screen_brightness == 1;
|
||||
if (!touchBacklightWasOn) {
|
||||
digitalWrite(PIN_EINK_EN, HIGH);
|
||||
}
|
||||
touchBacklightActive = true;
|
||||
};
|
||||
touchConfig.onRelease = []() {
|
||||
if (touchBacklightActive && !touchBacklightWasOn) {
|
||||
digitalWrite(PIN_EINK_EN, LOW);
|
||||
}
|
||||
touchBacklightActive = false;
|
||||
};
|
||||
#endif
|
||||
TouchButtonThread->initButton(touchConfig);
|
||||
#endif
|
||||
|
||||
#if defined(CANCEL_BUTTON_PIN)
|
||||
// Buttons. Moved here cause we need NodeDB to be initialized
|
||||
CancelButtonThread = new ButtonThread("CancelButton");
|
||||
ButtonConfig cancelConfig;
|
||||
cancelConfig.pinNumber = CANCEL_BUTTON_PIN;
|
||||
cancelConfig.activeLow = CANCEL_BUTTON_ACTIVE_LOW;
|
||||
cancelConfig.activePullup = CANCEL_BUTTON_ACTIVE_PULLUP;
|
||||
cancelConfig.pullupSense = pullup_sense;
|
||||
cancelConfig.intRoutine = []() {
|
||||
CancelButtonThread->userButton.tick();
|
||||
CancelButtonThread->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
concurrency::mainDelay.interruptFromISR(&higherWake);
|
||||
};
|
||||
cancelConfig.singlePress = INPUT_BROKER_CANCEL;
|
||||
cancelConfig.longPress = INPUT_BROKER_SHUTDOWN;
|
||||
cancelConfig.longPressTime = 4000;
|
||||
CancelButtonThread->initButton(cancelConfig);
|
||||
#endif
|
||||
|
||||
#if defined(ALT_BUTTON_PIN)
|
||||
// Buttons. Moved here cause we need NodeDB to be initialized
|
||||
BackButtonThread = new ButtonThread("BackButton");
|
||||
ButtonConfig backConfig;
|
||||
backConfig.pinNumber = ALT_BUTTON_PIN;
|
||||
backConfig.activeLow = ALT_BUTTON_ACTIVE_LOW;
|
||||
backConfig.activePullup = ALT_BUTTON_ACTIVE_PULLUP;
|
||||
backConfig.pullupSense = pullup_sense;
|
||||
backConfig.intRoutine = []() {
|
||||
BackButtonThread->userButton.tick();
|
||||
BackButtonThread->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
concurrency::mainDelay.interruptFromISR(&higherWake);
|
||||
};
|
||||
backConfig.singlePress = INPUT_BROKER_ALT_PRESS;
|
||||
backConfig.longPress = INPUT_BROKER_ALT_LONG;
|
||||
backConfig.longPressTime = 500;
|
||||
BackButtonThread->initButton(backConfig);
|
||||
#endif
|
||||
|
||||
#if defined(BUTTON_PIN)
|
||||
#if defined(USERPREFS_BUTTON_PIN)
|
||||
int _pinNum = config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN;
|
||||
#else
|
||||
int _pinNum = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN;
|
||||
#endif
|
||||
#ifndef BUTTON_ACTIVE_LOW
|
||||
#define BUTTON_ACTIVE_LOW true
|
||||
#endif
|
||||
#ifndef BUTTON_ACTIVE_PULLUP
|
||||
#define BUTTON_ACTIVE_PULLUP true
|
||||
#endif
|
||||
|
||||
// Buttons. Moved here cause we need NodeDB to be initialized
|
||||
// If your variant.h has a BUTTON_PIN defined, go ahead and define BUTTON_ACTIVE_LOW and BUTTON_ACTIVE_PULLUP
|
||||
UserButtonThread = new ButtonThread("UserButton");
|
||||
if (screen) {
|
||||
ButtonConfig userConfig;
|
||||
userConfig.pinNumber = (uint8_t)_pinNum;
|
||||
userConfig.activeLow = BUTTON_ACTIVE_LOW;
|
||||
userConfig.activePullup = BUTTON_ACTIVE_PULLUP;
|
||||
userConfig.pullupSense = pullup_sense;
|
||||
userConfig.intRoutine = []() {
|
||||
UserButtonThread->userButton.tick();
|
||||
UserButtonThread->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
concurrency::mainDelay.interruptFromISR(&higherWake);
|
||||
};
|
||||
userConfig.singlePress = INPUT_BROKER_USER_PRESS;
|
||||
userConfig.longPress = INPUT_BROKER_SELECT;
|
||||
userConfig.longPressTime = 500;
|
||||
userConfig.longLongPress = INPUT_BROKER_SHUTDOWN;
|
||||
UserButtonThread->initButton(userConfig);
|
||||
} else {
|
||||
ButtonConfig userConfigNoScreen;
|
||||
userConfigNoScreen.pinNumber = (uint8_t)_pinNum;
|
||||
userConfigNoScreen.activeLow = BUTTON_ACTIVE_LOW;
|
||||
userConfigNoScreen.activePullup = BUTTON_ACTIVE_PULLUP;
|
||||
userConfigNoScreen.pullupSense = pullup_sense;
|
||||
userConfigNoScreen.intRoutine = []() {
|
||||
UserButtonThread->userButton.tick();
|
||||
UserButtonThread->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
concurrency::mainDelay.interruptFromISR(&higherWake);
|
||||
};
|
||||
userConfigNoScreen.singlePress = INPUT_BROKER_USER_PRESS;
|
||||
userConfigNoScreen.longPress = INPUT_BROKER_NONE;
|
||||
userConfigNoScreen.longPressTime = 500;
|
||||
userConfigNoScreen.longLongPress = INPUT_BROKER_SHUTDOWN;
|
||||
userConfigNoScreen.doublePress = INPUT_BROKER_SEND_PING;
|
||||
userConfigNoScreen.triplePress = INPUT_BROKER_GPS_TOGGLE;
|
||||
UserButtonThread->initButton(userConfigNoScreen);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
|
||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
||||
#if defined(T_LORA_PAGER)
|
||||
// use a special FSM based rotary encoder version for T-LoRa Pager
|
||||
rotaryEncoderImpl = new RotaryEncoderImpl();
|
||||
if (!rotaryEncoderImpl->init()) {
|
||||
delete rotaryEncoderImpl;
|
||||
rotaryEncoderImpl = nullptr;
|
||||
}
|
||||
#elif defined(INPUTDRIVER_ENCODER_TYPE) && (INPUTDRIVER_ENCODER_TYPE == 2)
|
||||
upDownInterruptImpl1 = new UpDownInterruptImpl1();
|
||||
if (!upDownInterruptImpl1->init()) {
|
||||
delete upDownInterruptImpl1;
|
||||
upDownInterruptImpl1 = nullptr;
|
||||
}
|
||||
#else
|
||||
rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
|
||||
if (!rotaryEncoderInterruptImpl1->init()) {
|
||||
delete rotaryEncoderInterruptImpl1;
|
||||
rotaryEncoderInterruptImpl1 = nullptr;
|
||||
}
|
||||
#endif
|
||||
cardKbI2cImpl = new CardKbI2cImpl();
|
||||
cardKbI2cImpl->init();
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
i2cButton = new i2cButtonThread("i2cButtonThread");
|
||||
#endif
|
||||
#ifdef INPUTBROKER_MATRIX_TYPE
|
||||
kbMatrixImpl = new KbMatrixImpl();
|
||||
kbMatrixImpl->init();
|
||||
#endif // INPUTBROKER_MATRIX_TYPE
|
||||
#ifdef INPUTBROKER_SERIAL_TYPE
|
||||
aSerialKeyboardImpl = new SerialKeyboardImpl();
|
||||
aSerialKeyboardImpl->init();
|
||||
#endif // INPUTBROKER_MATRIX_TYPE
|
||||
}
|
||||
#endif // HAS_BUTTON
|
||||
#if ARCH_PORTDUINO
|
||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR && portduino_config.i2cdev != "") {
|
||||
seesawRotary = new SeesawRotary("SeesawRotary");
|
||||
if (!seesawRotary->init()) {
|
||||
delete seesawRotary;
|
||||
seesawRotary = nullptr;
|
||||
}
|
||||
aLinuxInputImpl = new LinuxInputImpl();
|
||||
aLinuxInputImpl->init();
|
||||
}
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_INPUTBROKER && HAS_TRACKBALL
|
||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
||||
trackballInterruptImpl1 = new TrackballInterruptImpl1();
|
||||
trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS);
|
||||
}
|
||||
#endif
|
||||
#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE
|
||||
expressLRSFiveWayInput = new ExpressLRSFiveWay();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Observer.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "freertosinc.h"
|
||||
|
||||
#ifdef InputBrokerDebug
|
||||
@@ -27,6 +28,11 @@ enum input_broker_event {
|
||||
INPUT_BROKER_SHUTDOWN = 0x9b,
|
||||
INPUT_BROKER_GPS_TOGGLE = 0x9e,
|
||||
INPUT_BROKER_SEND_PING = 0xaf,
|
||||
INPUT_BROKER_FN_F1 = 0xf1,
|
||||
INPUT_BROKER_FN_F2 = 0xf2,
|
||||
INPUT_BROKER_FN_F3 = 0xf3,
|
||||
INPUT_BROKER_FN_F4 = 0xf4,
|
||||
INPUT_BROKER_FN_F5 = 0xf5,
|
||||
INPUT_BROKER_MATRIXKEY = 0xFE,
|
||||
INPUT_BROKER_ANYKEY = 0xff
|
||||
|
||||
@@ -71,6 +77,7 @@ class InputBroker : public Observable<const InputEvent *>
|
||||
void queueInputEvent(const InputEvent *event);
|
||||
void processInputEventQueue();
|
||||
#endif
|
||||
void Init();
|
||||
|
||||
protected:
|
||||
int handleInputEvent(const InputEvent *event);
|
||||
@@ -84,4 +91,5 @@ class InputBroker : public Observable<const InputEvent *>
|
||||
#endif
|
||||
};
|
||||
|
||||
extern InputBroker *inputBroker;
|
||||
extern InputBroker *inputBroker;
|
||||
extern bool runASAP;
|
||||
@@ -5,7 +5,6 @@
|
||||
SerialKeyboard *globalSerialKeyboard = nullptr;
|
||||
|
||||
#ifdef INPUTBROKER_SERIAL_TYPE
|
||||
#define CANNED_MESSAGE_MODULE_ENABLE 1 // in case it's not set in the variant file
|
||||
|
||||
#if INPUTBROKER_SERIAL_TYPE == 1 // It's a Chatter
|
||||
// 3 SHIFT level (lower case, upper case, numbers), up to 4 repeated presses, button number
|
||||
|
||||
@@ -26,7 +26,12 @@ class TCA8418KeyboardBase
|
||||
GPS_TOGGLE = 0x9E,
|
||||
MUTE_TOGGLE = 0xAC,
|
||||
SEND_PING = 0xAF,
|
||||
BL_TOGGLE = 0xAB
|
||||
BL_TOGGLE = 0xAB,
|
||||
FUNCTION_F1 = 0xF1,
|
||||
FUNCTION_F2 = 0xF2,
|
||||
FUNCTION_F3 = 0xF3,
|
||||
FUNCTION_F4 = 0xF4,
|
||||
FUNCTION_F5 = 0xF5
|
||||
};
|
||||
|
||||
typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "TrackballInterruptBase.h"
|
||||
#include "Throttle.h"
|
||||
#include "configuration.h"
|
||||
|
||||
extern bool osk_found;
|
||||
|
||||
TrackballInterruptBase::TrackballInterruptBase(const char *name) : concurrency::OSThread(name), _originName(name) {}
|
||||
@@ -55,17 +57,27 @@ int32_t TrackballInterruptBase::runOnce()
|
||||
{
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
#if TB_THRESHOLD
|
||||
if (lastInterruptTime && !Throttle::isWithinTimespanMs(lastInterruptTime, 1000)) {
|
||||
left_counter = 0;
|
||||
right_counter = 0;
|
||||
up_counter = 0;
|
||||
down_counter = 0;
|
||||
lastInterruptTime = 0;
|
||||
}
|
||||
#ifdef INPUT_DEBUG
|
||||
if (left_counter > 0 || right_counter > 0 || up_counter > 0 || down_counter > 0) {
|
||||
LOG_DEBUG("L %u R %u U %u D %u, time %u", left_counter, right_counter, up_counter, down_counter, millis());
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Handle long press detection for press button
|
||||
if (pressDetected && pressStartTime > 0) {
|
||||
uint32_t pressDuration = millis() - pressStartTime;
|
||||
bool buttonStillPressed = false;
|
||||
|
||||
#if defined(T_DECK)
|
||||
buttonStillPressed = (this->action == TB_ACTION_PRESSED);
|
||||
#else
|
||||
buttonStillPressed = !digitalRead(_pinPress);
|
||||
#endif
|
||||
|
||||
if (!buttonStillPressed) {
|
||||
// Button released
|
||||
@@ -134,23 +146,31 @@ int32_t TrackballInterruptBase::runOnce()
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(T_DECK) // T-deck gets a super-simple debounce on trackball
|
||||
if (this->action == TB_ACTION_PRESSED && !pressDetected) {
|
||||
#if TB_THRESHOLD
|
||||
if (this->action == TB_ACTION_PRESSED && (!pressDetected || pressStartTime == 0)) {
|
||||
// Start long press detection
|
||||
pressDetected = true;
|
||||
pressStartTime = millis();
|
||||
// Don't send event yet, wait to see if it's a long press
|
||||
} else if (this->action == TB_ACTION_UP && lastEvent == TB_ACTION_UP) {
|
||||
// LOG_DEBUG("Trackball event UP");
|
||||
} else if (up_counter >= TB_THRESHOLD) {
|
||||
#ifdef INPUT_DEBUG
|
||||
LOG_DEBUG("Trackball event UP %u", millis());
|
||||
#endif
|
||||
e.inputEvent = this->_eventUp;
|
||||
} else if (this->action == TB_ACTION_DOWN && lastEvent == TB_ACTION_DOWN) {
|
||||
// LOG_DEBUG("Trackball event DOWN");
|
||||
} else if (down_counter >= TB_THRESHOLD) {
|
||||
#ifdef INPUT_DEBUG
|
||||
LOG_DEBUG("Trackball event DOWN %u", millis());
|
||||
#endif
|
||||
e.inputEvent = this->_eventDown;
|
||||
} else if (this->action == TB_ACTION_LEFT && lastEvent == TB_ACTION_LEFT) {
|
||||
// LOG_DEBUG("Trackball event LEFT");
|
||||
} else if (left_counter >= TB_THRESHOLD) {
|
||||
#ifdef INPUT_DEBUG
|
||||
LOG_DEBUG("Trackball event LEFT %u", millis());
|
||||
#endif
|
||||
e.inputEvent = this->_eventLeft;
|
||||
} else if (this->action == TB_ACTION_RIGHT && lastEvent == TB_ACTION_RIGHT) {
|
||||
// LOG_DEBUG("Trackball event RIGHT");
|
||||
} else if (right_counter >= TB_THRESHOLD) {
|
||||
#ifdef INPUT_DEBUG
|
||||
LOG_DEBUG("Trackball event RIGHT %u", millis());
|
||||
#endif
|
||||
e.inputEvent = this->_eventRight;
|
||||
}
|
||||
#else
|
||||
@@ -183,6 +203,12 @@ int32_t TrackballInterruptBase::runOnce()
|
||||
e.source = this->_originName;
|
||||
e.kbchar = 0x00;
|
||||
this->notifyObservers(&e);
|
||||
#if TB_THRESHOLD
|
||||
left_counter = 0;
|
||||
right_counter = 0;
|
||||
up_counter = 0;
|
||||
down_counter = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Only update lastEvent for non-press actions or completed press actions
|
||||
@@ -198,25 +224,49 @@ int32_t TrackballInterruptBase::runOnce()
|
||||
|
||||
void TrackballInterruptBase::intPressHandler()
|
||||
{
|
||||
this->action = TB_ACTION_PRESSED;
|
||||
if (!Throttle::isWithinTimespanMs(lastInterruptTime, 10))
|
||||
this->action = TB_ACTION_PRESSED;
|
||||
lastInterruptTime = millis();
|
||||
}
|
||||
|
||||
void TrackballInterruptBase::intDownHandler()
|
||||
{
|
||||
this->action = TB_ACTION_DOWN;
|
||||
if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10))
|
||||
this->action = TB_ACTION_DOWN;
|
||||
lastInterruptTime = millis();
|
||||
|
||||
#if TB_THRESHOLD
|
||||
down_counter++;
|
||||
#endif
|
||||
}
|
||||
|
||||
void TrackballInterruptBase::intUpHandler()
|
||||
{
|
||||
this->action = TB_ACTION_UP;
|
||||
if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10))
|
||||
this->action = TB_ACTION_UP;
|
||||
lastInterruptTime = millis();
|
||||
|
||||
#if TB_THRESHOLD
|
||||
up_counter++;
|
||||
#endif
|
||||
}
|
||||
|
||||
void TrackballInterruptBase::intLeftHandler()
|
||||
{
|
||||
this->action = TB_ACTION_LEFT;
|
||||
if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10))
|
||||
this->action = TB_ACTION_LEFT;
|
||||
lastInterruptTime = millis();
|
||||
#if TB_THRESHOLD
|
||||
left_counter++;
|
||||
#endif
|
||||
}
|
||||
|
||||
void TrackballInterruptBase::intRightHandler()
|
||||
{
|
||||
this->action = TB_ACTION_RIGHT;
|
||||
if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10))
|
||||
this->action = TB_ACTION_RIGHT;
|
||||
lastInterruptTime = millis();
|
||||
#if TB_THRESHOLD
|
||||
right_counter++;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef TB_THRESHOLD
|
||||
#define TB_THRESHOLD 0
|
||||
#endif
|
||||
|
||||
class TrackballInterruptBase : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
@@ -25,8 +29,6 @@ class TrackballInterruptBase : public Observable<const InputEvent *>, public con
|
||||
void intUpHandler();
|
||||
void intLeftHandler();
|
||||
void intRightHandler();
|
||||
uint32_t lastTime = 0;
|
||||
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
protected:
|
||||
@@ -67,4 +69,12 @@ class TrackballInterruptBase : public Observable<const InputEvent *>, public con
|
||||
input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
|
||||
const char *_originName;
|
||||
TrackballInterruptBaseActionType lastEvent = TB_ACTION_NONE;
|
||||
volatile uint32_t lastInterruptTime = 0;
|
||||
|
||||
#if TB_THRESHOLD
|
||||
volatile uint8_t left_counter = 0;
|
||||
volatile uint8_t right_counter = 0;
|
||||
volatile uint8_t up_counter = 0;
|
||||
volatile uint8_t down_counter = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -24,41 +24,26 @@ void TrackballInterruptImpl1::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLe
|
||||
|
||||
void TrackballInterruptImpl1::handleIntDown()
|
||||
{
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intDownHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
trackballInterruptImpl1->intDownHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
void TrackballInterruptImpl1::handleIntUp()
|
||||
{
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intUpHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
trackballInterruptImpl1->intUpHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
void TrackballInterruptImpl1::handleIntLeft()
|
||||
{
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intLeftHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
trackballInterruptImpl1->intLeftHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
void TrackballInterruptImpl1::handleIntRight()
|
||||
{
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intRightHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
trackballInterruptImpl1->intRightHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
void TrackballInterruptImpl1::handleIntPressed()
|
||||
{
|
||||
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||
trackballInterruptImpl1->lastTime = millis();
|
||||
trackballInterruptImpl1->intPressHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
trackballInterruptImpl1->intPressHandler();
|
||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||
}
|
||||
|
||||
@@ -321,6 +321,26 @@ int32_t KbI2cBase::runOnce()
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_TAB;
|
||||
break;
|
||||
case TCA8418KeyboardBase::FUNCTION_F1:
|
||||
e.inputEvent = INPUT_BROKER_FN_F1;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case TCA8418KeyboardBase::FUNCTION_F2:
|
||||
e.inputEvent = INPUT_BROKER_FN_F2;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case TCA8418KeyboardBase::FUNCTION_F3:
|
||||
e.inputEvent = INPUT_BROKER_FN_F3;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case TCA8418KeyboardBase::FUNCTION_F4:
|
||||
e.inputEvent = INPUT_BROKER_FN_F4;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
case TCA8418KeyboardBase::FUNCTION_F5:
|
||||
e.inputEvent = INPUT_BROKER_FN_F5;
|
||||
e.kbchar = 0x00;
|
||||
break;
|
||||
default:
|
||||
if (nextEvent > 127) {
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
|
||||
707
src/main.cpp
707
src/main.cpp
@@ -10,6 +10,7 @@
|
||||
#include "ReliableRouter.h"
|
||||
#include "airtime.h"
|
||||
#include "buzz.h"
|
||||
#include "power/PowerHAL.h"
|
||||
|
||||
#include "FSCommon.h"
|
||||
#include "Led.h"
|
||||
@@ -42,10 +43,6 @@
|
||||
#include "MessageStore.h"
|
||||
#endif
|
||||
|
||||
#ifdef ELECROW_ThinkNode_M5
|
||||
PCA9557 io(0x18, &Wire);
|
||||
#endif
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
#include "freertosinc.h"
|
||||
#if !MESHTASTIC_EXCLUDE_WEBSERVER
|
||||
@@ -76,59 +73,52 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr;
|
||||
#include "mqtt/MQTT.h"
|
||||
#endif
|
||||
|
||||
#include "LLCC68Interface.h"
|
||||
#include "LR1110Interface.h"
|
||||
#include "LR1120Interface.h"
|
||||
#include "LR1121Interface.h"
|
||||
#include "RF95Interface.h"
|
||||
#include "SX1262Interface.h"
|
||||
#include "SX1268Interface.h"
|
||||
#include "SX1280Interface.h"
|
||||
#include "detect/LoRaRadioType.h"
|
||||
|
||||
#ifdef ARCH_STM32WL
|
||||
#include "STM32WLE5JCInterface.h"
|
||||
#endif
|
||||
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
#include "platform/portduino/SimRadio.h"
|
||||
#endif
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
#include "linux/LinuxHardwareI2C.h"
|
||||
#include "mesh/raspihttp/PiWebServer.h"
|
||||
#include "platform/portduino/PortduinoGlue.h"
|
||||
#include "platform/portduino/USBHal.h"
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#endif
|
||||
|
||||
#if HAS_BUTTON || defined(ARCH_PORTDUINO)
|
||||
#include "input/ButtonThread.h"
|
||||
#ifdef ARCH_ESP32
|
||||
#ifdef DEBUG_PARTITION_TABLE
|
||||
#include "esp_partition.h"
|
||||
|
||||
#if defined(BUTTON_PIN_TOUCH)
|
||||
ButtonThread *TouchButtonThread = nullptr;
|
||||
#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN)
|
||||
static bool touchBacklightWasOn = false;
|
||||
static bool touchBacklightActive = false;
|
||||
#endif
|
||||
#endif
|
||||
void printPartitionTable()
|
||||
{
|
||||
printf("\n--- Partition Table ---\n");
|
||||
// Print Column Headers
|
||||
printf("| %-16s | %-4s | %-7s | %-10s | %-10s |\n", "Label", "Type", "Subtype", "Offset", "Size");
|
||||
printf("|------------------|------|---------|------------|------------|\n");
|
||||
|
||||
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
|
||||
ButtonThread *UserButtonThread = nullptr;
|
||||
#endif
|
||||
// Create an iterator to find ALL partitions (Type ANY, Subtype ANY)
|
||||
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
|
||||
|
||||
#if defined(ALT_BUTTON_PIN)
|
||||
ButtonThread *BackButtonThread = nullptr;
|
||||
#endif
|
||||
// Loop through the iterator
|
||||
if (it != NULL) {
|
||||
do {
|
||||
const esp_partition_t *part = esp_partition_get(it);
|
||||
|
||||
#if defined(CANCEL_BUTTON_PIN)
|
||||
ButtonThread *CancelButtonThread = nullptr;
|
||||
#endif
|
||||
// Print details: Label, Type (Hex), Subtype (Hex), Offset (Hex), Size (Hex)
|
||||
printf("| %-16s | 0x%02x | 0x%02x | 0x%08x | 0x%08x |\n", part->label, part->type, part->subtype, part->address,
|
||||
part->size);
|
||||
|
||||
#endif
|
||||
// Move to next partition
|
||||
it = esp_partition_next(it);
|
||||
} while (it != NULL);
|
||||
|
||||
// Release the iterator memory
|
||||
esp_partition_iterator_release(it);
|
||||
} else {
|
||||
printf("No partitions found.\n");
|
||||
}
|
||||
printf("-----------------------\n");
|
||||
}
|
||||
#endif // DEBUG_PARTITION_TABLE
|
||||
#endif // ARCH_ESP32
|
||||
|
||||
#include "AmbientLightingThread.h"
|
||||
#include "PowerFSMThread.h"
|
||||
@@ -216,9 +206,6 @@ ScanI2C::DeviceAddress aqi_found = ScanI2C::ADDRESS_NONE;
|
||||
Adafruit_DRV2605 drv;
|
||||
#endif
|
||||
|
||||
// Global LoRa radio type
|
||||
LoRaRadioType radioType = NO_RADIO;
|
||||
|
||||
bool isVibrating = false;
|
||||
|
||||
bool eink_found = true;
|
||||
@@ -255,6 +242,7 @@ const char *getDeviceName()
|
||||
return name;
|
||||
}
|
||||
|
||||
// TODO remove from main.cpp
|
||||
static int32_t ledBlinker()
|
||||
{
|
||||
// Still set up the blinking (heartbeat) interval but skip code path below, so LED will blink if
|
||||
@@ -295,6 +283,46 @@ __attribute__((weak, noinline)) bool loopCanSleep()
|
||||
void lateInitVariant() __attribute__((weak));
|
||||
void lateInitVariant() {}
|
||||
|
||||
void earlyInitVariant() __attribute__((weak));
|
||||
void earlyInitVariant() {}
|
||||
|
||||
// NRF52 (and probably other platforms) can report when system is in power failure mode
|
||||
// (eg. too low battery voltage) and operating it is unsafe (data corruption, bootloops, etc).
|
||||
// For example NRF52 will prevent any flash writes in that case automatically
|
||||
// (but it causes issues we need to handle).
|
||||
// This detection is independent from whatever ADC or dividers used in Meshtastic
|
||||
// boards and is internal to chip.
|
||||
|
||||
// we use powerHAL layer to get this info and delay booting until power level is safe
|
||||
|
||||
// wait until power level is safe to continue booting (to avoid bootloops)
|
||||
// blink user led in 3 flashes sequence to indicate what is happening
|
||||
void waitUntilPowerLevelSafe()
|
||||
{
|
||||
|
||||
#ifdef LED_PIN
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
#endif
|
||||
|
||||
while (powerHAL_isPowerLevelSafe() == false) {
|
||||
|
||||
#ifdef LED_PIN
|
||||
|
||||
// 3x: blink for 300 ms, pause for 300 ms
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
digitalWrite(LED_PIN, LED_STATE_ON);
|
||||
delay(300);
|
||||
digitalWrite(LED_PIN, LED_STATE_OFF);
|
||||
delay(300);
|
||||
}
|
||||
#endif
|
||||
|
||||
// sleep for 2s
|
||||
delay(2000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print info as a structured log message (for automated log processing)
|
||||
*/
|
||||
@@ -305,39 +333,35 @@ void printInfo()
|
||||
#ifndef PIO_UNIT_TESTING
|
||||
void setup()
|
||||
{
|
||||
#if defined(R1_NEO)
|
||||
pinMode(DCDC_EN_HOLD, OUTPUT);
|
||||
digitalWrite(DCDC_EN_HOLD, HIGH);
|
||||
pinMode(NRF_ON, OUTPUT);
|
||||
digitalWrite(NRF_ON, HIGH);
|
||||
#endif
|
||||
|
||||
// initialize power HAL layer as early as possible
|
||||
powerHAL_init();
|
||||
|
||||
// prevent booting if device is in power failure mode
|
||||
// boot sequence will follow when battery level raises to safe mode
|
||||
waitUntilPowerLevelSafe();
|
||||
|
||||
// Defined in variant.cpp for early init code
|
||||
earlyInitVariant();
|
||||
|
||||
#if defined(PIN_POWER_EN)
|
||||
pinMode(PIN_POWER_EN, OUTPUT);
|
||||
digitalWrite(PIN_POWER_EN, HIGH);
|
||||
#endif
|
||||
|
||||
#if defined(ELECROW_ThinkNode_M5)
|
||||
Wire.begin(48, 47);
|
||||
io.pinMode(PCA_PIN_EINK_EN, OUTPUT);
|
||||
io.pinMode(PCA_PIN_POWER_EN, OUTPUT);
|
||||
io.digitalWrite(PCA_PIN_POWER_EN, HIGH);
|
||||
// io.pinMode(C2_PIN, OUTPUT);
|
||||
#endif
|
||||
|
||||
#ifdef LED_POWER
|
||||
pinMode(LED_POWER, OUTPUT);
|
||||
digitalWrite(LED_POWER, LED_STATE_ON);
|
||||
#endif
|
||||
|
||||
#ifdef USER_LED
|
||||
pinMode(USER_LED, OUTPUT);
|
||||
digitalWrite(USER_LED, HIGH ^ LED_STATE_ON);
|
||||
#ifdef LED_NOTIFICATION
|
||||
pinMode(LED_NOTIFICATION, OUTPUT);
|
||||
digitalWrite(LED_NOTIFICATION, HIGH ^ LED_STATE_ON);
|
||||
#endif
|
||||
|
||||
#ifdef WIFI_LED
|
||||
pinMode(WIFI_LED, OUTPUT);
|
||||
digitalWrite(WIFI_LED, HIGH ^ WIFI_STATE_ON);
|
||||
digitalWrite(WIFI_LED, LOW);
|
||||
#endif
|
||||
|
||||
#ifdef BLE_LED
|
||||
@@ -349,68 +373,7 @@ void setup()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(T_DECK)
|
||||
// GPIO10 manages all peripheral power supplies
|
||||
// Turn on peripheral power immediately after MUC starts.
|
||||
// If some boards are turned on late, ESP32 will reset due to low voltage.
|
||||
// ESP32-C3(Keyboard) , MAX98357A(Audio Power Amplifier) ,
|
||||
// TF Card , Display backlight(AW9364DNR) , AN48841B(Trackball) , ES7210(Decoder)
|
||||
pinMode(KB_POWERON, OUTPUT);
|
||||
digitalWrite(KB_POWERON, HIGH);
|
||||
// T-Deck has all three SPI peripherals (TFT, SD, LoRa) attached to the same SPI bus
|
||||
// We need to initialize all CS pins in advance otherwise there will be SPI communication issues
|
||||
// e.g. when detecting the SD card
|
||||
pinMode(LORA_CS, OUTPUT);
|
||||
digitalWrite(LORA_CS, HIGH);
|
||||
pinMode(SDCARD_CS, OUTPUT);
|
||||
digitalWrite(SDCARD_CS, HIGH);
|
||||
pinMode(TFT_CS, OUTPUT);
|
||||
digitalWrite(TFT_CS, HIGH);
|
||||
delay(100);
|
||||
#elif defined(T_DECK_PRO)
|
||||
pinMode(LORA_EN, OUTPUT);
|
||||
digitalWrite(LORA_EN, HIGH);
|
||||
pinMode(LORA_CS, OUTPUT);
|
||||
digitalWrite(LORA_CS, HIGH);
|
||||
pinMode(SDCARD_CS, OUTPUT);
|
||||
digitalWrite(SDCARD_CS, HIGH);
|
||||
pinMode(PIN_EINK_CS, OUTPUT);
|
||||
digitalWrite(PIN_EINK_CS, HIGH);
|
||||
#elif defined(T_LORA_PAGER)
|
||||
pinMode(LORA_CS, OUTPUT);
|
||||
digitalWrite(LORA_CS, HIGH);
|
||||
pinMode(SDCARD_CS, OUTPUT);
|
||||
digitalWrite(SDCARD_CS, HIGH);
|
||||
pinMode(TFT_CS, OUTPUT);
|
||||
digitalWrite(TFT_CS, HIGH);
|
||||
pinMode(KB_INT, INPUT_PULLUP);
|
||||
// io expander
|
||||
io.begin(Wire, XL9555_SLAVE_ADDRESS0, SDA, SCL);
|
||||
io.pinMode(EXPANDS_DRV_EN, OUTPUT);
|
||||
io.digitalWrite(EXPANDS_DRV_EN, HIGH);
|
||||
io.pinMode(EXPANDS_AMP_EN, OUTPUT);
|
||||
io.digitalWrite(EXPANDS_AMP_EN, LOW);
|
||||
io.pinMode(EXPANDS_LORA_EN, OUTPUT);
|
||||
io.digitalWrite(EXPANDS_LORA_EN, HIGH);
|
||||
io.pinMode(EXPANDS_GPS_EN, OUTPUT);
|
||||
io.digitalWrite(EXPANDS_GPS_EN, HIGH);
|
||||
io.pinMode(EXPANDS_KB_EN, OUTPUT);
|
||||
io.digitalWrite(EXPANDS_KB_EN, HIGH);
|
||||
io.pinMode(EXPANDS_SD_EN, OUTPUT);
|
||||
io.digitalWrite(EXPANDS_SD_EN, HIGH);
|
||||
io.pinMode(EXPANDS_GPIO_EN, OUTPUT);
|
||||
io.digitalWrite(EXPANDS_GPIO_EN, HIGH);
|
||||
io.pinMode(EXPANDS_SD_PULLEN, INPUT);
|
||||
#elif defined(HACKADAY_COMMUNICATOR)
|
||||
pinMode(KB_INT, INPUT);
|
||||
#endif
|
||||
|
||||
concurrency::hasBeenSetup = true;
|
||||
#if ARCH_PORTDUINO
|
||||
SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0);
|
||||
#else
|
||||
SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
|
||||
#endif
|
||||
|
||||
meshtastic_Config_DisplayConfig_OledType screen_model =
|
||||
meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO;
|
||||
@@ -521,34 +484,11 @@ void setup()
|
||||
LOG_INFO("Wait for peripherals to stabilize");
|
||||
delay(PERIPHERAL_WARMUP_MS);
|
||||
#endif
|
||||
|
||||
#ifdef BUTTON_PIN
|
||||
#ifdef ARCH_ESP32
|
||||
|
||||
#if ESP_ARDUINO_VERSION_MAJOR >= 3
|
||||
#ifdef BUTTON_NEED_PULLUP
|
||||
pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT_PULLUP);
|
||||
#else
|
||||
pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN
|
||||
#endif
|
||||
#else
|
||||
pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN
|
||||
#ifdef BUTTON_NEED_PULLUP
|
||||
gpio_pullup_en((gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN));
|
||||
delay(10);
|
||||
#endif
|
||||
#ifdef BUTTON_NEED_PULLUP2
|
||||
gpio_pullup_en((gpio_num_t)BUTTON_NEED_PULLUP2);
|
||||
delay(10);
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
initSPI();
|
||||
|
||||
OSThread::setup();
|
||||
|
||||
// TODO make this ifdef based on defined pins and move from main.cpp
|
||||
#if defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2)
|
||||
// The ThinkNodes have their own blink logic
|
||||
// ledPeriodic = new Periodic("Blink", elecrowLedBlinker);
|
||||
@@ -648,7 +588,11 @@ void setup()
|
||||
sensor_detected = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
#ifdef DEBUG_PARTITION_TABLE
|
||||
printPartitionTable();
|
||||
#endif
|
||||
#endif // ARCH_ESP32
|
||||
#ifdef ARCH_ESP32
|
||||
// Don't init display if we don't have one or we are waking headless due to a timer event
|
||||
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) {
|
||||
@@ -922,6 +866,7 @@ void setup()
|
||||
}
|
||||
#endif // HAS_SCREEN
|
||||
|
||||
// TODO Remove magic string
|
||||
// setup TZ prior to time actions.
|
||||
#if !MESHTASTIC_EXCLUDE_TZ
|
||||
LOG_DEBUG("Use compiled/slipstreamed %s", slipstreamTZString); // important, removing this clobbers our magic string
|
||||
@@ -1005,180 +950,9 @@ void setup()
|
||||
nodeDB->hasWarned = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// buttons are now inputBroker, so have to come after setupModules
|
||||
#if HAS_BUTTON
|
||||
int pullup_sense = 0;
|
||||
#ifdef INPUT_PULLUP_SENSE
|
||||
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
|
||||
#ifdef BUTTON_SENSE_TYPE
|
||||
pullup_sense = BUTTON_SENSE_TYPE;
|
||||
#else
|
||||
pullup_sense = INPUT_PULLUP_SENSE;
|
||||
#endif
|
||||
#endif
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
|
||||
if (portduino_config.userButtonPin.enabled) {
|
||||
|
||||
LOG_DEBUG("Use GPIO%02d for button", portduino_config.userButtonPin.pin);
|
||||
UserButtonThread = new ButtonThread("UserButton");
|
||||
if (screen) {
|
||||
ButtonConfig config;
|
||||
config.pinNumber = (uint8_t)portduino_config.userButtonPin.pin;
|
||||
config.activeLow = true;
|
||||
config.activePullup = true;
|
||||
config.pullupSense = INPUT_PULLUP;
|
||||
config.intRoutine = []() {
|
||||
UserButtonThread->userButton.tick();
|
||||
UserButtonThread->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
mainDelay.interruptFromISR(&higherWake);
|
||||
};
|
||||
config.singlePress = INPUT_BROKER_USER_PRESS;
|
||||
config.longPress = INPUT_BROKER_SELECT;
|
||||
UserButtonThread->initButton(config);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef BUTTON_PIN_TOUCH
|
||||
TouchButtonThread = new ButtonThread("BackButton");
|
||||
ButtonConfig touchConfig;
|
||||
touchConfig.pinNumber = BUTTON_PIN_TOUCH;
|
||||
touchConfig.activeLow = true;
|
||||
touchConfig.activePullup = true;
|
||||
touchConfig.pullupSense = pullup_sense;
|
||||
touchConfig.intRoutine = []() {
|
||||
TouchButtonThread->userButton.tick();
|
||||
TouchButtonThread->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
mainDelay.interruptFromISR(&higherWake);
|
||||
};
|
||||
touchConfig.singlePress = INPUT_BROKER_NONE;
|
||||
touchConfig.longPress = INPUT_BROKER_BACK;
|
||||
#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN)
|
||||
// On T-Echo Plus the touch pad should only drive the backlight, not UI navigation/sounds
|
||||
touchConfig.longPress = INPUT_BROKER_NONE;
|
||||
touchConfig.suppressLeadUpSound = true;
|
||||
touchConfig.onPress = []() {
|
||||
touchBacklightWasOn = uiconfig.screen_brightness == 1;
|
||||
if (!touchBacklightWasOn) {
|
||||
digitalWrite(PIN_EINK_EN, HIGH);
|
||||
}
|
||||
touchBacklightActive = true;
|
||||
};
|
||||
touchConfig.onRelease = []() {
|
||||
if (touchBacklightActive && !touchBacklightWasOn) {
|
||||
digitalWrite(PIN_EINK_EN, LOW);
|
||||
}
|
||||
touchBacklightActive = false;
|
||||
};
|
||||
#endif
|
||||
TouchButtonThread->initButton(touchConfig);
|
||||
#endif
|
||||
|
||||
#if defined(CANCEL_BUTTON_PIN)
|
||||
// Buttons. Moved here cause we need NodeDB to be initialized
|
||||
CancelButtonThread = new ButtonThread("CancelButton");
|
||||
ButtonConfig cancelConfig;
|
||||
cancelConfig.pinNumber = CANCEL_BUTTON_PIN;
|
||||
cancelConfig.activeLow = CANCEL_BUTTON_ACTIVE_LOW;
|
||||
cancelConfig.activePullup = CANCEL_BUTTON_ACTIVE_PULLUP;
|
||||
cancelConfig.pullupSense = pullup_sense;
|
||||
cancelConfig.intRoutine = []() {
|
||||
CancelButtonThread->userButton.tick();
|
||||
CancelButtonThread->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
mainDelay.interruptFromISR(&higherWake);
|
||||
};
|
||||
cancelConfig.singlePress = INPUT_BROKER_CANCEL;
|
||||
cancelConfig.longPress = INPUT_BROKER_SHUTDOWN;
|
||||
cancelConfig.longPressTime = 4000;
|
||||
CancelButtonThread->initButton(cancelConfig);
|
||||
#endif
|
||||
|
||||
#if defined(ALT_BUTTON_PIN)
|
||||
// Buttons. Moved here cause we need NodeDB to be initialized
|
||||
BackButtonThread = new ButtonThread("BackButton");
|
||||
ButtonConfig backConfig;
|
||||
backConfig.pinNumber = ALT_BUTTON_PIN;
|
||||
backConfig.activeLow = ALT_BUTTON_ACTIVE_LOW;
|
||||
backConfig.activePullup = ALT_BUTTON_ACTIVE_PULLUP;
|
||||
backConfig.pullupSense = pullup_sense;
|
||||
backConfig.intRoutine = []() {
|
||||
BackButtonThread->userButton.tick();
|
||||
BackButtonThread->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
mainDelay.interruptFromISR(&higherWake);
|
||||
};
|
||||
backConfig.singlePress = INPUT_BROKER_ALT_PRESS;
|
||||
backConfig.longPress = INPUT_BROKER_ALT_LONG;
|
||||
backConfig.longPressTime = 500;
|
||||
BackButtonThread->initButton(backConfig);
|
||||
#endif
|
||||
|
||||
#if defined(BUTTON_PIN)
|
||||
#if defined(USERPREFS_BUTTON_PIN)
|
||||
int _pinNum = config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN;
|
||||
#else
|
||||
int _pinNum = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN;
|
||||
#endif
|
||||
#ifndef BUTTON_ACTIVE_LOW
|
||||
#define BUTTON_ACTIVE_LOW true
|
||||
#endif
|
||||
#ifndef BUTTON_ACTIVE_PULLUP
|
||||
#define BUTTON_ACTIVE_PULLUP true
|
||||
#endif
|
||||
|
||||
// Buttons. Moved here cause we need NodeDB to be initialized
|
||||
// If your variant.h has a BUTTON_PIN defined, go ahead and define BUTTON_ACTIVE_LOW and BUTTON_ACTIVE_PULLUP
|
||||
UserButtonThread = new ButtonThread("UserButton");
|
||||
if (screen) {
|
||||
ButtonConfig userConfig;
|
||||
userConfig.pinNumber = (uint8_t)_pinNum;
|
||||
userConfig.activeLow = BUTTON_ACTIVE_LOW;
|
||||
userConfig.activePullup = BUTTON_ACTIVE_PULLUP;
|
||||
userConfig.pullupSense = pullup_sense;
|
||||
userConfig.intRoutine = []() {
|
||||
UserButtonThread->userButton.tick();
|
||||
UserButtonThread->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
mainDelay.interruptFromISR(&higherWake);
|
||||
};
|
||||
userConfig.singlePress = INPUT_BROKER_USER_PRESS;
|
||||
userConfig.longPress = INPUT_BROKER_SELECT;
|
||||
userConfig.longPressTime = 500;
|
||||
userConfig.longLongPress = INPUT_BROKER_SHUTDOWN;
|
||||
UserButtonThread->initButton(userConfig);
|
||||
} else {
|
||||
ButtonConfig userConfigNoScreen;
|
||||
userConfigNoScreen.pinNumber = (uint8_t)_pinNum;
|
||||
userConfigNoScreen.activeLow = BUTTON_ACTIVE_LOW;
|
||||
userConfigNoScreen.activePullup = BUTTON_ACTIVE_PULLUP;
|
||||
userConfigNoScreen.pullupSense = pullup_sense;
|
||||
userConfigNoScreen.intRoutine = []() {
|
||||
UserButtonThread->userButton.tick();
|
||||
UserButtonThread->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
mainDelay.interruptFromISR(&higherWake);
|
||||
};
|
||||
userConfigNoScreen.singlePress = INPUT_BROKER_USER_PRESS;
|
||||
userConfigNoScreen.longPress = INPUT_BROKER_NONE;
|
||||
userConfigNoScreen.longPressTime = 500;
|
||||
userConfigNoScreen.longLongPress = INPUT_BROKER_SHUTDOWN;
|
||||
userConfigNoScreen.doublePress = INPUT_BROKER_SEND_PING;
|
||||
userConfigNoScreen.triplePress = INPUT_BROKER_GPS_TOGGLE;
|
||||
UserButtonThread->initButton(userConfigNoScreen);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
|
||||
if (inputBroker)
|
||||
inputBroker->Init();
|
||||
#endif
|
||||
|
||||
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
@@ -1217,252 +991,7 @@ void setup()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
// as one can't use a function pointer to the class constructor:
|
||||
auto loraModuleInterface = [](LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
|
||||
RADIOLIB_PIN_TYPE busy) {
|
||||
switch (portduino_config.lora_module) {
|
||||
case use_rf95:
|
||||
return (RadioInterface *)new RF95Interface(hal, cs, irq, rst, busy);
|
||||
case use_sx1262:
|
||||
return (RadioInterface *)new SX1262Interface(hal, cs, irq, rst, busy);
|
||||
case use_sx1268:
|
||||
return (RadioInterface *)new SX1268Interface(hal, cs, irq, rst, busy);
|
||||
case use_sx1280:
|
||||
return (RadioInterface *)new SX1280Interface(hal, cs, irq, rst, busy);
|
||||
case use_lr1110:
|
||||
return (RadioInterface *)new LR1110Interface(hal, cs, irq, rst, busy);
|
||||
case use_lr1120:
|
||||
return (RadioInterface *)new LR1120Interface(hal, cs, irq, rst, busy);
|
||||
case use_lr1121:
|
||||
return (RadioInterface *)new LR1121Interface(hal, cs, irq, rst, busy);
|
||||
case use_llcc68:
|
||||
return (RadioInterface *)new LLCC68Interface(hal, cs, irq, rst, busy);
|
||||
case use_simradio:
|
||||
return (RadioInterface *)new SimRadio;
|
||||
default:
|
||||
assert(0); // shouldn't happen
|
||||
return (RadioInterface *)nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
LOG_DEBUG("Activate %s radio on SPI port %s", portduino_config.loraModules[portduino_config.lora_module].c_str(),
|
||||
portduino_config.lora_spi_dev.c_str());
|
||||
if (portduino_config.lora_spi_dev == "ch341") {
|
||||
RadioLibHAL = ch341Hal;
|
||||
} else {
|
||||
RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
|
||||
}
|
||||
rIf =
|
||||
loraModuleInterface((LockingArduinoHal *)RadioLibHAL, portduino_config.lora_cs_pin.pin, portduino_config.lora_irq_pin.pin,
|
||||
portduino_config.lora_reset_pin.pin, portduino_config.lora_busy_pin.pin);
|
||||
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No %s radio", portduino_config.loraModules[portduino_config.lora_module].c_str());
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
LOG_INFO("%s init success", portduino_config.loraModules[portduino_config.lora_module].c_str());
|
||||
}
|
||||
|
||||
#elif defined(HW_SPI1_DEVICE)
|
||||
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI1, spiSettings);
|
||||
#else // HW_SPI1_DEVICE
|
||||
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
|
||||
#endif
|
||||
|
||||
// radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init)
|
||||
#if defined(USE_STM32WLx)
|
||||
if (!rIf) {
|
||||
rIf = new STM32WLE5JCInterface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No STM32WL radio");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("STM32WL init success");
|
||||
radioType = STM32WLx_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(RF95_IRQ) && RADIOLIB_EXCLUDE_SX127X != 1
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No RF95 radio");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("RF95 init success");
|
||||
radioType = RF95_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
#ifdef SX126X_DIO3_TCXO_VOLTAGE
|
||||
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
|
||||
#endif
|
||||
if (!sxIf->init()) {
|
||||
LOG_WARN("No SX1262 radio");
|
||||
delete sxIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1262 init success");
|
||||
rIf = sxIf;
|
||||
radioType = SX1262_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL)
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
// try using the specified TCXO voltage
|
||||
auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
|
||||
if (!sxIf->init()) {
|
||||
LOG_WARN("No SX1262 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
|
||||
delete sxIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1262 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
|
||||
rIf = sxIf;
|
||||
radioType = SX1262_RADIO;
|
||||
}
|
||||
}
|
||||
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
// If specified TCXO voltage fails, attempt to use DIO3 as a reference instead
|
||||
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No SX1262 radio with XTAL, Vref 0.0V");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1262 init success, XTAL, Vref 0.0V");
|
||||
radioType = SX1262_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_SX1268)
|
||||
#if defined(SX126X_DIO3_TCXO_VOLTAGE) && defined(TCXO_OPTIONAL)
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
// try using the specified TCXO voltage
|
||||
auto *sxIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
|
||||
if (!sxIf->init()) {
|
||||
LOG_WARN("No SX1268 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
|
||||
delete sxIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1268 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
|
||||
rIf = sxIf;
|
||||
radioType = SX1268_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
rIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No SX1268 radio");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1268 init success");
|
||||
radioType = SX1268_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_LLCC68)
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
rIf = new LLCC68Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No LLCC68 radio");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("LLCC68 init success");
|
||||
radioType = LLCC68_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_LR1110) && RADIOLIB_EXCLUDE_LR11X0 != 1
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No LR1110 radio");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("LR1110 init success");
|
||||
radioType = LR1110_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_LR1120) && RADIOLIB_EXCLUDE_LR11X0 != 1
|
||||
if (!rIf) {
|
||||
rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No LR1120 radio");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("LR1120 init success");
|
||||
radioType = LR1120_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_LR1121) && RADIOLIB_EXCLUDE_LR11X0 != 1
|
||||
if (!rIf) {
|
||||
rIf = new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No LR1121 radio");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("LR1121 init success");
|
||||
radioType = LR1121_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_SX1280) && RADIOLIB_EXCLUDE_SX128X != 1
|
||||
if (!rIf) {
|
||||
rIf = new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No SX1280 radio");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1280 init success");
|
||||
radioType = SX1280_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// check if the radio chip matches the selected region
|
||||
if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && rIf && (!rIf->wideLora())) {
|
||||
LOG_WARN("LoRa chip does not support 2.4GHz. Revert to unset");
|
||||
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET;
|
||||
nodeDB->saveToDisk(SEGMENT_CONFIG);
|
||||
|
||||
if (!rIf->reconfigure()) {
|
||||
LOG_WARN("Reconfigure failed, rebooting");
|
||||
if (screen) {
|
||||
screen->showSimpleBanner("Rebooting...");
|
||||
}
|
||||
rebootAtMsec = millis() + 5000;
|
||||
}
|
||||
}
|
||||
initLoRa();
|
||||
|
||||
lateInitVariant(); // Do board specific init (see extra_variants/README.md for documentation)
|
||||
|
||||
@@ -1551,6 +1080,7 @@ bool suppressRebootBanner; // If true, suppress "Rebooting..." overlay (used for
|
||||
// This will suppress the current delay and instead try to run ASAP.
|
||||
bool runASAP;
|
||||
|
||||
// TODO find better home than main.cpp
|
||||
extern meshtastic_DeviceMetadata getDeviceMetadata()
|
||||
{
|
||||
meshtastic_DeviceMetadata deviceMetadata;
|
||||
@@ -1651,7 +1181,43 @@ void loop()
|
||||
if (inputBroker)
|
||||
inputBroker->processInputEventQueue();
|
||||
#endif
|
||||
#if ARCH_PORTDUINO && HAS_TFT
|
||||
#if ARCH_PORTDUINO
|
||||
if (portduino_config.lora_spi_dev == "ch341" && ch341Hal != nullptr) {
|
||||
ch341Hal->checkError();
|
||||
}
|
||||
if (portduino_status.LoRa_in_error && rebootAtMsec == 0) {
|
||||
LOG_ERROR("LoRa in error detected, attempting to recover");
|
||||
if (rIf != nullptr) {
|
||||
delete rIf;
|
||||
rIf = nullptr;
|
||||
}
|
||||
if (portduino_config.lora_spi_dev == "ch341") {
|
||||
if (ch341Hal != nullptr) {
|
||||
delete ch341Hal;
|
||||
ch341Hal = nullptr;
|
||||
sleep(3);
|
||||
}
|
||||
try {
|
||||
ch341Hal = new Ch341Hal(0, portduino_config.lora_usb_serial_num, portduino_config.lora_usb_vid,
|
||||
portduino_config.lora_usb_pid);
|
||||
} catch (std::exception &e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
std::cerr << "Could not initialize CH341 device!" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
if (initLoRa()) {
|
||||
router->addInterface(rIf);
|
||||
portduino_status.LoRa_in_error = false;
|
||||
} else {
|
||||
LOG_WARN("Reconfigure failed, rebooting");
|
||||
if (screen) {
|
||||
screen->showSimpleBanner("Rebooting...");
|
||||
}
|
||||
rebootAtMsec = millis() + 25;
|
||||
}
|
||||
}
|
||||
#if HAS_TFT
|
||||
if (screen && portduino_config.displayPanel == x11 &&
|
||||
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
||||
auto dispdev = screen->getDisplayDevice();
|
||||
@@ -1659,6 +1225,7 @@ void loop()
|
||||
static_cast<TFTDisplay *>(dispdev)->sdlLoop();
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#if HAS_SCREEN && ENABLE_MESSAGE_PERSISTENCE
|
||||
messageStoreAutosaveTick();
|
||||
#endif
|
||||
|
||||
12
src/main.h
12
src/main.h
@@ -26,8 +26,8 @@ extern NRF52Bluetooth *nrf52Bluetooth;
|
||||
#if ARCH_PORTDUINO
|
||||
extern HardwareSPI *DisplaySPI;
|
||||
extern HardwareSPI *LoraSPI;
|
||||
|
||||
#endif
|
||||
|
||||
extern ScanI2C::DeviceAddress screen_found;
|
||||
extern ScanI2C::DeviceAddress cardkb_found;
|
||||
extern uint8_t kb_model;
|
||||
@@ -47,16 +47,16 @@ extern bool isUSBPowered;
|
||||
extern Adafruit_DRV2605 drv;
|
||||
#endif
|
||||
|
||||
#ifdef HAS_PCA9557
|
||||
#include <PCA9557.h>
|
||||
extern PCA9557 io;
|
||||
#endif
|
||||
|
||||
#ifdef HAS_I2S
|
||||
#include "AudioThread.h"
|
||||
extern AudioThread *audioThread;
|
||||
#endif
|
||||
|
||||
#ifdef ELECROW_ThinkNode_M5
|
||||
#include <PCA9557.h>
|
||||
extern PCA9557 io;
|
||||
#endif
|
||||
|
||||
#ifdef HAS_UDP_MULTICAST
|
||||
#include "mesh/udp/UdpMulticastHandler.h"
|
||||
extern UdpMulticastHandler *udpHandler;
|
||||
|
||||
@@ -61,11 +61,6 @@ bool CryptoEngine::regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey)
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
void CryptoEngine::clearKeys()
|
||||
{
|
||||
memset(public_key, 0, sizeof(public_key));
|
||||
memset(private_key, 0, sizeof(private_key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a packet's payload using a key generated with Curve25519 and SHA256
|
||||
|
||||
@@ -37,7 +37,6 @@ class CryptoEngine
|
||||
virtual bool regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey);
|
||||
|
||||
#endif
|
||||
void clearKeys();
|
||||
void setDHPrivateKey(uint8_t *_private_key);
|
||||
virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic,
|
||||
uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut);
|
||||
|
||||
@@ -25,7 +25,7 @@ template class LR11x0Interface<LR1121>;
|
||||
template class SX126xInterface<STM32WLx>;
|
||||
#endif
|
||||
|
||||
#if HAS_ETHERNET && !defined(USE_WS5500) && !defined(USE_CH390D)
|
||||
#if HAS_ETHERNET && !defined(USE_WS5500)
|
||||
#include "api/ethServerAPI.h"
|
||||
template class ServerAPI<EthernetClient>;
|
||||
template class APIServerPort<ethServerAPI, EthernetServer>;
|
||||
|
||||
@@ -91,10 +91,21 @@ template <typename T> bool LR11x0Interface<T>::init()
|
||||
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("LR11x0 init failed with %d (SPI_CMD_FAILED), retrying after delay...", res);
|
||||
delay(100);
|
||||
res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage);
|
||||
}
|
||||
|
||||
// \todo Display actual typename of the adapter, not just `LR11x0`
|
||||
LOG_INFO("LR11x0 init result %d", res);
|
||||
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND)
|
||||
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
|
||||
return false;
|
||||
|
||||
LR11x0VersionInfo_t version;
|
||||
@@ -159,7 +170,7 @@ template <typename T> bool LR11x0Interface<T>::reconfigure()
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
|
||||
err = lora.setCodingRate(cr);
|
||||
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);
|
||||
|
||||
|
||||
@@ -22,4 +22,99 @@ struct RegionInfo {
|
||||
extern const RegionInfo regions[];
|
||||
extern const RegionInfo *myRegion;
|
||||
|
||||
extern void initRegion();
|
||||
extern void initRegion();
|
||||
|
||||
static inline float bwCodeToKHz(uint16_t bwCode)
|
||||
{
|
||||
if (bwCode == 31)
|
||||
return 31.25f;
|
||||
if (bwCode == 62)
|
||||
return 62.5f;
|
||||
if (bwCode == 200)
|
||||
return 203.125f;
|
||||
if (bwCode == 400)
|
||||
return 406.25f;
|
||||
if (bwCode == 800)
|
||||
return 812.5f;
|
||||
if (bwCode == 1600)
|
||||
return 1625.0f;
|
||||
return (float)bwCode;
|
||||
}
|
||||
|
||||
static inline uint16_t bwKHzToCode(float bwKHz)
|
||||
{
|
||||
if (bwKHz > 31.24f && bwKHz < 31.26f)
|
||||
return 31;
|
||||
if (bwKHz > 62.49f && bwKHz < 62.51f)
|
||||
return 62;
|
||||
if (bwKHz > 203.12f && bwKHz < 203.13f)
|
||||
return 200;
|
||||
if (bwKHz > 406.24f && bwKHz < 406.26f)
|
||||
return 400;
|
||||
if (bwKHz > 812.49f && bwKHz < 812.51f)
|
||||
return 800;
|
||||
if (bwKHz > 1624.99f && bwKHz < 1625.01f)
|
||||
return 1600;
|
||||
return (uint16_t)(bwKHz + 0.5f);
|
||||
}
|
||||
|
||||
static inline void modemPresetToParams(meshtastic_Config_LoRaConfig_ModemPreset preset, bool wideLora, float &bwKHz, uint8_t &sf,
|
||||
uint8_t &cr)
|
||||
{
|
||||
switch (preset) {
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
|
||||
bwKHz = wideLora ? 1625.0f : 500.0f;
|
||||
cr = 5;
|
||||
sf = 7;
|
||||
break;
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST:
|
||||
bwKHz = wideLora ? 812.5f : 250.0f;
|
||||
cr = 5;
|
||||
sf = 7;
|
||||
break;
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW:
|
||||
bwKHz = wideLora ? 812.5f : 250.0f;
|
||||
cr = 5;
|
||||
sf = 8;
|
||||
break;
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST:
|
||||
bwKHz = wideLora ? 812.5f : 250.0f;
|
||||
cr = 5;
|
||||
sf = 9;
|
||||
break;
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW:
|
||||
bwKHz = wideLora ? 812.5f : 250.0f;
|
||||
cr = 5;
|
||||
sf = 10;
|
||||
break;
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO:
|
||||
bwKHz = wideLora ? 1625.0f : 500.0f;
|
||||
cr = 8;
|
||||
sf = 11;
|
||||
break;
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
|
||||
bwKHz = wideLora ? 406.25f : 125.0f;
|
||||
cr = 8;
|
||||
sf = 11;
|
||||
break;
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW:
|
||||
bwKHz = wideLora ? 406.25f : 125.0f;
|
||||
cr = 8;
|
||||
sf = 12;
|
||||
break;
|
||||
default: // LONG_FAST (or illegal)
|
||||
bwKHz = wideLora ? 812.5f : 250.0f;
|
||||
cr = 5;
|
||||
sf = 11;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline float modemPresetToBwKHz(meshtastic_Config_LoRaConfig_ModemPreset preset, bool wideLora)
|
||||
{
|
||||
float bwKHz = 0;
|
||||
uint8_t sf = 0;
|
||||
uint8_t cr = 0;
|
||||
modemPresetToParams(preset, wideLora, bwKHz, sf, cr);
|
||||
return bwKHz;
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "PacketHistory.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "RTC.h"
|
||||
#include "RadioInterface.h"
|
||||
#include "Router.h"
|
||||
#include "SPILock.h"
|
||||
#include "SafeFile.h"
|
||||
@@ -26,6 +27,7 @@
|
||||
#include <algorithm>
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
#include <power/PowerHAL.h>
|
||||
#include <vector>
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
@@ -822,16 +824,10 @@ void NodeDB::installDefaultModuleConfig()
|
||||
moduleConfig.external_notification.output_ms = 500;
|
||||
moduleConfig.external_notification.nag_timeout = 2;
|
||||
#endif
|
||||
#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312) || defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3) || \
|
||||
defined(ELECROW_ThinkNode_M4) || defined(ELECROW_ThinkNode_M6)
|
||||
// Default to PIN_LED2 for external notification output (LED color depends on device variant)
|
||||
#if defined(LED_NOTIFICATION)
|
||||
moduleConfig.external_notification.enabled = true;
|
||||
moduleConfig.external_notification.output = PIN_LED2;
|
||||
#if defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3)
|
||||
moduleConfig.external_notification.active = false;
|
||||
#else
|
||||
moduleConfig.external_notification.active = true;
|
||||
#endif
|
||||
moduleConfig.external_notification.output = LED_NOTIFICATION;
|
||||
moduleConfig.external_notification.active = LED_STATE_ON;
|
||||
moduleConfig.external_notification.alert_message = true;
|
||||
moduleConfig.external_notification.output_ms = 1000;
|
||||
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
|
||||
@@ -855,15 +851,6 @@ void NodeDB::installDefaultModuleConfig()
|
||||
moduleConfig.external_notification.output_ms = 100;
|
||||
moduleConfig.external_notification.active = true;
|
||||
#endif
|
||||
#ifdef ELECROW_ThinkNode_M1
|
||||
// Default to Elecrow USER_LED (blue)
|
||||
moduleConfig.external_notification.enabled = true;
|
||||
moduleConfig.external_notification.output = USER_LED;
|
||||
moduleConfig.external_notification.active = true;
|
||||
moduleConfig.external_notification.alert_message = true;
|
||||
moduleConfig.external_notification.output_ms = 1000;
|
||||
moduleConfig.external_notification.nag_timeout = 60;
|
||||
#endif
|
||||
#ifdef T_LORA_PAGER
|
||||
moduleConfig.canned_message.updown1_enabled = true;
|
||||
moduleConfig.canned_message.inputbroker_pin_a = ROTARY_A;
|
||||
@@ -1297,6 +1284,13 @@ void NodeDB::loadFromDisk()
|
||||
LOG_INFO("Loaded saved config version %d", config.version);
|
||||
}
|
||||
}
|
||||
|
||||
// Coerce LoRa config fields derived from presets while bootstrapping.
|
||||
// Some clients/UI components display bandwidth/spread_factor directly from config even in preset mode.
|
||||
if (config.has_lora && config.lora.use_preset) {
|
||||
RadioInterface::bootstrapLoRaConfigFromPreset(config.lora);
|
||||
}
|
||||
|
||||
if (backupSecurity.private_key.size > 0) {
|
||||
LOG_DEBUG("Restoring backup of security config");
|
||||
config.security = backupSecurity;
|
||||
@@ -1418,6 +1412,14 @@ void NodeDB::loadFromDisk()
|
||||
bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct,
|
||||
bool fullAtomic)
|
||||
{
|
||||
|
||||
// do not try to save anything if power level is not safe. In many cases flash will be lock-protected
|
||||
// and all writes will fail anyway. Device should be sleeping at this point anyway.
|
||||
if (!powerHAL_isPowerLevelSafe()) {
|
||||
LOG_ERROR("Error: trying to saveProto() on unsafe device power level.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool okay = false;
|
||||
#ifdef FSCom
|
||||
auto f = SafeFile(filename, fullAtomic);
|
||||
@@ -1444,6 +1446,14 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_
|
||||
|
||||
bool NodeDB::saveChannelsToDisk()
|
||||
{
|
||||
|
||||
// do not try to save anything if power level is not safe. In many cases flash will be lock-protected
|
||||
// and all writes will fail anyway.
|
||||
if (!powerHAL_isPowerLevelSafe()) {
|
||||
LOG_ERROR("Error: trying to saveChannelsToDisk() on unsafe device power level.");
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef FSCom
|
||||
spiLock->lock();
|
||||
FSCom.mkdir("/prefs");
|
||||
@@ -1454,6 +1464,14 @@ bool NodeDB::saveChannelsToDisk()
|
||||
|
||||
bool NodeDB::saveDeviceStateToDisk()
|
||||
{
|
||||
|
||||
// do not try to save anything if power level is not safe. In many cases flash will be lock-protected
|
||||
// and all writes will fail anyway. Device should be sleeping at this point anyway.
|
||||
if (!powerHAL_isPowerLevelSafe()) {
|
||||
LOG_ERROR("Error: trying to saveDeviceStateToDisk() on unsafe device power level.");
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef FSCom
|
||||
spiLock->lock();
|
||||
FSCom.mkdir("/prefs");
|
||||
@@ -1466,6 +1484,14 @@ bool NodeDB::saveDeviceStateToDisk()
|
||||
|
||||
bool NodeDB::saveNodeDatabaseToDisk()
|
||||
{
|
||||
|
||||
// do not try to save anything if power level is not safe. In many cases flash will be lock-protected
|
||||
// and all writes will fail anyway. Device should be sleeping at this point anyway.
|
||||
if (!powerHAL_isPowerLevelSafe()) {
|
||||
LOG_ERROR("Error: trying to saveNodeDatabaseToDisk() on unsafe device power level.");
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef FSCom
|
||||
spiLock->lock();
|
||||
FSCom.mkdir("/prefs");
|
||||
@@ -1478,6 +1504,14 @@ bool NodeDB::saveNodeDatabaseToDisk()
|
||||
|
||||
bool NodeDB::saveToDiskNoRetry(int saveWhat)
|
||||
{
|
||||
|
||||
// do not try to save anything if power level is not safe. In many cases flash will be lock-protected
|
||||
// and all writes will fail anyway. Device should be sleeping at this point anyway.
|
||||
if (!powerHAL_isPowerLevelSafe()) {
|
||||
LOG_ERROR("Error: trying to saveToDiskNoRetry() on unsafe device power level.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
#ifdef FSCom
|
||||
spiLock->lock();
|
||||
@@ -1533,6 +1567,14 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
|
||||
bool NodeDB::saveToDisk(int saveWhat)
|
||||
{
|
||||
LOG_DEBUG("Save to disk %d", saveWhat);
|
||||
|
||||
// do not try to save anything if power level is not safe. In many cases flash will be lock-protected
|
||||
// and all writes will fail anyway. Device should be sleeping at this point anyway.
|
||||
if (!powerHAL_isPowerLevelSafe()) {
|
||||
LOG_ERROR("Error: trying to saveToDisk() on unsafe device power level.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = saveToDiskNoRetry(saveWhat);
|
||||
|
||||
if (!success) {
|
||||
@@ -2190,7 +2232,10 @@ void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, co
|
||||
|
||||
// Currently portuino is mostly used for simulation. Make sure the user notices something really bad happened
|
||||
#ifdef ARCH_PORTDUINO
|
||||
LOG_ERROR("A critical failure occurred, portduino is exiting");
|
||||
exit(2);
|
||||
LOG_ERROR("A critical failure occurred");
|
||||
// TODO: Determine if other critical errors should also cause an immediate exit
|
||||
if (code == meshtastic_CriticalErrorCode_FLASH_CORRUPTION_RECOVERABLE ||
|
||||
code == meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE)
|
||||
exit(2);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -177,6 +177,9 @@ bool RF95Interface::init()
|
||||
|
||||
int res = lora->begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength);
|
||||
LOG_INFO("RF95 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);
|
||||
|
||||
@@ -1,17 +1,36 @@
|
||||
#include "RadioInterface.h"
|
||||
#include "Channels.h"
|
||||
#include "DisplayFormatters.h"
|
||||
#include "LLCC68Interface.h"
|
||||
#include "LR1110Interface.h"
|
||||
#include "LR1120Interface.h"
|
||||
#include "LR1121Interface.h"
|
||||
#include "MeshRadio.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "RF95Interface.h"
|
||||
#include "Router.h"
|
||||
#include "SX1262Interface.h"
|
||||
#include "SX1268Interface.h"
|
||||
#include "SX1280Interface.h"
|
||||
#include "configuration.h"
|
||||
#include "detect/LoRaRadioType.h"
|
||||
#include "main.h"
|
||||
#include "sleep.h"
|
||||
#include <assert.h>
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
#include "platform/portduino/PortduinoGlue.h"
|
||||
#include "platform/portduino/SimRadio.h"
|
||||
#include "platform/portduino/USBHal.h"
|
||||
#endif
|
||||
|
||||
#ifdef ARCH_STM32WL
|
||||
#include "STM32WLE5JCInterface.h"
|
||||
#endif
|
||||
|
||||
// Calculate 2^n without calling pow()
|
||||
uint32_t pow_of_2(uint32_t n)
|
||||
{
|
||||
@@ -205,6 +224,281 @@ bool RadioInterface::uses_default_frequency_slot = true;
|
||||
|
||||
static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1];
|
||||
|
||||
// Global LoRa radio type
|
||||
LoRaRadioType radioType = NO_RADIO;
|
||||
|
||||
extern RadioInterface *rIf;
|
||||
extern RadioLibHal *RadioLibHAL;
|
||||
#if defined(HW_SPI1_DEVICE) && defined(ARCH_ESP32)
|
||||
extern SPIClass SPI1;
|
||||
#endif
|
||||
|
||||
bool initLoRa()
|
||||
{
|
||||
if (rIf != nullptr) {
|
||||
delete rIf;
|
||||
rIf = nullptr;
|
||||
}
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0);
|
||||
#else
|
||||
SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
|
||||
#endif
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
// as one can't use a function pointer to the class constructor:
|
||||
auto loraModuleInterface = [](LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
|
||||
RADIOLIB_PIN_TYPE busy) {
|
||||
switch (portduino_config.lora_module) {
|
||||
case use_rf95:
|
||||
return (RadioInterface *)new RF95Interface(hal, cs, irq, rst, busy);
|
||||
case use_sx1262:
|
||||
return (RadioInterface *)new SX1262Interface(hal, cs, irq, rst, busy);
|
||||
case use_sx1268:
|
||||
return (RadioInterface *)new SX1268Interface(hal, cs, irq, rst, busy);
|
||||
case use_sx1280:
|
||||
return (RadioInterface *)new SX1280Interface(hal, cs, irq, rst, busy);
|
||||
case use_lr1110:
|
||||
return (RadioInterface *)new LR1110Interface(hal, cs, irq, rst, busy);
|
||||
case use_lr1120:
|
||||
return (RadioInterface *)new LR1120Interface(hal, cs, irq, rst, busy);
|
||||
case use_lr1121:
|
||||
return (RadioInterface *)new LR1121Interface(hal, cs, irq, rst, busy);
|
||||
case use_llcc68:
|
||||
return (RadioInterface *)new LLCC68Interface(hal, cs, irq, rst, busy);
|
||||
case use_simradio:
|
||||
return (RadioInterface *)new SimRadio;
|
||||
default:
|
||||
assert(0); // shouldn't happen
|
||||
return (RadioInterface *)nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
LOG_DEBUG("Activate %s radio on SPI port %s", portduino_config.loraModules[portduino_config.lora_module].c_str(),
|
||||
portduino_config.lora_spi_dev.c_str());
|
||||
if (portduino_config.lora_spi_dev == "ch341") {
|
||||
RadioLibHAL = ch341Hal;
|
||||
} else {
|
||||
if (RadioLibHAL != nullptr) {
|
||||
delete RadioLibHAL;
|
||||
RadioLibHAL = nullptr;
|
||||
}
|
||||
RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
|
||||
}
|
||||
rIf =
|
||||
loraModuleInterface((LockingArduinoHal *)RadioLibHAL, portduino_config.lora_cs_pin.pin, portduino_config.lora_irq_pin.pin,
|
||||
portduino_config.lora_reset_pin.pin, portduino_config.lora_busy_pin.pin);
|
||||
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No %s radio", portduino_config.loraModules[portduino_config.lora_module].c_str());
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
LOG_INFO("%s init success", portduino_config.loraModules[portduino_config.lora_module].c_str());
|
||||
}
|
||||
|
||||
#elif defined(HW_SPI1_DEVICE)
|
||||
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI1, spiSettings);
|
||||
#else // HW_SPI1_DEVICE
|
||||
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
|
||||
#endif
|
||||
|
||||
// radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init)
|
||||
#if defined(USE_STM32WLx)
|
||||
if (!rIf) {
|
||||
rIf = new STM32WLE5JCInterface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No STM32WL radio");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("STM32WL init success");
|
||||
radioType = STM32WLx_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(RF95_IRQ) && RADIOLIB_EXCLUDE_SX127X != 1
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No RF95 radio");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("RF95 init success");
|
||||
radioType = RF95_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
#ifdef SX126X_DIO3_TCXO_VOLTAGE
|
||||
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
|
||||
#endif
|
||||
if (!sxIf->init()) {
|
||||
LOG_WARN("No SX1262 radio");
|
||||
delete sxIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1262 init success");
|
||||
rIf = sxIf;
|
||||
radioType = SX1262_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL)
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
// try using the specified TCXO voltage
|
||||
auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
|
||||
if (!sxIf->init()) {
|
||||
LOG_WARN("No SX1262 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
|
||||
delete sxIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1262 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
|
||||
rIf = sxIf;
|
||||
radioType = SX1262_RADIO;
|
||||
}
|
||||
}
|
||||
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
// If specified TCXO voltage fails, attempt to use DIO3 as a reference instead
|
||||
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No SX1262 radio with XTAL, Vref 0.0V");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1262 init success, XTAL, Vref 0.0V");
|
||||
radioType = SX1262_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_SX1268)
|
||||
#if defined(SX126X_DIO3_TCXO_VOLTAGE) && defined(TCXO_OPTIONAL)
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
// try using the specified TCXO voltage
|
||||
auto *sxIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
|
||||
if (!sxIf->init()) {
|
||||
LOG_WARN("No SX1268 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
|
||||
delete sxIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1268 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
|
||||
rIf = sxIf;
|
||||
radioType = SX1268_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
rIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No SX1268 radio");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1268 init success");
|
||||
radioType = SX1268_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_LLCC68)
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
rIf = new LLCC68Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No LLCC68 radio");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("LLCC68 init success");
|
||||
radioType = LLCC68_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_LR1110) && RADIOLIB_EXCLUDE_LR11X0 != 1
|
||||
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
|
||||
rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No LR1110 radio");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("LR1110 init success");
|
||||
radioType = LR1110_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_LR1120) && RADIOLIB_EXCLUDE_LR11X0 != 1
|
||||
if (!rIf) {
|
||||
rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No LR1120 radio");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("LR1120 init success");
|
||||
radioType = LR1120_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_LR1121) && RADIOLIB_EXCLUDE_LR11X0 != 1
|
||||
if (!rIf) {
|
||||
rIf = new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No LR1121 radio");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("LR1121 init success");
|
||||
radioType = LR1121_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_SX1280) && RADIOLIB_EXCLUDE_SX128X != 1
|
||||
if (!rIf) {
|
||||
rIf = new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY);
|
||||
if (!rIf->init()) {
|
||||
LOG_WARN("No SX1280 radio");
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
LOG_INFO("SX1280 init success");
|
||||
radioType = SX1280_RADIO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// check if the radio chip matches the selected region
|
||||
if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && rIf && (!rIf->wideLora())) {
|
||||
LOG_WARN("LoRa chip does not support 2.4GHz. Revert to unset");
|
||||
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET;
|
||||
nodeDB->saveToDisk(SEGMENT_CONFIG);
|
||||
|
||||
if (rIf && !rIf->reconfigure()) {
|
||||
LOG_WARN("Reconfigure failed, rebooting");
|
||||
if (screen) {
|
||||
screen->showSimpleBanner("Rebooting...");
|
||||
}
|
||||
rebootAtMsec = millis() + 5000;
|
||||
}
|
||||
}
|
||||
return rIf != nullptr;
|
||||
}
|
||||
|
||||
void initRegion()
|
||||
{
|
||||
const RegionInfo *r = regions;
|
||||
@@ -220,6 +514,34 @@ void initRegion()
|
||||
myRegion = r;
|
||||
}
|
||||
|
||||
void RadioInterface::bootstrapLoRaConfigFromPreset(meshtastic_Config_LoRaConfig &loraConfig)
|
||||
{
|
||||
if (!loraConfig.use_preset) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find region info to determine whether "wide" LoRa is permitted (2.4 GHz uses wider bandwidth codes).
|
||||
const RegionInfo *r = regions;
|
||||
for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != loraConfig.region; r++)
|
||||
;
|
||||
|
||||
const bool regionWideLora = r->wideLora;
|
||||
|
||||
float bwKHz = 0;
|
||||
uint8_t sf = 0;
|
||||
uint8_t cr = 0;
|
||||
modemPresetToParams(loraConfig.modem_preset, regionWideLora, bwKHz, sf, cr);
|
||||
|
||||
// If selected preset requests a bandwidth larger than the region span, fall back to LONG_FAST.
|
||||
if (r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && (r->freqEnd - r->freqStart) < (bwKHz / 1000.0f)) {
|
||||
loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
|
||||
modemPresetToParams(loraConfig.modem_preset, regionWideLora, bwKHz, sf, cr);
|
||||
}
|
||||
|
||||
loraConfig.bandwidth = bwKHzToCode(bwKHz);
|
||||
loraConfig.spread_factor = sf;
|
||||
}
|
||||
|
||||
/**
|
||||
* ## LoRaWAN for North America
|
||||
|
||||
@@ -474,54 +796,7 @@ void RadioInterface::applyModemConfig()
|
||||
bool validConfig = false; // We need to check for a valid configuration
|
||||
while (!validConfig) {
|
||||
if (loraConfig.use_preset) {
|
||||
|
||||
switch (loraConfig.modem_preset) {
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
|
||||
bw = (myRegion->wideLora) ? 1625.0 : 500;
|
||||
cr = 5;
|
||||
sf = 7;
|
||||
break;
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST:
|
||||
bw = (myRegion->wideLora) ? 812.5 : 250;
|
||||
cr = 5;
|
||||
sf = 7;
|
||||
break;
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW:
|
||||
bw = (myRegion->wideLora) ? 812.5 : 250;
|
||||
cr = 5;
|
||||
sf = 8;
|
||||
break;
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST:
|
||||
bw = (myRegion->wideLora) ? 812.5 : 250;
|
||||
cr = 5;
|
||||
sf = 9;
|
||||
break;
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW:
|
||||
bw = (myRegion->wideLora) ? 812.5 : 250;
|
||||
cr = 5;
|
||||
sf = 10;
|
||||
break;
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO:
|
||||
bw = (myRegion->wideLora) ? 1625.0 : 500;
|
||||
cr = 8;
|
||||
sf = 11;
|
||||
break;
|
||||
default: // Config_LoRaConfig_ModemPreset_LONG_FAST is default. Gracefully use this is preset is something illegal.
|
||||
bw = (myRegion->wideLora) ? 812.5 : 250;
|
||||
cr = 5;
|
||||
sf = 11;
|
||||
break;
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
|
||||
bw = (myRegion->wideLora) ? 406.25 : 125;
|
||||
cr = 8;
|
||||
sf = 11;
|
||||
break;
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW:
|
||||
bw = (myRegion->wideLora) ? 406.25 : 125;
|
||||
cr = 8;
|
||||
sf = 12;
|
||||
break;
|
||||
}
|
||||
modemPresetToParams(loraConfig.modem_preset, myRegion->wideLora, bw, sf, cr);
|
||||
if (loraConfig.coding_rate >= 5 && loraConfig.coding_rate <= 8 && loraConfig.coding_rate != cr) {
|
||||
cr = loraConfig.coding_rate;
|
||||
LOG_INFO("Using custom Coding Rate %u", cr);
|
||||
@@ -529,20 +804,7 @@ void RadioInterface::applyModemConfig()
|
||||
} else {
|
||||
sf = loraConfig.spread_factor;
|
||||
cr = loraConfig.coding_rate;
|
||||
bw = loraConfig.bandwidth;
|
||||
|
||||
if (bw == 31) // This parameter is not an integer
|
||||
bw = 31.25;
|
||||
if (bw == 62) // Fix for 62.5Khz bandwidth
|
||||
bw = 62.5;
|
||||
if (bw == 200)
|
||||
bw = 203.125;
|
||||
if (bw == 400)
|
||||
bw = 406.25;
|
||||
if (bw == 800)
|
||||
bw = 812.5;
|
||||
if (bw == 1600)
|
||||
bw = 1625.0;
|
||||
bw = bwCodeToKHz(loraConfig.bandwidth);
|
||||
}
|
||||
|
||||
if ((myRegion->freqEnd - myRegion->freqStart) < bw / 1000) {
|
||||
@@ -666,18 +928,24 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
|
||||
power = maxPower;
|
||||
}
|
||||
|
||||
#ifndef NUM_PA_POINTS
|
||||
if (TX_GAIN_LORA > 0 && !devicestate.owner.is_licensed) {
|
||||
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, TX_GAIN_LORA);
|
||||
power -= TX_GAIN_LORA;
|
||||
}
|
||||
#ifdef ARCH_PORTDUINO
|
||||
size_t num_pa_points = portduino_config.num_pa_points;
|
||||
const uint16_t *tx_gain = portduino_config.tx_gain_lora;
|
||||
#else
|
||||
if (!devicestate.owner.is_licensed) {
|
||||
size_t num_pa_points = NUM_PA_POINTS;
|
||||
const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA};
|
||||
#endif
|
||||
|
||||
if (num_pa_points == 1) {
|
||||
if (tx_gain[0] > 0 && !devicestate.owner.is_licensed) {
|
||||
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[0]);
|
||||
power -= tx_gain[0];
|
||||
}
|
||||
} else if (!devicestate.owner.is_licensed) {
|
||||
// we have an array of PA gain values. Find the highest power setting that works.
|
||||
const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA};
|
||||
for (int radio_dbm = 0; radio_dbm < NUM_PA_POINTS; radio_dbm++) {
|
||||
for (int radio_dbm = 0; radio_dbm < num_pa_points; radio_dbm++) {
|
||||
if (((radio_dbm + tx_gain[radio_dbm]) > power) ||
|
||||
((radio_dbm == (NUM_PA_POINTS - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) {
|
||||
((radio_dbm == (num_pa_points - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) {
|
||||
// we've exceeded the power limit, or hit the max we can do
|
||||
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[radio_dbm]);
|
||||
power -= tx_gain[radio_dbm];
|
||||
@@ -685,7 +953,7 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (power > loraMaxPower) // Clamp power to maximum defined level
|
||||
power = loraMaxPower;
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
#include "airtime.h"
|
||||
#include "error.h"
|
||||
|
||||
// Forward decl to avoid a direct include of generated config headers / full LoRaConfig definition in this widely-included file.
|
||||
typedef struct _meshtastic_Config_LoRaConfig meshtastic_Config_LoRaConfig;
|
||||
|
||||
#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission
|
||||
|
||||
#define MAX_LORA_PAYLOAD_LEN 255 // max length of 255 per Semtech's datasheets on SX12xx
|
||||
@@ -115,6 +118,12 @@ class RadioInterface
|
||||
|
||||
virtual ~RadioInterface() {}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
static void bootstrapLoRaConfigFromPreset(meshtastic_Config_LoRaConfig &loraConfig);
|
||||
|
||||
/**
|
||||
* Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving)
|
||||
*
|
||||
@@ -270,5 +279,7 @@ class RadioInterface
|
||||
}
|
||||
};
|
||||
|
||||
bool initLoRa();
|
||||
|
||||
/// Debug printing for packets
|
||||
void printPacket(const char *prefix, const meshtastic_MeshPacket *p);
|
||||
|
||||
@@ -17,12 +17,6 @@
|
||||
ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (p->want_ack) {
|
||||
// If someone asks for acks on broadcast, we need the hop limit to be at least one, so that first node that receives our
|
||||
// message will rebroadcast. But asking for hop_limit 0 in that context means the client app has no preference on hop
|
||||
// counts and we want this message to get through the whole mesh, so use the default.
|
||||
if (p->hop_limit == 0) {
|
||||
p->hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
|
||||
}
|
||||
DEBUG_HEAP_BEFORE;
|
||||
auto copy = packetPool.allocCopy(*p);
|
||||
DEBUG_HEAP_AFTER("ReliableRouter::send", copy);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "RTC.h"
|
||||
|
||||
#include "configuration.h"
|
||||
#include "detect/LoRaRadioType.h"
|
||||
#include "main.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "meshUtils.h"
|
||||
@@ -267,6 +266,13 @@ ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src)
|
||||
}
|
||||
}
|
||||
|
||||
// If someone asks for acks on broadcast, we need the hop limit to be at least one, so that first node that receives our
|
||||
// message will rebroadcast. But asking for hop_limit 0 in that context means the client app has no preference on hop
|
||||
// counts and we want this message to get through the whole mesh, so use the default.
|
||||
if (src == RX_SRC_USER && p->want_ack && p->hop_limit == 0) {
|
||||
p->hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
|
||||
}
|
||||
|
||||
return send(p);
|
||||
}
|
||||
}
|
||||
@@ -614,15 +620,19 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
|
||||
!(p->pki_encrypted != true && (strcasecmp(channels.getName(chIndex), Channels::serialChannel) == 0 ||
|
||||
strcasecmp(channels.getName(chIndex), Channels::gpioChannel) == 0)) &&
|
||||
// Check for valid keys and single node destination
|
||||
config.security.private_key.size == 32 && !isBroadcast(p->to) && node != nullptr &&
|
||||
// Check for a known public key for the destination
|
||||
(node->user.public_key.size == 32) &&
|
||||
config.security.private_key.size == 32 && !isBroadcast(p->to) &&
|
||||
// Some portnums either make no sense to send with PKC
|
||||
p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP &&
|
||||
p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && p->decoded.portnum != meshtastic_PortNum_POSITION_APP) {
|
||||
LOG_DEBUG("Use PKI!");
|
||||
if (numbytes + MESHTASTIC_HEADER_LENGTH + MESHTASTIC_PKC_OVERHEAD > MAX_LORA_PAYLOAD_LEN)
|
||||
return meshtastic_Routing_Error_TOO_LARGE;
|
||||
// Check for a known public key for the destination
|
||||
if (node == nullptr || node->user.public_key.size != 32) {
|
||||
LOG_WARN("Unknown public key for destination node 0x%08x (portnum %d), refusing to send legacy DM", p->to,
|
||||
p->decoded.portnum);
|
||||
return meshtastic_Routing_Error_PKI_SEND_FAIL_PUBLIC_KEY;
|
||||
}
|
||||
if (p->pki_encrypted && !memfll(p->public_key.bytes, 0, 32) &&
|
||||
memcmp(p->public_key.bytes, node->user.public_key.bytes, 32) != 0) {
|
||||
LOG_WARN("Client public key differs from requested: 0x%02x, stored key begins 0x%02x", *p->public_key.bytes,
|
||||
|
||||
@@ -269,8 +269,12 @@ template <typename T> void SX126xInterface<T>::setStandby()
|
||||
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_DEBUG("SX126x standby %s%d", radioLibErr, err);
|
||||
#ifdef ARCH_PORTDUINO
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
portduino_status.LoRa_in_error = true;
|
||||
#else
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
#endif
|
||||
isReceiving = false; // If we were receiving, not any more
|
||||
activeReceiveStart = 0;
|
||||
disableInterrupt();
|
||||
@@ -313,7 +317,12 @@ template <typename T> void SX126xInterface<T>::startReceive()
|
||||
int err = lora.startReceiveDutyCycleAuto(preambleLength, 8, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS);
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_ERROR("SX126X startReceiveDutyCycleAuto %s%d", radioLibErr, err);
|
||||
#ifdef ARCH_PORTDUINO
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
portduino_status.LoRa_in_error = true;
|
||||
#else
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
#endif
|
||||
|
||||
RadioLibInterface::startReceive();
|
||||
|
||||
@@ -341,7 +350,12 @@ template <typename T> bool SX126xInterface<T>::isChannelActive()
|
||||
return true;
|
||||
if (result != RADIOLIB_CHANNEL_FREE)
|
||||
LOG_ERROR("SX126X scanChannel %s%d", radioLibErr, result);
|
||||
#ifdef ARCH_PORTDUINO
|
||||
if (result == RADIOLIB_ERR_WRONG_MODEM)
|
||||
portduino_status.LoRa_in_error = true;
|
||||
#else
|
||||
assert(result != RADIOLIB_ERR_WRONG_MODEM);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -69,6 +69,8 @@ template <typename T> bool SX128xInterface<T>::init()
|
||||
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength);
|
||||
// \todo Display actual typename of the adapter, not just `SX128x`
|
||||
LOG_INFO("SX128x init result %d", res);
|
||||
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
|
||||
return false;
|
||||
|
||||
if ((config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (res == RADIOLIB_ERR_INVALID_FREQUENCY)) {
|
||||
LOG_WARN("Radio only supports 2.4GHz LoRa. Adjusting Region and rebooting");
|
||||
@@ -124,7 +126,7 @@ template <typename T> bool SX128xInterface<T>::reconfigure()
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
|
||||
err = lora.setCodingRate(cr);
|
||||
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);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "configuration.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
#if HAS_ETHERNET && !defined(USE_WS5500) && !defined(USE_CH390D)
|
||||
#if HAS_ETHERNET && !defined(USE_WS5500)
|
||||
|
||||
#include "ethServerAPI.h"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "ServerAPI.h"
|
||||
#if !defined(USE_WS5500) && !defined(USE_CH390D)
|
||||
#ifndef USE_WS5500
|
||||
#include <RAK13800_W5100S.h>
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,15 @@ PB_BIND(meshtastic_SharedContact, meshtastic_SharedContact, AUTO)
|
||||
PB_BIND(meshtastic_KeyVerificationAdmin, meshtastic_KeyVerificationAdmin, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_SensorConfig, meshtastic_SensorConfig, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_SCD4X_config, meshtastic_SCD4X_config, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_SEN5X_config, meshtastic_SEN5X_config, AUTO)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -77,7 +77,9 @@ typedef enum _meshtastic_AdminMessage_ModuleConfigType {
|
||||
/* TODO: REPLACE */
|
||||
meshtastic_AdminMessage_ModuleConfigType_DETECTIONSENSOR_CONFIG = 11,
|
||||
/* TODO: REPLACE */
|
||||
meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12
|
||||
meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12,
|
||||
/* TODO: REPLACE */
|
||||
meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG = 13
|
||||
} meshtastic_AdminMessage_ModuleConfigType;
|
||||
|
||||
typedef enum _meshtastic_AdminMessage_BackupLocation {
|
||||
@@ -169,6 +171,48 @@ typedef struct _meshtastic_KeyVerificationAdmin {
|
||||
uint32_t security_number;
|
||||
} meshtastic_KeyVerificationAdmin;
|
||||
|
||||
typedef struct _meshtastic_SCD4X_config {
|
||||
/* Set Automatic self-calibration enabled */
|
||||
bool has_set_asc;
|
||||
bool set_asc;
|
||||
/* Recalibration target CO2 concentration in ppm (FRC or ASC) */
|
||||
bool has_set_target_co2_conc;
|
||||
uint32_t set_target_co2_conc;
|
||||
/* Reference temperature in degC */
|
||||
bool has_set_temperature;
|
||||
float set_temperature;
|
||||
/* Altitude of sensor in meters above sea level. 0 - 3000m (overrides ambient pressure) */
|
||||
bool has_set_altitude;
|
||||
uint32_t set_altitude;
|
||||
/* Sensor ambient pressure in Pa. 70000 - 120000 Pa (overrides altitude) */
|
||||
bool has_set_ambient_pressure;
|
||||
uint32_t set_ambient_pressure;
|
||||
/* Perform a factory reset of the sensor */
|
||||
bool has_factory_reset;
|
||||
bool factory_reset;
|
||||
/* Power mode for sensor (true for low power, false for normal) */
|
||||
bool has_set_power_mode;
|
||||
bool set_power_mode;
|
||||
} meshtastic_SCD4X_config;
|
||||
|
||||
typedef struct _meshtastic_SEN5X_config {
|
||||
/* Reference temperature in degC */
|
||||
bool has_set_temperature;
|
||||
float set_temperature;
|
||||
/* One-shot mode (true for low power - one-shot mode, false for normal - continuous mode) */
|
||||
bool has_set_one_shot_mode;
|
||||
bool set_one_shot_mode;
|
||||
} meshtastic_SEN5X_config;
|
||||
|
||||
typedef struct _meshtastic_SensorConfig {
|
||||
/* SCD4X CO2 Sensor configuration */
|
||||
bool has_scd4x_config;
|
||||
meshtastic_SCD4X_config scd4x_config;
|
||||
/* SEN5X PM Sensor configuration */
|
||||
bool has_sen5x_config;
|
||||
meshtastic_SEN5X_config sen5x_config;
|
||||
} meshtastic_SensorConfig;
|
||||
|
||||
typedef PB_BYTES_ARRAY_T(8) meshtastic_AdminMessage_session_passkey_t;
|
||||
/* This message is handled by the Admin module and is responsible for all settings/channel read/write operations.
|
||||
This message is used to do settings operations to both remote AND local nodes.
|
||||
@@ -301,6 +345,8 @@ typedef struct _meshtastic_AdminMessage {
|
||||
bool nodedb_reset;
|
||||
/* Tell the node to reset into the OTA Loader */
|
||||
meshtastic_AdminMessage_OTAEvent ota_request;
|
||||
/* Parameters and sensor configuration */
|
||||
meshtastic_SensorConfig sensor_config;
|
||||
};
|
||||
/* The node generates this key and sends it with any get_x_response packets.
|
||||
The client MUST include the same key with any set_x commands. Key expires after 300 seconds.
|
||||
@@ -323,8 +369,8 @@ extern "C" {
|
||||
#define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG+1))
|
||||
|
||||
#define _meshtastic_AdminMessage_ModuleConfigType_MIN meshtastic_AdminMessage_ModuleConfigType_MQTT_CONFIG
|
||||
#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG
|
||||
#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG+1))
|
||||
#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG
|
||||
#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG+1))
|
||||
|
||||
#define _meshtastic_AdminMessage_BackupLocation_MIN meshtastic_AdminMessage_BackupLocation_FLASH
|
||||
#define _meshtastic_AdminMessage_BackupLocation_MAX meshtastic_AdminMessage_BackupLocation_SD
|
||||
@@ -349,6 +395,9 @@ extern "C" {
|
||||
#define meshtastic_KeyVerificationAdmin_message_type_ENUMTYPE meshtastic_KeyVerificationAdmin_MessageType
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* Initializer values for message structs */
|
||||
#define meshtastic_AdminMessage_init_default {0, {0}, {0, {0}}}
|
||||
#define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0}
|
||||
@@ -357,6 +406,9 @@ extern "C" {
|
||||
#define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
|
||||
#define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0}
|
||||
#define meshtastic_KeyVerificationAdmin_init_default {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
|
||||
#define meshtastic_SensorConfig_init_default {false, meshtastic_SCD4X_config_init_default, false, meshtastic_SEN5X_config_init_default}
|
||||
#define meshtastic_SCD4X_config_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_SEN5X_config_init_default {false, 0, false, 0}
|
||||
#define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}}
|
||||
#define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0}
|
||||
#define meshtastic_AdminMessage_OTAEvent_init_zero {_meshtastic_OTAMode_MIN, {0, {0}}}
|
||||
@@ -364,6 +416,9 @@ extern "C" {
|
||||
#define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
|
||||
#define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0}
|
||||
#define meshtastic_KeyVerificationAdmin_init_zero {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
|
||||
#define meshtastic_SensorConfig_init_zero {false, meshtastic_SCD4X_config_init_zero, false, meshtastic_SEN5X_config_init_zero}
|
||||
#define meshtastic_SCD4X_config_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_SEN5X_config_init_zero {false, 0, false, 0}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
#define meshtastic_AdminMessage_InputEvent_event_code_tag 1
|
||||
@@ -385,6 +440,17 @@ extern "C" {
|
||||
#define meshtastic_KeyVerificationAdmin_remote_nodenum_tag 2
|
||||
#define meshtastic_KeyVerificationAdmin_nonce_tag 3
|
||||
#define meshtastic_KeyVerificationAdmin_security_number_tag 4
|
||||
#define meshtastic_SCD4X_config_set_asc_tag 1
|
||||
#define meshtastic_SCD4X_config_set_target_co2_conc_tag 2
|
||||
#define meshtastic_SCD4X_config_set_temperature_tag 3
|
||||
#define meshtastic_SCD4X_config_set_altitude_tag 4
|
||||
#define meshtastic_SCD4X_config_set_ambient_pressure_tag 5
|
||||
#define meshtastic_SCD4X_config_factory_reset_tag 6
|
||||
#define meshtastic_SCD4X_config_set_power_mode_tag 7
|
||||
#define meshtastic_SEN5X_config_set_temperature_tag 1
|
||||
#define meshtastic_SEN5X_config_set_one_shot_mode_tag 2
|
||||
#define meshtastic_SensorConfig_scd4x_config_tag 1
|
||||
#define meshtastic_SensorConfig_sen5x_config_tag 2
|
||||
#define meshtastic_AdminMessage_get_channel_request_tag 1
|
||||
#define meshtastic_AdminMessage_get_channel_response_tag 2
|
||||
#define meshtastic_AdminMessage_get_owner_request_tag 3
|
||||
@@ -441,6 +507,7 @@ extern "C" {
|
||||
#define meshtastic_AdminMessage_factory_reset_config_tag 99
|
||||
#define meshtastic_AdminMessage_nodedb_reset_tag 100
|
||||
#define meshtastic_AdminMessage_ota_request_tag 102
|
||||
#define meshtastic_AdminMessage_sensor_config_tag 103
|
||||
#define meshtastic_AdminMessage_session_passkey_tag 101
|
||||
|
||||
/* Struct field encoding specification for nanopb */
|
||||
@@ -501,7 +568,8 @@ X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_se
|
||||
X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \
|
||||
X(a, STATIC, ONEOF, BOOL, (payload_variant,nodedb_reset,nodedb_reset), 100) \
|
||||
X(a, STATIC, SINGULAR, BYTES, session_passkey, 101) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 102)
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 102) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,sensor_config,sensor_config), 103)
|
||||
#define meshtastic_AdminMessage_CALLBACK NULL
|
||||
#define meshtastic_AdminMessage_DEFAULT NULL
|
||||
#define meshtastic_AdminMessage_payload_variant_get_channel_response_MSGTYPE meshtastic_Channel
|
||||
@@ -523,6 +591,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 10
|
||||
#define meshtastic_AdminMessage_payload_variant_add_contact_MSGTYPE meshtastic_SharedContact
|
||||
#define meshtastic_AdminMessage_payload_variant_key_verification_MSGTYPE meshtastic_KeyVerificationAdmin
|
||||
#define meshtastic_AdminMessage_payload_variant_ota_request_MSGTYPE meshtastic_AdminMessage_OTAEvent
|
||||
#define meshtastic_AdminMessage_payload_variant_sensor_config_MSGTYPE meshtastic_SensorConfig
|
||||
|
||||
#define meshtastic_AdminMessage_InputEvent_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, UINT32, event_code, 1) \
|
||||
@@ -569,6 +638,31 @@ X(a, STATIC, OPTIONAL, UINT32, security_number, 4)
|
||||
#define meshtastic_KeyVerificationAdmin_CALLBACK NULL
|
||||
#define meshtastic_KeyVerificationAdmin_DEFAULT NULL
|
||||
|
||||
#define meshtastic_SensorConfig_FIELDLIST(X, a) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, scd4x_config, 1) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, sen5x_config, 2)
|
||||
#define meshtastic_SensorConfig_CALLBACK NULL
|
||||
#define meshtastic_SensorConfig_DEFAULT NULL
|
||||
#define meshtastic_SensorConfig_scd4x_config_MSGTYPE meshtastic_SCD4X_config
|
||||
#define meshtastic_SensorConfig_sen5x_config_MSGTYPE meshtastic_SEN5X_config
|
||||
|
||||
#define meshtastic_SCD4X_config_FIELDLIST(X, a) \
|
||||
X(a, STATIC, OPTIONAL, BOOL, set_asc, 1) \
|
||||
X(a, STATIC, OPTIONAL, UINT32, set_target_co2_conc, 2) \
|
||||
X(a, STATIC, OPTIONAL, FLOAT, set_temperature, 3) \
|
||||
X(a, STATIC, OPTIONAL, UINT32, set_altitude, 4) \
|
||||
X(a, STATIC, OPTIONAL, UINT32, set_ambient_pressure, 5) \
|
||||
X(a, STATIC, OPTIONAL, BOOL, factory_reset, 6) \
|
||||
X(a, STATIC, OPTIONAL, BOOL, set_power_mode, 7)
|
||||
#define meshtastic_SCD4X_config_CALLBACK NULL
|
||||
#define meshtastic_SCD4X_config_DEFAULT NULL
|
||||
|
||||
#define meshtastic_SEN5X_config_FIELDLIST(X, a) \
|
||||
X(a, STATIC, OPTIONAL, FLOAT, set_temperature, 1) \
|
||||
X(a, STATIC, OPTIONAL, BOOL, set_one_shot_mode, 2)
|
||||
#define meshtastic_SEN5X_config_CALLBACK NULL
|
||||
#define meshtastic_SEN5X_config_DEFAULT NULL
|
||||
|
||||
extern const pb_msgdesc_t meshtastic_AdminMessage_msg;
|
||||
extern const pb_msgdesc_t meshtastic_AdminMessage_InputEvent_msg;
|
||||
extern const pb_msgdesc_t meshtastic_AdminMessage_OTAEvent_msg;
|
||||
@@ -576,6 +670,9 @@ extern const pb_msgdesc_t meshtastic_HamParameters_msg;
|
||||
extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePinsResponse_msg;
|
||||
extern const pb_msgdesc_t meshtastic_SharedContact_msg;
|
||||
extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg;
|
||||
extern const pb_msgdesc_t meshtastic_SensorConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_SCD4X_config_msg;
|
||||
extern const pb_msgdesc_t meshtastic_SEN5X_config_msg;
|
||||
|
||||
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
|
||||
#define meshtastic_AdminMessage_fields &meshtastic_AdminMessage_msg
|
||||
@@ -585,6 +682,9 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg;
|
||||
#define meshtastic_NodeRemoteHardwarePinsResponse_fields &meshtastic_NodeRemoteHardwarePinsResponse_msg
|
||||
#define meshtastic_SharedContact_fields &meshtastic_SharedContact_msg
|
||||
#define meshtastic_KeyVerificationAdmin_fields &meshtastic_KeyVerificationAdmin_msg
|
||||
#define meshtastic_SensorConfig_fields &meshtastic_SensorConfig_msg
|
||||
#define meshtastic_SCD4X_config_fields &meshtastic_SCD4X_config_msg
|
||||
#define meshtastic_SEN5X_config_fields &meshtastic_SEN5X_config_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define MESHTASTIC_MESHTASTIC_ADMIN_PB_H_MAX_SIZE meshtastic_AdminMessage_size
|
||||
@@ -594,6 +694,9 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg;
|
||||
#define meshtastic_HamParameters_size 31
|
||||
#define meshtastic_KeyVerificationAdmin_size 25
|
||||
#define meshtastic_NodeRemoteHardwarePinsResponse_size 496
|
||||
#define meshtastic_SCD4X_config_size 29
|
||||
#define meshtastic_SEN5X_config_size 7
|
||||
#define meshtastic_SensorConfig_size 40
|
||||
#define meshtastic_SharedContact_size 127
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -361,7 +361,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
/* meshtastic_NodeDatabase_size depends on runtime parameters */
|
||||
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size
|
||||
#define meshtastic_BackupPreferences_size 2279
|
||||
#define meshtastic_BackupPreferences_size 2362
|
||||
#define meshtastic_ChannelFile_size 718
|
||||
#define meshtastic_DeviceState_size 1737
|
||||
#define meshtastic_NodeInfoLite_size 196
|
||||
|
||||
@@ -87,6 +87,9 @@ typedef struct _meshtastic_LocalModuleConfig {
|
||||
/* Paxcounter Config */
|
||||
bool has_paxcounter;
|
||||
meshtastic_ModuleConfig_PaxcounterConfig paxcounter;
|
||||
/* StatusMessage Config */
|
||||
bool has_statusmessage;
|
||||
meshtastic_ModuleConfig_StatusMessageConfig statusmessage;
|
||||
} meshtastic_LocalModuleConfig;
|
||||
|
||||
|
||||
@@ -96,9 +99,9 @@ extern "C" {
|
||||
|
||||
/* Initializer values for message structs */
|
||||
#define meshtastic_LocalConfig_init_default {false, meshtastic_Config_DeviceConfig_init_default, false, meshtastic_Config_PositionConfig_init_default, false, meshtastic_Config_PowerConfig_init_default, false, meshtastic_Config_NetworkConfig_init_default, false, meshtastic_Config_DisplayConfig_init_default, false, meshtastic_Config_LoRaConfig_init_default, false, meshtastic_Config_BluetoothConfig_init_default, 0, false, meshtastic_Config_SecurityConfig_init_default}
|
||||
#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default}
|
||||
#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default}
|
||||
#define meshtastic_LocalConfig_init_zero {false, meshtastic_Config_DeviceConfig_init_zero, false, meshtastic_Config_PositionConfig_init_zero, false, meshtastic_Config_PowerConfig_init_zero, false, meshtastic_Config_NetworkConfig_init_zero, false, meshtastic_Config_DisplayConfig_init_zero, false, meshtastic_Config_LoRaConfig_init_zero, false, meshtastic_Config_BluetoothConfig_init_zero, 0, false, meshtastic_Config_SecurityConfig_init_zero}
|
||||
#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero}
|
||||
#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
#define meshtastic_LocalConfig_device_tag 1
|
||||
@@ -124,6 +127,7 @@ extern "C" {
|
||||
#define meshtastic_LocalModuleConfig_ambient_lighting_tag 12
|
||||
#define meshtastic_LocalModuleConfig_detection_sensor_tag 13
|
||||
#define meshtastic_LocalModuleConfig_paxcounter_tag 14
|
||||
#define meshtastic_LocalModuleConfig_statusmessage_tag 15
|
||||
|
||||
/* Struct field encoding specification for nanopb */
|
||||
#define meshtastic_LocalConfig_FIELDLIST(X, a) \
|
||||
@@ -161,7 +165,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, remote_hardware, 10) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, neighbor_info, 11) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, ambient_lighting, 12) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, detection_sensor, 13) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14)
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, statusmessage, 15)
|
||||
#define meshtastic_LocalModuleConfig_CALLBACK NULL
|
||||
#define meshtastic_LocalModuleConfig_DEFAULT NULL
|
||||
#define meshtastic_LocalModuleConfig_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig
|
||||
@@ -177,6 +182,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14)
|
||||
#define meshtastic_LocalModuleConfig_ambient_lighting_MSGTYPE meshtastic_ModuleConfig_AmbientLightingConfig
|
||||
#define meshtastic_LocalModuleConfig_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig
|
||||
#define meshtastic_LocalModuleConfig_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig
|
||||
#define meshtastic_LocalModuleConfig_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig
|
||||
|
||||
extern const pb_msgdesc_t meshtastic_LocalConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
|
||||
@@ -186,9 +192,9 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
|
||||
#define meshtastic_LocalModuleConfig_fields &meshtastic_LocalModuleConfig_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size
|
||||
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size
|
||||
#define meshtastic_LocalConfig_size 749
|
||||
#define meshtastic_LocalModuleConfig_size 675
|
||||
#define meshtastic_LocalModuleConfig_size 758
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -30,6 +30,9 @@ PB_BIND(meshtastic_StoreForwardPlusPlus, meshtastic_StoreForwardPlusPlus, 2)
|
||||
PB_BIND(meshtastic_Waypoint, meshtastic_Waypoint, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_StatusMessage, meshtastic_StatusMessage, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_MqttClientProxyMessage, meshtastic_MqttClientProxyMessage, 2)
|
||||
|
||||
|
||||
|
||||
@@ -858,6 +858,11 @@ typedef struct _meshtastic_Waypoint {
|
||||
uint32_t icon;
|
||||
} meshtastic_Waypoint;
|
||||
|
||||
/* Message for node status */
|
||||
typedef struct _meshtastic_StatusMessage {
|
||||
char status[80];
|
||||
} meshtastic_StatusMessage;
|
||||
|
||||
typedef PB_BYTES_ARRAY_T(435) meshtastic_MqttClientProxyMessage_data_t;
|
||||
/* This message will be proxied over the PhoneAPI for the client to deliver to the MQTT server */
|
||||
typedef struct _meshtastic_MqttClientProxyMessage {
|
||||
@@ -1402,6 +1407,7 @@ extern "C" {
|
||||
|
||||
|
||||
|
||||
|
||||
#define meshtastic_MeshPacket_priority_ENUMTYPE meshtastic_MeshPacket_Priority
|
||||
#define meshtastic_MeshPacket_delayed_ENUMTYPE meshtastic_MeshPacket_Delayed
|
||||
#define meshtastic_MeshPacket_transport_mechanism_ENUMTYPE meshtastic_MeshPacket_TransportMechanism
|
||||
@@ -1444,6 +1450,7 @@ extern "C" {
|
||||
#define meshtastic_KeyVerification_init_default {0, {0, {0}}, {0, {0}}}
|
||||
#define meshtastic_StoreForwardPlusPlus_init_default {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0}
|
||||
#define meshtastic_StatusMessage_init_default {""}
|
||||
#define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0}
|
||||
#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN}
|
||||
#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0}
|
||||
@@ -1476,6 +1483,7 @@ extern "C" {
|
||||
#define meshtastic_KeyVerification_init_zero {0, {0, {0}}, {0, {0}}}
|
||||
#define meshtastic_StoreForwardPlusPlus_init_zero {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0}
|
||||
#define meshtastic_StatusMessage_init_zero {""}
|
||||
#define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0}
|
||||
#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN}
|
||||
#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0}
|
||||
@@ -1571,6 +1579,7 @@ extern "C" {
|
||||
#define meshtastic_Waypoint_name_tag 6
|
||||
#define meshtastic_Waypoint_description_tag 7
|
||||
#define meshtastic_Waypoint_icon_tag 8
|
||||
#define meshtastic_StatusMessage_status_tag 1
|
||||
#define meshtastic_MqttClientProxyMessage_topic_tag 1
|
||||
#define meshtastic_MqttClientProxyMessage_data_tag 2
|
||||
#define meshtastic_MqttClientProxyMessage_text_tag 3
|
||||
@@ -1806,6 +1815,11 @@ X(a, STATIC, SINGULAR, FIXED32, icon, 8)
|
||||
#define meshtastic_Waypoint_CALLBACK NULL
|
||||
#define meshtastic_Waypoint_DEFAULT NULL
|
||||
|
||||
#define meshtastic_StatusMessage_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, STRING, status, 1)
|
||||
#define meshtastic_StatusMessage_CALLBACK NULL
|
||||
#define meshtastic_StatusMessage_DEFAULT NULL
|
||||
|
||||
#define meshtastic_MqttClientProxyMessage_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, STRING, topic, 1) \
|
||||
X(a, STATIC, ONEOF, BYTES, (payload_variant,data,payload_variant.data), 2) \
|
||||
@@ -2072,6 +2086,7 @@ extern const pb_msgdesc_t meshtastic_Data_msg;
|
||||
extern const pb_msgdesc_t meshtastic_KeyVerification_msg;
|
||||
extern const pb_msgdesc_t meshtastic_StoreForwardPlusPlus_msg;
|
||||
extern const pb_msgdesc_t meshtastic_Waypoint_msg;
|
||||
extern const pb_msgdesc_t meshtastic_StatusMessage_msg;
|
||||
extern const pb_msgdesc_t meshtastic_MqttClientProxyMessage_msg;
|
||||
extern const pb_msgdesc_t meshtastic_MeshPacket_msg;
|
||||
extern const pb_msgdesc_t meshtastic_NodeInfo_msg;
|
||||
@@ -2106,6 +2121,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
|
||||
#define meshtastic_KeyVerification_fields &meshtastic_KeyVerification_msg
|
||||
#define meshtastic_StoreForwardPlusPlus_fields &meshtastic_StoreForwardPlusPlus_msg
|
||||
#define meshtastic_Waypoint_fields &meshtastic_Waypoint_msg
|
||||
#define meshtastic_StatusMessage_fields &meshtastic_StatusMessage_msg
|
||||
#define meshtastic_MqttClientProxyMessage_fields &meshtastic_MqttClientProxyMessage_msg
|
||||
#define meshtastic_MeshPacket_fields &meshtastic_MeshPacket_msg
|
||||
#define meshtastic_NodeInfo_fields &meshtastic_NodeInfo_msg
|
||||
@@ -2161,6 +2177,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
|
||||
#define meshtastic_QueueStatus_size 23
|
||||
#define meshtastic_RouteDiscovery_size 256
|
||||
#define meshtastic_Routing_size 259
|
||||
#define meshtastic_StatusMessage_size 81
|
||||
#define meshtastic_StoreForwardPlusPlus_size 377
|
||||
#define meshtastic_ToRadio_size 504
|
||||
#define meshtastic_User_size 115
|
||||
|
||||
@@ -51,6 +51,9 @@ PB_BIND(meshtastic_ModuleConfig_CannedMessageConfig, meshtastic_ModuleConfig_Can
|
||||
PB_BIND(meshtastic_ModuleConfig_AmbientLightingConfig, meshtastic_ModuleConfig_AmbientLightingConfig, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_ModuleConfig_StatusMessageConfig, meshtastic_ModuleConfig_StatusMessageConfig, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_RemoteHardwarePin, meshtastic_RemoteHardwarePin, AUTO)
|
||||
|
||||
|
||||
|
||||
@@ -409,6 +409,12 @@ typedef struct _meshtastic_ModuleConfig_AmbientLightingConfig {
|
||||
uint8_t blue;
|
||||
} meshtastic_ModuleConfig_AmbientLightingConfig;
|
||||
|
||||
/* StatusMessage config - Allows setting a status message for a node to periodically rebroadcast */
|
||||
typedef struct _meshtastic_ModuleConfig_StatusMessageConfig {
|
||||
/* The actual status string */
|
||||
char node_status[80];
|
||||
} meshtastic_ModuleConfig_StatusMessageConfig;
|
||||
|
||||
/* A GPIO pin definition for remote hardware module */
|
||||
typedef struct _meshtastic_RemoteHardwarePin {
|
||||
/* GPIO Pin number (must match Arduino) */
|
||||
@@ -460,6 +466,8 @@ typedef struct _meshtastic_ModuleConfig {
|
||||
meshtastic_ModuleConfig_DetectionSensorConfig detection_sensor;
|
||||
/* TODO: REPLACE */
|
||||
meshtastic_ModuleConfig_PaxcounterConfig paxcounter;
|
||||
/* TODO: REPLACE */
|
||||
meshtastic_ModuleConfig_StatusMessageConfig statusmessage;
|
||||
} payload_variant;
|
||||
} meshtastic_ModuleConfig;
|
||||
|
||||
@@ -515,6 +523,7 @@ extern "C" {
|
||||
#define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_event_press_ENUMTYPE meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar
|
||||
|
||||
|
||||
|
||||
#define meshtastic_RemoteHardwarePin_type_ENUMTYPE meshtastic_RemoteHardwarePinType
|
||||
|
||||
|
||||
@@ -534,6 +543,7 @@ extern "C" {
|
||||
#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0}
|
||||
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_StatusMessageConfig_init_default {""}
|
||||
#define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN}
|
||||
#define meshtastic_ModuleConfig_init_zero {0, {meshtastic_ModuleConfig_MQTTConfig_init_zero}}
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0, 0, "", 0, 0, false, meshtastic_ModuleConfig_MapReportSettings_init_zero}
|
||||
@@ -550,6 +560,7 @@ extern "C" {
|
||||
#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0}
|
||||
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_zero {0, 0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_StatusMessageConfig_init_zero {""}
|
||||
#define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
@@ -653,6 +664,7 @@ extern "C" {
|
||||
#define meshtastic_ModuleConfig_AmbientLightingConfig_red_tag 3
|
||||
#define meshtastic_ModuleConfig_AmbientLightingConfig_green_tag 4
|
||||
#define meshtastic_ModuleConfig_AmbientLightingConfig_blue_tag 5
|
||||
#define meshtastic_ModuleConfig_StatusMessageConfig_node_status_tag 1
|
||||
#define meshtastic_RemoteHardwarePin_gpio_pin_tag 1
|
||||
#define meshtastic_RemoteHardwarePin_name_tag 2
|
||||
#define meshtastic_RemoteHardwarePin_type_tag 3
|
||||
@@ -672,6 +684,7 @@ extern "C" {
|
||||
#define meshtastic_ModuleConfig_ambient_lighting_tag 11
|
||||
#define meshtastic_ModuleConfig_detection_sensor_tag 12
|
||||
#define meshtastic_ModuleConfig_paxcounter_tag 13
|
||||
#define meshtastic_ModuleConfig_statusmessage_tag 14
|
||||
|
||||
/* Struct field encoding specification for nanopb */
|
||||
#define meshtastic_ModuleConfig_FIELDLIST(X, a) \
|
||||
@@ -687,7 +700,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,remote_hardware,payload_vari
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,neighbor_info,payload_variant.neighbor_info), 10) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ambient_lighting,payload_variant.ambient_lighting), 11) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,detection_sensor,payload_variant.detection_sensor), 12) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.paxcounter), 13)
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.paxcounter), 13) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,statusmessage,payload_variant.statusmessage), 14)
|
||||
#define meshtastic_ModuleConfig_CALLBACK NULL
|
||||
#define meshtastic_ModuleConfig_DEFAULT NULL
|
||||
#define meshtastic_ModuleConfig_payload_variant_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig
|
||||
@@ -703,6 +717,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.p
|
||||
#define meshtastic_ModuleConfig_payload_variant_ambient_lighting_MSGTYPE meshtastic_ModuleConfig_AmbientLightingConfig
|
||||
#define meshtastic_ModuleConfig_payload_variant_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig
|
||||
#define meshtastic_ModuleConfig_payload_variant_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig
|
||||
#define meshtastic_ModuleConfig_payload_variant_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig
|
||||
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, BOOL, enabled, 1) \
|
||||
@@ -865,6 +880,11 @@ X(a, STATIC, SINGULAR, UINT32, blue, 5)
|
||||
#define meshtastic_ModuleConfig_AmbientLightingConfig_CALLBACK NULL
|
||||
#define meshtastic_ModuleConfig_AmbientLightingConfig_DEFAULT NULL
|
||||
|
||||
#define meshtastic_ModuleConfig_StatusMessageConfig_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, STRING, node_status, 1)
|
||||
#define meshtastic_ModuleConfig_StatusMessageConfig_CALLBACK NULL
|
||||
#define meshtastic_ModuleConfig_StatusMessageConfig_DEFAULT NULL
|
||||
|
||||
#define meshtastic_RemoteHardwarePin_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, UINT32, gpio_pin, 1) \
|
||||
X(a, STATIC, SINGULAR, STRING, name, 2) \
|
||||
@@ -887,6 +907,7 @@ extern const pb_msgdesc_t meshtastic_ModuleConfig_RangeTestConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_TelemetryConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_CannedMessageConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_AmbientLightingConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_StatusMessageConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
|
||||
|
||||
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
|
||||
@@ -905,6 +926,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
|
||||
#define meshtastic_ModuleConfig_TelemetryConfig_fields &meshtastic_ModuleConfig_TelemetryConfig_msg
|
||||
#define meshtastic_ModuleConfig_CannedMessageConfig_fields &meshtastic_ModuleConfig_CannedMessageConfig_msg
|
||||
#define meshtastic_ModuleConfig_AmbientLightingConfig_fields &meshtastic_ModuleConfig_AmbientLightingConfig_msg
|
||||
#define meshtastic_ModuleConfig_StatusMessageConfig_fields &meshtastic_ModuleConfig_StatusMessageConfig_msg
|
||||
#define meshtastic_RemoteHardwarePin_fields &meshtastic_RemoteHardwarePin_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
@@ -921,6 +943,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
|
||||
#define meshtastic_ModuleConfig_RangeTestConfig_size 12
|
||||
#define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96
|
||||
#define meshtastic_ModuleConfig_SerialConfig_size 28
|
||||
#define meshtastic_ModuleConfig_StatusMessageConfig_size 81
|
||||
#define meshtastic_ModuleConfig_StoreForwardConfig_size 24
|
||||
#define meshtastic_ModuleConfig_TelemetryConfig_size 50
|
||||
#define meshtastic_ModuleConfig_size 227
|
||||
|
||||
@@ -91,6 +91,11 @@ typedef enum _meshtastic_PortNum {
|
||||
This module is specifically for Native Linux nodes, and provides a Git-style
|
||||
chain of messages. */
|
||||
meshtastic_PortNum_STORE_FORWARD_PLUSPLUS_APP = 35,
|
||||
/* Node Status module
|
||||
ENCODING: protobuf
|
||||
This module allows setting an extra string of status for a node.
|
||||
Broadcasts on change and on a timer, possibly once a day. */
|
||||
meshtastic_PortNum_NODE_STATUS_APP = 36,
|
||||
/* Provides a hardware serial interface to send and receive from the Meshtastic network.
|
||||
Connect to the RX/TX pins of a device with 38400 8N1. Packets received from the Meshtastic
|
||||
network is forwarded to the RX pin while sending a packet to TX will go out to the Mesh network.
|
||||
|
||||
@@ -33,6 +33,9 @@ PB_BIND(meshtastic_Telemetry, meshtastic_Telemetry, 2)
|
||||
PB_BIND(meshtastic_Nau7802Config, meshtastic_Nau7802Config, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_SEN5XState, meshtastic_SEN5XState, AUTO)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -361,6 +361,8 @@ typedef struct _meshtastic_LocalStats {
|
||||
uint32_t heap_free_bytes;
|
||||
/* Number of packets that were dropped because the transmit queue was full. */
|
||||
uint16_t num_tx_dropped;
|
||||
/* Noise floor value measured in dBm */
|
||||
int32_t noise_floor;
|
||||
} meshtastic_LocalStats;
|
||||
|
||||
/* Health telemetry metrics */
|
||||
@@ -433,6 +435,25 @@ typedef struct _meshtastic_Nau7802Config {
|
||||
float calibrationFactor;
|
||||
} meshtastic_Nau7802Config;
|
||||
|
||||
/* SEN5X State, for saving to flash */
|
||||
typedef struct _meshtastic_SEN5XState {
|
||||
/* Last cleaning time for SEN5X */
|
||||
uint32_t last_cleaning_time;
|
||||
/* Last cleaning time for SEN5X - valid flag */
|
||||
bool last_cleaning_valid;
|
||||
/* Config flag for one-shot mode (see admin.proto) */
|
||||
bool one_shot_mode;
|
||||
/* Last VOC state time for SEN55 */
|
||||
bool has_voc_state_time;
|
||||
uint32_t voc_state_time;
|
||||
/* Last VOC state validity flag for SEN55 */
|
||||
bool has_voc_state_valid;
|
||||
bool voc_state_valid;
|
||||
/* VOC state array (8x uint8t) for SEN55 */
|
||||
bool has_voc_state_array;
|
||||
uint64_t voc_state_array;
|
||||
} meshtastic_SEN5XState;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -453,25 +474,28 @@ extern "C" {
|
||||
|
||||
|
||||
|
||||
|
||||
/* Initializer values for message structs */
|
||||
#define meshtastic_DeviceMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_HealthMetrics_init_default {false, 0, false, 0, false, 0}
|
||||
#define meshtastic_HostMetrics_init_default {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
|
||||
#define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}}
|
||||
#define meshtastic_Nau7802Config_init_default {0, 0}
|
||||
#define meshtastic_SEN5XState_init_default {0, 0, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_DeviceMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_HealthMetrics_init_zero {false, 0, false, 0, false, 0}
|
||||
#define meshtastic_HostMetrics_init_zero {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
|
||||
#define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}}
|
||||
#define meshtastic_Nau7802Config_init_zero {0, 0}
|
||||
#define meshtastic_SEN5XState_init_zero {0, 0, 0, false, 0, false, 0, false, 0}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
#define meshtastic_DeviceMetrics_battery_level_tag 1
|
||||
@@ -556,6 +580,7 @@ extern "C" {
|
||||
#define meshtastic_LocalStats_heap_total_bytes_tag 12
|
||||
#define meshtastic_LocalStats_heap_free_bytes_tag 13
|
||||
#define meshtastic_LocalStats_num_tx_dropped_tag 14
|
||||
#define meshtastic_LocalStats_noise_floor_tag 15
|
||||
#define meshtastic_HealthMetrics_heart_bpm_tag 1
|
||||
#define meshtastic_HealthMetrics_spO2_tag 2
|
||||
#define meshtastic_HealthMetrics_temperature_tag 3
|
||||
@@ -578,6 +603,12 @@ extern "C" {
|
||||
#define meshtastic_Telemetry_host_metrics_tag 8
|
||||
#define meshtastic_Nau7802Config_zeroOffset_tag 1
|
||||
#define meshtastic_Nau7802Config_calibrationFactor_tag 2
|
||||
#define meshtastic_SEN5XState_last_cleaning_time_tag 1
|
||||
#define meshtastic_SEN5XState_last_cleaning_valid_tag 2
|
||||
#define meshtastic_SEN5XState_one_shot_mode_tag 3
|
||||
#define meshtastic_SEN5XState_voc_state_time_tag 4
|
||||
#define meshtastic_SEN5XState_voc_state_valid_tag 5
|
||||
#define meshtastic_SEN5XState_voc_state_array_tag 6
|
||||
|
||||
/* Struct field encoding specification for nanopb */
|
||||
#define meshtastic_DeviceMetrics_FIELDLIST(X, a) \
|
||||
@@ -678,7 +709,8 @@ X(a, STATIC, SINGULAR, UINT32, num_tx_relay, 10) \
|
||||
X(a, STATIC, SINGULAR, UINT32, num_tx_relay_canceled, 11) \
|
||||
X(a, STATIC, SINGULAR, UINT32, heap_total_bytes, 12) \
|
||||
X(a, STATIC, SINGULAR, UINT32, heap_free_bytes, 13) \
|
||||
X(a, STATIC, SINGULAR, UINT32, num_tx_dropped, 14)
|
||||
X(a, STATIC, SINGULAR, UINT32, num_tx_dropped, 14) \
|
||||
X(a, STATIC, SINGULAR, INT32, noise_floor, 15)
|
||||
#define meshtastic_LocalStats_CALLBACK NULL
|
||||
#define meshtastic_LocalStats_DEFAULT NULL
|
||||
|
||||
@@ -727,6 +759,16 @@ X(a, STATIC, SINGULAR, FLOAT, calibrationFactor, 2)
|
||||
#define meshtastic_Nau7802Config_CALLBACK NULL
|
||||
#define meshtastic_Nau7802Config_DEFAULT NULL
|
||||
|
||||
#define meshtastic_SEN5XState_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, UINT32, last_cleaning_time, 1) \
|
||||
X(a, STATIC, SINGULAR, BOOL, last_cleaning_valid, 2) \
|
||||
X(a, STATIC, SINGULAR, BOOL, one_shot_mode, 3) \
|
||||
X(a, STATIC, OPTIONAL, UINT32, voc_state_time, 4) \
|
||||
X(a, STATIC, OPTIONAL, BOOL, voc_state_valid, 5) \
|
||||
X(a, STATIC, OPTIONAL, FIXED64, voc_state_array, 6)
|
||||
#define meshtastic_SEN5XState_CALLBACK NULL
|
||||
#define meshtastic_SEN5XState_DEFAULT NULL
|
||||
|
||||
extern const pb_msgdesc_t meshtastic_DeviceMetrics_msg;
|
||||
extern const pb_msgdesc_t meshtastic_EnvironmentMetrics_msg;
|
||||
extern const pb_msgdesc_t meshtastic_PowerMetrics_msg;
|
||||
@@ -736,6 +778,7 @@ extern const pb_msgdesc_t meshtastic_HealthMetrics_msg;
|
||||
extern const pb_msgdesc_t meshtastic_HostMetrics_msg;
|
||||
extern const pb_msgdesc_t meshtastic_Telemetry_msg;
|
||||
extern const pb_msgdesc_t meshtastic_Nau7802Config_msg;
|
||||
extern const pb_msgdesc_t meshtastic_SEN5XState_msg;
|
||||
|
||||
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
|
||||
#define meshtastic_DeviceMetrics_fields &meshtastic_DeviceMetrics_msg
|
||||
@@ -747,6 +790,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg;
|
||||
#define meshtastic_HostMetrics_fields &meshtastic_HostMetrics_msg
|
||||
#define meshtastic_Telemetry_fields &meshtastic_Telemetry_msg
|
||||
#define meshtastic_Nau7802Config_fields &meshtastic_Nau7802Config_msg
|
||||
#define meshtastic_SEN5XState_fields &meshtastic_SEN5XState_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size
|
||||
@@ -755,9 +799,10 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg;
|
||||
#define meshtastic_EnvironmentMetrics_size 113
|
||||
#define meshtastic_HealthMetrics_size 11
|
||||
#define meshtastic_HostMetrics_size 264
|
||||
#define meshtastic_LocalStats_size 76
|
||||
#define meshtastic_LocalStats_size 87
|
||||
#define meshtastic_Nau7802Config_size 16
|
||||
#define meshtastic_PowerMetrics_size 81
|
||||
#define meshtastic_SEN5XState_size 27
|
||||
#define meshtastic_Telemetry_size 272
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -173,7 +173,7 @@ void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res)
|
||||
|
||||
if (req->getMethod() == "OPTIONS") {
|
||||
res->setStatusCode(204); // Success with no content
|
||||
// res->print(""); @todo remove
|
||||
res->print("");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res)
|
||||
|
||||
if (req->getMethod() == "OPTIONS") {
|
||||
res->setStatusCode(204); // Success with no content
|
||||
// res->print(""); @todo remove
|
||||
res->print("");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -384,13 +384,13 @@ static void WiFiEvent(WiFiEvent_t event)
|
||||
#endif
|
||||
}
|
||||
#ifdef WIFI_LED
|
||||
digitalWrite(WIFI_LED, LOW ^ WIFI_STATE_ON);
|
||||
digitalWrite(WIFI_LED, HIGH);
|
||||
#endif
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||
LOG_INFO("Disconnected from WiFi access point");
|
||||
#ifdef WIFI_LED
|
||||
digitalWrite(WIFI_LED, HIGH ^ WIFI_STATE_ON);
|
||||
digitalWrite(WIFI_LED, LOW);
|
||||
#endif
|
||||
if (!isReconnecting) {
|
||||
WiFi.disconnect(false, true);
|
||||
@@ -442,13 +442,13 @@ static void WiFiEvent(WiFiEvent_t event)
|
||||
case ARDUINO_EVENT_WIFI_AP_START:
|
||||
LOG_INFO("WiFi access point started");
|
||||
#ifdef WIFI_LED
|
||||
digitalWrite(WIFI_LED, LOW ^ WIFI_STATE_ON);
|
||||
digitalWrite(WIFI_LED, HIGH);
|
||||
#endif
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_AP_STOP:
|
||||
LOG_INFO("WiFi access point stopped");
|
||||
#ifdef WIFI_LED
|
||||
digitalWrite(WIFI_LED, HIGH ^ WIFI_STATE_ON);
|
||||
digitalWrite(WIFI_LED, LOW);
|
||||
#endif
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_AP_STACONNECTED:
|
||||
|
||||
@@ -235,22 +235,46 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
}
|
||||
case meshtastic_AdminMessage_ota_request_tag: {
|
||||
#if defined(ARCH_ESP32)
|
||||
LOG_INFO("OTA Requested");
|
||||
|
||||
if (r->ota_request.ota_hash.size != 32) {
|
||||
suppressRebootBanner = true;
|
||||
LOG_INFO("OTA Failed: Invalid `ota_hash` provided");
|
||||
sendWarningAndLog("Cannot start OTA: Invalid `ota_hash` provided.");
|
||||
break;
|
||||
}
|
||||
|
||||
meshtastic_OTAMode mode = r->ota_request.reboot_ota_mode;
|
||||
const char *mode_name = (mode == METHOD_OTA_BLE ? "BLE" : "WiFi");
|
||||
|
||||
// Check that we have an OTA partition
|
||||
const esp_partition_t *part = MeshtasticOTA::getAppPartition();
|
||||
if (part == NULL) {
|
||||
suppressRebootBanner = true;
|
||||
sendWarningAndLog("Cannot start OTA: Cannot find OTA Loader partition.");
|
||||
break;
|
||||
}
|
||||
|
||||
static esp_app_desc_t app_desc;
|
||||
if (!MeshtasticOTA::getAppDesc(part, &app_desc)) {
|
||||
suppressRebootBanner = true;
|
||||
sendWarningAndLog("Cannot start OTA: Device does have a valid OTA Loader.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (!MeshtasticOTA::checkOTACapability(&app_desc, mode)) {
|
||||
suppressRebootBanner = true;
|
||||
sendWarningAndLog("OTA Loader does not support %s", mode_name);
|
||||
break;
|
||||
}
|
||||
|
||||
if (MeshtasticOTA::trySwitchToOTA()) {
|
||||
LOG_INFO("OTA Requested");
|
||||
suppressRebootBanner = true;
|
||||
if (screen)
|
||||
screen->startFirmwareUpdateScreen();
|
||||
MeshtasticOTA::saveConfig(&config.network, mode, r->ota_request.ota_hash.bytes);
|
||||
LOG_INFO("Rebooting to WiFi OTA");
|
||||
sendWarningAndLog("Rebooting to %s OTA", mode_name);
|
||||
} else {
|
||||
LOG_INFO("WIFI OTA Failed");
|
||||
sendWarningAndLog("Unable to switch to the OTA partition.");
|
||||
}
|
||||
#endif
|
||||
int s = 1; // Reboot in 1 second, hard coded
|
||||
@@ -1238,7 +1262,7 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAS_ETHERNET && !defined(USE_WS5500) && !defined(USE_CH390D)
|
||||
#if HAS_ETHERNET && !defined(USE_WS5500)
|
||||
conn.has_ethernet = true;
|
||||
conn.ethernet.has_status = true;
|
||||
if (Ethernet.linkStatus() == LinkON) {
|
||||
@@ -1472,15 +1496,43 @@ void AdminModule::handleSendInputEvent(const meshtastic_AdminMessage_InputEvent
|
||||
#endif
|
||||
}
|
||||
|
||||
void AdminModule::sendWarning(const char *message)
|
||||
void AdminModule::sendWarning(const char *format, ...)
|
||||
{
|
||||
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
|
||||
if (!cn)
|
||||
return;
|
||||
|
||||
cn->level = meshtastic_LogRecord_Level_WARNING;
|
||||
cn->time = getValidTime(RTCQualityFromNet);
|
||||
strncpy(cn->message, message, sizeof(cn->message));
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
// Format the arguments directly into the notification object
|
||||
vsnprintf(cn->message, sizeof(cn->message), format, args);
|
||||
va_end(args);
|
||||
|
||||
service->sendClientNotification(cn);
|
||||
}
|
||||
|
||||
void AdminModule::sendWarningAndLog(const char *format, ...)
|
||||
{
|
||||
// We need a temporary buffer to hold the formatted text so we can log it
|
||||
// Using 250 bytes as a safe upper limit for typical text notifications
|
||||
char buf[250];
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsnprintf(buf, sizeof(buf), format, args);
|
||||
va_end(args);
|
||||
|
||||
LOG_WARN(buf);
|
||||
// 2. Call sendWarning
|
||||
// SECURITY NOTE: We pass "%s", buf instead of just 'buf'.
|
||||
// If 'buf' contained a % symbol (e.g. "Battery 50%"), passing it directly
|
||||
// would crash sendWarning. "%s" treats it purely as text.
|
||||
sendWarning("%s", buf);
|
||||
}
|
||||
|
||||
void disableBluetooth()
|
||||
{
|
||||
#if HAS_BLUETOOTH
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include <sys/types.h>
|
||||
|
||||
#pragma once
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <esp_ota_ops.h>
|
||||
#endif
|
||||
#include "ProtobufModule.h"
|
||||
#include <sys/types.h>
|
||||
#if HAS_WIFI
|
||||
#include "mesh/wifi/WiFiAPClient.h"
|
||||
#endif
|
||||
@@ -71,7 +73,8 @@ class AdminModule : public ProtobufModule<meshtastic_AdminMessage>, public Obser
|
||||
|
||||
bool messageIsResponse(const meshtastic_AdminMessage *r);
|
||||
bool messageIsRequest(const meshtastic_AdminMessage *r);
|
||||
void sendWarning(const char *message);
|
||||
void sendWarning(const char *format, ...) __attribute__((format(printf, 2, 3)));
|
||||
void sendWarningAndLog(const char *format, ...) __attribute__((format(printf, 2, 3)));
|
||||
};
|
||||
|
||||
static constexpr const char *licensedModeMessage =
|
||||
|
||||
@@ -130,8 +130,7 @@ CannedMessageModule::CannedMessageModule()
|
||||
: SinglePortModule("canned", meshtastic_PortNum_TEXT_MESSAGE_APP), concurrency::OSThread("CannedMessage")
|
||||
{
|
||||
this->loadProtoForModule();
|
||||
if ((this->splitConfiguredMessages() <= 0) && (cardkb_found.address == 0x00) && !INPUTBROKER_MATRIX_TYPE &&
|
||||
!CANNED_MESSAGE_MODULE_ENABLE) {
|
||||
if ((this->splitConfiguredMessages() <= 0) && (cardkb_found.address == 0x00) && !INPUTBROKER_MATRIX_TYPE) {
|
||||
LOG_INFO("CannedMessageModule: No messages are configured. Module is disabled");
|
||||
this->runState = CANNED_MESSAGE_RUN_STATE_DISABLED;
|
||||
disable();
|
||||
|
||||
@@ -27,10 +27,6 @@ enum CannedMessageModuleIconType { shift, backspace, space, enter };
|
||||
#define CANNED_MESSAGE_MODULE_MESSAGE_MAX_COUNT 50
|
||||
#define CANNED_MESSAGE_MODULE_MESSAGES_SIZE 800
|
||||
|
||||
#ifndef CANNED_MESSAGE_MODULE_ENABLE
|
||||
#define CANNED_MESSAGE_MODULE_ENABLE 0
|
||||
#endif
|
||||
|
||||
// ============================
|
||||
// Data Structures
|
||||
// ============================
|
||||
|
||||
@@ -442,6 +442,7 @@ ExternalNotificationModule::ExternalNotificationModule()
|
||||
|
||||
ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshPacket &mp)
|
||||
{
|
||||
// Trigger external notification if enabled and not muted; isSilenced is from temporary mute toggles
|
||||
if (moduleConfig.external_notification.enabled && !isSilenced) {
|
||||
#ifdef T_WATCH_S3
|
||||
drv.setWaveform(0, 75);
|
||||
@@ -456,99 +457,57 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
|
||||
for (size_t i = 0; i < p.payload.size; i++) {
|
||||
if (p.payload.bytes[i] == ASCII_BELL) {
|
||||
containsBell = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
|
||||
bool mutedNode = false;
|
||||
if (sender) {
|
||||
mutedNode = (sender->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK);
|
||||
}
|
||||
meshtastic_Channel ch = channels.getByIndex(mp.channel ? mp.channel : channels.getPrimaryIndex());
|
||||
|
||||
if (moduleConfig.external_notification.alert_bell) {
|
||||
if (containsBell) {
|
||||
LOG_INFO("externalNotificationModule - Notification Bell");
|
||||
// If we receive a broadcast message, apply channel mute setting
|
||||
// If we receive a direct message and the receipent is us, apply DM mute setting
|
||||
// Else we just handle it as not muted.
|
||||
const bool isDmToUs = !isBroadcast(mp.to) && isToUs(&mp);
|
||||
bool is_muted = isDmToUs ? (sender && ((sender->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0))
|
||||
: (ch.settings.has_module_settings && ch.settings.module_settings.is_muted);
|
||||
|
||||
const bool buzzerModeIsDirectOnly =
|
||||
(config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY);
|
||||
|
||||
if (containsBell || !is_muted) {
|
||||
if (moduleConfig.external_notification.alert_bell || moduleConfig.external_notification.alert_message ||
|
||||
moduleConfig.external_notification.alert_bell_vibra ||
|
||||
moduleConfig.external_notification.alert_message_vibra ||
|
||||
((moduleConfig.external_notification.alert_bell_buzzer ||
|
||||
moduleConfig.external_notification.alert_message_buzzer) &&
|
||||
canBuzz())) {
|
||||
nagCycleCutoff = millis() + (moduleConfig.external_notification.nag_timeout
|
||||
? (moduleConfig.external_notification.nag_timeout * 1000)
|
||||
: moduleConfig.external_notification.output_ms);
|
||||
LOG_INFO("Toggling nagCycleCutoff to %lu", nagCycleCutoff);
|
||||
isNagging = true;
|
||||
}
|
||||
|
||||
if (moduleConfig.external_notification.alert_bell || moduleConfig.external_notification.alert_message) {
|
||||
LOG_INFO("externalNotificationModule - Notification Module or Bell");
|
||||
setExternalState(0, true);
|
||||
if (moduleConfig.external_notification.nag_timeout) {
|
||||
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
|
||||
} else {
|
||||
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleConfig.external_notification.alert_bell_vibra) {
|
||||
if (containsBell) {
|
||||
LOG_INFO("externalNotificationModule - Notification Bell (Vibra)");
|
||||
isNagging = true;
|
||||
if (moduleConfig.external_notification.alert_bell_vibra ||
|
||||
moduleConfig.external_notification.alert_message_vibra) {
|
||||
LOG_INFO("externalNotificationModule - Notification Module or Bell (Vibra)");
|
||||
setExternalState(1, true);
|
||||
if (moduleConfig.external_notification.nag_timeout) {
|
||||
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
|
||||
}
|
||||
|
||||
if ((moduleConfig.external_notification.alert_bell_buzzer ||
|
||||
moduleConfig.external_notification.alert_message_buzzer) &&
|
||||
canBuzz()) {
|
||||
LOG_INFO("externalNotificationModule - Notification Module or Bell (Buzzer)");
|
||||
if (buzzerModeIsDirectOnly && !isDmToUs && !containsBell) {
|
||||
LOG_INFO("Message buzzer was suppressed because buzzer mode DIRECT_MSG_ONLY");
|
||||
} else {
|
||||
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleConfig.external_notification.alert_bell_buzzer && canBuzz()) {
|
||||
if (containsBell) {
|
||||
LOG_INFO("externalNotificationModule - Notification Bell (Buzzer)");
|
||||
isNagging = true;
|
||||
if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {
|
||||
setExternalState(2, true);
|
||||
} else {
|
||||
#ifdef HAS_I2S
|
||||
if (moduleConfig.external_notification.use_i2s_as_buzzer) {
|
||||
audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
|
||||
} else
|
||||
#endif
|
||||
if (moduleConfig.external_notification.use_pwm) {
|
||||
rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
|
||||
}
|
||||
}
|
||||
if (moduleConfig.external_notification.nag_timeout) {
|
||||
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
|
||||
} else {
|
||||
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleConfig.external_notification.alert_message && !mutedNode &&
|
||||
(!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) {
|
||||
LOG_INFO("externalNotificationModule - Notification Module");
|
||||
isNagging = true;
|
||||
setExternalState(0, true);
|
||||
if (moduleConfig.external_notification.nag_timeout) {
|
||||
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
|
||||
} else {
|
||||
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleConfig.external_notification.alert_message_vibra && !mutedNode &&
|
||||
(!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) {
|
||||
LOG_INFO("externalNotificationModule - Notification Module (Vibra)");
|
||||
isNagging = true;
|
||||
setExternalState(1, true);
|
||||
if (moduleConfig.external_notification.nag_timeout) {
|
||||
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
|
||||
} else {
|
||||
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleConfig.external_notification.alert_message_buzzer && !mutedNode &&
|
||||
(!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) {
|
||||
LOG_INFO("externalNotificationModule - Notification Module (Buzzer)");
|
||||
if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
|
||||
(!isBroadcast(mp.to) && isToUs(&mp))) {
|
||||
// Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us
|
||||
isNagging = true;
|
||||
// Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us
|
||||
#ifdef T_LORA_PAGER
|
||||
if (canBuzz()) {
|
||||
drv.setWaveform(0, 16); // Long buzzer 100%
|
||||
drv.setWaveform(1, 0); // Pause
|
||||
drv.setWaveform(2, 16);
|
||||
@@ -558,11 +517,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
|
||||
drv.setWaveform(6, 16);
|
||||
drv.setWaveform(7, 0);
|
||||
drv.go();
|
||||
}
|
||||
#endif
|
||||
if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {
|
||||
setExternalState(2, true);
|
||||
} else {
|
||||
#ifdef HAS_I2S
|
||||
if (moduleConfig.external_notification.use_i2s_as_buzzer) {
|
||||
audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
|
||||
@@ -570,18 +525,13 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
|
||||
#endif
|
||||
if (moduleConfig.external_notification.use_pwm) {
|
||||
rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
|
||||
} else {
|
||||
setExternalState(2, true);
|
||||
}
|
||||
}
|
||||
if (moduleConfig.external_notification.nag_timeout) {
|
||||
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
|
||||
} else {
|
||||
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
|
||||
}
|
||||
} else {
|
||||
// Don't beep if buzzer mode is "direct messages only" and it is no direct message
|
||||
LOG_INFO("Message buzzer was suppressed because buzzer mode DIRECT_MSG_ONLY");
|
||||
}
|
||||
}
|
||||
|
||||
setIntervalFromNow(0); // run once so we know if we should do something
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,24 +1,8 @@
|
||||
#include "configuration.h"
|
||||
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
|
||||
#include "buzz/BuzzerFeedbackThread.h"
|
||||
#include "input/ExpressLRSFiveWay.h"
|
||||
#include "input/InputBroker.h"
|
||||
#include "input/RotaryEncoderImpl.h"
|
||||
#include "input/RotaryEncoderInterruptImpl1.h"
|
||||
#include "input/SerialKeyboardImpl.h"
|
||||
#include "input/UpDownInterruptImpl1.h"
|
||||
#include "input/i2cButton.h"
|
||||
#include "modules/SystemCommandsModule.h"
|
||||
#if HAS_TRACKBALL
|
||||
#include "input/TrackballInterruptImpl1.h"
|
||||
#endif
|
||||
|
||||
#include "modules/StatusLEDModule.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_I2C
|
||||
#include "input/cardKbI2cImpl.h"
|
||||
#endif
|
||||
#include "input/kbMatrixImpl.h"
|
||||
#include "modules/SystemCommandsModule.h"
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_PKI
|
||||
#include "KeyVerificationModule.h"
|
||||
@@ -59,8 +43,6 @@
|
||||
#include "modules/WaypointModule.h"
|
||||
#endif
|
||||
#if ARCH_PORTDUINO
|
||||
#include "input/LinuxInputImpl.h"
|
||||
#include "input/SeesawRotary.h"
|
||||
#include "modules/Telemetry/HostMetrics.h"
|
||||
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
|
||||
#include "modules/StoreForwardModule.h"
|
||||
@@ -109,6 +91,9 @@
|
||||
#include "modules/DropzoneModule.h"
|
||||
#endif
|
||||
|
||||
#if defined(HAS_HARDWARE_WATCHDOG)
|
||||
#include "watchdog/watchdogThread.h"
|
||||
#endif
|
||||
/**
|
||||
* Create module instances here. If you are adding a new module, you must 'new' it here (or somewhere else)
|
||||
*/
|
||||
@@ -179,63 +164,6 @@ void setupModules()
|
||||
#endif
|
||||
// Example: Put your module here
|
||||
// new ReplyModule();
|
||||
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
|
||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
||||
#if defined(T_LORA_PAGER)
|
||||
// use a special FSM based rotary encoder version for T-LoRa Pager
|
||||
rotaryEncoderImpl = new RotaryEncoderImpl();
|
||||
if (!rotaryEncoderImpl->init()) {
|
||||
delete rotaryEncoderImpl;
|
||||
rotaryEncoderImpl = nullptr;
|
||||
}
|
||||
#elif defined(INPUTDRIVER_ENCODER_TYPE) && (INPUTDRIVER_ENCODER_TYPE == 2)
|
||||
upDownInterruptImpl1 = new UpDownInterruptImpl1();
|
||||
if (!upDownInterruptImpl1->init()) {
|
||||
delete upDownInterruptImpl1;
|
||||
upDownInterruptImpl1 = nullptr;
|
||||
}
|
||||
#else
|
||||
rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
|
||||
if (!rotaryEncoderInterruptImpl1->init()) {
|
||||
delete rotaryEncoderInterruptImpl1;
|
||||
rotaryEncoderInterruptImpl1 = nullptr;
|
||||
}
|
||||
#endif
|
||||
cardKbI2cImpl = new CardKbI2cImpl();
|
||||
cardKbI2cImpl->init();
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
i2cButton = new i2cButtonThread("i2cButtonThread");
|
||||
#endif
|
||||
#ifdef INPUTBROKER_MATRIX_TYPE
|
||||
kbMatrixImpl = new KbMatrixImpl();
|
||||
kbMatrixImpl->init();
|
||||
#endif // INPUTBROKER_MATRIX_TYPE
|
||||
#ifdef INPUTBROKER_SERIAL_TYPE
|
||||
aSerialKeyboardImpl = new SerialKeyboardImpl();
|
||||
aSerialKeyboardImpl->init();
|
||||
#endif // INPUTBROKER_MATRIX_TYPE
|
||||
}
|
||||
#endif // HAS_BUTTON
|
||||
#if ARCH_PORTDUINO
|
||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR && portduino_config.i2cdev != "") {
|
||||
seesawRotary = new SeesawRotary("SeesawRotary");
|
||||
if (!seesawRotary->init()) {
|
||||
delete seesawRotary;
|
||||
seesawRotary = nullptr;
|
||||
}
|
||||
aLinuxInputImpl = new LinuxInputImpl();
|
||||
aLinuxInputImpl->init();
|
||||
}
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_INPUTBROKER && HAS_TRACKBALL
|
||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
||||
trackballInterruptImpl1 = new TrackballInterruptImpl1();
|
||||
trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS);
|
||||
}
|
||||
#endif
|
||||
#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE
|
||||
expressLRSFiveWayInput = new ExpressLRSFiveWay();
|
||||
#endif
|
||||
#if HAS_SCREEN && !MESHTASTIC_EXCLUDE_CANNEDMESSAGES
|
||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
||||
cannedMessageModule = new CannedMessageModule();
|
||||
@@ -304,6 +232,9 @@ void setupModules()
|
||||
#if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS
|
||||
if (moduleConfig.has_range_test && moduleConfig.range_test.enabled)
|
||||
new RangeTestModule();
|
||||
#endif
|
||||
#if defined(HAS_HARDWARE_WATCHDOG)
|
||||
watchdogThread = new WatchdogThread();
|
||||
#endif
|
||||
// NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra
|
||||
// acks
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "Default.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "NodeStatus.h"
|
||||
#include "RTC.h"
|
||||
#include "Router.h"
|
||||
#include "configuration.h"
|
||||
@@ -129,14 +130,17 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
|
||||
LOG_DEBUG("Skip send NodeInfo > 40%% ch. util");
|
||||
return NULL;
|
||||
}
|
||||
// If we sent our NodeInfo less than 5 min. ago, don't send it again as it may be still underway.
|
||||
if (!shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 5 * 60 * 1000)) {
|
||||
LOG_DEBUG("Skip send NodeInfo since we sent it <5min ago");
|
||||
|
||||
// Use graduated scaling based on active mesh size (10 minute base, scales with congestion coefficient)
|
||||
uint32_t timeoutMs = Default::getConfiguredOrDefaultMsScaled(0, 10 * 60, nodeStatus->getNumOnline());
|
||||
if (!shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, timeoutMs)) {
|
||||
LOG_DEBUG("Skip send NodeInfo since we sent it <%us ago", timeoutMs / 1000);
|
||||
ignoreRequest = true; // Mark it as ignored for MeshModule
|
||||
return NULL;
|
||||
} else if (shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 60 * 1000)) {
|
||||
// For interactive/urgent requests (e.g., user-triggered or implicit requests), use a shorter 60s timeout
|
||||
LOG_DEBUG("Skip send NodeInfo since we sent it <60s ago");
|
||||
ignoreRequest = true; // Mark it as ignored for MeshModule
|
||||
ignoreRequest = true;
|
||||
return NULL;
|
||||
} else {
|
||||
ignoreRequest = false; // Don't ignore requests anymore
|
||||
|
||||
@@ -67,6 +67,8 @@ uint8_t RoutingModule::getHopLimitForResponse(const meshtastic_MeshPacket &mp)
|
||||
#if !(EVENTMODE) // This falls through to the default.
|
||||
return hopsUsed; // If the request used more hops than the limit, use the same amount of hops
|
||||
#endif
|
||||
} else if (mp.hop_start == 0) {
|
||||
return 0; // The requesting node wanted 0 hops, so the response also uses a direct/local path.
|
||||
} else if ((uint8_t)(hopsUsed + 2) < config.lora.hop_limit) {
|
||||
return hopsUsed + 2; // Use only the amount of hops needed with some margin as the way back may be different
|
||||
}
|
||||
|
||||
@@ -63,29 +63,26 @@
|
||||
SerialModule *serialModule;
|
||||
SerialModuleRadio *serialModuleRadio;
|
||||
|
||||
#if defined(TTGO_T_ECHO) || defined(TTGO_T_ECHO_PLUS) || defined(CANARYONE) || defined(MESHLINK) || \
|
||||
defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M4) || defined(ELECROW_ThinkNode_M5) || \
|
||||
defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) || defined(ELECROW_ThinkNode_M3) || defined(MUZI_BASE)
|
||||
|
||||
SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial")
|
||||
{
|
||||
api_type = TYPE_SERIAL;
|
||||
}
|
||||
static Print *serialPrint = &Serial;
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C6) || defined(RAK3172) || defined(EBYTE_E77_MBL)
|
||||
SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial")
|
||||
{
|
||||
api_type = TYPE_SERIAL;
|
||||
}
|
||||
static Print *serialPrint = &Serial1;
|
||||
#else
|
||||
SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("Serial")
|
||||
{
|
||||
api_type = TYPE_SERIAL;
|
||||
}
|
||||
static Print *serialPrint = &Serial2;
|
||||
#ifndef SERIAL_PRINT_PORT
|
||||
#define SERIAL_PRINT_PORT 2
|
||||
#endif
|
||||
|
||||
#if SERIAL_PRINT_PORT == 0
|
||||
#define SERIAL_PRINT_OBJECT Serial
|
||||
#elif SERIAL_PRINT_PORT == 1
|
||||
#define SERIAL_PRINT_OBJECT Serial1
|
||||
#elif SERIAL_PRINT_PORT == 2
|
||||
#define SERIAL_PRINT_OBJECT Serial2
|
||||
#else
|
||||
#error "Unsupported SERIAL_PRINT_PORT value. Allowed values are 0, 1, or 2."
|
||||
#endif
|
||||
|
||||
SerialModule::SerialModule() : StreamAPI(&SERIAL_PRINT_OBJECT), concurrency::OSThread("Serial")
|
||||
{
|
||||
api_type = TYPE_SERIAL;
|
||||
}
|
||||
static Print *serialPrint = &SERIAL_PRINT_OBJECT;
|
||||
|
||||
char serialBytes[512];
|
||||
size_t serialPayloadSize;
|
||||
|
||||
@@ -205,9 +202,7 @@ int32_t SerialModule::runOnce()
|
||||
Serial.begin(baud);
|
||||
Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT);
|
||||
}
|
||||
#elif !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && \
|
||||
!defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M4) && \
|
||||
!defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE)
|
||||
#elif SERIAL_PRINT_PORT != 0
|
||||
|
||||
if (moduleConfig.serial.rxd && moduleConfig.serial.txd) {
|
||||
#ifdef ARCH_RP2040
|
||||
@@ -264,9 +259,7 @@ int32_t SerialModule::runOnce()
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \
|
||||
!defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M4) && \
|
||||
!defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE)
|
||||
#if SERIAL_PRINT_PORT != 0
|
||||
else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) {
|
||||
processWXSerial();
|
||||
|
||||
@@ -540,11 +533,7 @@ ParsedLine parseLine(const char *line)
|
||||
*/
|
||||
void SerialModule::processWXSerial()
|
||||
{
|
||||
#if !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && \
|
||||
!defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && \
|
||||
!defined(ELECROW_ThinkNode_M3) && \
|
||||
!defined(ELECROW_ThinkNode_M4) && \
|
||||
!defined(ELECROW_ThinkNode_M5) && !defined(ARCH_STM32WL) && !defined(MUZI_BASE)
|
||||
#if SERIAL_PRINT_PORT != 0 && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6)
|
||||
|
||||
static unsigned int lastAveraged = 0;
|
||||
static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded.
|
||||
|
||||
@@ -50,9 +50,10 @@ int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg)
|
||||
break;
|
||||
}
|
||||
case meshtastic::BluetoothStatus::ConnectionState::CONNECTED: {
|
||||
ble_state = connected;
|
||||
PAIRING_LED_starttime = millis();
|
||||
break;
|
||||
if (ble_state != connected) {
|
||||
ble_state = connected;
|
||||
PAIRING_LED_starttime = millis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +130,6 @@ int32_t StatusLEDModule::runOnce()
|
||||
#ifdef LED_CHARGE
|
||||
digitalWrite(LED_CHARGE, CHARGE_LED_state);
|
||||
#endif
|
||||
// digitalWrite(green_LED_PIN, LED_STATE_OFF);
|
||||
#ifdef LED_PAIRING
|
||||
digitalWrite(LED_PAIRING, PAIRING_LED_state);
|
||||
#endif
|
||||
|
||||
@@ -513,7 +513,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp,
|
||||
LOG_DEBUG("StoreAndForward_RequestResponse_ROUTER_BUSY");
|
||||
// retry in messages_saved * packetTimeMax ms
|
||||
retry_delay = millis() + getNumAvailablePackets(this->busyTo, this->last_time) * packetTimeMax *
|
||||
(meshtastic_StoreAndForward_RequestResponse_ROUTER_ERROR ? 2 : 1);
|
||||
(p->rr == meshtastic_StoreAndForward_RequestResponse_ROUTER_ERROR ? 2 : 1);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
|
||||
#include "Sensor/LTR390UVSensor.h"
|
||||
#endif
|
||||
|
||||
#if __has_include(MESHTASTIC_BME680_HEADER)
|
||||
#if __has_include(<bsec2.h>) || __has_include(<Adafruit_BME680.h>)
|
||||
#include "Sensor/BME680Sensor.h"
|
||||
#endif
|
||||
|
||||
@@ -187,7 +187,7 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
|
||||
#if __has_include(<Adafruit_LTR390.h>)
|
||||
addSensor<LTR390UVSensor>(i2cScanner, ScanI2C::DeviceType::LTR390UV);
|
||||
#endif
|
||||
#if __has_include(MESHTASTIC_BME680_HEADER)
|
||||
#if __has_include(<bsec2.h>) || __has_include(<Adafruit_BME680.h>)
|
||||
addSensor<BME680Sensor>(i2cScanner, ScanI2C::DeviceType::BME_680);
|
||||
#endif
|
||||
#if __has_include(<Adafruit_BMP280.h>)
|
||||
@@ -529,37 +529,46 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac
|
||||
|
||||
bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m)
|
||||
{
|
||||
bool valid = true;
|
||||
bool valid = false;
|
||||
bool hasSensor = false;
|
||||
// getMetrics() doesn't always get evaluated because of
|
||||
// short-circuit evaluation rules in c++
|
||||
bool get_metrics;
|
||||
m->time = getTime();
|
||||
m->which_variant = meshtastic_Telemetry_environment_metrics_tag;
|
||||
m->variant.environment_metrics = meshtastic_EnvironmentMetrics_init_zero;
|
||||
|
||||
for (TelemetrySensor *sensor : sensors) {
|
||||
valid = valid && sensor->getMetrics(m);
|
||||
get_metrics = sensor->getMetrics(m); // avoid short-circuit evaluation rules
|
||||
valid = valid || get_metrics;
|
||||
hasSensor = true;
|
||||
}
|
||||
|
||||
#ifndef T1000X_SENSOR_EN
|
||||
if (ina219Sensor.hasSensor()) {
|
||||
valid = valid && ina219Sensor.getMetrics(m);
|
||||
get_metrics = ina219Sensor.getMetrics(m);
|
||||
valid = valid || get_metrics;
|
||||
hasSensor = true;
|
||||
}
|
||||
if (ina260Sensor.hasSensor()) {
|
||||
valid = valid && ina260Sensor.getMetrics(m);
|
||||
get_metrics = ina260Sensor.getMetrics(m);
|
||||
valid = valid || get_metrics;
|
||||
hasSensor = true;
|
||||
}
|
||||
if (ina3221Sensor.hasSensor()) {
|
||||
valid = valid && ina3221Sensor.getMetrics(m);
|
||||
get_metrics = ina3221Sensor.getMetrics(m);
|
||||
valid = valid || get_metrics;
|
||||
hasSensor = true;
|
||||
}
|
||||
if (max17048Sensor.hasSensor()) {
|
||||
valid = valid && max17048Sensor.getMetrics(m);
|
||||
get_metrics = max17048Sensor.getMetrics(m);
|
||||
valid = valid || get_metrics;
|
||||
hasSensor = true;
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_RAKPROT
|
||||
valid = valid && rak9154Sensor.getMetrics(m);
|
||||
get_metrics = rak9154Sensor.getMetrics(m);
|
||||
valid = valid || get_metrics;
|
||||
hasSensor = true;
|
||||
#endif
|
||||
return valid && hasSensor;
|
||||
|
||||
@@ -168,18 +168,21 @@ bool HealthTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &
|
||||
|
||||
bool HealthTelemetryModule::getHealthTelemetry(meshtastic_Telemetry *m)
|
||||
{
|
||||
bool valid = true;
|
||||
bool valid = false;
|
||||
bool hasSensor = false;
|
||||
bool get_metrics;
|
||||
m->time = getTime();
|
||||
m->which_variant = meshtastic_Telemetry_health_metrics_tag;
|
||||
m->variant.health_metrics = meshtastic_HealthMetrics_init_zero;
|
||||
|
||||
if (max30102Sensor.hasSensor()) {
|
||||
valid = valid && max30102Sensor.getMetrics(m);
|
||||
get_metrics = max30102Sensor.getMetrics(m);
|
||||
valid = valid || get_metrics; // avoid short-circuit evaluation rules
|
||||
hasSensor = true;
|
||||
}
|
||||
if (mlx90614Sensor.hasSensor()) {
|
||||
valid = valid && mlx90614Sensor.getMetrics(m);
|
||||
get_metrics = mlx90614Sensor.getMetrics(m);
|
||||
valid = valid || get_metrics;
|
||||
hasSensor = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER)
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && (__has_include(<bsec2.h>) || __has_include(<Adafruit_BME680.h>))
|
||||
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "BME680Sensor.h"
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {}
|
||||
|
||||
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
|
||||
#if __has_include(<bsec2.h>)
|
||||
int32_t BME680Sensor::runOnce()
|
||||
{
|
||||
if (!bme680.run()) {
|
||||
@@ -18,13 +18,13 @@ int32_t BME680Sensor::runOnce()
|
||||
}
|
||||
return 35;
|
||||
}
|
||||
#endif // defined(MESHTASTIC_BME680_BSEC2_SUPPORTED)
|
||||
#endif
|
||||
|
||||
bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
|
||||
{
|
||||
status = 0;
|
||||
|
||||
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
|
||||
#if __has_include(<bsec2.h>)
|
||||
if (!bme680.begin(dev->address.address, *bus))
|
||||
checkStatus("begin");
|
||||
|
||||
@@ -56,7 +56,7 @@ bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
|
||||
|
||||
status = 1;
|
||||
|
||||
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
|
||||
#endif
|
||||
|
||||
initI2CSensor();
|
||||
return status;
|
||||
@@ -64,7 +64,7 @@ bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
|
||||
|
||||
bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement)
|
||||
{
|
||||
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
|
||||
#if __has_include(<bsec2.h>)
|
||||
if (bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal == 0)
|
||||
return false;
|
||||
|
||||
@@ -98,11 +98,11 @@ bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement)
|
||||
measurement->variant.environment_metrics.barometric_pressure = bme680->readPressure() / 100.0F;
|
||||
measurement->variant.environment_metrics.gas_resistance = bme680->readGas() / 1000.0;
|
||||
|
||||
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
|
||||
#if __has_include(<bsec2.h>)
|
||||
void BME680Sensor::loadState()
|
||||
{
|
||||
#ifdef FSCom
|
||||
@@ -179,6 +179,6 @@ void BME680Sensor::checkStatus(const char *functionName)
|
||||
else if (bme680.sensor.status > BME68X_OK)
|
||||
LOG_WARN("%s BME68X code: %d", functionName, bme680.sensor.status);
|
||||
}
|
||||
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER)
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && (__has_include(<bsec2.h>) || __has_include(<Adafruit_BME680.h>))
|
||||
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "TelemetrySensor.h"
|
||||
|
||||
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
|
||||
#if __has_include(<bsec2.h>)
|
||||
#include <bme68xLibrary.h>
|
||||
#include <bsec2.h>
|
||||
#else
|
||||
#include <Adafruit_BME680.h>
|
||||
#include <memory>
|
||||
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
|
||||
#endif
|
||||
|
||||
#define STATE_SAVE_PERIOD UINT32_C(360 * 60 * 1000) // That's 6 hours worth of millis()
|
||||
|
||||
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
|
||||
#if __has_include(<bsec2.h>)
|
||||
const uint8_t bsec_config[] = {
|
||||
#include "config/bme680/bme680_iaq_33v_3s_4d/bsec_iaq.txt"
|
||||
};
|
||||
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
|
||||
#endif
|
||||
class BME680Sensor : public TelemetrySensor
|
||||
{
|
||||
private:
|
||||
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
|
||||
#if __has_include(<bsec2.h>)
|
||||
Bsec2 bme680;
|
||||
#else
|
||||
using BME680Ptr = std::unique_ptr<Adafruit_BME680>;
|
||||
@@ -31,10 +31,10 @@ class BME680Sensor : public TelemetrySensor
|
||||
static BME680Ptr makeBME680(TwoWire *bus) { return std::make_unique<Adafruit_BME680>(bus); }
|
||||
|
||||
BME680Ptr bme680;
|
||||
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
|
||||
#endif
|
||||
|
||||
protected:
|
||||
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
|
||||
#if __has_include(<bsec2.h>)
|
||||
const char *bsecConfigFileName = "/prefs/bsec.dat";
|
||||
uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0};
|
||||
uint8_t accuracy = 0;
|
||||
@@ -51,13 +51,13 @@ class BME680Sensor : public TelemetrySensor
|
||||
void loadState();
|
||||
void updateState();
|
||||
void checkStatus(const char *functionName);
|
||||
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
|
||||
#endif
|
||||
|
||||
public:
|
||||
BME680Sensor();
|
||||
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
|
||||
#if __has_include(<bsec2.h>)
|
||||
virtual int32_t runOnce() override;
|
||||
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
|
||||
#endif
|
||||
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
|
||||
virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
|
||||
};
|
||||
|
||||
@@ -4,6 +4,15 @@
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "RAK12035Sensor.h"
|
||||
|
||||
// The RAK12035 library's sensor_sleep() sets WB_IO2 (GPIO 34) LOW, which controls
|
||||
// the 3.3V switched power rail (PIN_3V3_EN). This turns off power to ALL peripherals
|
||||
// including GPS. We need to restore power after the library turns it off.
|
||||
#ifdef PIN_3V3_EN
|
||||
#define RESTORE_3V3_POWER() digitalWrite(PIN_3V3_EN, HIGH)
|
||||
#else
|
||||
#define RESTORE_3V3_POWER()
|
||||
#endif
|
||||
|
||||
RAK12035Sensor::RAK12035Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RAK12035, "RAK12035") {}
|
||||
|
||||
bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
|
||||
@@ -13,16 +22,15 @@ bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
|
||||
delay(100);
|
||||
sensor.begin(dev->address.address);
|
||||
|
||||
// Get sensor firmware version
|
||||
uint8_t data = 0;
|
||||
sensor.get_sensor_version(&data);
|
||||
if (data != 0) {
|
||||
LOG_INFO("Init sensor: %s", sensorName);
|
||||
LOG_INFO("RAK12035Sensor Init Succeed \nSensor1 Firmware version: %i, Sensor Name: %s", data, sensorName);
|
||||
LOG_INFO("RAK12035Sensor Init Succeed \nSensor Firmware version: %i, Sensor Name: %s", data, sensorName);
|
||||
status = true;
|
||||
sensor.sensor_sleep();
|
||||
RESTORE_3V3_POWER();
|
||||
} else {
|
||||
// If we reach here, it means the sensor did not initialize correctly.
|
||||
LOG_INFO("Init sensor: %s", sensorName);
|
||||
LOG_ERROR("RAK12035Sensor Init Failed");
|
||||
status = false;
|
||||
@@ -38,39 +46,44 @@ bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
|
||||
|
||||
void RAK12035Sensor::setup()
|
||||
{
|
||||
// Set the calibration values
|
||||
// Reading the saved calibration values from the sensor.
|
||||
// TODO:: Check for and run calibration check for up to 2 additional sensors if present.
|
||||
uint16_t zero_val = 0;
|
||||
uint16_t hundred_val = 0;
|
||||
uint16_t default_zero_val = 550;
|
||||
uint16_t default_hundred_val = 420;
|
||||
const uint16_t default_zero_val = 510;
|
||||
const uint16_t default_hundred_val = 390;
|
||||
|
||||
sensor.sensor_on();
|
||||
sensor.begin();
|
||||
delay(200);
|
||||
sensor.get_dry_cal(&zero_val);
|
||||
delay(200);
|
||||
sensor.get_wet_cal(&hundred_val);
|
||||
delay(200);
|
||||
if (zero_val == 0 || zero_val <= hundred_val) {
|
||||
LOG_INFO("Dry calibration value is %d", zero_val);
|
||||
LOG_INFO("Wet calibration value is %d", hundred_val);
|
||||
LOG_INFO("This does not make sense. You can recalibrate this sensor using the calibration sketch included here: "
|
||||
"https://github.com/RAKWireless/RAK12035_SoilMoisture.");
|
||||
LOG_INFO("For now, setting default calibration value for Dry Calibration: %d", default_zero_val);
|
||||
|
||||
bool calibrationReset = false;
|
||||
|
||||
if (zero_val == 0) {
|
||||
LOG_INFO("Dry calibration not set, using default: %d", default_zero_val);
|
||||
sensor.set_dry_cal(default_zero_val);
|
||||
sensor.get_dry_cal(&zero_val);
|
||||
LOG_INFO("Dry calibration reset complete. New value is %d", zero_val);
|
||||
delay(200);
|
||||
zero_val = default_zero_val;
|
||||
calibrationReset = true;
|
||||
}
|
||||
if (hundred_val == 0 || hundred_val >= zero_val) {
|
||||
LOG_INFO("Dry calibration value is %d", zero_val);
|
||||
LOG_INFO("Wet calibration value is %d", hundred_val);
|
||||
LOG_INFO("This does not make sense. You can recalibrate this sensor using the calibration sketch included here: "
|
||||
"https://github.com/RAKWireless/RAK12035_SoilMoisture.");
|
||||
LOG_INFO("For now, setting default calibration value for Wet Calibration: %d", default_hundred_val);
|
||||
LOG_INFO("Wet calibration not set, using default: %d", default_hundred_val);
|
||||
sensor.set_wet_cal(default_hundred_val);
|
||||
sensor.get_wet_cal(&hundred_val);
|
||||
LOG_INFO("Wet calibration reset complete. New value is %d", hundred_val);
|
||||
delay(200);
|
||||
hundred_val = default_hundred_val;
|
||||
calibrationReset = true;
|
||||
}
|
||||
if (calibrationReset) {
|
||||
LOG_INFO("Default calibration values applied. Consider running the calibration sketch for better accuracy: "
|
||||
"https://github.com/RAKWireless/RAK12035_SoilMoisture");
|
||||
}
|
||||
|
||||
LOG_INFO("Dry calibration value: %d, Wet calibration value: %d", zero_val, hundred_val);
|
||||
sensor.sensor_sleep();
|
||||
RESTORE_3V3_POWER();
|
||||
delay(200);
|
||||
LOG_INFO("Dry calibration value is %d", zero_val);
|
||||
LOG_INFO("Wet calibration value is %d", hundred_val);
|
||||
@@ -79,10 +92,6 @@ void RAK12035Sensor::setup()
|
||||
bool RAK12035Sensor::getMetrics(meshtastic_Telemetry *measurement)
|
||||
{
|
||||
// TODO:: read and send metrics for up to 2 additional soil monitors if present.
|
||||
// -- how to do this.. this could get a little complex..
|
||||
// ie - 1> we combine them into an average and send that, 2> we send them as separate metrics
|
||||
// ^-- these scenarios would require different handling of the metrics in the receiving end and maybe a setting in the
|
||||
// device ui and an additional proto for that?
|
||||
measurement->variant.environment_metrics.has_soil_temperature = true;
|
||||
measurement->variant.environment_metrics.has_soil_moisture = true;
|
||||
|
||||
@@ -97,6 +106,7 @@ bool RAK12035Sensor::getMetrics(meshtastic_Telemetry *measurement)
|
||||
success &= sensor.get_sensor_temperature(&temp);
|
||||
delay(200);
|
||||
sensor.sensor_sleep();
|
||||
RESTORE_3V3_POWER();
|
||||
|
||||
if (success == false) {
|
||||
LOG_ERROR("Failed to read sensor data");
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user