mirror of
https://github.com/meshtastic/firmware.git
synced 2026-02-03 23:52:01 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1163855ce4 | ||
|
|
65ba235360 | ||
|
|
4a5640b53d |
213
.github/workflows/models_issue_triage.yml
vendored
213
.github/workflows/models_issue_triage.yml
vendored
@@ -1,213 +0,0 @@
|
|||||||
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.
|
|
||||||
|
|
||||||
IMPORTANT: Distinguish between:
|
|
||||||
- Device/firmware bugs (crashes, reboots, lockups, radio/GPS/display/power issues) - these need device logs
|
|
||||||
- Build/release/packaging issues (missing files, CI failures, download problems) - these do NOT need device logs
|
|
||||||
- Documentation or website issues - these do NOT need device logs
|
|
||||||
|
|
||||||
If this is a device/firmware bug, 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 valid JSON (no markdown, no code fences):
|
|
||||||
{"complete": true, "comment": "", "label": "none"}
|
|
||||||
OR
|
|
||||||
{"complete": false, "comment": "Your helpful comment", "label": "needs-logs"}
|
|
||||||
|
|
||||||
Use "needs-logs" ONLY if this is a device/firmware 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, is a feature request, or is a build/CI/packaging issue.
|
|
||||||
|
|
||||||
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. Only request device logs for actual device/firmware bugs, not for build/release/CI issues.
|
|
||||||
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: |
|
|
||||||
let raw = (process.env.AI_RESPONSE || '').trim();
|
|
||||||
|
|
||||||
// Strip markdown code fences if present
|
|
||||||
raw = raw.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/i, '').trim();
|
|
||||||
|
|
||||||
let complete = true;
|
|
||||||
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, log warning and don't comment (avoid posting raw JSON)
|
|
||||||
console.log('Failed to parse AI response as JSON:', raw);
|
|
||||||
complete = true;
|
|
||||||
comment = '';
|
|
||||||
label = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate label
|
|
||||||
const allowedLabels = new Set(['needs-logs', 'needs-info', 'none']);
|
|
||||||
if (!allowedLabels.has(label)) label = 'none';
|
|
||||||
|
|
||||||
// Only comment if we have a valid parsed comment (not raw JSON)
|
|
||||||
const shouldComment = !complete && comment.length > 0 && !comment.startsWith('{');
|
|
||||||
core.setOutput('should_comment', shouldComment ? '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
|
|
||||||
});
|
|
||||||
139
.github/workflows/models_pr_triage.yml
vendored
139
.github/workflows/models_pr_triage.yml
vendored
@@ -1,139 +0,0 @@
|
|||||||
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)
|
|
||||||
# Only skip for spam/ai-generated; still classify needs-review PRs
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────
|
|
||||||
- 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 != 'spam' && steps.quality.outputs.response != 'ai-generated'
|
|
||||||
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,6 +50,3 @@ idf_component.yml
|
|||||||
CMakeLists.txt
|
CMakeLists.txt
|
||||||
/sdkconfig.*
|
/sdkconfig.*
|
||||||
.dummy/*
|
.dummy/*
|
||||||
|
|
||||||
# PYTHONPATH used by the Nix shell
|
|
||||||
.python3
|
|
||||||
|
|||||||
@@ -1,23 +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: 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.
|
|
||||||
15
bin/config.d/lora-usb-umesh-1262.yaml
Normal file
15
bin/config.d/lora-usb-umesh-1262.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
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
|
||||||
@@ -1,23 +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: 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.
|
|
||||||
15
bin/config.d/lora-usb-umesh-1268.yaml
Normal file
15
bin/config.d/lora-usb-umesh-1268.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
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,19 +32,6 @@ if ! command -v jq >/dev/null 2>&1; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
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
|
set -e
|
||||||
|
|
||||||
# Usage info
|
# Usage info
|
||||||
@@ -96,8 +83,8 @@ while [ $# -gt 0 ]; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
if [[ $BPS_RESET == true ]]; then
|
if [[ $BPS_RESET == true ]]; then
|
||||||
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset ${ESPTOOL_READ_FLASH_STATUS}
|
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[ -z "$FILENAME" ] && [ -n "$1" ] && {
|
[ -z "$FILENAME" ] && [ -n "$1" ] && {
|
||||||
@@ -157,12 +144,12 @@ if [[ -f "$FILENAME" && "$FILENAME" == *.factory.bin ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
|
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
|
||||||
$ESPTOOL_CMD ${ESPTOOL_ERASE_FLASH}
|
$ESPTOOL_CMD erase-flash
|
||||||
$ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $FIRMWARE_OFFSET "${FILENAME}"
|
$ESPTOOL_CMD write-flash $FIRMWARE_OFFSET "${FILENAME}"
|
||||||
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
|
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
|
||||||
$ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $OTA_OFFSET "${OTAFILE}"
|
$ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}"
|
||||||
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
|
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
|
||||||
$ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $OFFSET "${SPIFFSFILE}"
|
$ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}"
|
||||||
|
|
||||||
else
|
else
|
||||||
show_help
|
show_help
|
||||||
|
|||||||
@@ -20,17 +20,6 @@ else
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
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
|
# Usage info
|
||||||
show_help() {
|
show_help() {
|
||||||
cat << EOF
|
cat << EOF
|
||||||
@@ -80,7 +69,7 @@ done
|
|||||||
shift "$((OPTIND-1))"
|
shift "$((OPTIND-1))"
|
||||||
|
|
||||||
if [ "$CHANGE_MODE" = true ]; then
|
if [ "$CHANGE_MODE" = true ]; then
|
||||||
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset ${ESPTOOL_READ_FLASH_STATUS}
|
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -91,7 +80,7 @@ fi
|
|||||||
|
|
||||||
if [[ -f "$FILENAME" && "$FILENAME" != *.factory.bin ]]; then
|
if [[ -f "$FILENAME" && "$FILENAME" != *.factory.bin ]]; then
|
||||||
echo "Trying to flash update ${FILENAME}"
|
echo "Trying to flash update ${FILENAME}"
|
||||||
$ESPTOOL_CMD --baud $FLASH_BAUD ${ESPTOOL_WRITE_FLASH} $UPDATE_OFFSET "${FILENAME}"
|
$ESPTOOL_CMD --baud $FLASH_BAUD write-flash $UPDATE_OFFSET "${FILENAME}"
|
||||||
else
|
else
|
||||||
show_help
|
show_help
|
||||||
echo "Invalid file: ${FILENAME}"
|
echo "Invalid file: ${FILENAME}"
|
||||||
|
|||||||
@@ -87,9 +87,6 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
|
|
||||||
<releases>
|
<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">
|
<release version="2.7.18" date="2026-01-02">
|
||||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.18</url>
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.18</url>
|
||||||
</release>
|
</release>
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
{
|
|
||||||
"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,9 +1,3 @@
|
|||||||
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
|
meshtasticd (2.7.18.0) unstable; urgency=medium
|
||||||
|
|
||||||
* Version 2.7.18
|
* Version 2.7.18
|
||||||
|
|||||||
44
flake.lock
generated
44
flake.lock
generated
@@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"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
66
flake.nix
@@ -1,66 +0,0 @@
|
|||||||
{
|
|
||||||
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,11 +43,13 @@ class Esp32C3ExceptionDecoder(DeviceMonitorFilterBase):
|
|||||||
self.enabled = self.setup_paths()
|
self.enabled = self.setup_paths()
|
||||||
|
|
||||||
if self.config.get("env:" + self.environment, "build_type") != "debug":
|
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.
|
Please build project in debug configuration to get more details about an exception.
|
||||||
See https://docs.platformio.org/page/projectconf/build_configurations.html
|
See https://docs.platformio.org/page/projectconf/build_configurations.html
|
||||||
|
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ build_flags = -Wno-missing-field-initializers
|
|||||||
-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
|
-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
|
||||||
-DMESHTASTIC_EXCLUDE_POWERMON=1
|
-DMESHTASTIC_EXCLUDE_POWERMON=1
|
||||||
-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
|
-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
|
#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
|
||||||
#-D OLED_PL=1
|
#-D OLED_PL=1
|
||||||
#-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs
|
#-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs
|
||||||
@@ -120,7 +119,7 @@ lib_deps =
|
|||||||
[device-ui_base]
|
[device-ui_base]
|
||||||
lib_deps =
|
lib_deps =
|
||||||
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
||||||
https://github.com/meshtastic/device-ui/archive/63967a4a557d33d56fc5746f9128200dde2d88c5.zip
|
https://github.com/meshtastic/device-ui/archive/3480b731d28b10d73414cf0dd7975bff745de8cf.zip
|
||||||
|
|
||||||
; Common libs for environmental measurements in telemetry module
|
; Common libs for environmental measurements in telemetry module
|
||||||
[environmental_base]
|
[environmental_base]
|
||||||
@@ -213,30 +212,3 @@ lib_deps =
|
|||||||
sensirion/Sensirion Core@0.7.2
|
sensirion/Sensirion Core@0.7.2
|
||||||
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
|
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
|
||||||
sensirion/Sensirion I2C SCD4x@1.1.0
|
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: bc63a57f9e...bbde30a0b9
12
shell.nix
12
shell.nix
@@ -1,12 +0,0 @@
|
|||||||
(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
|
|
||||||
@@ -89,14 +89,22 @@ class BluetoothStatus : public Status
|
|||||||
case ConnectionState::CONNECTED:
|
case ConnectionState::CONNECTED:
|
||||||
LOG_DEBUG("BluetoothStatus CONNECTED");
|
LOG_DEBUG("BluetoothStatus CONNECTED");
|
||||||
#ifdef BLE_LED
|
#ifdef BLE_LED
|
||||||
digitalWrite(BLE_LED, LED_STATE_ON);
|
#ifdef BLE_LED_INVERTED
|
||||||
|
digitalWrite(BLE_LED, LOW);
|
||||||
|
#else
|
||||||
|
digitalWrite(BLE_LED, HIGH);
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ConnectionState::DISCONNECTED:
|
case ConnectionState::DISCONNECTED:
|
||||||
LOG_DEBUG("BluetoothStatus DISCONNECTED");
|
LOG_DEBUG("BluetoothStatus DISCONNECTED");
|
||||||
#ifdef BLE_LED
|
#ifdef BLE_LED
|
||||||
digitalWrite(BLE_LED, LED_STATE_OFF);
|
#ifdef BLE_LED_INVERTED
|
||||||
|
digitalWrite(BLE_LED, HIGH);
|
||||||
|
#else
|
||||||
|
digitalWrite(BLE_LED, LOW);
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
66
src/Led.cpp
Normal file
66
src/Led.cpp
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#include "Led.h"
|
||||||
|
#include "PowerMon.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include "power.h"
|
||||||
|
|
||||||
|
GpioVirtPin ledForceOn, ledBlink;
|
||||||
|
|
||||||
|
#if defined(LED_PIN)
|
||||||
|
// Most boards have a GPIO for LED control
|
||||||
|
static GpioHwPin ledRawHwPin(LED_PIN);
|
||||||
|
#else
|
||||||
|
static GpioVirtPin ledRawHwPin; // Dummy pin for no hardware
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if LED_STATE_ON == 0
|
||||||
|
static GpioVirtPin ledHwPin;
|
||||||
|
static GpioNotTransformer ledInverter(&ledHwPin, &ledRawHwPin);
|
||||||
|
#else
|
||||||
|
static GpioPin &ledHwPin = ledRawHwPin;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(HAS_PMU)
|
||||||
|
/**
|
||||||
|
* A GPIO controlled by the PMU
|
||||||
|
*/
|
||||||
|
class GpioPmuPin : public GpioPin
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void set(bool value)
|
||||||
|
{
|
||||||
|
if (pmu_found && PMU) {
|
||||||
|
// blink the axp led
|
||||||
|
PMU->setChargingLedMode(value ? XPOWERS_CHG_LED_ON : XPOWERS_CHG_LED_OFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ledPmuHwPin;
|
||||||
|
|
||||||
|
// In some cases we need to drive a PMU LED and a normal LED
|
||||||
|
static GpioSplitter ledFinalPin(&ledHwPin, &ledPmuHwPin);
|
||||||
|
#else
|
||||||
|
static GpioPin &ledFinalPin = ledHwPin;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_POWERMON
|
||||||
|
/**
|
||||||
|
* We monitor changes to the LED drive output because we use that as a sanity test in our power monitor stuff.
|
||||||
|
*/
|
||||||
|
class MonitoredLedPin : public GpioPin
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void set(bool value)
|
||||||
|
{
|
||||||
|
if (powerMon) {
|
||||||
|
if (value)
|
||||||
|
powerMon->setState(meshtastic_PowerMon_State_LED_On);
|
||||||
|
else
|
||||||
|
powerMon->clearState(meshtastic_PowerMon_State_LED_On);
|
||||||
|
}
|
||||||
|
ledFinalPin.set(value);
|
||||||
|
}
|
||||||
|
} monitoredLedPin;
|
||||||
|
#else
|
||||||
|
static GpioPin &monitoredLedPin = ledFinalPin;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static GpioBinaryTransformer ledForcer(&ledForceOn, &ledBlink, &monitoredLedPin, GpioBinaryTransformer::Or);
|
||||||
7
src/Led.h
Normal file
7
src/Led.h
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#include "GpioLogic.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ledForceOn and ledForceOff both override the normal ledBlinker behavior (which is controlled by main)
|
||||||
|
*/
|
||||||
|
extern GpioVirtPin ledForceOn, ledBlink;
|
||||||
209
src/Power.cpp
209
src/Power.cpp
@@ -1,14 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* @file Power.cpp
|
* @file Power.cpp
|
||||||
* @brief This file contains the implementation of the Power class, which is
|
* @brief This file contains the implementation of the Power class, which is responsible for managing power-related functionality
|
||||||
* responsible for managing power-related functionality of the device. It
|
* of the device. It includes battery level sensing, power management unit (PMU) control, and power state machine management. The
|
||||||
* includes battery level sensing, power management unit (PMU) control, and
|
* Power class is used by the main device class to manage power-related functionality.
|
||||||
* 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
|
* The file also includes implementations of various battery level sensors, such as the AnalogBatteryLevel class, which assumes
|
||||||
* as the AnalogBatteryLevel class, which assumes the battery voltage is
|
* the battery voltage is attached via a voltage-divider to an analog input.
|
||||||
* attached via a voltage-divider to an analog input.
|
|
||||||
*
|
*
|
||||||
* This file is part of the Meshtastic project.
|
* This file is part of the Meshtastic project.
|
||||||
* For more information, see: https://meshtastic.org/
|
* For more information, see: https://meshtastic.org/
|
||||||
@@ -22,7 +19,6 @@
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "meshUtils.h"
|
#include "meshUtils.h"
|
||||||
#include "power/PowerHAL.h"
|
|
||||||
#include "sleep.h"
|
#include "sleep.h"
|
||||||
|
|
||||||
#if defined(ARCH_PORTDUINO)
|
#if defined(ARCH_PORTDUINO)
|
||||||
@@ -175,12 +171,22 @@ Power *power;
|
|||||||
|
|
||||||
using namespace meshtastic;
|
using namespace meshtastic;
|
||||||
|
|
||||||
// NRF52 has AREF_VOLTAGE defined in architecture.h but
|
#ifndef AREF_VOLTAGE
|
||||||
// make sure it's included. If something is wrong with NRF52
|
#if defined(ARCH_NRF52)
|
||||||
// definition - compilation will fail on missing definition
|
/*
|
||||||
#if !defined(AREF_VOLTAGE) && !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
|
||||||
#define AREF_VOLTAGE 3.3
|
#define AREF_VOLTAGE 3.3
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this board has a battery level sensor, set this to a valid implementation
|
* If this board has a battery level sensor, set this to a valid implementation
|
||||||
@@ -227,8 +233,7 @@ static void battery_adcDisable()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple battery level sensor that assumes the battery voltage is attached
|
* A simple battery level sensor that assumes the battery voltage is attached via a voltage-divider to an analog input
|
||||||
* via a voltage-divider to an analog input
|
|
||||||
*/
|
*/
|
||||||
class AnalogBatteryLevel : public HasBatteryLevel
|
class AnalogBatteryLevel : public HasBatteryLevel
|
||||||
{
|
{
|
||||||
@@ -306,8 +311,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
|
|
||||||
#ifndef BATTERY_SENSE_SAMPLES
|
#ifndef BATTERY_SENSE_SAMPLES
|
||||||
#define BATTERY_SENSE_SAMPLES \
|
#define BATTERY_SENSE_SAMPLES \
|
||||||
15 // Set the number of samples, it has an effect of increasing sensitivity in
|
15 // Set the number of samples, it has an effect of increasing sensitivity in complex electromagnetic environment.
|
||||||
// complex electromagnetic environment.
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef BATTERY_PIN
|
#ifdef BATTERY_PIN
|
||||||
@@ -337,8 +341,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
battery_adcDisable();
|
battery_adcDisable();
|
||||||
|
|
||||||
if (!initial_read_done) {
|
if (!initial_read_done) {
|
||||||
// Flush the smoothing filter with an ADC reading, if the reading is
|
// Flush the smoothing filter with an ADC reading, if the reading is plausibly correct
|
||||||
// plausibly correct
|
|
||||||
if (scaled > last_read_value)
|
if (scaled > last_read_value)
|
||||||
last_read_value = scaled;
|
last_read_value = scaled;
|
||||||
initial_read_done = true;
|
initial_read_done = true;
|
||||||
@@ -347,8 +350,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF
|
last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF
|
||||||
}
|
}
|
||||||
|
|
||||||
// LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u",
|
// LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u", BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t)
|
||||||
// BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t) (last_read_value));
|
// (last_read_value));
|
||||||
}
|
}
|
||||||
return last_read_value;
|
return last_read_value;
|
||||||
#endif // BATTERY_PIN
|
#endif // BATTERY_PIN
|
||||||
@@ -417,8 +420,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
/**
|
/**
|
||||||
* return true if there is a battery installed in this unit
|
* 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
|
// if we have a integrated device with a battery, we can assume that the battery is always connected
|
||||||
// battery is always connected
|
|
||||||
#ifdef BATTERY_IMMUTABLE
|
#ifdef BATTERY_IMMUTABLE
|
||||||
virtual bool isBatteryConnect() override { return true; }
|
virtual bool isBatteryConnect() override { return true; }
|
||||||
#elif defined(ADC_V)
|
#elif defined(ADC_V)
|
||||||
@@ -439,10 +441,10 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
virtual bool isBatteryConnect() override { return getBatteryPercent() != -1; }
|
virtual bool isBatteryConnect() override { return getBatteryPercent() != -1; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// If we see a battery voltage higher than physics allows - assume charger is
|
/// If we see a battery voltage higher than physics allows - assume charger is pumping
|
||||||
/// pumping in power On some boards we don't have the power management chip
|
/// in power
|
||||||
/// (like AXPxxxx) so we use EXT_PWR_DETECT GPIO pin to detect external power
|
/// On some boards we don't have the power management chip (like AXPxxxx)
|
||||||
/// source
|
/// so we use EXT_PWR_DETECT GPIO pin to detect external power source
|
||||||
virtual bool isVbusIn() override
|
virtual bool isVbusIn() override
|
||||||
{
|
{
|
||||||
#ifdef EXT_PWR_DETECT
|
#ifdef EXT_PWR_DETECT
|
||||||
@@ -459,14 +461,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
}
|
}
|
||||||
// if it's not HIGH - check the battery
|
// if it's not HIGH - check the battery
|
||||||
#endif
|
#endif
|
||||||
// If we have an EXT_PWR_DETECT pin and it indicates no external power, believe it.
|
#elif defined(MUZI_BASE)
|
||||||
return false;
|
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
|
#endif
|
||||||
return getBattVoltage() > chargingVolt;
|
return getBattVoltage() > chargingVolt;
|
||||||
}
|
}
|
||||||
@@ -489,9 +485,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
#else
|
#else
|
||||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION)
|
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION)
|
||||||
if (hasINA()) {
|
if (hasINA()) {
|
||||||
// get current flow from INA sensor - negative value means power flowing
|
// get current flow from INA sensor - negative value means power flowing into the battery
|
||||||
// into the battery default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT
|
// default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT RESISTOR <--> INA_VIN- <--> LOAD
|
||||||
// RESISTOR <--> INA_VIN- <--> LOAD
|
|
||||||
LOG_DEBUG("Using INA on I2C addr 0x%x for charging detection", config.power.device_battery_ina_address);
|
LOG_DEBUG("Using INA on I2C addr 0x%x for charging detection", config.power.device_battery_ina_address);
|
||||||
#if defined(INA_CHARGING_DETECTION_INVERT)
|
#if defined(INA_CHARGING_DETECTION_INVERT)
|
||||||
return getINACurrent() > 0;
|
return getINACurrent() > 0;
|
||||||
@@ -507,8 +502,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// If we see a battery voltage higher than physics allows - assume charger is
|
/// If we see a battery voltage higher than physics allows - assume charger is pumping
|
||||||
/// pumping in power
|
/// in power
|
||||||
|
|
||||||
/// For heltecs with no battery connected, the measured voltage is 2204, so
|
/// 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)
|
// need to be higher than that, in this case is 2500mV (3000-500)
|
||||||
@@ -517,8 +512,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
const float noBatVolt = (OCV[NUM_OCV_POINTS - 1] - 500) * NUM_CELLS;
|
const float noBatVolt = (OCV[NUM_OCV_POINTS - 1] - 500) * NUM_CELLS;
|
||||||
// Start value from minimum voltage for the filter to not start from 0
|
// Start value from minimum voltage for the filter to not start from 0
|
||||||
// that could trigger some events.
|
// that could trigger some events.
|
||||||
// This value is over-written by the first ADC reading, it the voltage seems
|
// This value is over-written by the first ADC reading, it the voltage seems reasonable.
|
||||||
// reasonable.
|
|
||||||
bool initial_read_done = false;
|
bool initial_read_done = false;
|
||||||
float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS);
|
float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS);
|
||||||
uint32_t last_read_time_ms = 0;
|
uint32_t last_read_time_ms = 0;
|
||||||
@@ -660,8 +654,7 @@ bool Power::analogInit()
|
|||||||
#ifdef CONFIG_IDF_TARGET_ESP32S3
|
#ifdef CONFIG_IDF_TARGET_ESP32S3
|
||||||
// ESP32S3
|
// ESP32S3
|
||||||
else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) {
|
else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) {
|
||||||
LOG_INFO("ADC config based on Two Point values and fitting curve "
|
LOG_INFO("ADC config based on Two Point values and fitting curve coefficients stored in eFuse");
|
||||||
"coefficients stored in eFuse");
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
else {
|
else {
|
||||||
@@ -669,7 +662,13 @@ bool Power::analogInit()
|
|||||||
}
|
}
|
||||||
#endif // ARCH_ESP32
|
#endif // ARCH_ESP32
|
||||||
|
|
||||||
// NRF52 ADC init moved to powerHAL_init in nrf52 platform
|
#ifdef ARCH_NRF52
|
||||||
|
#ifdef VBAT_AR_INTERNAL
|
||||||
|
analogReference(VBAT_AR_INTERNAL);
|
||||||
|
#else
|
||||||
|
analogReference(AR_INTERNAL); // 3.6V
|
||||||
|
#endif
|
||||||
|
#endif // ARCH_NRF52
|
||||||
|
|
||||||
#ifndef ARCH_ESP32
|
#ifndef ARCH_ESP32
|
||||||
analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS);
|
analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS);
|
||||||
@@ -724,16 +723,6 @@ bool Power::setup()
|
|||||||
runASAP = true;
|
runASAP = true;
|
||||||
},
|
},
|
||||||
CHANGE);
|
CHANGE);
|
||||||
#endif
|
|
||||||
#ifdef EXT_CHRG_DETECT
|
|
||||||
attachInterrupt(
|
|
||||||
EXT_CHRG_DETECT,
|
|
||||||
[]() {
|
|
||||||
power->setIntervalFromNow(0);
|
|
||||||
runASAP = true;
|
|
||||||
BaseType_t higherWake = 0;
|
|
||||||
},
|
|
||||||
CHANGE);
|
|
||||||
#endif
|
#endif
|
||||||
enabled = found;
|
enabled = found;
|
||||||
low_voltage_counter = 0;
|
low_voltage_counter = 0;
|
||||||
@@ -780,8 +769,7 @@ void Power::reboot()
|
|||||||
HAL_NVIC_SystemReset();
|
HAL_NVIC_SystemReset();
|
||||||
#else
|
#else
|
||||||
rebootAtMsec = -1;
|
rebootAtMsec = -1;
|
||||||
LOG_WARN("FIXME implement reboot for this platform. Note that some settings "
|
LOG_WARN("FIXME implement reboot for this platform. Note that some settings require a restart to be applied");
|
||||||
"require a restart to be applied");
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -791,12 +779,9 @@ void Power::shutdown()
|
|||||||
#if HAS_SCREEN
|
#if HAS_SCREEN
|
||||||
if (screen) {
|
if (screen) {
|
||||||
#ifdef T_DECK_PRO
|
#ifdef T_DECK_PRO
|
||||||
screen->showSimpleBanner("Device is powered off.\nConnect USB to start!",
|
screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button
|
||||||
0); // T-Deck Pro has no power button
|
|
||||||
#elif defined(USE_EINK)
|
#elif defined(USE_EINK)
|
||||||
screen->showSimpleBanner("Shutting Down...",
|
screen->showSimpleBanner("Shutting Down...", 2250); // dismiss after 3 seconds to avoid the banner on the sleep screen
|
||||||
2250); // dismiss after 3 seconds to avoid the
|
|
||||||
// banner on the sleep screen
|
|
||||||
#else
|
#else
|
||||||
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
|
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
|
||||||
#endif
|
#endif
|
||||||
@@ -818,9 +803,6 @@ void Power::shutdown()
|
|||||||
#endif
|
#endif
|
||||||
#ifdef PIN_LED3
|
#ifdef PIN_LED3
|
||||||
ledOff(PIN_LED3);
|
ledOff(PIN_LED3);
|
||||||
#endif
|
|
||||||
#ifdef LED_NOTIFICATION
|
|
||||||
ledOff(LED_NOTIFICATION);
|
|
||||||
#endif
|
#endif
|
||||||
doDeepSleep(DELAY_FOREVER, true, true);
|
doDeepSleep(DELAY_FOREVER, true, true);
|
||||||
#elif defined(ARCH_PORTDUINO)
|
#elif defined(ARCH_PORTDUINO)
|
||||||
@@ -838,8 +820,7 @@ void Power::readPowerStatus()
|
|||||||
int32_t batteryVoltageMv = -1; // Assume unknown
|
int32_t batteryVoltageMv = -1; // Assume unknown
|
||||||
int8_t batteryChargePercent = -1;
|
int8_t batteryChargePercent = -1;
|
||||||
OptionalBool usbPowered = OptUnknown;
|
OptionalBool usbPowered = OptUnknown;
|
||||||
OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM
|
OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM code doesn't run every time
|
||||||
// code doesn't run every time
|
|
||||||
OptionalBool isChargingNow = OptUnknown;
|
OptionalBool isChargingNow = OptUnknown;
|
||||||
|
|
||||||
if (batteryLevel) {
|
if (batteryLevel) {
|
||||||
@@ -852,10 +833,9 @@ void Power::readPowerStatus()
|
|||||||
if (batteryLevel->getBatteryPercent() >= 0) {
|
if (batteryLevel->getBatteryPercent() >= 0) {
|
||||||
batteryChargePercent = batteryLevel->getBatteryPercent();
|
batteryChargePercent = batteryLevel->getBatteryPercent();
|
||||||
} else {
|
} else {
|
||||||
// If the AXP192 returns a percentage less than 0, the feature is either
|
// If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error
|
||||||
// not supported or there is an error In that case, we compute an
|
// In that case, we compute an estimate of the charge percent based on open circuit voltage table defined
|
||||||
// estimate of the charge percent based on open circuit voltage table
|
// in power.h
|
||||||
// defined in power.h
|
|
||||||
batteryChargePercent = clamp((int)(((batteryVoltageMv - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS)) * 1e2) /
|
batteryChargePercent = clamp((int)(((batteryVoltageMv - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS)) * 1e2) /
|
||||||
((OCV[0] * NUM_CELLS) - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS))),
|
((OCV[0] * NUM_CELLS) - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS))),
|
||||||
0, 100);
|
0, 100);
|
||||||
@@ -863,12 +843,12 @@ void Power::readPowerStatus()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way
|
// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way better instead to make a Nrf52IsUsbPowered subclass
|
||||||
// better instead to make a Nrf52IsUsbPowered subclass (which shares a
|
// (which shares a superclass with the BatteryLevel stuff)
|
||||||
// superclass with the BatteryLevel stuff) that just provides a few methods. But
|
// that just provides a few methods. But in the interest of fixing this bug I'm going to follow current
|
||||||
// in the interest of fixing this bug I'm going to follow current practice.
|
// practice.
|
||||||
#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates
|
#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates the power states. Takes 20 seconds or so to detect
|
||||||
// the power states. Takes 20 seconds or so to detect changes.
|
// changes.
|
||||||
|
|
||||||
nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get();
|
nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get();
|
||||||
// LOG_DEBUG("NRF Power %d", nrf_usb_state);
|
// LOG_DEBUG("NRF Power %d", nrf_usb_state);
|
||||||
@@ -942,9 +922,8 @@ void Power::readPowerStatus()
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// If we have a battery at all and it is less than 0%, force deep sleep if we
|
// 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
|
||||||
// have more than 10 low readings in a row. NOTE: min LiIon/LiPo voltage
|
// a row. NOTE: min LiIon/LiPo voltage is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough.
|
||||||
// is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough.
|
|
||||||
//
|
//
|
||||||
|
|
||||||
if (batteryLevel && powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) {
|
if (batteryLevel && powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) {
|
||||||
@@ -966,8 +945,8 @@ int32_t Power::runOnce()
|
|||||||
readPowerStatus();
|
readPowerStatus();
|
||||||
|
|
||||||
#ifdef HAS_PMU
|
#ifdef HAS_PMU
|
||||||
// WE no longer use the IRQ line to wake the CPU (due to false wakes from
|
// WE no longer use the IRQ line to wake the CPU (due to false wakes from sleep), but we do poll
|
||||||
// sleep), but we do poll the IRQ status by reading the registers over I2C
|
// the IRQ status by reading the registers over I2C
|
||||||
if (PMU) {
|
if (PMU) {
|
||||||
|
|
||||||
PMU->getIrqStatus();
|
PMU->getIrqStatus();
|
||||||
@@ -1009,8 +988,7 @@ int32_t Power::runOnce()
|
|||||||
PMU->clearIrqStatus();
|
PMU->clearIrqStatus();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// Only read once every 20 seconds once the power status for the app has been
|
// Only read once every 20 seconds once the power status for the app has been initialized
|
||||||
// initialized
|
|
||||||
return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME;
|
return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1018,12 +996,10 @@ int32_t Power::runOnce()
|
|||||||
* Init the power manager chip
|
* Init the power manager chip
|
||||||
*
|
*
|
||||||
* axp192 power
|
* axp192 power
|
||||||
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose
|
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
|
||||||
comms to the axp192 because the OLED and the axp192 share the same i2c bus,
|
axp192 share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this
|
||||||
instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max ->
|
on!) LDO1 30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of
|
||||||
ESP32 (keep this on!) LDO1 30mA -> charges GPS backup battery // charges the
|
days), can not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
|
||||||
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()
|
bool Power::axpChipInit()
|
||||||
@@ -1068,10 +1044,9 @@ bool Power::axpChipInit()
|
|||||||
|
|
||||||
if (!PMU) {
|
if (!PMU) {
|
||||||
/*
|
/*
|
||||||
* In XPowersLib, if the XPowersAXPxxx object is released, Wire.end() will
|
* In XPowersLib, if the XPowersAXPxxx object is released, Wire.end() will be called at the same time.
|
||||||
* be called at the same time. In order not to affect other devices, if the
|
* In order not to affect other devices, if the initialization of the PMU fails, Wire needs to be re-initialized once,
|
||||||
* initialization of the PMU fails, Wire needs to be re-initialized once, if
|
* if there are multiple devices sharing the bus.
|
||||||
* there are multiple devices sharing the bus.
|
|
||||||
* * */
|
* * */
|
||||||
#ifndef PMU_USE_WIRE1
|
#ifndef PMU_USE_WIRE1
|
||||||
w->begin(I2C_SDA, I2C_SCL);
|
w->begin(I2C_SDA, I2C_SCL);
|
||||||
@@ -1088,8 +1063,8 @@ bool Power::axpChipInit()
|
|||||||
PMU->enablePowerOutput(XPOWERS_LDO2);
|
PMU->enablePowerOutput(XPOWERS_LDO2);
|
||||||
|
|
||||||
// oled module power channel,
|
// oled module power channel,
|
||||||
// disable it will cause abnormal communication between boot and AXP power
|
// disable it will cause abnormal communication between boot and AXP power supply,
|
||||||
// supply, do not turn it off
|
// do not turn it off
|
||||||
PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300);
|
PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300);
|
||||||
// enable oled power
|
// enable oled power
|
||||||
PMU->enablePowerOutput(XPOWERS_DCDC1);
|
PMU->enablePowerOutput(XPOWERS_DCDC1);
|
||||||
@@ -1116,8 +1091,7 @@ bool Power::axpChipInit()
|
|||||||
PMU->setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2);
|
PMU->setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2);
|
||||||
} else if (PMU->getChipModel() == XPOWERS_AXP2101) {
|
} else if (PMU->getChipModel() == XPOWERS_AXP2101) {
|
||||||
|
|
||||||
/*The alternative version of T-Beam 1.1 differs from T-Beam V1.1 in that it
|
/*The alternative version of T-Beam 1.1 differs from T-Beam V1.1 in that it uses an AXP2101 power chip*/
|
||||||
* uses an AXP2101 power chip*/
|
|
||||||
if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) {
|
if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) {
|
||||||
// Unuse power channel
|
// Unuse power channel
|
||||||
PMU->disablePowerOutput(XPOWERS_DCDC2);
|
PMU->disablePowerOutput(XPOWERS_DCDC2);
|
||||||
@@ -1152,8 +1126,8 @@ bool Power::axpChipInit()
|
|||||||
// t-beam s3 core
|
// t-beam s3 core
|
||||||
/**
|
/**
|
||||||
* gnss module power channel
|
* gnss module power channel
|
||||||
* The default ALDO4 is off, you need to turn on the GNSS power first,
|
* The default ALDO4 is off, you need to turn on the GNSS power first, otherwise it will be invalid during
|
||||||
* otherwise it will be invalid during initialization
|
* initialization
|
||||||
*/
|
*/
|
||||||
PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300);
|
PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300);
|
||||||
PMU->enablePowerOutput(XPOWERS_ALDO4);
|
PMU->enablePowerOutput(XPOWERS_ALDO4);
|
||||||
@@ -1203,8 +1177,7 @@ bool Power::axpChipInit()
|
|||||||
// disable all axp chip interrupt
|
// disable all axp chip interrupt
|
||||||
PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
|
PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
|
||||||
|
|
||||||
// Set the constant current charging current of AXP2101, temporarily use
|
// Set the constant current charging current of AXP2101, temporarily use 500mA by default
|
||||||
// 500mA by default
|
|
||||||
PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA);
|
PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA);
|
||||||
|
|
||||||
// Set up the charging voltage
|
// Set up the charging voltage
|
||||||
@@ -1270,12 +1243,11 @@ bool Power::axpChipInit()
|
|||||||
PMU->getPowerChannelVoltage(XPOWERS_BLDO2));
|
PMU->getPowerChannelVoltage(XPOWERS_BLDO2));
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can safely ignore this approach for most (or all) boards because MCU
|
// We can safely ignore this approach for most (or all) boards because MCU turned off
|
||||||
// turned off earlier than battery discharged to 2.6V.
|
// earlier than battery discharged to 2.6V.
|
||||||
//
|
//
|
||||||
// Unfortunately for now we can't use this killswitch for RAK4630-based boards
|
// Unfortanly for now we can't use this killswitch for RAK4630-based boards because they have a bug with
|
||||||
// because they have a bug with battery voltage measurement. Probably it
|
// battery voltage measurement. Probably it sometimes drops to low values.
|
||||||
// sometimes drops to low values.
|
|
||||||
#ifndef RAK4630
|
#ifndef RAK4630
|
||||||
// Set PMU shutdown voltage at 2.6V to maximize battery utilization
|
// Set PMU shutdown voltage at 2.6V to maximize battery utilization
|
||||||
PMU->setSysPowerDownVoltage(2600);
|
PMU->setSysPowerDownVoltage(2600);
|
||||||
@@ -1294,12 +1266,10 @@ bool Power::axpChipInit()
|
|||||||
attachInterrupt(
|
attachInterrupt(
|
||||||
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
||||||
|
|
||||||
// we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ
|
// we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ because it occurs repeatedly while there is
|
||||||
// because it occurs repeatedly while there is no battery also it could cause
|
// no battery also it could cause inadvertent waking from light sleep just because the battery filled
|
||||||
// inadvertent waking from light sleep just because the battery filled we
|
// we don't look for AXPXXX_BATT_REMOVED_IRQ because it occurs repeatedly while no battery installed
|
||||||
// don't look for AXPXXX_BATT_REMOVED_IRQ because it occurs repeatedly while
|
// we don't look at AXPXXX_VBUS_REMOVED_IRQ because we don't have anything hooked to vbus
|
||||||
// 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->enableIRQ(pmuIrqMask);
|
||||||
|
|
||||||
PMU->clearIrqStatus();
|
PMU->clearIrqStatus();
|
||||||
@@ -1415,8 +1385,8 @@ class LipoCharger : public HasBatteryLevel
|
|||||||
bool result = PPM->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR);
|
bool result = PPM->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR);
|
||||||
if (result) {
|
if (result) {
|
||||||
LOG_INFO("PPM BQ25896 init succeeded");
|
LOG_INFO("PPM BQ25896 init succeeded");
|
||||||
// Set the minimum operating voltage. Below this voltage, the PPM will
|
// Set the minimum operating voltage. Below this voltage, the PPM will protect
|
||||||
// protect PPM->setSysPowerDownVoltage(3100);
|
// PPM->setSysPowerDownVoltage(3100);
|
||||||
|
|
||||||
// Set input current limit, default is 500mA
|
// Set input current limit, default is 500mA
|
||||||
// PPM->setInputCurrentLimit(800);
|
// PPM->setInputCurrentLimit(800);
|
||||||
@@ -1439,8 +1409,7 @@ class LipoCharger : public HasBatteryLevel
|
|||||||
PPM->enableMeasure();
|
PPM->enableMeasure();
|
||||||
|
|
||||||
// Turn on charging function
|
// Turn on charging function
|
||||||
// If there is no battery connected, do not turn on the charging
|
// If there is no battery connected, do not turn on the charging function
|
||||||
// function
|
|
||||||
PPM->enableCharge();
|
PPM->enableCharge();
|
||||||
} else {
|
} else {
|
||||||
LOG_WARN("PPM BQ25896 init failed");
|
LOG_WARN("PPM BQ25896 init failed");
|
||||||
@@ -1475,8 +1444,7 @@ class LipoCharger : public HasBatteryLevel
|
|||||||
virtual int getBatteryPercent() override
|
virtual int getBatteryPercent() override
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
// return bq->getChargePercent(); // don't use BQ27220 for battery percent,
|
// return bq->getChargePercent(); // don't use BQ27220 for battery percent, it is not calibrated
|
||||||
// it is not calibrated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1598,8 +1566,7 @@ bool Power::meshSolarInit()
|
|||||||
|
|
||||||
#else
|
#else
|
||||||
/**
|
/**
|
||||||
* The meshSolar battery level sensor is unavailable - default to
|
* The meshSolar battery level sensor is unavailable - default to AnalogBatteryLevel
|
||||||
* AnalogBatteryLevel
|
|
||||||
*/
|
*/
|
||||||
bool Power::meshSolarInit()
|
bool Power::meshSolarInit()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,13 +9,13 @@
|
|||||||
*/
|
*/
|
||||||
#include "PowerFSM.h"
|
#include "PowerFSM.h"
|
||||||
#include "Default.h"
|
#include "Default.h"
|
||||||
|
#include "Led.h"
|
||||||
#include "MeshService.h"
|
#include "MeshService.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "PowerMon.h"
|
#include "PowerMon.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "graphics/Screen.h"
|
#include "graphics/Screen.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "modules/StatusLEDModule.h"
|
|
||||||
#include "sleep.h"
|
#include "sleep.h"
|
||||||
#include "target_specific.h"
|
#include "target_specific.h"
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ static void lsIdle()
|
|||||||
uint32_t sleepTime = SLEEP_TIME;
|
uint32_t sleepTime = SLEEP_TIME;
|
||||||
|
|
||||||
powerMon->setState(meshtastic_PowerMon_State_CPU_LightSleep);
|
powerMon->setState(meshtastic_PowerMon_State_CPU_LightSleep);
|
||||||
statusLEDModule->setPowerLED(false);
|
ledBlink.set(false); // Never leave led on while in light sleep
|
||||||
esp_sleep_source_t wakeCause2 = doLightSleep(sleepTime * 1000LL);
|
esp_sleep_source_t wakeCause2 = doLightSleep(sleepTime * 1000LL);
|
||||||
powerMon->clearState(meshtastic_PowerMon_State_CPU_LightSleep);
|
powerMon->clearState(meshtastic_PowerMon_State_CPU_LightSleep);
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ static void lsIdle()
|
|||||||
case ESP_SLEEP_WAKEUP_TIMER:
|
case ESP_SLEEP_WAKEUP_TIMER:
|
||||||
// Normal case: timer expired, we should just go back to sleep ASAP
|
// Normal case: timer expired, we should just go back to sleep ASAP
|
||||||
|
|
||||||
statusLEDModule->setPowerLED(true);
|
ledBlink.set(true); // briefly turn on led
|
||||||
wakeCause2 = doLightSleep(100); // leave led on for 1ms
|
wakeCause2 = doLightSleep(100); // leave led on for 1ms
|
||||||
|
|
||||||
secsSlept += sleepTime;
|
secsSlept += sleepTime;
|
||||||
@@ -146,7 +146,7 @@ static void lsIdle()
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Time to stop sleeping!
|
// Time to stop sleeping!
|
||||||
statusLEDModule->setPowerLED(false);
|
ledBlink.set(false);
|
||||||
LOG_INFO("Reached ls_secs, service loop()");
|
LOG_INFO("Reached ls_secs, service loop()");
|
||||||
powerFSM.trigger(EVENT_WAKE_TIMER);
|
powerFSM.trigger(EVENT_WAKE_TIMER);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,10 +155,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Default system gain to 0 if not defined
|
// Default system gain to 0 if not defined
|
||||||
#ifndef NUM_PA_POINTS
|
|
||||||
#define NUM_PA_POINTS 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef TX_GAIN_LORA
|
#ifndef TX_GAIN_LORA
|
||||||
#define TX_GAIN_LORA 0
|
#define TX_GAIN_LORA 0
|
||||||
#endif
|
#endif
|
||||||
@@ -390,6 +386,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#ifndef HAS_RADIO
|
#ifndef HAS_RADIO
|
||||||
#define HAS_RADIO 0
|
#define HAS_RADIO 0
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef HAS_RTC
|
||||||
|
#define HAS_RTC 0
|
||||||
|
#endif
|
||||||
#ifndef HAS_CPU_SHUTDOWN
|
#ifndef HAS_CPU_SHUTDOWN
|
||||||
#define HAS_CPU_SHUTDOWN 0
|
#define HAS_CPU_SHUTDOWN 0
|
||||||
#endif
|
#endif
|
||||||
@@ -425,16 +424,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#define HAS_RGB_LED
|
#define HAS_RGB_LED
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef LED_STATE_OFF
|
||||||
|
#define LED_STATE_OFF 0
|
||||||
|
#endif
|
||||||
#ifndef LED_STATE_ON
|
#ifndef LED_STATE_ON
|
||||||
#define LED_STATE_ON 1
|
#define LED_STATE_ON 1
|
||||||
#endif
|
#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
|
// default mapping of pins
|
||||||
#if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN)
|
#if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN)
|
||||||
|
|||||||
@@ -905,12 +905,6 @@ void GPS::writePinStandby(bool standby)
|
|||||||
// Write and log
|
// Write and log
|
||||||
pinMode(PIN_GPS_STANDBY, OUTPUT);
|
pinMode(PIN_GPS_STANDBY, OUTPUT);
|
||||||
digitalWrite(PIN_GPS_STANDBY, val);
|
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
|
#ifdef GPS_DEBUG
|
||||||
LOG_DEBUG("Pin STANDBY %s", val == HIGH ? "HI" : "LOW");
|
LOG_DEBUG("Pin STANDBY %s", val == HIGH ? "HI" : "LOW");
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -276,7 +276,11 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
|
|||||||
settimeofday(tv, NULL);
|
settimeofday(tv, NULL);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// nrf52 doesn't have a readable RTC (yet - software not written)
|
||||||
|
#if HAS_RTC
|
||||||
readFromRTC();
|
readFromRTC();
|
||||||
|
#endif
|
||||||
|
|
||||||
return RTCSetResultSuccess;
|
return RTCSetResultSuccess;
|
||||||
} else {
|
} else {
|
||||||
return RTCSetResultNotSet; // RTC was already set with a higher quality time
|
return RTCSetResultNotSet; // RTC was already set with a higher quality time
|
||||||
@@ -393,7 +397,7 @@ uint32_t getValidTime(RTCQuality minQuality, bool local)
|
|||||||
return (currentQuality >= minQuality) ? getTime(local) : 0;
|
return (currentQuality >= minQuality) ? getTime(local) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
time_t gm_mktime(const struct tm *tm)
|
time_t gm_mktime(struct tm *tm)
|
||||||
{
|
{
|
||||||
#if !MESHTASTIC_EXCLUDE_TZ
|
#if !MESHTASTIC_EXCLUDE_TZ
|
||||||
time_t result = 0;
|
time_t result = 0;
|
||||||
@@ -409,8 +413,8 @@ time_t gm_mktime(const struct tm *tm)
|
|||||||
days_before_this_year -= 719162; // (1969 * 365 + 1969 / 4 - 1969 / 100 + 1969 / 400);
|
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.
|
// Now, within this tm->year, compute the days *before* this tm->month starts.
|
||||||
static const int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year
|
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
|
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 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)) {
|
if (tm->tm_mon >= 2 && (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) {
|
||||||
@@ -431,7 +435,6 @@ time_t gm_mktime(const struct tm *tm)
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
#else
|
#else
|
||||||
struct tm tmCopy = *tm;
|
return mktime(tm);
|
||||||
return mktime(&tmCopy);
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ uint32_t getValidTime(RTCQuality minQuality, bool local = false);
|
|||||||
|
|
||||||
RTCSetResult readFromRTC();
|
RTCSetResult readFromRTC();
|
||||||
|
|
||||||
time_t gm_mktime(const struct tm *tm);
|
time_t gm_mktime(struct tm *tm);
|
||||||
|
|
||||||
#define SEC_PER_DAY 86400
|
#define SEC_PER_DAY 86400
|
||||||
#define SEC_PER_HOUR 3600
|
#define SEC_PER_HOUR 3600
|
||||||
|
|||||||
@@ -1731,26 +1731,6 @@ int Screen::handleInputEvent(const InputEvent *event)
|
|||||||
showFrame(FrameDirection::PREVIOUS);
|
showFrame(FrameDirection::PREVIOUS);
|
||||||
} else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) {
|
} else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) {
|
||||||
showFrame(FrameDirection::NEXT);
|
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) {
|
} else if (event->inputEvent == INPUT_BROKER_UP_LONG) {
|
||||||
// Long press up button for fast frame switching
|
// Long press up button for fast frame switching
|
||||||
showPrevFrame();
|
showPrevFrame();
|
||||||
|
|||||||
@@ -266,8 +266,52 @@ void menuHandler::FrequencySlotPicker()
|
|||||||
|
|
||||||
// Calculate number of channels (copied from RadioInterface::applyModemConfig())
|
// Calculate number of channels (copied from RadioInterface::applyModemConfig())
|
||||||
meshtastic_Config_LoRaConfig &loraConfig = config.lora;
|
meshtastic_Config_LoRaConfig &loraConfig = config.lora;
|
||||||
double bw = loraConfig.use_preset ? modemPresetToBwKHz(loraConfig.modem_preset, myRegion->wideLora)
|
double bw = loraConfig.bandwidth;
|
||||||
: bwCodeToKHz(loraConfig.bandwidth);
|
if (loraConfig.use_preset) {
|
||||||
|
switch (loraConfig.modem_preset) {
|
||||||
|
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
|
||||||
|
bw = (myRegion->wideLora) ? 1625.0 : 500;
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST:
|
||||||
|
bw = (myRegion->wideLora) ? 812.5 : 250;
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW:
|
||||||
|
bw = (myRegion->wideLora) ? 812.5 : 250;
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST:
|
||||||
|
bw = (myRegion->wideLora) ? 812.5 : 250;
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW:
|
||||||
|
bw = (myRegion->wideLora) ? 812.5 : 250;
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO:
|
||||||
|
bw = (myRegion->wideLora) ? 1625.0 : 500;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
bw = (myRegion->wideLora) ? 812.5 : 250;
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
|
||||||
|
bw = (myRegion->wideLora) ? 406.25 : 125;
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW:
|
||||||
|
bw = (myRegion->wideLora) ? 406.25 : 125;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t numChannels = 0;
|
uint32_t numChannels = 0;
|
||||||
if (myRegion) {
|
if (myRegion) {
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ struct ScreenColor {
|
|||||||
uint8_t b;
|
uint8_t b;
|
||||||
bool useVariant;
|
bool useVariant;
|
||||||
|
|
||||||
explicit ScreenColor(uint8_t rIn = 0, uint8_t gIn = 0, uint8_t bIn = 0, bool variantIn = false)
|
ScreenColor(uint8_t rIn = 0, uint8_t gIn = 0, uint8_t bIn = 0, bool variantIn = false)
|
||||||
: r(rIn), g(gIn), b(bIn), useVariant(variantIn)
|
: r(rIn), g(gIn), b(bIn), useVariant(variantIn)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "MessageStore.h"
|
#include "MessageStore.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "UIRenderer.h"
|
#include "UIRenderer.h"
|
||||||
|
#include "configuration.h"
|
||||||
#include "gps/RTC.h"
|
#include "gps/RTC.h"
|
||||||
#include "graphics/Screen.h"
|
#include "graphics/Screen.h"
|
||||||
#include "graphics/ScreenFonts.h"
|
#include "graphics/ScreenFonts.h"
|
||||||
@@ -19,6 +20,7 @@
|
|||||||
|
|
||||||
// External declarations
|
// External declarations
|
||||||
extern bool hasUnreadMessage;
|
extern bool hasUnreadMessage;
|
||||||
|
extern meshtastic_DeviceState devicestate;
|
||||||
extern graphics::Screen *screen;
|
extern graphics::Screen *screen;
|
||||||
|
|
||||||
using graphics::Emote;
|
using graphics::Emote;
|
||||||
@@ -47,7 +49,7 @@ static inline size_t utf8CharLen(uint8_t c)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove variation selectors (FE0F) and skin tone modifiers from emoji so they match your labels
|
// Remove variation selectors (FE0F) and skin tone modifiers from emoji so they match your labels
|
||||||
static std::string normalizeEmoji(const std::string &s)
|
std::string normalizeEmoji(const std::string &s)
|
||||||
{
|
{
|
||||||
std::string out;
|
std::string out;
|
||||||
for (size_t i = 0; i < s.size();) {
|
for (size_t i = 0; i < s.size();) {
|
||||||
@@ -80,7 +82,6 @@ uint32_t pauseStart = 0;
|
|||||||
bool waitingToReset = false;
|
bool waitingToReset = false;
|
||||||
bool scrollStarted = false;
|
bool scrollStarted = false;
|
||||||
static bool didReset = false;
|
static bool didReset = false;
|
||||||
static constexpr int MESSAGE_BLOCK_GAP = 6;
|
|
||||||
|
|
||||||
void scrollUp()
|
void scrollUp()
|
||||||
{
|
{
|
||||||
@@ -110,6 +111,22 @@ void scrollDown()
|
|||||||
|
|
||||||
void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string &line, const Emote *emotes, int emoteCount)
|
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;
|
int cursorX = x;
|
||||||
const int fontHeight = FONT_HEIGHT_SMALL;
|
const int fontHeight = FONT_HEIGHT_SMALL;
|
||||||
|
|
||||||
@@ -186,7 +203,8 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string
|
|||||||
|
|
||||||
// Render the emote (if found)
|
// Render the emote (if found)
|
||||||
if (matchedEmote && i == nextEmotePos) {
|
if (matchedEmote && i == nextEmotePos) {
|
||||||
int iconY = y + (lineHeight - matchedEmote->height) / 2;
|
// Vertically center emote relative to font baseline (not just midline)
|
||||||
|
int iconY = fontY + (fontHeight - matchedEmote->height) / 2;
|
||||||
display->drawXbm(cursorX, iconY, matchedEmote->width, matchedEmote->height, matchedEmote->bitmap);
|
display->drawXbm(cursorX, iconY, matchedEmote->width, matchedEmote->height, matchedEmote->bitmap);
|
||||||
cursorX += matchedEmote->width + 1;
|
cursorX += matchedEmote->width + 1;
|
||||||
i += emojiLen;
|
i += emojiLen;
|
||||||
@@ -405,63 +423,6 @@ static inline int getRenderedLineWidth(OLEDDisplay *display, const std::string &
|
|||||||
return totalWidth;
|
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)
|
static void drawMessageScrollbar(OLEDDisplay *display, int visibleHeight, int totalHeight, int scrollOffset, int startY)
|
||||||
{
|
{
|
||||||
if (totalHeight <= visibleHeight)
|
if (totalHeight <= visibleHeight)
|
||||||
@@ -521,14 +482,9 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
constexpr int LEFT_MARGIN = 2;
|
constexpr int LEFT_MARGIN = 2;
|
||||||
constexpr int RIGHT_MARGIN = 2;
|
constexpr int RIGHT_MARGIN = 2;
|
||||||
constexpr int SCROLLBAR_WIDTH = 3;
|
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;
|
|
||||||
|
|
||||||
// Derived widths
|
const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN;
|
||||||
const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - (BUBBLE_PAD_X * 2);
|
|
||||||
const int rightTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - SCROLLBAR_WIDTH;
|
const int rightTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - SCROLLBAR_WIDTH;
|
||||||
|
|
||||||
// Title string depending on mode
|
// Title string depending on mode
|
||||||
@@ -591,28 +547,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
char chanType[32] = "";
|
char chanType[32] = "";
|
||||||
if (currentMode == ThreadMode::ALL) {
|
if (currentMode == ThreadMode::ALL) {
|
||||||
if (m.dest == NODENUM_BROADCAST) {
|
if (m.dest == NODENUM_BROADCAST) {
|
||||||
const char *name = channels.getName(m.channelIndex);
|
snprintf(chanType, sizeof(chanType), "#%s", 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 {
|
} else {
|
||||||
snprintf(chanType, sizeof(chanType), "(DM)");
|
snprintf(chanType, sizeof(chanType), "(DM)");
|
||||||
}
|
}
|
||||||
@@ -679,8 +614,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Shrink Sender name if needed
|
// Shrink Sender name if needed
|
||||||
int availWidth = (mine ? rightTextWidth : leftTextWidth) - display->getStringWidth(timeBuf) -
|
int availWidth = SCREEN_WIDTH - display->getStringWidth(timeBuf) - display->getStringWidth(chanType) -
|
||||||
display->getStringWidth(chanType) - display->getStringWidth(" @...");
|
display->getStringWidth(" @...") - 10;
|
||||||
if (availWidth < 0)
|
if (availWidth < 0)
|
||||||
availWidth = 0;
|
availWidth = 0;
|
||||||
|
|
||||||
@@ -732,8 +667,6 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
cachedLines = allLines;
|
cachedLines = allLines;
|
||||||
cachedHeights = calculateLineHeights(cachedLines, emotes, isHeader);
|
cachedHeights = calculateLineHeights(cachedLines, emotes, isHeader);
|
||||||
|
|
||||||
std::vector<MessageBlock> blocks = buildMessageBlocks(isHeader, isMine);
|
|
||||||
|
|
||||||
// Scrolling logic (unchanged)
|
// Scrolling logic (unchanged)
|
||||||
int totalHeight = 0;
|
int totalHeight = 0;
|
||||||
for (size_t i = 0; i < cachedHeights.size(); ++i)
|
for (size_t i = 0; i < cachedHeights.size(); ++i)
|
||||||
@@ -781,133 +714,12 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
|
|
||||||
int finalScroll = (int)scrollY;
|
int finalScroll = (int)scrollY;
|
||||||
int yOffset = -finalScroll + getTextPositions(display)[1];
|
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 - 1, 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 + 2, topY - 1, 1, 1);
|
|
||||||
display->drawRect(x1 - bubbleW + 2, 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
|
// Render visible lines
|
||||||
int lineY = yOffset;
|
|
||||||
for (size_t i = 0; i < cachedLines.size(); ++i) {
|
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 (lineY > -cachedHeights[i] && lineY < scrollBottom) {
|
||||||
if (isHeader[i]) {
|
if (isHeader[i]) {
|
||||||
@@ -916,28 +728,14 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
int headerX;
|
int headerX;
|
||||||
if (isMine[i]) {
|
if (isMine[i]) {
|
||||||
// push header left to avoid overlap with scrollbar
|
// push header left to avoid overlap with scrollbar
|
||||||
headerX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - w - BUBBLE_TEXT_INDENT;
|
headerX = SCREEN_WIDTH - w - SCROLLBAR_WIDTH - RIGHT_MARGIN;
|
||||||
if (headerX < LEFT_MARGIN)
|
if (headerX < LEFT_MARGIN)
|
||||||
headerX = LEFT_MARGIN;
|
headerX = LEFT_MARGIN;
|
||||||
} else {
|
} else {
|
||||||
headerX = x + BUBBLE_PAD_X + BUBBLE_TEXT_INDENT;
|
headerX = x;
|
||||||
}
|
}
|
||||||
display->drawString(headerX, lineY, cachedLines[i].c_str());
|
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
|
// Draw ACK/NACK mark for our own messages
|
||||||
if (isMine[i]) {
|
if (isMine[i]) {
|
||||||
int markX = headerX - 10;
|
int markX = headerX - 10;
|
||||||
@@ -955,28 +753,32 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
// AckStatus::NONE → show nothing
|
// 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 {
|
} else {
|
||||||
// Render message line
|
// Render message line
|
||||||
if (isMine[i]) {
|
if (isMine[i]) {
|
||||||
// Calculate actual rendered width including emotes
|
// Calculate actual rendered width including emotes
|
||||||
int renderedWidth = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes);
|
int renderedWidth = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes);
|
||||||
int rightX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - renderedWidth - BUBBLE_TEXT_INDENT;
|
int rightX = SCREEN_WIDTH - renderedWidth - SCROLLBAR_WIDTH - RIGHT_MARGIN;
|
||||||
if (rightX < LEFT_MARGIN)
|
if (rightX < LEFT_MARGIN)
|
||||||
rightX = LEFT_MARGIN;
|
rightX = LEFT_MARGIN;
|
||||||
|
|
||||||
drawStringWithEmotes(display, rightX, lineY, cachedLines[i], emotes, numEmotes);
|
drawStringWithEmotes(display, rightX, lineY, cachedLines[i], emotes, numEmotes);
|
||||||
} else {
|
} else {
|
||||||
drawStringWithEmotes(display, x + BUBBLE_PAD_X + BUBBLE_TEXT_INDENT, lineY, cachedLines[i], emotes,
|
drawStringWithEmotes(display, x, lineY, cachedLines[i], emotes, numEmotes);
|
||||||
numEmotes);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lineY += cachedHeights[i];
|
|
||||||
}
|
}
|
||||||
|
int totalContentHeight = totalHeight;
|
||||||
|
int visibleHeight = usableHeight;
|
||||||
|
|
||||||
// Draw scrollbar
|
// Draw scrollbar
|
||||||
drawMessageScrollbar(display, usableHeight, totalHeight, finalScroll, getTextPositions(display)[1]);
|
drawMessageScrollbar(display, visibleHeight, totalContentHeight, finalScroll, getTextPositions(display)[1]);
|
||||||
graphics::drawCommonHeader(display, x, y, titleStr);
|
graphics::drawCommonHeader(display, x, y, titleStr);
|
||||||
graphics::drawCommonFooter(display, x, y);
|
graphics::drawCommonFooter(display, x, y);
|
||||||
}
|
}
|
||||||
@@ -1039,6 +841,7 @@ 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_GAP = 0; // space between underline and first body line
|
||||||
constexpr int HEADER_UNDERLINE_PIX = 1; // underline thickness (1px row drawn)
|
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 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_ABOVE = 4; // space above emote line (added to line above)
|
||||||
constexpr int EMOTE_PADDING_BELOW = 3; // space below emote line (added to emote line)
|
constexpr int EMOTE_PADDING_BELOW = 3; // space below emote line (added to emote line)
|
||||||
|
|
||||||
@@ -1048,7 +851,6 @@ std::vector<int> calculateLineHeights(const std::vector<std::string> &lines, con
|
|||||||
for (size_t idx = 0; idx < lines.size(); ++idx) {
|
for (size_t idx = 0; idx < lines.size(); ++idx) {
|
||||||
const auto &line = lines[idx];
|
const auto &line = lines[idx];
|
||||||
const int baseHeight = FONT_HEIGHT_SMALL;
|
const int baseHeight = FONT_HEIGHT_SMALL;
|
||||||
int lineHeight = baseHeight;
|
|
||||||
|
|
||||||
// Detect if THIS line or NEXT line contains an emote
|
// Detect if THIS line or NEXT line contains an emote
|
||||||
bool hasEmote = false;
|
bool hasEmote = false;
|
||||||
@@ -1070,6 +872,8 @@ std::vector<int> calculateLineHeights(const std::vector<std::string> &lines, con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int lineHeight = baseHeight;
|
||||||
|
|
||||||
if (isHeaderVec[idx]) {
|
if (isHeaderVec[idx]) {
|
||||||
// Header line spacing
|
// Header line spacing
|
||||||
lineHeight = baseHeight + HEADER_UNDERLINE_PIX + HEADER_UNDERLINE_GAP;
|
lineHeight = baseHeight + HEADER_UNDERLINE_PIX + HEADER_UNDERLINE_GAP;
|
||||||
@@ -1118,7 +922,7 @@ void handleNewMessage(OLEDDisplay *display, const StoredMessage &sm, const mesht
|
|||||||
|
|
||||||
// Banner logic
|
// Banner logic
|
||||||
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet.from);
|
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet.from);
|
||||||
char longName[48] = "?";
|
char longName[48] = "???";
|
||||||
if (node && node->user.long_name) {
|
if (node && node->user.long_name) {
|
||||||
strncpy(longName, node->user.long_name, sizeof(longName) - 1);
|
strncpy(longName, node->user.long_name, sizeof(longName) - 1);
|
||||||
longName[sizeof(longName) - 1] = '\0';
|
longName[sizeof(longName) - 1] = '\0';
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ InkHUD::Tile *InkHUD::Applet::getTile()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw the applet
|
// Draw the applet
|
||||||
void InkHUD::Applet::render(bool full)
|
void InkHUD::Applet::render()
|
||||||
{
|
{
|
||||||
assert(assignedTile); // Ensure that we have a tile
|
assert(assignedTile); // Ensure that we have a tile
|
||||||
assert(assignedTile->getAssignedApplet() == this); // Ensure that we have a reciprocal link with the tile
|
assert(assignedTile->getAssignedApplet() == this); // Ensure that we have a reciprocal link with the tile
|
||||||
@@ -65,11 +65,10 @@ void InkHUD::Applet::render(bool full)
|
|||||||
wantRender = false; // Flag set by requestUpdate
|
wantRender = false; // Flag set by requestUpdate
|
||||||
wantAutoshow = false; // Flag set by requestAutoShow. May or may not have been honored.
|
wantAutoshow = false; // Flag set by requestAutoShow. May or may not have been honored.
|
||||||
wantUpdateType = Drivers::EInk::UpdateTypes::UNSPECIFIED; // Update type we wanted. May on may not have been granted.
|
wantUpdateType = Drivers::EInk::UpdateTypes::UNSPECIFIED; // Update type we wanted. May on may not have been granted.
|
||||||
wantFullRender = true; // Default to a full render
|
|
||||||
|
|
||||||
updateDimensions();
|
updateDimensions();
|
||||||
resetDrawingSpace();
|
resetDrawingSpace();
|
||||||
onRender(full); // Draw the applet
|
onRender(); // Derived applet's drawing takes place here
|
||||||
|
|
||||||
// Handle "Tile Highlighting"
|
// Handle "Tile Highlighting"
|
||||||
// Some devices may use an auxiliary button to switch between tiles
|
// Some devices may use an auxiliary button to switch between tiles
|
||||||
@@ -116,11 +115,6 @@ Drivers::EInk::UpdateTypes InkHUD::Applet::wantsUpdateType()
|
|||||||
return wantUpdateType;
|
return wantUpdateType;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InkHUD::Applet::wantsFullRender()
|
|
||||||
{
|
|
||||||
return wantFullRender;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get size of the applet's drawing space from its tile
|
// Get size of the applet's drawing space from its tile
|
||||||
// Performed immediately before derived applet's drawing code runs
|
// Performed immediately before derived applet's drawing code runs
|
||||||
void InkHUD::Applet::updateDimensions()
|
void InkHUD::Applet::updateDimensions()
|
||||||
@@ -148,11 +142,10 @@ void InkHUD::Applet::resetDrawingSpace()
|
|||||||
// Once the renderer has given other applets a chance to process whatever event we just detected,
|
// Once the renderer has given other applets a chance to process whatever event we just detected,
|
||||||
// it will run Applet::render(), which may draw our applet to screen, if it is shown (foreground)
|
// it will run Applet::render(), which may draw our applet to screen, if it is shown (foreground)
|
||||||
// We should requestUpdate even if our applet is currently background, because this might be changed by autoshow
|
// We should requestUpdate even if our applet is currently background, because this might be changed by autoshow
|
||||||
void InkHUD::Applet::requestUpdate(Drivers::EInk::UpdateTypes type, bool full)
|
void InkHUD::Applet::requestUpdate(Drivers::EInk::UpdateTypes type)
|
||||||
{
|
{
|
||||||
wantRender = true;
|
wantRender = true;
|
||||||
wantUpdateType = type;
|
wantUpdateType = type;
|
||||||
wantFullRender = full;
|
|
||||||
inkhud->requestUpdate();
|
inkhud->requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,11 +64,10 @@ class Applet : public GFX
|
|||||||
|
|
||||||
// Rendering
|
// Rendering
|
||||||
|
|
||||||
void render(bool full); // Draw the applet
|
void render(); // Draw the applet
|
||||||
bool wantsToRender(); // Check whether applet wants to render
|
bool wantsToRender(); // Check whether applet wants to render
|
||||||
bool wantsToAutoshow(); // Check whether applet wants to become foreground
|
bool wantsToAutoshow(); // Check whether applet wants to become foreground
|
||||||
Drivers::EInk::UpdateTypes wantsUpdateType(); // Check which display update type the applet would prefer
|
Drivers::EInk::UpdateTypes wantsUpdateType(); // Check which display update type the applet would prefer
|
||||||
bool wantsFullRender(); // Check whether applet wants to render over its previous render
|
|
||||||
void updateDimensions(); // Get current size from tile
|
void updateDimensions(); // Get current size from tile
|
||||||
void resetDrawingSpace(); // Makes sure every render starts with same parameters
|
void resetDrawingSpace(); // Makes sure every render starts with same parameters
|
||||||
|
|
||||||
@@ -83,7 +82,7 @@ class Applet : public GFX
|
|||||||
|
|
||||||
// Event handlers
|
// Event handlers
|
||||||
|
|
||||||
virtual void onRender(bool full) = 0; // For drawing the applet
|
virtual void onRender() = 0; // All drawing happens here
|
||||||
virtual void onActivate() {}
|
virtual void onActivate() {}
|
||||||
virtual void onDeactivate() {}
|
virtual void onDeactivate() {}
|
||||||
virtual void onForeground() {}
|
virtual void onForeground() {}
|
||||||
@@ -97,9 +96,6 @@ class Applet : public GFX
|
|||||||
virtual void onNavDown() {}
|
virtual void onNavDown() {}
|
||||||
virtual void onNavLeft() {}
|
virtual void onNavLeft() {}
|
||||||
virtual void onNavRight() {}
|
virtual void onNavRight() {}
|
||||||
virtual void onFreeText(char c) {}
|
|
||||||
virtual void onFreeTextDone() {}
|
|
||||||
virtual void onFreeTextCancel() {}
|
|
||||||
|
|
||||||
virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification
|
virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification
|
||||||
|
|
||||||
@@ -112,9 +108,8 @@ class Applet : public GFX
|
|||||||
protected:
|
protected:
|
||||||
void drawPixel(int16_t x, int16_t y, uint16_t color) override; // Place a single pixel. All drawing output passes through here
|
void drawPixel(int16_t x, int16_t y, uint16_t color) override; // Place a single pixel. All drawing output passes through here
|
||||||
|
|
||||||
void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED,
|
void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED); // Ask WindowManager to schedule a display update
|
||||||
bool full = true); // Ask WindowManager to schedule a display update
|
void requestAutoshow(); // Ask for applet to be moved to foreground
|
||||||
void requestAutoshow(); // Ask for applet to be moved to foreground
|
|
||||||
|
|
||||||
uint16_t X(float f); // Map applet width, mapped from 0 to 1.0
|
uint16_t X(float f); // Map applet width, mapped from 0 to 1.0
|
||||||
uint16_t Y(float f); // Map applet height, mapped from 0 to 1.0
|
uint16_t Y(float f); // Map applet height, mapped from 0 to 1.0
|
||||||
@@ -169,7 +164,6 @@ class Applet : public GFX
|
|||||||
bool wantAutoshow = false; // Does the applet have new data it would like to display in foreground?
|
bool wantAutoshow = false; // Does the applet have new data it would like to display in foreground?
|
||||||
NicheGraphics::Drivers::EInk::UpdateTypes wantUpdateType =
|
NicheGraphics::Drivers::EInk::UpdateTypes wantUpdateType =
|
||||||
NicheGraphics::Drivers::EInk::UpdateTypes::UNSPECIFIED; // Which update method we'd prefer when redrawing the display
|
NicheGraphics::Drivers::EInk::UpdateTypes::UNSPECIFIED; // Which update method we'd prefer when redrawing the display
|
||||||
bool wantFullRender = true; // Render with a fresh canvas
|
|
||||||
|
|
||||||
using GFX::setFont; // Make sure derived classes use AppletFont instead of AdafruitGFX fonts directly
|
using GFX::setFont; // Make sure derived classes use AppletFont instead of AdafruitGFX fonts directly
|
||||||
using GFX::setRotation; // Block setRotation calls. Rotation is handled globally by WindowManager.
|
using GFX::setRotation; // Block setRotation calls. Rotation is handled globally by WindowManager.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
using namespace NicheGraphics;
|
using namespace NicheGraphics;
|
||||||
|
|
||||||
void InkHUD::MapApplet::onRender(bool full)
|
void InkHUD::MapApplet::onRender()
|
||||||
{
|
{
|
||||||
// Abort if no markers to render
|
// Abort if no markers to render
|
||||||
if (!enoughMarkers()) {
|
if (!enoughMarkers()) {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace NicheGraphics::InkHUD
|
|||||||
class MapApplet : public Applet
|
class MapApplet : public Applet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void onRender(bool full) override;
|
void onRender() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool shouldDrawNode(meshtastic_NodeInfoLite *node) { return true; } // Allow derived applets to filter the nodes
|
virtual bool shouldDrawNode(meshtastic_NodeInfoLite *node) { return true; } // Allow derived applets to filter the nodes
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ uint8_t InkHUD::NodeListApplet::maxCards()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw, using info which derived applet placed into NodeListApplet::cards for us
|
// Draw, using info which derived applet placed into NodeListApplet::cards for us
|
||||||
void InkHUD::NodeListApplet::onRender(bool full)
|
void InkHUD::NodeListApplet::onRender()
|
||||||
{
|
{
|
||||||
|
|
||||||
// ================================
|
// ================================
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class NodeListApplet : public Applet, public MeshModule
|
|||||||
public:
|
public:
|
||||||
NodeListApplet(const char *name);
|
NodeListApplet(const char *name);
|
||||||
|
|
||||||
void onRender(bool full) override;
|
void onRender() override;
|
||||||
|
|
||||||
bool wantPacket(const meshtastic_MeshPacket *p) override;
|
bool wantPacket(const meshtastic_MeshPacket *p) override;
|
||||||
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ using namespace NicheGraphics;
|
|||||||
|
|
||||||
// All drawing happens here
|
// All drawing happens here
|
||||||
// Our basic example doesn't do anything useful. It just passively prints some text.
|
// Our basic example doesn't do anything useful. It just passively prints some text.
|
||||||
void InkHUD::BasicExampleApplet::onRender(bool full)
|
void InkHUD::BasicExampleApplet::onRender()
|
||||||
{
|
{
|
||||||
printAt(0, 0, "Hello, World!");
|
printAt(0, 0, "Hello, World!");
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class BasicExampleApplet : public Applet
|
|||||||
// You must have an onRender() method
|
// You must have an onRender() method
|
||||||
// All drawing happens here
|
// All drawing happens here
|
||||||
|
|
||||||
void onRender(bool full) override;
|
void onRender() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace NicheGraphics::InkHUD
|
} // namespace NicheGraphics::InkHUD
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ ProcessMessage InkHUD::NewMsgExampleApplet::handleReceived(const meshtastic_Mesh
|
|||||||
// We can trigger a render by calling requestUpdate()
|
// We can trigger a render by calling requestUpdate()
|
||||||
// Render might be called by some external source
|
// Render might be called by some external source
|
||||||
// We should always be ready to draw
|
// We should always be ready to draw
|
||||||
void InkHUD::NewMsgExampleApplet::onRender(bool full)
|
void InkHUD::NewMsgExampleApplet::onRender()
|
||||||
{
|
{
|
||||||
printAt(0, 0, "Example: NewMsg", LEFT, TOP); // Print top-left corner of text at (0,0)
|
printAt(0, 0, "Example: NewMsg", LEFT, TOP); // Print top-left corner of text at (0,0)
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class NewMsgExampleApplet : public Applet, public SinglePortModule
|
|||||||
NewMsgExampleApplet() : SinglePortModule("NewMsgExampleApplet", meshtastic_PortNum_TEXT_MESSAGE_APP) {}
|
NewMsgExampleApplet() : SinglePortModule("NewMsgExampleApplet", meshtastic_PortNum_TEXT_MESSAGE_APP) {}
|
||||||
|
|
||||||
// All drawing happens here
|
// All drawing happens here
|
||||||
void onRender(bool full) override;
|
void onRender() override;
|
||||||
|
|
||||||
// Your applet might also want to use some of these
|
// Your applet might also want to use some of these
|
||||||
// Useful for setting up or tidying up
|
// Useful for setting up or tidying up
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ InkHUD::AlignStickApplet::AlignStickApplet()
|
|||||||
bringToForeground();
|
bringToForeground();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onRender(bool full)
|
void InkHUD::AlignStickApplet::onRender()
|
||||||
{
|
{
|
||||||
setFont(fontMedium);
|
setFont(fontMedium);
|
||||||
printAt(0, 0, "Align Joystick:");
|
printAt(0, 0, "Align Joystick:");
|
||||||
@@ -152,17 +152,19 @@ void InkHUD::AlignStickApplet::onBackground()
|
|||||||
|
|
||||||
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
||||||
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL, true);
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onButtonLongPress()
|
void InkHUD::AlignStickApplet::onButtonLongPress()
|
||||||
{
|
{
|
||||||
sendToBackground();
|
sendToBackground();
|
||||||
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onExitLong()
|
void InkHUD::AlignStickApplet::onExitLong()
|
||||||
{
|
{
|
||||||
sendToBackground();
|
sendToBackground();
|
||||||
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onNavUp()
|
void InkHUD::AlignStickApplet::onNavUp()
|
||||||
@@ -170,6 +172,7 @@ void InkHUD::AlignStickApplet::onNavUp()
|
|||||||
settings->joystick.aligned = true;
|
settings->joystick.aligned = true;
|
||||||
|
|
||||||
sendToBackground();
|
sendToBackground();
|
||||||
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onNavDown()
|
void InkHUD::AlignStickApplet::onNavDown()
|
||||||
@@ -178,6 +181,7 @@ void InkHUD::AlignStickApplet::onNavDown()
|
|||||||
settings->joystick.aligned = true;
|
settings->joystick.aligned = true;
|
||||||
|
|
||||||
sendToBackground();
|
sendToBackground();
|
||||||
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onNavLeft()
|
void InkHUD::AlignStickApplet::onNavLeft()
|
||||||
@@ -186,6 +190,7 @@ void InkHUD::AlignStickApplet::onNavLeft()
|
|||||||
settings->joystick.aligned = true;
|
settings->joystick.aligned = true;
|
||||||
|
|
||||||
sendToBackground();
|
sendToBackground();
|
||||||
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onNavRight()
|
void InkHUD::AlignStickApplet::onNavRight()
|
||||||
@@ -194,6 +199,7 @@ void InkHUD::AlignStickApplet::onNavRight()
|
|||||||
settings->joystick.aligned = true;
|
settings->joystick.aligned = true;
|
||||||
|
|
||||||
sendToBackground();
|
sendToBackground();
|
||||||
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -23,7 +23,7 @@ class AlignStickApplet : public SystemApplet
|
|||||||
public:
|
public:
|
||||||
AlignStickApplet();
|
AlignStickApplet();
|
||||||
|
|
||||||
void onRender(bool full) override;
|
void onRender() override;
|
||||||
void onForeground() override;
|
void onForeground() override;
|
||||||
void onBackground() override;
|
void onBackground() override;
|
||||||
void onButtonLongPress() override;
|
void onButtonLongPress() override;
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ using namespace NicheGraphics;
|
|||||||
|
|
||||||
InkHUD::BatteryIconApplet::BatteryIconApplet()
|
InkHUD::BatteryIconApplet::BatteryIconApplet()
|
||||||
{
|
{
|
||||||
alwaysRender = true; // render everytime the screen is updated
|
|
||||||
|
|
||||||
// Show at boot, if user has previously enabled the feature
|
// Show at boot, if user has previously enabled the feature
|
||||||
if (settings->optionalFeatures.batteryIcon)
|
if (settings->optionalFeatures.batteryIcon)
|
||||||
bringToForeground();
|
bringToForeground();
|
||||||
@@ -46,7 +44,7 @@ int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *sta
|
|||||||
return 0; // Tell Observable to continue informing other observers
|
return 0; // Tell Observable to continue informing other observers
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::BatteryIconApplet::onRender(bool full)
|
void InkHUD::BatteryIconApplet::onRender()
|
||||||
{
|
{
|
||||||
// Fill entire tile
|
// Fill entire tile
|
||||||
// - size of icon controlled by size of tile
|
// - size of icon controlled by size of tile
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class BatteryIconApplet : public SystemApplet
|
|||||||
public:
|
public:
|
||||||
BatteryIconApplet();
|
BatteryIconApplet();
|
||||||
|
|
||||||
void onRender(bool full) override;
|
void onRender() override;
|
||||||
int onPowerStatusUpdate(const meshtastic::Status *status); // Called when new info about battery is available
|
int onPowerStatusUpdate(const meshtastic::Status *status); // Called when new info about battery is available
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -1,257 +0,0 @@
|
|||||||
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
|
||||||
#include "./KeyboardApplet.h"
|
|
||||||
|
|
||||||
using namespace NicheGraphics;
|
|
||||||
|
|
||||||
InkHUD::KeyboardApplet::KeyboardApplet()
|
|
||||||
{
|
|
||||||
// Calculate row widths
|
|
||||||
for (uint8_t row = 0; row < KBD_ROWS; row++) {
|
|
||||||
rowWidths[row] = 0;
|
|
||||||
for (uint8_t col = 0; col < KBD_COLS; col++)
|
|
||||||
rowWidths[row] += keyWidths[row * KBD_COLS + col];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::KeyboardApplet::onRender(bool full)
|
|
||||||
{
|
|
||||||
uint16_t em = fontSmall.lineHeight(); // 16 pt
|
|
||||||
uint16_t keyH = Y(1.0) / KBD_ROWS;
|
|
||||||
int16_t keyTopPadding = (keyH - fontSmall.lineHeight()) / 2;
|
|
||||||
|
|
||||||
if (full) { // Draw full keyboard
|
|
||||||
for (uint8_t row = 0; row < KBD_ROWS; row++) {
|
|
||||||
|
|
||||||
// Calculate the remaining space to be used as padding
|
|
||||||
int16_t keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4);
|
|
||||||
|
|
||||||
// Draw keys
|
|
||||||
uint16_t xPos = 0;
|
|
||||||
for (uint8_t col = 0; col < KBD_COLS; col++) {
|
|
||||||
Color fgcolor = BLACK;
|
|
||||||
uint8_t index = row * KBD_COLS + col;
|
|
||||||
uint16_t keyX = ((xPos * em) >> 4) + ((col * keyXPadding) / (KBD_COLS - 1));
|
|
||||||
uint16_t keyY = row * keyH;
|
|
||||||
uint16_t keyW = (keyWidths[index] * em) >> 4;
|
|
||||||
if (index == selectedKey) {
|
|
||||||
fgcolor = WHITE;
|
|
||||||
fillRect(keyX, keyY, keyW, keyH, BLACK);
|
|
||||||
}
|
|
||||||
drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[index], fgcolor);
|
|
||||||
xPos += keyWidths[index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else { // Only draw the difference
|
|
||||||
if (selectedKey != prevSelectedKey) {
|
|
||||||
// Draw previously selected key
|
|
||||||
uint8_t row = prevSelectedKey / KBD_COLS;
|
|
||||||
int16_t keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4);
|
|
||||||
uint16_t xPos = 0;
|
|
||||||
for (uint8_t i = prevSelectedKey - (prevSelectedKey % KBD_COLS); i < prevSelectedKey; i++)
|
|
||||||
xPos += keyWidths[i];
|
|
||||||
uint16_t keyX = ((xPos * em) >> 4) + (((prevSelectedKey % KBD_COLS) * keyXPadding) / (KBD_COLS - 1));
|
|
||||||
uint16_t keyY = row * keyH;
|
|
||||||
uint16_t keyW = (keyWidths[prevSelectedKey] * em) >> 4;
|
|
||||||
fillRect(keyX, keyY, keyW, keyH, WHITE);
|
|
||||||
drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[prevSelectedKey], BLACK);
|
|
||||||
|
|
||||||
// Draw newly selected key
|
|
||||||
row = selectedKey / KBD_COLS;
|
|
||||||
keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4);
|
|
||||||
xPos = 0;
|
|
||||||
for (uint8_t i = selectedKey - (selectedKey % KBD_COLS); i < selectedKey; i++)
|
|
||||||
xPos += keyWidths[i];
|
|
||||||
keyX = ((xPos * em) >> 4) + (((selectedKey % KBD_COLS) * keyXPadding) / (KBD_COLS - 1));
|
|
||||||
keyY = row * keyH;
|
|
||||||
keyW = (keyWidths[selectedKey] * em) >> 4;
|
|
||||||
fillRect(keyX, keyY, keyW, keyH, BLACK);
|
|
||||||
drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[selectedKey], WHITE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prevSelectedKey = selectedKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the key label corresponding to the char
|
|
||||||
// for most keys it draws the character itself
|
|
||||||
// for ['\b', '\n', ' ', '\x1b'] it draws special glyphs
|
|
||||||
void InkHUD::KeyboardApplet::drawKeyLabel(uint16_t left, uint16_t top, uint16_t width, char key, Color color)
|
|
||||||
{
|
|
||||||
if (key == '\b') {
|
|
||||||
// Draw backspace glyph: 13 x 9 px
|
|
||||||
/**
|
|
||||||
* [][][][][][][][][]
|
|
||||||
* [][] []
|
|
||||||
* [][] [] [] []
|
|
||||||
* [][] [] [] []
|
|
||||||
* [][] [] []
|
|
||||||
* [][] [] [] []
|
|
||||||
* [][] [] [] []
|
|
||||||
* [][] []
|
|
||||||
* [][][][][][][][][]
|
|
||||||
*/
|
|
||||||
const uint8_t bsBitmap[] = {0x0f, 0xf8, 0x18, 0x08, 0x32, 0x28, 0x61, 0x48, 0xc0,
|
|
||||||
0x88, 0x61, 0x48, 0x32, 0x28, 0x18, 0x08, 0x0f, 0xf8};
|
|
||||||
uint16_t leftPadding = (width - 13) >> 1;
|
|
||||||
drawBitmap(left + leftPadding, top + 1, bsBitmap, 13, 9, color);
|
|
||||||
} else if (key == '\n') {
|
|
||||||
// Draw done glyph: 12 x 9 px
|
|
||||||
/**
|
|
||||||
* [][]
|
|
||||||
* [][]
|
|
||||||
* [][]
|
|
||||||
* [][]
|
|
||||||
* [][]
|
|
||||||
* [][] [][]
|
|
||||||
* [][] [][]
|
|
||||||
* [][][]
|
|
||||||
* []
|
|
||||||
*/
|
|
||||||
const uint8_t doneBitmap[] = {0x00, 0x30, 0x00, 0x60, 0x00, 0xc0, 0x01, 0x80, 0x03,
|
|
||||||
0x00, 0xc6, 0x00, 0x6c, 0x00, 0x38, 0x00, 0x10, 0x00};
|
|
||||||
uint16_t leftPadding = (width - 12) >> 1;
|
|
||||||
drawBitmap(left + leftPadding, top + 1, doneBitmap, 12, 9, color);
|
|
||||||
} else if (key == ' ') {
|
|
||||||
// Draw space glyph: 13 x 9 px
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* [] []
|
|
||||||
* [] []
|
|
||||||
* [][][][][][][][][][][][][]
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
const uint8_t spaceBitmap[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
|
|
||||||
0x08, 0x80, 0x08, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00};
|
|
||||||
uint16_t leftPadding = (width - 13) >> 1;
|
|
||||||
drawBitmap(left + leftPadding, top + 1, spaceBitmap, 13, 9, color);
|
|
||||||
} else if (key == '\x1b') {
|
|
||||||
setTextColor(color);
|
|
||||||
std::string keyText = "ESC";
|
|
||||||
uint16_t leftPadding = (width - getTextWidth(keyText)) >> 1;
|
|
||||||
printAt(left + leftPadding, top, keyText);
|
|
||||||
} else {
|
|
||||||
setTextColor(color);
|
|
||||||
if (key >= 0x61)
|
|
||||||
key -= 32; // capitalize
|
|
||||||
std::string keyText = std::string(1, key);
|
|
||||||
uint16_t leftPadding = (width - getTextWidth(keyText)) >> 1;
|
|
||||||
printAt(left + leftPadding, top, keyText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::KeyboardApplet::onForeground()
|
|
||||||
{
|
|
||||||
handleInput = true; // Intercept the button input for our applet
|
|
||||||
|
|
||||||
// Select the first key
|
|
||||||
selectedKey = 0;
|
|
||||||
prevSelectedKey = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::KeyboardApplet::onBackground()
|
|
||||||
{
|
|
||||||
handleInput = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::KeyboardApplet::onButtonShortPress()
|
|
||||||
{
|
|
||||||
char key = keys[selectedKey];
|
|
||||||
if (key == '\n') {
|
|
||||||
inkhud->freeTextDone();
|
|
||||||
inkhud->closeKeyboard();
|
|
||||||
} else if (key == '\x1b') {
|
|
||||||
inkhud->freeTextCancel();
|
|
||||||
inkhud->closeKeyboard();
|
|
||||||
} else {
|
|
||||||
inkhud->freeText(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::KeyboardApplet::onButtonLongPress()
|
|
||||||
{
|
|
||||||
char key = keys[selectedKey];
|
|
||||||
if (key == '\n') {
|
|
||||||
inkhud->freeTextDone();
|
|
||||||
inkhud->closeKeyboard();
|
|
||||||
} else if (key == '\x1b') {
|
|
||||||
inkhud->freeTextCancel();
|
|
||||||
inkhud->closeKeyboard();
|
|
||||||
} else {
|
|
||||||
if (key >= 0x61)
|
|
||||||
key -= 32; // capitalize
|
|
||||||
inkhud->freeText(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::KeyboardApplet::onExitShort()
|
|
||||||
{
|
|
||||||
inkhud->freeTextCancel();
|
|
||||||
inkhud->closeKeyboard();
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::KeyboardApplet::onExitLong()
|
|
||||||
{
|
|
||||||
inkhud->freeTextCancel();
|
|
||||||
inkhud->closeKeyboard();
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::KeyboardApplet::onNavUp()
|
|
||||||
{
|
|
||||||
if (selectedKey < KBD_COLS) // wrap
|
|
||||||
selectedKey += KBD_COLS * (KBD_ROWS - 1);
|
|
||||||
else // move 1 row back
|
|
||||||
selectedKey -= KBD_COLS;
|
|
||||||
|
|
||||||
// Request rendering over the previously drawn render
|
|
||||||
requestUpdate(EInk::UpdateTypes::FAST, false);
|
|
||||||
// Force an update to bypass lockRequests
|
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::KeyboardApplet::onNavDown()
|
|
||||||
{
|
|
||||||
selectedKey += KBD_COLS;
|
|
||||||
selectedKey %= (KBD_COLS * KBD_ROWS);
|
|
||||||
|
|
||||||
// Request rendering over the previously drawn render
|
|
||||||
requestUpdate(EInk::UpdateTypes::FAST, false);
|
|
||||||
// Force an update to bypass lockRequests
|
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::KeyboardApplet::onNavLeft()
|
|
||||||
{
|
|
||||||
if (selectedKey % KBD_COLS == 0) // wrap
|
|
||||||
selectedKey += KBD_COLS - 1;
|
|
||||||
else // move 1 column back
|
|
||||||
selectedKey--;
|
|
||||||
|
|
||||||
// Request rendering over the previously drawn render
|
|
||||||
requestUpdate(EInk::UpdateTypes::FAST, false);
|
|
||||||
// Force an update to bypass lockRequests
|
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::KeyboardApplet::onNavRight()
|
|
||||||
{
|
|
||||||
if (selectedKey % KBD_COLS == KBD_COLS - 1) // wrap
|
|
||||||
selectedKey -= KBD_COLS - 1;
|
|
||||||
else // move 1 column forward
|
|
||||||
selectedKey++;
|
|
||||||
|
|
||||||
// Request rendering over the previously drawn render
|
|
||||||
requestUpdate(EInk::UpdateTypes::FAST, false);
|
|
||||||
// Force an update to bypass lockRequests
|
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t InkHUD::KeyboardApplet::getKeyboardHeight()
|
|
||||||
{
|
|
||||||
const uint16_t keyH = fontSmall.lineHeight() * 1.2;
|
|
||||||
return keyH * KBD_ROWS;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
System Applet to render an on-screeen keyboard
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "configuration.h"
|
|
||||||
#include "graphics/niche/InkHUD/InkHUD.h"
|
|
||||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
|
||||||
#include <string>
|
|
||||||
namespace NicheGraphics::InkHUD
|
|
||||||
{
|
|
||||||
|
|
||||||
class KeyboardApplet : public SystemApplet
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
KeyboardApplet();
|
|
||||||
|
|
||||||
void onRender(bool full) override;
|
|
||||||
void onForeground() override;
|
|
||||||
void onBackground() override;
|
|
||||||
void onButtonShortPress() override;
|
|
||||||
void onButtonLongPress() override;
|
|
||||||
void onExitShort() override;
|
|
||||||
void onExitLong() override;
|
|
||||||
void onNavUp() override;
|
|
||||||
void onNavDown() override;
|
|
||||||
void onNavLeft() override;
|
|
||||||
void onNavRight() override;
|
|
||||||
|
|
||||||
static uint16_t getKeyboardHeight(); // used to set the keyboard tile height
|
|
||||||
|
|
||||||
private:
|
|
||||||
void drawKeyLabel(uint16_t left, uint16_t top, uint16_t width, char key, Color color);
|
|
||||||
|
|
||||||
static const uint8_t KBD_COLS = 11;
|
|
||||||
static const uint8_t KBD_ROWS = 4;
|
|
||||||
|
|
||||||
const char keys[KBD_COLS * KBD_ROWS] = {
|
|
||||||
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '\b', // row 0
|
|
||||||
'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '\n', // row 1
|
|
||||||
'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', '!', ' ', // row 2
|
|
||||||
'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '?', '\x1b' // row 3
|
|
||||||
};
|
|
||||||
|
|
||||||
// This array represents the widths of each key in points
|
|
||||||
// 16 pt = line height of the text
|
|
||||||
const uint16_t keyWidths[KBD_COLS * KBD_ROWS] = {
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 0
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 1
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 2
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 10, 10, 12, 40 // row 3
|
|
||||||
};
|
|
||||||
|
|
||||||
uint16_t rowWidths[KBD_ROWS];
|
|
||||||
uint8_t selectedKey = 0; // selected key index
|
|
||||||
uint8_t prevSelectedKey = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace NicheGraphics::InkHUD
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -30,7 +30,7 @@ InkHUD::LogoApplet::LogoApplet() : concurrency::OSThread("LogoApplet")
|
|||||||
// This is then drawn with a FULL refresh by Renderer::begin
|
// This is then drawn with a FULL refresh by Renderer::begin
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::LogoApplet::onRender(bool full)
|
void InkHUD::LogoApplet::onRender()
|
||||||
{
|
{
|
||||||
// Size of the region which the logo should "scale to fit"
|
// Size of the region which the logo should "scale to fit"
|
||||||
uint16_t logoWLimit = X(0.8);
|
uint16_t logoWLimit = X(0.8);
|
||||||
@@ -120,7 +120,7 @@ void InkHUD::LogoApplet::onBackground()
|
|||||||
|
|
||||||
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
||||||
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL, true);
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin displaying the screen which is shown at shutdown
|
// Begin displaying the screen which is shown at shutdown
|
||||||
@@ -138,10 +138,10 @@ void InkHUD::LogoApplet::onShutdown()
|
|||||||
// Intention is to restore display health.
|
// Intention is to restore display health.
|
||||||
|
|
||||||
inverted = true;
|
inverted = true;
|
||||||
inkhud->forceUpdate(Drivers::EInk::FULL, true, false);
|
inkhud->forceUpdate(Drivers::EInk::FULL, false);
|
||||||
delay(1000); // Cooldown. Back to back updates aren't great for health.
|
delay(1000); // Cooldown. Back to back updates aren't great for health.
|
||||||
inverted = false;
|
inverted = false;
|
||||||
inkhud->forceUpdate(Drivers::EInk::FULL, true, false);
|
inkhud->forceUpdate(Drivers::EInk::FULL, false);
|
||||||
delay(1000); // Cooldown
|
delay(1000); // Cooldown
|
||||||
|
|
||||||
// Prepare for the powered-off screen now
|
// Prepare for the powered-off screen now
|
||||||
@@ -155,18 +155,6 @@ void InkHUD::LogoApplet::onShutdown()
|
|||||||
// This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update, after InkHUD's flash write is complete
|
// 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()
|
void InkHUD::LogoApplet::onReboot()
|
||||||
{
|
{
|
||||||
bringToForeground();
|
bringToForeground();
|
||||||
@@ -176,7 +164,7 @@ void InkHUD::LogoApplet::onReboot()
|
|||||||
textTitle = "Rebooting...";
|
textTitle = "Rebooting...";
|
||||||
fontTitle = fontSmall;
|
fontTitle = fontSmall;
|
||||||
|
|
||||||
inkhud->forceUpdate(Drivers::EInk::FULL, true, false);
|
inkhud->forceUpdate(Drivers::EInk::FULL, false);
|
||||||
// Perform the update right now, waiting here until complete
|
// Perform the update right now, waiting here until complete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,12 +21,11 @@ class LogoApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LogoApplet();
|
LogoApplet();
|
||||||
void onRender(bool full) override;
|
void onRender() override;
|
||||||
void onForeground() override;
|
void onForeground() override;
|
||||||
void onBackground() override;
|
void onBackground() override;
|
||||||
void onShutdown() override;
|
void onShutdown() override;
|
||||||
void onReboot() override;
|
void onReboot() override;
|
||||||
void onApplyingChanges();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int32_t runOnce() override;
|
int32_t runOnce() override;
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ namespace NicheGraphics::InkHUD
|
|||||||
enum MenuAction {
|
enum MenuAction {
|
||||||
NO_ACTION,
|
NO_ACTION,
|
||||||
SEND_PING,
|
SEND_PING,
|
||||||
FREE_TEXT,
|
|
||||||
STORE_CANNEDMESSAGE_SELECTION,
|
STORE_CANNEDMESSAGE_SELECTION,
|
||||||
SEND_CANNEDMESSAGE,
|
SEND_CANNEDMESSAGE,
|
||||||
SHUTDOWN,
|
SHUTDOWN,
|
||||||
@@ -37,84 +36,6 @@ enum MenuAction {
|
|||||||
TOGGLE_NOTIFICATIONS,
|
TOGGLE_NOTIFICATIONS,
|
||||||
TOGGLE_INVERT_COLOR,
|
TOGGLE_INVERT_COLOR,
|
||||||
TOGGLE_12H_CLOCK,
|
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
|
} // namespace NicheGraphics::InkHUD
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -32,13 +32,9 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
void onNavDown() override;
|
void onNavDown() override;
|
||||||
void onNavLeft() override;
|
void onNavLeft() override;
|
||||||
void onNavRight() override;
|
void onNavRight() override;
|
||||||
void onFreeText(char c) override;
|
void onRender() override;
|
||||||
void onFreeTextDone() override;
|
|
||||||
void onFreeTextCancel() override;
|
|
||||||
void onRender(bool full) override;
|
|
||||||
|
|
||||||
void show(Tile *t); // Open the menu, onto a user tile
|
void show(Tile *t); // Open the menu, onto a user tile
|
||||||
void setStartPage(MenuPage page);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton
|
Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton
|
||||||
@@ -54,32 +50,20 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
void populateAutoshowPage(); // Dynamically create MenuItems for selecting which applets can autoshow
|
void populateAutoshowPage(); // Dynamically create MenuItems for selecting which applets can autoshow
|
||||||
void populateRecentsPage(); // Create menu items: a choice of values for settings.recentlyActiveSeconds
|
void populateRecentsPage(); // Create menu items: a choice of values for settings.recentlyActiveSeconds
|
||||||
|
|
||||||
void drawInputField(uint16_t left, uint16_t top, uint16_t width, uint16_t height,
|
|
||||||
std::string text); // Draw input field for free text
|
|
||||||
uint16_t getSystemInfoPanelHeight();
|
uint16_t getSystemInfoPanelHeight();
|
||||||
void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width,
|
void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width,
|
||||||
uint16_t *height = nullptr); // Info panel at top of root menu
|
uint16_t *height = nullptr); // Info panel at top of root menu
|
||||||
void sendText(NodeNum dest, ChannelIndex channel, const char *message); // Send a text message to mesh
|
void sendText(NodeNum dest, ChannelIndex channel, const char *message); // Send a text message to mesh
|
||||||
void freeCannedMessageResources(); // Clear MenuApplet's canned message processing data
|
void freeCannedMessageResources(); // Clear MenuApplet's canned message processing data
|
||||||
|
|
||||||
MenuPage startPageOverride = MenuPage::ROOT;
|
|
||||||
MenuPage currentPage = MenuPage::ROOT;
|
MenuPage currentPage = MenuPage::ROOT;
|
||||||
MenuPage previousPage = MenuPage::EXIT;
|
MenuPage previousPage = MenuPage::EXIT;
|
||||||
uint8_t cursor = 0; // Which menu item is currently highlighted
|
uint8_t cursor = 0; // Which menu item is currently highlighted
|
||||||
bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection)
|
bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection)
|
||||||
bool freeTextMode = false;
|
|
||||||
uint16_t systemInfoPanelHeight = 0; // Need to know before we render
|
uint16_t systemInfoPanelHeight = 0; // Need to know before we render
|
||||||
uint16_t menuTextLimit = 200;
|
|
||||||
|
|
||||||
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
|
// Data for selecting and sending canned messages via the menu
|
||||||
// Placed into a sub-class for organization only
|
// Placed into a sub-class for organization only
|
||||||
@@ -110,8 +94,6 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
// Cleared onBackground (when MenuApplet closes)
|
// Cleared onBackground (when MenuApplet closes)
|
||||||
std::vector<MessageItem> messageItems;
|
std::vector<MessageItem> messageItems;
|
||||||
std::vector<RecipientItem> recipientItems;
|
std::vector<RecipientItem> recipientItems;
|
||||||
|
|
||||||
MessageItem freeTextItem;
|
|
||||||
} cm;
|
} cm;
|
||||||
|
|
||||||
Applet *borrowedTileOwner = nullptr; // Which applet we have temporarily replaced while displaying menu
|
Applet *borrowedTileOwner = nullptr; // Which applet we have temporarily replaced while displaying menu
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ class MenuItem
|
|||||||
MenuAction action = NO_ACTION;
|
MenuAction action = NO_ACTION;
|
||||||
MenuPage nextPage = EXIT;
|
MenuPage nextPage = EXIT;
|
||||||
bool *checkState = nullptr;
|
bool *checkState = nullptr;
|
||||||
bool isHeader = false; // Non-selectable section label
|
|
||||||
|
|
||||||
// Various constructors, depending on the intended function of the item
|
// Various constructors, depending on the intended function of the item
|
||||||
|
|
||||||
@@ -41,12 +40,6 @@ class MenuItem
|
|||||||
: label(label), action(action), nextPage(nextPage), checkState(checkState)
|
: 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
|
} // namespace NicheGraphics::InkHUD
|
||||||
|
|||||||
@@ -20,27 +20,10 @@ enum MenuPage : uint8_t {
|
|||||||
SEND,
|
SEND,
|
||||||
CANNEDMESSAGE_RECIPIENT, // Select destination for a canned message
|
CANNEDMESSAGE_RECIPIENT, // Select destination for a canned message
|
||||||
OPTIONS,
|
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,
|
APPLETS,
|
||||||
AUTOSHOW,
|
AUTOSHOW,
|
||||||
RECENTS, // Select length of "recentlyActiveSeconds"
|
RECENTS, // Select length of "recentlyActiveSeconds"
|
||||||
REGION,
|
EXIT, // Dismiss the menu applet
|
||||||
EXIT, // Dismiss the menu applet
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace NicheGraphics::InkHUD
|
} // namespace NicheGraphics::InkHUD
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onRender(bool full)
|
void InkHUD::NotificationApplet::onRender()
|
||||||
{
|
{
|
||||||
// Clear the region beneath the tile
|
// Clear the region beneath the tile
|
||||||
// Most applets are drawing onto an empty frame buffer and don't need to do this
|
// Most applets are drawing onto an empty frame buffer and don't need to do this
|
||||||
@@ -139,47 +139,54 @@ void InkHUD::NotificationApplet::onForeground()
|
|||||||
void InkHUD::NotificationApplet::onBackground()
|
void InkHUD::NotificationApplet::onBackground()
|
||||||
{
|
{
|
||||||
handleInput = false;
|
handleInput = false;
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onButtonShortPress()
|
void InkHUD::NotificationApplet::onButtonShortPress()
|
||||||
{
|
{
|
||||||
dismiss();
|
dismiss();
|
||||||
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onButtonLongPress()
|
void InkHUD::NotificationApplet::onButtonLongPress()
|
||||||
{
|
{
|
||||||
dismiss();
|
dismiss();
|
||||||
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onExitShort()
|
void InkHUD::NotificationApplet::onExitShort()
|
||||||
{
|
{
|
||||||
dismiss();
|
dismiss();
|
||||||
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onExitLong()
|
void InkHUD::NotificationApplet::onExitLong()
|
||||||
{
|
{
|
||||||
dismiss();
|
dismiss();
|
||||||
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onNavUp()
|
void InkHUD::NotificationApplet::onNavUp()
|
||||||
{
|
{
|
||||||
dismiss();
|
dismiss();
|
||||||
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onNavDown()
|
void InkHUD::NotificationApplet::onNavDown()
|
||||||
{
|
{
|
||||||
dismiss();
|
dismiss();
|
||||||
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onNavLeft()
|
void InkHUD::NotificationApplet::onNavLeft()
|
||||||
{
|
{
|
||||||
dismiss();
|
dismiss();
|
||||||
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onNavRight()
|
void InkHUD::NotificationApplet::onNavRight()
|
||||||
{
|
{
|
||||||
dismiss();
|
dismiss();
|
||||||
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask the WindowManager to check whether any displayed applets are already displaying the info from this notification
|
// Ask the WindowManager to check whether any displayed applets are already displaying the info from this notification
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class NotificationApplet : public SystemApplet
|
|||||||
public:
|
public:
|
||||||
NotificationApplet();
|
NotificationApplet();
|
||||||
|
|
||||||
void onRender(bool full) override;
|
void onRender() override;
|
||||||
void onForeground() override;
|
void onForeground() override;
|
||||||
void onBackground() override;
|
void onBackground() override;
|
||||||
void onButtonShortPress() override;
|
void onButtonShortPress() override;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ InkHUD::PairingApplet::PairingApplet()
|
|||||||
bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
|
bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::PairingApplet::onRender(bool full)
|
void InkHUD::PairingApplet::onRender()
|
||||||
{
|
{
|
||||||
// Header
|
// Header
|
||||||
setFont(fontMedium);
|
setFont(fontMedium);
|
||||||
@@ -45,7 +45,7 @@ void InkHUD::PairingApplet::onBackground()
|
|||||||
|
|
||||||
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
||||||
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL, true);
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *status)
|
int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *status)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class PairingApplet : public SystemApplet
|
|||||||
public:
|
public:
|
||||||
PairingApplet();
|
PairingApplet();
|
||||||
|
|
||||||
void onRender(bool full) override;
|
void onRender() override;
|
||||||
void onForeground() override;
|
void onForeground() override;
|
||||||
void onBackground() override;
|
void onBackground() override;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
using namespace NicheGraphics;
|
using namespace NicheGraphics;
|
||||||
|
|
||||||
void InkHUD::PlaceholderApplet::onRender(bool full)
|
void InkHUD::PlaceholderApplet::onRender()
|
||||||
{
|
{
|
||||||
// This placeholder applet fills its area with sparse diagonal lines
|
// This placeholder applet fills its area with sparse diagonal lines
|
||||||
hatchRegion(0, 0, width(), height(), 8, BLACK);
|
hatchRegion(0, 0, width(), height(), 8, BLACK);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace NicheGraphics::InkHUD
|
|||||||
class PlaceholderApplet : public SystemApplet
|
class PlaceholderApplet : public SystemApplet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void onRender(bool full) override;
|
void onRender() override;
|
||||||
|
|
||||||
// Note: onForeground, onBackground, and wantsToRender are not meaningful for this applet.
|
// Note: onForeground, onBackground, and wantsToRender are not meaningful for this applet.
|
||||||
// The window manager decides when and where it should be rendered
|
// The window manager decides when and where it should be rendered
|
||||||
|
|||||||
@@ -10,42 +10,39 @@ using namespace NicheGraphics;
|
|||||||
|
|
||||||
InkHUD::TipsApplet::TipsApplet()
|
InkHUD::TipsApplet::TipsApplet()
|
||||||
{
|
{
|
||||||
bool needsRegion = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET);
|
// Decide which tips (if any) should be shown to user after the boot screen
|
||||||
|
|
||||||
bool showTutorialTips = (settings->tips.firstBoot || needsRegion);
|
|
||||||
|
|
||||||
// Welcome screen
|
// Welcome screen
|
||||||
if (showTutorialTips)
|
if (settings->tips.firstBoot)
|
||||||
tipQueue.push_back(Tip::WELCOME);
|
tipQueue.push_back(Tip::WELCOME);
|
||||||
|
|
||||||
// Finish setup
|
// Antenna, region, timezone
|
||||||
if (needsRegion)
|
// Shown at boot if region not yet set
|
||||||
|
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET)
|
||||||
tipQueue.push_back(Tip::FINISH_SETUP);
|
tipQueue.push_back(Tip::FINISH_SETUP);
|
||||||
|
|
||||||
// Using the UI
|
|
||||||
if (showTutorialTips) {
|
|
||||||
tipQueue.push_back(Tip::CUSTOMIZATION);
|
|
||||||
tipQueue.push_back(Tip::BUTTONS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown info
|
// Shutdown info
|
||||||
// Shown until user performs one valid shutdown
|
// Shown until user performs one valid shutdown
|
||||||
if (!settings->tips.safeShutdownSeen)
|
if (!settings->tips.safeShutdownSeen)
|
||||||
tipQueue.push_back(Tip::SAFE_SHUTDOWN);
|
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
|
// Catch an incorrect attempt at rotating display
|
||||||
if (config.display.flip_screen)
|
if (config.display.flip_screen)
|
||||||
tipQueue.push_back(Tip::ROTATION);
|
tipQueue.push_back(Tip::ROTATION);
|
||||||
|
|
||||||
// Region picker
|
// Applet is foreground immediately at boot, but is obscured by LogoApplet, which is also foreground
|
||||||
if (needsRegion)
|
// LogoApplet can be considered to have a higher Z-index, because it is placed before TipsApplet in the systemApplets vector
|
||||||
tipQueue.push_back(Tip::PICK_REGION);
|
|
||||||
|
|
||||||
if (!tipQueue.empty())
|
if (!tipQueue.empty())
|
||||||
bringToForeground();
|
bringToForeground();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::TipsApplet::onRender(bool full)
|
void InkHUD::TipsApplet::onRender()
|
||||||
{
|
{
|
||||||
switch (tipQueue.front()) {
|
switch (tipQueue.front()) {
|
||||||
case Tip::WELCOME:
|
case Tip::WELCOME:
|
||||||
@@ -54,109 +51,81 @@ void InkHUD::TipsApplet::onRender(bool full)
|
|||||||
|
|
||||||
case Tip::FINISH_SETUP: {
|
case Tip::FINISH_SETUP: {
|
||||||
setFont(fontMedium);
|
setFont(fontMedium);
|
||||||
const char *title = "Tip: Finish Setup";
|
printAt(0, 0, "Tip: Finish Setup");
|
||||||
uint16_t h = getWrappedTextHeight(0, width(), title);
|
|
||||||
printWrapped(0, 0, width(), title);
|
|
||||||
|
|
||||||
setFont(fontSmall);
|
setFont(fontSmall);
|
||||||
int16_t cursorY = h + fontSmall.lineHeight();
|
int16_t cursorY = fontMedium.lineHeight() * 1.5;
|
||||||
|
printAt(0, cursorY, "- connect antenna");
|
||||||
|
|
||||||
auto drawBullet = [&](const char *text) {
|
cursorY += fontSmall.lineHeight() * 1.2;
|
||||||
uint16_t bh = getWrappedTextHeight(0, width(), text);
|
printAt(0, cursorY, "- connect a client app");
|
||||||
printWrapped(0, cursorY, width(), text);
|
|
||||||
cursorY += bh + (fontSmall.lineHeight() / 3);
|
|
||||||
};
|
|
||||||
|
|
||||||
drawBullet("- connect antenna");
|
// Only if region not set
|
||||||
drawBullet("- connect a client app");
|
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
|
||||||
|
cursorY += fontSmall.lineHeight() * 1.2;
|
||||||
|
printAt(0, cursorY, "- set region");
|
||||||
|
}
|
||||||
|
|
||||||
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET)
|
// Only if tz not set
|
||||||
drawBullet("- set region");
|
if (!(*config.device.tzdef && config.device.tzdef[0] != 0)) {
|
||||||
|
cursorY += fontSmall.lineHeight() * 1.2;
|
||||||
|
printAt(0, cursorY, "- set timezone");
|
||||||
|
}
|
||||||
|
|
||||||
if (!(*config.device.tzdef && config.device.tzdef[0] != 0))
|
cursorY += fontSmall.lineHeight() * 1.5;
|
||||||
drawBullet("- set timezone");
|
printAt(0, cursorY, "More info at meshtastic.org");
|
||||||
|
|
||||||
cursorY += fontSmall.lineHeight() / 2;
|
|
||||||
drawBullet("More info at meshtastic.org");
|
|
||||||
|
|
||||||
|
setFont(fontSmall);
|
||||||
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
||||||
} break;
|
} 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: {
|
case Tip::SAFE_SHUTDOWN: {
|
||||||
setFont(fontMedium);
|
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);
|
setFont(fontSmall);
|
||||||
int16_t cursorY = h + fontSmall.lineHeight();
|
std::string shutdown;
|
||||||
|
shutdown += "Before removing power, please shut down from InkHUD menu, or a client app. \n";
|
||||||
const char *body = "Before removing power, please shut down from InkHUD menu, or a client app.\n\n"
|
shutdown += "\n";
|
||||||
"This ensures data is saved.";
|
shutdown += "This ensures data is saved.";
|
||||||
|
printWrapped(0, fontMedium.lineHeight() * 1.5, width(), shutdown);
|
||||||
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);
|
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
||||||
|
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Tip::CUSTOMIZATION: {
|
case Tip::CUSTOMIZATION: {
|
||||||
setFont(fontMedium);
|
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);
|
setFont(fontSmall);
|
||||||
int16_t cursorY = h + fontSmall.lineHeight();
|
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
|
||||||
|
"Configure & control display with the InkHUD menu. Optional features, layout, rotation, and more.");
|
||||||
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);
|
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Tip::BUTTONS: {
|
case Tip::BUTTONS: {
|
||||||
setFont(fontMedium);
|
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);
|
setFont(fontSmall);
|
||||||
int16_t cursorY = h + fontSmall.lineHeight();
|
int16_t cursorY = fontMedium.lineHeight() * 1.5;
|
||||||
|
|
||||||
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) {
|
if (!settings->joystick.enabled) {
|
||||||
drawBullet("User Button");
|
printAt(0, cursorY, "User Button");
|
||||||
drawBullet("- short press: next");
|
cursorY += fontSmall.lineHeight() * 1.2;
|
||||||
drawBullet("- long press: select or open menu");
|
printAt(0, cursorY, "- short press: next");
|
||||||
|
cursorY += fontSmall.lineHeight() * 1.2;
|
||||||
|
printAt(0, cursorY, "- long press: select / open menu");
|
||||||
} else {
|
} else {
|
||||||
drawBullet("Joystick");
|
printAt(0, cursorY, "Joystick");
|
||||||
drawBullet("- press: open menu or select");
|
cursorY += fontSmall.lineHeight() * 1.2;
|
||||||
drawBullet("Exit Button");
|
printAt(0, cursorY, "- open menu / select");
|
||||||
drawBullet("- press: switch tile or close menu");
|
cursorY += fontSmall.lineHeight() * 1.5;
|
||||||
|
printAt(0, cursorY, "Exit Button");
|
||||||
|
cursorY += fontSmall.lineHeight() * 1.2;
|
||||||
|
printAt(0, cursorY, "- switch tile / close menu");
|
||||||
}
|
}
|
||||||
|
|
||||||
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
||||||
@@ -164,21 +133,12 @@ void InkHUD::TipsApplet::onRender(bool full)
|
|||||||
|
|
||||||
case Tip::ROTATION: {
|
case Tip::ROTATION: {
|
||||||
setFont(fontMedium);
|
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);
|
setFont(fontSmall);
|
||||||
if (!settings->joystick.enabled) {
|
if (!settings->joystick.enabled) {
|
||||||
int16_t cursorY = h + fontSmall.lineHeight();
|
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
|
||||||
|
"To rotate the display, use the InkHUD menu. Long-press the user button > Options > Rotate.");
|
||||||
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 {
|
} else {
|
||||||
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
|
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
|
||||||
"To rotate the display, use the InkHUD menu. Press the user button > Options > Rotate.");
|
"To rotate the display, use the InkHUD menu. Press the user button > Options > Rotate.");
|
||||||
@@ -199,15 +159,12 @@ void InkHUD::TipsApplet::renderWelcome()
|
|||||||
{
|
{
|
||||||
uint16_t padW = X(0.05);
|
uint16_t padW = X(0.05);
|
||||||
|
|
||||||
// Detect portrait orientation
|
|
||||||
bool portrait = height() > width();
|
|
||||||
|
|
||||||
// Block 1 - logo & title
|
// Block 1 - logo & title
|
||||||
// ========================
|
// ========================
|
||||||
|
|
||||||
// Logo size
|
// Logo size
|
||||||
uint16_t logoWLimit = portrait ? X(0.5) : X(0.3);
|
uint16_t logoWLimit = X(0.3);
|
||||||
uint16_t logoHLimit = portrait ? Y(0.25) : Y(0.3);
|
uint16_t logoHLimit = Y(0.3);
|
||||||
uint16_t logoW = getLogoWidth(logoWLimit, logoHLimit);
|
uint16_t logoW = getLogoWidth(logoWLimit, logoHLimit);
|
||||||
uint16_t logoH = getLogoHeight(logoWLimit, logoHLimit);
|
uint16_t logoH = getLogoHeight(logoWLimit, logoHLimit);
|
||||||
|
|
||||||
@@ -220,7 +177,7 @@ void InkHUD::TipsApplet::renderWelcome()
|
|||||||
|
|
||||||
// Center the block
|
// Center the block
|
||||||
// Desired effect: equal margin from display edge for logo left and title right
|
// Desired effect: equal margin from display edge for logo left and title right
|
||||||
int16_t block1Y = portrait ? Y(0.2) : Y(0.3);
|
int16_t block1Y = Y(0.3);
|
||||||
int16_t block1CX = X(0.5) + (logoW / 2) - (titleW / 2);
|
int16_t block1CX = X(0.5) + (logoW / 2) - (titleW / 2);
|
||||||
int16_t logoCX = block1CX - (logoW / 2) - (padW / 2);
|
int16_t logoCX = block1CX - (logoW / 2) - (padW / 2);
|
||||||
int16_t titleCX = block1CX + (titleW / 2) + (padW / 2);
|
int16_t titleCX = block1CX + (titleW / 2) + (padW / 2);
|
||||||
@@ -235,7 +192,7 @@ void InkHUD::TipsApplet::renderWelcome()
|
|||||||
std::string subtitle = "InkHUD";
|
std::string subtitle = "InkHUD";
|
||||||
if (width() >= 200)
|
if (width() >= 200)
|
||||||
subtitle += " - A Heads-Up Display"; // Future proofing: narrower for tiny display
|
subtitle += " - A Heads-Up Display"; // Future proofing: narrower for tiny display
|
||||||
printAt(X(0.5), portrait ? Y(0.45) : Y(0.6), subtitle, CENTER, MIDDLE);
|
printAt(X(0.5), Y(0.6), subtitle, CENTER, MIDDLE);
|
||||||
|
|
||||||
// Block 3 - press to continue
|
// Block 3 - press to continue
|
||||||
// ============================
|
// ============================
|
||||||
@@ -261,42 +218,32 @@ void InkHUD::TipsApplet::onBackground()
|
|||||||
|
|
||||||
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
||||||
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL, true);
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// While our SystemApplet::handleInput flag is true
|
// While our SystemApplet::handleInput flag is true
|
||||||
void InkHUD::TipsApplet::onButtonShortPress()
|
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();
|
tipQueue.pop_front();
|
||||||
|
|
||||||
// All tips done
|
// All tips done
|
||||||
if (tipQueue.empty()) {
|
if (tipQueue.empty()) {
|
||||||
// Record that user has now seen the "tutorial" set of tips
|
// Record that user has now seen the "tutorial" set of tips
|
||||||
// Don't show them on subsequent boots
|
// Don't show them on subsequent boots
|
||||||
if (settings->tips.firstBoot && !needsRegion) {
|
if (settings->tips.firstBoot) {
|
||||||
settings->tips.firstBoot = false;
|
settings->tips.firstBoot = false;
|
||||||
inkhud->persistence->saveSettings();
|
inkhud->persistence->saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close applet
|
// 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
|
||||||
sendToBackground();
|
sendToBackground();
|
||||||
} else {
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
requestUpdate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// More tips left
|
||||||
|
else
|
||||||
|
requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Functions the same as the user button in this instance
|
// Functions the same as the user button in this instance
|
||||||
@@ -305,4 +252,4 @@ void InkHUD::TipsApplet::onExitShort()
|
|||||||
onButtonShortPress();
|
onButtonShortPress();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -23,7 +23,6 @@ class TipsApplet : public SystemApplet
|
|||||||
enum class Tip {
|
enum class Tip {
|
||||||
WELCOME,
|
WELCOME,
|
||||||
FINISH_SETUP,
|
FINISH_SETUP,
|
||||||
PICK_REGION,
|
|
||||||
SAFE_SHUTDOWN,
|
SAFE_SHUTDOWN,
|
||||||
CUSTOMIZATION,
|
CUSTOMIZATION,
|
||||||
BUTTONS,
|
BUTTONS,
|
||||||
@@ -33,7 +32,7 @@ class TipsApplet : public SystemApplet
|
|||||||
public:
|
public:
|
||||||
TipsApplet();
|
TipsApplet();
|
||||||
|
|
||||||
void onRender(bool full) override;
|
void onRender() override;
|
||||||
void onForeground() override;
|
void onForeground() override;
|
||||||
void onBackground() override;
|
void onBackground() override;
|
||||||
void onButtonShortPress() override;
|
void onButtonShortPress() override;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ int InkHUD::AllMessageApplet::onReceiveTextMessage(const meshtastic_MeshPacket *
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::AllMessageApplet::onRender(bool full)
|
void InkHUD::AllMessageApplet::onRender()
|
||||||
{
|
{
|
||||||
// Find newest message, regardless of whether DM or broadcast
|
// Find newest message, regardless of whether DM or broadcast
|
||||||
MessageStore::Message *message;
|
MessageStore::Message *message;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class Applet;
|
|||||||
class AllMessageApplet : public Applet
|
class AllMessageApplet : public Applet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void onRender(bool full) override;
|
void onRender() override;
|
||||||
|
|
||||||
void onActivate() override;
|
void onActivate() override;
|
||||||
void onDeactivate() override;
|
void onDeactivate() override;
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ int InkHUD::DMApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::DMApplet::onRender(bool full)
|
void InkHUD::DMApplet::onRender()
|
||||||
{
|
{
|
||||||
// Abort if no text message
|
// Abort if no text message
|
||||||
if (!latestMessage->dm.sender) {
|
if (!latestMessage->dm.sender) {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class Applet;
|
|||||||
class DMApplet : public Applet
|
class DMApplet : public Applet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void onRender(bool full) override;
|
void onRender() override;
|
||||||
|
|
||||||
void onActivate() override;
|
void onActivate() override;
|
||||||
void onDeactivate() override;
|
void onDeactivate() override;
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
|
|
||||||
using namespace NicheGraphics;
|
using namespace NicheGraphics;
|
||||||
|
|
||||||
void InkHUD::PositionsApplet::onRender(bool full)
|
void InkHUD::PositionsApplet::onRender()
|
||||||
{
|
{
|
||||||
// Draw the usual map applet first
|
// Draw the usual map applet first
|
||||||
MapApplet::onRender(full);
|
MapApplet::onRender();
|
||||||
|
|
||||||
// Draw our latest "node of interest" as a special marker
|
// Draw our latest "node of interest" as a special marker
|
||||||
// -------------------------------------------------------
|
// -------------------------------------------------------
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class PositionsApplet : public MapApplet, public SinglePortModule
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PositionsApplet() : SinglePortModule("PositionsApplet", meshtastic_PortNum_POSITION_APP) {}
|
PositionsApplet() : SinglePortModule("PositionsApplet", meshtastic_PortNum_POSITION_APP) {}
|
||||||
void onRender(bool full) override;
|
void onRender() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ InkHUD::ThreadedMessageApplet::ThreadedMessageApplet(uint8_t channelIndex)
|
|||||||
store = new MessageStore("ch" + to_string(channelIndex));
|
store = new MessageStore("ch" + to_string(channelIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::ThreadedMessageApplet::onRender(bool full)
|
void InkHUD::ThreadedMessageApplet::onRender()
|
||||||
{
|
{
|
||||||
// =============
|
// =============
|
||||||
// Draw a header
|
// Draw a header
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class ThreadedMessageApplet : public Applet, public SinglePortModule
|
|||||||
explicit ThreadedMessageApplet(uint8_t channelIndex);
|
explicit ThreadedMessageApplet(uint8_t channelIndex);
|
||||||
ThreadedMessageApplet() = delete;
|
ThreadedMessageApplet() = delete;
|
||||||
|
|
||||||
void onRender(bool full) override;
|
void onRender() override;
|
||||||
|
|
||||||
void onActivate() override;
|
void onActivate() override;
|
||||||
void onDeactivate() override;
|
void onDeactivate() override;
|
||||||
|
|||||||
@@ -238,39 +238,6 @@ void InkHUD::Events::onNavRight()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::Events::onFreeText(char c)
|
|
||||||
{
|
|
||||||
// Trigger the first system applet that wants to handle the new character
|
|
||||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
|
||||||
if (sa->handleFreeText) {
|
|
||||||
sa->onFreeText(c);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::Events::onFreeTextDone()
|
|
||||||
{
|
|
||||||
// Trigger the first system applet that wants to handle it
|
|
||||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
|
||||||
if (sa->handleFreeText) {
|
|
||||||
sa->onFreeTextDone();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::Events::onFreeTextCancel()
|
|
||||||
{
|
|
||||||
// Trigger the first system applet that wants to handle it
|
|
||||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
|
||||||
if (sa->handleFreeText) {
|
|
||||||
sa->onFreeTextCancel();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback for deepSleepObserver
|
// Callback for deepSleepObserver
|
||||||
// Returns 0 to signal that we agree to sleep now
|
// Returns 0 to signal that we agree to sleep now
|
||||||
int InkHUD::Events::beforeDeepSleep(void *unused)
|
int InkHUD::Events::beforeDeepSleep(void *unused)
|
||||||
@@ -299,7 +266,7 @@ int InkHUD::Events::beforeDeepSleep(void *unused)
|
|||||||
// then prepared a final powered-off screen for us, which shows device shortname.
|
// then prepared a final powered-off screen for us, which shows device shortname.
|
||||||
// We're updating to show that one now.
|
// We're updating to show that one now.
|
||||||
|
|
||||||
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL, true, false);
|
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL, false);
|
||||||
delay(1000); // Cooldown, before potentially yanking display power
|
delay(1000); // Cooldown, before potentially yanking display power
|
||||||
|
|
||||||
// InkHUD shutdown complete
|
// InkHUD shutdown complete
|
||||||
@@ -309,15 +276,6 @@ int InkHUD::Events::beforeDeepSleep(void *unused)
|
|||||||
return 0; // We agree: deep sleep now
|
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
|
// Callback for rebootObserver
|
||||||
// Same as shutdown, without drawing the logoApplet
|
// Same as shutdown, without drawing the logoApplet
|
||||||
// Makes sure we don't lose message history / InkHUD config
|
// Makes sure we don't lose message history / InkHUD config
|
||||||
|
|||||||
@@ -29,18 +29,12 @@ class Events
|
|||||||
|
|
||||||
void onButtonShort(); // User button: short press
|
void onButtonShort(); // User button: short press
|
||||||
void onButtonLong(); // User button: long press
|
void onButtonLong(); // User button: long press
|
||||||
void applyingChanges();
|
void onExitShort(); // Exit button: short press
|
||||||
void onExitShort(); // Exit button: short press
|
void onExitLong(); // Exit button: long press
|
||||||
void onExitLong(); // Exit button: long press
|
void onNavUp(); // Navigate up
|
||||||
void onNavUp(); // Navigate up
|
void onNavDown(); // Navigate down
|
||||||
void onNavDown(); // Navigate down
|
void onNavLeft(); // Navigate left
|
||||||
void onNavLeft(); // Navigate left
|
void onNavRight(); // Navigate right
|
||||||
void onNavRight(); // Navigate right
|
|
||||||
|
|
||||||
// Free text typing events
|
|
||||||
void onFreeText(char c); // New freetext character input
|
|
||||||
void onFreeTextDone();
|
|
||||||
void onFreeTextCancel();
|
|
||||||
|
|
||||||
int beforeDeepSleep(void *unused); // Prepare for shutdown
|
int beforeDeepSleep(void *unused); // Prepare for shutdown
|
||||||
int beforeReboot(void *unused); // Prepare for reboot
|
int beforeReboot(void *unused); // Prepare for reboot
|
||||||
|
|||||||
@@ -53,13 +53,6 @@ void InkHUD::InkHUD::addApplet(const char *name, Applet *a, bool defaultActive,
|
|||||||
windowManager->addApplet(name, a, defaultActive, defaultAutoshow, onTile);
|
windowManager->addApplet(name, a, defaultActive, defaultAutoshow, onTile);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::InkHUD::notifyApplyingChanges()
|
|
||||||
{
|
|
||||||
if (events) {
|
|
||||||
events->applyingChanges();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start InkHUD!
|
// Start InkHUD!
|
||||||
// Call this only after you have configured InkHUD
|
// Call this only after you have configured InkHUD
|
||||||
void InkHUD::InkHUD::begin()
|
void InkHUD::InkHUD::begin()
|
||||||
@@ -175,25 +168,6 @@ void InkHUD::InkHUD::navRight()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call this for keyboard input
|
|
||||||
// The Keyboard Applet also calls this
|
|
||||||
void InkHUD::InkHUD::freeText(char c)
|
|
||||||
{
|
|
||||||
events->onFreeText(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call this to complete a freetext input
|
|
||||||
void InkHUD::InkHUD::freeTextDone()
|
|
||||||
{
|
|
||||||
events->onFreeTextDone();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call this to cancel a freetext input
|
|
||||||
void InkHUD::InkHUD::freeTextCancel()
|
|
||||||
{
|
|
||||||
events->onFreeTextCancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cycle the next user applet to the foreground
|
// Cycle the next user applet to the foreground
|
||||||
// Only activated applets are cycled
|
// Only activated applets are cycled
|
||||||
// If user has a multi-applet layout, the applets will cycle on the "focused tile"
|
// If user has a multi-applet layout, the applets will cycle on the "focused tile"
|
||||||
@@ -223,18 +197,6 @@ void InkHUD::InkHUD::openAlignStick()
|
|||||||
windowManager->openAlignStick();
|
windowManager->openAlignStick();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the on-screen keyboard
|
|
||||||
void InkHUD::InkHUD::openKeyboard()
|
|
||||||
{
|
|
||||||
windowManager->openKeyboard();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the on-screen keyboard
|
|
||||||
void InkHUD::InkHUD::closeKeyboard()
|
|
||||||
{
|
|
||||||
windowManager->closeKeyboard();
|
|
||||||
}
|
|
||||||
|
|
||||||
// In layouts where multiple applets are shown at once, change which tile is focused
|
// In layouts where multiple applets are shown at once, change which tile is focused
|
||||||
// The focused tile in the one which cycles applets on button short press, and displays menu on long press
|
// The focused tile in the one which cycles applets on button short press, and displays menu on long press
|
||||||
void InkHUD::InkHUD::nextTile()
|
void InkHUD::InkHUD::nextTile()
|
||||||
@@ -283,11 +245,10 @@ void InkHUD::InkHUD::requestUpdate()
|
|||||||
// Ignores all diplomacy:
|
// Ignores all diplomacy:
|
||||||
// - the display *will* update
|
// - the display *will* update
|
||||||
// - the specified update type *will* be used
|
// - the specified update type *will* be used
|
||||||
// If the all parameter is true, the whole screen buffer is cleared and re-rendered
|
|
||||||
// If the async parameter is false, code flow is blocked while the update takes place
|
// If the async parameter is false, code flow is blocked while the update takes place
|
||||||
void InkHUD::InkHUD::forceUpdate(EInk::UpdateTypes type, bool all, bool async)
|
void InkHUD::InkHUD::forceUpdate(EInk::UpdateTypes type, bool async)
|
||||||
{
|
{
|
||||||
renderer->forceUpdate(type, all, async);
|
renderer->forceUpdate(type, async);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for any in-progress display update to complete before continuing
|
// Wait for any in-progress display update to complete before continuing
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ class InkHUD
|
|||||||
void setDriver(Drivers::EInk *driver);
|
void setDriver(Drivers::EInk *driver);
|
||||||
void setDisplayResilience(uint8_t fastPerFull = 5, float stressMultiplier = 2.0);
|
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 addApplet(const char *name, Applet *a, bool defaultActive = false, bool defaultAutoshow = false, uint8_t onTile = -1);
|
||||||
void notifyApplyingChanges();
|
|
||||||
|
|
||||||
void begin();
|
void begin();
|
||||||
|
|
||||||
@@ -63,11 +62,6 @@ class InkHUD
|
|||||||
void navLeft();
|
void navLeft();
|
||||||
void navRight();
|
void navRight();
|
||||||
|
|
||||||
// Freetext handlers
|
|
||||||
void freeText(char c);
|
|
||||||
void freeTextDone();
|
|
||||||
void freeTextCancel();
|
|
||||||
|
|
||||||
// Trigger UI changes
|
// Trigger UI changes
|
||||||
// - called by various InkHUD components
|
// - called by various InkHUD components
|
||||||
// - suitable(?) for use by aux button, connected in variant nicheGraphics.h
|
// - suitable(?) for use by aux button, connected in variant nicheGraphics.h
|
||||||
@@ -76,23 +70,17 @@ class InkHUD
|
|||||||
void prevApplet();
|
void prevApplet();
|
||||||
void openMenu();
|
void openMenu();
|
||||||
void openAlignStick();
|
void openAlignStick();
|
||||||
void openKeyboard();
|
|
||||||
void closeKeyboard();
|
|
||||||
void nextTile();
|
void nextTile();
|
||||||
void prevTile();
|
void prevTile();
|
||||||
void rotate();
|
void rotate();
|
||||||
void rotateJoystick(uint8_t angle = 1); // rotate 90 deg by default
|
void rotateJoystick(uint8_t angle = 1); // rotate 90 deg by default
|
||||||
void toggleBatteryIcon();
|
void toggleBatteryIcon();
|
||||||
|
|
||||||
// Used by TipsApplet to force menu to start on Region selection
|
|
||||||
bool forceRegionMenu = false;
|
|
||||||
|
|
||||||
// Updating the display
|
// Updating the display
|
||||||
// - called by various InkHUD components
|
// - called by various InkHUD components
|
||||||
|
|
||||||
void requestUpdate();
|
void requestUpdate();
|
||||||
void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool all = false,
|
void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool async = true);
|
||||||
bool async = true);
|
|
||||||
void awaitUpdate();
|
void awaitUpdate();
|
||||||
|
|
||||||
// (Re)configuring WindowManager
|
// (Re)configuring WindowManager
|
||||||
|
|||||||
@@ -56,16 +56,15 @@ void InkHUD::Renderer::setDisplayResilience(uint8_t fastPerFull, float stressMul
|
|||||||
|
|
||||||
void InkHUD::Renderer::begin()
|
void InkHUD::Renderer::begin()
|
||||||
{
|
{
|
||||||
forceUpdate(Drivers::EInk::UpdateTypes::FULL, true, false);
|
forceUpdate(Drivers::EInk::UpdateTypes::FULL, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a flag, which will be picked up by runOnce, ASAP.
|
// Set a flag, which will be picked up by runOnce, ASAP.
|
||||||
// Quite likely, multiple applets will all want to respond to one event (Observable, etc)
|
// Quite likely, multiple applets will all want to respond to one event (Observable, etc)
|
||||||
// Each affected applet can independently call requestUpdate(), and all share the one opportunity to render, at next runOnce
|
// Each affected applet can independently call requestUpdate(), and all share the one opportunity to render, at next runOnce
|
||||||
void InkHUD::Renderer::requestUpdate(bool all)
|
void InkHUD::Renderer::requestUpdate()
|
||||||
{
|
{
|
||||||
requested = true;
|
requested = true;
|
||||||
renderAll |= all;
|
|
||||||
|
|
||||||
// We will run the thread as soon as we loop(),
|
// We will run the thread as soon as we loop(),
|
||||||
// after all Applets have had a chance to observe whatever event set this off
|
// after all Applets have had a chance to observe whatever event set this off
|
||||||
@@ -80,11 +79,10 @@ void InkHUD::Renderer::requestUpdate(bool all)
|
|||||||
// Sometimes, however, we will want to trigger a display update manually, in the absence of any sort of applet event
|
// Sometimes, however, we will want to trigger a display update manually, in the absence of any sort of applet event
|
||||||
// Display health, for example.
|
// Display health, for example.
|
||||||
// In these situations, we use forceUpdate
|
// In these situations, we use forceUpdate
|
||||||
void InkHUD::Renderer::forceUpdate(Drivers::EInk::UpdateTypes type, bool all, bool async)
|
void InkHUD::Renderer::forceUpdate(Drivers::EInk::UpdateTypes type, bool async)
|
||||||
{
|
{
|
||||||
requested = true;
|
requested = true;
|
||||||
forced = true;
|
forced = true;
|
||||||
renderAll |= all;
|
|
||||||
displayHealth.forceUpdateType(type);
|
displayHealth.forceUpdateType(type);
|
||||||
|
|
||||||
// Normally, we need to start the timer, in case the display is busy and we briefly defer the update
|
// Normally, we need to start the timer, in case the display is busy and we briefly defer the update
|
||||||
@@ -221,8 +219,7 @@ void InkHUD::Renderer::render(bool async)
|
|||||||
Drivers::EInk::UpdateTypes updateType = decideUpdateType();
|
Drivers::EInk::UpdateTypes updateType = decideUpdateType();
|
||||||
|
|
||||||
// Render the new image
|
// Render the new image
|
||||||
if (renderAll)
|
clearBuffer();
|
||||||
clearBuffer();
|
|
||||||
renderUserApplets();
|
renderUserApplets();
|
||||||
renderPlaceholders();
|
renderPlaceholders();
|
||||||
renderSystemApplets();
|
renderSystemApplets();
|
||||||
@@ -250,7 +247,6 @@ void InkHUD::Renderer::render(bool async)
|
|||||||
// Tidy up, ready for a new request
|
// Tidy up, ready for a new request
|
||||||
requested = false;
|
requested = false;
|
||||||
forced = false;
|
forced = false;
|
||||||
renderAll = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manually fill the image buffer with WHITE
|
// Manually fill the image buffer with WHITE
|
||||||
@@ -263,76 +259,6 @@ void InkHUD::Renderer::clearBuffer()
|
|||||||
memset(imageBuffer, 0xFF, imageBufferHeight * imageBufferWidth);
|
memset(imageBuffer, 0xFF, imageBufferHeight * imageBufferWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manually clear the pixels below a tile
|
|
||||||
void InkHUD::Renderer::clearTile(Tile *t)
|
|
||||||
{
|
|
||||||
// Rotate the tile dimensions
|
|
||||||
int16_t left = 0;
|
|
||||||
int16_t top = 0;
|
|
||||||
uint16_t width = 0;
|
|
||||||
uint16_t height = 0;
|
|
||||||
switch (settings->rotation) {
|
|
||||||
case 0:
|
|
||||||
left = t->getLeft();
|
|
||||||
top = t->getTop();
|
|
||||||
width = t->getWidth();
|
|
||||||
height = t->getHeight();
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
left = driver->width - (t->getTop() + t->getHeight());
|
|
||||||
top = t->getLeft();
|
|
||||||
width = t->getHeight();
|
|
||||||
height = t->getWidth();
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
left = driver->width - (t->getLeft() + t->getWidth());
|
|
||||||
top = driver->height - (t->getTop() + t->getHeight());
|
|
||||||
width = t->getWidth();
|
|
||||||
height = t->getHeight();
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
left = t->getTop();
|
|
||||||
top = driver->height - (t->getLeft() + t->getWidth());
|
|
||||||
width = t->getHeight();
|
|
||||||
height = t->getWidth();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the bounds to clear
|
|
||||||
uint16_t xStart = (left < 0) ? 0 : left;
|
|
||||||
uint16_t yStart = (top < 0) ? 0 : top;
|
|
||||||
if (xStart >= driver->width || yStart >= driver->height || left + width < 0 || top + height < 0)
|
|
||||||
return; // the box is completely off the screen
|
|
||||||
uint16_t xEnd = left + width;
|
|
||||||
uint16_t yEnd = top + height;
|
|
||||||
if (xEnd > driver->width)
|
|
||||||
xEnd = driver->width;
|
|
||||||
if (yEnd > driver->height)
|
|
||||||
yEnd = driver->height;
|
|
||||||
|
|
||||||
// Clear the pixels
|
|
||||||
if (xStart == 0 && xEnd == driver->width) { // full width box is easier to clear
|
|
||||||
memset(imageBuffer + (yStart * imageBufferWidth), 0xFF, (yEnd - yStart) * imageBufferWidth);
|
|
||||||
} else {
|
|
||||||
const uint16_t byteStart = (xStart / 8) + 1;
|
|
||||||
const uint16_t byteEnd = xEnd / 8;
|
|
||||||
const uint8_t leadingByte = 0xFF >> (xStart - ((byteStart - 1) * 8));
|
|
||||||
const uint8_t trailingByte = (0xFF00 >> (xEnd - (byteEnd * 8))) & 0xFF;
|
|
||||||
for (uint16_t i = yStart * imageBufferWidth; i < yEnd * imageBufferWidth; i += imageBufferWidth) {
|
|
||||||
// Set the leading byte
|
|
||||||
imageBuffer[i + byteStart - 1] |= leadingByte;
|
|
||||||
|
|
||||||
// Set the continuous bytes
|
|
||||||
if (byteStart < byteEnd)
|
|
||||||
memset(imageBuffer + i + byteStart, 0xFF, byteEnd - byteStart);
|
|
||||||
|
|
||||||
// Set the trailing byte
|
|
||||||
if (byteEnd != imageBufferWidth)
|
|
||||||
imageBuffer[i + byteEnd] |= trailingByte;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::Renderer::checkLocks()
|
void InkHUD::Renderer::checkLocks()
|
||||||
{
|
{
|
||||||
lockRendering = nullptr;
|
lockRendering = nullptr;
|
||||||
@@ -397,12 +323,12 @@ Drivers::EInk::UpdateTypes InkHUD::Renderer::decideUpdateType()
|
|||||||
if (!forced) {
|
if (!forced) {
|
||||||
// User applets
|
// User applets
|
||||||
for (Applet *ua : inkhud->userApplets) {
|
for (Applet *ua : inkhud->userApplets) {
|
||||||
if (ua && ua->isForeground() && (ua->wantsToRender() || renderAll))
|
if (ua && ua->isForeground())
|
||||||
displayHealth.requestUpdateType(ua->wantsUpdateType());
|
displayHealth.requestUpdateType(ua->wantsUpdateType());
|
||||||
}
|
}
|
||||||
// System Applets
|
// System Applets
|
||||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
for (SystemApplet *sa : inkhud->systemApplets) {
|
||||||
if (sa && sa->isForeground() && (sa->wantsToRender() || sa->alwaysRender || renderAll))
|
if (sa && sa->isForeground())
|
||||||
displayHealth.requestUpdateType(sa->wantsUpdateType());
|
displayHealth.requestUpdateType(sa->wantsUpdateType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -420,16 +346,9 @@ void InkHUD::Renderer::renderUserApplets()
|
|||||||
|
|
||||||
// Render any user applets which are currently visible
|
// Render any user applets which are currently visible
|
||||||
for (Applet *ua : inkhud->userApplets) {
|
for (Applet *ua : inkhud->userApplets) {
|
||||||
if (ua && ua->isActive() && ua->isForeground() && (ua->wantsToRender() || renderAll)) {
|
if (ua && ua->isActive() && ua->isForeground()) {
|
||||||
|
|
||||||
// Clear the tile unless the applet wants to draw over its previous render
|
|
||||||
// or everything is getting re-rendered anyways
|
|
||||||
if (ua->wantsFullRender() && !renderAll)
|
|
||||||
clearTile(ua->getTile());
|
|
||||||
|
|
||||||
uint32_t start = millis();
|
uint32_t start = millis();
|
||||||
bool full = ua->wantsFullRender() || renderAll;
|
ua->render(); // Draw!
|
||||||
ua->render(full); // Draw!
|
|
||||||
uint32_t stop = millis();
|
uint32_t stop = millis();
|
||||||
LOG_DEBUG("%s took %dms to render", ua->name, stop - start);
|
LOG_DEBUG("%s took %dms to render", ua->name, stop - start);
|
||||||
}
|
}
|
||||||
@@ -451,9 +370,6 @@ void InkHUD::Renderer::renderSystemApplets()
|
|||||||
if (!sa->isForeground())
|
if (!sa->isForeground())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!sa->wantsToRender() && !sa->alwaysRender && !renderAll)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Skip if locked by another applet
|
// Skip if locked by another applet
|
||||||
if (lockRendering && lockRendering != sa)
|
if (lockRendering && lockRendering != sa)
|
||||||
continue;
|
continue;
|
||||||
@@ -465,14 +381,8 @@ void InkHUD::Renderer::renderSystemApplets()
|
|||||||
|
|
||||||
assert(sa->getTile());
|
assert(sa->getTile());
|
||||||
|
|
||||||
// Clear the tile unless the applet wants to draw over its previous render
|
|
||||||
// or everything is getting re-rendered anyways
|
|
||||||
if (sa->wantsFullRender() && !renderAll)
|
|
||||||
clearTile(sa->getTile());
|
|
||||||
|
|
||||||
// uint32_t start = millis();
|
// uint32_t start = millis();
|
||||||
bool full = sa->wantsFullRender() || renderAll;
|
sa->render(); // Draw!
|
||||||
sa->render(full); // Draw!
|
|
||||||
// uint32_t stop = millis();
|
// uint32_t stop = millis();
|
||||||
// LOG_DEBUG("%s took %dms to render", sa->name, stop - start);
|
// LOG_DEBUG("%s took %dms to render", sa->name, stop - start);
|
||||||
}
|
}
|
||||||
@@ -499,10 +409,7 @@ void InkHUD::Renderer::renderPlaceholders()
|
|||||||
// uint32_t start = millis();
|
// uint32_t start = millis();
|
||||||
for (Tile *t : emptyTiles) {
|
for (Tile *t : emptyTiles) {
|
||||||
t->assignApplet(placeholder);
|
t->assignApplet(placeholder);
|
||||||
// Clear the tile unless everything is getting re-rendered
|
placeholder->render();
|
||||||
if (!renderAll)
|
|
||||||
clearTile(t);
|
|
||||||
placeholder->render(true); // full render
|
|
||||||
t->assignApplet(nullptr);
|
t->assignApplet(nullptr);
|
||||||
}
|
}
|
||||||
// uint32_t stop = millis();
|
// uint32_t stop = millis();
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ class Renderer : protected concurrency::OSThread
|
|||||||
|
|
||||||
// Call these to make the image change
|
// Call these to make the image change
|
||||||
|
|
||||||
void requestUpdate(bool all = false); // Update display, if a foreground applet has info it wants to show
|
void requestUpdate(); // Update display, if a foreground applet has info it wants to show
|
||||||
void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool all = false,
|
void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED,
|
||||||
bool async = true); // Update display, regardless of whether any applets requested this
|
bool async = true); // Update display, regardless of whether any applets requested this
|
||||||
|
|
||||||
// Wait for an update to complete
|
// Wait for an update to complete
|
||||||
@@ -65,7 +65,6 @@ class Renderer : protected concurrency::OSThread
|
|||||||
// Steps of the rendering process
|
// Steps of the rendering process
|
||||||
|
|
||||||
void clearBuffer();
|
void clearBuffer();
|
||||||
void clearTile(Tile *t);
|
|
||||||
void checkLocks();
|
void checkLocks();
|
||||||
bool shouldUpdate();
|
bool shouldUpdate();
|
||||||
Drivers::EInk::UpdateTypes decideUpdateType();
|
Drivers::EInk::UpdateTypes decideUpdateType();
|
||||||
@@ -86,7 +85,6 @@ class Renderer : protected concurrency::OSThread
|
|||||||
|
|
||||||
bool requested = false;
|
bool requested = false;
|
||||||
bool forced = false;
|
bool forced = false;
|
||||||
bool renderAll = false;
|
|
||||||
|
|
||||||
// For convenience
|
// For convenience
|
||||||
InkHUD *inkhud = nullptr;
|
InkHUD *inkhud = nullptr;
|
||||||
|
|||||||
@@ -22,14 +22,11 @@ class SystemApplet : public Applet
|
|||||||
public:
|
public:
|
||||||
// System applets have the right to:
|
// System applets have the right to:
|
||||||
|
|
||||||
bool handleInput = false; // - respond to input from the user button
|
bool handleInput = false; // - respond to input from the user button
|
||||||
bool handleFreeText = false; // - respond to free text input
|
bool lockRendering = false; // - prevent other applets from being rendered during an update
|
||||||
bool lockRendering = false; // - prevent other applets from being rendered during an update
|
bool lockRequests = false; // - prevent other applets from triggering display updates
|
||||||
bool lockRequests = false; // - prevent other applets from triggering display updates
|
|
||||||
bool alwaysRender = false; // - render every time the screen is updated
|
|
||||||
|
|
||||||
virtual void onReboot() { onShutdown(); } // - handle reboot specially
|
virtual void onReboot() { onShutdown(); } // - handle reboot specially
|
||||||
virtual void onApplyingChanges() {}
|
|
||||||
|
|
||||||
// Other system applets may take precedence over our own system applet though
|
// 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)
|
// The order an applet is passed to WindowManager::addSystemApplet determines this hierarchy (added earlier = higher rank)
|
||||||
@@ -43,4 +40,4 @@ class SystemApplet : public Applet
|
|||||||
|
|
||||||
}; // namespace NicheGraphics::InkHUD
|
}; // namespace NicheGraphics::InkHUD
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -18,7 +18,7 @@ static int32_t runtaskHighlight()
|
|||||||
LOG_DEBUG("Dismissing Highlight");
|
LOG_DEBUG("Dismissing Highlight");
|
||||||
InkHUD::Tile::highlightShown = false;
|
InkHUD::Tile::highlightShown = false;
|
||||||
InkHUD::Tile::highlightTarget = nullptr;
|
InkHUD::Tile::highlightTarget = nullptr;
|
||||||
InkHUD::InkHUD::getInstance()->forceUpdate(Drivers::EInk::UpdateTypes::FAST, true); // Re-render, clearing the highlighting
|
InkHUD::InkHUD::getInstance()->forceUpdate(Drivers::EInk::UpdateTypes::FAST); // Re-render, clearing the highlighting
|
||||||
return taskHighlight->disable();
|
return taskHighlight->disable();
|
||||||
}
|
}
|
||||||
static void inittaskHighlight()
|
static void inittaskHighlight()
|
||||||
@@ -190,18 +190,6 @@ void InkHUD::Tile::handleAppletPixel(int16_t x, int16_t y, Color c)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used in Renderer for clearing the tile
|
|
||||||
int16_t InkHUD::Tile::getLeft()
|
|
||||||
{
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used in Renderer for clearing the tile
|
|
||||||
int16_t InkHUD::Tile::getTop()
|
|
||||||
{
|
|
||||||
return top;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called by Applet base class, when setting applet dimensions, immediately before render
|
// Called by Applet base class, when setting applet dimensions, immediately before render
|
||||||
uint16_t InkHUD::Tile::getWidth()
|
uint16_t InkHUD::Tile::getWidth()
|
||||||
{
|
{
|
||||||
@@ -232,7 +220,7 @@ void InkHUD::Tile::requestHighlight()
|
|||||||
{
|
{
|
||||||
Tile::highlightTarget = this;
|
Tile::highlightTarget = this;
|
||||||
Tile::highlightShown = false;
|
Tile::highlightShown = false;
|
||||||
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FAST, true);
|
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FAST);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts the timer which will automatically dismiss the highlighting, if the tile doesn't organically redraw first
|
// Starts the timer which will automatically dismiss the highlighting, if the tile doesn't organically redraw first
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ class Tile
|
|||||||
void setRegion(uint8_t layoutSize, uint8_t tileIndex); // Assign region automatically, based on layout
|
void setRegion(uint8_t layoutSize, uint8_t tileIndex); // Assign region automatically, based on layout
|
||||||
void setRegion(int16_t left, int16_t top, uint16_t width, uint16_t height); // Assign region manually
|
void setRegion(int16_t left, int16_t top, uint16_t width, uint16_t height); // Assign region manually
|
||||||
void handleAppletPixel(int16_t x, int16_t y, Color c); // Receive px output from assigned applet
|
void handleAppletPixel(int16_t x, int16_t y, Color c); // Receive px output from assigned applet
|
||||||
int16_t getLeft();
|
|
||||||
int16_t getTop();
|
|
||||||
uint16_t getWidth();
|
uint16_t getWidth();
|
||||||
uint16_t getHeight();
|
uint16_t getHeight();
|
||||||
static uint16_t maxDisplayDimension(); // Largest possible width / height any tile may ever encounter
|
static uint16_t maxDisplayDimension(); // Largest possible width / height any tile may ever encounter
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
#include "./Applets/System/AlignStick/AlignStickApplet.h"
|
#include "./Applets/System/AlignStick/AlignStickApplet.h"
|
||||||
#include "./Applets/System/BatteryIcon/BatteryIconApplet.h"
|
#include "./Applets/System/BatteryIcon/BatteryIconApplet.h"
|
||||||
#include "./Applets/System/Keyboard/KeyboardApplet.h"
|
|
||||||
#include "./Applets/System/Logo/LogoApplet.h"
|
#include "./Applets/System/Logo/LogoApplet.h"
|
||||||
#include "./Applets/System/Menu/MenuApplet.h"
|
#include "./Applets/System/Menu/MenuApplet.h"
|
||||||
#include "./Applets/System/Notification/NotificationApplet.h"
|
#include "./Applets/System/Notification/NotificationApplet.h"
|
||||||
@@ -149,28 +148,6 @@ void InkHUD::WindowManager::openAlignStick()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::WindowManager::openKeyboard()
|
|
||||||
{
|
|
||||||
KeyboardApplet *keyboard = (KeyboardApplet *)inkhud->getSystemApplet("Keyboard");
|
|
||||||
|
|
||||||
if (keyboard) {
|
|
||||||
keyboard->bringToForeground();
|
|
||||||
keyboardOpen = true;
|
|
||||||
changeLayout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::WindowManager::closeKeyboard()
|
|
||||||
{
|
|
||||||
KeyboardApplet *keyboard = (KeyboardApplet *)inkhud->getSystemApplet("Keyboard");
|
|
||||||
|
|
||||||
if (keyboard) {
|
|
||||||
keyboard->sendToBackground();
|
|
||||||
keyboardOpen = false;
|
|
||||||
changeLayout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// On the currently focussed tile: cycle to the next available user applet
|
// On the currently focussed tile: cycle to the next available user applet
|
||||||
// Applets available for this must be activated, and not already displayed on another tile
|
// Applets available for this must be activated, and not already displayed on another tile
|
||||||
void InkHUD::WindowManager::nextApplet()
|
void InkHUD::WindowManager::nextApplet()
|
||||||
@@ -295,6 +272,7 @@ void InkHUD::WindowManager::toggleBatteryIcon()
|
|||||||
batteryIcon->sendToBackground();
|
batteryIcon->sendToBackground();
|
||||||
|
|
||||||
// Force-render
|
// Force-render
|
||||||
|
// - redraw all applets
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,25 +311,9 @@ void InkHUD::WindowManager::changeLayout()
|
|||||||
menu->show(ft);
|
menu->show(ft);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resize for the on-screen keyboard
|
|
||||||
if (keyboardOpen) {
|
|
||||||
// Send all user applets to the background
|
|
||||||
// User applets currently don't handle free text input
|
|
||||||
for (uint8_t i = 0; i < inkhud->userApplets.size(); i++)
|
|
||||||
inkhud->userApplets.at(i)->sendToBackground();
|
|
||||||
// Find the first system applet that can handle freetext and resize it
|
|
||||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
|
||||||
if (sa->handleFreeText) {
|
|
||||||
const uint16_t keyboardHeight = KeyboardApplet::getKeyboardHeight();
|
|
||||||
sa->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height() - keyboardHeight - 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force-render
|
// Force-render
|
||||||
// - redraw all applets
|
// - redraw all applets
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FAST, true);
|
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform necessary reconfiguration when user activates or deactivates applets at run-time
|
// Perform necessary reconfiguration when user activates or deactivates applets at run-time
|
||||||
@@ -385,7 +347,7 @@ void InkHUD::WindowManager::changeActivatedApplets()
|
|||||||
|
|
||||||
// Force-render
|
// Force-render
|
||||||
// - redraw all applets
|
// - redraw all applets
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FAST, true);
|
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some applets may be permitted to bring themselves to foreground, to show new data
|
// Some applets may be permitted to bring themselves to foreground, to show new data
|
||||||
@@ -471,10 +433,8 @@ void InkHUD::WindowManager::createSystemApplets()
|
|||||||
addSystemApplet("Logo", new LogoApplet, new Tile);
|
addSystemApplet("Logo", new LogoApplet, new Tile);
|
||||||
addSystemApplet("Pairing", new PairingApplet, new Tile);
|
addSystemApplet("Pairing", new PairingApplet, new Tile);
|
||||||
addSystemApplet("Tips", new TipsApplet, new Tile);
|
addSystemApplet("Tips", new TipsApplet, new Tile);
|
||||||
if (settings->joystick.enabled) {
|
if (settings->joystick.enabled)
|
||||||
addSystemApplet("AlignStick", new AlignStickApplet, new Tile);
|
addSystemApplet("AlignStick", new AlignStickApplet, new Tile);
|
||||||
addSystemApplet("Keyboard", new KeyboardApplet, new Tile);
|
|
||||||
}
|
|
||||||
|
|
||||||
addSystemApplet("Menu", new MenuApplet, nullptr);
|
addSystemApplet("Menu", new MenuApplet, nullptr);
|
||||||
|
|
||||||
@@ -497,13 +457,9 @@ void InkHUD::WindowManager::placeSystemTiles()
|
|||||||
inkhud->getSystemApplet("Logo")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
inkhud->getSystemApplet("Logo")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
||||||
inkhud->getSystemApplet("Pairing")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
inkhud->getSystemApplet("Pairing")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
||||||
inkhud->getSystemApplet("Tips")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
inkhud->getSystemApplet("Tips")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
||||||
if (settings->joystick.enabled) {
|
if (settings->joystick.enabled)
|
||||||
inkhud->getSystemApplet("AlignStick")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
inkhud->getSystemApplet("AlignStick")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
||||||
const uint16_t keyboardHeight = KeyboardApplet::getKeyboardHeight();
|
|
||||||
inkhud->getSystemApplet("Keyboard")
|
|
||||||
->getTile()
|
|
||||||
->setRegion(0, inkhud->height() - keyboardHeight, inkhud->width(), keyboardHeight);
|
|
||||||
}
|
|
||||||
inkhud->getSystemApplet("Notification")->getTile()->setRegion(0, 0, inkhud->width(), 20);
|
inkhud->getSystemApplet("Notification")->getTile()->setRegion(0, 0, inkhud->width(), 20);
|
||||||
|
|
||||||
const uint16_t batteryIconHeight = Applet::getHeaderHeight() - 2 - 2;
|
const uint16_t batteryIconHeight = Applet::getHeaderHeight() - 2 - 2;
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ class WindowManager
|
|||||||
void prevTile();
|
void prevTile();
|
||||||
void openMenu();
|
void openMenu();
|
||||||
void openAlignStick();
|
void openAlignStick();
|
||||||
void openKeyboard();
|
|
||||||
void closeKeyboard();
|
|
||||||
void nextApplet();
|
void nextApplet();
|
||||||
void prevApplet();
|
void prevApplet();
|
||||||
void rotate();
|
void rotate();
|
||||||
@@ -66,7 +64,6 @@ class WindowManager
|
|||||||
void findOrphanApplets(); // Find any applets left-behind when layout changes
|
void findOrphanApplets(); // Find any applets left-behind when layout changes
|
||||||
|
|
||||||
std::vector<Tile *> userTiles; // Tiles which can host user applets
|
std::vector<Tile *> userTiles; // Tiles which can host user applets
|
||||||
bool keyboardOpen = false;
|
|
||||||
|
|
||||||
// For convenience
|
// For convenience
|
||||||
InkHUD *inkhud = nullptr;
|
InkHUD *inkhud = nullptr;
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ class BasicExampleApplet : public Applet
|
|||||||
// You must have an onRender() method
|
// You must have an onRender() method
|
||||||
// All drawing happens here
|
// All drawing happens here
|
||||||
|
|
||||||
void onRender(bool full) override;
|
void onRender() override;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ The `onRender` method is called when the display image is redrawn. This can happ
|
|||||||
```cpp
|
```cpp
|
||||||
// All drawing happens here
|
// All drawing happens here
|
||||||
// Our basic example doesn't do anything useful. It just passively prints some text.
|
// Our basic example doesn't do anything useful. It just passively prints some text.
|
||||||
void InkHUD::BasicExampleApplet::onRender(bool full)
|
void InkHUD::BasicExampleApplet::onRender()
|
||||||
{
|
{
|
||||||
printAt(0, 0, "Hello, world!");
|
printAt(0, 0, "Hello, world!");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,20 +20,20 @@ constexpr uint8_t modifierLeftShift = 0b0001;
|
|||||||
|
|
||||||
// Num chars per key, Modulus for rotating through characters
|
// Num chars per key, Modulus for rotating through characters
|
||||||
static uint8_t HackadayCommunicatorTapMod[_TCA8418_NUM_KEYS] = {
|
static uint8_t HackadayCommunicatorTapMod[_TCA8418_NUM_KEYS] = {
|
||||||
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, 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,
|
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] = {{},
|
static unsigned char HackadayCommunicatorTapMap[_TCA8418_NUM_KEYS][2] = {{},
|
||||||
{Key::FUNCTION_F1},
|
{},
|
||||||
{'+'},
|
{'+'},
|
||||||
{'9'},
|
{'9'},
|
||||||
{'8'},
|
{'8'},
|
||||||
{'7'},
|
{'7'},
|
||||||
{Key::FUNCTION_F2},
|
{'2'},
|
||||||
{Key::FUNCTION_F3},
|
{'3'},
|
||||||
{Key::FUNCTION_F4},
|
{'4'},
|
||||||
{Key::FUNCTION_F5},
|
{'5'},
|
||||||
{Key::ESC},
|
{Key::ESC},
|
||||||
{'q', 'Q'},
|
{'q', 'Q'},
|
||||||
{'w', 'W'},
|
{'w', 'W'},
|
||||||
@@ -141,7 +141,6 @@ void HackadayCommunicatorKeyboard::pressed(uint8_t key)
|
|||||||
if (state == Init || state == Busy) {
|
if (state == Init || state == Busy) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LOG_DEBUG("Key pressed: %u", key);
|
|
||||||
|
|
||||||
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
|
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
|
||||||
modifierFlag = 0;
|
modifierFlag = 0;
|
||||||
|
|||||||
@@ -1,59 +1,8 @@
|
|||||||
#include "InputBroker.h"
|
#include "InputBroker.h"
|
||||||
#include "PowerFSM.h" // needed for event trigger
|
#include "PowerFSM.h" // needed for event trigger
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "graphics/Screen.h"
|
|
||||||
#include "modules/ExternalNotificationModule.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 = nullptr;
|
||||||
|
|
||||||
InputBroker::InputBroker()
|
InputBroker::InputBroker()
|
||||||
@@ -125,262 +74,3 @@ void InputBroker::pollSoonWorker(void *p)
|
|||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
}
|
}
|
||||||
#endif
|
#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,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Observer.h"
|
#include "Observer.h"
|
||||||
#include "concurrency/OSThread.h"
|
|
||||||
#include "freertosinc.h"
|
#include "freertosinc.h"
|
||||||
|
|
||||||
#ifdef InputBrokerDebug
|
#ifdef InputBrokerDebug
|
||||||
@@ -28,11 +27,6 @@ enum input_broker_event {
|
|||||||
INPUT_BROKER_SHUTDOWN = 0x9b,
|
INPUT_BROKER_SHUTDOWN = 0x9b,
|
||||||
INPUT_BROKER_GPS_TOGGLE = 0x9e,
|
INPUT_BROKER_GPS_TOGGLE = 0x9e,
|
||||||
INPUT_BROKER_SEND_PING = 0xaf,
|
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_MATRIXKEY = 0xFE,
|
||||||
INPUT_BROKER_ANYKEY = 0xff
|
INPUT_BROKER_ANYKEY = 0xff
|
||||||
|
|
||||||
@@ -77,7 +71,6 @@ class InputBroker : public Observable<const InputEvent *>
|
|||||||
void queueInputEvent(const InputEvent *event);
|
void queueInputEvent(const InputEvent *event);
|
||||||
void processInputEventQueue();
|
void processInputEventQueue();
|
||||||
#endif
|
#endif
|
||||||
void Init();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int handleInputEvent(const InputEvent *event);
|
int handleInputEvent(const InputEvent *event);
|
||||||
@@ -91,5 +84,4 @@ class InputBroker : public Observable<const InputEvent *>
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
extern InputBroker *inputBroker;
|
extern InputBroker *inputBroker;
|
||||||
extern bool runASAP;
|
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
SerialKeyboard *globalSerialKeyboard = nullptr;
|
SerialKeyboard *globalSerialKeyboard = nullptr;
|
||||||
|
|
||||||
#ifdef INPUTBROKER_SERIAL_TYPE
|
#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
|
#if INPUTBROKER_SERIAL_TYPE == 1 // It's a Chatter
|
||||||
// 3 SHIFT level (lower case, upper case, numbers), up to 4 repeated presses, button number
|
// 3 SHIFT level (lower case, upper case, numbers), up to 4 repeated presses, button number
|
||||||
|
|||||||
@@ -26,12 +26,7 @@ class TCA8418KeyboardBase
|
|||||||
GPS_TOGGLE = 0x9E,
|
GPS_TOGGLE = 0x9E,
|
||||||
MUTE_TOGGLE = 0xAC,
|
MUTE_TOGGLE = 0xAC,
|
||||||
SEND_PING = 0xAF,
|
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);
|
typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len);
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#include "TrackballInterruptBase.h"
|
#include "TrackballInterruptBase.h"
|
||||||
#include "Throttle.h"
|
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
extern bool osk_found;
|
extern bool osk_found;
|
||||||
|
|
||||||
TrackballInterruptBase::TrackballInterruptBase(const char *name) : concurrency::OSThread(name), _originName(name) {}
|
TrackballInterruptBase::TrackballInterruptBase(const char *name) : concurrency::OSThread(name), _originName(name) {}
|
||||||
@@ -57,27 +55,17 @@ int32_t TrackballInterruptBase::runOnce()
|
|||||||
{
|
{
|
||||||
InputEvent e = {};
|
InputEvent e = {};
|
||||||
e.inputEvent = INPUT_BROKER_NONE;
|
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
|
// Handle long press detection for press button
|
||||||
if (pressDetected && pressStartTime > 0) {
|
if (pressDetected && pressStartTime > 0) {
|
||||||
uint32_t pressDuration = millis() - pressStartTime;
|
uint32_t pressDuration = millis() - pressStartTime;
|
||||||
bool buttonStillPressed = false;
|
bool buttonStillPressed = false;
|
||||||
|
|
||||||
|
#if defined(T_DECK)
|
||||||
|
buttonStillPressed = (this->action == TB_ACTION_PRESSED);
|
||||||
|
#else
|
||||||
buttonStillPressed = !digitalRead(_pinPress);
|
buttonStillPressed = !digitalRead(_pinPress);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!buttonStillPressed) {
|
if (!buttonStillPressed) {
|
||||||
// Button released
|
// Button released
|
||||||
@@ -146,31 +134,23 @@ int32_t TrackballInterruptBase::runOnce()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if TB_THRESHOLD
|
#if defined(T_DECK) // T-deck gets a super-simple debounce on trackball
|
||||||
if (this->action == TB_ACTION_PRESSED && (!pressDetected || pressStartTime == 0)) {
|
if (this->action == TB_ACTION_PRESSED && !pressDetected) {
|
||||||
// Start long press detection
|
// Start long press detection
|
||||||
pressDetected = true;
|
pressDetected = true;
|
||||||
pressStartTime = millis();
|
pressStartTime = millis();
|
||||||
// Don't send event yet, wait to see if it's a long press
|
// Don't send event yet, wait to see if it's a long press
|
||||||
} else if (up_counter >= TB_THRESHOLD) {
|
} else if (this->action == TB_ACTION_UP && lastEvent == TB_ACTION_UP) {
|
||||||
#ifdef INPUT_DEBUG
|
// LOG_DEBUG("Trackball event UP");
|
||||||
LOG_DEBUG("Trackball event UP %u", millis());
|
|
||||||
#endif
|
|
||||||
e.inputEvent = this->_eventUp;
|
e.inputEvent = this->_eventUp;
|
||||||
} else if (down_counter >= TB_THRESHOLD) {
|
} else if (this->action == TB_ACTION_DOWN && lastEvent == TB_ACTION_DOWN) {
|
||||||
#ifdef INPUT_DEBUG
|
// LOG_DEBUG("Trackball event DOWN");
|
||||||
LOG_DEBUG("Trackball event DOWN %u", millis());
|
|
||||||
#endif
|
|
||||||
e.inputEvent = this->_eventDown;
|
e.inputEvent = this->_eventDown;
|
||||||
} else if (left_counter >= TB_THRESHOLD) {
|
} else if (this->action == TB_ACTION_LEFT && lastEvent == TB_ACTION_LEFT) {
|
||||||
#ifdef INPUT_DEBUG
|
// LOG_DEBUG("Trackball event LEFT");
|
||||||
LOG_DEBUG("Trackball event LEFT %u", millis());
|
|
||||||
#endif
|
|
||||||
e.inputEvent = this->_eventLeft;
|
e.inputEvent = this->_eventLeft;
|
||||||
} else if (right_counter >= TB_THRESHOLD) {
|
} else if (this->action == TB_ACTION_RIGHT && lastEvent == TB_ACTION_RIGHT) {
|
||||||
#ifdef INPUT_DEBUG
|
// LOG_DEBUG("Trackball event RIGHT");
|
||||||
LOG_DEBUG("Trackball event RIGHT %u", millis());
|
|
||||||
#endif
|
|
||||||
e.inputEvent = this->_eventRight;
|
e.inputEvent = this->_eventRight;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
@@ -203,12 +183,6 @@ int32_t TrackballInterruptBase::runOnce()
|
|||||||
e.source = this->_originName;
|
e.source = this->_originName;
|
||||||
e.kbchar = 0x00;
|
e.kbchar = 0x00;
|
||||||
this->notifyObservers(&e);
|
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
|
// Only update lastEvent for non-press actions or completed press actions
|
||||||
@@ -224,49 +198,25 @@ int32_t TrackballInterruptBase::runOnce()
|
|||||||
|
|
||||||
void TrackballInterruptBase::intPressHandler()
|
void TrackballInterruptBase::intPressHandler()
|
||||||
{
|
{
|
||||||
if (!Throttle::isWithinTimespanMs(lastInterruptTime, 10))
|
this->action = TB_ACTION_PRESSED;
|
||||||
this->action = TB_ACTION_PRESSED;
|
|
||||||
lastInterruptTime = millis();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrackballInterruptBase::intDownHandler()
|
void TrackballInterruptBase::intDownHandler()
|
||||||
{
|
{
|
||||||
if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10))
|
this->action = TB_ACTION_DOWN;
|
||||||
this->action = TB_ACTION_DOWN;
|
|
||||||
lastInterruptTime = millis();
|
|
||||||
|
|
||||||
#if TB_THRESHOLD
|
|
||||||
down_counter++;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrackballInterruptBase::intUpHandler()
|
void TrackballInterruptBase::intUpHandler()
|
||||||
{
|
{
|
||||||
if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10))
|
this->action = TB_ACTION_UP;
|
||||||
this->action = TB_ACTION_UP;
|
|
||||||
lastInterruptTime = millis();
|
|
||||||
|
|
||||||
#if TB_THRESHOLD
|
|
||||||
up_counter++;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrackballInterruptBase::intLeftHandler()
|
void TrackballInterruptBase::intLeftHandler()
|
||||||
{
|
{
|
||||||
if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10))
|
this->action = TB_ACTION_LEFT;
|
||||||
this->action = TB_ACTION_LEFT;
|
|
||||||
lastInterruptTime = millis();
|
|
||||||
#if TB_THRESHOLD
|
|
||||||
left_counter++;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrackballInterruptBase::intRightHandler()
|
void TrackballInterruptBase::intRightHandler()
|
||||||
{
|
{
|
||||||
if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10))
|
this->action = TB_ACTION_RIGHT;
|
||||||
this->action = TB_ACTION_RIGHT;
|
|
||||||
lastInterruptTime = millis();
|
|
||||||
#if TB_THRESHOLD
|
|
||||||
right_counter++;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,6 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef TB_THRESHOLD
|
|
||||||
#define TB_THRESHOLD 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class TrackballInterruptBase : public Observable<const InputEvent *>, public concurrency::OSThread
|
class TrackballInterruptBase : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -29,6 +25,8 @@ class TrackballInterruptBase : public Observable<const InputEvent *>, public con
|
|||||||
void intUpHandler();
|
void intUpHandler();
|
||||||
void intLeftHandler();
|
void intLeftHandler();
|
||||||
void intRightHandler();
|
void intRightHandler();
|
||||||
|
uint32_t lastTime = 0;
|
||||||
|
|
||||||
virtual int32_t runOnce() override;
|
virtual int32_t runOnce() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -69,12 +67,4 @@ class TrackballInterruptBase : public Observable<const InputEvent *>, public con
|
|||||||
input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
|
input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
|
||||||
const char *_originName;
|
const char *_originName;
|
||||||
TrackballInterruptBaseActionType lastEvent = TB_ACTION_NONE;
|
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,26 +24,41 @@ void TrackballInterruptImpl1::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLe
|
|||||||
|
|
||||||
void TrackballInterruptImpl1::handleIntDown()
|
void TrackballInterruptImpl1::handleIntDown()
|
||||||
{
|
{
|
||||||
trackballInterruptImpl1->intDownHandler();
|
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
trackballInterruptImpl1->lastTime = millis();
|
||||||
|
trackballInterruptImpl1->intDownHandler();
|
||||||
|
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void TrackballInterruptImpl1::handleIntUp()
|
void TrackballInterruptImpl1::handleIntUp()
|
||||||
{
|
{
|
||||||
trackballInterruptImpl1->intUpHandler();
|
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
trackballInterruptImpl1->lastTime = millis();
|
||||||
|
trackballInterruptImpl1->intUpHandler();
|
||||||
|
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void TrackballInterruptImpl1::handleIntLeft()
|
void TrackballInterruptImpl1::handleIntLeft()
|
||||||
{
|
{
|
||||||
trackballInterruptImpl1->intLeftHandler();
|
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
trackballInterruptImpl1->lastTime = millis();
|
||||||
|
trackballInterruptImpl1->intLeftHandler();
|
||||||
|
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void TrackballInterruptImpl1::handleIntRight()
|
void TrackballInterruptImpl1::handleIntRight()
|
||||||
{
|
{
|
||||||
trackballInterruptImpl1->intRightHandler();
|
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
trackballInterruptImpl1->lastTime = millis();
|
||||||
|
trackballInterruptImpl1->intRightHandler();
|
||||||
|
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void TrackballInterruptImpl1::handleIntPressed()
|
void TrackballInterruptImpl1::handleIntPressed()
|
||||||
{
|
{
|
||||||
trackballInterruptImpl1->intPressHandler();
|
if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) {
|
||||||
trackballInterruptImpl1->setIntervalFromNow(20);
|
trackballInterruptImpl1->lastTime = millis();
|
||||||
|
trackballInterruptImpl1->intPressHandler();
|
||||||
|
trackballInterruptImpl1->setIntervalFromNow(20);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -321,26 +321,6 @@ int32_t KbI2cBase::runOnce()
|
|||||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||||
e.kbchar = INPUT_BROKER_MSG_TAB;
|
e.kbchar = INPUT_BROKER_MSG_TAB;
|
||||||
break;
|
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:
|
default:
|
||||||
if (nextEvent > 127) {
|
if (nextEvent > 127) {
|
||||||
e.inputEvent = INPUT_BROKER_NONE;
|
e.inputEvent = INPUT_BROKER_NONE;
|
||||||
|
|||||||
721
src/main.cpp
721
src/main.cpp
@@ -10,9 +10,9 @@
|
|||||||
#include "ReliableRouter.h"
|
#include "ReliableRouter.h"
|
||||||
#include "airtime.h"
|
#include "airtime.h"
|
||||||
#include "buzz.h"
|
#include "buzz.h"
|
||||||
#include "power/PowerHAL.h"
|
|
||||||
|
|
||||||
#include "FSCommon.h"
|
#include "FSCommon.h"
|
||||||
|
#include "Led.h"
|
||||||
#include "RTC.h"
|
#include "RTC.h"
|
||||||
#include "SPILock.h"
|
#include "SPILock.h"
|
||||||
#include "Throttle.h"
|
#include "Throttle.h"
|
||||||
@@ -42,6 +42,10 @@
|
|||||||
#include "MessageStore.h"
|
#include "MessageStore.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ELECROW_ThinkNode_M5
|
||||||
|
PCA9557 io(0x18, &Wire);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
#include "freertosinc.h"
|
#include "freertosinc.h"
|
||||||
#if !MESHTASTIC_EXCLUDE_WEBSERVER
|
#if !MESHTASTIC_EXCLUDE_WEBSERVER
|
||||||
@@ -72,10 +76,29 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr;
|
|||||||
#include "mqtt/MQTT.h"
|
#include "mqtt/MQTT.h"
|
||||||
#endif
|
#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
|
#ifdef ARCH_PORTDUINO
|
||||||
#include "linux/LinuxHardwareI2C.h"
|
#include "linux/LinuxHardwareI2C.h"
|
||||||
#include "mesh/raspihttp/PiWebServer.h"
|
#include "mesh/raspihttp/PiWebServer.h"
|
||||||
#include "platform/portduino/PortduinoGlue.h"
|
#include "platform/portduino/PortduinoGlue.h"
|
||||||
|
#include "platform/portduino/USBHal.h"
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -119,6 +142,31 @@ void printPartitionTable()
|
|||||||
#endif // DEBUG_PARTITION_TABLE
|
#endif // DEBUG_PARTITION_TABLE
|
||||||
#endif // ARCH_ESP32
|
#endif // ARCH_ESP32
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
||||||
#include "AmbientLightingThread.h"
|
#include "AmbientLightingThread.h"
|
||||||
#include "PowerFSMThread.h"
|
#include "PowerFSMThread.h"
|
||||||
|
|
||||||
@@ -205,6 +253,9 @@ ScanI2C::DeviceAddress aqi_found = ScanI2C::ADDRESS_NONE;
|
|||||||
Adafruit_DRV2605 drv;
|
Adafruit_DRV2605 drv;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Global LoRa radio type
|
||||||
|
LoRaRadioType radioType = NO_RADIO;
|
||||||
|
|
||||||
bool isVibrating = false;
|
bool isVibrating = false;
|
||||||
|
|
||||||
bool eink_found = true;
|
bool eink_found = true;
|
||||||
@@ -241,8 +292,25 @@ const char *getDeviceName()
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int32_t ledBlinker()
|
||||||
|
{
|
||||||
|
// Still set up the blinking (heartbeat) interval but skip code path below, so LED will blink if
|
||||||
|
// config.device.led_heartbeat_disabled is changed
|
||||||
|
if (config.device.led_heartbeat_disabled)
|
||||||
|
return 1000;
|
||||||
|
|
||||||
|
static bool ledOn;
|
||||||
|
ledOn ^= 1;
|
||||||
|
|
||||||
|
ledBlink.set(ledOn);
|
||||||
|
|
||||||
|
// have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that
|
||||||
|
return powerStatus->getIsCharging() ? 1000 : (ledOn ? 1 : 1000);
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t timeLastPowered = 0;
|
uint32_t timeLastPowered = 0;
|
||||||
|
|
||||||
|
static Periodic *ledPeriodic;
|
||||||
static OSThread *powerFSMthread;
|
static OSThread *powerFSMthread;
|
||||||
static OSThread *ambientLightingThread;
|
static OSThread *ambientLightingThread;
|
||||||
|
|
||||||
@@ -264,41 +332,6 @@ __attribute__((weak, noinline)) bool loopCanSleep()
|
|||||||
void lateInitVariant() __attribute__((weak));
|
void lateInitVariant() __attribute__((weak));
|
||||||
void lateInitVariant() {}
|
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()
|
|
||||||
{
|
|
||||||
while (powerHAL_isPowerLevelSafe() == false) {
|
|
||||||
|
|
||||||
#ifdef LED_POWER
|
|
||||||
|
|
||||||
// 3x: blink for 300 ms, pause for 300 ms
|
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
digitalWrite(LED_POWER, LED_STATE_ON);
|
|
||||||
delay(300);
|
|
||||||
digitalWrite(LED_POWER, LED_STATE_OFF);
|
|
||||||
delay(300);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// sleep for 2s
|
|
||||||
delay(2000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print info as a structured log message (for automated log processing)
|
* Print info as a structured log message (for automated log processing)
|
||||||
*/
|
*/
|
||||||
@@ -309,30 +342,34 @@ void printInfo()
|
|||||||
#ifndef PIO_UNIT_TESTING
|
#ifndef PIO_UNIT_TESTING
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
|
#if defined(R1_NEO)
|
||||||
// initialize power HAL layer as early as possible
|
pinMode(DCDC_EN_HOLD, OUTPUT);
|
||||||
powerHAL_init();
|
digitalWrite(DCDC_EN_HOLD, HIGH);
|
||||||
|
pinMode(NRF_ON, OUTPUT);
|
||||||
#ifdef LED_POWER
|
digitalWrite(NRF_ON, HIGH);
|
||||||
pinMode(LED_POWER, OUTPUT);
|
|
||||||
digitalWrite(LED_POWER, LED_STATE_ON);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// 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)
|
#if defined(PIN_POWER_EN)
|
||||||
pinMode(PIN_POWER_EN, OUTPUT);
|
pinMode(PIN_POWER_EN, OUTPUT);
|
||||||
digitalWrite(PIN_POWER_EN, HIGH);
|
digitalWrite(PIN_POWER_EN, HIGH);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef LED_NOTIFICATION
|
#if defined(ELECROW_ThinkNode_M5)
|
||||||
pinMode(LED_NOTIFICATION, OUTPUT);
|
Wire.begin(48, 47);
|
||||||
digitalWrite(LED_NOTIFICATION, HIGH ^ LED_STATE_ON);
|
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);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef WIFI_LED
|
#ifdef WIFI_LED
|
||||||
@@ -342,10 +379,75 @@ void setup()
|
|||||||
|
|
||||||
#ifdef BLE_LED
|
#ifdef BLE_LED
|
||||||
pinMode(BLE_LED, OUTPUT);
|
pinMode(BLE_LED, OUTPUT);
|
||||||
digitalWrite(BLE_LED, LED_STATE_OFF);
|
#ifdef BLE_LED_INVERTED
|
||||||
|
digitalWrite(BLE_LED, HIGH);
|
||||||
|
#else
|
||||||
|
digitalWrite(BLE_LED, LOW);
|
||||||
|
#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
|
#endif
|
||||||
|
|
||||||
concurrency::hasBeenSetup = true;
|
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 screen_model =
|
||||||
meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO;
|
meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO;
|
||||||
@@ -456,10 +558,41 @@ void setup()
|
|||||||
LOG_INFO("Wait for peripherals to stabilize");
|
LOG_INFO("Wait for peripherals to stabilize");
|
||||||
delay(PERIPHERAL_WARMUP_MS);
|
delay(PERIPHERAL_WARMUP_MS);
|
||||||
#endif
|
#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();
|
initSPI();
|
||||||
|
|
||||||
OSThread::setup();
|
OSThread::setup();
|
||||||
|
|
||||||
|
#if defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2)
|
||||||
|
// The ThinkNodes have their own blink logic
|
||||||
|
// ledPeriodic = new Periodic("Blink", elecrowLedBlinker);
|
||||||
|
#else
|
||||||
|
ledPeriodic = new Periodic("Blink", ledBlinker);
|
||||||
|
#endif
|
||||||
|
|
||||||
fsInit();
|
fsInit();
|
||||||
|
|
||||||
#if !MESHTASTIC_EXCLUDE_I2C
|
#if !MESHTASTIC_EXCLUDE_I2C
|
||||||
@@ -684,6 +817,13 @@ void setup()
|
|||||||
setupSDCard();
|
setupSDCard();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// LED init
|
||||||
|
|
||||||
|
#ifdef LED_PIN
|
||||||
|
pinMode(LED_PIN, OUTPUT);
|
||||||
|
digitalWrite(LED_PIN, LED_STATE_ON); // turn on for now
|
||||||
|
#endif
|
||||||
|
|
||||||
// Hello
|
// Hello
|
||||||
printInfo();
|
printInfo();
|
||||||
#ifdef BUILD_EPOCH
|
#ifdef BUILD_EPOCH
|
||||||
@@ -791,7 +931,7 @@ void setup()
|
|||||||
SPI.begin();
|
SPI.begin();
|
||||||
#endif
|
#endif
|
||||||
#else
|
#else
|
||||||
// ESP32
|
// ESP32
|
||||||
#if defined(HW_SPI1_DEVICE)
|
#if defined(HW_SPI1_DEVICE)
|
||||||
SPI1.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
SPI1.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
||||||
LOG_DEBUG("SPI1.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
LOG_DEBUG("SPI1.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
||||||
@@ -823,7 +963,6 @@ void setup()
|
|||||||
}
|
}
|
||||||
#endif // HAS_SCREEN
|
#endif // HAS_SCREEN
|
||||||
|
|
||||||
// TODO Remove magic string
|
|
||||||
// setup TZ prior to time actions.
|
// setup TZ prior to time actions.
|
||||||
#if !MESHTASTIC_EXCLUDE_TZ
|
#if !MESHTASTIC_EXCLUDE_TZ
|
||||||
LOG_DEBUG("Use compiled/slipstreamed %s", slipstreamTZString); // important, removing this clobbers our magic string
|
LOG_DEBUG("Use compiled/slipstreamed %s", slipstreamTZString); // important, removing this clobbers our magic string
|
||||||
@@ -907,9 +1046,180 @@ void setup()
|
|||||||
nodeDB->hasWarned = true;
|
nodeDB->hasWarned = true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
|
|
||||||
if (inputBroker)
|
// buttons are now inputBroker, so have to come after setupModules
|
||||||
inputBroker->Init();
|
#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
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||||
@@ -917,6 +1227,12 @@ void setup()
|
|||||||
setupNicheGraphics();
|
setupNicheGraphics();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef LED_PIN
|
||||||
|
// Turn LED off after boot, if heartbeat by config
|
||||||
|
if (config.device.led_heartbeat_disabled)
|
||||||
|
digitalWrite(LED_PIN, HIGH ^ LED_STATE_ON);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Do this after service.init (because that clears error_code)
|
// Do this after service.init (because that clears error_code)
|
||||||
#ifdef HAS_PMU
|
#ifdef HAS_PMU
|
||||||
if (!pmu_found)
|
if (!pmu_found)
|
||||||
@@ -942,7 +1258,252 @@ void setup()
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
initLoRa();
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lateInitVariant(); // Do board specific init (see extra_variants/README.md for documentation)
|
lateInitVariant(); // Do board specific init (see extra_variants/README.md for documentation)
|
||||||
|
|
||||||
@@ -1031,7 +1592,6 @@ bool suppressRebootBanner; // If true, suppress "Rebooting..." overlay (used for
|
|||||||
// This will suppress the current delay and instead try to run ASAP.
|
// This will suppress the current delay and instead try to run ASAP.
|
||||||
bool runASAP;
|
bool runASAP;
|
||||||
|
|
||||||
// TODO find better home than main.cpp
|
|
||||||
extern meshtastic_DeviceMetadata getDeviceMetadata()
|
extern meshtastic_DeviceMetadata getDeviceMetadata()
|
||||||
{
|
{
|
||||||
meshtastic_DeviceMetadata deviceMetadata;
|
meshtastic_DeviceMetadata deviceMetadata;
|
||||||
@@ -1132,43 +1692,7 @@ void loop()
|
|||||||
if (inputBroker)
|
if (inputBroker)
|
||||||
inputBroker->processInputEventQueue();
|
inputBroker->processInputEventQueue();
|
||||||
#endif
|
#endif
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO && HAS_TFT
|
||||||
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 &&
|
if (screen && portduino_config.displayPanel == x11 &&
|
||||||
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
||||||
auto dispdev = screen->getDisplayDevice();
|
auto dispdev = screen->getDisplayDevice();
|
||||||
@@ -1176,7 +1700,6 @@ void loop()
|
|||||||
static_cast<TFTDisplay *>(dispdev)->sdlLoop();
|
static_cast<TFTDisplay *>(dispdev)->sdlLoop();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#endif
|
|
||||||
#if HAS_SCREEN && ENABLE_MESSAGE_PERSISTENCE
|
#if HAS_SCREEN && ENABLE_MESSAGE_PERSISTENCE
|
||||||
messageStoreAutosaveTick();
|
messageStoreAutosaveTick();
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
12
src/main.h
12
src/main.h
@@ -26,8 +26,8 @@ extern NRF52Bluetooth *nrf52Bluetooth;
|
|||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
extern HardwareSPI *DisplaySPI;
|
extern HardwareSPI *DisplaySPI;
|
||||||
extern HardwareSPI *LoraSPI;
|
extern HardwareSPI *LoraSPI;
|
||||||
#endif
|
|
||||||
|
|
||||||
|
#endif
|
||||||
extern ScanI2C::DeviceAddress screen_found;
|
extern ScanI2C::DeviceAddress screen_found;
|
||||||
extern ScanI2C::DeviceAddress cardkb_found;
|
extern ScanI2C::DeviceAddress cardkb_found;
|
||||||
extern uint8_t kb_model;
|
extern uint8_t kb_model;
|
||||||
@@ -47,16 +47,16 @@ extern bool isUSBPowered;
|
|||||||
extern Adafruit_DRV2605 drv;
|
extern Adafruit_DRV2605 drv;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAS_PCA9557
|
|
||||||
#include <PCA9557.h>
|
|
||||||
extern PCA9557 io;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef HAS_I2S
|
#ifdef HAS_I2S
|
||||||
#include "AudioThread.h"
|
#include "AudioThread.h"
|
||||||
extern AudioThread *audioThread;
|
extern AudioThread *audioThread;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ELECROW_ThinkNode_M5
|
||||||
|
#include <PCA9557.h>
|
||||||
|
extern PCA9557 io;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef HAS_UDP_MULTICAST
|
#ifdef HAS_UDP_MULTICAST
|
||||||
#include "mesh/udp/UdpMulticastHandler.h"
|
#include "mesh/udp/UdpMulticastHandler.h"
|
||||||
extern UdpMulticastHandler *udpHandler;
|
extern UdpMulticastHandler *udpHandler;
|
||||||
|
|||||||
@@ -61,6 +61,11 @@ bool CryptoEngine::regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
* Encrypt a packet's payload using a key generated with Curve25519 and SHA256
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class CryptoEngine
|
|||||||
virtual bool regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey);
|
virtual bool regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
void clearKeys();
|
||||||
void setDHPrivateKey(uint8_t *_private_key);
|
void setDHPrivateKey(uint8_t *_private_key);
|
||||||
virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic,
|
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);
|
uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut);
|
||||||
|
|||||||
@@ -91,21 +91,10 @@ template <typename T> bool LR11x0Interface<T>::init()
|
|||||||
LOG_DEBUG("Set RF1 switch to %s", getFreq() < 1e9 ? "SubGHz" : "2.4GHz");
|
LOG_DEBUG("Set RF1 switch to %s", getFreq() < 1e9 ? "SubGHz" : "2.4GHz");
|
||||||
#endif
|
#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);
|
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`
|
// \todo Display actual typename of the adapter, not just `LR11x0`
|
||||||
LOG_INFO("LR11x0 init result %d", res);
|
LOG_INFO("LR11x0 init result %d", res);
|
||||||
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
|
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
LR11x0VersionInfo_t version;
|
LR11x0VersionInfo_t version;
|
||||||
@@ -170,7 +159,7 @@ template <typename T> bool LR11x0Interface<T>::reconfigure()
|
|||||||
if (err != RADIOLIB_ERR_NONE)
|
if (err != RADIOLIB_ERR_NONE)
|
||||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||||
|
|
||||||
err = lora.setCodingRate(cr, cr != 7); // use long interleaving except if CR is 4/7 which doesn't support it
|
err = lora.setCodingRate(cr);
|
||||||
if (err != RADIOLIB_ERR_NONE)
|
if (err != RADIOLIB_ERR_NONE)
|
||||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||||
|
|
||||||
|
|||||||
@@ -22,99 +22,4 @@ struct RegionInfo {
|
|||||||
extern const RegionInfo regions[];
|
extern const RegionInfo regions[];
|
||||||
extern const RegionInfo *myRegion;
|
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,7 +13,6 @@
|
|||||||
#include "PacketHistory.h"
|
#include "PacketHistory.h"
|
||||||
#include "PowerFSM.h"
|
#include "PowerFSM.h"
|
||||||
#include "RTC.h"
|
#include "RTC.h"
|
||||||
#include "RadioInterface.h"
|
|
||||||
#include "Router.h"
|
#include "Router.h"
|
||||||
#include "SPILock.h"
|
#include "SPILock.h"
|
||||||
#include "SafeFile.h"
|
#include "SafeFile.h"
|
||||||
@@ -27,7 +26,6 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <pb_decode.h>
|
#include <pb_decode.h>
|
||||||
#include <pb_encode.h>
|
#include <pb_encode.h>
|
||||||
#include <power/PowerHAL.h>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
@@ -824,10 +822,16 @@ void NodeDB::installDefaultModuleConfig()
|
|||||||
moduleConfig.external_notification.output_ms = 500;
|
moduleConfig.external_notification.output_ms = 500;
|
||||||
moduleConfig.external_notification.nag_timeout = 2;
|
moduleConfig.external_notification.nag_timeout = 2;
|
||||||
#endif
|
#endif
|
||||||
#if defined(LED_NOTIFICATION)
|
#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)
|
||||||
moduleConfig.external_notification.enabled = true;
|
moduleConfig.external_notification.enabled = true;
|
||||||
moduleConfig.external_notification.output = LED_NOTIFICATION;
|
moduleConfig.external_notification.output = PIN_LED2;
|
||||||
moduleConfig.external_notification.active = LED_STATE_ON;
|
#if defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3)
|
||||||
|
moduleConfig.external_notification.active = false;
|
||||||
|
#else
|
||||||
|
moduleConfig.external_notification.active = true;
|
||||||
|
#endif
|
||||||
moduleConfig.external_notification.alert_message = true;
|
moduleConfig.external_notification.alert_message = true;
|
||||||
moduleConfig.external_notification.output_ms = 1000;
|
moduleConfig.external_notification.output_ms = 1000;
|
||||||
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
|
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
|
||||||
@@ -851,6 +855,15 @@ void NodeDB::installDefaultModuleConfig()
|
|||||||
moduleConfig.external_notification.output_ms = 100;
|
moduleConfig.external_notification.output_ms = 100;
|
||||||
moduleConfig.external_notification.active = true;
|
moduleConfig.external_notification.active = true;
|
||||||
#endif
|
#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
|
#ifdef T_LORA_PAGER
|
||||||
moduleConfig.canned_message.updown1_enabled = true;
|
moduleConfig.canned_message.updown1_enabled = true;
|
||||||
moduleConfig.canned_message.inputbroker_pin_a = ROTARY_A;
|
moduleConfig.canned_message.inputbroker_pin_a = ROTARY_A;
|
||||||
@@ -1284,13 +1297,6 @@ void NodeDB::loadFromDisk()
|
|||||||
LOG_INFO("Loaded saved config version %d", config.version);
|
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) {
|
if (backupSecurity.private_key.size > 0) {
|
||||||
LOG_DEBUG("Restoring backup of security config");
|
LOG_DEBUG("Restoring backup of security config");
|
||||||
config.security = backupSecurity;
|
config.security = backupSecurity;
|
||||||
@@ -1404,15 +1410,6 @@ void NodeDB::loadFromDisk()
|
|||||||
if (portduino_config.has_configDisplayMode) {
|
if (portduino_config.has_configDisplayMode) {
|
||||||
config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)portduino_config.configDisplayMode;
|
config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)portduino_config.configDisplayMode;
|
||||||
}
|
}
|
||||||
if (portduino_config.has_statusMessage) {
|
|
||||||
moduleConfig.has_statusmessage = true;
|
|
||||||
strncpy(moduleConfig.statusmessage.node_status, portduino_config.statusMessage.c_str(),
|
|
||||||
sizeof(moduleConfig.statusmessage.node_status));
|
|
||||||
moduleConfig.statusmessage.node_status[sizeof(moduleConfig.statusmessage.node_status) - 1] = '\0';
|
|
||||||
}
|
|
||||||
if (portduino_config.enable_UDP) {
|
|
||||||
config.network.enabled_protocols = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -1421,14 +1418,6 @@ void NodeDB::loadFromDisk()
|
|||||||
bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct,
|
bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct,
|
||||||
bool fullAtomic)
|
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;
|
bool okay = false;
|
||||||
#ifdef FSCom
|
#ifdef FSCom
|
||||||
auto f = SafeFile(filename, fullAtomic);
|
auto f = SafeFile(filename, fullAtomic);
|
||||||
@@ -1455,14 +1444,6 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_
|
|||||||
|
|
||||||
bool NodeDB::saveChannelsToDisk()
|
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
|
#ifdef FSCom
|
||||||
spiLock->lock();
|
spiLock->lock();
|
||||||
FSCom.mkdir("/prefs");
|
FSCom.mkdir("/prefs");
|
||||||
@@ -1473,14 +1454,6 @@ bool NodeDB::saveChannelsToDisk()
|
|||||||
|
|
||||||
bool NodeDB::saveDeviceStateToDisk()
|
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
|
#ifdef FSCom
|
||||||
spiLock->lock();
|
spiLock->lock();
|
||||||
FSCom.mkdir("/prefs");
|
FSCom.mkdir("/prefs");
|
||||||
@@ -1493,14 +1466,6 @@ bool NodeDB::saveDeviceStateToDisk()
|
|||||||
|
|
||||||
bool NodeDB::saveNodeDatabaseToDisk()
|
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
|
#ifdef FSCom
|
||||||
spiLock->lock();
|
spiLock->lock();
|
||||||
FSCom.mkdir("/prefs");
|
FSCom.mkdir("/prefs");
|
||||||
@@ -1513,14 +1478,6 @@ bool NodeDB::saveNodeDatabaseToDisk()
|
|||||||
|
|
||||||
bool NodeDB::saveToDiskNoRetry(int saveWhat)
|
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;
|
bool success = true;
|
||||||
#ifdef FSCom
|
#ifdef FSCom
|
||||||
spiLock->lock();
|
spiLock->lock();
|
||||||
@@ -1553,7 +1510,6 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
|
|||||||
moduleConfig.has_ambient_lighting = true;
|
moduleConfig.has_ambient_lighting = true;
|
||||||
moduleConfig.has_audio = true;
|
moduleConfig.has_audio = true;
|
||||||
moduleConfig.has_paxcounter = true;
|
moduleConfig.has_paxcounter = true;
|
||||||
moduleConfig.has_statusmessage = true;
|
|
||||||
|
|
||||||
success &=
|
success &=
|
||||||
saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig);
|
saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig);
|
||||||
@@ -1577,14 +1533,6 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
|
|||||||
bool NodeDB::saveToDisk(int saveWhat)
|
bool NodeDB::saveToDisk(int saveWhat)
|
||||||
{
|
{
|
||||||
LOG_DEBUG("Save to disk %d", 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);
|
bool success = saveToDiskNoRetry(saveWhat);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
@@ -2223,8 +2171,8 @@ bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location,
|
|||||||
} else if (location == meshtastic_AdminMessage_BackupLocation_SD) {
|
} else if (location == meshtastic_AdminMessage_BackupLocation_SD) {
|
||||||
// TODO: After more mainline SD card support
|
// TODO: After more mainline SD card support
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
return success;
|
return success;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Record an error that should be reported via analytics
|
/// Record an error that should be reported via analytics
|
||||||
@@ -2242,10 +2190,7 @@ 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
|
// Currently portuino is mostly used for simulation. Make sure the user notices something really bad happened
|
||||||
#ifdef ARCH_PORTDUINO
|
#ifdef ARCH_PORTDUINO
|
||||||
LOG_ERROR("A critical failure occurred");
|
LOG_ERROR("A critical failure occurred, portduino is exiting");
|
||||||
// TODO: Determine if other critical errors should also cause an immediate exit
|
exit(2);
|
||||||
if (code == meshtastic_CriticalErrorCode_FLASH_CORRUPTION_RECOVERABLE ||
|
|
||||||
code == meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE)
|
|
||||||
exit(2);
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,9 +177,6 @@ bool RF95Interface::init()
|
|||||||
|
|
||||||
int res = lora->begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength);
|
int res = lora->begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength);
|
||||||
LOG_INFO("RF95 init result %d", res);
|
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("Frequency set to %f", getFreq());
|
||||||
LOG_INFO("Bandwidth set to %f", bw);
|
LOG_INFO("Bandwidth set to %f", bw);
|
||||||
LOG_INFO("Power output set to %d", power);
|
LOG_INFO("Power output set to %d", power);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user