Compare commits

...

26 Commits

Author SHA1 Message Date
Jonathan Bennett
bcf1ed5c2e Merge branch 'develop' into move-input-init 2026-01-28 14:08:51 -06:00
Jonathan Bennett
571c1ac34c Initial serialModule cleanup (#9465)
* Initial serialModule cleanup

* Move SERIAL_PRINT_PORT definition to variant.h

* Add missed c6 check

* Update src/modules/SerialModule.cpp

Compile error for invalid SERIAL_PRINT_OBJECT value

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-28 14:08:32 -06:00
Ben Meadors
9684d9ece6 Merge branch 'develop' into move-input-init 2026-01-28 13:45:02 -06:00
Ben Meadors
fb635987d1 Merge remote-tracking branch 'origin/master' into develop 2026-01-28 13:31:39 -06:00
Jonathan Bennett
d6914c105f Trunk 2026-01-28 13:26:57 -06:00
Jonathan Bennett
8f367d32a2 Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-28 11:52:08 -06:00
Jonathan Bennett
017affcd74 Fix conditional compilation for input broker 2026-01-28 11:51:34 -06:00
Jonathan Bennett
19a47ffacd Update src/input/InputBroker.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-28 11:43:48 -06:00
Ben Meadors
5707c35f01 Merge branch 'develop' into move-input-init 2026-01-28 11:16:25 -06:00
Jason P
a922751afc External Notification - handleReceived Rewrite (#9454)
* First steps in consolidating code and minimizing rewrite

* Continuing code cleanup

* Merge containsBell and !isMuted to a single code path

* Forgot about alert_message_buzzer in the cleanup

* More code refinements and cleanup

* Fix nagCycleCutoff

* CoPilot Updates
2026-01-28 11:12:02 -06:00
renovate[bot]
c1e3f56324 Update LovyanGFX to v1.2.19 (#9405)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-28 11:08:07 -06:00
Jonathan Bennett
8822f8b685 Unbreak targets with EXCLUDE_INPUTBROKER 2026-01-28 11:07:58 -06:00
Ben Meadors
d0562e1ee6 Add model workflows (#9462)
* Add GitHub workflows for issue completeness, duplicate detection, onboarding, and contribution quality checks

* Fix indentation

* Refactor GitHub workflows for issue handling

* Consolidate to two triage workflows

* Update .github/workflows/models_pr_triage.yml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-28 10:50:56 -06:00
Jonathan Bennett
9ef9000082 Merge branch 'develop' into move-input-init 2026-01-28 10:37:03 -06:00
Jonathan Bennett
42e3e90ad0 Move input init to an init function in iInputBroker 2026-01-28 10:34:21 -06:00
Jason P
4eb4c4b584 BaseUI Message Bubble Improvements (#9452)
* Improve Message bubbles for more distinct markers and improved layout

* Tune message bubble size and corner markers

* Finish message bubble tuning

---------

Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
2026-01-27 21:11:56 -05:00
Jonathan Bennett
69a42e1fd2 Add portduino_status, assign hardware device IDs... (#9441)
* Add portduino_status, assign hardware device IDs, and try to recover a CH341 device on a USB error

* Minor fixes suggested by Copilot
2026-01-27 18:00:20 -06:00
Jonathan Bennett
fd498bebad Add support for Hackaday Communicator function keys (#9444) 2026-01-27 16:09:18 -06:00
小林
23a8b5a66f Fix uMesh RF POWER configuration error (#9326)
* fix issue https://github.com/linser233/uMesh/issues/1

* fix issue https://github.com/linser233/uMesh/issues/1

* Update and rename lora-usb-umesh-1262.yaml to lora-usb-umesh-1262-30dbm.yaml

* Update and rename lora-usb-umesh-1268.yaml to lora-usb-umesh-1268-30dbm.yaml
2026-01-28 07:50:50 +11:00
Ben Meadors
e1e8d6124d Merge branch 'master' into develop 2026-01-27 14:01:27 -06:00
Jonathan Bennett
10b2eae70c Move more code out of main-nrf52 into variant.cpp (#9450) 2026-01-27 13:56:32 -06:00
github-actions[bot]
cfda9bb8ef Update protobufs (#9453)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-01-27 13:12:03 -06:00
renovate[bot]
d1edd386b6 Update meshtastic/device-ui digest to 69739b8 (#9448)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-27 13:08:10 -06:00
Ben Meadors
b6a1020fc5 Add error handling for SPI command failures in LR11x0, RF95, and SX128x interfaces (#9447) 2026-01-27 13:06:50 -06:00
Jonathan Bennett
91ad861b26 Add Thinknode M4 variant_shutdown() (#9449) 2026-01-27 09:56:56 -06:00
Ben Meadors
c8079d4115 Metadata for heltec tracker v2 2026-01-27 08:05:36 -06:00
67 changed files with 1237 additions and 561 deletions

View File

@@ -0,0 +1,204 @@
name: Issue Triage (Models)
on:
issues:
types: [opened]
permissions:
issues: write
models: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.issue.number }}
cancel-in-progress: true
jobs:
triage:
if: ${{ github.repository == 'meshtastic/firmware' && github.event.issue.user.type != 'Bot' }}
runs-on: ubuntu-latest
steps:
# ─────────────────────────────────────────────────────────────────────────
# Step 1: Quality check (spam/AI-slop detection) - runs first, exits early if spam
# ─────────────────────────────────────────────────────────────────────────
- name: Detect spam or low-quality content
uses: actions/ai-inference@v2
id: quality
continue-on-error: true
with:
max-tokens: 20
prompt: |
Is this GitHub issue spam, AI-generated slop, or low quality?
Title: ${{ github.event.issue.title }}
Body: ${{ github.event.issue.body }}
Respond with exactly one of: spam, ai-generated, needs-review, ok
system-prompt: You detect spam and low-quality contributions. Be conservative - only flag obvious spam or AI slop.
model: openai/gpt-4o-mini
- name: Apply quality label if needed
if: steps.quality.outputs.response != '' && steps.quality.outputs.response != 'ok'
uses: actions/github-script@v8
env:
QUALITY_LABEL: ${{ steps.quality.outputs.response }}
with:
script: |
const label = (process.env.QUALITY_LABEL || '').trim().toLowerCase();
const labelMeta = {
'spam': { color: 'd73a4a', description: 'Possible spam' },
'ai-generated': { color: 'fbca04', description: 'Possible AI-generated low-quality content' },
'needs-review': { color: 'f9d0c4', description: 'Needs human review' },
};
const meta = labelMeta[label];
if (!meta) return;
// Ensure label exists
try {
await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label });
} catch (e) {
if (e.status !== 404) throw e;
await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description });
}
// Apply label
await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.issue.number, labels: [label] });
// Set output to skip remaining steps
core.setOutput('is_spam', 'true');
# ─────────────────────────────────────────────────────────────────────────
# Step 2: Duplicate detection - only if not spam
# ─────────────────────────────────────────────────────────────────────────
- name: Detect duplicate issues
if: steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == ''
uses: pelikhan/action-genai-issue-dedup@bdb3b5d9451c1090ffcdf123d7447a5e7c7a2528 # v0.0.19
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# ─────────────────────────────────────────────────────────────────────────
# Step 3: Completeness check + auto-labeling (combined into one AI call)
# ─────────────────────────────────────────────────────────────────────────
- name: Determine if completeness check should be skipped
if: steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == ''
uses: actions/github-script@v8
id: check-skip
with:
script: |
const title = (context.payload.issue.title || '').toLowerCase();
const labels = (context.payload.issue.labels || []).map(label => label.name);
const hasFeatureRequest = title.includes('feature request');
const hasEnhancement = labels.includes('enhancement');
const shouldSkip = hasFeatureRequest && hasEnhancement;
core.setOutput('should_skip', shouldSkip ? 'true' : 'false');
- name: Analyze issue completeness and determine labels
if: (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '') && steps.check-skip.outputs.should_skip != 'true'
uses: actions/ai-inference@v2
id: analysis
continue-on-error: true
with:
prompt: |
Analyze this GitHub issue for completeness and determine if it needs labels.
If this looks like a bug on the device/firmware (crash, reboot, lockup, radio issues, GPS issues, display issues, power/sleep issues), request device logs and explain how to get them:
Web Flasher logs:
- Go to https://flasher.meshtastic.org
- Connect the device via USB and click Connect
- Open the device console/log output, reproduce the problem, then copy/download and attach/paste the logs
Meshtastic CLI logs:
- Run: meshtastic --port <serial-port> --noproto
- Reproduce the problem, then copy/paste the terminal output
Also request key context if missing: device model/variant, firmware version, region, steps to reproduce, expected vs actual.
Respond ONLY with JSON:
{
"complete": true|false,
"comment": "Your helpful comment requesting missing info, or empty string if complete",
"label": "needs-logs" | "needs-info" | "none"
}
Use "needs-logs" if this is a device bug AND no logs are attached.
Use "needs-info" if basic info like firmware version or steps to reproduce are missing.
Use "none" if the issue is complete or is a feature request.
Title: ${{ github.event.issue.title }}
Body: ${{ github.event.issue.body }}
system-prompt: You are a helpful assistant that triages GitHub issues. Be conservative with labels.
model: openai/gpt-4o-mini
- name: Process analysis result
if: (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '') && steps.check-skip.outputs.should_skip != 'true' && steps.analysis.outputs.response != ''
uses: actions/github-script@v8
id: process
env:
AI_RESPONSE: ${{ steps.analysis.outputs.response }}
with:
script: |
const raw = (process.env.AI_RESPONSE || '').trim();
let complete = false;
let comment = '';
let label = 'none';
try {
const parsed = JSON.parse(raw);
complete = !!parsed.complete;
comment = (parsed.comment ?? '').toString().trim();
label = (parsed.label ?? 'none').toString().trim().toLowerCase();
} catch {
// If JSON parse fails, treat as incomplete with raw response as comment
complete = false;
comment = raw;
label = 'none';
}
// Validate label
const allowedLabels = new Set(['needs-logs', 'needs-info', 'none']);
if (!allowedLabels.has(label)) label = 'none';
core.setOutput('should_comment', (!complete && comment.length > 0) ? 'true' : 'false');
core.setOutput('comment_body', comment);
core.setOutput('label', label);
- name: Apply triage label
if: steps.process.outputs.label != '' && steps.process.outputs.label != 'none'
uses: actions/github-script@v8
env:
LABEL_NAME: ${{ steps.process.outputs.label }}
with:
script: |
const label = process.env.LABEL_NAME;
const labelMeta = {
'needs-logs': { color: 'cfd3d7', description: 'Device logs requested for triage' },
'needs-info': { color: 'f9d0c4', description: 'More information requested for triage' },
};
const meta = labelMeta[label];
if (!meta) return;
// Ensure label exists
try {
await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label });
} catch (e) {
if (e.status !== 404) throw e;
await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description });
}
// Apply label
await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.issue.number, labels: [label] });
- name: Comment on issue
if: steps.process.outputs.should_comment == 'true'
uses: actions/github-script@v8
env:
COMMENT_BODY: ${{ steps.process.outputs.comment_body }}
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: process.env.COMMENT_BODY
});

138
.github/workflows/models_pr_triage.yml vendored Normal file
View File

@@ -0,0 +1,138 @@
name: PR Triage (Models)
on:
pull_request_target:
types: [opened]
permissions:
pull-requests: write
issues: write
models: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
triage:
if: ${{ github.repository == 'meshtastic/firmware' && github.event.pull_request.user.type != 'Bot' }}
runs-on: ubuntu-latest
steps:
# ─────────────────────────────────────────────────────────────────────────
# Step 1: Check if PR already has automation/type labels (skip if so)
# ─────────────────────────────────────────────────────────────────────────
- name: Check existing labels
uses: actions/github-script@v8
id: check-labels
with:
script: |
const skipLabels = new Set(['automation']);
const typeLabels = new Set(['bugfix', 'hardware-support', 'enhancement', 'dependencies', 'submodules', 'github_actions', 'trunk', 'cleanup']);
const prLabels = context.payload.pull_request.labels.map(l => l.name);
const shouldSkipAll = prLabels.some(l => skipLabels.has(l));
const hasTypeLabel = prLabels.some(l => typeLabels.has(l));
core.setOutput('skip_all', shouldSkipAll ? 'true' : 'false');
core.setOutput('has_type_label', hasTypeLabel ? 'true' : 'false');
# ─────────────────────────────────────────────────────────────────────────
# Step 2: Quality check (spam/AI-slop detection)
# ─────────────────────────────────────────────────────────────────────────
- name: Detect spam or low-quality content
if: steps.check-labels.outputs.skip_all != 'true'
uses: actions/ai-inference@v2
id: quality
continue-on-error: true
with:
max-tokens: 20
prompt: |
Is this GitHub pull request spam, AI-generated slop, or low quality?
Title: ${{ github.event.pull_request.title }}
Body: ${{ github.event.pull_request.body }}
Respond with exactly one of: spam, ai-generated, needs-review, ok
system-prompt: You detect spam and low-quality contributions. Be conservative - only flag obvious spam or AI slop.
model: openai/gpt-4o-mini
- name: Apply quality label if needed
if: steps.check-labels.outputs.skip_all != 'true' && steps.quality.outputs.response != '' && steps.quality.outputs.response != 'ok'
uses: actions/github-script@v8
id: quality-label
env:
QUALITY_LABEL: ${{ steps.quality.outputs.response }}
with:
script: |
const label = (process.env.QUALITY_LABEL || '').trim().toLowerCase();
const labelMeta = {
'spam': { color: 'd73a4a', description: 'Possible spam' },
'ai-generated': { color: 'fbca04', description: 'Possible AI-generated low-quality content' },
'needs-review': { color: 'f9d0c4', description: 'Needs human review' },
};
const meta = labelMeta[label];
if (!meta) return;
// Ensure label exists
try {
await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label });
} catch (e) {
if (e.status !== 404) throw e;
await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description });
}
// Apply label
await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, labels: [label] });
core.setOutput('is_spam', 'true');
# ─────────────────────────────────────────────────────────────────────────
# Step 3: Auto-label PR type (bugfix/hardware-support/enhancement)
# ─────────────────────────────────────────────────────────────────────────
- name: Classify PR for labeling
if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '')
uses: actions/ai-inference@v2
id: classify
continue-on-error: true
with:
max-tokens: 30
prompt: |
Classify this pull request into exactly one category.
Return exactly one of: bugfix, hardware-support, enhancement
Use bugfix if it fixes a bug, crash, or incorrect behavior.
Use hardware-support if it adds or improves support for a specific hardware device/variant.
Use enhancement if it adds a new feature, improves performance, or refactors code.
Title: ${{ github.event.pull_request.title }}
Body: ${{ github.event.pull_request.body }}
system-prompt: You classify pull requests into categories. Be conservative and pick the most appropriate single label.
model: openai/gpt-4o-mini
- name: Apply type label
if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && steps.classify.outputs.response != ''
uses: actions/github-script@v8
env:
TYPE_LABEL: ${{ steps.classify.outputs.response }}
with:
script: |
const label = (process.env.TYPE_LABEL || '').trim().toLowerCase();
const labelMeta = {
'bugfix': { color: 'd73a4a', description: 'Bug fix' },
'hardware-support': { color: '0e8a16', description: 'Hardware support addition or improvement' },
'enhancement': { color: 'a2eeef', description: 'New feature or enhancement' },
};
const meta = labelMeta[label];
if (!meta) return;
// Ensure label exists
try {
await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label });
} catch (e) {
if (e.status !== 404) throw e;
await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description });
}
// Apply label
await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, labels: [label] });

View File

@@ -0,0 +1,23 @@
Lora:
Module: sx1262
CS: 0
IRQ: 6
Reset: 1
Busy: 4
RXen: 2
DIO2_AS_RF_SWITCH: true
spidev: ch341
USB_PID: 0x5512
USB_VID: 0x1A86
DIO3_TCXO_VOLTAGE: true
# USB_Serialnum: 12345678
SX126X_MAX_POWER: 22
# Reduce output power to improve EMI
NUM_PA_POINTS: 22
TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7
# Note: This module integrates an additional PA to achieve higher output power.
# The 'power' parameter here does not represent the actual RF output.
# TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (122 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.

View File

@@ -1,15 +0,0 @@
Lora:
Module: sx1262
CS: 0
IRQ: 6
Reset: 1
Busy: 4
RXen: 2
DIO2_AS_RF_SWITCH: true
spidev: ch341
USB_PID: 0x5512
USB_VID: 0x1A86
DIO3_TCXO_VOLTAGE: true
# USB_Serialnum: 12345678
SX126X_MAX_POWER: 30
# Reduce output power to improve EMI

View File

@@ -0,0 +1,23 @@
Lora:
Module: sx1268
CS: 0
IRQ: 6
Reset: 1
Busy: 4
RXen: 2
DIO2_AS_RF_SWITCH: true
spidev: ch341
USB_PID: 0x5512
USB_VID: 0x1A86
DIO3_TCXO_VOLTAGE: true
# USB_Serialnum: 12345678
SX126X_MAX_POWER: 22
# Reduce output power to improve EMI
NUM_PA_POINTS: 22
TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7
# Note: This module integrates an additional PA to achieve higher output power.
# The 'power' parameter here does not represent the actual RF output.
# TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (122 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.

View File

@@ -1,15 +0,0 @@
Lora:
Module: sx1268
CS: 0
IRQ: 6
Reset: 1
Busy: 4
RXen: 2
DIO2_AS_RF_SWITCH: true
spidev: ch341
USB_PID: 0x5512
USB_VID: 0x1A86
DIO3_TCXO_VOLTAGE: true
# USB_Serialnum: 12345678
SX126X_MAX_POWER: 30
# Reduce output power to improve EMI

View File

@@ -119,7 +119,7 @@ lib_deps =
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/37ad715b76cd6ca4aa500a4a4d9740e3cdf3e3cb.zip
https://github.com/meshtastic/device-ui/archive/69739b84f87a91568d3c421498bc89977937a141.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]

View File

@@ -1731,6 +1731,26 @@ int Screen::handleInputEvent(const InputEvent *event)
showFrame(FrameDirection::PREVIOUS);
} else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) {
showFrame(FrameDirection::NEXT);
} else if (event->inputEvent == INPUT_BROKER_FN_F1) {
this->ui->switchToFrame(0);
lastScreenTransition = millis();
setFastFramerate();
} else if (event->inputEvent == INPUT_BROKER_FN_F2) {
this->ui->switchToFrame(1);
lastScreenTransition = millis();
setFastFramerate();
} else if (event->inputEvent == INPUT_BROKER_FN_F3) {
this->ui->switchToFrame(2);
lastScreenTransition = millis();
setFastFramerate();
} else if (event->inputEvent == INPUT_BROKER_FN_F4) {
this->ui->switchToFrame(3);
lastScreenTransition = millis();
setFastFramerate();
} else if (event->inputEvent == INPUT_BROKER_FN_F5) {
this->ui->switchToFrame(4);
lastScreenTransition = millis();
setFastFramerate();
} else if (event->inputEvent == INPUT_BROKER_UP_LONG) {
// Long press up button for fast frame switching
showPrevFrame();

View File

@@ -431,45 +431,6 @@ static int getDrawnLinePixelBottom(int lineTopY, const std::string &line, bool i
return iconTop + tallest - 1;
}
static void drawRoundedRectOutline(OLEDDisplay *display, int x, int y, int w, int h, int r)
{
if (w <= 1 || h <= 1)
return;
if (r < 0)
r = 0;
int maxR = (std::min(w, h) / 2) - 1;
if (r > maxR)
r = maxR;
if (r == 0) {
display->drawRect(x, y, w, h);
return;
}
const int x0 = x;
const int y0 = y;
const int x1 = x + w - 1;
const int y1 = y + h - 1;
// sides
if (x0 + r <= x1 - r) {
display->drawLine(x0 + r, y0, x1 - r, y0); // top
display->drawLine(x0 + r, y1, x1 - r, y1); // bottom
}
if (y0 + r <= y1 - r) {
display->drawLine(x0, y0 + r, x0, y1 - r); // left
display->drawLine(x1, y0 + r, x1, y1 - r); // right
}
// corner arcs
display->drawCircleQuads(x0 + r, y0 + r, r, 2); // top left
display->drawCircleQuads(x1 - r, y0 + r, r, 1); // top right
display->drawCircleQuads(x1 - r, y1 - r, r, 8); // bottom right
display->drawCircleQuads(x0 + r, y1 - r, r, 4); // bottom left
}
static std::vector<MessageBlock> buildMessageBlocks(const std::vector<bool> &isHeaderVec, const std::vector<bool> &isMineVec)
{
std::vector<MessageBlock> blocks;
@@ -909,27 +870,37 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
bubbleW = std::max(1, rightEdge - bubbleX);
if (bubbleW > 1 && bubbleH > 1) {
int r = BUBBLE_RADIUS;
int maxR = (std::min(bubbleW, bubbleH) / 2) - 1;
if (maxR < 0)
maxR = 0;
if (r > maxR)
r = maxR;
drawRoundedRectOutline(display, bubbleX, topY, bubbleW, bubbleH, r);
const int extra = 3;
const int rr = r + extra;
int x1 = bubbleX + bubbleW - 1;
int y1 = topY + bubbleH - 1;
if (!b.mine) {
// top-left corner square
display->drawLine(bubbleX, topY, bubbleX + rr, topY);
display->drawLine(bubbleX, topY, bubbleX, topY + rr);
if (b.mine) {
// Send Message (Right side)
display->drawRect(x1 + 2 - bubbleW, y1 - bubbleH, bubbleW, bubbleH);
// Top Right Corner
display->drawRect(x1, topY, 2, 1);
display->drawRect(x1, topY, 1, 2);
// Bottom Right Corner
display->drawRect(x1 - 1, bottomY - 2, 2, 1);
display->drawRect(x1, bottomY - 3, 1, 2);
// Knock the corners off to make a bubble
display->setColor(BLACK);
display->drawRect(x1 - bubbleW, topY - 1, 1, 1);
display->drawRect(x1 - bubbleW, bottomY - 1, 1, 1);
display->setColor(WHITE);
} else {
// bottom-right corner square
display->drawLine(x1 - rr, y1, x1, y1);
display->drawLine(x1, y1 - rr, x1, y1);
// 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);
}
}
}

View File

@@ -20,20 +20,20 @@ constexpr uint8_t modifierLeftShift = 0b0001;
// Num chars per key, Modulus for rotating through characters
static uint8_t HackadayCommunicatorTapMod[_TCA8418_NUM_KEYS] = {
0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 0, 0, 0, 1, 2, 2, 2, 1, 2, 2, 0, 0, 0, 2, 1, 2, 2, 0, 1, 1, 0,
};
static unsigned char HackadayCommunicatorTapMap[_TCA8418_NUM_KEYS][2] = {{},
{},
{Key::FUNCTION_F1},
{'+'},
{'9'},
{'8'},
{'7'},
{'2'},
{'3'},
{'4'},
{'5'},
{Key::FUNCTION_F2},
{Key::FUNCTION_F3},
{Key::FUNCTION_F4},
{Key::FUNCTION_F5},
{Key::ESC},
{'q', 'Q'},
{'w', 'W'},
@@ -141,6 +141,7 @@ void HackadayCommunicatorKeyboard::pressed(uint8_t key)
if (state == Init || state == Busy) {
return;
}
LOG_DEBUG("Key pressed: %u", key);
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
modifierFlag = 0;

View File

@@ -1,8 +1,59 @@
#include "InputBroker.h"
#include "PowerFSM.h" // needed for event trigger
#include "configuration.h"
#include "graphics/Screen.h"
#include "modules/ExternalNotificationModule.h"
#if ARCH_PORTDUINO
#include "input/LinuxInputImpl.h"
#include "input/SeesawRotary.h"
#include "platform/portduino/PortduinoGlue.h"
#endif
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
#include "input/ExpressLRSFiveWay.h"
#include "input/RotaryEncoderImpl.h"
#include "input/RotaryEncoderInterruptImpl1.h"
#include "input/SerialKeyboardImpl.h"
#include "input/UpDownInterruptImpl1.h"
#include "input/i2cButton.h"
#if HAS_TRACKBALL
#include "input/TrackballInterruptImpl1.h"
#endif
#include "modules/StatusLEDModule.h"
#if !MESHTASTIC_EXCLUDE_I2C
#include "input/cardKbI2cImpl.h"
#endif
#include "input/kbMatrixImpl.h"
#endif
#if HAS_BUTTON || defined(ARCH_PORTDUINO)
#include "input/ButtonThread.h"
#if defined(BUTTON_PIN_TOUCH)
ButtonThread *TouchButtonThread = nullptr;
#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN)
static bool touchBacklightWasOn = false;
static bool touchBacklightActive = false;
#endif
#endif
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
ButtonThread *UserButtonThread = nullptr;
#endif
#if defined(ALT_BUTTON_PIN)
ButtonThread *BackButtonThread = nullptr;
#endif
#if defined(CANCEL_BUTTON_PIN)
ButtonThread *CancelButtonThread = nullptr;
#endif
#endif
InputBroker *inputBroker = nullptr;
InputBroker::InputBroker()
@@ -74,3 +125,262 @@ void InputBroker::pollSoonWorker(void *p)
vTaskDelete(NULL);
}
#endif
void InputBroker::Init()
{
#ifdef BUTTON_PIN
#ifdef ARCH_ESP32
#if ESP_ARDUINO_VERSION_MAJOR >= 3
#ifdef BUTTON_NEED_PULLUP
pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT_PULLUP);
#else
pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN
#endif
#else
pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN
#ifdef BUTTON_NEED_PULLUP
gpio_pullup_en((gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN));
delay(10);
#endif
#ifdef BUTTON_NEED_PULLUP2
gpio_pullup_en((gpio_num_t)BUTTON_NEED_PULLUP2);
delay(10);
#endif
#endif
#endif
#endif
// buttons are now inputBroker, so have to come after setupModules
#if HAS_BUTTON
int pullup_sense = 0;
#ifdef INPUT_PULLUP_SENSE
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
#ifdef BUTTON_SENSE_TYPE
pullup_sense = BUTTON_SENSE_TYPE;
#else
pullup_sense = INPUT_PULLUP_SENSE;
#endif
#endif
#if defined(ARCH_PORTDUINO)
if (portduino_config.userButtonPin.enabled) {
LOG_DEBUG("Use GPIO%02d for button", portduino_config.userButtonPin.pin);
UserButtonThread = new ButtonThread("UserButton");
if (screen) {
ButtonConfig config;
config.pinNumber = (uint8_t)portduino_config.userButtonPin.pin;
config.activeLow = true;
config.activePullup = true;
config.pullupSense = INPUT_PULLUP;
config.intRoutine = []() {
UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
concurrency::mainDelay.interruptFromISR(&higherWake);
};
config.singlePress = INPUT_BROKER_USER_PRESS;
config.longPress = INPUT_BROKER_SELECT;
UserButtonThread->initButton(config);
}
}
#endif
#ifdef BUTTON_PIN_TOUCH
TouchButtonThread = new ButtonThread("BackButton");
ButtonConfig touchConfig;
touchConfig.pinNumber = BUTTON_PIN_TOUCH;
touchConfig.activeLow = true;
touchConfig.activePullup = true;
touchConfig.pullupSense = pullup_sense;
touchConfig.intRoutine = []() {
TouchButtonThread->userButton.tick();
TouchButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
concurrency::mainDelay.interruptFromISR(&higherWake);
};
touchConfig.singlePress = INPUT_BROKER_NONE;
touchConfig.longPress = INPUT_BROKER_BACK;
#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN)
// On T-Echo Plus the touch pad should only drive the backlight, not UI navigation/sounds
touchConfig.longPress = INPUT_BROKER_NONE;
touchConfig.suppressLeadUpSound = true;
touchConfig.onPress = []() {
touchBacklightWasOn = uiconfig.screen_brightness == 1;
if (!touchBacklightWasOn) {
digitalWrite(PIN_EINK_EN, HIGH);
}
touchBacklightActive = true;
};
touchConfig.onRelease = []() {
if (touchBacklightActive && !touchBacklightWasOn) {
digitalWrite(PIN_EINK_EN, LOW);
}
touchBacklightActive = false;
};
#endif
TouchButtonThread->initButton(touchConfig);
#endif
#if defined(CANCEL_BUTTON_PIN)
// Buttons. Moved here cause we need NodeDB to be initialized
CancelButtonThread = new ButtonThread("CancelButton");
ButtonConfig cancelConfig;
cancelConfig.pinNumber = CANCEL_BUTTON_PIN;
cancelConfig.activeLow = CANCEL_BUTTON_ACTIVE_LOW;
cancelConfig.activePullup = CANCEL_BUTTON_ACTIVE_PULLUP;
cancelConfig.pullupSense = pullup_sense;
cancelConfig.intRoutine = []() {
CancelButtonThread->userButton.tick();
CancelButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
concurrency::mainDelay.interruptFromISR(&higherWake);
};
cancelConfig.singlePress = INPUT_BROKER_CANCEL;
cancelConfig.longPress = INPUT_BROKER_SHUTDOWN;
cancelConfig.longPressTime = 4000;
CancelButtonThread->initButton(cancelConfig);
#endif
#if defined(ALT_BUTTON_PIN)
// Buttons. Moved here cause we need NodeDB to be initialized
BackButtonThread = new ButtonThread("BackButton");
ButtonConfig backConfig;
backConfig.pinNumber = ALT_BUTTON_PIN;
backConfig.activeLow = ALT_BUTTON_ACTIVE_LOW;
backConfig.activePullup = ALT_BUTTON_ACTIVE_PULLUP;
backConfig.pullupSense = pullup_sense;
backConfig.intRoutine = []() {
BackButtonThread->userButton.tick();
BackButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
concurrency::mainDelay.interruptFromISR(&higherWake);
};
backConfig.singlePress = INPUT_BROKER_ALT_PRESS;
backConfig.longPress = INPUT_BROKER_ALT_LONG;
backConfig.longPressTime = 500;
BackButtonThread->initButton(backConfig);
#endif
#if defined(BUTTON_PIN)
#if defined(USERPREFS_BUTTON_PIN)
int _pinNum = config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN;
#else
int _pinNum = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN;
#endif
#ifndef BUTTON_ACTIVE_LOW
#define BUTTON_ACTIVE_LOW true
#endif
#ifndef BUTTON_ACTIVE_PULLUP
#define BUTTON_ACTIVE_PULLUP true
#endif
// Buttons. Moved here cause we need NodeDB to be initialized
// If your variant.h has a BUTTON_PIN defined, go ahead and define BUTTON_ACTIVE_LOW and BUTTON_ACTIVE_PULLUP
UserButtonThread = new ButtonThread("UserButton");
if (screen) {
ButtonConfig userConfig;
userConfig.pinNumber = (uint8_t)_pinNum;
userConfig.activeLow = BUTTON_ACTIVE_LOW;
userConfig.activePullup = BUTTON_ACTIVE_PULLUP;
userConfig.pullupSense = pullup_sense;
userConfig.intRoutine = []() {
UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
concurrency::mainDelay.interruptFromISR(&higherWake);
};
userConfig.singlePress = INPUT_BROKER_USER_PRESS;
userConfig.longPress = INPUT_BROKER_SELECT;
userConfig.longPressTime = 500;
userConfig.longLongPress = INPUT_BROKER_SHUTDOWN;
UserButtonThread->initButton(userConfig);
} else {
ButtonConfig userConfigNoScreen;
userConfigNoScreen.pinNumber = (uint8_t)_pinNum;
userConfigNoScreen.activeLow = BUTTON_ACTIVE_LOW;
userConfigNoScreen.activePullup = BUTTON_ACTIVE_PULLUP;
userConfigNoScreen.pullupSense = pullup_sense;
userConfigNoScreen.intRoutine = []() {
UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
concurrency::mainDelay.interruptFromISR(&higherWake);
};
userConfigNoScreen.singlePress = INPUT_BROKER_USER_PRESS;
userConfigNoScreen.longPress = INPUT_BROKER_NONE;
userConfigNoScreen.longPressTime = 500;
userConfigNoScreen.longLongPress = INPUT_BROKER_SHUTDOWN;
userConfigNoScreen.doublePress = INPUT_BROKER_SEND_PING;
userConfigNoScreen.triplePress = INPUT_BROKER_GPS_TOGGLE;
UserButtonThread->initButton(userConfigNoScreen);
}
#endif
#endif
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
#if defined(T_LORA_PAGER)
// use a special FSM based rotary encoder version for T-LoRa Pager
rotaryEncoderImpl = new RotaryEncoderImpl();
if (!rotaryEncoderImpl->init()) {
delete rotaryEncoderImpl;
rotaryEncoderImpl = nullptr;
}
#elif defined(INPUTDRIVER_ENCODER_TYPE) && (INPUTDRIVER_ENCODER_TYPE == 2)
upDownInterruptImpl1 = new UpDownInterruptImpl1();
if (!upDownInterruptImpl1->init()) {
delete upDownInterruptImpl1;
upDownInterruptImpl1 = nullptr;
}
#else
rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
if (!rotaryEncoderInterruptImpl1->init()) {
delete rotaryEncoderInterruptImpl1;
rotaryEncoderInterruptImpl1 = nullptr;
}
#endif
cardKbI2cImpl = new CardKbI2cImpl();
cardKbI2cImpl->init();
#if defined(M5STACK_UNITC6L)
i2cButton = new i2cButtonThread("i2cButtonThread");
#endif
#ifdef INPUTBROKER_MATRIX_TYPE
kbMatrixImpl = new KbMatrixImpl();
kbMatrixImpl->init();
#endif // INPUTBROKER_MATRIX_TYPE
#ifdef INPUTBROKER_SERIAL_TYPE
aSerialKeyboardImpl = new SerialKeyboardImpl();
aSerialKeyboardImpl->init();
#endif // INPUTBROKER_MATRIX_TYPE
}
#endif // HAS_BUTTON
#if ARCH_PORTDUINO
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR && portduino_config.i2cdev != "") {
seesawRotary = new SeesawRotary("SeesawRotary");
if (!seesawRotary->init()) {
delete seesawRotary;
seesawRotary = nullptr;
}
aLinuxInputImpl = new LinuxInputImpl();
aLinuxInputImpl->init();
}
#endif
#if !MESHTASTIC_EXCLUDE_INPUTBROKER && HAS_TRACKBALL
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
trackballInterruptImpl1 = new TrackballInterruptImpl1();
trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS);
}
#endif
#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE
expressLRSFiveWayInput = new ExpressLRSFiveWay();
#endif
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include "Observer.h"
#include "concurrency/OSThread.h"
#include "freertosinc.h"
#ifdef InputBrokerDebug
@@ -27,6 +28,11 @@ enum input_broker_event {
INPUT_BROKER_SHUTDOWN = 0x9b,
INPUT_BROKER_GPS_TOGGLE = 0x9e,
INPUT_BROKER_SEND_PING = 0xaf,
INPUT_BROKER_FN_F1 = 0xf1,
INPUT_BROKER_FN_F2 = 0xf2,
INPUT_BROKER_FN_F3 = 0xf3,
INPUT_BROKER_FN_F4 = 0xf4,
INPUT_BROKER_FN_F5 = 0xf5,
INPUT_BROKER_MATRIXKEY = 0xFE,
INPUT_BROKER_ANYKEY = 0xff
@@ -71,6 +77,7 @@ class InputBroker : public Observable<const InputEvent *>
void queueInputEvent(const InputEvent *event);
void processInputEventQueue();
#endif
void Init();
protected:
int handleInputEvent(const InputEvent *event);
@@ -84,4 +91,5 @@ class InputBroker : public Observable<const InputEvent *>
#endif
};
extern InputBroker *inputBroker;
extern InputBroker *inputBroker;
extern bool runASAP;

View File

@@ -26,7 +26,12 @@ class TCA8418KeyboardBase
GPS_TOGGLE = 0x9E,
MUTE_TOGGLE = 0xAC,
SEND_PING = 0xAF,
BL_TOGGLE = 0xAB
BL_TOGGLE = 0xAB,
FUNCTION_F1 = 0xF1,
FUNCTION_F2 = 0xF2,
FUNCTION_F3 = 0xF3,
FUNCTION_F4 = 0xF4,
FUNCTION_F5 = 0xF5
};
typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len);

View File

@@ -321,6 +321,26 @@ int32_t KbI2cBase::runOnce()
e.inputEvent = INPUT_BROKER_ANYKEY;
e.kbchar = INPUT_BROKER_MSG_TAB;
break;
case TCA8418KeyboardBase::FUNCTION_F1:
e.inputEvent = INPUT_BROKER_FN_F1;
e.kbchar = 0x00;
break;
case TCA8418KeyboardBase::FUNCTION_F2:
e.inputEvent = INPUT_BROKER_FN_F2;
e.kbchar = 0x00;
break;
case TCA8418KeyboardBase::FUNCTION_F3:
e.inputEvent = INPUT_BROKER_FN_F3;
e.kbchar = 0x00;
break;
case TCA8418KeyboardBase::FUNCTION_F4:
e.inputEvent = INPUT_BROKER_FN_F4;
e.kbchar = 0x00;
break;
case TCA8418KeyboardBase::FUNCTION_F5:
e.inputEvent = INPUT_BROKER_FN_F5;
e.kbchar = 0x00;
break;
default:
if (nextEvent > 127) {
e.inputEvent = INPUT_BROKER_NONE;

View File

@@ -120,31 +120,6 @@ void printPartitionTable()
#endif // DEBUG_PARTITION_TABLE
#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 "PowerFSMThread.h"
@@ -509,30 +484,6 @@ void setup()
LOG_INFO("Wait for peripherals to stabilize");
delay(PERIPHERAL_WARMUP_MS);
#endif
#ifdef BUTTON_PIN
#ifdef ARCH_ESP32
#if ESP_ARDUINO_VERSION_MAJOR >= 3
#ifdef BUTTON_NEED_PULLUP
pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT_PULLUP);
#else
pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN
#endif
#else
pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN
#ifdef BUTTON_NEED_PULLUP
gpio_pullup_en((gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN));
delay(10);
#endif
#ifdef BUTTON_NEED_PULLUP2
gpio_pullup_en((gpio_num_t)BUTTON_NEED_PULLUP2);
delay(10);
#endif
#endif
#endif
#endif
initSPI();
OSThread::setup();
@@ -999,180 +950,9 @@ void setup()
nodeDB->hasWarned = true;
}
#endif
// buttons are now inputBroker, so have to come after setupModules
#if HAS_BUTTON
int pullup_sense = 0;
#ifdef INPUT_PULLUP_SENSE
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
#ifdef BUTTON_SENSE_TYPE
pullup_sense = BUTTON_SENSE_TYPE;
#else
pullup_sense = INPUT_PULLUP_SENSE;
#endif
#endif
#if defined(ARCH_PORTDUINO)
if (portduino_config.userButtonPin.enabled) {
LOG_DEBUG("Use GPIO%02d for button", portduino_config.userButtonPin.pin);
UserButtonThread = new ButtonThread("UserButton");
if (screen) {
ButtonConfig config;
config.pinNumber = (uint8_t)portduino_config.userButtonPin.pin;
config.activeLow = true;
config.activePullup = true;
config.pullupSense = INPUT_PULLUP;
config.intRoutine = []() {
UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
};
config.singlePress = INPUT_BROKER_USER_PRESS;
config.longPress = INPUT_BROKER_SELECT;
UserButtonThread->initButton(config);
}
}
#endif
#ifdef BUTTON_PIN_TOUCH
TouchButtonThread = new ButtonThread("BackButton");
ButtonConfig touchConfig;
touchConfig.pinNumber = BUTTON_PIN_TOUCH;
touchConfig.activeLow = true;
touchConfig.activePullup = true;
touchConfig.pullupSense = pullup_sense;
touchConfig.intRoutine = []() {
TouchButtonThread->userButton.tick();
TouchButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
};
touchConfig.singlePress = INPUT_BROKER_NONE;
touchConfig.longPress = INPUT_BROKER_BACK;
#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN)
// On T-Echo Plus the touch pad should only drive the backlight, not UI navigation/sounds
touchConfig.longPress = INPUT_BROKER_NONE;
touchConfig.suppressLeadUpSound = true;
touchConfig.onPress = []() {
touchBacklightWasOn = uiconfig.screen_brightness == 1;
if (!touchBacklightWasOn) {
digitalWrite(PIN_EINK_EN, HIGH);
}
touchBacklightActive = true;
};
touchConfig.onRelease = []() {
if (touchBacklightActive && !touchBacklightWasOn) {
digitalWrite(PIN_EINK_EN, LOW);
}
touchBacklightActive = false;
};
#endif
TouchButtonThread->initButton(touchConfig);
#endif
#if defined(CANCEL_BUTTON_PIN)
// Buttons. Moved here cause we need NodeDB to be initialized
CancelButtonThread = new ButtonThread("CancelButton");
ButtonConfig cancelConfig;
cancelConfig.pinNumber = CANCEL_BUTTON_PIN;
cancelConfig.activeLow = CANCEL_BUTTON_ACTIVE_LOW;
cancelConfig.activePullup = CANCEL_BUTTON_ACTIVE_PULLUP;
cancelConfig.pullupSense = pullup_sense;
cancelConfig.intRoutine = []() {
CancelButtonThread->userButton.tick();
CancelButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
};
cancelConfig.singlePress = INPUT_BROKER_CANCEL;
cancelConfig.longPress = INPUT_BROKER_SHUTDOWN;
cancelConfig.longPressTime = 4000;
CancelButtonThread->initButton(cancelConfig);
#endif
#if defined(ALT_BUTTON_PIN)
// Buttons. Moved here cause we need NodeDB to be initialized
BackButtonThread = new ButtonThread("BackButton");
ButtonConfig backConfig;
backConfig.pinNumber = ALT_BUTTON_PIN;
backConfig.activeLow = ALT_BUTTON_ACTIVE_LOW;
backConfig.activePullup = ALT_BUTTON_ACTIVE_PULLUP;
backConfig.pullupSense = pullup_sense;
backConfig.intRoutine = []() {
BackButtonThread->userButton.tick();
BackButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
};
backConfig.singlePress = INPUT_BROKER_ALT_PRESS;
backConfig.longPress = INPUT_BROKER_ALT_LONG;
backConfig.longPressTime = 500;
BackButtonThread->initButton(backConfig);
#endif
#if defined(BUTTON_PIN)
#if defined(USERPREFS_BUTTON_PIN)
int _pinNum = config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN;
#else
int _pinNum = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN;
#endif
#ifndef BUTTON_ACTIVE_LOW
#define BUTTON_ACTIVE_LOW true
#endif
#ifndef BUTTON_ACTIVE_PULLUP
#define BUTTON_ACTIVE_PULLUP true
#endif
// Buttons. Moved here cause we need NodeDB to be initialized
// If your variant.h has a BUTTON_PIN defined, go ahead and define BUTTON_ACTIVE_LOW and BUTTON_ACTIVE_PULLUP
UserButtonThread = new ButtonThread("UserButton");
if (screen) {
ButtonConfig userConfig;
userConfig.pinNumber = (uint8_t)_pinNum;
userConfig.activeLow = BUTTON_ACTIVE_LOW;
userConfig.activePullup = BUTTON_ACTIVE_PULLUP;
userConfig.pullupSense = pullup_sense;
userConfig.intRoutine = []() {
UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
};
userConfig.singlePress = INPUT_BROKER_USER_PRESS;
userConfig.longPress = INPUT_BROKER_SELECT;
userConfig.longPressTime = 500;
userConfig.longLongPress = INPUT_BROKER_SHUTDOWN;
UserButtonThread->initButton(userConfig);
} else {
ButtonConfig userConfigNoScreen;
userConfigNoScreen.pinNumber = (uint8_t)_pinNum;
userConfigNoScreen.activeLow = BUTTON_ACTIVE_LOW;
userConfigNoScreen.activePullup = BUTTON_ACTIVE_PULLUP;
userConfigNoScreen.pullupSense = pullup_sense;
userConfigNoScreen.intRoutine = []() {
UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
};
userConfigNoScreen.singlePress = INPUT_BROKER_USER_PRESS;
userConfigNoScreen.longPress = INPUT_BROKER_NONE;
userConfigNoScreen.longPressTime = 500;
userConfigNoScreen.longLongPress = INPUT_BROKER_SHUTDOWN;
userConfigNoScreen.doublePress = INPUT_BROKER_SEND_PING;
userConfigNoScreen.triplePress = INPUT_BROKER_GPS_TOGGLE;
UserButtonThread->initButton(userConfigNoScreen);
}
#endif
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
if (inputBroker)
inputBroker->Init();
#endif
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -1401,7 +1181,43 @@ void loop()
if (inputBroker)
inputBroker->processInputEventQueue();
#endif
#if ARCH_PORTDUINO && HAS_TFT
#if ARCH_PORTDUINO
if (portduino_config.lora_spi_dev == "ch341" && ch341Hal != nullptr) {
ch341Hal->checkError();
}
if (portduino_status.LoRa_in_error && rebootAtMsec == 0) {
LOG_ERROR("LoRa in error detected, attempting to recover");
if (rIf != nullptr) {
delete rIf;
rIf = nullptr;
}
if (portduino_config.lora_spi_dev == "ch341") {
if (ch341Hal != nullptr) {
delete ch341Hal;
ch341Hal = nullptr;
sleep(3);
}
try {
ch341Hal = new Ch341Hal(0, portduino_config.lora_usb_serial_num, portduino_config.lora_usb_vid,
portduino_config.lora_usb_pid);
} catch (std::exception &e) {
std::cerr << e.what() << std::endl;
std::cerr << "Could not initialize CH341 device!" << std::endl;
exit(EXIT_FAILURE);
}
}
if (initLoRa()) {
router->addInterface(rIf);
portduino_status.LoRa_in_error = false;
} else {
LOG_WARN("Reconfigure failed, rebooting");
if (screen) {
screen->showSimpleBanner("Rebooting...");
}
rebootAtMsec = millis() + 25;
}
}
#if HAS_TFT
if (screen && portduino_config.displayPanel == x11 &&
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
auto dispdev = screen->getDisplayDevice();
@@ -1409,6 +1225,7 @@ void loop()
static_cast<TFTDisplay *>(dispdev)->sdlLoop();
}
#endif
#endif
#if HAS_SCREEN && ENABLE_MESSAGE_PERSISTENCE
messageStoreAutosaveTick();
#endif

View File

@@ -91,10 +91,21 @@ template <typename T> bool LR11x0Interface<T>::init()
LOG_DEBUG("Set RF1 switch to %s", getFreq() < 1e9 ? "SubGHz" : "2.4GHz");
#endif
// Allow extra time for TCXO to stabilize after power-on
delay(10);
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage);
// Retry if we get SPI command failed - some units need extra TCXO stabilization time
if (res == RADIOLIB_ERR_SPI_CMD_FAILED) {
LOG_WARN("LR11x0 init failed with %d (SPI_CMD_FAILED), retrying after delay...", res);
delay(100);
res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage);
}
// \todo Display actual typename of the adapter, not just `LR11x0`
LOG_INFO("LR11x0 init result %d", res);
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND)
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
return false;
LR11x0VersionInfo_t version;

View File

@@ -2247,7 +2247,10 @@ void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, co
// Currently portuino is mostly used for simulation. Make sure the user notices something really bad happened
#ifdef ARCH_PORTDUINO
LOG_ERROR("A critical failure occurred, portduino is exiting");
exit(2);
LOG_ERROR("A critical failure occurred");
// TODO: Determine if other critical errors should also cause an immediate exit
if (code == meshtastic_CriticalErrorCode_FLASH_CORRUPTION_RECOVERABLE ||
code == meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE)
exit(2);
#endif
}

View File

@@ -177,6 +177,9 @@ bool RF95Interface::init()
int res = lora->begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength);
LOG_INFO("RF95 init result %d", res);
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
return false;
LOG_INFO("Frequency set to %f", getFreq());
LOG_INFO("Bandwidth set to %f", bw);
LOG_INFO("Power output set to %d", power);

View File

@@ -269,8 +269,12 @@ template <typename T> void SX126xInterface<T>::setStandby()
if (err != RADIOLIB_ERR_NONE)
LOG_DEBUG("SX126x standby %s%d", radioLibErr, err);
#ifdef ARCH_PORTDUINO
if (err != RADIOLIB_ERR_NONE)
portduino_status.LoRa_in_error = true;
#else
assert(err == RADIOLIB_ERR_NONE);
#endif
isReceiving = false; // If we were receiving, not any more
activeReceiveStart = 0;
disableInterrupt();
@@ -313,7 +317,12 @@ template <typename T> void SX126xInterface<T>::startReceive()
int err = lora.startReceiveDutyCycleAuto(preambleLength, 8, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS);
if (err != RADIOLIB_ERR_NONE)
LOG_ERROR("SX126X startReceiveDutyCycleAuto %s%d", radioLibErr, err);
#ifdef ARCH_PORTDUINO
if (err != RADIOLIB_ERR_NONE)
portduino_status.LoRa_in_error = true;
#else
assert(err == RADIOLIB_ERR_NONE);
#endif
RadioLibInterface::startReceive();
@@ -341,7 +350,12 @@ template <typename T> bool SX126xInterface<T>::isChannelActive()
return true;
if (result != RADIOLIB_CHANNEL_FREE)
LOG_ERROR("SX126X scanChannel %s%d", radioLibErr, result);
#ifdef ARCH_PORTDUINO
if (result == RADIOLIB_ERR_WRONG_MODEM)
portduino_status.LoRa_in_error = true;
#else
assert(result != RADIOLIB_ERR_WRONG_MODEM);
#endif
return false;
}

View File

@@ -69,6 +69,8 @@ template <typename T> bool SX128xInterface<T>::init()
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength);
// \todo Display actual typename of the adapter, not just `SX128x`
LOG_INFO("SX128x init result %d", res);
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
return false;
if ((config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (res == RADIOLIB_ERR_INVALID_FREQUENCY)) {
LOG_WARN("Radio only supports 2.4GHz LoRa. Adjusting Region and rebooting");

View File

@@ -27,6 +27,15 @@ PB_BIND(meshtastic_SharedContact, meshtastic_SharedContact, AUTO)
PB_BIND(meshtastic_KeyVerificationAdmin, meshtastic_KeyVerificationAdmin, AUTO)
PB_BIND(meshtastic_SensorConfig, meshtastic_SensorConfig, AUTO)
PB_BIND(meshtastic_SCD4X_config, meshtastic_SCD4X_config, AUTO)
PB_BIND(meshtastic_SEN5X_config, meshtastic_SEN5X_config, AUTO)

View File

@@ -171,6 +171,48 @@ typedef struct _meshtastic_KeyVerificationAdmin {
uint32_t security_number;
} meshtastic_KeyVerificationAdmin;
typedef struct _meshtastic_SCD4X_config {
/* Set Automatic self-calibration enabled */
bool has_set_asc;
bool set_asc;
/* Recalibration target CO2 concentration in ppm (FRC or ASC) */
bool has_set_target_co2_conc;
uint32_t set_target_co2_conc;
/* Reference temperature in degC */
bool has_set_temperature;
float set_temperature;
/* Altitude of sensor in meters above sea level. 0 - 3000m (overrides ambient pressure) */
bool has_set_altitude;
uint32_t set_altitude;
/* Sensor ambient pressure in Pa. 70000 - 120000 Pa (overrides altitude) */
bool has_set_ambient_pressure;
uint32_t set_ambient_pressure;
/* Perform a factory reset of the sensor */
bool has_factory_reset;
bool factory_reset;
/* Power mode for sensor (true for low power, false for normal) */
bool has_set_power_mode;
bool set_power_mode;
} meshtastic_SCD4X_config;
typedef struct _meshtastic_SEN5X_config {
/* Reference temperature in degC */
bool has_set_temperature;
float set_temperature;
/* One-shot mode (true for low power - one-shot mode, false for normal - continuous mode) */
bool has_set_one_shot_mode;
bool set_one_shot_mode;
} meshtastic_SEN5X_config;
typedef struct _meshtastic_SensorConfig {
/* SCD4X CO2 Sensor configuration */
bool has_scd4x_config;
meshtastic_SCD4X_config scd4x_config;
/* SEN5X PM Sensor configuration */
bool has_sen5x_config;
meshtastic_SEN5X_config sen5x_config;
} meshtastic_SensorConfig;
typedef PB_BYTES_ARRAY_T(8) meshtastic_AdminMessage_session_passkey_t;
/* This message is handled by the Admin module and is responsible for all settings/channel read/write operations.
This message is used to do settings operations to both remote AND local nodes.
@@ -303,6 +345,8 @@ typedef struct _meshtastic_AdminMessage {
bool nodedb_reset;
/* Tell the node to reset into the OTA Loader */
meshtastic_AdminMessage_OTAEvent ota_request;
/* Parameters and sensor configuration */
meshtastic_SensorConfig sensor_config;
};
/* The node generates this key and sends it with any get_x_response packets.
The client MUST include the same key with any set_x commands. Key expires after 300 seconds.
@@ -351,6 +395,9 @@ extern "C" {
#define meshtastic_KeyVerificationAdmin_message_type_ENUMTYPE meshtastic_KeyVerificationAdmin_MessageType
/* Initializer values for message structs */
#define meshtastic_AdminMessage_init_default {0, {0}, {0, {0}}}
#define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0}
@@ -359,6 +406,9 @@ extern "C" {
#define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
#define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0}
#define meshtastic_KeyVerificationAdmin_init_default {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
#define meshtastic_SensorConfig_init_default {false, meshtastic_SCD4X_config_init_default, false, meshtastic_SEN5X_config_init_default}
#define meshtastic_SCD4X_config_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_SEN5X_config_init_default {false, 0, false, 0}
#define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}}
#define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0}
#define meshtastic_AdminMessage_OTAEvent_init_zero {_meshtastic_OTAMode_MIN, {0, {0}}}
@@ -366,6 +416,9 @@ extern "C" {
#define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
#define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0}
#define meshtastic_KeyVerificationAdmin_init_zero {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
#define meshtastic_SensorConfig_init_zero {false, meshtastic_SCD4X_config_init_zero, false, meshtastic_SEN5X_config_init_zero}
#define meshtastic_SCD4X_config_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_SEN5X_config_init_zero {false, 0, false, 0}
/* Field tags (for use in manual encoding/decoding) */
#define meshtastic_AdminMessage_InputEvent_event_code_tag 1
@@ -387,6 +440,17 @@ extern "C" {
#define meshtastic_KeyVerificationAdmin_remote_nodenum_tag 2
#define meshtastic_KeyVerificationAdmin_nonce_tag 3
#define meshtastic_KeyVerificationAdmin_security_number_tag 4
#define meshtastic_SCD4X_config_set_asc_tag 1
#define meshtastic_SCD4X_config_set_target_co2_conc_tag 2
#define meshtastic_SCD4X_config_set_temperature_tag 3
#define meshtastic_SCD4X_config_set_altitude_tag 4
#define meshtastic_SCD4X_config_set_ambient_pressure_tag 5
#define meshtastic_SCD4X_config_factory_reset_tag 6
#define meshtastic_SCD4X_config_set_power_mode_tag 7
#define meshtastic_SEN5X_config_set_temperature_tag 1
#define meshtastic_SEN5X_config_set_one_shot_mode_tag 2
#define meshtastic_SensorConfig_scd4x_config_tag 1
#define meshtastic_SensorConfig_sen5x_config_tag 2
#define meshtastic_AdminMessage_get_channel_request_tag 1
#define meshtastic_AdminMessage_get_channel_response_tag 2
#define meshtastic_AdminMessage_get_owner_request_tag 3
@@ -443,6 +507,7 @@ extern "C" {
#define meshtastic_AdminMessage_factory_reset_config_tag 99
#define meshtastic_AdminMessage_nodedb_reset_tag 100
#define meshtastic_AdminMessage_ota_request_tag 102
#define meshtastic_AdminMessage_sensor_config_tag 103
#define meshtastic_AdminMessage_session_passkey_tag 101
/* Struct field encoding specification for nanopb */
@@ -503,7 +568,8 @@ X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_se
X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \
X(a, STATIC, ONEOF, BOOL, (payload_variant,nodedb_reset,nodedb_reset), 100) \
X(a, STATIC, SINGULAR, BYTES, session_passkey, 101) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 102)
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 102) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,sensor_config,sensor_config), 103)
#define meshtastic_AdminMessage_CALLBACK NULL
#define meshtastic_AdminMessage_DEFAULT NULL
#define meshtastic_AdminMessage_payload_variant_get_channel_response_MSGTYPE meshtastic_Channel
@@ -525,6 +591,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 10
#define meshtastic_AdminMessage_payload_variant_add_contact_MSGTYPE meshtastic_SharedContact
#define meshtastic_AdminMessage_payload_variant_key_verification_MSGTYPE meshtastic_KeyVerificationAdmin
#define meshtastic_AdminMessage_payload_variant_ota_request_MSGTYPE meshtastic_AdminMessage_OTAEvent
#define meshtastic_AdminMessage_payload_variant_sensor_config_MSGTYPE meshtastic_SensorConfig
#define meshtastic_AdminMessage_InputEvent_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, event_code, 1) \
@@ -571,6 +638,31 @@ X(a, STATIC, OPTIONAL, UINT32, security_number, 4)
#define meshtastic_KeyVerificationAdmin_CALLBACK NULL
#define meshtastic_KeyVerificationAdmin_DEFAULT NULL
#define meshtastic_SensorConfig_FIELDLIST(X, a) \
X(a, STATIC, OPTIONAL, MESSAGE, scd4x_config, 1) \
X(a, STATIC, OPTIONAL, MESSAGE, sen5x_config, 2)
#define meshtastic_SensorConfig_CALLBACK NULL
#define meshtastic_SensorConfig_DEFAULT NULL
#define meshtastic_SensorConfig_scd4x_config_MSGTYPE meshtastic_SCD4X_config
#define meshtastic_SensorConfig_sen5x_config_MSGTYPE meshtastic_SEN5X_config
#define meshtastic_SCD4X_config_FIELDLIST(X, a) \
X(a, STATIC, OPTIONAL, BOOL, set_asc, 1) \
X(a, STATIC, OPTIONAL, UINT32, set_target_co2_conc, 2) \
X(a, STATIC, OPTIONAL, FLOAT, set_temperature, 3) \
X(a, STATIC, OPTIONAL, UINT32, set_altitude, 4) \
X(a, STATIC, OPTIONAL, UINT32, set_ambient_pressure, 5) \
X(a, STATIC, OPTIONAL, BOOL, factory_reset, 6) \
X(a, STATIC, OPTIONAL, BOOL, set_power_mode, 7)
#define meshtastic_SCD4X_config_CALLBACK NULL
#define meshtastic_SCD4X_config_DEFAULT NULL
#define meshtastic_SEN5X_config_FIELDLIST(X, a) \
X(a, STATIC, OPTIONAL, FLOAT, set_temperature, 1) \
X(a, STATIC, OPTIONAL, BOOL, set_one_shot_mode, 2)
#define meshtastic_SEN5X_config_CALLBACK NULL
#define meshtastic_SEN5X_config_DEFAULT NULL
extern const pb_msgdesc_t meshtastic_AdminMessage_msg;
extern const pb_msgdesc_t meshtastic_AdminMessage_InputEvent_msg;
extern const pb_msgdesc_t meshtastic_AdminMessage_OTAEvent_msg;
@@ -578,6 +670,9 @@ extern const pb_msgdesc_t meshtastic_HamParameters_msg;
extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePinsResponse_msg;
extern const pb_msgdesc_t meshtastic_SharedContact_msg;
extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg;
extern const pb_msgdesc_t meshtastic_SensorConfig_msg;
extern const pb_msgdesc_t meshtastic_SCD4X_config_msg;
extern const pb_msgdesc_t meshtastic_SEN5X_config_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define meshtastic_AdminMessage_fields &meshtastic_AdminMessage_msg
@@ -587,6 +682,9 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg;
#define meshtastic_NodeRemoteHardwarePinsResponse_fields &meshtastic_NodeRemoteHardwarePinsResponse_msg
#define meshtastic_SharedContact_fields &meshtastic_SharedContact_msg
#define meshtastic_KeyVerificationAdmin_fields &meshtastic_KeyVerificationAdmin_msg
#define meshtastic_SensorConfig_fields &meshtastic_SensorConfig_msg
#define meshtastic_SCD4X_config_fields &meshtastic_SCD4X_config_msg
#define meshtastic_SEN5X_config_fields &meshtastic_SEN5X_config_msg
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_ADMIN_PB_H_MAX_SIZE meshtastic_AdminMessage_size
@@ -596,6 +694,9 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg;
#define meshtastic_HamParameters_size 31
#define meshtastic_KeyVerificationAdmin_size 25
#define meshtastic_NodeRemoteHardwarePinsResponse_size 496
#define meshtastic_SCD4X_config_size 29
#define meshtastic_SEN5X_config_size 7
#define meshtastic_SensorConfig_size 40
#define meshtastic_SharedContact_size 127
#ifdef __cplusplus

View File

@@ -33,6 +33,9 @@ PB_BIND(meshtastic_Telemetry, meshtastic_Telemetry, 2)
PB_BIND(meshtastic_Nau7802Config, meshtastic_Nau7802Config, AUTO)
PB_BIND(meshtastic_SEN5XState, meshtastic_SEN5XState, AUTO)

View File

@@ -435,6 +435,25 @@ typedef struct _meshtastic_Nau7802Config {
float calibrationFactor;
} meshtastic_Nau7802Config;
/* SEN5X State, for saving to flash */
typedef struct _meshtastic_SEN5XState {
/* Last cleaning time for SEN5X */
uint32_t last_cleaning_time;
/* Last cleaning time for SEN5X - valid flag */
bool last_cleaning_valid;
/* Config flag for one-shot mode (see admin.proto) */
bool one_shot_mode;
/* Last VOC state time for SEN55 */
bool has_voc_state_time;
uint32_t voc_state_time;
/* Last VOC state validity flag for SEN55 */
bool has_voc_state_valid;
bool voc_state_valid;
/* VOC state array (8x uint8t) for SEN55 */
bool has_voc_state_array;
uint64_t voc_state_array;
} meshtastic_SEN5XState;
#ifdef __cplusplus
extern "C" {
@@ -455,6 +474,7 @@ extern "C" {
/* Initializer values for message structs */
#define meshtastic_DeviceMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
@@ -465,6 +485,7 @@ extern "C" {
#define meshtastic_HostMetrics_init_default {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
#define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}}
#define meshtastic_Nau7802Config_init_default {0, 0}
#define meshtastic_SEN5XState_init_default {0, 0, 0, false, 0, false, 0, false, 0}
#define meshtastic_DeviceMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
@@ -474,6 +495,7 @@ extern "C" {
#define meshtastic_HostMetrics_init_zero {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
#define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}}
#define meshtastic_Nau7802Config_init_zero {0, 0}
#define meshtastic_SEN5XState_init_zero {0, 0, 0, false, 0, false, 0, false, 0}
/* Field tags (for use in manual encoding/decoding) */
#define meshtastic_DeviceMetrics_battery_level_tag 1
@@ -581,6 +603,12 @@ extern "C" {
#define meshtastic_Telemetry_host_metrics_tag 8
#define meshtastic_Nau7802Config_zeroOffset_tag 1
#define meshtastic_Nau7802Config_calibrationFactor_tag 2
#define meshtastic_SEN5XState_last_cleaning_time_tag 1
#define meshtastic_SEN5XState_last_cleaning_valid_tag 2
#define meshtastic_SEN5XState_one_shot_mode_tag 3
#define meshtastic_SEN5XState_voc_state_time_tag 4
#define meshtastic_SEN5XState_voc_state_valid_tag 5
#define meshtastic_SEN5XState_voc_state_array_tag 6
/* Struct field encoding specification for nanopb */
#define meshtastic_DeviceMetrics_FIELDLIST(X, a) \
@@ -731,6 +759,16 @@ X(a, STATIC, SINGULAR, FLOAT, calibrationFactor, 2)
#define meshtastic_Nau7802Config_CALLBACK NULL
#define meshtastic_Nau7802Config_DEFAULT NULL
#define meshtastic_SEN5XState_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, last_cleaning_time, 1) \
X(a, STATIC, SINGULAR, BOOL, last_cleaning_valid, 2) \
X(a, STATIC, SINGULAR, BOOL, one_shot_mode, 3) \
X(a, STATIC, OPTIONAL, UINT32, voc_state_time, 4) \
X(a, STATIC, OPTIONAL, BOOL, voc_state_valid, 5) \
X(a, STATIC, OPTIONAL, FIXED64, voc_state_array, 6)
#define meshtastic_SEN5XState_CALLBACK NULL
#define meshtastic_SEN5XState_DEFAULT NULL
extern const pb_msgdesc_t meshtastic_DeviceMetrics_msg;
extern const pb_msgdesc_t meshtastic_EnvironmentMetrics_msg;
extern const pb_msgdesc_t meshtastic_PowerMetrics_msg;
@@ -740,6 +778,7 @@ extern const pb_msgdesc_t meshtastic_HealthMetrics_msg;
extern const pb_msgdesc_t meshtastic_HostMetrics_msg;
extern const pb_msgdesc_t meshtastic_Telemetry_msg;
extern const pb_msgdesc_t meshtastic_Nau7802Config_msg;
extern const pb_msgdesc_t meshtastic_SEN5XState_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define meshtastic_DeviceMetrics_fields &meshtastic_DeviceMetrics_msg
@@ -751,6 +790,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg;
#define meshtastic_HostMetrics_fields &meshtastic_HostMetrics_msg
#define meshtastic_Telemetry_fields &meshtastic_Telemetry_msg
#define meshtastic_Nau7802Config_fields &meshtastic_Nau7802Config_msg
#define meshtastic_SEN5XState_fields &meshtastic_SEN5XState_msg
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size
@@ -762,6 +802,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg;
#define meshtastic_LocalStats_size 87
#define meshtastic_Nau7802Config_size 16
#define meshtastic_PowerMetrics_size 81
#define meshtastic_SEN5XState_size 27
#define meshtastic_Telemetry_size 272
#ifdef __cplusplus

View File

@@ -442,6 +442,7 @@ ExternalNotificationModule::ExternalNotificationModule()
ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshPacket &mp)
{
// Trigger external notification if enabled and not muted; isSilenced is from temporary mute toggles
if (moduleConfig.external_notification.enabled && !isSilenced) {
#ifdef T_WATCH_S3
drv.setWaveform(0, 75);
@@ -456,6 +457,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
for (size_t i = 0; i < p.payload.size; i++) {
if (p.payload.bytes[i] == ASCII_BELL) {
containsBell = true;
break;
}
}
@@ -465,90 +467,47 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
// If we receive a broadcast message, apply channel mute setting
// If we receive a direct message and the receipent is us, apply DM mute setting
// Else we just handle it as not muted.
const bool directToUs = !isBroadcast(mp.to) && isToUs(&mp);
bool is_muted = directToUs ? (sender && ((sender->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0))
: (ch.settings.has_module_settings && ch.settings.module_settings.is_muted);
const bool isDmToUs = !isBroadcast(mp.to) && isToUs(&mp);
bool is_muted = isDmToUs ? (sender && ((sender->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0))
: (ch.settings.has_module_settings && ch.settings.module_settings.is_muted);
if (moduleConfig.external_notification.alert_bell) {
if (containsBell) {
LOG_INFO("externalNotificationModule - Notification Bell");
const bool buzzerModeIsDirectOnly =
(config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY);
if (containsBell || !is_muted) {
if (moduleConfig.external_notification.alert_bell || moduleConfig.external_notification.alert_message ||
moduleConfig.external_notification.alert_bell_vibra ||
moduleConfig.external_notification.alert_message_vibra ||
((moduleConfig.external_notification.alert_bell_buzzer ||
moduleConfig.external_notification.alert_message_buzzer) &&
canBuzz())) {
nagCycleCutoff = millis() + (moduleConfig.external_notification.nag_timeout
? (moduleConfig.external_notification.nag_timeout * 1000)
: moduleConfig.external_notification.output_ms);
LOG_INFO("Toggling nagCycleCutoff to %lu", nagCycleCutoff);
isNagging = true;
}
if (moduleConfig.external_notification.alert_bell || moduleConfig.external_notification.alert_message) {
LOG_INFO("externalNotificationModule - Notification Module or Bell");
setExternalState(0, true);
if (moduleConfig.external_notification.nag_timeout) {
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
} else {
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
}
}
}
if (moduleConfig.external_notification.alert_bell_vibra) {
if (containsBell) {
LOG_INFO("externalNotificationModule - Notification Bell (Vibra)");
isNagging = true;
if (moduleConfig.external_notification.alert_bell_vibra ||
moduleConfig.external_notification.alert_message_vibra) {
LOG_INFO("externalNotificationModule - Notification Module or Bell (Vibra)");
setExternalState(1, true);
if (moduleConfig.external_notification.nag_timeout) {
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
}
if ((moduleConfig.external_notification.alert_bell_buzzer ||
moduleConfig.external_notification.alert_message_buzzer) &&
canBuzz()) {
LOG_INFO("externalNotificationModule - Notification Module or Bell (Buzzer)");
if (buzzerModeIsDirectOnly && !isDmToUs && !containsBell) {
LOG_INFO("Message buzzer was suppressed because buzzer mode DIRECT_MSG_ONLY");
} else {
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
}
}
}
if (moduleConfig.external_notification.alert_bell_buzzer && canBuzz()) {
if (containsBell) {
LOG_INFO("externalNotificationModule - Notification Bell (Buzzer)");
isNagging = true;
if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {
setExternalState(2, true);
} else {
#ifdef HAS_I2S
if (moduleConfig.external_notification.use_i2s_as_buzzer) {
audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
} else
#endif
if (moduleConfig.external_notification.use_pwm) {
rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
}
}
if (moduleConfig.external_notification.nag_timeout) {
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
} else {
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
}
}
}
if (moduleConfig.external_notification.alert_message && !is_muted) {
LOG_INFO("externalNotificationModule - Notification Module");
isNagging = true;
setExternalState(0, true);
if (moduleConfig.external_notification.nag_timeout) {
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
} else {
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
}
}
if (moduleConfig.external_notification.alert_message_vibra && !is_muted) {
LOG_INFO("externalNotificationModule - Notification Module (Vibra)");
isNagging = true;
setExternalState(1, true);
if (moduleConfig.external_notification.nag_timeout) {
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
} else {
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
}
}
if (moduleConfig.external_notification.alert_message_buzzer && !is_muted) {
LOG_INFO("externalNotificationModule - Notification Module (Buzzer)");
if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
(!isBroadcast(mp.to) && isToUs(&mp))) {
// Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us
isNagging = true;
// Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us
#ifdef T_LORA_PAGER
if (canBuzz()) {
drv.setWaveform(0, 16); // Long buzzer 100%
drv.setWaveform(1, 0); // Pause
drv.setWaveform(2, 16);
@@ -558,11 +517,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
drv.setWaveform(6, 16);
drv.setWaveform(7, 0);
drv.go();
}
#endif
if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {
setExternalState(2, true);
} else {
#ifdef HAS_I2S
if (moduleConfig.external_notification.use_i2s_as_buzzer) {
audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
@@ -570,18 +525,13 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
#endif
if (moduleConfig.external_notification.use_pwm) {
rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
} else {
setExternalState(2, true);
}
}
if (moduleConfig.external_notification.nag_timeout) {
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
} else {
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
}
} else {
// Don't beep if buzzer mode is "direct messages only" and it is no direct message
LOG_INFO("Message buzzer was suppressed because buzzer mode DIRECT_MSG_ONLY");
}
}
setIntervalFromNow(0); // run once so we know if we should do something
}
} else {

View File

@@ -1,24 +1,7 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
#include "buzz/BuzzerFeedbackThread.h"
#include "input/ExpressLRSFiveWay.h"
#include "input/InputBroker.h"
#include "input/RotaryEncoderImpl.h"
#include "input/RotaryEncoderInterruptImpl1.h"
#include "input/SerialKeyboardImpl.h"
#include "input/UpDownInterruptImpl1.h"
#include "input/i2cButton.h"
#include "modules/SystemCommandsModule.h"
#if HAS_TRACKBALL
#include "input/TrackballInterruptImpl1.h"
#endif
#include "modules/StatusLEDModule.h"
#if !MESHTASTIC_EXCLUDE_I2C
#include "input/cardKbI2cImpl.h"
#endif
#include "input/kbMatrixImpl.h"
#endif
#if !MESHTASTIC_EXCLUDE_PKI
#include "KeyVerificationModule.h"
@@ -59,8 +42,6 @@
#include "modules/WaypointModule.h"
#endif
#if ARCH_PORTDUINO
#include "input/LinuxInputImpl.h"
#include "input/SeesawRotary.h"
#include "modules/Telemetry/HostMetrics.h"
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
#include "modules/StoreForwardModule.h"
@@ -179,63 +160,6 @@ void setupModules()
#endif
// Example: Put your module here
// new ReplyModule();
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
#if defined(T_LORA_PAGER)
// use a special FSM based rotary encoder version for T-LoRa Pager
rotaryEncoderImpl = new RotaryEncoderImpl();
if (!rotaryEncoderImpl->init()) {
delete rotaryEncoderImpl;
rotaryEncoderImpl = nullptr;
}
#elif defined(INPUTDRIVER_ENCODER_TYPE) && (INPUTDRIVER_ENCODER_TYPE == 2)
upDownInterruptImpl1 = new UpDownInterruptImpl1();
if (!upDownInterruptImpl1->init()) {
delete upDownInterruptImpl1;
upDownInterruptImpl1 = nullptr;
}
#else
rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
if (!rotaryEncoderInterruptImpl1->init()) {
delete rotaryEncoderInterruptImpl1;
rotaryEncoderInterruptImpl1 = nullptr;
}
#endif
cardKbI2cImpl = new CardKbI2cImpl();
cardKbI2cImpl->init();
#if defined(M5STACK_UNITC6L)
i2cButton = new i2cButtonThread("i2cButtonThread");
#endif
#ifdef INPUTBROKER_MATRIX_TYPE
kbMatrixImpl = new KbMatrixImpl();
kbMatrixImpl->init();
#endif // INPUTBROKER_MATRIX_TYPE
#ifdef INPUTBROKER_SERIAL_TYPE
aSerialKeyboardImpl = new SerialKeyboardImpl();
aSerialKeyboardImpl->init();
#endif // INPUTBROKER_MATRIX_TYPE
}
#endif // HAS_BUTTON
#if ARCH_PORTDUINO
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR && portduino_config.i2cdev != "") {
seesawRotary = new SeesawRotary("SeesawRotary");
if (!seesawRotary->init()) {
delete seesawRotary;
seesawRotary = nullptr;
}
aLinuxInputImpl = new LinuxInputImpl();
aLinuxInputImpl->init();
}
#endif
#if !MESHTASTIC_EXCLUDE_INPUTBROKER && HAS_TRACKBALL
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
trackballInterruptImpl1 = new TrackballInterruptImpl1();
trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS);
}
#endif
#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE
expressLRSFiveWayInput = new ExpressLRSFiveWay();
#endif
#if HAS_SCREEN && !MESHTASTIC_EXCLUDE_CANNEDMESSAGES
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
cannedMessageModule = new CannedMessageModule();

View File

@@ -63,29 +63,26 @@
SerialModule *serialModule;
SerialModuleRadio *serialModuleRadio;
#if defined(TTGO_T_ECHO) || defined(TTGO_T_ECHO_PLUS) || defined(CANARYONE) || defined(MESHLINK) || \
defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M4) || defined(ELECROW_ThinkNode_M5) || \
defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) || defined(ELECROW_ThinkNode_M3) || defined(MUZI_BASE)
SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial")
{
api_type = TYPE_SERIAL;
}
static Print *serialPrint = &Serial;
#elif defined(CONFIG_IDF_TARGET_ESP32C6) || defined(RAK3172) || defined(EBYTE_E77_MBL)
SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial")
{
api_type = TYPE_SERIAL;
}
static Print *serialPrint = &Serial1;
#else
SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("Serial")
{
api_type = TYPE_SERIAL;
}
static Print *serialPrint = &Serial2;
#ifndef SERIAL_PRINT_PORT
#define SERIAL_PRINT_PORT 2
#endif
#if SERIAL_PRINT_PORT == 0
#define SERIAL_PRINT_OBJECT Serial
#elif SERIAL_PRINT_PORT == 1
#define SERIAL_PRINT_OBJECT Serial1
#elif SERIAL_PRINT_PORT == 2
#define SERIAL_PRINT_OBJECT Serial2
#else
#error "Unsupported SERIAL_PRINT_PORT value. Allowed values are 0, 1, or 2."
#endif
SerialModule::SerialModule() : StreamAPI(&SERIAL_PRINT_OBJECT), concurrency::OSThread("Serial")
{
api_type = TYPE_SERIAL;
}
static Print *serialPrint = &SERIAL_PRINT_OBJECT;
char serialBytes[512];
size_t serialPayloadSize;
@@ -205,9 +202,7 @@ int32_t SerialModule::runOnce()
Serial.begin(baud);
Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT);
}
#elif !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && \
!defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M4) && \
!defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE)
#elif SERIAL_PRINT_PORT != 0
if (moduleConfig.serial.rxd && moduleConfig.serial.txd) {
#ifdef ARCH_RP2040
@@ -264,9 +259,7 @@ int32_t SerialModule::runOnce()
}
}
#if !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \
!defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M4) && \
!defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE)
#if SERIAL_PRINT_PORT != 0
else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) {
processWXSerial();
@@ -540,11 +533,7 @@ ParsedLine parseLine(const char *line)
*/
void SerialModule::processWXSerial()
{
#if !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && \
!defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && \
!defined(ELECROW_ThinkNode_M3) && \
!defined(ELECROW_ThinkNode_M4) && \
!defined(ELECROW_ThinkNode_M5) && !defined(ARCH_STM32WL) && !defined(MUZI_BASE)
#if SERIAL_PRINT_PORT != 0 && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6)
static unsigned int lastAveraged = 0;
static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded.

View File

@@ -464,22 +464,6 @@ void cpuDeepSleep(uint32_t msecToWake)
// FIXME, use non-init RAM per
// https://devzone.nordicsemi.com/f/nordic-q-a/48919/ram-retention-settings-with-softdevice-enabled
#ifdef ELECROW_ThinkNode_M1
nrf_gpio_cfg_input(PIN_BUTTON1, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input
nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW;
nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense);
nrf_gpio_cfg_input(PIN_BUTTON2, NRF_GPIO_PIN_PULLUP);
nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW;
nrf_gpio_cfg_sense_set(PIN_BUTTON2, sense1);
#endif
#ifdef PROMICRO_DIY_TCXO
nrf_gpio_cfg_input(BUTTON_PIN, NRF_GPIO_PIN_PULLUP); // Enable internal pull-up on the button pin
nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW; // Configure SENSE signal on low edge
nrf_gpio_cfg_sense_set(BUTTON_PIN, sense); // Apply SENSE to wake up the device from the deep sleep
#endif
#ifdef BATTERY_LPCOMP_INPUT
// Wake up if power rises again
nrf_lpcomp_config_t c;

View File

@@ -19,6 +19,7 @@
#include <fstream>
#include <iostream>
#include <map>
#include <stdexcept>
#include <sys/ioctl.h>
#include <unistd.h>
@@ -29,6 +30,7 @@
#include "platform/portduino/USBHal.h"
portduino_config_struct portduino_config;
portduino_status_struct portduino_status;
std::ofstream traceFile;
std::ofstream JSONFile;
Ch341Hal *ch341Hal = nullptr;
@@ -400,6 +402,11 @@ void portduinoSetup()
if (found_hat) {
product_config =
cleanupNameForAutoconf("lora-hat-" + std::string(hat_vendor) + "-" + autoconf_product + ".yaml");
if (strncmp(hat_vendor, "RAK", strlen("RAK")) == 0 &&
strncmp(autoconf_product, "6421 Pi Hat", strlen("6421 Pi Hat")) == 0) {
std::cout << "autoconf: Setting hardwareModel to RAK6421" << std::endl;
portduino_status.hardwareModel = meshtastic_HardwareModel_RAK6421;
}
} else if (found_ch341) {
product_config = cleanupNameForAutoconf("lora-usb-" + std::string(autoconf_product) + ".yaml");
// look for more data after the null terminator
@@ -408,6 +415,10 @@ void portduinoSetup()
memcpy(portduino_config.device_id, autoconf_product + len + 1, 16);
if (!memfll(portduino_config.device_id, '\0', 16) && !memfll(portduino_config.device_id, 0xff, 16)) {
portduino_config.has_device_id = true;
if (strncmp(autoconf_product, "MESHSTICK 1262", strlen("MESHSTICK 1262")) == 0) {
std::cout << "autoconf: Setting hardwareModel to Meshstick 1262" << std::endl;
portduino_status.hardwareModel = meshtastic_HardwareModel_MESHSTICK_1262;
}
}
}
}

View File

@@ -1,15 +1,22 @@
#pragma once
#include <fstream>
#include <map>
#include <unistd.h>
#include <unordered_map>
#include <vector>
#include "LR11x0Interface.h"
#include "Module.h"
#include "mesh/generated/meshtastic/mesh.pb.h"
#include "platform/portduino/USBHal.h"
#include "yaml-cpp/yaml.h"
extern struct portduino_status_struct {
bool LoRa_in_error = false;
_meshtastic_HardwareModel hardwareModel = meshtastic_HardwareModel_PORTDUINO;
} portduino_status;
#include "platform/portduino/USBHal.h"
// Product strings for auto-configuration
// {"PRODUCT_STRING", "CONFIG.YAML"}
// YAML paths are relative to `meshtastic/available.d`

View File

@@ -9,6 +9,8 @@
#include <libpinedio-usb.h>
#include <unistd.h>
extern uint32_t rebootAtMsec;
// include the library for Raspberry GPIO pins
#define PI_RISING (PINEDIO_INT_MODE_RISING)
@@ -45,7 +47,7 @@ class Ch341Hal : public RadioLibHal
int32_t ret = pinedio_init(&pinedio, NULL);
if (ret != 0) {
std::string s = "Could not open SPI: ";
throw(s + std::to_string(ret));
throw std::runtime_error(s + std::to_string(ret));
}
pinedio_set_option(&pinedio, PINEDIO_OPTION_AUTO_CS, 0);
@@ -74,30 +76,55 @@ class Ch341Hal : public RadioLibHal
// RADIOLIB_NC as an alias for non-connected pins
void pinMode(uint32_t pin, uint32_t mode) override
{
if (checkError()) {
return;
}
if (pin == RADIOLIB_NC) {
return;
}
pinedio_set_pin_mode(&pinedio, pin, mode);
auto res = pinedio_set_pin_mode(&pinedio, pin, mode);
if (res < 0 && rebootAtMsec == 0) {
LOG_ERROR("USBHal pinMode: Could not set pin %u mode to %u: %d", pin, mode, res);
}
}
void digitalWrite(uint32_t pin, uint32_t value) override
{
if (checkError()) {
return;
}
if (pin == RADIOLIB_NC) {
return;
}
pinedio_digital_write(&pinedio, pin, value);
auto res = pinedio_digital_write(&pinedio, pin, value);
if (res < 0 && rebootAtMsec == 0) {
LOG_ERROR("USBHal digitalWrite: Could not write pin %u: %d", pin, res);
portduino_status.LoRa_in_error = true;
}
}
uint32_t digitalRead(uint32_t pin) override
{
if (checkError()) {
return 0;
}
if (pin == RADIOLIB_NC) {
return 0;
}
return pinedio_digital_read(&pinedio, pin);
auto res = pinedio_digital_read(&pinedio, pin);
if (res < 0 && rebootAtMsec == 0) {
LOG_ERROR("USBHal digitalRead: Could not read pin %u: %d", pin, res);
portduino_status.LoRa_in_error = true;
return 0;
}
return res;
}
void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override
{
if (checkError()) {
return;
}
if (interruptNum == RADIOLIB_NC) {
return;
}
@@ -107,6 +134,9 @@ class Ch341Hal : public RadioLibHal
void detachInterrupt(uint32_t interruptNum) override
{
if (checkError()) {
return;
}
if (interruptNum == RADIOLIB_NC) {
return;
}
@@ -152,6 +182,9 @@ class Ch341Hal : public RadioLibHal
void spiTransfer(uint8_t *out, size_t len, uint8_t *in)
{
if (checkError()) {
return;
}
int32_t ret = pinedio_transceive(&this->pinedio, out, in, len);
if (ret < 0) {
std::cerr << "Could not perform SPI transfer: " << ret << std::endl;
@@ -160,9 +193,22 @@ class Ch341Hal : public RadioLibHal
void spiEndTransaction() {}
void spiEnd() {}
bool checkError()
{
if (pinedio.in_error) {
if (!has_warned)
LOG_ERROR("USBHal: libch341 in_error detected");
portduino_status.LoRa_in_error = true;
has_warned = true;
return true;
}
has_warned = false;
return false;
}
private:
pinedio_inst pinedio = {0};
bool has_warned = false;
};
#endif

View File

@@ -6,7 +6,7 @@
// set HW_VENDOR
//
#define HW_VENDOR meshtastic_HardwareModel_PORTDUINO
#define HW_VENDOR portduino_status.hardwareModel
#ifndef HAS_BUTTON
#define HAS_BUTTON 1

View File

@@ -12,4 +12,4 @@ build_flags =
lib_deps =
${esp32_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.7
lovyan03/LovyanGFX@1.2.19

View File

@@ -36,4 +36,4 @@ lib_ignore =
lib_deps =
${esp32_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.7
lovyan03/LovyanGFX@1.2.19

View File

@@ -11,7 +11,7 @@ build_flags =
lib_deps =
${esp32_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.7
lovyan03/LovyanGFX@1.2.19
# renovate: datasource=custom.pio depName=SX1509 IO Expander packageName=sparkfun/library/SX1509 IO Expander
sparkfun/SX1509 IO Expander@3.0.6
# renovate: datasource=custom.pio depName=APA102 packageName=pololu/library/APA102

View File

@@ -50,3 +50,5 @@ void c6l_init();
#endif
#define SCREEN_TRANSITION_FRAMERATE 10
#define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness
#define SERIAL_PRINT_PORT 1

View File

@@ -19,3 +19,5 @@
#define SX126X_TXEN 14
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
#define SERIAL_PRINT_PORT 1

View File

@@ -81,4 +81,6 @@
#define BUTTON_PIN PIN_BUTTON1
#define BUTTON_PIN_ALT PIN_BUTTON2
#define SERIAL_PRINT_PORT 0
#endif

View File

@@ -126,7 +126,7 @@ build_flags =
lib_deps = ${heltec_v4_base.lib_deps}
; ${device-ui_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.0
lovyan03/LovyanGFX@1.2.19
# renovate: datasource=git-refs depName=Quency-D_chsc6x packageName=https://github.com/Quency-D/chsc6x gitBranch=master
https://github.com/Quency-D/chsc6x/archive/5cbead829d6b432a8d621ed1aafd4eb474fd4f27.zip
; TODO revert to official device-ui (when merged)

View File

@@ -24,4 +24,4 @@ build_flags =
lib_deps =
${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.7
lovyan03/LovyanGFX@1.2.19

View File

@@ -22,4 +22,4 @@ build_flags =
lib_deps =
${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.7
lovyan03/LovyanGFX@1.2.19

View File

@@ -1,8 +1,17 @@
[env:heltec-wireless-tracker-v2]
custom_meshtastic_support_level = 1
custom_meshtastic_images = heltec_wireless_tracker_v2.svg
custom_meshtastic_tags = Heltec
extends = esp32s3_base
board = heltec_wireless_tracker_v2
board_build.partitions = default_8MB.csv
upload_protocol = esptool
custom_meshtastic_hw_model = 113
custom_meshtastic_hw_model_slug = HELTEC_WIRELESS_TRACKER_V2
custom_meshtastic_architecture = esp32s3
custom_meshtastic_display_name = Heltec Wireless Tracker V2
custom_meshtastic_actively_supported = true
build_flags =
${esp32s3_base.build_flags}
@@ -11,4 +20,4 @@ build_flags =
lib_deps =
${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.7
lovyan03/LovyanGFX@1.2.19

View File

@@ -51,7 +51,7 @@ lib_deps =
${esp32s3_base.lib_deps}
${device-ui_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.7
lovyan03/LovyanGFX@1.2.19
[mesh_tab_xpt2046]
extends = mesh_tab_base

View File

@@ -24,7 +24,7 @@ build_flags =
lib_deps =
${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.7
lovyan03/LovyanGFX@1.2.19
build_src_filter =
${esp32s3_base.build_src_filter}

View File

@@ -16,7 +16,7 @@ build_flags =
lib_deps =
${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.7
lovyan03/LovyanGFX@1.2.19
[ft5x06]
extends = mesh_tab_base

View File

@@ -28,7 +28,7 @@ build_flags = ${esp32s3_base.build_flags}
lib_deps = ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.7
lovyan03/LovyanGFX@1.2.19
# renovate: datasource=custom.pio depName=ESP8266Audio packageName=earlephilhower/library/ESP8266Audio
earlephilhower/ESP8266Audio@1.9.9
# renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM

View File

@@ -22,7 +22,7 @@ build_flags = ${esp32s3_base.build_flags}
lib_deps = ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.7
lovyan03/LovyanGFX@1.2.19
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
lewisxhe/SensorLib@0.3.4
# renovate: datasource=custom.pio depName=Adafruit DRV2605 packageName=adafruit/library/Adafruit DRV2605 Library

View File

@@ -33,7 +33,7 @@ build_flags = ${esp32s3_base.build_flags}
lib_deps = ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.7
lovyan03/LovyanGFX@1.2.19
# renovate: datasource=custom.pio depName=ESP8266Audio packageName=earlephilhower/library/ESP8266Audio
earlephilhower/ESP8266Audio@1.9.9
# renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM

View File

@@ -22,7 +22,7 @@ build_flags =
lib_deps =
${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.7
lovyan03/LovyanGFX@1.2.19
[env:tracksenger-lcd]
custom_meshtastic_hw_model = 48
@@ -48,7 +48,7 @@ build_flags =
lib_deps =
${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.7
lovyan03/LovyanGFX@1.2.19
[env:tracksenger-oled]
custom_meshtastic_hw_model = 48

View File

@@ -37,7 +37,7 @@ build_src_filter =
lib_deps = ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.0
lovyan03/LovyanGFX@1.2.19
# TODO renovate
https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic@9.0.0
# renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel

View File

@@ -27,9 +27,9 @@ lib_deps =
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
rweather/Crypto@0.4.0
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.7
lovyan03/LovyanGFX@1.2.19
# renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main
https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip
https://github.com/pine64/libch341-spi-userspace/archive/23c42319a69cffcb65868e3c72e6bed83974a393.zip
# renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library
adafruit/Adafruit seesaw Library@1.7.9
# renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main

View File

@@ -59,4 +59,11 @@ void variant_shutdown()
NRF_GPIO->DIRCLR = (1 << pin);
}
}
nrf_gpio_cfg_input(PIN_BUTTON1, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input
nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW;
nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense);
nrf_gpio_cfg_input(PIN_BUTTON2, NRF_GPIO_PIN_PULLUP);
nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW;
nrf_gpio_cfg_sense_set(PIN_BUTTON2, sense1);
}

View File

@@ -159,6 +159,8 @@ External serial flash WP25R1635FZUIL0
#define PIN_SERIAL1_TX GPS_TX_PIN
#define PIN_SERIAL1_RX GPS_RX_PIN
#define SERIAL_PRINT_PORT 0
/*
* SPI Interfaces
*/

View File

@@ -113,6 +113,8 @@ extern "C" {
#define LR11X0_DIO3_TCXO_VOLTAGE 3.3
#define LR11X0_DIO_AS_RF_SWITCH
#define SERIAL_PRINT_PORT 0
// PCF8563 RTC Module
// REVISIT https://github.com/meshtastic/firmware/pull/9084
// #define PCF8563_RTC 0x51

View File

@@ -49,3 +49,21 @@ void initVariant()
pinMode(Battery_LED_4, OUTPUT);
ledOff(Battery_LED_4);
}
/// called from main-nrf52.cpp during the cpuDeepSleep() function
void variant_shutdown()
{
for (int pin = 0; pin < 48; pin++) {
if (pin == PIN_GPS_EN || pin == PIN_BUTTON1) {
continue;
}
pinMode(pin, OUTPUT);
digitalWrite(pin, LOW);
if (pin >= 32) {
NRF_P1->DIRCLR = (1 << (pin - 32));
} else {
NRF_GPIO->DIRCLR = (1 << pin);
}
}
digitalWrite(PIN_GPS_EN, HIGH);
}

View File

@@ -135,6 +135,8 @@ static const uint8_t A0 = PIN_A0;
#define PIN_SERIAL1_RX GPS_RX_PIN
#define PIN_SERIAL1_TX GPS_TX_PIN
#define SERIAL_PRINT_PORT 0
#ifdef __cplusplus
}
#endif

View File

@@ -170,6 +170,8 @@ static const uint8_t A0 = PIN_A0;
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
#define ADC_MULTIPLIER (2.0F)
#define SERIAL_PRINT_PORT 0
#ifdef __cplusplus
}
#endif

View File

@@ -36,3 +36,10 @@ void initVariant()
pinMode(PIN_3V3_EN, OUTPUT);
digitalWrite(PIN_3V3_EN, HIGH);
}
void variant_shutdown()
{
nrf_gpio_cfg_input(BUTTON_PIN, NRF_GPIO_PIN_PULLUP); // Enable internal pull-up on the button pin
nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW; // Configure SENSE signal on low edge
nrf_gpio_cfg_sense_set(BUTTON_PIN, sense); // Apply SENSE to wake up the device from the deep sleep
}

View File

@@ -142,6 +142,8 @@ No longer populated on PCB
#define BQ4050_SCL_PIN (32 + 0) // I2C clock line pin
#define BQ4050_EMERGENCY_SHUTDOWN_PIN (32 + 3) // Emergency shutdown pin
#define SERIAL_PRINT_PORT 0
#ifdef __cplusplus
}
#endif

View File

@@ -54,6 +54,7 @@ extern "C" {
*/
#define PIN_SERIAL1_RX (32 + 8)
#define PIN_SERIAL1_TX (7)
#define SERIAL_PRINT_PORT 0
/*
* SPI Interfaces

View File

@@ -176,6 +176,8 @@ extern "C" {
#define EXTERNAL_FLASH_DEVICES W25Q32JVSS
#define EXTERNAL_FLASH_USE_QSPI
#define SERIAL_PRINT_PORT 0
// NFC is disabled via CONFIG_NFCT_PINS_AS_GPIOS=1 build flag
// This configures P0.09 and P0.10 as regular GPIO pins instead of NFC pins

View File

@@ -171,6 +171,8 @@ static const uint8_t A0 = PIN_A0;
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
#define ADC_MULTIPLIER (2.0F)
#define SERIAL_PRINT_PORT 0
// #define NO_EXT_GPIO 1
// PINs back side
// Batt & solar connector left up corner

View File

@@ -138,6 +138,8 @@ static const uint8_t A0 = PIN_A0;
// Battery / ADC already defined above
#define HAS_RTC 1
#define SERIAL_PRINT_PORT 0
#ifdef __cplusplus
}
#endif

View File

@@ -88,6 +88,7 @@ static const uint8_t A0 = PIN_A0;
/*
* Serial interfaces
*/
#define SERIAL_PRINT_PORT 0
/*
No longer populated on PCB

View File

@@ -19,5 +19,7 @@ Do not expect a working Meshtastic device with this target.
// #define LED_PIN PB3 // LED2
#define LED_STATE_ON 1
#define SERIAL_PRINT_PORT 1
#define EBYTE_E77_MBL
#endif

View File

@@ -17,5 +17,6 @@ Do not expect a working Meshtastic device with this target.
#define LED_STATE_ON 1
#define RAK3172
#define SERIAL_PRINT_PORT 1
#endif