diff --git a/.envrc b/.envrc new file mode 100644 index 000000000..65326bb6d --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use nix \ No newline at end of file diff --git a/.github/workflows/models_issue_triage.yml b/.github/workflows/models_issue_triage.yml new file mode 100644 index 000000000..ef12885f8 --- /dev/null +++ b/.github/workflows/models_issue_triage.yml @@ -0,0 +1,213 @@ +name: Issue Triage (Models) + +on: + issues: + types: [opened] + +permissions: + issues: write + models: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number }} + cancel-in-progress: true + +jobs: + triage: + if: ${{ github.repository == 'meshtastic/firmware' && github.event.issue.user.type != 'Bot' }} + runs-on: ubuntu-latest + steps: + # ───────────────────────────────────────────────────────────────────────── + # Step 1: Quality check (spam/AI-slop detection) - runs first, exits early if spam + # ───────────────────────────────────────────────────────────────────────── + - name: Detect spam or low-quality content + uses: actions/ai-inference@v2 + id: quality + continue-on-error: true + with: + max-tokens: 20 + prompt: | + Is this GitHub issue spam, AI-generated slop, or low quality? + + Title: ${{ github.event.issue.title }} + Body: ${{ github.event.issue.body }} + + Respond with exactly one of: spam, ai-generated, needs-review, ok + system-prompt: You detect spam and low-quality contributions. Be conservative - only flag obvious spam or AI slop. + model: openai/gpt-4o-mini + + - name: Apply quality label if needed + if: steps.quality.outputs.response != '' && steps.quality.outputs.response != 'ok' + uses: actions/github-script@v8 + env: + QUALITY_LABEL: ${{ steps.quality.outputs.response }} + with: + script: | + const label = (process.env.QUALITY_LABEL || '').trim().toLowerCase(); + const labelMeta = { + 'spam': { color: 'd73a4a', description: 'Possible spam' }, + 'ai-generated': { color: 'fbca04', description: 'Possible AI-generated low-quality content' }, + 'needs-review': { color: 'f9d0c4', description: 'Needs human review' }, + }; + const meta = labelMeta[label]; + if (!meta) return; + + // Ensure label exists + try { + await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label }); + } catch (e) { + if (e.status !== 404) throw e; + await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description }); + } + + // Apply label + await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.issue.number, labels: [label] }); + + // Set output to skip remaining steps + core.setOutput('is_spam', 'true'); + + # ───────────────────────────────────────────────────────────────────────── + # Step 2: Duplicate detection - only if not spam + # ───────────────────────────────────────────────────────────────────────── + - name: Detect duplicate issues + if: steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '' + uses: pelikhan/action-genai-issue-dedup@bdb3b5d9451c1090ffcdf123d7447a5e7c7a2528 # v0.0.19 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + + # ───────────────────────────────────────────────────────────────────────── + # Step 3: Completeness check + auto-labeling (combined into one AI call) + # ───────────────────────────────────────────────────────────────────────── + - name: Determine if completeness check should be skipped + if: steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '' + uses: actions/github-script@v8 + id: check-skip + with: + script: | + const title = (context.payload.issue.title || '').toLowerCase(); + const labels = (context.payload.issue.labels || []).map(label => label.name); + const hasFeatureRequest = title.includes('feature request'); + const hasEnhancement = labels.includes('enhancement'); + const shouldSkip = hasFeatureRequest && hasEnhancement; + core.setOutput('should_skip', shouldSkip ? 'true' : 'false'); + + - name: Analyze issue completeness and determine labels + if: (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '') && steps.check-skip.outputs.should_skip != 'true' + uses: actions/ai-inference@v2 + id: analysis + continue-on-error: true + with: + prompt: | + Analyze this GitHub issue for completeness and determine if it needs labels. + + IMPORTANT: Distinguish between: + - Device/firmware bugs (crashes, reboots, lockups, radio/GPS/display/power issues) - these need device logs + - Build/release/packaging issues (missing files, CI failures, download problems) - these do NOT need device logs + - Documentation or website issues - these do NOT need device logs + + If this is a device/firmware bug, request device logs and explain how to get them: + + Web Flasher logs: + - Go to https://flasher.meshtastic.org + - Connect the device via USB and click Connect + - Open the device console/log output, reproduce the problem, then copy/download and attach/paste the logs + + Meshtastic CLI logs: + - Run: meshtastic --port --noproto + - Reproduce the problem, then copy/paste the terminal output + + Also request key context if missing: device model/variant, firmware version, region, steps to reproduce, expected vs actual. + + Respond ONLY with valid JSON (no markdown, no code fences): + {"complete": true, "comment": "", "label": "none"} + OR + {"complete": false, "comment": "Your helpful comment", "label": "needs-logs"} + + Use "needs-logs" ONLY if this is a device/firmware bug AND no logs are attached. + Use "needs-info" if basic info like firmware version or steps to reproduce are missing. + Use "none" if the issue is complete, is a feature request, or is a build/CI/packaging issue. + + Title: ${{ github.event.issue.title }} + Body: ${{ github.event.issue.body }} + system-prompt: You are a helpful assistant that triages GitHub issues. Be conservative with labels. Only request device logs for actual device/firmware bugs, not for build/release/CI issues. + model: openai/gpt-4o-mini + + - name: Process analysis result + if: (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '') && steps.check-skip.outputs.should_skip != 'true' && steps.analysis.outputs.response != '' + uses: actions/github-script@v8 + id: process + env: + AI_RESPONSE: ${{ steps.analysis.outputs.response }} + with: + script: | + let raw = (process.env.AI_RESPONSE || '').trim(); + + // Strip markdown code fences if present + raw = raw.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/i, '').trim(); + + let complete = true; + let comment = ''; + let label = 'none'; + + try { + const parsed = JSON.parse(raw); + complete = !!parsed.complete; + comment = (parsed.comment ?? '').toString().trim(); + label = (parsed.label ?? 'none').toString().trim().toLowerCase(); + } catch { + // If JSON parse fails, log warning and don't comment (avoid posting raw JSON) + console.log('Failed to parse AI response as JSON:', raw); + complete = true; + comment = ''; + label = 'none'; + } + + // Validate label + const allowedLabels = new Set(['needs-logs', 'needs-info', 'none']); + if (!allowedLabels.has(label)) label = 'none'; + + // Only comment if we have a valid parsed comment (not raw JSON) + const shouldComment = !complete && comment.length > 0 && !comment.startsWith('{'); + core.setOutput('should_comment', shouldComment ? 'true' : 'false'); + core.setOutput('comment_body', comment); + core.setOutput('label', label); + + - name: Apply triage label + if: steps.process.outputs.label != '' && steps.process.outputs.label != 'none' + uses: actions/github-script@v8 + env: + LABEL_NAME: ${{ steps.process.outputs.label }} + with: + script: | + const label = process.env.LABEL_NAME; + const labelMeta = { + 'needs-logs': { color: 'cfd3d7', description: 'Device logs requested for triage' }, + 'needs-info': { color: 'f9d0c4', description: 'More information requested for triage' }, + }; + const meta = labelMeta[label]; + if (!meta) return; + + // Ensure label exists + try { + await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label }); + } catch (e) { + if (e.status !== 404) throw e; + await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description }); + } + + // Apply label + await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.issue.number, labels: [label] }); + + - name: Comment on issue + if: steps.process.outputs.should_comment == 'true' + uses: actions/github-script@v8 + env: + COMMENT_BODY: ${{ steps.process.outputs.comment_body }} + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: process.env.COMMENT_BODY + }); diff --git a/.github/workflows/models_pr_triage.yml b/.github/workflows/models_pr_triage.yml new file mode 100644 index 000000000..d4c8509d2 --- /dev/null +++ b/.github/workflows/models_pr_triage.yml @@ -0,0 +1,139 @@ +name: PR Triage (Models) + +on: + pull_request_target: + types: [opened] + +permissions: + pull-requests: write + issues: write + models: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + triage: + if: ${{ github.repository == 'meshtastic/firmware' && github.event.pull_request.user.type != 'Bot' }} + runs-on: ubuntu-latest + steps: + # ───────────────────────────────────────────────────────────────────────── + # Step 1: Check if PR already has automation/type labels (skip if so) + # ───────────────────────────────────────────────────────────────────────── + - name: Check existing labels + uses: actions/github-script@v8 + id: check-labels + with: + script: | + const skipLabels = new Set(['automation']); + const typeLabels = new Set(['bugfix', 'hardware-support', 'enhancement', 'dependencies', 'submodules', 'github_actions', 'trunk', 'cleanup']); + const prLabels = context.payload.pull_request.labels.map(l => l.name); + + const shouldSkipAll = prLabels.some(l => skipLabels.has(l)); + const hasTypeLabel = prLabels.some(l => typeLabels.has(l)); + + core.setOutput('skip_all', shouldSkipAll ? 'true' : 'false'); + core.setOutput('has_type_label', hasTypeLabel ? 'true' : 'false'); + + # ───────────────────────────────────────────────────────────────────────── + # Step 2: Quality check (spam/AI-slop detection) + # ───────────────────────────────────────────────────────────────────────── + - name: Detect spam or low-quality content + if: steps.check-labels.outputs.skip_all != 'true' + uses: actions/ai-inference@v2 + id: quality + continue-on-error: true + with: + max-tokens: 20 + prompt: | + Is this GitHub pull request spam, AI-generated slop, or low quality? + + Title: ${{ github.event.pull_request.title }} + Body: ${{ github.event.pull_request.body }} + + Respond with exactly one of: spam, ai-generated, needs-review, ok + system-prompt: You detect spam and low-quality contributions. Be conservative - only flag obvious spam or AI slop. + model: openai/gpt-4o-mini + + - name: Apply quality label if needed + if: steps.check-labels.outputs.skip_all != 'true' && steps.quality.outputs.response != '' && steps.quality.outputs.response != 'ok' + uses: actions/github-script@v8 + id: quality-label + env: + QUALITY_LABEL: ${{ steps.quality.outputs.response }} + with: + script: | + const label = (process.env.QUALITY_LABEL || '').trim().toLowerCase(); + const labelMeta = { + 'spam': { color: 'd73a4a', description: 'Possible spam' }, + 'ai-generated': { color: 'fbca04', description: 'Possible AI-generated low-quality content' }, + 'needs-review': { color: 'f9d0c4', description: 'Needs human review' }, + }; + const meta = labelMeta[label]; + if (!meta) return; + + // Ensure label exists + try { + await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label }); + } catch (e) { + if (e.status !== 404) throw e; + await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description }); + } + + // Apply label + await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, labels: [label] }); + + core.setOutput('is_spam', 'true'); + + # ───────────────────────────────────────────────────────────────────────── + # Step 3: Auto-label PR type (bugfix/hardware-support/enhancement) + # Only skip for spam/ai-generated; still classify needs-review PRs + # ───────────────────────────────────────────────────────────────────────── + - name: Classify PR for labeling + if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && steps.quality.outputs.response != 'spam' && steps.quality.outputs.response != 'ai-generated' + uses: actions/ai-inference@v2 + id: classify + continue-on-error: true + with: + max-tokens: 30 + prompt: | + Classify this pull request into exactly one category. + + Return exactly one of: bugfix, hardware-support, enhancement + + Use bugfix if it fixes a bug, crash, or incorrect behavior. + Use hardware-support if it adds or improves support for a specific hardware device/variant. + Use enhancement if it adds a new feature, improves performance, or refactors code. + + Title: ${{ github.event.pull_request.title }} + Body: ${{ github.event.pull_request.body }} + system-prompt: You classify pull requests into categories. Be conservative and pick the most appropriate single label. + model: openai/gpt-4o-mini + + - name: Apply type label + if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && steps.classify.outputs.response != '' + uses: actions/github-script@v8 + env: + TYPE_LABEL: ${{ steps.classify.outputs.response }} + with: + script: | + const label = (process.env.TYPE_LABEL || '').trim().toLowerCase(); + const labelMeta = { + 'bugfix': { color: 'd73a4a', description: 'Bug fix' }, + 'hardware-support': { color: '0e8a16', description: 'Hardware support addition or improvement' }, + 'enhancement': { color: 'a2eeef', description: 'New feature or enhancement' }, + }; + const meta = labelMeta[label]; + if (!meta) return; + + // Ensure label exists + try { + await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label }); + } catch (e) { + if (e.status !== 404) throw e; + await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description }); + } + + // Apply label + await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, labels: [label] }); diff --git a/.gitignore b/.gitignore index 769603202..d6d97c6c4 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,6 @@ idf_component.yml CMakeLists.txt /sdkconfig.* .dummy/* + +# PYTHONPATH used by the Nix shell +.python3 diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 12e6696c0..9d563a39a 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -9,14 +9,14 @@ plugins: lint: enabled: - checkov@3.2.497 - - renovate@42.81.8 + - renovate@42.84.2 - prettier@3.8.0 - - trufflehog@3.92.4 + - trufflehog@3.92.5 - yamllint@1.38.0 - - bandit@1.9.2 + - bandit@1.9.3 - trivy@0.68.2 - taplo@0.10.0 - - ruff@0.14.11 + - ruff@0.14.13 - isort@7.0.0 - markdownlint@0.47.0 - oxipng@10.0.0 @@ -26,7 +26,7 @@ lint: - hadolint@2.14.0 - shfmt@3.6.0 - shellcheck@0.11.0 - - black@25.12.0 + - black@26.1.0 - git-diff-check - gitleaks@8.30.0 - clang-format@16.0.3 diff --git a/bin/config.d/lora-usb-umesh-1262-30dbm.yaml b/bin/config.d/lora-usb-umesh-1262-30dbm.yaml new file mode 100644 index 000000000..7726eccd1 --- /dev/null +++ b/bin/config.d/lora-usb-umesh-1262-30dbm.yaml @@ -0,0 +1,23 @@ +Lora: + Module: sx1262 + CS: 0 + IRQ: 6 + Reset: 1 + Busy: 4 + RXen: 2 + DIO2_AS_RF_SWITCH: true + spidev: ch341 + USB_PID: 0x5512 + USB_VID: 0x1A86 + DIO3_TCXO_VOLTAGE: true +# USB_Serialnum: 12345678 + SX126X_MAX_POWER: 22 +# Reduce output power to improve EMI + NUM_PA_POINTS: 22 + TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7 +# Note: This module integrates an additional PA to achieve higher output power. +# The 'power' parameter here does not represent the actual RF output. +# TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (1–22 dBm). +# Each array element corresponds to the additional gain when that input level is set, +# The effective RF output is: Pout ≈ Pset + TX_GAIN_LORA[index]. +# Please refer to https://github.com/linser233/uMesh/blob/main/RF_Power.md for detailed information. diff --git a/bin/config.d/lora-usb-umesh-1262.yaml b/bin/config.d/lora-usb-umesh-1262.yaml deleted file mode 100644 index 6008e63b7..000000000 --- a/bin/config.d/lora-usb-umesh-1262.yaml +++ /dev/null @@ -1,15 +0,0 @@ -Lora: - Module: sx1262 - CS: 0 - IRQ: 6 - Reset: 1 - Busy: 4 - RXen: 2 - DIO2_AS_RF_SWITCH: true - spidev: ch341 - USB_PID: 0x5512 - USB_VID: 0x1A86 - DIO3_TCXO_VOLTAGE: true -# USB_Serialnum: 12345678 - SX126X_MAX_POWER: 30 -# Reduce output power to improve EMI diff --git a/bin/config.d/lora-usb-umesh-1268-30dbm.yaml b/bin/config.d/lora-usb-umesh-1268-30dbm.yaml new file mode 100644 index 000000000..c054a92f9 --- /dev/null +++ b/bin/config.d/lora-usb-umesh-1268-30dbm.yaml @@ -0,0 +1,23 @@ +Lora: + Module: sx1268 + CS: 0 + IRQ: 6 + Reset: 1 + Busy: 4 + RXen: 2 + DIO2_AS_RF_SWITCH: true + spidev: ch341 + USB_PID: 0x5512 + USB_VID: 0x1A86 + DIO3_TCXO_VOLTAGE: true +# USB_Serialnum: 12345678 + SX126X_MAX_POWER: 22 +# Reduce output power to improve EMI + NUM_PA_POINTS: 22 + TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7 +# Note: This module integrates an additional PA to achieve higher output power. +# The 'power' parameter here does not represent the actual RF output. +# TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (1–22 dBm). +# Each array element corresponds to the additional gain when that input level is set, +# The effective RF output is: Pout ≈ Pset + TX_GAIN_LORA[index]. +# Please refer to https://github.com/linser233/uMesh/blob/main/RF_Power.md for detailed information. diff --git a/bin/config.d/lora-usb-umesh-1268.yaml b/bin/config.d/lora-usb-umesh-1268.yaml deleted file mode 100644 index 637472966..000000000 --- a/bin/config.d/lora-usb-umesh-1268.yaml +++ /dev/null @@ -1,15 +0,0 @@ -Lora: - Module: sx1268 - CS: 0 - IRQ: 6 - Reset: 1 - Busy: 4 - RXen: 2 - DIO2_AS_RF_SWITCH: true - spidev: ch341 - USB_PID: 0x5512 - USB_VID: 0x1A86 - DIO3_TCXO_VOLTAGE: true -# USB_Serialnum: 12345678 - SX126X_MAX_POWER: 30 -# Reduce output power to improve EMI diff --git a/bin/device-install.sh b/bin/device-install.sh index 1778a952d..49427524e 100755 --- a/bin/device-install.sh +++ b/bin/device-install.sh @@ -32,6 +32,19 @@ if ! command -v jq >/dev/null 2>&1; then exit 1 fi +# esptool v5 supports commands with dashes and deprecates commands with +# underscores. Prior versions only support commands with underscores +if ${ESPTOOL_CMD} | grep --quiet write-flash +then + ESPTOOL_WRITE_FLASH=write-flash + ESPTOOL_ERASE_FLASH=erase-flash + ESPTOOL_READ_FLASH_STATUS=read-flash-status +else + ESPTOOL_WRITE_FLASH=write_flash + ESPTOOL_ERASE_FLASH=erase_flash + ESPTOOL_READ_FLASH_STATUS=read_flash_status +fi + set -e # Usage info @@ -83,8 +96,8 @@ while [ $# -gt 0 ]; do done if [[ $BPS_RESET == true ]]; then - $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status - exit 0 + $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset ${ESPTOOL_READ_FLASH_STATUS} + exit 0 fi [ -z "$FILENAME" ] && [ -n "$1" ] && { @@ -144,12 +157,12 @@ if [[ -f "$FILENAME" && "$FILENAME" == *.factory.bin ]]; then fi echo "Trying to flash ${FILENAME}, but first erasing and writing system information" - $ESPTOOL_CMD erase-flash - $ESPTOOL_CMD write-flash $FIRMWARE_OFFSET "${FILENAME}" + $ESPTOOL_CMD ${ESPTOOL_ERASE_FLASH} + $ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $FIRMWARE_OFFSET "${FILENAME}" echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}" - $ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}" + $ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $OTA_OFFSET "${OTAFILE}" echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}" - $ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}" + $ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $OFFSET "${SPIFFSFILE}" else show_help diff --git a/bin/device-update.sh b/bin/device-update.sh index 1c3d6be70..10eb5eedd 100755 --- a/bin/device-update.sh +++ b/bin/device-update.sh @@ -20,6 +20,17 @@ else exit 1 fi +# esptool v5 supports commands with dashes and deprecates commands with +# underscores. Prior versions only support commands with underscores +if ${ESPTOOL_CMD} | grep --quiet write-flash +then + ESPTOOL_WRITE_FLASH=write-flash + ESPTOOL_READ_FLASH_STATUS=read-flash-status +else + ESPTOOL_WRITE_FLASH=write_flash + ESPTOOL_READ_FLASH_STATUS=read_flash_status +fi + # Usage info show_help() { cat << EOF @@ -69,7 +80,7 @@ done shift "$((OPTIND-1))" if [ "$CHANGE_MODE" = true ]; then - $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status + $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset ${ESPTOOL_READ_FLASH_STATUS} exit 0 fi @@ -80,7 +91,7 @@ fi if [[ -f "$FILENAME" && "$FILENAME" != *.factory.bin ]]; then echo "Trying to flash update ${FILENAME}" - $ESPTOOL_CMD --baud $FLASH_BAUD write-flash $UPDATE_OFFSET "${FILENAME}" + $ESPTOOL_CMD --baud $FLASH_BAUD ${ESPTOOL_WRITE_FLASH} $UPDATE_OFFSET "${FILENAME}" else show_help echo "Invalid file: ${FILENAME}" diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml index cb8985ee6..6ad8962d1 100644 --- a/bin/org.meshtastic.meshtasticd.metainfo.xml +++ b/bin/org.meshtastic.meshtasticd.metainfo.xml @@ -87,6 +87,9 @@ + + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.19 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.18 diff --git a/boards/minimesh_lite.json b/boards/minimesh_lite.json new file mode 100644 index 000000000..0b8f0b909 --- /dev/null +++ b/boards/minimesh_lite.json @@ -0,0 +1,50 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DMINIMESH_LITE -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x8029"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"] + ], + "usb_product": "Minimesh Lite", + "mcu": "nrf52840", + "variant": "dls_Minimesh_Lite", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd" + }, + "frameworks": ["arduino"], + "name": "Minimesh Lite", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": ["nrfutil", "jlink", "nrfjprog", "stlink"], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://deeplabstudio.com", + "vendor": "Deeplab Studio" +} diff --git a/debian/changelog b/debian/changelog index 5f25d53ad..38489b074 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +meshtasticd (2.7.19.0) unstable; urgency=medium + + * Version 2.7.19 + + -- GitHub Actions Thu, 22 Jan 2026 22:17:40 +0000 + meshtasticd (2.7.18.0) unstable; urgency=medium * Version 2.7.18 diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..e7a4c7ff7 --- /dev/null +++ b/flake.lock @@ -0,0 +1,44 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", + "owner": "NixOS", + "repo": "flake-compat", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "flake-compat", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1766314097, + "narHash": "sha256-laJftWbghBehazn/zxVJ8NdENVgjccsWAdAqKXhErrM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "306ea70f9eb0fb4e040f8540e2deab32ed7e2055", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..1af493c6d --- /dev/null +++ b/flake.nix @@ -0,0 +1,66 @@ +{ + description = "Nix flake to compile Meshtastic firmware"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + # Shim to make flake.nix work with stable Nix. + flake-compat = { + url = "github:NixOS/flake-compat"; + flake = false; + }; + }; + + outputs = + inputs: + let + lib = inputs.nixpkgs.lib; + + forAllSystems = + fn: + lib.genAttrs lib.systems.flakeExposed ( + system: + fn { + pkgs = import inputs.nixpkgs { + inherit system; + }; + inherit system; + } + ); + in + { + devShells = forAllSystems ( + { pkgs, ... }: + let + python3 = pkgs.python312.withPackages ( + ps: with ps; [ + google + ] + ); + in + { + default = pkgs.mkShell { + buildInputs = with pkgs; [ + python3 + platformio + ]; + + shellHook = '' + # Set up PlatformIO to use a local core directory. + export PLATFORMIO_CORE_DIR=$PWD/.platformio + # Tell pip to put packages into $PIP_PREFIX instead of the usual + # location. This is especially necessary under NixOS to avoid having + # pip trying to write to the read-only Nix store. For more info, + # see https://wiki.nixos.org/wiki/Python + export PIP_PREFIX=$PWD/.python3 + export PYTHONPATH="$PIP_PREFIX/${python3.sitePackages}" + export PATH="$PIP_PREFIX/bin:$PATH" + # Avoids reproducibility issues with some Python packages + # See https://nixos.org/manual/nixpkgs/stable/#python-setup.py-bdist_wheel-cannot-create-.whl + unset SOURCE_DATE_EPOCH + ''; + }; + } + ); + }; +} diff --git a/monitor/filter_c3_exception_decoder.py b/monitor/filter_c3_exception_decoder.py index 5e74dc2b9..fbc372bcf 100644 --- a/monitor/filter_c3_exception_decoder.py +++ b/monitor/filter_c3_exception_decoder.py @@ -43,13 +43,11 @@ class Esp32C3ExceptionDecoder(DeviceMonitorFilterBase): self.enabled = self.setup_paths() if self.config.get("env:" + self.environment, "build_type") != "debug": - print( - """ + print(""" Please build project in debug configuration to get more details about an exception. See https://docs.platformio.org/page/projectconf/build_configurations.html -""" - ) +""") return self diff --git a/platformio.ini b/platformio.ini index 4c19136af..291915d3c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -50,12 +50,14 @@ build_flags = -Wno-missing-field-initializers -DRADIOLIB_EXCLUDE_APRS=1 -DRADIOLIB_EXCLUDE_LORAWAN=1 -DMESHTASTIC_EXCLUDE_DROPZONE=1 + -DMESHTASTIC_EXCLUDE_REPLYBOT=1 -DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1 -DMESHTASTIC_EXCLUDE_HEALTH_TELEMETRY=1 -DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware -DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1 -DMESHTASTIC_EXCLUDE_POWERMON=1 -D MAX_THREADS=40 ; As we've split modules, we have more threads to manage + -DLED_BUILTIN=-1 #-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now #-D OLED_PL=1 #-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs @@ -119,7 +121,7 @@ lib_deps = [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/5a870c623a4e9ab7a7abe3d02950536f107d1a31.zip + https://github.com/meshtastic/device-ui/archive/63967a4a557d33d56fc5746f9128200dde2d88c5.zip ; Common libs for environmental measurements in telemetry module [environmental_base] @@ -212,3 +214,30 @@ lib_deps = sensirion/Sensirion Core@0.7.2 # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x sensirion/Sensirion I2C SCD4x@1.1.0 +; Same as environmental_extra but without BSEC (saves ~3.5KB DRAM for original ESP32 targets) +[environmental_extra_no_bsec] +lib_deps = + # renovate: datasource=custom.pio depName=Adafruit BMP3XX packageName=adafruit/library/Adafruit BMP3XX Library + adafruit/Adafruit BMP3XX Library@2.1.6 + # renovate: datasource=custom.pio depName=Adafruit MAX1704X packageName=adafruit/library/Adafruit MAX1704X + adafruit/Adafruit MAX1704X@1.0.3 + # renovate: datasource=custom.pio depName=Adafruit SHTC3 packageName=adafruit/library/Adafruit SHTC3 Library + adafruit/Adafruit SHTC3 Library@1.0.2 + # renovate: datasource=custom.pio depName=Adafruit LPS2X packageName=adafruit/library/Adafruit LPS2X + adafruit/Adafruit LPS2X@2.0.6 + # renovate: datasource=custom.pio depName=Adafruit SHT31 packageName=adafruit/library/Adafruit SHT31 Library + adafruit/Adafruit SHT31 Library@2.2.2 + # renovate: datasource=custom.pio depName=Adafruit VEML7700 packageName=adafruit/library/Adafruit VEML7700 Library + adafruit/Adafruit VEML7700 Library@2.1.6 + # renovate: datasource=custom.pio depName=Adafruit SHT4x packageName=adafruit/library/Adafruit SHT4x Library + adafruit/Adafruit SHT4x Library@1.0.5 + # renovate: datasource=custom.pio depName=SparkFun Qwiic Scale NAU7802 packageName=sparkfun/library/SparkFun Qwiic Scale NAU7802 Arduino Library + sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@1.0.6 + # renovate: datasource=custom.pio depName=ClosedCube OPT3001 packageName=closedcube/library/ClosedCube OPT3001 + closedcube/ClosedCube OPT3001@1.1.2 + # renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master + https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip + # renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core + sensirion/Sensirion Core@0.7.2 + # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x + sensirion/Sensirion I2C SCD4x@1.1.0 \ No newline at end of file diff --git a/protobufs b/protobufs index 4b9f104a1..bc63a57f9 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 4b9f104a18ea43b1b2091ee2b48899fe43ad8a0b +Subproject commit bc63a57f9e5dba8a7c90ee0bd4a9840862d61f6d diff --git a/shell.nix b/shell.nix new file mode 100644 index 000000000..692cd4df8 --- /dev/null +++ b/shell.nix @@ -0,0 +1,12 @@ +(import ( + let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + nodeName = lock.nodes.root.inputs.flake-compat; + in + fetchTarball { + url = + lock.nodes.${nodeName}.locked.url + or "https://github.com/NixOS/flake-compat/archive/${lock.nodes.${nodeName}.locked.rev}.tar.gz"; + sha256 = lock.nodes.${nodeName}.locked.narHash; + } +) { src = ./.; }).shellNix diff --git a/src/BluetoothStatus.h b/src/BluetoothStatus.h index 680aec929..4ea4a95ac 100644 --- a/src/BluetoothStatus.h +++ b/src/BluetoothStatus.h @@ -89,22 +89,14 @@ 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 + digitalWrite(BLE_LED, LED_STATE_ON); #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 + digitalWrite(BLE_LED, LED_STATE_OFF); #endif break; } diff --git a/src/Power.cpp b/src/Power.cpp index e320f0557..a7da6f7a9 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -1,11 +1,14 @@ /** * @file Power.cpp - * @brief This file contains the implementation of the Power class, which is responsible for managing power-related functionality - * of the device. It includes battery level sensing, power management unit (PMU) control, and power state machine management. The - * Power class is used by the main device class to manage power-related functionality. + * @brief This file contains the implementation of the Power class, which is + * responsible for managing power-related functionality of the device. It + * includes battery level sensing, power management unit (PMU) control, and + * power state machine management. The Power class is used by the main device + * class to manage power-related functionality. * - * The file also includes implementations of various battery level sensors, such as the AnalogBatteryLevel class, which assumes - * the battery voltage is attached via a voltage-divider to an analog input. + * The file also includes implementations of various battery level sensors, such + * as the AnalogBatteryLevel class, which assumes the battery voltage is + * attached via a voltage-divider to an analog input. * * This file is part of the Meshtastic project. * For more information, see: https://meshtastic.org/ @@ -19,6 +22,7 @@ #include "configuration.h" #include "main.h" #include "meshUtils.h" +#include "power/PowerHAL.h" #include "sleep.h" #if defined(ARCH_PORTDUINO) @@ -171,22 +175,12 @@ Power *power; using namespace meshtastic; -#ifndef AREF_VOLTAGE -#if defined(ARCH_NRF52) -/* - * Internal Reference is +/-0.6V, with an adjustable gain of 1/6, 1/5, 1/4, - * 1/3, 1/2 or 1, meaning 3.6, 3.0, 2.4, 1.8, 1.2 or 0.6V for the ADC levels. - * - * External Reference is VDD/4, with an adjustable gain of 1, 2 or 4, meaning - * VDD/4, VDD/2 or VDD for the ADC levels. - * - * Default settings are internal reference with 1/6 gain (GND..3.6V ADC range) - */ -#define AREF_VOLTAGE 3.6 -#else +// NRF52 has AREF_VOLTAGE defined in architecture.h but +// make sure it's included. If something is wrong with NRF52 +// definition - compilation will fail on missing definition +#if !defined(AREF_VOLTAGE) && !defined(ARCH_NRF52) #define AREF_VOLTAGE 3.3 #endif -#endif /** * If this board has a battery level sensor, set this to a valid implementation @@ -233,7 +227,8 @@ static void battery_adcDisable() #endif /** - * A simple battery level sensor that assumes the battery voltage is attached via a voltage-divider to an analog input + * A simple battery level sensor that assumes the battery voltage is attached + * via a voltage-divider to an analog input */ class AnalogBatteryLevel : public HasBatteryLevel { @@ -311,7 +306,8 @@ class AnalogBatteryLevel : public HasBatteryLevel #ifndef BATTERY_SENSE_SAMPLES #define BATTERY_SENSE_SAMPLES \ - 15 // Set the number of samples, it has an effect of increasing sensitivity in complex electromagnetic environment. + 15 // Set the number of samples, it has an effect of increasing sensitivity in + // complex electromagnetic environment. #endif #ifdef BATTERY_PIN @@ -341,7 +337,8 @@ class AnalogBatteryLevel : public HasBatteryLevel battery_adcDisable(); if (!initial_read_done) { - // Flush the smoothing filter with an ADC reading, if the reading is plausibly correct + // Flush the smoothing filter with an ADC reading, if the reading is + // plausibly correct if (scaled > last_read_value) last_read_value = scaled; initial_read_done = true; @@ -350,8 +347,8 @@ class AnalogBatteryLevel : public HasBatteryLevel last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF } - // LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u", BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t) - // (last_read_value)); + // LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u", + // BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t) (last_read_value)); } return last_read_value; #endif // BATTERY_PIN @@ -420,7 +417,8 @@ class AnalogBatteryLevel : public HasBatteryLevel /** * return true if there is a battery installed in this unit */ - // if we have a integrated device with a battery, we can assume that the battery is always connected + // if we have a integrated device with a battery, we can assume that the + // battery is always connected #ifdef BATTERY_IMMUTABLE virtual bool isBatteryConnect() override { return true; } #elif defined(ADC_V) @@ -441,10 +439,10 @@ class AnalogBatteryLevel : public HasBatteryLevel virtual bool isBatteryConnect() override { return getBatteryPercent() != -1; } #endif - /// If we see a battery voltage higher than physics allows - assume charger is pumping - /// in power - /// On some boards we don't have the power management chip (like AXPxxxx) - /// so we use EXT_PWR_DETECT GPIO pin to detect external power source + /// If we see a battery voltage higher than physics allows - assume charger is + /// pumping in power On some boards we don't have the power management chip + /// (like AXPxxxx) so we use EXT_PWR_DETECT GPIO pin to detect external power + /// source virtual bool isVbusIn() override { #ifdef EXT_PWR_DETECT @@ -461,8 +459,14 @@ class AnalogBatteryLevel : public HasBatteryLevel } // if it's not HIGH - check the battery #endif -#elif defined(MUZI_BASE) - return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk; + // If we have an EXT_PWR_DETECT pin and it indicates no external power, believe it. + return false; + +// technically speaking this should work for all(?) NRF52 boards +// but needs testing across multiple devices. NRF52 USB would not even work if +// VBUS was not properly connected and detected by the CPU +#elif defined(MUZI_BASE) || defined(PROMICRO_DIY_TCXO) + return powerHAL_isVBUSConnected(); #endif return getBattVoltage() > chargingVolt; } @@ -485,8 +489,9 @@ class AnalogBatteryLevel : public HasBatteryLevel #else #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION) if (hasINA()) { - // get current flow from INA sensor - negative value means power flowing into the battery - // default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT RESISTOR <--> INA_VIN- <--> LOAD + // get current flow from INA sensor - negative value means power flowing + // into the battery default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT + // RESISTOR <--> INA_VIN- <--> LOAD LOG_DEBUG("Using INA on I2C addr 0x%x for charging detection", config.power.device_battery_ina_address); #if defined(INA_CHARGING_DETECTION_INVERT) return getINACurrent() > 0; @@ -502,8 +507,8 @@ class AnalogBatteryLevel : public HasBatteryLevel } private: - /// If we see a battery voltage higher than physics allows - assume charger is pumping - /// in power + /// If we see a battery voltage higher than physics allows - assume charger is + /// pumping in power /// For heltecs with no battery connected, the measured voltage is 2204, so // need to be higher than that, in this case is 2500mV (3000-500) @@ -512,7 +517,8 @@ class AnalogBatteryLevel : public HasBatteryLevel const float noBatVolt = (OCV[NUM_OCV_POINTS - 1] - 500) * NUM_CELLS; // Start value from minimum voltage for the filter to not start from 0 // that could trigger some events. - // This value is over-written by the first ADC reading, it the voltage seems reasonable. + // This value is over-written by the first ADC reading, it the voltage seems + // reasonable. bool initial_read_done = false; float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS); uint32_t last_read_time_ms = 0; @@ -654,7 +660,8 @@ bool Power::analogInit() #ifdef CONFIG_IDF_TARGET_ESP32S3 // ESP32S3 else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) { - LOG_INFO("ADC config based on Two Point values and fitting curve coefficients stored in eFuse"); + LOG_INFO("ADC config based on Two Point values and fitting curve " + "coefficients stored in eFuse"); } #endif else { @@ -662,13 +669,7 @@ bool Power::analogInit() } #endif // ARCH_ESP32 -#ifdef ARCH_NRF52 -#ifdef VBAT_AR_INTERNAL - analogReference(VBAT_AR_INTERNAL); -#else - analogReference(AR_INTERNAL); // 3.6V -#endif -#endif // ARCH_NRF52 + // NRF52 ADC init moved to powerHAL_init in nrf52 platform #ifndef ARCH_ESP32 analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS); @@ -723,6 +724,16 @@ bool Power::setup() runASAP = true; }, CHANGE); +#endif +#ifdef EXT_CHRG_DETECT + attachInterrupt( + EXT_CHRG_DETECT, + []() { + power->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + }, + CHANGE); #endif enabled = found; low_voltage_counter = 0; @@ -769,7 +780,8 @@ void Power::reboot() HAL_NVIC_SystemReset(); #else rebootAtMsec = -1; - LOG_WARN("FIXME implement reboot for this platform. Note that some settings require a restart to be applied"); + LOG_WARN("FIXME implement reboot for this platform. Note that some settings " + "require a restart to be applied"); #endif } @@ -779,9 +791,12 @@ void Power::shutdown() #if HAS_SCREEN if (screen) { #ifdef T_DECK_PRO - screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button + screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", + 0); // T-Deck Pro has no power button #elif defined(USE_EINK) - screen->showSimpleBanner("Shutting Down...", 2250); // dismiss after 3 seconds to avoid the banner on the sleep screen + screen->showSimpleBanner("Shutting Down...", + 2250); // dismiss after 3 seconds to avoid the + // banner on the sleep screen #else screen->showSimpleBanner("Shutting Down...", 0); // stays on screen #endif @@ -803,6 +818,9 @@ void Power::shutdown() #endif #ifdef PIN_LED3 ledOff(PIN_LED3); +#endif +#ifdef LED_NOTIFICATION + ledOff(LED_NOTIFICATION); #endif doDeepSleep(DELAY_FOREVER, true, true); #elif defined(ARCH_PORTDUINO) @@ -820,7 +838,8 @@ void Power::readPowerStatus() int32_t batteryVoltageMv = -1; // Assume unknown int8_t batteryChargePercent = -1; OptionalBool usbPowered = OptUnknown; - OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM code doesn't run every time + OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM + // code doesn't run every time OptionalBool isChargingNow = OptUnknown; if (batteryLevel) { @@ -833,9 +852,10 @@ void Power::readPowerStatus() if (batteryLevel->getBatteryPercent() >= 0) { batteryChargePercent = batteryLevel->getBatteryPercent(); } else { - // If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error - // In that case, we compute an estimate of the charge percent based on open circuit voltage table defined - // in power.h + // If the AXP192 returns a percentage less than 0, the feature is either + // not supported or there is an error In that case, we compute an + // estimate of the charge percent based on open circuit voltage table + // defined in power.h batteryChargePercent = clamp((int)(((batteryVoltageMv - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS)) * 1e2) / ((OCV[0] * NUM_CELLS) - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS))), 0, 100); @@ -843,12 +863,12 @@ void Power::readPowerStatus() } } -// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way better instead to make a Nrf52IsUsbPowered subclass -// (which shares a superclass with the BatteryLevel stuff) -// that just provides a few methods. But in the interest of fixing this bug I'm going to follow current -// practice. -#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates the power states. Takes 20 seconds or so to detect - // changes. +// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way +// better instead to make a Nrf52IsUsbPowered subclass (which shares a +// superclass with the BatteryLevel stuff) that just provides a few methods. But +// in the interest of fixing this bug I'm going to follow current practice. +#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates + // the power states. Takes 20 seconds or so to detect changes. nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get(); // LOG_DEBUG("NRF Power %d", nrf_usb_state); @@ -922,8 +942,9 @@ void Power::readPowerStatus() #endif - // If we have a battery at all and it is less than 0%, force deep sleep if we have more than 10 low readings in - // a row. NOTE: min LiIon/LiPo voltage is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough. + // If we have a battery at all and it is less than 0%, force deep sleep if we + // have more than 10 low readings in a row. NOTE: min LiIon/LiPo voltage + // is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough. // if (batteryLevel && powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) { @@ -945,8 +966,8 @@ int32_t Power::runOnce() readPowerStatus(); #ifdef HAS_PMU - // WE no longer use the IRQ line to wake the CPU (due to false wakes from sleep), but we do poll - // the IRQ status by reading the registers over I2C + // WE no longer use the IRQ line to wake the CPU (due to false wakes from + // sleep), but we do poll the IRQ status by reading the registers over I2C if (PMU) { PMU->getIrqStatus(); @@ -988,7 +1009,8 @@ int32_t Power::runOnce() PMU->clearIrqStatus(); } #endif - // Only read once every 20 seconds once the power status for the app has been initialized + // Only read once every 20 seconds once the power status for the app has been + // initialized return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME; } @@ -996,10 +1018,12 @@ int32_t Power::runOnce() * Init the power manager chip * * axp192 power - DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the - axp192 share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this - on!) LDO1 30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of - days), can not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS + DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose + comms to the axp192 because the OLED and the axp192 share the same i2c bus, + instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> + ESP32 (keep this on!) LDO1 30mA -> charges GPS backup battery // charges the + tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can + not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS * */ bool Power::axpChipInit() @@ -1044,9 +1068,10 @@ bool Power::axpChipInit() if (!PMU) { /* - * In XPowersLib, if the XPowersAXPxxx object is released, Wire.end() will be called at the same time. - * In order not to affect other devices, if the initialization of the PMU fails, Wire needs to be re-initialized once, - * if there are multiple devices sharing the bus. + * In XPowersLib, if the XPowersAXPxxx object is released, Wire.end() will + * be called at the same time. In order not to affect other devices, if the + * initialization of the PMU fails, Wire needs to be re-initialized once, if + * there are multiple devices sharing the bus. * * */ #ifndef PMU_USE_WIRE1 w->begin(I2C_SDA, I2C_SCL); @@ -1063,8 +1088,8 @@ bool Power::axpChipInit() PMU->enablePowerOutput(XPOWERS_LDO2); // oled module power channel, - // disable it will cause abnormal communication between boot and AXP power supply, - // do not turn it off + // disable it will cause abnormal communication between boot and AXP power + // supply, do not turn it off PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300); // enable oled power PMU->enablePowerOutput(XPOWERS_DCDC1); @@ -1091,7 +1116,8 @@ bool Power::axpChipInit() PMU->setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2); } else if (PMU->getChipModel() == XPOWERS_AXP2101) { - /*The alternative version of T-Beam 1.1 differs from T-Beam V1.1 in that it uses an AXP2101 power chip*/ + /*The alternative version of T-Beam 1.1 differs from T-Beam V1.1 in that it + * uses an AXP2101 power chip*/ if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) { // Unuse power channel PMU->disablePowerOutput(XPOWERS_DCDC2); @@ -1126,8 +1152,8 @@ bool Power::axpChipInit() // t-beam s3 core /** * gnss module power channel - * The default ALDO4 is off, you need to turn on the GNSS power first, otherwise it will be invalid during - * initialization + * The default ALDO4 is off, you need to turn on the GNSS power first, + * otherwise it will be invalid during initialization */ PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300); PMU->enablePowerOutput(XPOWERS_ALDO4); @@ -1177,7 +1203,8 @@ bool Power::axpChipInit() // disable all axp chip interrupt PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ); - // Set the constant current charging current of AXP2101, temporarily use 500mA by default + // Set the constant current charging current of AXP2101, temporarily use + // 500mA by default PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA); // Set up the charging voltage @@ -1243,11 +1270,12 @@ bool Power::axpChipInit() PMU->getPowerChannelVoltage(XPOWERS_BLDO2)); } -// We can safely ignore this approach for most (or all) boards because MCU turned off -// earlier than battery discharged to 2.6V. +// We can safely ignore this approach for most (or all) boards because MCU +// turned off earlier than battery discharged to 2.6V. // -// Unfortanly for now we can't use this killswitch for RAK4630-based boards because they have a bug with -// battery voltage measurement. Probably it sometimes drops to low values. +// Unfortunately for now we can't use this killswitch for RAK4630-based boards +// because they have a bug with battery voltage measurement. Probably it +// sometimes drops to low values. #ifndef RAK4630 // Set PMU shutdown voltage at 2.6V to maximize battery utilization PMU->setSysPowerDownVoltage(2600); @@ -1266,10 +1294,12 @@ bool Power::axpChipInit() attachInterrupt( PMU_IRQ, [] { pmu_irq = true; }, FALLING); - // we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ because it occurs repeatedly while there is - // no battery also it could cause inadvertent waking from light sleep just because the battery filled - // we don't look for AXPXXX_BATT_REMOVED_IRQ because it occurs repeatedly while no battery installed - // we don't look at AXPXXX_VBUS_REMOVED_IRQ because we don't have anything hooked to vbus + // we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ + // because it occurs repeatedly while there is no battery also it could cause + // inadvertent waking from light sleep just because the battery filled we + // don't look for AXPXXX_BATT_REMOVED_IRQ because it occurs repeatedly while + // no battery installed we don't look at AXPXXX_VBUS_REMOVED_IRQ because we + // don't have anything hooked to vbus PMU->enableIRQ(pmuIrqMask); PMU->clearIrqStatus(); @@ -1385,8 +1415,8 @@ class LipoCharger : public HasBatteryLevel bool result = PPM->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR); if (result) { LOG_INFO("PPM BQ25896 init succeeded"); - // Set the minimum operating voltage. Below this voltage, the PPM will protect - // PPM->setSysPowerDownVoltage(3100); + // Set the minimum operating voltage. Below this voltage, the PPM will + // protect PPM->setSysPowerDownVoltage(3100); // Set input current limit, default is 500mA // PPM->setInputCurrentLimit(800); @@ -1409,7 +1439,8 @@ class LipoCharger : public HasBatteryLevel PPM->enableMeasure(); // Turn on charging function - // If there is no battery connected, do not turn on the charging function + // If there is no battery connected, do not turn on the charging + // function PPM->enableCharge(); } else { LOG_WARN("PPM BQ25896 init failed"); @@ -1444,7 +1475,8 @@ class LipoCharger : public HasBatteryLevel virtual int getBatteryPercent() override { return -1; - // return bq->getChargePercent(); // don't use BQ27220 for battery percent, it is not calibrated + // return bq->getChargePercent(); // don't use BQ27220 for battery percent, + // it is not calibrated } /** @@ -1566,7 +1598,8 @@ bool Power::meshSolarInit() #else /** - * The meshSolar battery level sensor is unavailable - default to AnalogBatteryLevel + * The meshSolar battery level sensor is unavailable - default to + * AnalogBatteryLevel */ bool Power::meshSolarInit() { diff --git a/src/configuration.h b/src/configuration.h index eb258651c..66fa4492d 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -155,6 +155,10 @@ along with this program. If not, see . #endif // Default system gain to 0 if not defined +#ifndef NUM_PA_POINTS +#define NUM_PA_POINTS 1 +#endif + #ifndef TX_GAIN_LORA #define TX_GAIN_LORA 0 #endif @@ -386,9 +390,6 @@ along with this program. If not, see . #ifndef HAS_RADIO #define HAS_RADIO 0 #endif -#ifndef HAS_RTC -#define HAS_RTC 0 -#endif #ifndef HAS_CPU_SHUTDOWN #define HAS_CPU_SHUTDOWN 0 #endif @@ -424,12 +425,16 @@ along with this program. If not, see . #define HAS_RGB_LED #endif -#ifndef LED_STATE_OFF -#define LED_STATE_OFF 0 -#endif #ifndef LED_STATE_ON #define LED_STATE_ON 1 #endif +#ifndef LED_STATE_OFF +#define LED_STATE_OFF (LED_STATE_ON ^ 1) +#endif + +#ifndef ledOff +#define ledOff(pin) pinMode(pin, INPUT) +#endif // default mapping of pins #if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN) @@ -445,18 +450,6 @@ along with this program. If not, see . #endif #endif -// BME680 BSEC2 support detection -#if !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED) -#if defined(RAK_4631) || defined(TBEAM_V10) - -#define MESHTASTIC_BME680_BSEC2_SUPPORTED 1 -#define MESHTASTIC_BME680_HEADER -#else -#define MESHTASTIC_BME680_BSEC2_SUPPORTED 0 -#define MESHTASTIC_BME680_HEADER -#endif // defined(RAK_4631) -#endif // !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED) - // ----------------------------------------------------------------------------- // Global switches to turn off features for a minimized build // ----------------------------------------------------------------------------- diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index fd121861c..13e5c32d1 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -905,6 +905,12 @@ void GPS::writePinStandby(bool standby) // Write and log pinMode(PIN_GPS_STANDBY, OUTPUT); digitalWrite(PIN_GPS_STANDBY, val); + + // Enter backup mode on PA1010D; TODO: may be applicable to other MTK GPS too + if (IS_ONE_OF(gnssModel, GNSS_MODEL_MTK_PA1010D)) { + _serial_gps->write("$PMTK225,4*2F\r\n"); + } + #ifdef GPS_DEBUG LOG_DEBUG("Pin STANDBY %s", val == HIGH ? "HI" : "LOW"); #endif diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 25cd3ceff..3bca6f6ec 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -276,11 +276,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd settimeofday(tv, NULL); #endif - // nrf52 doesn't have a readable RTC (yet - software not written) -#if HAS_RTC readFromRTC(); -#endif - return RTCSetResultSuccess; } else { return RTCSetResultNotSet; // RTC was already set with a higher quality time @@ -397,7 +393,7 @@ uint32_t getValidTime(RTCQuality minQuality, bool local) return (currentQuality >= minQuality) ? getTime(local) : 0; } -time_t gm_mktime(struct tm *tm) +time_t gm_mktime(const struct tm *tm) { #if !MESHTASTIC_EXCLUDE_TZ time_t result = 0; @@ -413,8 +409,8 @@ time_t gm_mktime(struct tm *tm) days_before_this_year -= 719162; // (1969 * 365 + 1969 / 4 - 1969 / 100 + 1969 / 400); // Now, within this tm->year, compute the days *before* this tm->month starts. - int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year - int days_this_year_before_this_month = days_before_month[tm->tm_mon]; // tm->tm_mon is 0..11 + static const int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year + int days_this_year_before_this_month = days_before_month[tm->tm_mon]; // tm->tm_mon is 0..11 // If this is a leap year, and we're past February, add a day: if (tm->tm_mon >= 2 && (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) { @@ -435,6 +431,7 @@ time_t gm_mktime(struct tm *tm) return result; #else - return mktime(tm); + struct tm tmCopy = *tm; + return mktime(&tmCopy); #endif } diff --git a/src/gps/RTC.h b/src/gps/RTC.h index 06dd34c16..cf6db0239 100644 --- a/src/gps/RTC.h +++ b/src/gps/RTC.h @@ -54,7 +54,7 @@ uint32_t getValidTime(RTCQuality minQuality, bool local = false); RTCSetResult readFromRTC(); -time_t gm_mktime(struct tm *tm); +time_t gm_mktime(const struct tm *tm); #define SEC_PER_DAY 86400 #define SEC_PER_HOUR 3600 diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 8bf69b7a0..111a47f7c 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1731,6 +1731,26 @@ int Screen::handleInputEvent(const InputEvent *event) showFrame(FrameDirection::PREVIOUS); } else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) { showFrame(FrameDirection::NEXT); + } else if (event->inputEvent == INPUT_BROKER_FN_F1) { + this->ui->switchToFrame(0); + lastScreenTransition = millis(); + setFastFramerate(); + } else if (event->inputEvent == INPUT_BROKER_FN_F2) { + this->ui->switchToFrame(1); + lastScreenTransition = millis(); + setFastFramerate(); + } else if (event->inputEvent == INPUT_BROKER_FN_F3) { + this->ui->switchToFrame(2); + lastScreenTransition = millis(); + setFastFramerate(); + } else if (event->inputEvent == INPUT_BROKER_FN_F4) { + this->ui->switchToFrame(3); + lastScreenTransition = millis(); + setFastFramerate(); + } else if (event->inputEvent == INPUT_BROKER_FN_F5) { + this->ui->switchToFrame(4); + lastScreenTransition = millis(); + setFastFramerate(); } else if (event->inputEvent == INPUT_BROKER_UP_LONG) { // Long press up button for fast frame switching showPrevFrame(); diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index 75b65c65f..2dca38d66 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -438,7 +438,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, if (currentResolution == ScreenResolution::UltraLow) { snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz (%d)", freqStr, config.lora.channel_num); } else { - snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %sMHz (%d)", freqStr, config.lora.channel_num); + snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz (%d)", freqStr, config.lora.channel_num); } } size_t len = strlen(frequencyslot); diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 5c459d984..6d29e9f7f 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -65,12 +65,12 @@ uint8_t test_count = 0; void menuHandler::loraMenu() { - static const char *optionsArray[] = {"Back", "Device Role", "Radio Preset", "LoRa Region"}; - enum optionsNumbers { Back = 0, device_role_picker = 1, radio_preset_picker = 2, lora_picker = 3 }; + static const char *optionsArray[] = {"Back", "Device Role", "Radio Preset", "Frequency Slot", "LoRa Region"}; + enum optionsNumbers { Back = 0, device_role_picker = 1, radio_preset_picker = 2, frequency_slot = 3, lora_picker = 4 }; BannerOverlayOptions bannerOptions; bannerOptions.message = "LoRa Actions"; bannerOptions.optionsArrayPtr = optionsArray; - bannerOptions.optionsCount = 4; + bannerOptions.optionsCount = 5; bannerOptions.bannerCallback = [](int selected) -> void { if (selected == Back) { // No action @@ -78,6 +78,8 @@ void menuHandler::loraMenu() menuHandler::menuQueue = menuHandler::device_role_picker; } else if (selected == radio_preset_picker) { menuHandler::menuQueue = menuHandler::radio_preset_picker; + } else if (selected == frequency_slot) { + menuHandler::menuQueue = menuHandler::frequency_slot; } else if (selected == lora_picker) { menuHandler::menuQueue = menuHandler::lora_picker; } @@ -248,6 +250,69 @@ void menuHandler::DeviceRolePicker() screen->showOverlayBanner(bannerOptions); } +void menuHandler::FrequencySlotPicker() +{ + + enum ReplyOptions : int { Back = -1 }; + constexpr int MAX_CHANNEL_OPTIONS = 202; + static const char *optionsArray[MAX_CHANNEL_OPTIONS]; + static int optionsEnumArray[MAX_CHANNEL_OPTIONS]; + static char channelText[MAX_CHANNEL_OPTIONS - 1][12]; + int options = 0; + optionsArray[options] = "Back"; + optionsEnumArray[options++] = Back; + optionsArray[options] = "Slot 0 (Auto)"; + optionsEnumArray[options++] = 0; + + // Calculate number of channels (copied from RadioInterface::applyModemConfig()) + meshtastic_Config_LoRaConfig &loraConfig = config.lora; + double bw = loraConfig.use_preset ? modemPresetToBwKHz(loraConfig.modem_preset, myRegion->wideLora) + : bwCodeToKHz(loraConfig.bandwidth); + + uint32_t numChannels = 0; + if (myRegion) { + numChannels = (uint32_t)floor((myRegion->freqEnd - myRegion->freqStart) / (myRegion->spacing + (bw / 1000.0))); + } else { + LOG_WARN("Region not set, cannot calculate number of channels"); + return; + } + + if (numChannels > (uint32_t)(MAX_CHANNEL_OPTIONS - 2)) + numChannels = (uint32_t)(MAX_CHANNEL_OPTIONS - 2); + + for (uint32_t ch = 1; ch <= numChannels; ch++) { + snprintf(channelText[ch - 1], sizeof(channelText[ch - 1]), "Slot %lu", (unsigned long)ch); + optionsArray[options] = channelText[ch - 1]; + optionsEnumArray[options++] = (int)ch; + } + + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Frequency Slot"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsEnumPtr = optionsEnumArray; + bannerOptions.optionsCount = options; + + // Start highlight on current channel if possible, otherwise on "1" + int initial = (int)config.lora.channel_num + 1; + if (initial < 2 || initial > (int)numChannels + 1) + initial = 1; + bannerOptions.InitialSelected = initial; + + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Back) { + menuHandler::menuQueue = menuHandler::lora_Menu; + screen->runNow(); + return; + } + + config.lora.channel_num = selected; + service->reloadConfig(SEGMENT_CONFIG); + rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); + }; + + screen->showOverlayBanner(bannerOptions); +} + void menuHandler::RadioPresetPicker() { static const RadioPresetOption presetOptions[] = { @@ -278,6 +343,8 @@ void menuHandler::RadioPresetPicker() } config.lora.modem_preset = option.value; + config.lora.channel_num = 0; // Reset to default channel for the preset + config.lora.override_frequency = 0; // Clear any custom frequency service->reloadConfig(SEGMENT_CONFIG); rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); }); @@ -2551,6 +2618,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display) case radio_preset_picker: RadioPresetPicker(); break; + case frequency_slot: + FrequencySlotPicker(); + break; case no_timeout_lora_picker: LoraRegionPicker(0); break; diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h index 121b6dfc9..1b964678b 100644 --- a/src/graphics/draw/MenuHandler.h +++ b/src/graphics/draw/MenuHandler.h @@ -13,6 +13,7 @@ class menuHandler lora_picker, device_role_picker, radio_preset_picker, + frequency_slot, no_timeout_lora_picker, TZ_picker, twelve_hour_picker, @@ -63,6 +64,7 @@ class menuHandler static void loraMenu(); static void DeviceRolePicker(); static void RadioPresetPicker(); + static void FrequencySlotPicker(); static void handleMenuSwitch(OLEDDisplay *display); static void showConfirmationBanner(const char *message, std::function onConfirm); static void clockMenu(); @@ -138,7 +140,7 @@ struct ScreenColor { uint8_t b; bool useVariant; - ScreenColor(uint8_t rIn = 0, uint8_t gIn = 0, uint8_t bIn = 0, bool variantIn = false) + explicit ScreenColor(uint8_t rIn = 0, uint8_t gIn = 0, uint8_t bIn = 0, bool variantIn = false) : r(rIn), g(gIn), b(bIn), useVariant(variantIn) { } diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp index 09b798e06..01fdbb966 100644 --- a/src/graphics/draw/MessageRenderer.cpp +++ b/src/graphics/draw/MessageRenderer.cpp @@ -6,7 +6,6 @@ #include "MessageStore.h" #include "NodeDB.h" #include "UIRenderer.h" -#include "configuration.h" #include "gps/RTC.h" #include "graphics/Screen.h" #include "graphics/ScreenFonts.h" @@ -20,7 +19,6 @@ // External declarations extern bool hasUnreadMessage; -extern meshtastic_DeviceState devicestate; extern graphics::Screen *screen; using graphics::Emote; @@ -49,7 +47,7 @@ static inline size_t utf8CharLen(uint8_t c) } // Remove variation selectors (FE0F) and skin tone modifiers from emoji so they match your labels -std::string normalizeEmoji(const std::string &s) +static std::string normalizeEmoji(const std::string &s) { std::string out; for (size_t i = 0; i < s.size();) { @@ -82,6 +80,7 @@ uint32_t pauseStart = 0; bool waitingToReset = false; bool scrollStarted = false; static bool didReset = false; +static constexpr int MESSAGE_BLOCK_GAP = 6; void scrollUp() { @@ -111,22 +110,6 @@ void scrollDown() void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string &line, const Emote *emotes, int emoteCount) { - std::string renderLine; - for (size_t i = 0; i < line.size();) { - uint8_t c = (uint8_t)line[i]; - size_t len = utf8CharLen(c); - if (c == 0xEF && i + 2 < line.size() && (uint8_t)line[i + 1] == 0xB8 && (uint8_t)line[i + 2] == 0x8F) { - i += 3; - continue; - } - if (c == 0xF0 && i + 3 < line.size() && (uint8_t)line[i + 1] == 0x9F && (uint8_t)line[i + 2] == 0x8F && - ((uint8_t)line[i + 3] >= 0xBB && (uint8_t)line[i + 3] <= 0xBF)) { - i += 4; - continue; - } - renderLine.append(line, i, len); - i += len; - } int cursorX = x; const int fontHeight = FONT_HEIGHT_SMALL; @@ -203,8 +186,7 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string // Render the emote (if found) if (matchedEmote && i == nextEmotePos) { - // Vertically center emote relative to font baseline (not just midline) - int iconY = fontY + (fontHeight - matchedEmote->height) / 2; + int iconY = y + (lineHeight - matchedEmote->height) / 2; display->drawXbm(cursorX, iconY, matchedEmote->width, matchedEmote->height, matchedEmote->bitmap); cursorX += matchedEmote->width + 1; i += emojiLen; @@ -423,6 +405,63 @@ static inline int getRenderedLineWidth(OLEDDisplay *display, const std::string & return totalWidth; } +struct MessageBlock { + size_t start; + size_t end; + bool mine; +}; + +static int getDrawnLinePixelBottom(int lineTopY, const std::string &line, bool isHeaderLine) +{ + if (isHeaderLine) { + return lineTopY + (FONT_HEIGHT_SMALL - 1); + } + + int tallest = FONT_HEIGHT_SMALL; + for (int e = 0; e < numEmotes; ++e) { + if (line.find(emotes[e].label) != std::string::npos) { + if (emotes[e].height > tallest) + tallest = emotes[e].height; + } + } + + const int lineHeight = std::max(FONT_HEIGHT_SMALL, tallest); + const int iconTop = lineTopY + (lineHeight - tallest) / 2; + + return iconTop + tallest - 1; +} + +static std::vector buildMessageBlocks(const std::vector &isHeaderVec, const std::vector &isMineVec) +{ + std::vector blocks; + if (isHeaderVec.empty()) + return blocks; + + size_t start = 0; + bool mine = isMineVec[0]; + + for (size_t i = 1; i < isHeaderVec.size(); ++i) { + if (isHeaderVec[i]) { + MessageBlock b; + b.start = start; + b.end = i - 1; + b.mine = mine; + blocks.push_back(b); + + start = i; + mine = isMineVec[i]; + } + } + + MessageBlock last; + last.start = start; + last.end = isHeaderVec.size() - 1; + last.mine = mine; + blocks.push_back(last); + + return blocks; +} + static void drawMessageScrollbar(OLEDDisplay *display, int visibleHeight, int totalHeight, int scrollOffset, int startY) { if (totalHeight <= visibleHeight) @@ -482,9 +521,14 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 constexpr int LEFT_MARGIN = 2; constexpr int RIGHT_MARGIN = 2; constexpr int SCROLLBAR_WIDTH = 3; + constexpr int BUBBLE_PAD_X = 3; + constexpr int BUBBLE_PAD_Y = 4; + constexpr int BUBBLE_RADIUS = 4; + constexpr int BUBBLE_MIN_W = 24; + constexpr int BUBBLE_TEXT_INDENT = 2; - const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN; - + // Derived widths + const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - (BUBBLE_PAD_X * 2); const int rightTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - SCROLLBAR_WIDTH; // Title string depending on mode @@ -547,7 +591,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 char chanType[32] = ""; if (currentMode == ThreadMode::ALL) { if (m.dest == NODENUM_BROADCAST) { - snprintf(chanType, sizeof(chanType), "#%s", channels.getName(m.channelIndex)); + const char *name = channels.getName(m.channelIndex); + if (currentResolution == ScreenResolution::Low || currentResolution == ScreenResolution::UltraLow) { + if (strcmp(name, "ShortTurbo") == 0) + name = "ShortT"; + else if (strcmp(name, "ShortSlow") == 0) + name = "ShortS"; + else if (strcmp(name, "ShortFast") == 0) + name = "ShortF"; + else if (strcmp(name, "MediumSlow") == 0) + name = "MedS"; + else if (strcmp(name, "MediumFast") == 0) + name = "MedF"; + else if (strcmp(name, "LongSlow") == 0) + name = "LongS"; + else if (strcmp(name, "LongFast") == 0) + name = "LongF"; + else if (strcmp(name, "LongTurbo") == 0) + name = "LongT"; + else if (strcmp(name, "LongMod") == 0) + name = "LongM"; + } + snprintf(chanType, sizeof(chanType), "#%s", name); } else { snprintf(chanType, sizeof(chanType), "(DM)"); } @@ -614,8 +679,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 } // Shrink Sender name if needed - int availWidth = SCREEN_WIDTH - display->getStringWidth(timeBuf) - display->getStringWidth(chanType) - - display->getStringWidth(" @...") - 10; + int availWidth = (mine ? rightTextWidth : leftTextWidth) - display->getStringWidth(timeBuf) - + display->getStringWidth(chanType) - display->getStringWidth(" @..."); if (availWidth < 0) availWidth = 0; @@ -667,6 +732,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 cachedLines = allLines; cachedHeights = calculateLineHeights(cachedLines, emotes, isHeader); + std::vector blocks = buildMessageBlocks(isHeader, isMine); + // Scrolling logic (unchanged) int totalHeight = 0; for (size_t i = 0; i < cachedHeights.size(); ++i) @@ -714,12 +781,133 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 int finalScroll = (int)scrollY; int yOffset = -finalScroll + getTextPositions(display)[1]; + const int contentTop = getTextPositions(display)[1]; + const int contentBottom = scrollBottom; // already excludes nav line + const int rightEdge = SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN; + const int bubbleGapY = std::max(1, MESSAGE_BLOCK_GAP / 2); + + std::vector lineTop; + lineTop.resize(cachedLines.size()); + { + int acc = 0; + for (size_t i = 0; i < cachedLines.size(); ++i) { + lineTop[i] = yOffset + acc; + acc += cachedHeights[i]; + } + } + + // Draw bubbles + for (size_t bi = 0; bi < blocks.size(); ++bi) { + const auto &b = blocks[bi]; + if (b.start >= cachedLines.size() || b.end >= cachedLines.size() || b.start > b.end) + continue; + + int visualTop = lineTop[b.start]; + + int topY; + if (isHeader[b.start]) { + // Header start + constexpr int BUBBLE_PAD_TOP_HEADER = 1; // try 1 or 2 + topY = visualTop - BUBBLE_PAD_TOP_HEADER; + } else { + // Body start + bool thisLineHasEmote = false; + for (int e = 0; e < numEmotes; ++e) { + if (cachedLines[b.start].find(emotes[e].label) != std::string::npos) { + thisLineHasEmote = true; + break; + } + } + if (thisLineHasEmote) { + constexpr int EMOTE_PADDING_ABOVE = 4; + visualTop -= EMOTE_PADDING_ABOVE; + } + topY = visualTop - BUBBLE_PAD_Y; + } + int visualBottom = getDrawnLinePixelBottom(lineTop[b.end], cachedLines[b.end], isHeader[b.end]); + int bottomY = visualBottom + BUBBLE_PAD_Y; + + if (bi + 1 < blocks.size()) { + int nextHeaderIndex = (int)blocks[bi + 1].start; + int nextTop = lineTop[nextHeaderIndex]; + int maxBottom = nextTop - 1 - bubbleGapY; + if (bottomY > maxBottom) + bottomY = maxBottom; + } + + if (bottomY <= topY + 2) + continue; + + if (bottomY < contentTop || topY > contentBottom - 1) + continue; + + int maxLineW = 0; + + for (size_t i = b.start; i <= b.end; ++i) { + int w = 0; + if (isHeader[i]) { + w = display->getStringWidth(cachedLines[i].c_str()); + if (b.mine) + w += 12; // room for ACK/NACK/relay mark + } else { + w = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes); + } + if (w > maxLineW) + maxLineW = w; + } + + int bubbleW = std::max(BUBBLE_MIN_W, maxLineW + (BUBBLE_PAD_X * 2)); + int bubbleH = (bottomY - topY) + 1; + int bubbleX = 0; + if (b.mine) { + bubbleX = rightEdge - bubbleW; + } else { + bubbleX = x; + } + if (bubbleX < x) + bubbleX = x; + if (bubbleX + bubbleW > rightEdge) + bubbleW = std::max(1, rightEdge - bubbleX); + + if (bubbleW > 1 && bubbleH > 1) { + int x1 = bubbleX + bubbleW - 1; + int y1 = topY + bubbleH - 1; + + if (b.mine) { + // Send Message (Right side) + display->drawRect(x1 + 2 - bubbleW, y1 - bubbleH, bubbleW, bubbleH); + // Top Right Corner + display->drawRect(x1 - 1, topY, 2, 1); + display->drawRect(x1, topY, 1, 2); + // Bottom Right Corner + display->drawRect(x1 - 1, bottomY - 2, 2, 1); + display->drawRect(x1, bottomY - 3, 1, 2); + // Knock the corners off to make a bubble + display->setColor(BLACK); + display->drawRect(x1 - bubbleW + 2, topY - 1, 1, 1); + display->drawRect(x1 - bubbleW + 2, bottomY - 1, 1, 1); + display->setColor(WHITE); + } else { + // Received Message (Left Side) + display->drawRect(bubbleX, topY, bubbleW + 1, bubbleH); + // Top Left Corner + display->drawRect(bubbleX + 1, topY + 1, 2, 1); + display->drawRect(bubbleX + 1, topY + 1, 1, 2); + // Bottom Left Corner + display->drawRect(bubbleX + 1, bottomY - 1, 2, 1); + display->drawRect(bubbleX + 1, bottomY - 2, 1, 2); + // Knock the corners off to make a bubble + display->setColor(BLACK); + display->drawRect(bubbleX + bubbleW, topY, 1, 1); + display->drawRect(bubbleX + bubbleW, bottomY, 1, 1); + display->setColor(WHITE); + } + } + } // Render visible lines + int lineY = yOffset; for (size_t i = 0; i < cachedLines.size(); ++i) { - int lineY = yOffset; - for (size_t j = 0; j < i; ++j) - lineY += cachedHeights[j]; if (lineY > -cachedHeights[i] && lineY < scrollBottom) { if (isHeader[i]) { @@ -728,14 +916,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 int headerX; if (isMine[i]) { // push header left to avoid overlap with scrollbar - headerX = SCREEN_WIDTH - w - SCROLLBAR_WIDTH - RIGHT_MARGIN; + headerX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - w - BUBBLE_TEXT_INDENT; if (headerX < LEFT_MARGIN) headerX = LEFT_MARGIN; } else { - headerX = x; + headerX = x + BUBBLE_PAD_X + BUBBLE_TEXT_INDENT; } display->drawString(headerX, lineY, cachedLines[i].c_str()); + // Draw underline just under header text + int underlineY = lineY + FONT_HEIGHT_SMALL; + + int underlineW = w; + int maxW = rightEdge - headerX; + if (maxW < 0) + maxW = 0; + if (underlineW > maxW) + underlineW = maxW; + + for (int px = 0; px < underlineW; ++px) { + display->setPixel(headerX + px, underlineY); + } + // Draw ACK/NACK mark for our own messages if (isMine[i]) { int markX = headerX - 10; @@ -753,32 +955,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 // AckStatus::NONE → show nothing } - // Draw underline just under header text - int underlineY = lineY + FONT_HEIGHT_SMALL; - for (int px = 0; px < w; ++px) { - display->setPixel(headerX + px, underlineY); - } } else { // Render message line if (isMine[i]) { // Calculate actual rendered width including emotes int renderedWidth = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes); - int rightX = SCREEN_WIDTH - renderedWidth - SCROLLBAR_WIDTH - RIGHT_MARGIN; + int rightX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - renderedWidth - BUBBLE_TEXT_INDENT; if (rightX < LEFT_MARGIN) rightX = LEFT_MARGIN; drawStringWithEmotes(display, rightX, lineY, cachedLines[i], emotes, numEmotes); } else { - drawStringWithEmotes(display, x, lineY, cachedLines[i], emotes, numEmotes); + drawStringWithEmotes(display, x + BUBBLE_PAD_X + BUBBLE_TEXT_INDENT, lineY, cachedLines[i], emotes, + numEmotes); } } } + + lineY += cachedHeights[i]; } - int totalContentHeight = totalHeight; - int visibleHeight = usableHeight; // Draw scrollbar - drawMessageScrollbar(display, visibleHeight, totalContentHeight, finalScroll, getTextPositions(display)[1]); + drawMessageScrollbar(display, usableHeight, totalHeight, finalScroll, getTextPositions(display)[1]); graphics::drawCommonHeader(display, x, y, titleStr); graphics::drawCommonFooter(display, x, y); } @@ -841,7 +1039,6 @@ std::vector calculateLineHeights(const std::vector &lines, con constexpr int HEADER_UNDERLINE_GAP = 0; // space between underline and first body line constexpr int HEADER_UNDERLINE_PIX = 1; // underline thickness (1px row drawn) constexpr int BODY_LINE_LEADING = -4; // default vertical leading for normal body lines - constexpr int MESSAGE_BLOCK_GAP = 4; // gap after a message block before a new header constexpr int EMOTE_PADDING_ABOVE = 4; // space above emote line (added to line above) constexpr int EMOTE_PADDING_BELOW = 3; // space below emote line (added to emote line) @@ -851,6 +1048,7 @@ std::vector calculateLineHeights(const std::vector &lines, con for (size_t idx = 0; idx < lines.size(); ++idx) { const auto &line = lines[idx]; const int baseHeight = FONT_HEIGHT_SMALL; + int lineHeight = baseHeight; // Detect if THIS line or NEXT line contains an emote bool hasEmote = false; @@ -872,8 +1070,6 @@ std::vector calculateLineHeights(const std::vector &lines, con } } - int lineHeight = baseHeight; - if (isHeaderVec[idx]) { // Header line spacing lineHeight = baseHeight + HEADER_UNDERLINE_PIX + HEADER_UNDERLINE_GAP; @@ -922,7 +1118,7 @@ void handleNewMessage(OLEDDisplay *display, const StoredMessage &sm, const mesht // Banner logic const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet.from); - char longName[48] = "???"; + char longName[48] = "?"; if (node && node->user.long_name) { strncpy(longName, node->user.long_name, sizeof(longName) - 1); longName[sizeof(longName) - 1] = '\0'; diff --git a/src/graphics/niche/InkHUD/Applet.cpp b/src/graphics/niche/InkHUD/Applet.cpp index 1e89ebe1b..0616d03cd 100644 --- a/src/graphics/niche/InkHUD/Applet.cpp +++ b/src/graphics/niche/InkHUD/Applet.cpp @@ -1,3 +1,5 @@ +#include "graphics/niche/InkHUD/Tile.h" +#include #ifdef MESHTASTIC_INCLUDE_INKHUD #include "./Applet.h" @@ -55,7 +57,7 @@ InkHUD::Tile *InkHUD::Applet::getTile() } // Draw the applet -void InkHUD::Applet::render() +void InkHUD::Applet::render(bool full) { assert(assignedTile); // Ensure that we have a tile assert(assignedTile->getAssignedApplet() == this); // Ensure that we have a reciprocal link with the tile @@ -65,10 +67,11 @@ void InkHUD::Applet::render() wantRender = false; // Flag set by requestUpdate wantAutoshow = false; // Flag set by requestAutoShow. May or may not have been honored. wantUpdateType = Drivers::EInk::UpdateTypes::UNSPECIFIED; // Update type we wanted. May on may not have been granted. + wantFullRender = true; // Default to a full render updateDimensions(); resetDrawingSpace(); - onRender(); // Derived applet's drawing takes place here + onRender(full); // Draw the applet // Handle "Tile Highlighting" // Some devices may use an auxiliary button to switch between tiles @@ -115,6 +118,11 @@ Drivers::EInk::UpdateTypes InkHUD::Applet::wantsUpdateType() return wantUpdateType; } +bool InkHUD::Applet::wantsFullRender() +{ + return wantFullRender; +} + // Get size of the applet's drawing space from its tile // Performed immediately before derived applet's drawing code runs void InkHUD::Applet::updateDimensions() @@ -142,10 +150,11 @@ void InkHUD::Applet::resetDrawingSpace() // Once the renderer has given other applets a chance to process whatever event we just detected, // it will run Applet::render(), which may draw our applet to screen, if it is shown (foreground) // We should requestUpdate even if our applet is currently background, because this might be changed by autoshow -void InkHUD::Applet::requestUpdate(Drivers::EInk::UpdateTypes type) +void InkHUD::Applet::requestUpdate(Drivers::EInk::UpdateTypes type, bool full) { wantRender = true; wantUpdateType = type; + wantFullRender = full; inkhud->requestUpdate(); } @@ -778,6 +787,16 @@ void InkHUD::Applet::drawHeader(std::string text) drawPixel(x, 0, BLACK); drawPixel(x, headerDivY, BLACK); // Dotted 50% } + + // Dither near battery + if (settings->optionalFeatures.batteryIcon) { + constexpr uint16_t ditherSizePx = 4; + Tile *batteryTile = ((Applet *)inkhud->getSystemApplet("BatteryIcon"))->getTile(); + const uint16_t batteryTileLeft = batteryTile->getLeft(); + const uint16_t batteryTileTop = batteryTile->getTop(); + const uint16_t batteryTileHeight = batteryTile->getHeight(); + hatchRegion(batteryTileLeft - ditherSizePx, batteryTileTop, ditherSizePx, batteryTileHeight, 2, WHITE); + } } // Get the height of the standard applet header diff --git a/src/graphics/niche/InkHUD/Applet.h b/src/graphics/niche/InkHUD/Applet.h index b35ca5cc0..69d35a234 100644 --- a/src/graphics/niche/InkHUD/Applet.h +++ b/src/graphics/niche/InkHUD/Applet.h @@ -64,10 +64,11 @@ class Applet : public GFX // Rendering - void render(); // Draw the applet + void render(bool full); // Draw the applet bool wantsToRender(); // Check whether applet wants to render bool wantsToAutoshow(); // Check whether applet wants to become foreground Drivers::EInk::UpdateTypes wantsUpdateType(); // Check which display update type the applet would prefer + bool wantsFullRender(); // Check whether applet wants to render over its previous render void updateDimensions(); // Get current size from tile void resetDrawingSpace(); // Makes sure every render starts with same parameters @@ -82,7 +83,7 @@ class Applet : public GFX // Event handlers - virtual void onRender() = 0; // All drawing happens here + virtual void onRender(bool full) = 0; // For drawing the applet virtual void onActivate() {} virtual void onDeactivate() {} virtual void onForeground() {} @@ -96,6 +97,9 @@ class Applet : public GFX virtual void onNavDown() {} virtual void onNavLeft() {} virtual void onNavRight() {} + virtual void onFreeText(char c) {} + virtual void onFreeTextDone() {} + virtual void onFreeTextCancel() {} virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification @@ -108,8 +112,9 @@ class Applet : public GFX protected: void drawPixel(int16_t x, int16_t y, uint16_t color) override; // Place a single pixel. All drawing output passes through here - void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED); // Ask WindowManager to schedule a display update - void requestAutoshow(); // Ask for applet to be moved to foreground + void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED, + bool full = true); // Ask WindowManager to schedule a display update + void requestAutoshow(); // Ask for applet to be moved to foreground uint16_t X(float f); // Map applet width, mapped from 0 to 1.0 uint16_t Y(float f); // Map applet height, mapped from 0 to 1.0 @@ -164,6 +169,7 @@ class Applet : public GFX bool wantAutoshow = false; // Does the applet have new data it would like to display in foreground? NicheGraphics::Drivers::EInk::UpdateTypes wantUpdateType = NicheGraphics::Drivers::EInk::UpdateTypes::UNSPECIFIED; // Which update method we'd prefer when redrawing the display + bool wantFullRender = true; // Render with a fresh canvas using GFX::setFont; // Make sure derived classes use AppletFont instead of AdafruitGFX fonts directly using GFX::setRotation; // Block setRotation calls. Rotation is handled globally by WindowManager. diff --git a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp index d383a11e4..4cf83966b 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp @@ -4,7 +4,7 @@ using namespace NicheGraphics; -void InkHUD::MapApplet::onRender() +void InkHUD::MapApplet::onRender(bool full) { // Abort if no markers to render if (!enoughMarkers()) { diff --git a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h index f45a36071..11dfb39d9 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h +++ b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h @@ -27,7 +27,7 @@ namespace NicheGraphics::InkHUD class MapApplet : public Applet { public: - void onRender() override; + void onRender(bool full) override; protected: virtual bool shouldDrawNode(meshtastic_NodeInfoLite *node) { return true; } // Allow derived applets to filter the nodes diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp index 5c9906fba..9794c3efb 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp @@ -103,7 +103,7 @@ uint8_t InkHUD::NodeListApplet::maxCards() } // Draw, using info which derived applet placed into NodeListApplet::cards for us -void InkHUD::NodeListApplet::onRender() +void InkHUD::NodeListApplet::onRender(bool full) { // ================================ diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h index c2340027b..8babdba03 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h +++ b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h @@ -46,7 +46,7 @@ class NodeListApplet : public Applet, public MeshModule public: NodeListApplet(const char *name); - void onRender() override; + void onRender(bool full) override; bool wantPacket(const meshtastic_MeshPacket *p) override; ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; diff --git a/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.cpp b/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.cpp index c52719e55..71b6d9a7a 100644 --- a/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.cpp @@ -6,7 +6,7 @@ using namespace NicheGraphics; // All drawing happens here // Our basic example doesn't do anything useful. It just passively prints some text. -void InkHUD::BasicExampleApplet::onRender() +void InkHUD::BasicExampleApplet::onRender(bool full) { printAt(0, 0, "Hello, World!"); diff --git a/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h b/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h index aed63cdc8..a36f6e8d5 100644 --- a/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h +++ b/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h @@ -28,7 +28,7 @@ class BasicExampleApplet : public Applet // You must have an onRender() method // All drawing happens here - void onRender() override; + void onRender(bool full) override; }; } // namespace NicheGraphics::InkHUD diff --git a/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.cpp b/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.cpp index 6b02f4c92..cf3fd7714 100644 --- a/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.cpp @@ -35,7 +35,7 @@ ProcessMessage InkHUD::NewMsgExampleApplet::handleReceived(const meshtastic_Mesh // We can trigger a render by calling requestUpdate() // Render might be called by some external source // We should always be ready to draw -void InkHUD::NewMsgExampleApplet::onRender() +void InkHUD::NewMsgExampleApplet::onRender(bool full) { printAt(0, 0, "Example: NewMsg", LEFT, TOP); // Print top-left corner of text at (0,0) diff --git a/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h b/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h index 22670a0f0..599f08a7a 100644 --- a/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h +++ b/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h @@ -34,7 +34,7 @@ class NewMsgExampleApplet : public Applet, public SinglePortModule NewMsgExampleApplet() : SinglePortModule("NewMsgExampleApplet", meshtastic_PortNum_TEXT_MESSAGE_APP) {} // All drawing happens here - void onRender() override; + void onRender(bool full) override; // Your applet might also want to use some of these // Useful for setting up or tidying up diff --git a/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.cpp index 67ef87f41..3afa80149 100644 --- a/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.cpp @@ -10,7 +10,7 @@ InkHUD::AlignStickApplet::AlignStickApplet() bringToForeground(); } -void InkHUD::AlignStickApplet::onRender() +void InkHUD::AlignStickApplet::onRender(bool full) { setFont(fontMedium); printAt(0, 0, "Align Joystick:"); @@ -152,19 +152,17 @@ void InkHUD::AlignStickApplet::onBackground() // Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background // Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case - inkhud->forceUpdate(EInk::UpdateTypes::FULL); + inkhud->forceUpdate(EInk::UpdateTypes::FULL, true); } void InkHUD::AlignStickApplet::onButtonLongPress() { sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::AlignStickApplet::onExitLong() { sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::AlignStickApplet::onNavUp() @@ -172,7 +170,6 @@ void InkHUD::AlignStickApplet::onNavUp() settings->joystick.aligned = true; sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::AlignStickApplet::onNavDown() @@ -181,7 +178,6 @@ void InkHUD::AlignStickApplet::onNavDown() settings->joystick.aligned = true; sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::AlignStickApplet::onNavLeft() @@ -190,7 +186,6 @@ void InkHUD::AlignStickApplet::onNavLeft() settings->joystick.aligned = true; sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::AlignStickApplet::onNavRight() @@ -199,7 +194,6 @@ void InkHUD::AlignStickApplet::onNavRight() settings->joystick.aligned = true; sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } #endif \ No newline at end of file diff --git a/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.h b/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.h index 8dba33165..7c8d00155 100644 --- a/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.h @@ -23,7 +23,7 @@ class AlignStickApplet : public SystemApplet public: AlignStickApplet(); - void onRender() override; + void onRender(bool full) override; void onForeground() override; void onBackground() override; void onButtonLongPress() override; diff --git a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp index 4f99d99ee..4fd01c348 100644 --- a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp @@ -6,6 +6,8 @@ using namespace NicheGraphics; InkHUD::BatteryIconApplet::BatteryIconApplet() { + alwaysRender = true; // render everytime the screen is updated + // Show at boot, if user has previously enabled the feature if (settings->optionalFeatures.batteryIcon) bringToForeground(); @@ -44,39 +46,29 @@ int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *sta return 0; // Tell Observable to continue informing other observers } -void InkHUD::BatteryIconApplet::onRender() +void InkHUD::BatteryIconApplet::onRender(bool full) { - // Fill entire tile - // - size of icon controlled by size of tile - int16_t l = 0; - int16_t t = 0; - uint16_t w = width(); - int16_t h = height(); - - // Clear the region beneath the tile + // Clear the region beneath the tile, including the border // Most applets are drawing onto an empty frame buffer and don't need to do this // We do need to do this with the battery though, as it is an "overlay" - fillRect(l, t, w, h, WHITE); - - // Vertical centerline - const int16_t m = t + (h / 2); + fillRect(0, 0, width(), height(), WHITE); // ===================== // Draw battery outline // ===================== // Positive terminal "bump" - const int16_t &bumpL = l; - const uint16_t bumpH = h / 2; - const int16_t bumpT = m - (bumpH / 2); constexpr uint16_t bumpW = 2; + const int16_t &bumpL = 1; + const uint16_t bumpH = (height() - 2) / 2; + const int16_t bumpT = (1 + ((height() - 2) / 2)) - (bumpH / 2); fillRect(bumpL, bumpT, bumpW, bumpH, BLACK); // Main body of battery - const int16_t bodyL = bumpL + bumpW; - const int16_t &bodyT = t; - const int16_t &bodyH = h; - const int16_t bodyW = w - bumpW; + const int16_t bodyL = 1 + bumpW; + const int16_t &bodyT = 1; + const int16_t &bodyH = height() - 2; // Handle top/bottom padding + const int16_t bodyW = (width() - 1) - bumpW; // Handle 1px left pad drawRect(bodyL, bodyT, bodyW, bodyH, BLACK); // Erase join between bump and body @@ -87,12 +79,13 @@ void InkHUD::BatteryIconApplet::onRender() // =================== constexpr int16_t slicePad = 2; - const int16_t sliceL = bodyL + slicePad; + int16_t sliceL = bodyL + slicePad; const int16_t sliceT = bodyT + slicePad; const uint16_t sliceH = bodyH - (slicePad * 2); uint16_t sliceW = bodyW - (slicePad * 2); - sliceW = (sliceW * socRounded) / 100; // Apply percentage + sliceW = (sliceW * socRounded) / 100; // Apply percentage + sliceL += ((bodyW - (slicePad * 2)) - sliceW); // Shift slice to the battery's negative terminal, correcting drain direction hatchRegion(sliceL, sliceT, sliceW, sliceH, 2, BLACK); drawRect(sliceL, sliceT, sliceW, sliceH, BLACK); diff --git a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.h b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.h index e5b4172be..ceaf88d7f 100644 --- a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.h @@ -23,7 +23,7 @@ class BatteryIconApplet : public SystemApplet public: BatteryIconApplet(); - void onRender() override; + void onRender(bool full) override; int onPowerStatusUpdate(const meshtastic::Status *status); // Called when new info about battery is available private: diff --git a/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.cpp new file mode 100644 index 000000000..57581d56b --- /dev/null +++ b/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.cpp @@ -0,0 +1,257 @@ +#ifdef MESHTASTIC_INCLUDE_INKHUD +#include "./KeyboardApplet.h" + +using namespace NicheGraphics; + +InkHUD::KeyboardApplet::KeyboardApplet() +{ + // Calculate row widths + for (uint8_t row = 0; row < KBD_ROWS; row++) { + rowWidths[row] = 0; + for (uint8_t col = 0; col < KBD_COLS; col++) + rowWidths[row] += keyWidths[row * KBD_COLS + col]; + } +} + +void InkHUD::KeyboardApplet::onRender(bool full) +{ + uint16_t em = fontSmall.lineHeight(); // 16 pt + uint16_t keyH = Y(1.0) / KBD_ROWS; + int16_t keyTopPadding = (keyH - fontSmall.lineHeight()) / 2; + + if (full) { // Draw full keyboard + for (uint8_t row = 0; row < KBD_ROWS; row++) { + + // Calculate the remaining space to be used as padding + int16_t keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4); + + // Draw keys + uint16_t xPos = 0; + for (uint8_t col = 0; col < KBD_COLS; col++) { + Color fgcolor = BLACK; + uint8_t index = row * KBD_COLS + col; + uint16_t keyX = ((xPos * em) >> 4) + ((col * keyXPadding) / (KBD_COLS - 1)); + uint16_t keyY = row * keyH; + uint16_t keyW = (keyWidths[index] * em) >> 4; + if (index == selectedKey) { + fgcolor = WHITE; + fillRect(keyX, keyY, keyW, keyH, BLACK); + } + drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[index], fgcolor); + xPos += keyWidths[index]; + } + } + } else { // Only draw the difference + if (selectedKey != prevSelectedKey) { + // Draw previously selected key + uint8_t row = prevSelectedKey / KBD_COLS; + int16_t keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4); + uint16_t xPos = 0; + for (uint8_t i = prevSelectedKey - (prevSelectedKey % KBD_COLS); i < prevSelectedKey; i++) + xPos += keyWidths[i]; + uint16_t keyX = ((xPos * em) >> 4) + (((prevSelectedKey % KBD_COLS) * keyXPadding) / (KBD_COLS - 1)); + uint16_t keyY = row * keyH; + uint16_t keyW = (keyWidths[prevSelectedKey] * em) >> 4; + fillRect(keyX, keyY, keyW, keyH, WHITE); + drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[prevSelectedKey], BLACK); + + // Draw newly selected key + row = selectedKey / KBD_COLS; + keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4); + xPos = 0; + for (uint8_t i = selectedKey - (selectedKey % KBD_COLS); i < selectedKey; i++) + xPos += keyWidths[i]; + keyX = ((xPos * em) >> 4) + (((selectedKey % KBD_COLS) * keyXPadding) / (KBD_COLS - 1)); + keyY = row * keyH; + keyW = (keyWidths[selectedKey] * em) >> 4; + fillRect(keyX, keyY, keyW, keyH, BLACK); + drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[selectedKey], WHITE); + } + } + + prevSelectedKey = selectedKey; +} + +// Draw the key label corresponding to the char +// for most keys it draws the character itself +// for ['\b', '\n', ' ', '\x1b'] it draws special glyphs +void InkHUD::KeyboardApplet::drawKeyLabel(uint16_t left, uint16_t top, uint16_t width, char key, Color color) +{ + if (key == '\b') { + // Draw backspace glyph: 13 x 9 px + /** + * [][][][][][][][][] + * [][] [] + * [][] [] [] [] + * [][] [] [] [] + * [][] [] [] + * [][] [] [] [] + * [][] [] [] [] + * [][] [] + * [][][][][][][][][] + */ + const uint8_t bsBitmap[] = {0x0f, 0xf8, 0x18, 0x08, 0x32, 0x28, 0x61, 0x48, 0xc0, + 0x88, 0x61, 0x48, 0x32, 0x28, 0x18, 0x08, 0x0f, 0xf8}; + uint16_t leftPadding = (width - 13) >> 1; + drawBitmap(left + leftPadding, top + 1, bsBitmap, 13, 9, color); + } else if (key == '\n') { + // Draw done glyph: 12 x 9 px + /** + * [][] + * [][] + * [][] + * [][] + * [][] + * [][] [][] + * [][] [][] + * [][][] + * [] + */ + const uint8_t doneBitmap[] = {0x00, 0x30, 0x00, 0x60, 0x00, 0xc0, 0x01, 0x80, 0x03, + 0x00, 0xc6, 0x00, 0x6c, 0x00, 0x38, 0x00, 0x10, 0x00}; + uint16_t leftPadding = (width - 12) >> 1; + drawBitmap(left + leftPadding, top + 1, doneBitmap, 12, 9, color); + } else if (key == ' ') { + // Draw space glyph: 13 x 9 px + /** + * + * + * + * + * [] [] + * [] [] + * [][][][][][][][][][][][][] + * + * + */ + const uint8_t spaceBitmap[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x08, 0x80, 0x08, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00}; + uint16_t leftPadding = (width - 13) >> 1; + drawBitmap(left + leftPadding, top + 1, spaceBitmap, 13, 9, color); + } else if (key == '\x1b') { + setTextColor(color); + std::string keyText = "ESC"; + uint16_t leftPadding = (width - getTextWidth(keyText)) >> 1; + printAt(left + leftPadding, top, keyText); + } else { + setTextColor(color); + if (key >= 0x61) + key -= 32; // capitalize + std::string keyText = std::string(1, key); + uint16_t leftPadding = (width - getTextWidth(keyText)) >> 1; + printAt(left + leftPadding, top, keyText); + } +} + +void InkHUD::KeyboardApplet::onForeground() +{ + handleInput = true; // Intercept the button input for our applet + + // Select the first key + selectedKey = 0; + prevSelectedKey = 0; +} + +void InkHUD::KeyboardApplet::onBackground() +{ + handleInput = false; +} + +void InkHUD::KeyboardApplet::onButtonShortPress() +{ + char key = keys[selectedKey]; + if (key == '\n') { + inkhud->freeTextDone(); + inkhud->closeKeyboard(); + } else if (key == '\x1b') { + inkhud->freeTextCancel(); + inkhud->closeKeyboard(); + } else { + inkhud->freeText(key); + } +} + +void InkHUD::KeyboardApplet::onButtonLongPress() +{ + char key = keys[selectedKey]; + if (key == '\n') { + inkhud->freeTextDone(); + inkhud->closeKeyboard(); + } else if (key == '\x1b') { + inkhud->freeTextCancel(); + inkhud->closeKeyboard(); + } else { + if (key >= 0x61) + key -= 32; // capitalize + inkhud->freeText(key); + } +} + +void InkHUD::KeyboardApplet::onExitShort() +{ + inkhud->freeTextCancel(); + inkhud->closeKeyboard(); +} + +void InkHUD::KeyboardApplet::onExitLong() +{ + inkhud->freeTextCancel(); + inkhud->closeKeyboard(); +} + +void InkHUD::KeyboardApplet::onNavUp() +{ + if (selectedKey < KBD_COLS) // wrap + selectedKey += KBD_COLS * (KBD_ROWS - 1); + else // move 1 row back + selectedKey -= KBD_COLS; + + // Request rendering over the previously drawn render + requestUpdate(EInk::UpdateTypes::FAST, false); + // Force an update to bypass lockRequests + inkhud->forceUpdate(EInk::UpdateTypes::FAST); +} + +void InkHUD::KeyboardApplet::onNavDown() +{ + selectedKey += KBD_COLS; + selectedKey %= (KBD_COLS * KBD_ROWS); + + // Request rendering over the previously drawn render + requestUpdate(EInk::UpdateTypes::FAST, false); + // Force an update to bypass lockRequests + inkhud->forceUpdate(EInk::UpdateTypes::FAST); +} + +void InkHUD::KeyboardApplet::onNavLeft() +{ + if (selectedKey % KBD_COLS == 0) // wrap + selectedKey += KBD_COLS - 1; + else // move 1 column back + selectedKey--; + + // Request rendering over the previously drawn render + requestUpdate(EInk::UpdateTypes::FAST, false); + // Force an update to bypass lockRequests + inkhud->forceUpdate(EInk::UpdateTypes::FAST); +} + +void InkHUD::KeyboardApplet::onNavRight() +{ + if (selectedKey % KBD_COLS == KBD_COLS - 1) // wrap + selectedKey -= KBD_COLS - 1; + else // move 1 column forward + selectedKey++; + + // Request rendering over the previously drawn render + requestUpdate(EInk::UpdateTypes::FAST, false); + // Force an update to bypass lockRequests + inkhud->forceUpdate(EInk::UpdateTypes::FAST); +} + +uint16_t InkHUD::KeyboardApplet::getKeyboardHeight() +{ + const uint16_t keyH = fontSmall.lineHeight() * 1.2; + return keyH * KBD_ROWS; +} +#endif diff --git a/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h b/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h new file mode 100644 index 000000000..306a8d8e3 --- /dev/null +++ b/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h @@ -0,0 +1,66 @@ +#ifdef MESHTASTIC_INCLUDE_INKHUD + +/* + +System Applet to render an on-screeen keyboard + +*/ + +#pragma once + +#include "configuration.h" +#include "graphics/niche/InkHUD/InkHUD.h" +#include "graphics/niche/InkHUD/SystemApplet.h" +#include +namespace NicheGraphics::InkHUD +{ + +class KeyboardApplet : public SystemApplet +{ + public: + KeyboardApplet(); + + void onRender(bool full) override; + void onForeground() override; + void onBackground() override; + void onButtonShortPress() override; + void onButtonLongPress() override; + void onExitShort() override; + void onExitLong() override; + void onNavUp() override; + void onNavDown() override; + void onNavLeft() override; + void onNavRight() override; + + static uint16_t getKeyboardHeight(); // used to set the keyboard tile height + + private: + void drawKeyLabel(uint16_t left, uint16_t top, uint16_t width, char key, Color color); + + static const uint8_t KBD_COLS = 11; + static const uint8_t KBD_ROWS = 4; + + const char keys[KBD_COLS * KBD_ROWS] = { + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '\b', // row 0 + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '\n', // row 1 + 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', '!', ' ', // row 2 + 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '?', '\x1b' // row 3 + }; + + // This array represents the widths of each key in points + // 16 pt = line height of the text + const uint16_t keyWidths[KBD_COLS * KBD_ROWS] = { + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 0 + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 1 + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 2 + 16, 16, 16, 16, 16, 16, 16, 10, 10, 12, 40 // row 3 + }; + + uint16_t rowWidths[KBD_ROWS]; + uint8_t selectedKey = 0; // selected key index + uint8_t prevSelectedKey = 0; +}; + +} // namespace NicheGraphics::InkHUD + +#endif diff --git a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp index ecaa7cea3..b2c58fc60 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp @@ -30,7 +30,7 @@ InkHUD::LogoApplet::LogoApplet() : concurrency::OSThread("LogoApplet") // This is then drawn with a FULL refresh by Renderer::begin } -void InkHUD::LogoApplet::onRender() +void InkHUD::LogoApplet::onRender(bool full) { // Size of the region which the logo should "scale to fit" uint16_t logoWLimit = X(0.8); @@ -120,7 +120,7 @@ void InkHUD::LogoApplet::onBackground() // Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background // Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case - inkhud->forceUpdate(EInk::UpdateTypes::FULL); + inkhud->forceUpdate(EInk::UpdateTypes::FULL, true); } // Begin displaying the screen which is shown at shutdown @@ -138,10 +138,10 @@ void InkHUD::LogoApplet::onShutdown() // Intention is to restore display health. inverted = true; - inkhud->forceUpdate(Drivers::EInk::FULL, false); + inkhud->forceUpdate(Drivers::EInk::FULL, true, false); delay(1000); // Cooldown. Back to back updates aren't great for health. inverted = false; - inkhud->forceUpdate(Drivers::EInk::FULL, false); + inkhud->forceUpdate(Drivers::EInk::FULL, true, false); delay(1000); // Cooldown // Prepare for the powered-off screen now @@ -155,6 +155,18 @@ void InkHUD::LogoApplet::onShutdown() // This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update, after InkHUD's flash write is complete } +void InkHUD::LogoApplet::onApplyingChanges() +{ + bringToForeground(); + + textLeft = ""; + textRight = ""; + textTitle = "Applying changes"; + fontTitle = fontSmall; + + inkhud->forceUpdate(Drivers::EInk::FAST, false); +} + void InkHUD::LogoApplet::onReboot() { bringToForeground(); @@ -164,7 +176,7 @@ void InkHUD::LogoApplet::onReboot() textTitle = "Rebooting..."; fontTitle = fontSmall; - inkhud->forceUpdate(Drivers::EInk::FULL, false); + inkhud->forceUpdate(Drivers::EInk::FULL, true, false); // Perform the update right now, waiting here until complete } diff --git a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h index 3f604baed..d70dcc7b2 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h @@ -21,11 +21,12 @@ class LogoApplet : public SystemApplet, public concurrency::OSThread { public: LogoApplet(); - void onRender() override; + void onRender(bool full) override; void onForeground() override; void onBackground() override; void onShutdown() override; void onReboot() override; + void onApplyingChanges(); protected: int32_t runOnce() override; diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h index debe2b719..7ec76292b 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h @@ -19,6 +19,7 @@ namespace NicheGraphics::InkHUD enum MenuAction { NO_ACTION, SEND_PING, + FREE_TEXT, STORE_CANNEDMESSAGE_SELECTION, SEND_CANNEDMESSAGE, SHUTDOWN, @@ -36,6 +37,84 @@ enum MenuAction { TOGGLE_NOTIFICATIONS, TOGGLE_INVERT_COLOR, TOGGLE_12H_CLOCK, + // Regions + SET_REGION_US, + SET_REGION_EU_868, + SET_REGION_EU_433, + SET_REGION_CN, + SET_REGION_JP, + SET_REGION_ANZ, + SET_REGION_KR, + SET_REGION_TW, + SET_REGION_RU, + SET_REGION_IN, + SET_REGION_NZ_865, + SET_REGION_TH, + SET_REGION_LORA_24, + SET_REGION_UA_433, + SET_REGION_UA_868, + SET_REGION_MY_433, + SET_REGION_MY_919, + SET_REGION_SG_923, + SET_REGION_PH_433, + SET_REGION_PH_868, + SET_REGION_PH_915, + SET_REGION_ANZ_433, + SET_REGION_KZ_433, + SET_REGION_KZ_863, + SET_REGION_NP_865, + SET_REGION_BR_902, + // Device Roles + SET_ROLE_CLIENT, + SET_ROLE_CLIENT_MUTE, + SET_ROLE_ROUTER, + SET_ROLE_REPEATER, + // Presets + SET_PRESET_LONG_SLOW, + SET_PRESET_LONG_MODERATE, + SET_PRESET_LONG_FAST, + SET_PRESET_MEDIUM_SLOW, + SET_PRESET_MEDIUM_FAST, + SET_PRESET_SHORT_SLOW, + SET_PRESET_SHORT_FAST, + SET_PRESET_SHORT_TURBO, + // Timezones + SET_TZ_US_HAWAII, + SET_TZ_US_ALASKA, + SET_TZ_US_PACIFIC, + SET_TZ_US_ARIZONA, + SET_TZ_US_MOUNTAIN, + SET_TZ_US_CENTRAL, + SET_TZ_US_EASTERN, + SET_TZ_BR_BRAZILIA, + SET_TZ_UTC, + SET_TZ_EU_WESTERN, + SET_TZ_EU_CENTRAL, + SET_TZ_EU_EASTERN, + SET_TZ_ASIA_KOLKATA, + SET_TZ_ASIA_HONG_KONG, + SET_TZ_AU_AWST, + SET_TZ_AU_ACST, + SET_TZ_AU_AEST, + SET_TZ_PACIFIC_NZ, + // Power + TOGGLE_POWER_SAVE, + CALIBRATE_ADC, + // Bluetooth + TOGGLE_BLUETOOTH, + TOGGLE_BLUETOOTH_PAIR_MODE, + // Channel + TOGGLE_CHANNEL_UPLINK, + TOGGLE_CHANNEL_DOWNLINK, + TOGGLE_CHANNEL_POSITION, + SET_CHANNEL_PRECISION, + // Display + TOGGLE_DISPLAY_UNITS, + // Network + TOGGLE_WIFI, + // Administration + RESET_NODEDB_ALL, + RESET_NODEDB_KEEP_FAVORITES, }; } // namespace NicheGraphics::InkHUD diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp index 7e7093857..6a141f73e 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp @@ -2,16 +2,21 @@ #include "./MenuApplet.h" -#include "RTC.h" - +#include "DisplayFormatters.h" +#include "GPS.h" #include "MeshService.h" +#include "RTC.h" #include "Router.h" #include "airtime.h" #include "main.h" +#include "mesh/generated/meshtastic/deviceonly.pb.h" #include "power.h" - -#if !MESHTASTIC_EXCLUDE_GPS -#include "GPS.h" +#include +#include +#if defined(ARCH_ESP32) && HAS_WIFI +#include "mesh/wifi/WiFiAPClient.h" +#include +#include #endif using namespace NicheGraphics; @@ -22,6 +27,18 @@ static constexpr uint8_t MENU_TIMEOUT_SEC = 60; // How many seconds before menu // These are offered to users as possible values for settings.recentlyActiveSeconds static constexpr uint8_t RECENTS_OPTIONS_MINUTES[] = {2, 5, 10, 30, 60, 120}; +struct PositionPrecisionOption { + uint8_t value; // proto value + const char *metric; + const char *imperial; +}; + +static constexpr PositionPrecisionOption POSITION_PRECISION_OPTIONS[] = { + {32, "Precise", "Precise"}, {19, "50 m", "150 ft"}, {18, "90 m", "300 ft"}, {17, "200 m", "600 ft"}, + {16, "350 m", "0.2 mi"}, {15, "700 m", "0.5 mi"}, {14, "1.5 km", "0.9 mi"}, {13, "2.9 km", "1.8 mi"}, + {12, "5.8 km", "3.6 mi"}, {11, "12 km", "7.3 mi"}, {10, "23 km", "15 mi"}, +}; + InkHUD::MenuApplet::MenuApplet() : concurrency::OSThread("MenuApplet") { // No timer tasks at boot @@ -45,8 +62,15 @@ void InkHUD::MenuApplet::onForeground() // We do need this before we render, but we can optimize by just calculating it once now systemInfoPanelHeight = getSystemInfoPanelHeight(); - // Display initial menu page - showPage(MenuPage::ROOT); + // Force Region page ONLY when explicitly requested (one-shot) + if (inkhud->forceRegionMenu) { + + inkhud->forceRegionMenu = false; // consume one-shot flag + showPage(MenuPage::REGION); + + } else { + showPage(MenuPage::ROOT); + } // If device has a backlight which isn't controlled by aux button: // backlight on always when menu opens. @@ -66,6 +90,8 @@ void InkHUD::MenuApplet::onForeground() OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); OSThread::enabled = true; + freeTextMode = false; + // Upgrade the refresh to FAST, for guaranteed responsiveness inkhud->forceUpdate(EInk::UpdateTypes::FAST); } @@ -92,6 +118,8 @@ void InkHUD::MenuApplet::onBackground() SystemApplet::lockRequests = false; SystemApplet::handleInput = false; + handleFreeText = false; + // Restore the user applet whose tile we borrowed if (borrowedTileOwner) borrowedTileOwner->bringToForeground(); @@ -139,6 +167,150 @@ int32_t InkHUD::MenuApplet::runOnce() return OSThread::disable(); } +static void applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode region) +{ + if (config.lora.region == region) + return; + + config.lora.region = region; + + auto changes = SEGMENT_CONFIG; + +#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI) + if (!owner.is_licensed) { + bool keygenSuccess = false; + + if (config.security.private_key.size == 32) { + if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) { + keygenSuccess = true; + } + } else { + crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes); + keygenSuccess = true; + } + + if (keygenSuccess) { + config.security.public_key.size = 32; + config.security.private_key.size = 32; + owner.public_key.size = 32; + memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32); + } + } +#endif + + config.lora.tx_enabled = true; + + initRegion(); + + if (myRegion && myRegion->dutyCycle < 100) { + config.lora.ignore_mqtt = true; + } + + if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) { + sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name); + changes |= SEGMENT_MODULECONFIG; + } + // Notify UI that changes are being applied + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); + service->reloadConfig(changes); + + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; +} + +static void applyDeviceRole(meshtastic_Config_DeviceConfig_Role role) +{ + if (config.device.role == role) + return; + + config.device.role = role; + + nodeDB->saveToDisk(SEGMENT_CONFIG); + + service->reloadConfig(SEGMENT_CONFIG); + + // Notify UI that changes are being applied + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); + + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; +} + +static void applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset preset) +{ + if (config.lora.modem_preset == preset) + return; + + config.lora.use_preset = true; + config.lora.modem_preset = preset; + + nodeDB->saveToDisk(SEGMENT_CONFIG); + service->reloadConfig(SEGMENT_CONFIG); + + // Notify UI that changes are being applied + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); + + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; +} + +static const char *getTimezoneLabelFromValue(const char *tzdef) +{ + if (!tzdef || !*tzdef) + return "Unset"; + + // Must match TIMEZONE menu entries + if (strcmp(tzdef, "HST10") == 0) + return "US/Hawaii"; + if (strcmp(tzdef, "AKST9AKDT,M3.2.0,M11.1.0") == 0) + return "US/Alaska"; + if (strcmp(tzdef, "PST8PDT,M3.2.0,M11.1.0") == 0) + return "US/Pacific"; + if (strcmp(tzdef, "MST7") == 0) + return "US/Arizona"; + if (strcmp(tzdef, "MST7MDT,M3.2.0,M11.1.0") == 0) + return "US/Mountain"; + if (strcmp(tzdef, "CST6CDT,M3.2.0,M11.1.0") == 0) + return "US/Central"; + if (strcmp(tzdef, "EST5EDT,M3.2.0,M11.1.0") == 0) + return "US/Eastern"; + if (strcmp(tzdef, "BRT3") == 0) + return "BR/Brasilia"; + if (strcmp(tzdef, "UTC0") == 0) + return "UTC"; + if (strcmp(tzdef, "GMT0BST,M3.5.0/1,M10.5.0") == 0) + return "EU/Western"; + if (strcmp(tzdef, "CET-1CEST,M3.5.0,M10.5.0/3") == 0) + return "EU/Central"; + if (strcmp(tzdef, "EET-2EEST,M3.5.0/3,M10.5.0/4") == 0) + return "EU/Eastern"; + if (strcmp(tzdef, "IST-5:30") == 0) + return "Asia/Kolkata"; + if (strcmp(tzdef, "HKT-8") == 0) + return "Asia/Hong Kong"; + if (strcmp(tzdef, "AWST-8") == 0) + return "AU/AWST"; + if (strcmp(tzdef, "ACST-9:30ACDT,M10.1.0,M4.1.0/3") == 0) + return "AU/ACST"; + if (strcmp(tzdef, "AEST-10AEDT,M10.1.0,M4.1.0/3") == 0) + return "AU/AEST"; + if (strcmp(tzdef, "NZST-12NZDT,M9.5.0,M4.1.0/3") == 0) + return "Pacific/NZ"; + + return tzdef; // fallback for unknown/custom values +} + +static void applyTimezone(const char *tz) +{ + if (!tz || strcmp(config.device.tzdef, tz) == 0) + return; + + strncpy(config.device.tzdef, tz, sizeof(config.device.tzdef)); + config.device.tzdef[sizeof(config.device.tzdef) - 1] = '\0'; + + setenv("TZ", config.device.tzdef, 1); + + nodeDB->saveToDisk(SEGMENT_CONFIG); + service->reloadConfig(SEGMENT_CONFIG); +} + // Perform action for a menu item, then change page // Behaviors for MenuActions are defined here void InkHUD::MenuApplet::execute(MenuItem item) @@ -150,10 +322,18 @@ void InkHUD::MenuApplet::execute(MenuItem item) // Open a submenu without performing any action // Also handles exit case NO_ACTION: + if (currentPage == MenuPage::NODE_CONFIG_CHANNELS && item.nextPage == MenuPage::NODE_CONFIG_CHANNEL_DETAIL) { + + // cursor - 1 because index 0 is "Back" + selectedChannelIndex = cursor - 1; + } break; case NEXT_TILE: inkhud->nextTile(); + // Unselect menu item after tile change + cursorShown = false; + cursor = 0; break; case SEND_PING: @@ -164,12 +344,26 @@ void InkHUD::MenuApplet::execute(MenuItem item) inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL); break; + case FREE_TEXT: + OSThread::enabled = false; + handleFreeText = true; + cm.freeTextItem.rawText.erase(); // clear the previous freetext message + freeTextMode = true; // render input field instead of normal menu + // Open the on-screen keyboard if the joystick is enabled + if (settings->joystick.enabled) + inkhud->openKeyboard(); + break; + case STORE_CANNEDMESSAGE_SELECTION: - cm.selectedMessageItem = &cm.messageItems.at(cursor - 1); // Minus one: offset for the initial "Send Ping" entry + if (!settings->joystick.enabled) + cm.selectedMessageItem = &cm.messageItems.at(cursor - 1); // Minus one: offset for the initial "Send Ping" entry + else + cm.selectedMessageItem = &cm.messageItems.at(cursor - 2); // Minus two: offset for the "Send Ping" and free text entry break; case SEND_CANNEDMESSAGE: cm.selectedRecipientItem = &cm.recipientItems.at(cursor); + // send selected message sendText(cm.selectedRecipientItem->dest, cm.selectedRecipientItem->channelIndex, cm.selectedMessageItem->rawText.c_str()); inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL); // Next refresh should be FULL. Lots of button pressing to get here break; @@ -196,17 +390,23 @@ void InkHUD::MenuApplet::execute(MenuItem item) break; case TOGGLE_APPLET: - settings->userApplets.active[cursor] = !settings->userApplets.active[cursor]; - inkhud->updateAppletSelection(); + if (item.checkState) { + *item.checkState = !(*item.checkState); + inkhud->updateAppletSelection(); + } break; case TOGGLE_AUTOSHOW_APPLET: // Toggle settings.userApplets.autoshow[] value, via MenuItem::checkState pointer set in populateAutoshowPage() - *items.at(cursor).checkState = !(*items.at(cursor).checkState); + if (item.checkState) { + *item.checkState = !(*item.checkState); + } break; case TOGGLE_NOTIFICATIONS: - settings->optionalFeatures.notifications = !settings->optionalFeatures.notifications; + if (item.checkState) { + *item.checkState = !(*item.checkState); + } break; case TOGGLE_INVERT_COLOR: @@ -218,12 +418,14 @@ void InkHUD::MenuApplet::execute(MenuItem item) nodeDB->saveToDisk(SEGMENT_CONFIG); break; - case SET_RECENTS: - // Set value of settings.recentlyActiveSeconds - // Uses menu cursor to read RECENTS_OPTIONS_MINUTES array (defined at top of this file) - assert(cursor < sizeof(RECENTS_OPTIONS_MINUTES) / sizeof(RECENTS_OPTIONS_MINUTES[0])); - settings->recentlyActiveSeconds = RECENTS_OPTIONS_MINUTES[cursor] * 60; // Menu items are in minutes + case SET_RECENTS: { + // cursor - 1 because index 0 is "Back" + const uint8_t index = cursor - 1; + constexpr uint8_t optionCount = sizeof(RECENTS_OPTIONS_MINUTES) / sizeof(RECENTS_OPTIONS_MINUTES[0]); + assert(index < optionCount); + settings->recentlyActiveSeconds = RECENTS_OPTIONS_MINUTES[index] * 60; break; + } case SHUTDOWN: LOG_INFO("Shutting down from menu"); @@ -251,8 +453,18 @@ void InkHUD::MenuApplet::execute(MenuItem item) break; case TOGGLE_GPS: - gps->toggleGpsMode(); +#if !MESHTASTIC_EXCLUDE_GPS && HAS_GPS + if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED) { + config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; + } else if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) { + config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED; + } else { + // NOT_PRESENT do nothing + break; + } nodeDB->saveToDisk(SEGMENT_CONFIG); + service->reloadConfig(SEGMENT_CONFIG); +#endif break; case ENABLE_BLUETOOTH: @@ -260,10 +472,397 @@ void InkHUD::MenuApplet::execute(MenuItem item) LOG_INFO("Enabling Bluetooth"); config.network.wifi_enabled = false; config.bluetooth.enabled = true; - nodeDB->saveToDisk(); + nodeDB->saveToDisk(SEGMENT_CONFIG); + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); rebootAtMsec = millis() + 2000; break; + // Power / Network (ESP32-only) +#if defined(ARCH_ESP32) + case TOGGLE_POWER_SAVE: + config.power.is_power_saving = !config.power.is_power_saving; + nodeDB->saveToDisk(SEGMENT_CONFIG); + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + break; + + case TOGGLE_WIFI: + config.network.wifi_enabled = !config.network.wifi_enabled; + + if (config.network.wifi_enabled) { + // Switch behavior: WiFi ON forces Bluetooth OFF + config.bluetooth.enabled = false; + } + + nodeDB->saveToDisk(SEGMENT_CONFIG); + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + break; +#endif + // ADC Calibration + case CALIBRATE_ADC: { + // Read current measured voltage + float measuredV = powerStatus->getBatteryVoltageMv() / 1000.0f; + + // Sanity check + if (measuredV < 3.0f || measuredV > 4.5f) { + LOG_WARN("ADC calibration aborted, unreasonable voltage: %.2fV", measuredV); + break; + } + + // Determine the base multiplier currently in effect + float baseMult = 0.0f; + + if (config.power.adc_multiplier_override > 0.0f) { + baseMult = config.power.adc_multiplier_override; + } +#ifdef ADC_MULTIPLIER + else { + baseMult = ADC_MULTIPLIER; + } +#endif + + if (baseMult <= 0.0f) { + LOG_WARN("ADC calibration failed: no base multiplier"); + break; + } + + // Target voltage considered 100% by UI + constexpr float TARGET_VOLTAGE = 4.19f; + + // Calculate new multiplier + float newMult = baseMult * (TARGET_VOLTAGE / measuredV); + + config.power.adc_multiplier_override = newMult; + + nodeDB->saveToDisk(SEGMENT_CONFIG); + + LOG_INFO("ADC calibrated: measured=%.3fV base=%.4f new=%.4f", measuredV, baseMult, newMult); + + break; + } + + // Display + case TOGGLE_DISPLAY_UNITS: + if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) + config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_METRIC; + else + config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL; + + nodeDB->saveToDisk(SEGMENT_CONFIG); + break; + + // Bluetooth + case TOGGLE_BLUETOOTH: + config.bluetooth.enabled = !config.bluetooth.enabled; + + if (config.bluetooth.enabled) { + // Switch behavior: Bluetooth ON forces WiFi OFF + config.network.wifi_enabled = false; + } + + nodeDB->saveToDisk(SEGMENT_CONFIG); + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + break; + + case TOGGLE_BLUETOOTH_PAIR_MODE: + config.bluetooth.fixed_pin = !config.bluetooth.fixed_pin; + nodeDB->saveToDisk(SEGMENT_CONFIG); + break; + + // Regions + case SET_REGION_US: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_US); + break; + + case SET_REGION_EU_868: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_EU_868); + break; + + case SET_REGION_EU_433: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_EU_433); + break; + + case SET_REGION_CN: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_CN); + break; + + case SET_REGION_JP: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_JP); + break; + + case SET_REGION_ANZ: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_ANZ); + break; + case SET_REGION_KR: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_KR); + break; + + case SET_REGION_TW: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_TW); + break; + + case SET_REGION_RU: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_RU); + break; + + case SET_REGION_IN: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_IN); + break; + + case SET_REGION_NZ_865: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_NZ_865); + break; + + case SET_REGION_TH: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_TH); + break; + + case SET_REGION_LORA_24: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_LORA_24); + break; + + case SET_REGION_UA_433: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_UA_433); + break; + + case SET_REGION_UA_868: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_UA_868); + break; + + case SET_REGION_MY_433: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_MY_433); + break; + + case SET_REGION_MY_919: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_MY_919); + break; + + case SET_REGION_SG_923: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_SG_923); + break; + + case SET_REGION_PH_433: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_PH_433); + break; + + case SET_REGION_PH_868: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_PH_868); + break; + + case SET_REGION_PH_915: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_PH_915); + break; + + case SET_REGION_ANZ_433: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_ANZ_433); + break; + + case SET_REGION_KZ_433: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_KZ_433); + break; + + case SET_REGION_KZ_863: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_KZ_863); + break; + + case SET_REGION_NP_865: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_NP_865); + break; + + case SET_REGION_BR_902: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_BR_902); + break; + + // Roles + case SET_ROLE_CLIENT: + applyDeviceRole(meshtastic_Config_DeviceConfig_Role_CLIENT); + break; + + case SET_ROLE_CLIENT_MUTE: + applyDeviceRole(meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE); + break; + + case SET_ROLE_ROUTER: + applyDeviceRole(meshtastic_Config_DeviceConfig_Role_ROUTER); + break; + + case SET_ROLE_REPEATER: + applyDeviceRole(meshtastic_Config_DeviceConfig_Role_REPEATER); + break; + + // Presets + case SET_PRESET_LONG_SLOW: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW); + break; + + case SET_PRESET_LONG_MODERATE: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE); + break; + + case SET_PRESET_LONG_FAST: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST); + break; + + case SET_PRESET_MEDIUM_SLOW: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW); + break; + + case SET_PRESET_MEDIUM_FAST: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST); + break; + + case SET_PRESET_SHORT_SLOW: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW); + break; + + case SET_PRESET_SHORT_FAST: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST); + break; + + case SET_PRESET_SHORT_TURBO: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO); + break; + + // Timezones + case SET_TZ_US_HAWAII: + applyTimezone("HST10"); + break; + + case SET_TZ_US_ALASKA: + applyTimezone("AKST9AKDT,M3.2.0,M11.1.0"); + break; + + case SET_TZ_US_PACIFIC: + applyTimezone("PST8PDT,M3.2.0,M11.1.0"); + break; + + case SET_TZ_US_ARIZONA: + applyTimezone("MST7"); + break; + + case SET_TZ_US_MOUNTAIN: + applyTimezone("MST7MDT,M3.2.0,M11.1.0"); + break; + + case SET_TZ_US_CENTRAL: + applyTimezone("CST6CDT,M3.2.0,M11.1.0"); + break; + + case SET_TZ_US_EASTERN: + applyTimezone("EST5EDT,M3.2.0,M11.1.0"); + break; + + case SET_TZ_BR_BRAZILIA: + applyTimezone("BRT3"); + break; + + case SET_TZ_UTC: + applyTimezone("UTC0"); + break; + + case SET_TZ_EU_WESTERN: + applyTimezone("GMT0BST,M3.5.0/1,M10.5.0"); + break; + + case SET_TZ_EU_CENTRAL: + applyTimezone("CET-1CEST,M3.5.0,M10.5.0/3"); + break; + + case SET_TZ_EU_EASTERN: + applyTimezone("EET-2EEST,M3.5.0/3,M10.5.0/4"); + break; + + case SET_TZ_ASIA_KOLKATA: + applyTimezone("IST-5:30"); + break; + + case SET_TZ_ASIA_HONG_KONG: + applyTimezone("HKT-8"); + break; + + case SET_TZ_AU_AWST: + applyTimezone("AWST-8"); + break; + + case SET_TZ_AU_ACST: + applyTimezone("ACST-9:30ACDT,M10.1.0,M4.1.0/3"); + break; + + case SET_TZ_AU_AEST: + applyTimezone("AEST-10AEDT,M10.1.0,M4.1.0/3"); + break; + + case SET_TZ_PACIFIC_NZ: + applyTimezone("NZST-12NZDT,M9.5.0,M4.1.0/3"); + break; + + // Channels + case TOGGLE_CHANNEL_UPLINK: { + auto &ch = channels.getByIndex(selectedChannelIndex); + ch.settings.uplink_enabled = !ch.settings.uplink_enabled; + nodeDB->saveToDisk(SEGMENT_CHANNELS); + service->reloadConfig(SEGMENT_CHANNELS); + break; + } + + case TOGGLE_CHANNEL_DOWNLINK: { + auto &ch = channels.getByIndex(selectedChannelIndex); + ch.settings.downlink_enabled = !ch.settings.downlink_enabled; + nodeDB->saveToDisk(SEGMENT_CHANNELS); + service->reloadConfig(SEGMENT_CHANNELS); + break; + } + + case TOGGLE_CHANNEL_POSITION: { + auto &ch = channels.getByIndex(selectedChannelIndex); + + if (!ch.settings.has_module_settings) + ch.settings.has_module_settings = true; + + if (ch.settings.module_settings.position_precision > 0) + ch.settings.module_settings.position_precision = 0; + else + ch.settings.module_settings.position_precision = 13; // default + + nodeDB->saveToDisk(SEGMENT_CHANNELS); + service->reloadConfig(SEGMENT_CHANNELS); + break; + } + + case SET_CHANNEL_PRECISION: { + auto &ch = channels.getByIndex(selectedChannelIndex); + + if (!ch.settings.has_module_settings) + ch.settings.has_module_settings = true; + + // Cursor - 1 because of "Back" + uint8_t index = cursor - 1; + + constexpr uint8_t optionCount = sizeof(POSITION_PRECISION_OPTIONS) / sizeof(POSITION_PRECISION_OPTIONS[0]); + + if (index < optionCount) { + ch.settings.module_settings.position_precision = POSITION_PRECISION_OPTIONS[index].value; + } + + nodeDB->saveToDisk(SEGMENT_CHANNELS); + service->reloadConfig(SEGMENT_CHANNELS); + break; + } + + case RESET_NODEDB_ALL: + InkHUD::getInstance()->notifyApplyingChanges(); + nodeDB->resetNodes(); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + break; + + case RESET_NODEDB_KEEP_FAVORITES: + InkHUD::getInstance()->notifyApplyingChanges(); + nodeDB->resetNodes(1); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + break; + default: LOG_WARN("Action not implemented"); } @@ -279,9 +878,11 @@ void InkHUD::MenuApplet::showPage(MenuPage page) { items.clear(); items.shrink_to_fit(); + nodeConfigLabels.clear(); switch (page) { case ROOT: + previousPage = MenuPage::EXIT; // Optional: next applet if (settings->optionalMenuItems.nextTile && settings->userTiles.count > 1) items.push_back(MenuItem("Next Tile", MenuAction::NEXT_TILE, MenuPage::ROOT)); // Only if multiple applets shown @@ -289,9 +890,9 @@ void InkHUD::MenuApplet::showPage(MenuPage page) items.push_back(MenuItem("Send", MenuPage::SEND)); items.push_back(MenuItem("Options", MenuPage::OPTIONS)); // items.push_back(MenuItem("Display Off", MenuPage::EXIT)); // TODO + items.push_back(MenuItem("Node Config", MenuPage::NODE_CONFIG)); items.push_back(MenuItem("Save & Shut Down", MenuAction::SHUTDOWN)); items.push_back(MenuItem("Exit", MenuPage::EXIT)); - previousPage = MenuPage::EXIT; break; case SEND: @@ -301,10 +902,12 @@ void InkHUD::MenuApplet::showPage(MenuPage page) case CANNEDMESSAGE_RECIPIENT: populateRecipientPage(); - previousPage = MenuPage::OPTIONS; + previousPage = MenuPage::SEND; break; case OPTIONS: + previousPage = MenuPage::ROOT; + items.push_back(MenuItem("Back", previousPage)); // Optional: backlight if (settings->optionalMenuItems.backlight) items.push_back(MenuItem(backlight->isLatched() ? "Backlight Off" : "Keep Backlight On", // Label @@ -312,16 +915,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page) MenuPage::EXIT // Exit once complete )); - // Optional: GPS - if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED) - items.push_back(MenuItem("Enable GPS", MenuAction::TOGGLE_GPS, MenuPage::EXIT)); - if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) - items.push_back(MenuItem("Disable GPS", MenuAction::TOGGLE_GPS, MenuPage::EXIT)); - - // Optional: Enable Bluetooth, in case of lost wifi connection - if (!config.bluetooth.enabled || config.network.wifi_enabled) - items.push_back(MenuItem("Enable Bluetooth", MenuAction::ENABLE_BLUETOOTH, MenuPage::EXIT)); - + // Options Toggles items.push_back(MenuItem("Applets", MenuPage::APPLETS)); items.push_back(MenuItem("Auto-show", MenuPage::AUTOSHOW)); items.push_back(MenuItem("Recents Duration", MenuPage::RECENTS)); @@ -334,33 +928,438 @@ void InkHUD::MenuApplet::showPage(MenuPage page) &settings->optionalFeatures.notifications)); items.push_back(MenuItem("Battery Icon", MenuAction::TOGGLE_BATTERY_ICON, MenuPage::OPTIONS, &settings->optionalFeatures.batteryIcon)); - invertedColors = (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED); items.push_back(MenuItem("Invert Color", MenuAction::TOGGLE_INVERT_COLOR, MenuPage::OPTIONS, &invertedColors)); - - items.push_back( - MenuItem("12-Hour Clock", MenuAction::TOGGLE_12H_CLOCK, MenuPage::OPTIONS, &config.display.use_12h_clock)); items.push_back(MenuItem("Exit", MenuPage::EXIT)); - previousPage = MenuPage::ROOT; break; case APPLETS: - populateAppletPage(); - items.push_back(MenuItem("Exit", MenuPage::EXIT)); previousPage = MenuPage::OPTIONS; + populateAppletPage(); // must be first + items.insert(items.begin(), MenuItem("Back", previousPage)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); break; case AUTOSHOW: - populateAutoshowPage(); - items.push_back(MenuItem("Exit", MenuPage::EXIT)); previousPage = MenuPage::OPTIONS; + populateAutoshowPage(); // must be first + items.insert(items.begin(), MenuItem("Back", previousPage)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); break; case RECENTS: - populateRecentsPage(); previousPage = MenuPage::OPTIONS; + populateRecentsPage(); // builds only the options + items.insert(items.begin(), MenuItem("Back", previousPage)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); break; + case NODE_CONFIG: + previousPage = MenuPage::ROOT; + items.push_back(MenuItem("Back", previousPage)); + // Radio Config Section + items.push_back(MenuItem::Header("Radio Config")); + items.push_back(MenuItem("LoRa", MenuPage::NODE_CONFIG_LORA)); + items.push_back(MenuItem("Channel", MenuPage::NODE_CONFIG_CHANNELS)); + // Device Config Section + items.push_back(MenuItem::Header("Device Config")); + items.push_back(MenuItem("Device", MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("Position", MenuPage::NODE_CONFIG_POSITION)); + items.push_back(MenuItem("Power", MenuPage::NODE_CONFIG_POWER)); +#if defined(ARCH_ESP32) + items.push_back(MenuItem("Network", MenuPage::NODE_CONFIG_NETWORK)); +#endif + items.push_back(MenuItem("Display", MenuPage::NODE_CONFIG_DISPLAY)); + items.push_back(MenuItem("Bluetooth", MenuPage::NODE_CONFIG_BLUETOOTH)); + + // Administration Section + items.push_back(MenuItem::Header("Administration")); + items.push_back(MenuItem("Reset NodeDB", MenuPage::NODE_CONFIG_ADMIN_RESET)); + + // Exit + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + + case NODE_CONFIG_DEVICE: { + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); + + const char *role = DisplayFormatters::getDeviceRole(config.device.role); + nodeConfigLabels.emplace_back("Role: " + std::string(role)); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_DEVICE_ROLE)); + + const char *tzLabel = getTimezoneLabelFromValue(config.device.tzdef); + nodeConfigLabels.emplace_back("Timezone: " + std::string(tzLabel)); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::TIMEZONE)); + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_POSITION: { + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); +#if !MESHTASTIC_EXCLUDE_GPS && HAS_GPS + const auto mode = config.position.gps_mode; + if (mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) { + items.push_back(MenuItem("GPS None", MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_POSITION)); + } else { + gpsEnabled = (mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED); + items.push_back(MenuItem("GPS", MenuAction::TOGGLE_GPS, MenuPage::NODE_CONFIG_POSITION, &gpsEnabled)); + } +#endif + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_POWER: { + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); +#if defined(ARCH_ESP32) + items.push_back(MenuItem("Powersave", MenuAction::TOGGLE_POWER_SAVE, MenuPage::EXIT, &config.power.is_power_saving)); +#endif + // ADC Multiplier + float effectiveMult = 0.0f; + + // User override always shows if it exists + if (config.power.adc_multiplier_override > 0.0f) { + effectiveMult = config.power.adc_multiplier_override; + } +#ifdef ADC_MULTIPLIER + else { + // Fallback to variant defined + effectiveMult = ADC_MULTIPLIER; + } +#endif + + // Only show if we actually have a value + if (effectiveMult > 0.0f) { + char buf[32]; + snprintf(buf, sizeof(buf), "ADC Mult: %.3f", effectiveMult); + nodeConfigLabels.emplace_back(buf); + + items.push_back( + MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_POWER_ADC_CAL)); + } + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_POWER_ADC_CAL: { + previousPage = MenuPage::NODE_CONFIG_POWER; + items.push_back(MenuItem("Back", previousPage)); + + // Instruction text (header-style, non-selectable) + items.push_back(MenuItem::Header("Run on full charge Only")); + + // Action + items.push_back(MenuItem("Calibrate ADC", MenuAction::CALIBRATE_ADC, MenuPage::NODE_CONFIG_POWER)); + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_NETWORK: { + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); + + const char *wifiLabel = config.network.wifi_enabled ? "WiFi: On" : "WiFi: Off"; + + items.push_back(MenuItem(wifiLabel, MenuAction::TOGGLE_WIFI, MenuPage::EXIT)); + +#if HAS_WIFI && defined(ARCH_ESP32) + if (config.network.wifi_enabled) { + + // Status + if (WiFi.status() == WL_CONNECTED) { + nodeConfigLabels.emplace_back("Status: Connected"); + } else { + nodeConfigLabels.emplace_back("Status: Not Connected"); + } + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_NETWORK)); + + // Signal + if (WiFi.status() == WL_CONNECTED) { + int rssi = WiFi.RSSI(); + int quality = constrain(2 * (rssi + 100), 0, 100); + + char sigBuf[32]; + snprintf(sigBuf, sizeof(sigBuf), "Signal: %d%%", quality); + nodeConfigLabels.emplace_back(sigBuf); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_NETWORK)); + + char ipBuf[64]; + snprintf(ipBuf, sizeof(ipBuf), "IP: %s", WiFi.localIP().toString().c_str()); + nodeConfigLabels.emplace_back(ipBuf); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_NETWORK)); + } + + // SSID + if (config.network.wifi_ssid && strlen(config.network.wifi_ssid) > 0) { + std::string ssidLabel = "SSID: "; + ssidLabel += config.network.wifi_ssid; + nodeConfigLabels.emplace_back(ssidLabel); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_NETWORK)); + } + + // Hostname + const char *host = WiFi.getHostname(); + if (host && strlen(host) > 0) { + std::string hostLabel = "Host: "; + hostLabel += host; + nodeConfigLabels.emplace_back(hostLabel); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_NETWORK)); + } + } +#endif + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_DISPLAY: { + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); + + items.push_back(MenuItem("12-Hour Clock", MenuAction::TOGGLE_12H_CLOCK, MenuPage::NODE_CONFIG_DISPLAY, + &config.display.use_12h_clock)); + + const char *unitsLabel = + (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) ? "Units: Imperial" : "Units: Metric"; + + items.push_back(MenuItem(unitsLabel, MenuAction::TOGGLE_DISPLAY_UNITS, MenuPage::NODE_CONFIG_DISPLAY)); + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_BLUETOOTH: { + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); + + const char *btLabel = config.bluetooth.enabled ? "Bluetooth: On" : "Bluetooth: Off"; + items.push_back(MenuItem(btLabel, MenuAction::TOGGLE_BLUETOOTH, MenuPage::EXIT)); + + const char *pairLabel = config.bluetooth.fixed_pin ? "Pair Mode: Fixed" : "Pair Mode: Random"; + items.push_back(MenuItem(pairLabel, MenuAction::TOGGLE_BLUETOOTH_PAIR_MODE, MenuPage::NODE_CONFIG_BLUETOOTH)); + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_LORA: { + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); + + const char *region = myRegion ? myRegion->name : "Unset"; + nodeConfigLabels.emplace_back("Region: " + std::string(region)); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::REGION)); + + const char *preset = + DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset); + nodeConfigLabels.emplace_back("Preset: " + std::string(preset)); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_PRESET)); + + char freqBuf[32]; + float freq = RadioLibInterface::instance->getFreq(); + snprintf(freqBuf, sizeof(freqBuf), "Freq: %.3f MHz", freq); + nodeConfigLabels.emplace_back(freqBuf); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_LORA)); + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_CHANNELS: { + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); + + for (uint8_t i = 0; i < MAX_NUM_CHANNELS; i++) { + meshtastic_Channel &ch = channels.getByIndex(i); + + if (!ch.has_settings) + continue; + + if (ch.role == meshtastic_Channel_Role_DISABLED) + continue; + + std::string label = "#"; + + if (ch.role == meshtastic_Channel_Role_PRIMARY) { + label += "Primary"; + } else if (strlen(ch.settings.name) > 0) { + label += parse(ch.settings.name); + } else { + label += "Channel" + to_string(i + 1); + } + + nodeConfigLabels.push_back(label); + items.push_back( + MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); + } + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_CHANNEL_DETAIL: { + previousPage = MenuPage::NODE_CONFIG_CHANNELS; + items.push_back(MenuItem("Back", previousPage)); + + meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex); + + // Name (read-only) + const char *name = strlen(ch.settings.name) > 0 ? ch.settings.name : "Unnamed"; + nodeConfigLabels.emplace_back("Ch: " + parse(name)); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); + + // Uplink + items.push_back(MenuItem("Uplink", MenuAction::TOGGLE_CHANNEL_UPLINK, MenuPage::NODE_CONFIG_CHANNEL_DETAIL, + &ch.settings.uplink_enabled)); + + items.push_back(MenuItem("Downlink", MenuAction::TOGGLE_CHANNEL_DOWNLINK, MenuPage::NODE_CONFIG_CHANNEL_DETAIL, + &ch.settings.downlink_enabled)); + + // Position + channelPositionEnabled = ch.settings.has_module_settings && ch.settings.module_settings.position_precision > 0; + + items.push_back(MenuItem("Position", MenuAction::TOGGLE_CHANNEL_POSITION, MenuPage::NODE_CONFIG_CHANNEL_DETAIL, + &channelPositionEnabled)); + + // Precision + if (channelPositionEnabled) { + + std::string precisionLabel = "Unknown"; + + for (const auto &opt : POSITION_PRECISION_OPTIONS) { + if (opt.value == ch.settings.module_settings.position_precision) { + precisionLabel = (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) + ? opt.imperial + : opt.metric; + break; + } + } + nodeConfigLabels.emplace_back("Precision: " + precisionLabel); + items.push_back( + MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_CHANNEL_PRECISION)); + } + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_CHANNEL_PRECISION: { + previousPage = MenuPage::NODE_CONFIG_CHANNEL_DETAIL; + items.push_back(MenuItem("Back", previousPage)); + meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex); + if (!ch.settings.has_module_settings || ch.settings.module_settings.position_precision == 0) { + items.push_back(MenuItem("Position is Off", MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); + break; + } + constexpr uint8_t optionCount = sizeof(POSITION_PRECISION_OPTIONS) / sizeof(POSITION_PRECISION_OPTIONS[0]); + for (uint8_t i = 0; i < optionCount; i++) { + const auto &opt = POSITION_PRECISION_OPTIONS[i]; + const char *label = + (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) ? opt.imperial : opt.metric; + nodeConfigLabels.emplace_back(label); + + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::SET_CHANNEL_PRECISION, + MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); + } + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_DEVICE_ROLE: { + previousPage = MenuPage::NODE_CONFIG_DEVICE; + items.push_back(MenuItem("Back", previousPage)); + items.push_back(MenuItem("Client", MenuAction::SET_ROLE_CLIENT, MenuPage::EXIT)); + items.push_back(MenuItem("Client Mute", MenuAction::SET_ROLE_CLIENT_MUTE, MenuPage::EXIT)); + items.push_back(MenuItem("Router", MenuAction::SET_ROLE_ROUTER, MenuPage::EXIT)); + items.push_back(MenuItem("Repeater", MenuAction::SET_ROLE_REPEATER, MenuPage::EXIT)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case TIMEZONE: + previousPage = MenuPage::NODE_CONFIG_DEVICE; + items.push_back(MenuItem("Back", previousPage)); + items.push_back(MenuItem("US/Hawaii", SET_TZ_US_HAWAII, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Alaska", SET_TZ_US_ALASKA, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Pacific", SET_TZ_US_PACIFIC, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Arizona", SET_TZ_US_ARIZONA, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Mountain", SET_TZ_US_MOUNTAIN, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Central", SET_TZ_US_CENTRAL, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Eastern", SET_TZ_US_EASTERN, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("BR/Brasilia", SET_TZ_BR_BRAZILIA, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("UTC", SET_TZ_UTC, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("EU/Western", SET_TZ_EU_WESTERN, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("EU/Central", SET_TZ_EU_CENTRAL, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("EU/Eastern", SET_TZ_EU_EASTERN, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("Asia/Kolkata", SET_TZ_ASIA_KOLKATA, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("Asia/Hong Kong", SET_TZ_ASIA_HONG_KONG, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("AU/AWST", SET_TZ_AU_AWST, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("AU/ACST", SET_TZ_AU_ACST, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("AU/AEST", SET_TZ_AU_AEST, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + + case REGION: + previousPage = MenuPage::NODE_CONFIG_LORA; + items.push_back(MenuItem("Back", previousPage)); + items.push_back(MenuItem("US", MenuAction::SET_REGION_US, MenuPage::EXIT)); + items.push_back(MenuItem("EU 868", MenuAction::SET_REGION_EU_868, MenuPage::EXIT)); + items.push_back(MenuItem("EU 433", MenuAction::SET_REGION_EU_433, MenuPage::EXIT)); + items.push_back(MenuItem("CN", MenuAction::SET_REGION_CN, MenuPage::EXIT)); + items.push_back(MenuItem("JP", MenuAction::SET_REGION_JP, MenuPage::EXIT)); + items.push_back(MenuItem("ANZ", MenuAction::SET_REGION_ANZ, MenuPage::EXIT)); + items.push_back(MenuItem("KR", MenuAction::SET_REGION_KR, MenuPage::EXIT)); + items.push_back(MenuItem("TW", MenuAction::SET_REGION_TW, MenuPage::EXIT)); + items.push_back(MenuItem("RU", MenuAction::SET_REGION_RU, MenuPage::EXIT)); + items.push_back(MenuItem("IN", MenuAction::SET_REGION_IN, MenuPage::EXIT)); + items.push_back(MenuItem("NZ 865", MenuAction::SET_REGION_NZ_865, MenuPage::EXIT)); + items.push_back(MenuItem("TH", MenuAction::SET_REGION_TH, MenuPage::EXIT)); + items.push_back(MenuItem("LoRa 2.4", MenuAction::SET_REGION_LORA_24, MenuPage::EXIT)); + items.push_back(MenuItem("UA 433", MenuAction::SET_REGION_UA_433, MenuPage::EXIT)); + items.push_back(MenuItem("UA 868", MenuAction::SET_REGION_UA_868, MenuPage::EXIT)); + items.push_back(MenuItem("MY 433", MenuAction::SET_REGION_MY_433, MenuPage::EXIT)); + items.push_back(MenuItem("MY 919", MenuAction::SET_REGION_MY_919, MenuPage::EXIT)); + items.push_back(MenuItem("SG 923", MenuAction::SET_REGION_SG_923, MenuPage::EXIT)); + items.push_back(MenuItem("PH 433", MenuAction::SET_REGION_PH_433, MenuPage::EXIT)); + items.push_back(MenuItem("PH 868", MenuAction::SET_REGION_PH_868, MenuPage::EXIT)); + items.push_back(MenuItem("PH 915", MenuAction::SET_REGION_PH_915, MenuPage::EXIT)); + items.push_back(MenuItem("ANZ 433", MenuAction::SET_REGION_ANZ_433, MenuPage::EXIT)); + items.push_back(MenuItem("KZ 433", MenuAction::SET_REGION_KZ_433, MenuPage::EXIT)); + items.push_back(MenuItem("KZ 863", MenuAction::SET_REGION_KZ_863, MenuPage::EXIT)); + items.push_back(MenuItem("NP 865", MenuAction::SET_REGION_NP_865, MenuPage::EXIT)); + items.push_back(MenuItem("BR 902", MenuAction::SET_REGION_BR_902, MenuPage::EXIT)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + + case NODE_CONFIG_PRESET: { + previousPage = MenuPage::NODE_CONFIG_LORA; + items.push_back(MenuItem("Back", previousPage)); + items.push_back(MenuItem("Long Moderate", MenuAction::SET_PRESET_LONG_MODERATE, MenuPage::EXIT)); + items.push_back(MenuItem("Long Fast", MenuAction::SET_PRESET_LONG_FAST, MenuPage::EXIT)); + items.push_back(MenuItem("Medium Slow", MenuAction::SET_PRESET_MEDIUM_SLOW, MenuPage::EXIT)); + items.push_back(MenuItem("Medium Fast", MenuAction::SET_PRESET_MEDIUM_FAST, MenuPage::EXIT)); + items.push_back(MenuItem("Short Slow", MenuAction::SET_PRESET_SHORT_SLOW, MenuPage::EXIT)); + items.push_back(MenuItem("Short Fast", MenuAction::SET_PRESET_SHORT_FAST, MenuPage::EXIT)); + items.push_back(MenuItem("Short Turbo", MenuAction::SET_PRESET_SHORT_TURBO, MenuPage::EXIT)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + // Administration Section + case NODE_CONFIG_ADMIN_RESET: + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); + items.push_back(MenuItem("Reset All", MenuAction::RESET_NODEDB_ALL, MenuPage::EXIT)); + items.push_back(MenuItem("Keep Favorites Only", MenuAction::RESET_NODEDB_KEEP_FAVORITES, MenuPage::EXIT)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + + // Exit case EXIT: sendToBackground(); // Menu applet dismissed, allow normal behavior to resume break; @@ -379,18 +1378,34 @@ void InkHUD::MenuApplet::showPage(MenuPage page) cursorShown = false; } + // Ensure cursor never rests on a header + if (cursorShown) { + while (cursor < items.size() && items.at(cursor).isHeader) { + cursor++; + } + if (cursor >= items.size()) + cursor = 0; + } + // Remember which page we are on now currentPage = page; } -void InkHUD::MenuApplet::onRender() +void InkHUD::MenuApplet::onRender(bool full) { + // Free text mode draws a text input field and skips the normal rendering + if (freeTextMode) { + drawInputField(0, fontSmall.lineHeight(), X(1.0), Y(1.0) - fontSmall.lineHeight() - 1, cm.freeTextItem.rawText); + return; + } + if (items.size() == 0) LOG_ERROR("Empty Menu"); // Dimensions for the slots where we will draw menuItems const float padding = 0.05; - const uint16_t itemH = fontSmall.lineHeight() * 2; + const uint16_t itemH = fontSmall.lineHeight() * 1.6; + const int16_t selectInsetY = 2; const int16_t itemW = width() - X(padding) - X(padding); const int16_t itemL = X(padding); const int16_t itemR = X(1 - padding); @@ -441,18 +1456,31 @@ void InkHUD::MenuApplet::onRender() // -- Loop: draw each (visible) menu item -- for (uint8_t i = firstItem; i <= lastItem; i++) { - // Grab the menuItem - MenuItem item = items.at(i); - // Center-line for the text + // Grab the menu item + MenuItem &item = items.at(i); + + // Vertical center of this slot int16_t center = itemT + (itemH / 2); - // Box, if currently selected - if (cursorShown && i == cursor) - drawRect(itemL, itemT, itemW, itemH, BLACK); + // Header (non-selectable section label) + if (item.isHeader) { + setFont(fontSmall); - // Item's text - printAt(itemL + X(padding), center, item.label, LEFT, MIDDLE); + // Header text (flush left) + printAt(itemL + X(padding), center, item.label, LEFT, MIDDLE); + + // Subtle underline + int16_t underlineY = itemT + itemH - 2; + drawLine(itemL + X(padding), underlineY, itemR - X(padding), underlineY, BLACK); + } else { + // Box, if currently selected + if (cursorShown && i == cursor) + drawRect(itemL, itemT + selectInsetY, itemW, itemH - (selectInsetY * 2), BLACK); + + // Indented normal item text + printAt(itemL + X(padding * 2), center, item.label, LEFT, MIDDLE); + } // Checkbox, if relevant if (item.checkState) { @@ -489,41 +1517,48 @@ void InkHUD::MenuApplet::onRender() void InkHUD::MenuApplet::onButtonShortPress() { - // Push the auto-close timer back - OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (!freeTextMode) { + // Push the auto-close timer back + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); - if (!settings->joystick.enabled) { - // Move menu cursor to next entry, then update - if (cursorShown) - cursor = (cursor + 1) % items.size(); - else - cursorShown = true; - requestUpdate(Drivers::EInk::UpdateTypes::FAST); - } else { - if (cursorShown) - execute(items.at(cursor)); - else - showPage(MenuPage::EXIT); - if (!wantsToRender()) + if (!settings->joystick.enabled) { + if (!cursorShown) { + cursorShown = true; + cursor = 0; + } else { + do { + cursor = (cursor + 1) % items.size(); + } while (items.at(cursor).isHeader); + } requestUpdate(Drivers::EInk::UpdateTypes::FAST); + } else { + if (cursorShown) + execute(items.at(cursor)); + else + showPage(MenuPage::EXIT); + if (!wantsToRender()) + requestUpdate(Drivers::EInk::UpdateTypes::FAST); + } } } void InkHUD::MenuApplet::onButtonLongPress() { - // Push the auto-close timer back - OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (!freeTextMode) { + // Push the auto-close timer back + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); - if (cursorShown) - execute(items.at(cursor)); - else - showPage(MenuPage::EXIT); // Special case: Peek at root-menu; longpress again to close + if (cursorShown) + execute(items.at(cursor)); + else + showPage(MenuPage::EXIT); // Special case: Peek at root-menu; longpress again to close - // If we didn't already request a specialized update, when handling a menu action, - // then perform the usual fast update. - // FAST keeps things responsive: important because we're dealing with user input - if (!wantsToRender()) - requestUpdate(Drivers::EInk::UpdateTypes::FAST); + // If we didn't already request a specialized update, when handling a menu action, + // then perform the usual fast update. + // FAST keeps things responsive: important because we're dealing with user input + if (!wantsToRender()) + requestUpdate(Drivers::EInk::UpdateTypes::FAST); + } } void InkHUD::MenuApplet::onExitShort() @@ -536,50 +1571,107 @@ void InkHUD::MenuApplet::onExitShort() void InkHUD::MenuApplet::onNavUp() { - OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (!freeTextMode) { + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); - // Move menu cursor to previous entry, then update - if (cursor == 0) - cursor = items.size() - 1; - else - cursor--; + if (!cursorShown) { + cursorShown = true; + cursor = 0; + } else { + do { + if (cursor == 0) + cursor = items.size() - 1; + else + cursor--; + } while (items.at(cursor).isHeader); + } - if (!cursorShown) - cursorShown = true; - - requestUpdate(Drivers::EInk::UpdateTypes::FAST); + requestUpdate(Drivers::EInk::UpdateTypes::FAST); + } } void InkHUD::MenuApplet::onNavDown() { - OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (!freeTextMode) { + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); - // Move menu cursor to next entry, then update - if (cursorShown) - cursor = (cursor + 1) % items.size(); - else - cursorShown = true; + if (!cursorShown) { + cursorShown = true; + cursor = 0; + } else { + do { + cursor = (cursor + 1) % items.size(); + } while (items.at(cursor).isHeader); + } - requestUpdate(Drivers::EInk::UpdateTypes::FAST); + requestUpdate(Drivers::EInk::UpdateTypes::FAST); + } } void InkHUD::MenuApplet::onNavLeft() { - OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (!freeTextMode) { + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); - // Go to the previous menu page - showPage(previousPage); - requestUpdate(Drivers::EInk::UpdateTypes::FAST); + // Go to the previous menu page + showPage(previousPage); + requestUpdate(Drivers::EInk::UpdateTypes::FAST); + } } void InkHUD::MenuApplet::onNavRight() { - OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (!freeTextMode) { + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (cursorShown) + execute(items.at(cursor)); + if (!wantsToRender()) + requestUpdate(Drivers::EInk::UpdateTypes::FAST); + } +} - if (cursorShown) - execute(items.at(cursor)); - if (!wantsToRender()) - requestUpdate(Drivers::EInk::UpdateTypes::FAST); +void InkHUD::MenuApplet::onFreeText(char c) +{ + if (cm.freeTextItem.rawText.length() >= menuTextLimit && c != '\b') + return; + if (c == '\b') { + if (!cm.freeTextItem.rawText.empty()) + cm.freeTextItem.rawText.pop_back(); + } else { + cm.freeTextItem.rawText += c; + } + requestUpdate(Drivers::EInk::UpdateTypes::FAST); +} + +void InkHUD::MenuApplet::onFreeTextDone() +{ + // Restart the auto-close timeout + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + OSThread::enabled = true; + + handleFreeText = false; + freeTextMode = false; + + if (!cm.freeTextItem.rawText.empty()) { + cm.selectedMessageItem = &cm.freeTextItem; + showPage(MenuPage::CANNEDMESSAGE_RECIPIENT); + } + requestUpdate(Drivers::EInk::UpdateTypes::FAST); +} + +void InkHUD::MenuApplet::onFreeTextCancel() +{ + // Restart the auto-close timeout + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + OSThread::enabled = true; + + handleFreeText = false; + freeTextMode = false; + + // Clear the free text message + cm.freeTextItem.rawText.erase(); + + requestUpdate(Drivers::EInk::UpdateTypes::FAST); } // Dynamically create MenuItem entries for activating / deactivating Applets, for the "Applet Selection" submenu @@ -622,7 +1714,8 @@ void InkHUD::MenuApplet::populateRecentsPage() // (Defined at top of this file) for (uint8_t i = 0; i < optionCount; i++) { std::string label = to_string(RECENTS_OPTIONS_MINUTES[i]) + " mins"; - items.push_back(MenuItem(label.c_str(), MenuAction::SET_RECENTS, MenuPage::EXIT)); + recentsSelected[i] = (settings->recentlyActiveSeconds == RECENTS_OPTIONS_MINUTES[i] * 60); + items.push_back(MenuItem(label.c_str(), MenuAction::SET_RECENTS, MenuPage::OPTIONS, &recentsSelected[i])); } } @@ -633,6 +1726,10 @@ void InkHUD::MenuApplet::populateSendPage() // Position / NodeInfo packet items.push_back(MenuItem("Ping", MenuAction::SEND_PING, MenuPage::EXIT)); + // If joystick is available, include the Free Text option + if (settings->joystick.enabled) + items.push_back(MenuItem("Free Text", MenuAction::FREE_TEXT, MenuPage::SEND)); + // One menu item for each canned message uint8_t count = cm.store->size(); for (uint8_t i = 0; i < count; i++) { @@ -732,6 +1829,48 @@ void InkHUD::MenuApplet::populateRecipientPage() items.push_back(MenuItem("Exit", MenuPage::EXIT)); } +void InkHUD::MenuApplet::drawInputField(uint16_t left, uint16_t top, uint16_t width, uint16_t height, std::string text) +{ + setFont(fontSmall); + uint16_t wrapMaxH = 0; + + // Draw the text, input box, and cursor + // Adjusting the box for screen height + while (wrapMaxH < height - fontSmall.lineHeight()) { + wrapMaxH += fontSmall.lineHeight(); + } + + // If the text is so long that it goes outside of the input box, the text is actually rendered off screen. + uint32_t textHeight = getWrappedTextHeight(0, width - 5, text); + if (!text.empty()) { + uint16_t textPadding = X(1.0) > Y(1.0) ? wrapMaxH - textHeight : wrapMaxH - textHeight + 1; + if (textHeight > wrapMaxH) + printWrapped(2, textPadding, width - 5, text); + else + printWrapped(2, top + 2, width - 5, text); + } + + uint16_t textCursorX = text.empty() ? 1 : getCursorX(); + uint16_t textCursorY = text.empty() ? fontSmall.lineHeight() + 2 : getCursorY() - fontSmall.lineHeight() + 3; + + if (textCursorX + 1 > width - 5) { + textCursorX = getCursorX() - width + 5; + textCursorY += fontSmall.lineHeight(); + } + + fillRect(textCursorX + 1, textCursorY, 1, fontSmall.lineHeight(), BLACK); + + // A white rectangle clears the top part of the screen for any text that's printed beyond the input box + fillRect(0, 0, X(1.0), top, WHITE); + + // Draw character limit + std::string ftlen = std::to_string(text.length()) + "/" + to_string(menuTextLimit); + uint16_t textLen = getTextWidth(ftlen); + printAt(X(1.0) - textLen - 2, 0, ftlen); + + // Draw the border + drawRect(0, top, width, wrapMaxH + 5, BLACK); +} // Renders the panel shown at the top of the root menu. // Displays the clock, and several other pieces of instantaneous system info, // which we'd prefer not to have displayed in a normal applet, as they update too frequently. @@ -873,5 +2012,4 @@ void InkHUD::MenuApplet::freeCannedMessageResources() cm.messageItems.clear(); cm.recipientItems.clear(); } - -#endif +#endif // MESHTASTIC_INCLUDE_INKHUD diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h index 4f9f92227..7b092153b 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h @@ -32,9 +32,13 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread void onNavDown() override; void onNavLeft() override; void onNavRight() override; - void onRender() override; + void onFreeText(char c) override; + void onFreeTextDone() override; + void onFreeTextCancel() override; + void onRender(bool full) override; void show(Tile *t); // Open the menu, onto a user tile + void setStartPage(MenuPage page); protected: Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton @@ -50,20 +54,32 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread void populateAutoshowPage(); // Dynamically create MenuItems for selecting which applets can autoshow void populateRecentsPage(); // Create menu items: a choice of values for settings.recentlyActiveSeconds + void drawInputField(uint16_t left, uint16_t top, uint16_t width, uint16_t height, + std::string text); // Draw input field for free text uint16_t getSystemInfoPanelHeight(); void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width, uint16_t *height = nullptr); // Info panel at top of root menu void sendText(NodeNum dest, ChannelIndex channel, const char *message); // Send a text message to mesh void freeCannedMessageResources(); // Clear MenuApplet's canned message processing data + MenuPage startPageOverride = MenuPage::ROOT; MenuPage currentPage = MenuPage::ROOT; MenuPage previousPage = MenuPage::EXIT; uint8_t cursor = 0; // Which menu item is currently highlighted bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection) - + bool freeTextMode = false; uint16_t systemInfoPanelHeight = 0; // Need to know before we render + uint16_t menuTextLimit = 200; - std::vector items; // MenuItems for the current page. Filled by ShowPage + std::vector items; // MenuItems for the current page. Filled by ShowPage + std::vector nodeConfigLabels; // Persistent labels for Node Config pages + uint8_t selectedChannelIndex = 0; // Currently selected LoRa channel (Node Config → Radio → Channel) + bool channelPositionEnabled = false; + bool gpsEnabled = false; + + // Recents menu checkbox state (derived from settings.recentlyActiveSeconds) + static constexpr uint8_t RECENTS_COUNT = 6; + bool recentsSelected[RECENTS_COUNT] = {}; // Data for selecting and sending canned messages via the menu // Placed into a sub-class for organization only @@ -94,6 +110,8 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread // Cleared onBackground (when MenuApplet closes) std::vector messageItems; std::vector recipientItems; + + MessageItem freeTextItem; } cm; Applet *borrowedTileOwner = nullptr; // Which applet we have temporarily replaced while displaying menu diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuItem.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuItem.h index c74fe3d8a..51c9161a7 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuItem.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuItem.h @@ -30,6 +30,7 @@ class MenuItem MenuAction action = NO_ACTION; MenuPage nextPage = EXIT; bool *checkState = nullptr; + bool isHeader = false; // Non-selectable section label // Various constructors, depending on the intended function of the item @@ -40,6 +41,12 @@ class MenuItem : label(label), action(action), nextPage(nextPage), checkState(checkState) { } + static MenuItem Header(const char *label) + { + MenuItem item(label, NO_ACTION, EXIT); + item.isHeader = true; + return item; + } }; } // namespace NicheGraphics::InkHUD diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuPage.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuPage.h index 389e411c3..138c389be 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuPage.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuPage.h @@ -20,10 +20,27 @@ enum MenuPage : uint8_t { SEND, CANNEDMESSAGE_RECIPIENT, // Select destination for a canned message OPTIONS, + NODE_CONFIG, + NODE_CONFIG_LORA, + NODE_CONFIG_CHANNELS, // List of channels + NODE_CONFIG_CHANNEL_DETAIL, // Per-channel options + NODE_CONFIG_CHANNEL_PRECISION, + NODE_CONFIG_PRESET, + NODE_CONFIG_DEVICE, + NODE_CONFIG_DEVICE_ROLE, + NODE_CONFIG_POWER, + NODE_CONFIG_POWER_ADC_CAL, + NODE_CONFIG_NETWORK, + NODE_CONFIG_DISPLAY, + NODE_CONFIG_BLUETOOTH, + NODE_CONFIG_POSITION, + NODE_CONFIG_ADMIN_RESET, + TIMEZONE, APPLETS, AUTOSHOW, RECENTS, // Select length of "recentlyActiveSeconds" - EXIT, // Dismiss the menu applet + REGION, + EXIT, // Dismiss the menu applet }; } // namespace NicheGraphics::InkHUD diff --git a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp index 2ea9c7fe0..19cef4fbd 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp @@ -65,7 +65,7 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket return 0; } -void InkHUD::NotificationApplet::onRender() +void InkHUD::NotificationApplet::onRender(bool full) { // Clear the region beneath the tile // Most applets are drawing onto an empty frame buffer and don't need to do this @@ -139,54 +139,47 @@ void InkHUD::NotificationApplet::onForeground() void InkHUD::NotificationApplet::onBackground() { handleInput = false; + inkhud->forceUpdate(EInk::UpdateTypes::FULL, true); } void InkHUD::NotificationApplet::onButtonShortPress() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onButtonLongPress() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onExitShort() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onExitLong() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onNavUp() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onNavDown() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onNavLeft() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onNavRight() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } // Ask the WindowManager to check whether any displayed applets are already displaying the info from this notification diff --git a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.h b/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.h index 16ea13407..d398a36f3 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.h @@ -26,7 +26,7 @@ class NotificationApplet : public SystemApplet public: NotificationApplet(); - void onRender() override; + void onRender(bool full) override; void onForeground() override; void onBackground() override; void onButtonShortPress() override; diff --git a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp index 09931f109..a09ff55d5 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp @@ -9,7 +9,7 @@ InkHUD::PairingApplet::PairingApplet() bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus); } -void InkHUD::PairingApplet::onRender() +void InkHUD::PairingApplet::onRender(bool full) { // Header setFont(fontMedium); @@ -45,7 +45,7 @@ void InkHUD::PairingApplet::onBackground() // Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background // Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case - inkhud->forceUpdate(EInk::UpdateTypes::FULL); + inkhud->forceUpdate(EInk::UpdateTypes::FULL, true); } int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *status) diff --git a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.h b/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.h index b89783a25..4c2e95321 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.h @@ -22,7 +22,7 @@ class PairingApplet : public SystemApplet public: PairingApplet(); - void onRender() override; + void onRender(bool full) override; void onForeground() override; void onBackground() override; diff --git a/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.cpp index 99cdeb0ac..228c8b2ca 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.cpp @@ -4,7 +4,7 @@ using namespace NicheGraphics; -void InkHUD::PlaceholderApplet::onRender() +void InkHUD::PlaceholderApplet::onRender(bool full) { // This placeholder applet fills its area with sparse diagonal lines hatchRegion(0, 0, width(), height(), 8, BLACK); diff --git a/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.h b/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.h index 78ba5cd89..fa40913e0 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.h @@ -17,7 +17,7 @@ namespace NicheGraphics::InkHUD class PlaceholderApplet : public SystemApplet { public: - void onRender() override; + void onRender(bool full) override; // Note: onForeground, onBackground, and wantsToRender are not meaningful for this applet. // The window manager decides when and where it should be rendered diff --git a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp index a9d579873..6cac2644b 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp @@ -10,39 +10,42 @@ using namespace NicheGraphics; InkHUD::TipsApplet::TipsApplet() { - // Decide which tips (if any) should be shown to user after the boot screen + bool needsRegion = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET); + + bool showTutorialTips = (settings->tips.firstBoot || needsRegion); // Welcome screen - if (settings->tips.firstBoot) + if (showTutorialTips) tipQueue.push_back(Tip::WELCOME); - // Antenna, region, timezone - // Shown at boot if region not yet set - if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) + // Finish setup + if (needsRegion) tipQueue.push_back(Tip::FINISH_SETUP); + // Using the UI + if (showTutorialTips) { + tipQueue.push_back(Tip::CUSTOMIZATION); + tipQueue.push_back(Tip::BUTTONS); + } + // Shutdown info // Shown until user performs one valid shutdown if (!settings->tips.safeShutdownSeen) tipQueue.push_back(Tip::SAFE_SHUTDOWN); - // Using the UI - if (settings->tips.firstBoot) { - tipQueue.push_back(Tip::CUSTOMIZATION); - tipQueue.push_back(Tip::BUTTONS); - } - // Catch an incorrect attempt at rotating display if (config.display.flip_screen) tipQueue.push_back(Tip::ROTATION); - // Applet is foreground immediately at boot, but is obscured by LogoApplet, which is also foreground - // LogoApplet can be considered to have a higher Z-index, because it is placed before TipsApplet in the systemApplets vector + // Region picker + if (needsRegion) + tipQueue.push_back(Tip::PICK_REGION); + if (!tipQueue.empty()) bringToForeground(); } -void InkHUD::TipsApplet::onRender() +void InkHUD::TipsApplet::onRender(bool full) { switch (tipQueue.front()) { case Tip::WELCOME: @@ -51,81 +54,109 @@ void InkHUD::TipsApplet::onRender() case Tip::FINISH_SETUP: { setFont(fontMedium); - printAt(0, 0, "Tip: Finish Setup"); + const char *title = "Tip: Finish Setup"; + uint16_t h = getWrappedTextHeight(0, width(), title); + printWrapped(0, 0, width(), title); setFont(fontSmall); - int16_t cursorY = fontMedium.lineHeight() * 1.5; - printAt(0, cursorY, "- connect antenna"); + int16_t cursorY = h + fontSmall.lineHeight(); - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- connect a client app"); + auto drawBullet = [&](const char *text) { + uint16_t bh = getWrappedTextHeight(0, width(), text); + printWrapped(0, cursorY, width(), text); + cursorY += bh + (fontSmall.lineHeight() / 3); + }; - // Only if region not set - if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) { - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- set region"); - } + drawBullet("- connect antenna"); + drawBullet("- connect a client app"); - // Only if tz not set - if (!(*config.device.tzdef && config.device.tzdef[0] != 0)) { - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- set timezone"); - } + if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) + drawBullet("- set region"); - cursorY += fontSmall.lineHeight() * 1.5; - printAt(0, cursorY, "More info at meshtastic.org"); + if (!(*config.device.tzdef && config.device.tzdef[0] != 0)) + drawBullet("- set timezone"); + + cursorY += fontSmall.lineHeight() / 2; + drawBullet("More info at meshtastic.org"); - setFont(fontSmall); printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM); } break; + case Tip::PICK_REGION: { + setFont(fontMedium); + printAt(0, 0, "Set Region"); + + setFont(fontSmall); + printWrapped(0, fontMedium.lineHeight() * 1.5, width(), "Please select your LoRa region to complete setup."); + + printAt(0, Y(1.0), "Press button to choose", LEFT, BOTTOM); + } break; + case Tip::SAFE_SHUTDOWN: { setFont(fontMedium); - printAt(0, 0, "Tip: Shutdown"); + + const char *title = "Tip: Shutdown"; + uint16_t h = getWrappedTextHeight(0, width(), title); + printWrapped(0, 0, width(), title); setFont(fontSmall); - std::string shutdown; - shutdown += "Before removing power, please shut down from InkHUD menu, or a client app. \n"; - shutdown += "\n"; - shutdown += "This ensures data is saved."; - printWrapped(0, fontMedium.lineHeight() * 1.5, width(), shutdown); + int16_t cursorY = h + fontSmall.lineHeight(); + + const char *body = "Before removing power, please shut down from InkHUD menu, or a client app.\n\n" + "This ensures data is saved."; + + uint16_t bodyH = getWrappedTextHeight(0, width(), body); + printWrapped(0, cursorY, width(), body); + cursorY += bodyH + (fontSmall.lineHeight() / 2); printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM); - } break; case Tip::CUSTOMIZATION: { setFont(fontMedium); - printAt(0, 0, "Tip: Customization"); + + const char *title = "Tip: Customization"; + uint16_t h = getWrappedTextHeight(0, width(), title); + printWrapped(0, 0, width(), title); setFont(fontSmall); - printWrapped(0, fontMedium.lineHeight() * 1.5, width(), - "Configure & control display with the InkHUD menu. Optional features, layout, rotation, and more."); + int16_t cursorY = h + fontSmall.lineHeight(); + + const char *body = "Configure & control display with the InkHUD menu. " + "Optional features, layout, rotation, and more."; + + uint16_t bodyH = getWrappedTextHeight(0, width(), body); + printWrapped(0, cursorY, width(), body); + cursorY += bodyH + (fontSmall.lineHeight() / 2); printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM); } break; case Tip::BUTTONS: { setFont(fontMedium); - printAt(0, 0, "Tip: Buttons"); + + const char *title = "Tip: Buttons"; + uint16_t h = getWrappedTextHeight(0, width(), title); + printWrapped(0, 0, width(), title); setFont(fontSmall); - int16_t cursorY = fontMedium.lineHeight() * 1.5; + int16_t cursorY = h + fontSmall.lineHeight(); + + auto drawBullet = [&](const char *text) { + uint16_t bh = getWrappedTextHeight(0, width(), text); + printWrapped(0, cursorY, width(), text); + cursorY += bh + (fontSmall.lineHeight() / 3); + }; if (!settings->joystick.enabled) { - printAt(0, cursorY, "User Button"); - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- short press: next"); - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- long press: select / open menu"); + drawBullet("User Button"); + drawBullet("- short press: next"); + drawBullet("- long press: select or open menu"); } else { - printAt(0, cursorY, "Joystick"); - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- open menu / select"); - cursorY += fontSmall.lineHeight() * 1.5; - printAt(0, cursorY, "Exit Button"); - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- switch tile / close menu"); + drawBullet("Joystick"); + drawBullet("- press: open menu or select"); + drawBullet("Exit Button"); + drawBullet("- press: switch tile or close menu"); } printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM); @@ -133,12 +164,21 @@ void InkHUD::TipsApplet::onRender() case Tip::ROTATION: { setFont(fontMedium); - printAt(0, 0, "Tip: Rotation"); + + const char *title = "Tip: Rotation"; + uint16_t h = getWrappedTextHeight(0, width(), title); + printWrapped(0, 0, width(), title); setFont(fontSmall); if (!settings->joystick.enabled) { - printWrapped(0, fontMedium.lineHeight() * 1.5, width(), - "To rotate the display, use the InkHUD menu. Long-press the user button > Options > Rotate."); + int16_t cursorY = h + fontSmall.lineHeight(); + + const char *body = "To rotate the display, use the InkHUD menu. " + "Long-press the user button > Options > Rotate."; + + uint16_t bh = getWrappedTextHeight(0, width(), body); + printWrapped(0, cursorY, width(), body); + cursorY += bh + (fontSmall.lineHeight() / 2); } else { printWrapped(0, fontMedium.lineHeight() * 1.5, width(), "To rotate the display, use the InkHUD menu. Press the user button > Options > Rotate."); @@ -159,12 +199,15 @@ void InkHUD::TipsApplet::renderWelcome() { uint16_t padW = X(0.05); + // Detect portrait orientation + bool portrait = height() > width(); + // Block 1 - logo & title // ======================== // Logo size - uint16_t logoWLimit = X(0.3); - uint16_t logoHLimit = Y(0.3); + uint16_t logoWLimit = portrait ? X(0.5) : X(0.3); + uint16_t logoHLimit = portrait ? Y(0.25) : Y(0.3); uint16_t logoW = getLogoWidth(logoWLimit, logoHLimit); uint16_t logoH = getLogoHeight(logoWLimit, logoHLimit); @@ -177,7 +220,7 @@ void InkHUD::TipsApplet::renderWelcome() // Center the block // Desired effect: equal margin from display edge for logo left and title right - int16_t block1Y = Y(0.3); + int16_t block1Y = portrait ? Y(0.2) : Y(0.3); int16_t block1CX = X(0.5) + (logoW / 2) - (titleW / 2); int16_t logoCX = block1CX - (logoW / 2) - (padW / 2); int16_t titleCX = block1CX + (titleW / 2) + (padW / 2); @@ -192,7 +235,7 @@ void InkHUD::TipsApplet::renderWelcome() std::string subtitle = "InkHUD"; if (width() >= 200) subtitle += " - A Heads-Up Display"; // Future proofing: narrower for tiny display - printAt(X(0.5), Y(0.6), subtitle, CENTER, MIDDLE); + printAt(X(0.5), portrait ? Y(0.45) : Y(0.6), subtitle, CENTER, MIDDLE); // Block 3 - press to continue // ============================ @@ -218,32 +261,42 @@ void InkHUD::TipsApplet::onBackground() // Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background // Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case - inkhud->forceUpdate(EInk::UpdateTypes::FULL); + inkhud->forceUpdate(EInk::UpdateTypes::FULL, true); } // While our SystemApplet::handleInput flag is true void InkHUD::TipsApplet::onButtonShortPress() { + bool needsRegion = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET); + // If we're prompting the user to pick a region, hand off to the menu + if (!tipQueue.empty() && tipQueue.front() == Tip::PICK_REGION) { + tipQueue.pop_front(); + + // Signal InkHUD to open the menu on Region page + inkhud->forceRegionMenu = true; + + // Close tips and open menu + sendToBackground(); + inkhud->openMenu(); + return; + } + // Consume current tip tipQueue.pop_front(); // All tips done if (tipQueue.empty()) { // Record that user has now seen the "tutorial" set of tips // Don't show them on subsequent boots - if (settings->tips.firstBoot) { + if (settings->tips.firstBoot && !needsRegion) { settings->tips.firstBoot = false; inkhud->persistence->saveSettings(); } - // Close applet, and full refresh to clean the screen - // Need to force update, because our request would be ignored otherwise, as we are now background + // Close applet sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); - } - - // More tips left - else + } else { requestUpdate(); + } } // Functions the same as the user button in this instance @@ -252,4 +305,4 @@ void InkHUD::TipsApplet::onExitShort() onButtonShortPress(); } -#endif \ No newline at end of file +#endif diff --git a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h index 159e6f58f..2e81d678b 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h @@ -23,6 +23,7 @@ class TipsApplet : public SystemApplet enum class Tip { WELCOME, FINISH_SETUP, + PICK_REGION, SAFE_SHUTDOWN, CUSTOMIZATION, BUTTONS, @@ -32,7 +33,7 @@ class TipsApplet : public SystemApplet public: TipsApplet(); - void onRender() override; + void onRender(bool full) override; void onForeground() override; void onBackground() override; void onButtonShortPress() override; diff --git a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.cpp index 7c6232f3b..96c519599 100644 --- a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.cpp @@ -34,7 +34,7 @@ int InkHUD::AllMessageApplet::onReceiveTextMessage(const meshtastic_MeshPacket * return 0; } -void InkHUD::AllMessageApplet::onRender() +void InkHUD::AllMessageApplet::onRender(bool full) { // Find newest message, regardless of whether DM or broadcast MessageStore::Message *message; diff --git a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h b/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h index c74e16196..4aa97e4f1 100644 --- a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h +++ b/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h @@ -30,7 +30,7 @@ class Applet; class AllMessageApplet : public Applet { public: - void onRender() override; + void onRender(bool full) override; void onActivate() override; void onDeactivate() override; diff --git a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.cpp index a3b9615a5..189a56cab 100644 --- a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.cpp @@ -37,7 +37,7 @@ int InkHUD::DMApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p) return 0; } -void InkHUD::DMApplet::onRender() +void InkHUD::DMApplet::onRender(bool full) { // Abort if no text message if (!latestMessage->dm.sender) { diff --git a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h b/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h index b3dc36e66..4eb0ec704 100644 --- a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h +++ b/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h @@ -30,7 +30,7 @@ class Applet; class DMApplet : public Applet { public: - void onRender() override; + void onRender(bool full) override; void onActivate() override; void onDeactivate() override; diff --git a/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp index ad0f9fc47..ae7679962 100644 --- a/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp @@ -5,10 +5,10 @@ using namespace NicheGraphics; -void InkHUD::PositionsApplet::onRender() +void InkHUD::PositionsApplet::onRender(bool full) { // Draw the usual map applet first - MapApplet::onRender(); + MapApplet::onRender(full); // Draw our latest "node of interest" as a special marker // ------------------------------------------------------- diff --git a/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h b/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h index 28a53cb0f..d0d3e5f07 100644 --- a/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h +++ b/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h @@ -24,7 +24,7 @@ class PositionsApplet : public MapApplet, public SinglePortModule { public: PositionsApplet() : SinglePortModule("PositionsApplet", meshtastic_PortNum_POSITION_APP) {} - void onRender() override; + void onRender(bool full) override; protected: ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; diff --git a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp index fdb5a168d..f16721357 100644 --- a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp @@ -22,7 +22,7 @@ InkHUD::ThreadedMessageApplet::ThreadedMessageApplet(uint8_t channelIndex) store = new MessageStore("ch" + to_string(channelIndex)); } -void InkHUD::ThreadedMessageApplet::onRender() +void InkHUD::ThreadedMessageApplet::onRender(bool full) { // ============= // Draw a header diff --git a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h b/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h index c986539b3..045e2a6fc 100644 --- a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h +++ b/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h @@ -36,7 +36,7 @@ class ThreadedMessageApplet : public Applet, public SinglePortModule explicit ThreadedMessageApplet(uint8_t channelIndex); ThreadedMessageApplet() = delete; - void onRender() override; + void onRender(bool full) override; void onActivate() override; void onDeactivate() override; diff --git a/src/graphics/niche/InkHUD/Events.cpp b/src/graphics/niche/InkHUD/Events.cpp index 5382d2391..e6c16d350 100644 --- a/src/graphics/niche/InkHUD/Events.cpp +++ b/src/graphics/niche/InkHUD/Events.cpp @@ -238,6 +238,39 @@ void InkHUD::Events::onNavRight() } } +void InkHUD::Events::onFreeText(char c) +{ + // Trigger the first system applet that wants to handle the new character + for (SystemApplet *sa : inkhud->systemApplets) { + if (sa->handleFreeText) { + sa->onFreeText(c); + break; + } + } +} + +void InkHUD::Events::onFreeTextDone() +{ + // Trigger the first system applet that wants to handle it + for (SystemApplet *sa : inkhud->systemApplets) { + if (sa->handleFreeText) { + sa->onFreeTextDone(); + break; + } + } +} + +void InkHUD::Events::onFreeTextCancel() +{ + // Trigger the first system applet that wants to handle it + for (SystemApplet *sa : inkhud->systemApplets) { + if (sa->handleFreeText) { + sa->onFreeTextCancel(); + break; + } + } +} + // Callback for deepSleepObserver // Returns 0 to signal that we agree to sleep now int InkHUD::Events::beforeDeepSleep(void *unused) @@ -266,7 +299,7 @@ int InkHUD::Events::beforeDeepSleep(void *unused) // then prepared a final powered-off screen for us, which shows device shortname. // We're updating to show that one now. - inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL, false); + inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL, true, false); delay(1000); // Cooldown, before potentially yanking display power // InkHUD shutdown complete @@ -276,6 +309,15 @@ int InkHUD::Events::beforeDeepSleep(void *unused) return 0; // We agree: deep sleep now } +// Display an intermediate screen while configuration changes are applied +void InkHUD::Events::applyingChanges() +{ + // Bring the logo applet forward with a temporary message + for (SystemApplet *sa : inkhud->systemApplets) { + sa->onApplyingChanges(); + } +} + // Callback for rebootObserver // Same as shutdown, without drawing the logoApplet // Makes sure we don't lose message history / InkHUD config diff --git a/src/graphics/niche/InkHUD/Events.h b/src/graphics/niche/InkHUD/Events.h index 664ca19f0..873f53fd5 100644 --- a/src/graphics/niche/InkHUD/Events.h +++ b/src/graphics/niche/InkHUD/Events.h @@ -29,12 +29,18 @@ class Events void onButtonShort(); // User button: short press void onButtonLong(); // User button: long press - void onExitShort(); // Exit button: short press - void onExitLong(); // Exit button: long press - void onNavUp(); // Navigate up - void onNavDown(); // Navigate down - void onNavLeft(); // Navigate left - void onNavRight(); // Navigate right + void applyingChanges(); + void onExitShort(); // Exit button: short press + void onExitLong(); // Exit button: long press + void onNavUp(); // Navigate up + void onNavDown(); // Navigate down + void onNavLeft(); // Navigate left + void onNavRight(); // Navigate right + + // Free text typing events + void onFreeText(char c); // New freetext character input + void onFreeTextDone(); + void onFreeTextCancel(); int beforeDeepSleep(void *unused); // Prepare for shutdown int beforeReboot(void *unused); // Prepare for reboot diff --git a/src/graphics/niche/InkHUD/InkHUD.cpp b/src/graphics/niche/InkHUD/InkHUD.cpp index 9f05ae5bb..5fab67639 100644 --- a/src/graphics/niche/InkHUD/InkHUD.cpp +++ b/src/graphics/niche/InkHUD/InkHUD.cpp @@ -53,6 +53,13 @@ void InkHUD::InkHUD::addApplet(const char *name, Applet *a, bool defaultActive, windowManager->addApplet(name, a, defaultActive, defaultAutoshow, onTile); } +void InkHUD::InkHUD::notifyApplyingChanges() +{ + if (events) { + events->applyingChanges(); + } +} + // Start InkHUD! // Call this only after you have configured InkHUD void InkHUD::InkHUD::begin() @@ -168,6 +175,25 @@ void InkHUD::InkHUD::navRight() } } +// Call this for keyboard input +// The Keyboard Applet also calls this +void InkHUD::InkHUD::freeText(char c) +{ + events->onFreeText(c); +} + +// Call this to complete a freetext input +void InkHUD::InkHUD::freeTextDone() +{ + events->onFreeTextDone(); +} + +// Call this to cancel a freetext input +void InkHUD::InkHUD::freeTextCancel() +{ + events->onFreeTextCancel(); +} + // Cycle the next user applet to the foreground // Only activated applets are cycled // If user has a multi-applet layout, the applets will cycle on the "focused tile" @@ -197,6 +223,18 @@ void InkHUD::InkHUD::openAlignStick() windowManager->openAlignStick(); } +// Open the on-screen keyboard +void InkHUD::InkHUD::openKeyboard() +{ + windowManager->openKeyboard(); +} + +// Close the on-screen keyboard +void InkHUD::InkHUD::closeKeyboard() +{ + windowManager->closeKeyboard(); +} + // In layouts where multiple applets are shown at once, change which tile is focused // The focused tile in the one which cycles applets on button short press, and displays menu on long press void InkHUD::InkHUD::nextTile() @@ -245,10 +283,11 @@ void InkHUD::InkHUD::requestUpdate() // Ignores all diplomacy: // - the display *will* update // - the specified update type *will* be used +// If the all parameter is true, the whole screen buffer is cleared and re-rendered // If the async parameter is false, code flow is blocked while the update takes place -void InkHUD::InkHUD::forceUpdate(EInk::UpdateTypes type, bool async) +void InkHUD::InkHUD::forceUpdate(EInk::UpdateTypes type, bool all, bool async) { - renderer->forceUpdate(type, async); + renderer->forceUpdate(type, all, async); } // Wait for any in-progress display update to complete before continuing diff --git a/src/graphics/niche/InkHUD/InkHUD.h b/src/graphics/niche/InkHUD/InkHUD.h index 7325d8262..ae029137e 100644 --- a/src/graphics/niche/InkHUD/InkHUD.h +++ b/src/graphics/niche/InkHUD/InkHUD.h @@ -47,6 +47,7 @@ class InkHUD void setDriver(Drivers::EInk *driver); void setDisplayResilience(uint8_t fastPerFull = 5, float stressMultiplier = 2.0); void addApplet(const char *name, Applet *a, bool defaultActive = false, bool defaultAutoshow = false, uint8_t onTile = -1); + void notifyApplyingChanges(); void begin(); @@ -62,6 +63,11 @@ class InkHUD void navLeft(); void navRight(); + // Freetext handlers + void freeText(char c); + void freeTextDone(); + void freeTextCancel(); + // Trigger UI changes // - called by various InkHUD components // - suitable(?) for use by aux button, connected in variant nicheGraphics.h @@ -70,17 +76,23 @@ class InkHUD void prevApplet(); void openMenu(); void openAlignStick(); + void openKeyboard(); + void closeKeyboard(); void nextTile(); void prevTile(); void rotate(); void rotateJoystick(uint8_t angle = 1); // rotate 90 deg by default void toggleBatteryIcon(); + // Used by TipsApplet to force menu to start on Region selection + bool forceRegionMenu = false; + // Updating the display // - called by various InkHUD components void requestUpdate(); - void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool async = true); + void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool all = false, + bool async = true); void awaitUpdate(); // (Re)configuring WindowManager diff --git a/src/graphics/niche/InkHUD/Renderer.cpp b/src/graphics/niche/InkHUD/Renderer.cpp index 072e9dbd6..89a83c932 100644 --- a/src/graphics/niche/InkHUD/Renderer.cpp +++ b/src/graphics/niche/InkHUD/Renderer.cpp @@ -56,15 +56,16 @@ void InkHUD::Renderer::setDisplayResilience(uint8_t fastPerFull, float stressMul void InkHUD::Renderer::begin() { - forceUpdate(Drivers::EInk::UpdateTypes::FULL, false); + forceUpdate(Drivers::EInk::UpdateTypes::FULL, true, false); } // Set a flag, which will be picked up by runOnce, ASAP. // Quite likely, multiple applets will all want to respond to one event (Observable, etc) // Each affected applet can independently call requestUpdate(), and all share the one opportunity to render, at next runOnce -void InkHUD::Renderer::requestUpdate() +void InkHUD::Renderer::requestUpdate(bool all) { requested = true; + renderAll |= all; // We will run the thread as soon as we loop(), // after all Applets have had a chance to observe whatever event set this off @@ -79,10 +80,11 @@ void InkHUD::Renderer::requestUpdate() // Sometimes, however, we will want to trigger a display update manually, in the absence of any sort of applet event // Display health, for example. // In these situations, we use forceUpdate -void InkHUD::Renderer::forceUpdate(Drivers::EInk::UpdateTypes type, bool async) +void InkHUD::Renderer::forceUpdate(Drivers::EInk::UpdateTypes type, bool all, bool async) { requested = true; forced = true; + renderAll |= all; displayHealth.forceUpdateType(type); // Normally, we need to start the timer, in case the display is busy and we briefly defer the update @@ -219,7 +221,8 @@ void InkHUD::Renderer::render(bool async) Drivers::EInk::UpdateTypes updateType = decideUpdateType(); // Render the new image - clearBuffer(); + if (renderAll) + clearBuffer(); renderUserApplets(); renderPlaceholders(); renderSystemApplets(); @@ -247,6 +250,7 @@ void InkHUD::Renderer::render(bool async) // Tidy up, ready for a new request requested = false; forced = false; + renderAll = false; } // Manually fill the image buffer with WHITE @@ -259,6 +263,76 @@ void InkHUD::Renderer::clearBuffer() memset(imageBuffer, 0xFF, imageBufferHeight * imageBufferWidth); } +// Manually clear the pixels below a tile +void InkHUD::Renderer::clearTile(Tile *t) +{ + // Rotate the tile dimensions + int16_t left = 0; + int16_t top = 0; + uint16_t width = 0; + uint16_t height = 0; + switch (settings->rotation) { + case 0: + left = t->getLeft(); + top = t->getTop(); + width = t->getWidth(); + height = t->getHeight(); + break; + case 1: + left = driver->width - (t->getTop() + t->getHeight()); + top = t->getLeft(); + width = t->getHeight(); + height = t->getWidth(); + break; + case 2: + left = driver->width - (t->getLeft() + t->getWidth()); + top = driver->height - (t->getTop() + t->getHeight()); + width = t->getWidth(); + height = t->getHeight(); + break; + case 3: + left = t->getTop(); + top = driver->height - (t->getLeft() + t->getWidth()); + width = t->getHeight(); + height = t->getWidth(); + break; + } + + // Calculate the bounds to clear + uint16_t xStart = (left < 0) ? 0 : left; + uint16_t yStart = (top < 0) ? 0 : top; + if (xStart >= driver->width || yStart >= driver->height || left + width < 0 || top + height < 0) + return; // the box is completely off the screen + uint16_t xEnd = left + width; + uint16_t yEnd = top + height; + if (xEnd > driver->width) + xEnd = driver->width; + if (yEnd > driver->height) + yEnd = driver->height; + + // Clear the pixels + if (xStart == 0 && xEnd == driver->width) { // full width box is easier to clear + memset(imageBuffer + (yStart * imageBufferWidth), 0xFF, (yEnd - yStart) * imageBufferWidth); + } else { + const uint16_t byteStart = (xStart / 8) + 1; + const uint16_t byteEnd = xEnd / 8; + const uint8_t leadingByte = 0xFF >> (xStart - ((byteStart - 1) * 8)); + const uint8_t trailingByte = (0xFF00 >> (xEnd - (byteEnd * 8))) & 0xFF; + for (uint16_t i = yStart * imageBufferWidth; i < yEnd * imageBufferWidth; i += imageBufferWidth) { + // Set the leading byte + imageBuffer[i + byteStart - 1] |= leadingByte; + + // Set the continuous bytes + if (byteStart < byteEnd) + memset(imageBuffer + i + byteStart, 0xFF, byteEnd - byteStart); + + // Set the trailing byte + if (byteEnd != imageBufferWidth) + imageBuffer[i + byteEnd] |= trailingByte; + } + } +} + void InkHUD::Renderer::checkLocks() { lockRendering = nullptr; @@ -323,12 +397,12 @@ Drivers::EInk::UpdateTypes InkHUD::Renderer::decideUpdateType() if (!forced) { // User applets for (Applet *ua : inkhud->userApplets) { - if (ua && ua->isForeground()) + if (ua && ua->isForeground() && (ua->wantsToRender() || renderAll)) displayHealth.requestUpdateType(ua->wantsUpdateType()); } // System Applets for (SystemApplet *sa : inkhud->systemApplets) { - if (sa && sa->isForeground()) + if (sa && sa->isForeground() && (sa->wantsToRender() || sa->alwaysRender || renderAll)) displayHealth.requestUpdateType(sa->wantsUpdateType()); } } @@ -346,9 +420,16 @@ void InkHUD::Renderer::renderUserApplets() // Render any user applets which are currently visible for (Applet *ua : inkhud->userApplets) { - if (ua && ua->isActive() && ua->isForeground()) { + if (ua && ua->isActive() && ua->isForeground() && (ua->wantsToRender() || renderAll)) { + + // Clear the tile unless the applet wants to draw over its previous render + // or everything is getting re-rendered anyways + if (ua->wantsFullRender() && !renderAll) + clearTile(ua->getTile()); + uint32_t start = millis(); - ua->render(); // Draw! + bool full = ua->wantsFullRender() || renderAll; + ua->render(full); // Draw! uint32_t stop = millis(); LOG_DEBUG("%s took %dms to render", ua->name, stop - start); } @@ -370,6 +451,9 @@ void InkHUD::Renderer::renderSystemApplets() if (!sa->isForeground()) continue; + if (!sa->wantsToRender() && !sa->alwaysRender && !renderAll) + continue; + // Skip if locked by another applet if (lockRendering && lockRendering != sa) continue; @@ -381,8 +465,14 @@ void InkHUD::Renderer::renderSystemApplets() assert(sa->getTile()); + // Clear the tile unless the applet wants to draw over its previous render + // or everything is getting re-rendered anyways + if (sa->wantsFullRender() && !renderAll) + clearTile(sa->getTile()); + // uint32_t start = millis(); - sa->render(); // Draw! + bool full = sa->wantsFullRender() || renderAll; + sa->render(full); // Draw! // uint32_t stop = millis(); // LOG_DEBUG("%s took %dms to render", sa->name, stop - start); } @@ -409,7 +499,10 @@ void InkHUD::Renderer::renderPlaceholders() // uint32_t start = millis(); for (Tile *t : emptyTiles) { t->assignApplet(placeholder); - placeholder->render(); + // Clear the tile unless everything is getting re-rendered + if (!renderAll) + clearTile(t); + placeholder->render(true); // full render t->assignApplet(nullptr); } // uint32_t stop = millis(); diff --git a/src/graphics/niche/InkHUD/Renderer.h b/src/graphics/niche/InkHUD/Renderer.h index b6cf9e215..5cfb79277 100644 --- a/src/graphics/niche/InkHUD/Renderer.h +++ b/src/graphics/niche/InkHUD/Renderer.h @@ -37,8 +37,8 @@ class Renderer : protected concurrency::OSThread // Call these to make the image change - void requestUpdate(); // Update display, if a foreground applet has info it wants to show - void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, + void requestUpdate(bool all = false); // Update display, if a foreground applet has info it wants to show + void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool all = false, bool async = true); // Update display, regardless of whether any applets requested this // Wait for an update to complete @@ -65,6 +65,7 @@ class Renderer : protected concurrency::OSThread // Steps of the rendering process void clearBuffer(); + void clearTile(Tile *t); void checkLocks(); bool shouldUpdate(); Drivers::EInk::UpdateTypes decideUpdateType(); @@ -85,6 +86,7 @@ class Renderer : protected concurrency::OSThread bool requested = false; bool forced = false; + bool renderAll = false; // For convenience InkHUD *inkhud = nullptr; diff --git a/src/graphics/niche/InkHUD/SystemApplet.h b/src/graphics/niche/InkHUD/SystemApplet.h index 7ee47eeb9..32e0e58bb 100644 --- a/src/graphics/niche/InkHUD/SystemApplet.h +++ b/src/graphics/niche/InkHUD/SystemApplet.h @@ -22,11 +22,14 @@ class SystemApplet : public Applet public: // System applets have the right to: - bool handleInput = false; // - respond to input from the user button - bool lockRendering = false; // - prevent other applets from being rendered during an update - bool lockRequests = false; // - prevent other applets from triggering display updates + bool handleInput = false; // - respond to input from the user button + bool handleFreeText = false; // - respond to free text input + bool lockRendering = false; // - prevent other applets from being rendered during an update + bool lockRequests = false; // - prevent other applets from triggering display updates + bool alwaysRender = false; // - render every time the screen is updated virtual void onReboot() { onShutdown(); } // - handle reboot specially + virtual void onApplyingChanges() {} // Other system applets may take precedence over our own system applet though // The order an applet is passed to WindowManager::addSystemApplet determines this hierarchy (added earlier = higher rank) @@ -40,4 +43,4 @@ class SystemApplet : public Applet }; // namespace NicheGraphics::InkHUD -#endif \ No newline at end of file +#endif diff --git a/src/graphics/niche/InkHUD/Tile.cpp b/src/graphics/niche/InkHUD/Tile.cpp index 5e548de74..8beb25f39 100644 --- a/src/graphics/niche/InkHUD/Tile.cpp +++ b/src/graphics/niche/InkHUD/Tile.cpp @@ -18,7 +18,7 @@ static int32_t runtaskHighlight() LOG_DEBUG("Dismissing Highlight"); InkHUD::Tile::highlightShown = false; InkHUD::Tile::highlightTarget = nullptr; - InkHUD::InkHUD::getInstance()->forceUpdate(Drivers::EInk::UpdateTypes::FAST); // Re-render, clearing the highlighting + InkHUD::InkHUD::getInstance()->forceUpdate(Drivers::EInk::UpdateTypes::FAST, true); // Re-render, clearing the highlighting return taskHighlight->disable(); } static void inittaskHighlight() @@ -190,6 +190,18 @@ void InkHUD::Tile::handleAppletPixel(int16_t x, int16_t y, Color c) } } +// Used in Renderer for clearing the tile +int16_t InkHUD::Tile::getLeft() +{ + return left; +} + +// Used in Renderer for clearing the tile +int16_t InkHUD::Tile::getTop() +{ + return top; +} + // Called by Applet base class, when setting applet dimensions, immediately before render uint16_t InkHUD::Tile::getWidth() { @@ -220,7 +232,7 @@ void InkHUD::Tile::requestHighlight() { Tile::highlightTarget = this; Tile::highlightShown = false; - inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FAST); + inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FAST, true); } // Starts the timer which will automatically dismiss the highlighting, if the tile doesn't organically redraw first diff --git a/src/graphics/niche/InkHUD/Tile.h b/src/graphics/niche/InkHUD/Tile.h index 0f5444f17..0c09e4704 100644 --- a/src/graphics/niche/InkHUD/Tile.h +++ b/src/graphics/niche/InkHUD/Tile.h @@ -29,6 +29,8 @@ class Tile void setRegion(uint8_t layoutSize, uint8_t tileIndex); // Assign region automatically, based on layout void setRegion(int16_t left, int16_t top, uint16_t width, uint16_t height); // Assign region manually void handleAppletPixel(int16_t x, int16_t y, Color c); // Receive px output from assigned applet + int16_t getLeft(); + int16_t getTop(); uint16_t getWidth(); uint16_t getHeight(); static uint16_t maxDisplayDimension(); // Largest possible width / height any tile may ever encounter diff --git a/src/graphics/niche/InkHUD/WindowManager.cpp b/src/graphics/niche/InkHUD/WindowManager.cpp index 0548de1eb..cec72ce8f 100644 --- a/src/graphics/niche/InkHUD/WindowManager.cpp +++ b/src/graphics/niche/InkHUD/WindowManager.cpp @@ -4,6 +4,7 @@ #include "./Applets/System/AlignStick/AlignStickApplet.h" #include "./Applets/System/BatteryIcon/BatteryIconApplet.h" +#include "./Applets/System/Keyboard/KeyboardApplet.h" #include "./Applets/System/Logo/LogoApplet.h" #include "./Applets/System/Menu/MenuApplet.h" #include "./Applets/System/Notification/NotificationApplet.h" @@ -148,6 +149,28 @@ void InkHUD::WindowManager::openAlignStick() } } +void InkHUD::WindowManager::openKeyboard() +{ + KeyboardApplet *keyboard = (KeyboardApplet *)inkhud->getSystemApplet("Keyboard"); + + if (keyboard) { + keyboard->bringToForeground(); + keyboardOpen = true; + changeLayout(); + } +} + +void InkHUD::WindowManager::closeKeyboard() +{ + KeyboardApplet *keyboard = (KeyboardApplet *)inkhud->getSystemApplet("Keyboard"); + + if (keyboard) { + keyboard->sendToBackground(); + keyboardOpen = false; + changeLayout(); + } +} + // On the currently focussed tile: cycle to the next available user applet // Applets available for this must be activated, and not already displayed on another tile void InkHUD::WindowManager::nextApplet() @@ -272,7 +295,6 @@ void InkHUD::WindowManager::toggleBatteryIcon() batteryIcon->sendToBackground(); // Force-render - // - redraw all applets inkhud->forceUpdate(EInk::UpdateTypes::FAST); } @@ -311,9 +333,25 @@ void InkHUD::WindowManager::changeLayout() menu->show(ft); } + // Resize for the on-screen keyboard + if (keyboardOpen) { + // Send all user applets to the background + // User applets currently don't handle free text input + for (uint8_t i = 0; i < inkhud->userApplets.size(); i++) + inkhud->userApplets.at(i)->sendToBackground(); + // Find the first system applet that can handle freetext and resize it + for (SystemApplet *sa : inkhud->systemApplets) { + if (sa->handleFreeText) { + const uint16_t keyboardHeight = KeyboardApplet::getKeyboardHeight(); + sa->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height() - keyboardHeight - 1); + break; + } + } + } + // Force-render // - redraw all applets - inkhud->forceUpdate(EInk::UpdateTypes::FAST); + inkhud->forceUpdate(EInk::UpdateTypes::FAST, true); } // Perform necessary reconfiguration when user activates or deactivates applets at run-time @@ -347,7 +385,7 @@ void InkHUD::WindowManager::changeActivatedApplets() // Force-render // - redraw all applets - inkhud->forceUpdate(EInk::UpdateTypes::FAST); + inkhud->forceUpdate(EInk::UpdateTypes::FAST, true); } // Some applets may be permitted to bring themselves to foreground, to show new data @@ -433,8 +471,10 @@ void InkHUD::WindowManager::createSystemApplets() addSystemApplet("Logo", new LogoApplet, new Tile); addSystemApplet("Pairing", new PairingApplet, new Tile); addSystemApplet("Tips", new TipsApplet, new Tile); - if (settings->joystick.enabled) + if (settings->joystick.enabled) { addSystemApplet("AlignStick", new AlignStickApplet, new Tile); + addSystemApplet("Keyboard", new KeyboardApplet, new Tile); + } addSystemApplet("Menu", new MenuApplet, nullptr); @@ -457,19 +497,23 @@ void InkHUD::WindowManager::placeSystemTiles() inkhud->getSystemApplet("Logo")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height()); inkhud->getSystemApplet("Pairing")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height()); inkhud->getSystemApplet("Tips")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height()); - if (settings->joystick.enabled) + if (settings->joystick.enabled) { inkhud->getSystemApplet("AlignStick")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height()); - + const uint16_t keyboardHeight = KeyboardApplet::getKeyboardHeight(); + inkhud->getSystemApplet("Keyboard") + ->getTile() + ->setRegion(0, inkhud->height() - keyboardHeight, inkhud->width(), keyboardHeight); + } inkhud->getSystemApplet("Notification")->getTile()->setRegion(0, 0, inkhud->width(), 20); const uint16_t batteryIconHeight = Applet::getHeaderHeight() - 2 - 2; const uint16_t batteryIconWidth = batteryIconHeight * 1.8; inkhud->getSystemApplet("BatteryIcon") ->getTile() - ->setRegion(inkhud->width() - batteryIconWidth, // x - 2, // y - batteryIconWidth, // width - batteryIconHeight); // height + ->setRegion(inkhud->width() - batteryIconWidth - 1, // x + 1, // y + batteryIconWidth + 1, // width + batteryIconHeight + 2); // height // Note: the tiles of placeholder and menu applets are manipulated specially // - menuApplet borrows user tiles diff --git a/src/graphics/niche/InkHUD/WindowManager.h b/src/graphics/niche/InkHUD/WindowManager.h index 5def48f8c..948ef6131 100644 --- a/src/graphics/niche/InkHUD/WindowManager.h +++ b/src/graphics/niche/InkHUD/WindowManager.h @@ -31,6 +31,8 @@ class WindowManager void prevTile(); void openMenu(); void openAlignStick(); + void openKeyboard(); + void closeKeyboard(); void nextApplet(); void prevApplet(); void rotate(); @@ -64,6 +66,7 @@ class WindowManager void findOrphanApplets(); // Find any applets left-behind when layout changes std::vector userTiles; // Tiles which can host user applets + bool keyboardOpen = false; // For convenience InkHUD *inkhud = nullptr; diff --git a/src/graphics/niche/InkHUD/docs/README.md b/src/graphics/niche/InkHUD/docs/README.md index aa23f065f..8c30aba58 100644 --- a/src/graphics/niche/InkHUD/docs/README.md +++ b/src/graphics/niche/InkHUD/docs/README.md @@ -174,7 +174,7 @@ class BasicExampleApplet : public Applet // You must have an onRender() method // All drawing happens here - void onRender() override; + void onRender(bool full) override; }; ``` @@ -183,7 +183,7 @@ The `onRender` method is called when the display image is redrawn. This can happ ```cpp // All drawing happens here // Our basic example doesn't do anything useful. It just passively prints some text. -void InkHUD::BasicExampleApplet::onRender() +void InkHUD::BasicExampleApplet::onRender(bool full) { printAt(0, 0, "Hello, world!"); } diff --git a/src/input/HackadayCommunicatorKeyboard.cpp b/src/input/HackadayCommunicatorKeyboard.cpp index 87c8a24ae..c6a9e0ae8 100644 --- a/src/input/HackadayCommunicatorKeyboard.cpp +++ b/src/input/HackadayCommunicatorKeyboard.cpp @@ -20,20 +20,20 @@ constexpr uint8_t modifierLeftShift = 0b0001; // Num chars per key, Modulus for rotating through characters static uint8_t HackadayCommunicatorTapMod[_TCA8418_NUM_KEYS] = { - 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 0, 0, 0, 1, 2, 2, 2, 1, 2, 2, 0, 0, 0, 2, 1, 2, 2, 0, 1, 1, 0, }; static unsigned char HackadayCommunicatorTapMap[_TCA8418_NUM_KEYS][2] = {{}, - {}, + {Key::FUNCTION_F1}, {'+'}, {'9'}, {'8'}, {'7'}, - {'2'}, - {'3'}, - {'4'}, - {'5'}, + {Key::FUNCTION_F2}, + {Key::FUNCTION_F3}, + {Key::FUNCTION_F4}, + {Key::FUNCTION_F5}, {Key::ESC}, {'q', 'Q'}, {'w', 'W'}, @@ -141,6 +141,7 @@ void HackadayCommunicatorKeyboard::pressed(uint8_t key) if (state == Init || state == Busy) { return; } + LOG_DEBUG("Key pressed: %u", key); if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) { modifierFlag = 0; diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp index 0aa78e2b8..c0a56233f 100644 --- a/src/input/InputBroker.cpp +++ b/src/input/InputBroker.cpp @@ -1,8 +1,59 @@ #include "InputBroker.h" #include "PowerFSM.h" // needed for event trigger #include "configuration.h" +#include "graphics/Screen.h" #include "modules/ExternalNotificationModule.h" +#if ARCH_PORTDUINO +#include "input/LinuxInputImpl.h" +#include "input/SeesawRotary.h" +#include "platform/portduino/PortduinoGlue.h" +#endif + +#if !MESHTASTIC_EXCLUDE_INPUTBROKER +#include "input/ExpressLRSFiveWay.h" +#include "input/RotaryEncoderImpl.h" +#include "input/RotaryEncoderInterruptImpl1.h" +#include "input/SerialKeyboardImpl.h" +#include "input/UpDownInterruptImpl1.h" +#include "input/i2cButton.h" +#if HAS_TRACKBALL +#include "input/TrackballInterruptImpl1.h" +#endif + +#include "modules/StatusLEDModule.h" + +#if !MESHTASTIC_EXCLUDE_I2C +#include "input/cardKbI2cImpl.h" +#endif +#include "input/kbMatrixImpl.h" +#endif + +#if HAS_BUTTON || defined(ARCH_PORTDUINO) +#include "input/ButtonThread.h" + +#if defined(BUTTON_PIN_TOUCH) +ButtonThread *TouchButtonThread = nullptr; +#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN) +static bool touchBacklightWasOn = false; +static bool touchBacklightActive = false; +#endif +#endif + +#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) +ButtonThread *UserButtonThread = nullptr; +#endif + +#if defined(ALT_BUTTON_PIN) +ButtonThread *BackButtonThread = nullptr; +#endif + +#if defined(CANCEL_BUTTON_PIN) +ButtonThread *CancelButtonThread = nullptr; +#endif + +#endif + InputBroker *inputBroker = nullptr; InputBroker::InputBroker() @@ -74,3 +125,262 @@ void InputBroker::pollSoonWorker(void *p) vTaskDelete(NULL); } #endif + +void InputBroker::Init() +{ + +#ifdef BUTTON_PIN +#ifdef ARCH_ESP32 + +#if ESP_ARDUINO_VERSION_MAJOR >= 3 +#ifdef BUTTON_NEED_PULLUP + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT_PULLUP); +#else + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN +#endif +#else + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN +#ifdef BUTTON_NEED_PULLUP + gpio_pullup_en((gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); + delay(10); +#endif +#ifdef BUTTON_NEED_PULLUP2 + gpio_pullup_en((gpio_num_t)BUTTON_NEED_PULLUP2); + delay(10); +#endif +#endif +#endif +#endif + +// buttons are now inputBroker, so have to come after setupModules +#if HAS_BUTTON + int pullup_sense = 0; +#ifdef INPUT_PULLUP_SENSE + // Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did +#ifdef BUTTON_SENSE_TYPE + pullup_sense = BUTTON_SENSE_TYPE; +#else + pullup_sense = INPUT_PULLUP_SENSE; +#endif +#endif +#if defined(ARCH_PORTDUINO) + + if (portduino_config.userButtonPin.enabled) { + + LOG_DEBUG("Use GPIO%02d for button", portduino_config.userButtonPin.pin); + UserButtonThread = new ButtonThread("UserButton"); + if (screen) { + ButtonConfig config; + config.pinNumber = (uint8_t)portduino_config.userButtonPin.pin; + config.activeLow = true; + config.activePullup = true; + config.pullupSense = INPUT_PULLUP; + config.intRoutine = []() { + UserButtonThread->userButton.tick(); + UserButtonThread->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + concurrency::mainDelay.interruptFromISR(&higherWake); + }; + config.singlePress = INPUT_BROKER_USER_PRESS; + config.longPress = INPUT_BROKER_SELECT; + UserButtonThread->initButton(config); + } + } +#endif + +#ifdef BUTTON_PIN_TOUCH + TouchButtonThread = new ButtonThread("BackButton"); + ButtonConfig touchConfig; + touchConfig.pinNumber = BUTTON_PIN_TOUCH; + touchConfig.activeLow = true; + touchConfig.activePullup = true; + touchConfig.pullupSense = pullup_sense; + touchConfig.intRoutine = []() { + TouchButtonThread->userButton.tick(); + TouchButtonThread->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + concurrency::mainDelay.interruptFromISR(&higherWake); + }; + touchConfig.singlePress = INPUT_BROKER_NONE; + touchConfig.longPress = INPUT_BROKER_BACK; +#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN) + // On T-Echo Plus the touch pad should only drive the backlight, not UI navigation/sounds + touchConfig.longPress = INPUT_BROKER_NONE; + touchConfig.suppressLeadUpSound = true; + touchConfig.onPress = []() { + touchBacklightWasOn = uiconfig.screen_brightness == 1; + if (!touchBacklightWasOn) { + digitalWrite(PIN_EINK_EN, HIGH); + } + touchBacklightActive = true; + }; + touchConfig.onRelease = []() { + if (touchBacklightActive && !touchBacklightWasOn) { + digitalWrite(PIN_EINK_EN, LOW); + } + touchBacklightActive = false; + }; +#endif + TouchButtonThread->initButton(touchConfig); +#endif + +#if defined(CANCEL_BUTTON_PIN) + // Buttons. Moved here cause we need NodeDB to be initialized + CancelButtonThread = new ButtonThread("CancelButton"); + ButtonConfig cancelConfig; + cancelConfig.pinNumber = CANCEL_BUTTON_PIN; + cancelConfig.activeLow = CANCEL_BUTTON_ACTIVE_LOW; + cancelConfig.activePullup = CANCEL_BUTTON_ACTIVE_PULLUP; + cancelConfig.pullupSense = pullup_sense; + cancelConfig.intRoutine = []() { + CancelButtonThread->userButton.tick(); + CancelButtonThread->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + concurrency::mainDelay.interruptFromISR(&higherWake); + }; + cancelConfig.singlePress = INPUT_BROKER_CANCEL; + cancelConfig.longPress = INPUT_BROKER_SHUTDOWN; + cancelConfig.longPressTime = 4000; + CancelButtonThread->initButton(cancelConfig); +#endif + +#if defined(ALT_BUTTON_PIN) + // Buttons. Moved here cause we need NodeDB to be initialized + BackButtonThread = new ButtonThread("BackButton"); + ButtonConfig backConfig; + backConfig.pinNumber = ALT_BUTTON_PIN; + backConfig.activeLow = ALT_BUTTON_ACTIVE_LOW; + backConfig.activePullup = ALT_BUTTON_ACTIVE_PULLUP; + backConfig.pullupSense = pullup_sense; + backConfig.intRoutine = []() { + BackButtonThread->userButton.tick(); + BackButtonThread->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + concurrency::mainDelay.interruptFromISR(&higherWake); + }; + backConfig.singlePress = INPUT_BROKER_ALT_PRESS; + backConfig.longPress = INPUT_BROKER_ALT_LONG; + backConfig.longPressTime = 500; + BackButtonThread->initButton(backConfig); +#endif + +#if defined(BUTTON_PIN) +#if defined(USERPREFS_BUTTON_PIN) + int _pinNum = config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN; +#else + int _pinNum = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; +#endif +#ifndef BUTTON_ACTIVE_LOW +#define BUTTON_ACTIVE_LOW true +#endif +#ifndef BUTTON_ACTIVE_PULLUP +#define BUTTON_ACTIVE_PULLUP true +#endif + + // Buttons. Moved here cause we need NodeDB to be initialized + // If your variant.h has a BUTTON_PIN defined, go ahead and define BUTTON_ACTIVE_LOW and BUTTON_ACTIVE_PULLUP + UserButtonThread = new ButtonThread("UserButton"); + if (screen) { + ButtonConfig userConfig; + userConfig.pinNumber = (uint8_t)_pinNum; + userConfig.activeLow = BUTTON_ACTIVE_LOW; + userConfig.activePullup = BUTTON_ACTIVE_PULLUP; + userConfig.pullupSense = pullup_sense; + userConfig.intRoutine = []() { + UserButtonThread->userButton.tick(); + UserButtonThread->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + concurrency::mainDelay.interruptFromISR(&higherWake); + }; + userConfig.singlePress = INPUT_BROKER_USER_PRESS; + userConfig.longPress = INPUT_BROKER_SELECT; + userConfig.longPressTime = 500; + userConfig.longLongPress = INPUT_BROKER_SHUTDOWN; + UserButtonThread->initButton(userConfig); + } else { + ButtonConfig userConfigNoScreen; + userConfigNoScreen.pinNumber = (uint8_t)_pinNum; + userConfigNoScreen.activeLow = BUTTON_ACTIVE_LOW; + userConfigNoScreen.activePullup = BUTTON_ACTIVE_PULLUP; + userConfigNoScreen.pullupSense = pullup_sense; + userConfigNoScreen.intRoutine = []() { + UserButtonThread->userButton.tick(); + UserButtonThread->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + concurrency::mainDelay.interruptFromISR(&higherWake); + }; + userConfigNoScreen.singlePress = INPUT_BROKER_USER_PRESS; + userConfigNoScreen.longPress = INPUT_BROKER_NONE; + userConfigNoScreen.longPressTime = 500; + userConfigNoScreen.longLongPress = INPUT_BROKER_SHUTDOWN; + userConfigNoScreen.doublePress = INPUT_BROKER_SEND_PING; + userConfigNoScreen.triplePress = INPUT_BROKER_GPS_TOGGLE; + UserButtonThread->initButton(userConfigNoScreen); + } +#endif +#endif + +#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER + if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { +#if defined(T_LORA_PAGER) + // use a special FSM based rotary encoder version for T-LoRa Pager + rotaryEncoderImpl = new RotaryEncoderImpl(); + if (!rotaryEncoderImpl->init()) { + delete rotaryEncoderImpl; + rotaryEncoderImpl = nullptr; + } +#elif defined(INPUTDRIVER_ENCODER_TYPE) && (INPUTDRIVER_ENCODER_TYPE == 2) + upDownInterruptImpl1 = new UpDownInterruptImpl1(); + if (!upDownInterruptImpl1->init()) { + delete upDownInterruptImpl1; + upDownInterruptImpl1 = nullptr; + } +#else + rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1(); + if (!rotaryEncoderInterruptImpl1->init()) { + delete rotaryEncoderInterruptImpl1; + rotaryEncoderInterruptImpl1 = nullptr; + } +#endif + cardKbI2cImpl = new CardKbI2cImpl(); + cardKbI2cImpl->init(); +#if defined(M5STACK_UNITC6L) + i2cButton = new i2cButtonThread("i2cButtonThread"); +#endif +#ifdef INPUTBROKER_MATRIX_TYPE + kbMatrixImpl = new KbMatrixImpl(); + kbMatrixImpl->init(); +#endif // INPUTBROKER_MATRIX_TYPE +#ifdef INPUTBROKER_SERIAL_TYPE + aSerialKeyboardImpl = new SerialKeyboardImpl(); + aSerialKeyboardImpl->init(); +#endif // INPUTBROKER_MATRIX_TYPE + } +#endif // HAS_BUTTON +#if ARCH_PORTDUINO + if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR && portduino_config.i2cdev != "") { + seesawRotary = new SeesawRotary("SeesawRotary"); + if (!seesawRotary->init()) { + delete seesawRotary; + seesawRotary = nullptr; + } + aLinuxInputImpl = new LinuxInputImpl(); + aLinuxInputImpl->init(); + } +#endif +#if !MESHTASTIC_EXCLUDE_INPUTBROKER && HAS_TRACKBALL + if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { + trackballInterruptImpl1 = new TrackballInterruptImpl1(); + trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS); + } +#endif +#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE + expressLRSFiveWayInput = new ExpressLRSFiveWay(); +#endif +} diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h index c55d7fa53..9fcdd845f 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -1,6 +1,7 @@ #pragma once #include "Observer.h" +#include "concurrency/OSThread.h" #include "freertosinc.h" #ifdef InputBrokerDebug @@ -27,6 +28,11 @@ enum input_broker_event { INPUT_BROKER_SHUTDOWN = 0x9b, INPUT_BROKER_GPS_TOGGLE = 0x9e, INPUT_BROKER_SEND_PING = 0xaf, + INPUT_BROKER_FN_F1 = 0xf1, + INPUT_BROKER_FN_F2 = 0xf2, + INPUT_BROKER_FN_F3 = 0xf3, + INPUT_BROKER_FN_F4 = 0xf4, + INPUT_BROKER_FN_F5 = 0xf5, INPUT_BROKER_MATRIXKEY = 0xFE, INPUT_BROKER_ANYKEY = 0xff @@ -71,6 +77,7 @@ class InputBroker : public Observable void queueInputEvent(const InputEvent *event); void processInputEventQueue(); #endif + void Init(); protected: int handleInputEvent(const InputEvent *event); @@ -84,4 +91,5 @@ class InputBroker : public Observable #endif }; -extern InputBroker *inputBroker; \ No newline at end of file +extern InputBroker *inputBroker; +extern bool runASAP; \ No newline at end of file diff --git a/src/input/SerialKeyboard.cpp b/src/input/SerialKeyboard.cpp index a5d2c614f..8037b0d57 100644 --- a/src/input/SerialKeyboard.cpp +++ b/src/input/SerialKeyboard.cpp @@ -5,7 +5,6 @@ SerialKeyboard *globalSerialKeyboard = nullptr; #ifdef INPUTBROKER_SERIAL_TYPE -#define CANNED_MESSAGE_MODULE_ENABLE 1 // in case it's not set in the variant file #if INPUTBROKER_SERIAL_TYPE == 1 // It's a Chatter // 3 SHIFT level (lower case, upper case, numbers), up to 4 repeated presses, button number diff --git a/src/input/TCA8418KeyboardBase.h b/src/input/TCA8418KeyboardBase.h index 8e509ac7e..e608c6da5 100644 --- a/src/input/TCA8418KeyboardBase.h +++ b/src/input/TCA8418KeyboardBase.h @@ -26,7 +26,12 @@ class TCA8418KeyboardBase GPS_TOGGLE = 0x9E, MUTE_TOGGLE = 0xAC, SEND_PING = 0xAF, - BL_TOGGLE = 0xAB + BL_TOGGLE = 0xAB, + FUNCTION_F1 = 0xF1, + FUNCTION_F2 = 0xF2, + FUNCTION_F3 = 0xF3, + FUNCTION_F4 = 0xF4, + FUNCTION_F5 = 0xF5 }; typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len); diff --git a/src/input/TrackballInterruptBase.cpp b/src/input/TrackballInterruptBase.cpp index bbd07e199..1bbe75629 100644 --- a/src/input/TrackballInterruptBase.cpp +++ b/src/input/TrackballInterruptBase.cpp @@ -1,5 +1,7 @@ #include "TrackballInterruptBase.h" +#include "Throttle.h" #include "configuration.h" + extern bool osk_found; TrackballInterruptBase::TrackballInterruptBase(const char *name) : concurrency::OSThread(name), _originName(name) {} @@ -55,17 +57,27 @@ int32_t TrackballInterruptBase::runOnce() { InputEvent e = {}; e.inputEvent = INPUT_BROKER_NONE; +#if TB_THRESHOLD + if (lastInterruptTime && !Throttle::isWithinTimespanMs(lastInterruptTime, 1000)) { + left_counter = 0; + right_counter = 0; + up_counter = 0; + down_counter = 0; + lastInterruptTime = 0; + } +#ifdef INPUT_DEBUG + if (left_counter > 0 || right_counter > 0 || up_counter > 0 || down_counter > 0) { + LOG_DEBUG("L %u R %u U %u D %u, time %u", left_counter, right_counter, up_counter, down_counter, millis()); + } +#endif +#endif // Handle long press detection for press button if (pressDetected && pressStartTime > 0) { uint32_t pressDuration = millis() - pressStartTime; bool buttonStillPressed = false; -#if defined(T_DECK) - buttonStillPressed = (this->action == TB_ACTION_PRESSED); -#else buttonStillPressed = !digitalRead(_pinPress); -#endif if (!buttonStillPressed) { // Button released @@ -134,23 +146,31 @@ int32_t TrackballInterruptBase::runOnce() } } -#if defined(T_DECK) // T-deck gets a super-simple debounce on trackball - if (this->action == TB_ACTION_PRESSED && !pressDetected) { +#if TB_THRESHOLD + if (this->action == TB_ACTION_PRESSED && (!pressDetected || pressStartTime == 0)) { // Start long press detection pressDetected = true; pressStartTime = millis(); // Don't send event yet, wait to see if it's a long press - } else if (this->action == TB_ACTION_UP && lastEvent == TB_ACTION_UP) { - // LOG_DEBUG("Trackball event UP"); + } else if (up_counter >= TB_THRESHOLD) { +#ifdef INPUT_DEBUG + LOG_DEBUG("Trackball event UP %u", millis()); +#endif e.inputEvent = this->_eventUp; - } else if (this->action == TB_ACTION_DOWN && lastEvent == TB_ACTION_DOWN) { - // LOG_DEBUG("Trackball event DOWN"); + } else if (down_counter >= TB_THRESHOLD) { +#ifdef INPUT_DEBUG + LOG_DEBUG("Trackball event DOWN %u", millis()); +#endif e.inputEvent = this->_eventDown; - } else if (this->action == TB_ACTION_LEFT && lastEvent == TB_ACTION_LEFT) { - // LOG_DEBUG("Trackball event LEFT"); + } else if (left_counter >= TB_THRESHOLD) { +#ifdef INPUT_DEBUG + LOG_DEBUG("Trackball event LEFT %u", millis()); +#endif e.inputEvent = this->_eventLeft; - } else if (this->action == TB_ACTION_RIGHT && lastEvent == TB_ACTION_RIGHT) { - // LOG_DEBUG("Trackball event RIGHT"); + } else if (right_counter >= TB_THRESHOLD) { +#ifdef INPUT_DEBUG + LOG_DEBUG("Trackball event RIGHT %u", millis()); +#endif e.inputEvent = this->_eventRight; } #else @@ -183,6 +203,12 @@ int32_t TrackballInterruptBase::runOnce() e.source = this->_originName; e.kbchar = 0x00; this->notifyObservers(&e); +#if TB_THRESHOLD + left_counter = 0; + right_counter = 0; + up_counter = 0; + down_counter = 0; +#endif } // Only update lastEvent for non-press actions or completed press actions @@ -198,25 +224,49 @@ int32_t TrackballInterruptBase::runOnce() void TrackballInterruptBase::intPressHandler() { - this->action = TB_ACTION_PRESSED; + if (!Throttle::isWithinTimespanMs(lastInterruptTime, 10)) + this->action = TB_ACTION_PRESSED; + lastInterruptTime = millis(); } void TrackballInterruptBase::intDownHandler() { - this->action = TB_ACTION_DOWN; + if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10)) + this->action = TB_ACTION_DOWN; + lastInterruptTime = millis(); + +#if TB_THRESHOLD + down_counter++; +#endif } void TrackballInterruptBase::intUpHandler() { - this->action = TB_ACTION_UP; + if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10)) + this->action = TB_ACTION_UP; + lastInterruptTime = millis(); + +#if TB_THRESHOLD + up_counter++; +#endif } void TrackballInterruptBase::intLeftHandler() { - this->action = TB_ACTION_LEFT; + if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10)) + this->action = TB_ACTION_LEFT; + lastInterruptTime = millis(); +#if TB_THRESHOLD + left_counter++; +#endif } void TrackballInterruptBase::intRightHandler() { - this->action = TB_ACTION_RIGHT; + if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10)) + this->action = TB_ACTION_RIGHT; + lastInterruptTime = millis(); +#if TB_THRESHOLD + right_counter++; +#endif } diff --git a/src/input/TrackballInterruptBase.h b/src/input/TrackballInterruptBase.h index 67d4ee449..908f62769 100644 --- a/src/input/TrackballInterruptBase.h +++ b/src/input/TrackballInterruptBase.h @@ -12,6 +12,10 @@ #endif #endif +#ifndef TB_THRESHOLD +#define TB_THRESHOLD 0 +#endif + class TrackballInterruptBase : public Observable, public concurrency::OSThread { public: @@ -25,8 +29,6 @@ class TrackballInterruptBase : public Observable, public con void intUpHandler(); void intLeftHandler(); void intRightHandler(); - uint32_t lastTime = 0; - virtual int32_t runOnce() override; protected: @@ -67,4 +69,12 @@ class TrackballInterruptBase : public Observable, public con input_broker_event _eventPressedLong = INPUT_BROKER_NONE; const char *_originName; TrackballInterruptBaseActionType lastEvent = TB_ACTION_NONE; + volatile uint32_t lastInterruptTime = 0; + +#if TB_THRESHOLD + volatile uint8_t left_counter = 0; + volatile uint8_t right_counter = 0; + volatile uint8_t up_counter = 0; + volatile uint8_t down_counter = 0; +#endif }; diff --git a/src/input/TrackballInterruptImpl1.cpp b/src/input/TrackballInterruptImpl1.cpp index 594facdeb..fd126913a 100644 --- a/src/input/TrackballInterruptImpl1.cpp +++ b/src/input/TrackballInterruptImpl1.cpp @@ -24,41 +24,26 @@ void TrackballInterruptImpl1::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLe void TrackballInterruptImpl1::handleIntDown() { - if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) { - trackballInterruptImpl1->lastTime = millis(); - trackballInterruptImpl1->intDownHandler(); - trackballInterruptImpl1->setIntervalFromNow(20); - } + trackballInterruptImpl1->intDownHandler(); + trackballInterruptImpl1->setIntervalFromNow(20); } void TrackballInterruptImpl1::handleIntUp() { - if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) { - trackballInterruptImpl1->lastTime = millis(); - trackballInterruptImpl1->intUpHandler(); - trackballInterruptImpl1->setIntervalFromNow(20); - } + trackballInterruptImpl1->intUpHandler(); + trackballInterruptImpl1->setIntervalFromNow(20); } void TrackballInterruptImpl1::handleIntLeft() { - if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) { - trackballInterruptImpl1->lastTime = millis(); - trackballInterruptImpl1->intLeftHandler(); - trackballInterruptImpl1->setIntervalFromNow(20); - } + trackballInterruptImpl1->intLeftHandler(); + trackballInterruptImpl1->setIntervalFromNow(20); } void TrackballInterruptImpl1::handleIntRight() { - if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) { - trackballInterruptImpl1->lastTime = millis(); - trackballInterruptImpl1->intRightHandler(); - trackballInterruptImpl1->setIntervalFromNow(20); - } + trackballInterruptImpl1->intRightHandler(); + trackballInterruptImpl1->setIntervalFromNow(20); } void TrackballInterruptImpl1::handleIntPressed() { - if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) { - trackballInterruptImpl1->lastTime = millis(); - trackballInterruptImpl1->intPressHandler(); - trackballInterruptImpl1->setIntervalFromNow(20); - } + trackballInterruptImpl1->intPressHandler(); + trackballInterruptImpl1->setIntervalFromNow(20); } diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 12d0822f6..d744ee2ca 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -321,6 +321,26 @@ int32_t KbI2cBase::runOnce() e.inputEvent = INPUT_BROKER_ANYKEY; e.kbchar = INPUT_BROKER_MSG_TAB; break; + case TCA8418KeyboardBase::FUNCTION_F1: + e.inputEvent = INPUT_BROKER_FN_F1; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::FUNCTION_F2: + e.inputEvent = INPUT_BROKER_FN_F2; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::FUNCTION_F3: + e.inputEvent = INPUT_BROKER_FN_F3; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::FUNCTION_F4: + e.inputEvent = INPUT_BROKER_FN_F4; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::FUNCTION_F5: + e.inputEvent = INPUT_BROKER_FN_F5; + e.kbchar = 0x00; + break; default: if (nextEvent > 127) { e.inputEvent = INPUT_BROKER_NONE; diff --git a/src/main.cpp b/src/main.cpp index 69678237d..20f81bd9f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,7 @@ #include "ReliableRouter.h" #include "airtime.h" #include "buzz.h" +#include "power/PowerHAL.h" #include "FSCommon.h" #include "Led.h" @@ -42,10 +43,6 @@ #include "MessageStore.h" #endif -#ifdef ELECROW_ThinkNode_M5 -PCA9557 io(0x18, &Wire); -#endif - #ifdef ARCH_ESP32 #include "freertosinc.h" #if !MESHTASTIC_EXCLUDE_WEBSERVER @@ -76,59 +73,52 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr; #include "mqtt/MQTT.h" #endif -#include "LLCC68Interface.h" -#include "LR1110Interface.h" -#include "LR1120Interface.h" -#include "LR1121Interface.h" -#include "RF95Interface.h" -#include "SX1262Interface.h" -#include "SX1268Interface.h" -#include "SX1280Interface.h" -#include "detect/LoRaRadioType.h" - -#ifdef ARCH_STM32WL -#include "STM32WLE5JCInterface.h" -#endif - -#if defined(ARCH_PORTDUINO) -#include "platform/portduino/SimRadio.h" -#endif - #ifdef ARCH_PORTDUINO #include "linux/LinuxHardwareI2C.h" #include "mesh/raspihttp/PiWebServer.h" #include "platform/portduino/PortduinoGlue.h" -#include "platform/portduino/USBHal.h" #include #include #include #include #endif -#if HAS_BUTTON || defined(ARCH_PORTDUINO) -#include "input/ButtonThread.h" +#ifdef ARCH_ESP32 +#ifdef DEBUG_PARTITION_TABLE +#include "esp_partition.h" -#if defined(BUTTON_PIN_TOUCH) -ButtonThread *TouchButtonThread = nullptr; -#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN) -static bool touchBacklightWasOn = false; -static bool touchBacklightActive = false; -#endif -#endif +void printPartitionTable() +{ + printf("\n--- Partition Table ---\n"); + // Print Column Headers + printf("| %-16s | %-4s | %-7s | %-10s | %-10s |\n", "Label", "Type", "Subtype", "Offset", "Size"); + printf("|------------------|------|---------|------------|------------|\n"); -#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) -ButtonThread *UserButtonThread = nullptr; -#endif + // Create an iterator to find ALL partitions (Type ANY, Subtype ANY) + esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL); -#if defined(ALT_BUTTON_PIN) -ButtonThread *BackButtonThread = nullptr; -#endif + // Loop through the iterator + if (it != NULL) { + do { + const esp_partition_t *part = esp_partition_get(it); -#if defined(CANCEL_BUTTON_PIN) -ButtonThread *CancelButtonThread = nullptr; -#endif + // Print details: Label, Type (Hex), Subtype (Hex), Offset (Hex), Size (Hex) + printf("| %-16s | 0x%02x | 0x%02x | 0x%08x | 0x%08x |\n", part->label, part->type, part->subtype, part->address, + part->size); -#endif + // Move to next partition + it = esp_partition_next(it); + } while (it != NULL); + + // Release the iterator memory + esp_partition_iterator_release(it); + } else { + printf("No partitions found.\n"); + } + printf("-----------------------\n"); +} +#endif // DEBUG_PARTITION_TABLE +#endif // ARCH_ESP32 #include "AmbientLightingThread.h" #include "PowerFSMThread.h" @@ -216,9 +206,6 @@ ScanI2C::DeviceAddress aqi_found = ScanI2C::ADDRESS_NONE; Adafruit_DRV2605 drv; #endif -// Global LoRa radio type -LoRaRadioType radioType = NO_RADIO; - bool isVibrating = false; bool eink_found = true; @@ -255,6 +242,7 @@ const char *getDeviceName() return name; } +// TODO remove from main.cpp static int32_t ledBlinker() { // Still set up the blinking (heartbeat) interval but skip code path below, so LED will blink if @@ -295,6 +283,46 @@ __attribute__((weak, noinline)) bool loopCanSleep() void lateInitVariant() __attribute__((weak)); void lateInitVariant() {} +void earlyInitVariant() __attribute__((weak)); +void earlyInitVariant() {} + +// NRF52 (and probably other platforms) can report when system is in power failure mode +// (eg. too low battery voltage) and operating it is unsafe (data corruption, bootloops, etc). +// For example NRF52 will prevent any flash writes in that case automatically +// (but it causes issues we need to handle). +// This detection is independent from whatever ADC or dividers used in Meshtastic +// boards and is internal to chip. + +// we use powerHAL layer to get this info and delay booting until power level is safe + +// wait until power level is safe to continue booting (to avoid bootloops) +// blink user led in 3 flashes sequence to indicate what is happening +void waitUntilPowerLevelSafe() +{ + +#ifdef LED_PIN + pinMode(LED_PIN, OUTPUT); +#endif + + while (powerHAL_isPowerLevelSafe() == false) { + +#ifdef LED_PIN + + // 3x: blink for 300 ms, pause for 300 ms + + for (int i = 0; i < 3; i++) { + digitalWrite(LED_PIN, LED_STATE_ON); + delay(300); + digitalWrite(LED_PIN, LED_STATE_OFF); + delay(300); + } +#endif + + // sleep for 2s + delay(2000); + } +} + /** * Print info as a structured log message (for automated log processing) */ @@ -305,34 +333,30 @@ void printInfo() #ifndef PIO_UNIT_TESTING void setup() { -#if defined(R1_NEO) - pinMode(DCDC_EN_HOLD, OUTPUT); - digitalWrite(DCDC_EN_HOLD, HIGH); - pinMode(NRF_ON, OUTPUT); - digitalWrite(NRF_ON, HIGH); -#endif + + // initialize power HAL layer as early as possible + powerHAL_init(); + + // prevent booting if device is in power failure mode + // boot sequence will follow when battery level raises to safe mode + waitUntilPowerLevelSafe(); + + // Defined in variant.cpp for early init code + earlyInitVariant(); #if defined(PIN_POWER_EN) pinMode(PIN_POWER_EN, OUTPUT); digitalWrite(PIN_POWER_EN, HIGH); #endif -#if defined(ELECROW_ThinkNode_M5) - Wire.begin(48, 47); - io.pinMode(PCA_PIN_EINK_EN, OUTPUT); - io.pinMode(PCA_PIN_POWER_EN, OUTPUT); - io.digitalWrite(PCA_PIN_POWER_EN, HIGH); - // io.pinMode(C2_PIN, OUTPUT); -#endif - #ifdef LED_POWER pinMode(LED_POWER, OUTPUT); digitalWrite(LED_POWER, LED_STATE_ON); #endif -#ifdef USER_LED - pinMode(USER_LED, OUTPUT); - digitalWrite(USER_LED, HIGH ^ LED_STATE_ON); +#ifdef LED_NOTIFICATION + pinMode(LED_NOTIFICATION, OUTPUT); + digitalWrite(LED_NOTIFICATION, HIGH ^ LED_STATE_ON); #endif #ifdef WIFI_LED @@ -342,75 +366,10 @@ 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 - // Turn on peripheral power immediately after MUC starts. - // If some boards are turned on late, ESP32 will reset due to low voltage. - // ESP32-C3(Keyboard) , MAX98357A(Audio Power Amplifier) , - // TF Card , Display backlight(AW9364DNR) , AN48841B(Trackball) , ES7210(Decoder) - pinMode(KB_POWERON, OUTPUT); - digitalWrite(KB_POWERON, HIGH); - // T-Deck has all three SPI peripherals (TFT, SD, LoRa) attached to the same SPI bus - // We need to initialize all CS pins in advance otherwise there will be SPI communication issues - // e.g. when detecting the SD card - pinMode(LORA_CS, OUTPUT); - digitalWrite(LORA_CS, HIGH); - pinMode(SDCARD_CS, OUTPUT); - digitalWrite(SDCARD_CS, HIGH); - pinMode(TFT_CS, OUTPUT); - digitalWrite(TFT_CS, HIGH); - delay(100); -#elif defined(T_DECK_PRO) - pinMode(LORA_EN, OUTPUT); - digitalWrite(LORA_EN, HIGH); - pinMode(LORA_CS, OUTPUT); - digitalWrite(LORA_CS, HIGH); - pinMode(SDCARD_CS, OUTPUT); - digitalWrite(SDCARD_CS, HIGH); - pinMode(PIN_EINK_CS, OUTPUT); - digitalWrite(PIN_EINK_CS, HIGH); -#elif defined(T_LORA_PAGER) - pinMode(LORA_CS, OUTPUT); - digitalWrite(LORA_CS, HIGH); - pinMode(SDCARD_CS, OUTPUT); - digitalWrite(SDCARD_CS, HIGH); - pinMode(TFT_CS, OUTPUT); - digitalWrite(TFT_CS, HIGH); - pinMode(KB_INT, INPUT_PULLUP); - // io expander - io.begin(Wire, XL9555_SLAVE_ADDRESS0, SDA, SCL); - io.pinMode(EXPANDS_DRV_EN, OUTPUT); - io.digitalWrite(EXPANDS_DRV_EN, HIGH); - io.pinMode(EXPANDS_AMP_EN, OUTPUT); - io.digitalWrite(EXPANDS_AMP_EN, LOW); - io.pinMode(EXPANDS_LORA_EN, OUTPUT); - io.digitalWrite(EXPANDS_LORA_EN, HIGH); - io.pinMode(EXPANDS_GPS_EN, OUTPUT); - io.digitalWrite(EXPANDS_GPS_EN, HIGH); - io.pinMode(EXPANDS_KB_EN, OUTPUT); - io.digitalWrite(EXPANDS_KB_EN, HIGH); - io.pinMode(EXPANDS_SD_EN, OUTPUT); - io.digitalWrite(EXPANDS_SD_EN, HIGH); - io.pinMode(EXPANDS_GPIO_EN, OUTPUT); - io.digitalWrite(EXPANDS_GPIO_EN, HIGH); - io.pinMode(EXPANDS_SD_PULLEN, INPUT); -#elif defined(HACKADAY_COMMUNICATOR) - pinMode(KB_INT, INPUT); + digitalWrite(BLE_LED, LED_STATE_OFF); #endif concurrency::hasBeenSetup = true; -#if ARCH_PORTDUINO - SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0); -#else - SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); -#endif meshtastic_Config_DisplayConfig_OledType screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO; @@ -521,39 +480,18 @@ void setup() LOG_INFO("Wait for peripherals to stabilize"); delay(PERIPHERAL_WARMUP_MS); #endif - -#ifdef BUTTON_PIN -#ifdef ARCH_ESP32 - -#if ESP_ARDUINO_VERSION_MAJOR >= 3 -#ifdef BUTTON_NEED_PULLUP - pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT_PULLUP); -#else - pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN -#endif -#else - pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN -#ifdef BUTTON_NEED_PULLUP - gpio_pullup_en((gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); - delay(10); -#endif -#ifdef BUTTON_NEED_PULLUP2 - gpio_pullup_en((gpio_num_t)BUTTON_NEED_PULLUP2); - delay(10); -#endif -#endif -#endif -#endif - initSPI(); OSThread::setup(); + // TODO make this ifdef based on defined pins and move from main.cpp #if defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2) // The ThinkNodes have their own blink logic // ledPeriodic = new Periodic("Blink", elecrowLedBlinker); #else + ledPeriodic = new Periodic("Blink", ledBlinker); + #endif fsInit(); @@ -648,7 +586,11 @@ void setup() sensor_detected = true; #endif } - +#ifdef ARCH_ESP32 +#ifdef DEBUG_PARTITION_TABLE + printPartitionTable(); +#endif +#endif // ARCH_ESP32 #ifdef ARCH_ESP32 // Don't init display if we don't have one or we are waking headless due to a timer event if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) { @@ -890,7 +832,7 @@ void setup() SPI.begin(); #endif #else -// ESP32 + // ESP32 #if 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); @@ -922,6 +864,7 @@ void setup() } #endif // HAS_SCREEN + // TODO Remove magic string // setup TZ prior to time actions. #if !MESHTASTIC_EXCLUDE_TZ LOG_DEBUG("Use compiled/slipstreamed %s", slipstreamTZString); // important, removing this clobbers our magic string @@ -1005,180 +948,9 @@ void setup() nodeDB->hasWarned = true; } #endif - -// buttons are now inputBroker, so have to come after setupModules -#if HAS_BUTTON - int pullup_sense = 0; -#ifdef INPUT_PULLUP_SENSE - // Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did -#ifdef BUTTON_SENSE_TYPE - pullup_sense = BUTTON_SENSE_TYPE; -#else - pullup_sense = INPUT_PULLUP_SENSE; -#endif -#endif -#if defined(ARCH_PORTDUINO) - - if (portduino_config.userButtonPin.enabled) { - - LOG_DEBUG("Use GPIO%02d for button", portduino_config.userButtonPin.pin); - UserButtonThread = new ButtonThread("UserButton"); - if (screen) { - ButtonConfig config; - config.pinNumber = (uint8_t)portduino_config.userButtonPin.pin; - config.activeLow = true; - config.activePullup = true; - config.pullupSense = INPUT_PULLUP; - config.intRoutine = []() { - UserButtonThread->userButton.tick(); - UserButtonThread->setIntervalFromNow(0); - runASAP = true; - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); - }; - config.singlePress = INPUT_BROKER_USER_PRESS; - config.longPress = INPUT_BROKER_SELECT; - UserButtonThread->initButton(config); - } - } -#endif - -#ifdef BUTTON_PIN_TOUCH - TouchButtonThread = new ButtonThread("BackButton"); - ButtonConfig touchConfig; - touchConfig.pinNumber = BUTTON_PIN_TOUCH; - touchConfig.activeLow = true; - touchConfig.activePullup = true; - touchConfig.pullupSense = pullup_sense; - touchConfig.intRoutine = []() { - TouchButtonThread->userButton.tick(); - TouchButtonThread->setIntervalFromNow(0); - runASAP = true; - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); - }; - touchConfig.singlePress = INPUT_BROKER_NONE; - touchConfig.longPress = INPUT_BROKER_BACK; -#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN) - // On T-Echo Plus the touch pad should only drive the backlight, not UI navigation/sounds - touchConfig.longPress = INPUT_BROKER_NONE; - touchConfig.suppressLeadUpSound = true; - touchConfig.onPress = []() { - touchBacklightWasOn = uiconfig.screen_brightness == 1; - if (!touchBacklightWasOn) { - digitalWrite(PIN_EINK_EN, HIGH); - } - touchBacklightActive = true; - }; - touchConfig.onRelease = []() { - if (touchBacklightActive && !touchBacklightWasOn) { - digitalWrite(PIN_EINK_EN, LOW); - } - touchBacklightActive = false; - }; -#endif - TouchButtonThread->initButton(touchConfig); -#endif - -#if defined(CANCEL_BUTTON_PIN) - // Buttons. Moved here cause we need NodeDB to be initialized - CancelButtonThread = new ButtonThread("CancelButton"); - ButtonConfig cancelConfig; - cancelConfig.pinNumber = CANCEL_BUTTON_PIN; - cancelConfig.activeLow = CANCEL_BUTTON_ACTIVE_LOW; - cancelConfig.activePullup = CANCEL_BUTTON_ACTIVE_PULLUP; - cancelConfig.pullupSense = pullup_sense; - cancelConfig.intRoutine = []() { - CancelButtonThread->userButton.tick(); - CancelButtonThread->setIntervalFromNow(0); - runASAP = true; - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); - }; - cancelConfig.singlePress = INPUT_BROKER_CANCEL; - cancelConfig.longPress = INPUT_BROKER_SHUTDOWN; - cancelConfig.longPressTime = 4000; - CancelButtonThread->initButton(cancelConfig); -#endif - -#if defined(ALT_BUTTON_PIN) - // Buttons. Moved here cause we need NodeDB to be initialized - BackButtonThread = new ButtonThread("BackButton"); - ButtonConfig backConfig; - backConfig.pinNumber = ALT_BUTTON_PIN; - backConfig.activeLow = ALT_BUTTON_ACTIVE_LOW; - backConfig.activePullup = ALT_BUTTON_ACTIVE_PULLUP; - backConfig.pullupSense = pullup_sense; - backConfig.intRoutine = []() { - BackButtonThread->userButton.tick(); - BackButtonThread->setIntervalFromNow(0); - runASAP = true; - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); - }; - backConfig.singlePress = INPUT_BROKER_ALT_PRESS; - backConfig.longPress = INPUT_BROKER_ALT_LONG; - backConfig.longPressTime = 500; - BackButtonThread->initButton(backConfig); -#endif - -#if defined(BUTTON_PIN) -#if defined(USERPREFS_BUTTON_PIN) - int _pinNum = config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN; -#else - int _pinNum = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; -#endif -#ifndef BUTTON_ACTIVE_LOW -#define BUTTON_ACTIVE_LOW true -#endif -#ifndef BUTTON_ACTIVE_PULLUP -#define BUTTON_ACTIVE_PULLUP true -#endif - - // Buttons. Moved here cause we need NodeDB to be initialized - // If your variant.h has a BUTTON_PIN defined, go ahead and define BUTTON_ACTIVE_LOW and BUTTON_ACTIVE_PULLUP - UserButtonThread = new ButtonThread("UserButton"); - if (screen) { - ButtonConfig userConfig; - userConfig.pinNumber = (uint8_t)_pinNum; - userConfig.activeLow = BUTTON_ACTIVE_LOW; - userConfig.activePullup = BUTTON_ACTIVE_PULLUP; - userConfig.pullupSense = pullup_sense; - userConfig.intRoutine = []() { - UserButtonThread->userButton.tick(); - UserButtonThread->setIntervalFromNow(0); - runASAP = true; - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); - }; - userConfig.singlePress = INPUT_BROKER_USER_PRESS; - userConfig.longPress = INPUT_BROKER_SELECT; - userConfig.longPressTime = 500; - userConfig.longLongPress = INPUT_BROKER_SHUTDOWN; - UserButtonThread->initButton(userConfig); - } else { - ButtonConfig userConfigNoScreen; - userConfigNoScreen.pinNumber = (uint8_t)_pinNum; - userConfigNoScreen.activeLow = BUTTON_ACTIVE_LOW; - userConfigNoScreen.activePullup = BUTTON_ACTIVE_PULLUP; - userConfigNoScreen.pullupSense = pullup_sense; - userConfigNoScreen.intRoutine = []() { - UserButtonThread->userButton.tick(); - UserButtonThread->setIntervalFromNow(0); - runASAP = true; - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); - }; - userConfigNoScreen.singlePress = INPUT_BROKER_USER_PRESS; - userConfigNoScreen.longPress = INPUT_BROKER_NONE; - userConfigNoScreen.longPressTime = 500; - userConfigNoScreen.longLongPress = INPUT_BROKER_SHUTDOWN; - userConfigNoScreen.doublePress = INPUT_BROKER_SEND_PING; - userConfigNoScreen.triplePress = INPUT_BROKER_GPS_TOGGLE; - UserButtonThread->initButton(userConfigNoScreen); - } -#endif - +#if !MESHTASTIC_EXCLUDE_INPUTBROKER + if (inputBroker) + inputBroker->Init(); #endif #ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS @@ -1217,252 +989,7 @@ void setup() #endif #endif -#ifdef ARCH_PORTDUINO - // as one can't use a function pointer to the class constructor: - auto loraModuleInterface = [](LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, - RADIOLIB_PIN_TYPE busy) { - switch (portduino_config.lora_module) { - case use_rf95: - return (RadioInterface *)new RF95Interface(hal, cs, irq, rst, busy); - case use_sx1262: - return (RadioInterface *)new SX1262Interface(hal, cs, irq, rst, busy); - case use_sx1268: - return (RadioInterface *)new SX1268Interface(hal, cs, irq, rst, busy); - case use_sx1280: - return (RadioInterface *)new SX1280Interface(hal, cs, irq, rst, busy); - case use_lr1110: - return (RadioInterface *)new LR1110Interface(hal, cs, irq, rst, busy); - case use_lr1120: - return (RadioInterface *)new LR1120Interface(hal, cs, irq, rst, busy); - case use_lr1121: - return (RadioInterface *)new LR1121Interface(hal, cs, irq, rst, busy); - case use_llcc68: - return (RadioInterface *)new LLCC68Interface(hal, cs, irq, rst, busy); - case use_simradio: - return (RadioInterface *)new SimRadio; - default: - assert(0); // shouldn't happen - return (RadioInterface *)nullptr; - } - }; - - LOG_DEBUG("Activate %s radio on SPI port %s", portduino_config.loraModules[portduino_config.lora_module].c_str(), - portduino_config.lora_spi_dev.c_str()); - if (portduino_config.lora_spi_dev == "ch341") { - RadioLibHAL = ch341Hal; - } else { - RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); - } - rIf = - loraModuleInterface((LockingArduinoHal *)RadioLibHAL, portduino_config.lora_cs_pin.pin, portduino_config.lora_irq_pin.pin, - portduino_config.lora_reset_pin.pin, portduino_config.lora_busy_pin.pin); - - if (!rIf->init()) { - LOG_WARN("No %s radio", portduino_config.loraModules[portduino_config.lora_module].c_str()); - delete rIf; - rIf = NULL; - exit(EXIT_FAILURE); - } else { - LOG_INFO("%s init success", portduino_config.loraModules[portduino_config.lora_module].c_str()); - } - -#elif defined(HW_SPI1_DEVICE) - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI1, spiSettings); -#else // HW_SPI1_DEVICE - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); -#endif - - // radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init) -#if defined(USE_STM32WLx) - if (!rIf) { - rIf = new STM32WLE5JCInterface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - if (!rIf->init()) { - LOG_WARN("No STM32WL radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("STM32WL init success"); - radioType = STM32WLx_RADIO; - } - } -#endif - -#if defined(RF95_IRQ) && RADIOLIB_EXCLUDE_SX127X != 1 - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1); - if (!rIf->init()) { - LOG_WARN("No RF95 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("RF95 init success"); - radioType = RF95_RADIO; - } - } -#endif - -#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1 - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); -#ifdef SX126X_DIO3_TCXO_VOLTAGE - sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); -#endif - if (!sxIf->init()) { - LOG_WARN("No SX1262 radio"); - delete sxIf; - rIf = NULL; - } else { - LOG_INFO("SX1262 init success"); - rIf = sxIf; - radioType = SX1262_RADIO; - } - } -#endif - -#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL) - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - // try using the specified TCXO voltage - auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); - if (!sxIf->init()) { - LOG_WARN("No SX1262 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - delete sxIf; - rIf = NULL; - } else { - LOG_INFO("SX1262 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - rIf = sxIf; - radioType = SX1262_RADIO; - } - } - - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - // If specified TCXO voltage fails, attempt to use DIO3 as a reference instead - rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - if (!rIf->init()) { - LOG_WARN("No SX1262 radio with XTAL, Vref 0.0V"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("SX1262 init success, XTAL, Vref 0.0V"); - radioType = SX1262_RADIO; - } - } -#endif - -#if defined(USE_SX1268) -#if defined(SX126X_DIO3_TCXO_VOLTAGE) && defined(TCXO_OPTIONAL) - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - // try using the specified TCXO voltage - auto *sxIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); - if (!sxIf->init()) { - LOG_WARN("No SX1268 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - delete sxIf; - rIf = NULL; - } else { - LOG_INFO("SX1268 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - rIf = sxIf; - radioType = SX1268_RADIO; - } - } -#endif - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - if (!rIf->init()) { - LOG_WARN("No SX1268 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("SX1268 init success"); - radioType = SX1268_RADIO; - } - } -#endif - -#if defined(USE_LLCC68) - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new LLCC68Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - if (!rIf->init()) { - LOG_WARN("No LLCC68 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("LLCC68 init success"); - radioType = LLCC68_RADIO; - } - } -#endif - -#if defined(USE_LR1110) && RADIOLIB_EXCLUDE_LR11X0 != 1 - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN); - if (!rIf->init()) { - LOG_WARN("No LR1110 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("LR1110 init success"); - radioType = LR1110_RADIO; - } - } -#endif - -#if defined(USE_LR1120) && RADIOLIB_EXCLUDE_LR11X0 != 1 - if (!rIf) { - rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN); - if (!rIf->init()) { - LOG_WARN("No LR1120 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("LR1120 init success"); - radioType = LR1120_RADIO; - } - } -#endif - -#if defined(USE_LR1121) && RADIOLIB_EXCLUDE_LR11X0 != 1 - if (!rIf) { - rIf = new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN); - if (!rIf->init()) { - LOG_WARN("No LR1121 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("LR1121 init success"); - radioType = LR1121_RADIO; - } - } -#endif - -#if defined(USE_SX1280) && RADIOLIB_EXCLUDE_SX128X != 1 - if (!rIf) { - rIf = new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY); - if (!rIf->init()) { - LOG_WARN("No SX1280 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("SX1280 init success"); - radioType = SX1280_RADIO; - } - } -#endif - - // check if the radio chip matches the selected region - if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && rIf && (!rIf->wideLora())) { - LOG_WARN("LoRa chip does not support 2.4GHz. Revert to unset"); - config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET; - nodeDB->saveToDisk(SEGMENT_CONFIG); - - if (!rIf->reconfigure()) { - LOG_WARN("Reconfigure failed, rebooting"); - if (screen) { - screen->showSimpleBanner("Rebooting..."); - } - rebootAtMsec = millis() + 5000; - } - } + initLoRa(); lateInitVariant(); // Do board specific init (see extra_variants/README.md for documentation) @@ -1551,6 +1078,7 @@ bool suppressRebootBanner; // If true, suppress "Rebooting..." overlay (used for // This will suppress the current delay and instead try to run ASAP. bool runASAP; +// TODO find better home than main.cpp extern meshtastic_DeviceMetadata getDeviceMetadata() { meshtastic_DeviceMetadata deviceMetadata; @@ -1651,7 +1179,43 @@ void loop() if (inputBroker) inputBroker->processInputEventQueue(); #endif -#if ARCH_PORTDUINO && HAS_TFT +#if ARCH_PORTDUINO + if (portduino_config.lora_spi_dev == "ch341" && ch341Hal != nullptr) { + ch341Hal->checkError(); + } + if (portduino_status.LoRa_in_error && rebootAtMsec == 0) { + LOG_ERROR("LoRa in error detected, attempting to recover"); + if (rIf != nullptr) { + delete rIf; + rIf = nullptr; + } + if (portduino_config.lora_spi_dev == "ch341") { + if (ch341Hal != nullptr) { + delete ch341Hal; + ch341Hal = nullptr; + sleep(3); + } + try { + ch341Hal = new Ch341Hal(0, portduino_config.lora_usb_serial_num, portduino_config.lora_usb_vid, + portduino_config.lora_usb_pid); + } catch (std::exception &e) { + std::cerr << e.what() << std::endl; + std::cerr << "Could not initialize CH341 device!" << std::endl; + exit(EXIT_FAILURE); + } + } + if (initLoRa()) { + router->addInterface(rIf); + portduino_status.LoRa_in_error = false; + } else { + LOG_WARN("Reconfigure failed, rebooting"); + if (screen) { + screen->showSimpleBanner("Rebooting..."); + } + rebootAtMsec = millis() + 25; + } + } +#if HAS_TFT if (screen && portduino_config.displayPanel == x11 && config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { auto dispdev = screen->getDisplayDevice(); @@ -1659,6 +1223,7 @@ void loop() static_cast(dispdev)->sdlLoop(); } #endif +#endif #if HAS_SCREEN && ENABLE_MESSAGE_PERSISTENCE messageStoreAutosaveTick(); #endif diff --git a/src/main.h b/src/main.h index c3528a63d..91e27951f 100644 --- a/src/main.h +++ b/src/main.h @@ -26,8 +26,8 @@ extern NRF52Bluetooth *nrf52Bluetooth; #if ARCH_PORTDUINO extern HardwareSPI *DisplaySPI; extern HardwareSPI *LoraSPI; - #endif + extern ScanI2C::DeviceAddress screen_found; extern ScanI2C::DeviceAddress cardkb_found; extern uint8_t kb_model; @@ -47,16 +47,16 @@ extern bool isUSBPowered; extern Adafruit_DRV2605 drv; #endif +#ifdef HAS_PCA9557 +#include +extern PCA9557 io; +#endif + #ifdef HAS_I2S #include "AudioThread.h" extern AudioThread *audioThread; #endif -#ifdef ELECROW_ThinkNode_M5 -#include -extern PCA9557 io; -#endif - #ifdef HAS_UDP_MULTICAST #include "mesh/udp/UdpMulticastHandler.h" extern UdpMulticastHandler *udpHandler; diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 9ca16878d..0f4d64113 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -61,11 +61,6 @@ bool CryptoEngine::regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey) return true; } #endif -void CryptoEngine::clearKeys() -{ - memset(public_key, 0, sizeof(public_key)); - memset(private_key, 0, sizeof(private_key)); -} /** * Encrypt a packet's payload using a key generated with Curve25519 and SHA256 diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h index 6bbcb3b8a..7689006ab 100644 --- a/src/mesh/CryptoEngine.h +++ b/src/mesh/CryptoEngine.h @@ -37,7 +37,6 @@ class CryptoEngine virtual bool regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey); #endif - void clearKeys(); void setDHPrivateKey(uint8_t *_private_key); virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic, uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut); diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index 341afe78d..7c73b56cd 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -91,10 +91,21 @@ template bool LR11x0Interface::init() LOG_DEBUG("Set RF1 switch to %s", getFreq() < 1e9 ? "SubGHz" : "2.4GHz"); #endif + // Allow extra time for TCXO to stabilize after power-on + delay(10); + int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage); + + // Retry if we get SPI command failed - some units need extra TCXO stabilization time + if (res == RADIOLIB_ERR_SPI_CMD_FAILED) { + LOG_WARN("LR11x0 init failed with %d (SPI_CMD_FAILED), retrying after delay...", res); + delay(100); + res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage); + } + // \todo Display actual typename of the adapter, not just `LR11x0` LOG_INFO("LR11x0 init result %d", res); - if (res == RADIOLIB_ERR_CHIP_NOT_FOUND) + if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED) return false; LR11x0VersionInfo_t version; @@ -159,7 +170,7 @@ template bool LR11x0Interface::reconfigure() if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); - err = lora.setCodingRate(cr); + err = lora.setCodingRate(cr, cr != 7); // use long interleaving except if CR is 4/7 which doesn't support it if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); diff --git a/src/mesh/MeshRadio.h b/src/mesh/MeshRadio.h index f2514eea1..bbb0ee00f 100644 --- a/src/mesh/MeshRadio.h +++ b/src/mesh/MeshRadio.h @@ -22,4 +22,99 @@ struct RegionInfo { extern const RegionInfo regions[]; extern const RegionInfo *myRegion; -extern void initRegion(); \ No newline at end of file +extern void initRegion(); + +static inline float bwCodeToKHz(uint16_t bwCode) +{ + if (bwCode == 31) + return 31.25f; + if (bwCode == 62) + return 62.5f; + if (bwCode == 200) + return 203.125f; + if (bwCode == 400) + return 406.25f; + if (bwCode == 800) + return 812.5f; + if (bwCode == 1600) + return 1625.0f; + return (float)bwCode; +} + +static inline uint16_t bwKHzToCode(float bwKHz) +{ + if (bwKHz > 31.24f && bwKHz < 31.26f) + return 31; + if (bwKHz > 62.49f && bwKHz < 62.51f) + return 62; + if (bwKHz > 203.12f && bwKHz < 203.13f) + return 200; + if (bwKHz > 406.24f && bwKHz < 406.26f) + return 400; + if (bwKHz > 812.49f && bwKHz < 812.51f) + return 800; + if (bwKHz > 1624.99f && bwKHz < 1625.01f) + return 1600; + return (uint16_t)(bwKHz + 0.5f); +} + +static inline void modemPresetToParams(meshtastic_Config_LoRaConfig_ModemPreset preset, bool wideLora, float &bwKHz, uint8_t &sf, + uint8_t &cr) +{ + switch (preset) { + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: + bwKHz = wideLora ? 1625.0f : 500.0f; + cr = 5; + sf = 7; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST: + bwKHz = wideLora ? 812.5f : 250.0f; + cr = 5; + sf = 7; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW: + bwKHz = wideLora ? 812.5f : 250.0f; + cr = 5; + sf = 8; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST: + bwKHz = wideLora ? 812.5f : 250.0f; + cr = 5; + sf = 9; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW: + bwKHz = wideLora ? 812.5f : 250.0f; + cr = 5; + sf = 10; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO: + bwKHz = wideLora ? 1625.0f : 500.0f; + cr = 8; + sf = 11; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE: + bwKHz = wideLora ? 406.25f : 125.0f; + cr = 8; + sf = 11; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW: + bwKHz = wideLora ? 406.25f : 125.0f; + cr = 8; + sf = 12; + break; + default: // LONG_FAST (or illegal) + bwKHz = wideLora ? 812.5f : 250.0f; + cr = 5; + sf = 11; + break; + } +} + +static inline float modemPresetToBwKHz(meshtastic_Config_LoRaConfig_ModemPreset preset, bool wideLora) +{ + float bwKHz = 0; + uint8_t sf = 0; + uint8_t cr = 0; + modemPresetToParams(preset, wideLora, bwKHz, sf, cr); + return bwKHz; +} \ No newline at end of file diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 375bc76e3..f23d6490b 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -13,6 +13,7 @@ #include "PacketHistory.h" #include "PowerFSM.h" #include "RTC.h" +#include "RadioInterface.h" #include "Router.h" #include "SPILock.h" #include "SafeFile.h" @@ -26,6 +27,7 @@ #include #include #include +#include #include #ifdef ARCH_ESP32 @@ -822,16 +824,10 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.external_notification.output_ms = 500; moduleConfig.external_notification.nag_timeout = 2; #endif -#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312) || defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3) || \ - defined(ELECROW_ThinkNode_M4) || defined(ELECROW_ThinkNode_M6) - // Default to PIN_LED2 for external notification output (LED color depends on device variant) +#if defined(LED_NOTIFICATION) moduleConfig.external_notification.enabled = true; - moduleConfig.external_notification.output = PIN_LED2; -#if defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3) - moduleConfig.external_notification.active = false; -#else - moduleConfig.external_notification.active = true; -#endif + moduleConfig.external_notification.output = LED_NOTIFICATION; + moduleConfig.external_notification.active = LED_STATE_ON; moduleConfig.external_notification.alert_message = true; moduleConfig.external_notification.output_ms = 1000; moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs; @@ -855,15 +851,6 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.external_notification.output_ms = 100; moduleConfig.external_notification.active = true; #endif -#ifdef ELECROW_ThinkNode_M1 - // Default to Elecrow USER_LED (blue) - moduleConfig.external_notification.enabled = true; - moduleConfig.external_notification.output = USER_LED; - moduleConfig.external_notification.active = true; - moduleConfig.external_notification.alert_message = true; - moduleConfig.external_notification.output_ms = 1000; - moduleConfig.external_notification.nag_timeout = 60; -#endif #ifdef T_LORA_PAGER moduleConfig.canned_message.updown1_enabled = true; moduleConfig.canned_message.inputbroker_pin_a = ROTARY_A; @@ -1297,6 +1284,13 @@ void NodeDB::loadFromDisk() LOG_INFO("Loaded saved config version %d", config.version); } } + + // Coerce LoRa config fields derived from presets while bootstrapping. + // Some clients/UI components display bandwidth/spread_factor directly from config even in preset mode. + if (config.has_lora && config.lora.use_preset) { + RadioInterface::bootstrapLoRaConfigFromPreset(config.lora); + } + if (backupSecurity.private_key.size > 0) { LOG_DEBUG("Restoring backup of security config"); config.security = backupSecurity; @@ -1410,6 +1404,15 @@ void NodeDB::loadFromDisk() if (portduino_config.has_configDisplayMode) { config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)portduino_config.configDisplayMode; } + if (portduino_config.has_statusMessage) { + moduleConfig.has_statusmessage = true; + strncpy(moduleConfig.statusmessage.node_status, portduino_config.statusMessage.c_str(), + sizeof(moduleConfig.statusmessage.node_status)); + moduleConfig.statusmessage.node_status[sizeof(moduleConfig.statusmessage.node_status) - 1] = '\0'; + } + if (portduino_config.enable_UDP) { + config.network.enabled_protocols = true; + } #endif } @@ -1418,6 +1421,14 @@ void NodeDB::loadFromDisk() bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct, bool fullAtomic) { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveProto() on unsafe device power level."); + return false; + } + bool okay = false; #ifdef FSCom auto f = SafeFile(filename, fullAtomic); @@ -1444,6 +1455,14 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_ bool NodeDB::saveChannelsToDisk() { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveChannelsToDisk() on unsafe device power level."); + return false; + } + #ifdef FSCom spiLock->lock(); FSCom.mkdir("/prefs"); @@ -1454,6 +1473,14 @@ bool NodeDB::saveChannelsToDisk() bool NodeDB::saveDeviceStateToDisk() { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveDeviceStateToDisk() on unsafe device power level."); + return false; + } + #ifdef FSCom spiLock->lock(); FSCom.mkdir("/prefs"); @@ -1466,6 +1493,14 @@ bool NodeDB::saveDeviceStateToDisk() bool NodeDB::saveNodeDatabaseToDisk() { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveNodeDatabaseToDisk() on unsafe device power level."); + return false; + } + #ifdef FSCom spiLock->lock(); FSCom.mkdir("/prefs"); @@ -1478,6 +1513,14 @@ bool NodeDB::saveNodeDatabaseToDisk() bool NodeDB::saveToDiskNoRetry(int saveWhat) { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveToDiskNoRetry() on unsafe device power level."); + return false; + } + bool success = true; #ifdef FSCom spiLock->lock(); @@ -1510,6 +1553,7 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat) moduleConfig.has_ambient_lighting = true; moduleConfig.has_audio = true; moduleConfig.has_paxcounter = true; + moduleConfig.has_statusmessage = true; success &= saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig); @@ -1533,6 +1577,14 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat) bool NodeDB::saveToDisk(int saveWhat) { LOG_DEBUG("Save to disk %d", saveWhat); + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveToDisk() on unsafe device power level."); + return false; + } + bool success = saveToDiskNoRetry(saveWhat); if (!success) { @@ -2171,8 +2223,8 @@ bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location, } else if (location == meshtastic_AdminMessage_BackupLocation_SD) { // TODO: After more mainline SD card support } - return success; #endif + return success; } /// Record an error that should be reported via analytics @@ -2190,7 +2242,10 @@ void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, co // Currently portuino is mostly used for simulation. Make sure the user notices something really bad happened #ifdef ARCH_PORTDUINO - LOG_ERROR("A critical failure occurred, portduino is exiting"); - exit(2); + LOG_ERROR("A critical failure occurred"); + // TODO: Determine if other critical errors should also cause an immediate exit + if (code == meshtastic_CriticalErrorCode_FLASH_CORRUPTION_RECOVERABLE || + code == meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE) + exit(2); #endif } diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index 5588fc348..0c12401ca 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -177,6 +177,9 @@ bool RF95Interface::init() int res = lora->begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength); LOG_INFO("RF95 init result %d", res); + if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED) + return false; + LOG_INFO("Frequency set to %f", getFreq()); LOG_INFO("Bandwidth set to %f", bw); LOG_INFO("Power output set to %d", power); diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index aaaca719e..bbd766329 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -1,17 +1,36 @@ #include "RadioInterface.h" #include "Channels.h" #include "DisplayFormatters.h" +#include "LLCC68Interface.h" +#include "LR1110Interface.h" +#include "LR1120Interface.h" +#include "LR1121Interface.h" #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" +#include "RF95Interface.h" #include "Router.h" +#include "SX1262Interface.h" +#include "SX1268Interface.h" +#include "SX1280Interface.h" #include "configuration.h" +#include "detect/LoRaRadioType.h" #include "main.h" #include "sleep.h" #include #include #include +#ifdef ARCH_PORTDUINO +#include "platform/portduino/PortduinoGlue.h" +#include "platform/portduino/SimRadio.h" +#include "platform/portduino/USBHal.h" +#endif + +#ifdef ARCH_STM32WL +#include "STM32WLE5JCInterface.h" +#endif + // Calculate 2^n without calling pow() uint32_t pow_of_2(uint32_t n) { @@ -205,6 +224,281 @@ bool RadioInterface::uses_default_frequency_slot = true; static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1]; +// Global LoRa radio type +LoRaRadioType radioType = NO_RADIO; + +extern RadioInterface *rIf; +extern RadioLibHal *RadioLibHAL; +#if defined(HW_SPI1_DEVICE) && defined(ARCH_ESP32) +extern SPIClass SPI1; +#endif + +bool initLoRa() +{ + if (rIf != nullptr) { + delete rIf; + rIf = nullptr; + } + +#if ARCH_PORTDUINO + SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0); +#else + SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); +#endif + +#ifdef ARCH_PORTDUINO + // as one can't use a function pointer to the class constructor: + auto loraModuleInterface = [](LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, + RADIOLIB_PIN_TYPE busy) { + switch (portduino_config.lora_module) { + case use_rf95: + return (RadioInterface *)new RF95Interface(hal, cs, irq, rst, busy); + case use_sx1262: + return (RadioInterface *)new SX1262Interface(hal, cs, irq, rst, busy); + case use_sx1268: + return (RadioInterface *)new SX1268Interface(hal, cs, irq, rst, busy); + case use_sx1280: + return (RadioInterface *)new SX1280Interface(hal, cs, irq, rst, busy); + case use_lr1110: + return (RadioInterface *)new LR1110Interface(hal, cs, irq, rst, busy); + case use_lr1120: + return (RadioInterface *)new LR1120Interface(hal, cs, irq, rst, busy); + case use_lr1121: + return (RadioInterface *)new LR1121Interface(hal, cs, irq, rst, busy); + case use_llcc68: + return (RadioInterface *)new LLCC68Interface(hal, cs, irq, rst, busy); + case use_simradio: + return (RadioInterface *)new SimRadio; + default: + assert(0); // shouldn't happen + return (RadioInterface *)nullptr; + } + }; + + LOG_DEBUG("Activate %s radio on SPI port %s", portduino_config.loraModules[portduino_config.lora_module].c_str(), + portduino_config.lora_spi_dev.c_str()); + if (portduino_config.lora_spi_dev == "ch341") { + RadioLibHAL = ch341Hal; + } else { + if (RadioLibHAL != nullptr) { + delete RadioLibHAL; + RadioLibHAL = nullptr; + } + RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); + } + rIf = + loraModuleInterface((LockingArduinoHal *)RadioLibHAL, portduino_config.lora_cs_pin.pin, portduino_config.lora_irq_pin.pin, + portduino_config.lora_reset_pin.pin, portduino_config.lora_busy_pin.pin); + + if (!rIf->init()) { + LOG_WARN("No %s radio", portduino_config.loraModules[portduino_config.lora_module].c_str()); + delete rIf; + rIf = NULL; + exit(EXIT_FAILURE); + } else { + LOG_INFO("%s init success", portduino_config.loraModules[portduino_config.lora_module].c_str()); + } + +#elif defined(HW_SPI1_DEVICE) + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI1, spiSettings); +#else // HW_SPI1_DEVICE + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); +#endif + +// radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init) +#if defined(USE_STM32WLx) + if (!rIf) { + rIf = new STM32WLE5JCInterface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + if (!rIf->init()) { + LOG_WARN("No STM32WL radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("STM32WL init success"); + radioType = STM32WLx_RADIO; + } + } +#endif + +#if defined(RF95_IRQ) && RADIOLIB_EXCLUDE_SX127X != 1 + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1); + if (!rIf->init()) { + LOG_WARN("No RF95 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("RF95 init success"); + radioType = RF95_RADIO; + } + } +#endif + +#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1 + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); +#ifdef SX126X_DIO3_TCXO_VOLTAGE + sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); +#endif + if (!sxIf->init()) { + LOG_WARN("No SX1262 radio"); + delete sxIf; + rIf = NULL; + } else { + LOG_INFO("SX1262 init success"); + rIf = sxIf; + radioType = SX1262_RADIO; + } + } +#endif + +#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL) + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + // try using the specified TCXO voltage + auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); + if (!sxIf->init()) { + LOG_WARN("No SX1262 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); + delete sxIf; + rIf = NULL; + } else { + LOG_INFO("SX1262 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); + rIf = sxIf; + radioType = SX1262_RADIO; + } + } + + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + // If specified TCXO voltage fails, attempt to use DIO3 as a reference instead + rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + if (!rIf->init()) { + LOG_WARN("No SX1262 radio with XTAL, Vref 0.0V"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("SX1262 init success, XTAL, Vref 0.0V"); + radioType = SX1262_RADIO; + } + } +#endif + +#if defined(USE_SX1268) +#if defined(SX126X_DIO3_TCXO_VOLTAGE) && defined(TCXO_OPTIONAL) + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + // try using the specified TCXO voltage + auto *sxIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); + if (!sxIf->init()) { + LOG_WARN("No SX1268 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); + delete sxIf; + rIf = NULL; + } else { + LOG_INFO("SX1268 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); + rIf = sxIf; + radioType = SX1268_RADIO; + } + } +#endif + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + rIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + if (!rIf->init()) { + LOG_WARN("No SX1268 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("SX1268 init success"); + radioType = SX1268_RADIO; + } + } +#endif + +#if defined(USE_LLCC68) + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + rIf = new LLCC68Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + if (!rIf->init()) { + LOG_WARN("No LLCC68 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("LLCC68 init success"); + radioType = LLCC68_RADIO; + } + } +#endif + +#if defined(USE_LR1110) && RADIOLIB_EXCLUDE_LR11X0 != 1 + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN); + if (!rIf->init()) { + LOG_WARN("No LR1110 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("LR1110 init success"); + radioType = LR1110_RADIO; + } + } +#endif + +#if defined(USE_LR1120) && RADIOLIB_EXCLUDE_LR11X0 != 1 + if (!rIf) { + rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN); + if (!rIf->init()) { + LOG_WARN("No LR1120 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("LR1120 init success"); + radioType = LR1120_RADIO; + } + } +#endif + +#if defined(USE_LR1121) && RADIOLIB_EXCLUDE_LR11X0 != 1 + if (!rIf) { + rIf = new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN); + if (!rIf->init()) { + LOG_WARN("No LR1121 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("LR1121 init success"); + radioType = LR1121_RADIO; + } + } +#endif + +#if defined(USE_SX1280) && RADIOLIB_EXCLUDE_SX128X != 1 + if (!rIf) { + rIf = new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY); + if (!rIf->init()) { + LOG_WARN("No SX1280 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("SX1280 init success"); + radioType = SX1280_RADIO; + } + } +#endif + + // check if the radio chip matches the selected region + if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && rIf && (!rIf->wideLora())) { + LOG_WARN("LoRa chip does not support 2.4GHz. Revert to unset"); + config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET; + nodeDB->saveToDisk(SEGMENT_CONFIG); + + if (rIf && !rIf->reconfigure()) { + LOG_WARN("Reconfigure failed, rebooting"); + if (screen) { + screen->showSimpleBanner("Rebooting..."); + } + rebootAtMsec = millis() + 5000; + } + } + return rIf != nullptr; +} + void initRegion() { const RegionInfo *r = regions; @@ -220,6 +514,34 @@ void initRegion() myRegion = r; } +void RadioInterface::bootstrapLoRaConfigFromPreset(meshtastic_Config_LoRaConfig &loraConfig) +{ + if (!loraConfig.use_preset) { + return; + } + + // Find region info to determine whether "wide" LoRa is permitted (2.4 GHz uses wider bandwidth codes). + const RegionInfo *r = regions; + for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != loraConfig.region; r++) + ; + + const bool regionWideLora = r->wideLora; + + float bwKHz = 0; + uint8_t sf = 0; + uint8_t cr = 0; + modemPresetToParams(loraConfig.modem_preset, regionWideLora, bwKHz, sf, cr); + + // If selected preset requests a bandwidth larger than the region span, fall back to LONG_FAST. + if (r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && (r->freqEnd - r->freqStart) < (bwKHz / 1000.0f)) { + loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; + modemPresetToParams(loraConfig.modem_preset, regionWideLora, bwKHz, sf, cr); + } + + loraConfig.bandwidth = bwKHzToCode(bwKHz); + loraConfig.spread_factor = sf; +} + /** * ## LoRaWAN for North America @@ -474,54 +796,7 @@ void RadioInterface::applyModemConfig() bool validConfig = false; // We need to check for a valid configuration while (!validConfig) { if (loraConfig.use_preset) { - - switch (loraConfig.modem_preset) { - case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: - bw = (myRegion->wideLora) ? 1625.0 : 500; - cr = 5; - sf = 7; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST: - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 7; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW: - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 8; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST: - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 9; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW: - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 10; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO: - bw = (myRegion->wideLora) ? 1625.0 : 500; - cr = 8; - sf = 11; - break; - default: // Config_LoRaConfig_ModemPreset_LONG_FAST is default. Gracefully use this is preset is something illegal. - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 11; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE: - bw = (myRegion->wideLora) ? 406.25 : 125; - cr = 8; - sf = 11; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW: - bw = (myRegion->wideLora) ? 406.25 : 125; - cr = 8; - sf = 12; - break; - } + modemPresetToParams(loraConfig.modem_preset, myRegion->wideLora, bw, sf, cr); if (loraConfig.coding_rate >= 5 && loraConfig.coding_rate <= 8 && loraConfig.coding_rate != cr) { cr = loraConfig.coding_rate; LOG_INFO("Using custom Coding Rate %u", cr); @@ -529,20 +804,7 @@ void RadioInterface::applyModemConfig() } else { sf = loraConfig.spread_factor; cr = loraConfig.coding_rate; - bw = loraConfig.bandwidth; - - if (bw == 31) // This parameter is not an integer - bw = 31.25; - if (bw == 62) // Fix for 62.5Khz bandwidth - bw = 62.5; - if (bw == 200) - bw = 203.125; - if (bw == 400) - bw = 406.25; - if (bw == 800) - bw = 812.5; - if (bw == 1600) - bw = 1625.0; + bw = bwCodeToKHz(loraConfig.bandwidth); } if ((myRegion->freqEnd - myRegion->freqStart) < bw / 1000) { @@ -666,18 +928,24 @@ void RadioInterface::limitPower(int8_t loraMaxPower) power = maxPower; } -#ifndef NUM_PA_POINTS - if (TX_GAIN_LORA > 0 && !devicestate.owner.is_licensed) { - LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, TX_GAIN_LORA); - power -= TX_GAIN_LORA; - } +#ifdef ARCH_PORTDUINO + size_t num_pa_points = portduino_config.num_pa_points; + const uint16_t *tx_gain = portduino_config.tx_gain_lora; #else - if (!devicestate.owner.is_licensed) { + size_t num_pa_points = NUM_PA_POINTS; + const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA}; +#endif + + if (num_pa_points == 1) { + if (tx_gain[0] > 0 && !devicestate.owner.is_licensed) { + LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[0]); + power -= tx_gain[0]; + } + } else if (!devicestate.owner.is_licensed) { // we have an array of PA gain values. Find the highest power setting that works. - const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA}; - for (int radio_dbm = 0; radio_dbm < NUM_PA_POINTS; radio_dbm++) { + for (int radio_dbm = 0; radio_dbm < num_pa_points; radio_dbm++) { if (((radio_dbm + tx_gain[radio_dbm]) > power) || - ((radio_dbm == (NUM_PA_POINTS - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) { + ((radio_dbm == (num_pa_points - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) { // we've exceeded the power limit, or hit the max we can do LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[radio_dbm]); power -= tx_gain[radio_dbm]; @@ -685,7 +953,7 @@ void RadioInterface::limitPower(int8_t loraMaxPower) } } } -#endif + if (power > loraMaxPower) // Clamp power to maximum defined level power = loraMaxPower; diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 6049a11cc..cb092bc6d 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -7,6 +7,9 @@ #include "airtime.h" #include "error.h" +// Forward decl to avoid a direct include of generated config headers / full LoRaConfig definition in this widely-included file. +typedef struct _meshtastic_Config_LoRaConfig meshtastic_Config_LoRaConfig; + #define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission #define MAX_LORA_PAYLOAD_LEN 255 // max length of 255 per Semtech's datasheets on SX12xx @@ -115,6 +118,12 @@ class RadioInterface virtual ~RadioInterface() {} + /** + * Coerce LoRa config fields (bandwidth/spread_factor) derived from presets. + * This is used during early bootstrapping so UIs that display these fields directly remain consistent. + */ + static void bootstrapLoRaConfigFromPreset(meshtastic_Config_LoRaConfig &loraConfig); + /** * Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving) * @@ -270,5 +279,7 @@ class RadioInterface } }; +bool initLoRa(); + /// Debug printing for packets void printPacket(const char *prefix, const meshtastic_MeshPacket *p); diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 2b9b17183..42c24c783 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -17,12 +17,6 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p) { if (p->want_ack) { - // If someone asks for acks on broadcast, we need the hop limit to be at least one, so that first node that receives our - // message will rebroadcast. But asking for hop_limit 0 in that context means the client app has no preference on hop - // counts and we want this message to get through the whole mesh, so use the default. - if (p->hop_limit == 0) { - p->hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); - } DEBUG_HEAP_BEFORE; auto copy = packetPool.allocCopy(*p); DEBUG_HEAP_AFTER("ReliableRouter::send", copy); diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index a3861521a..32544a051 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -7,7 +7,6 @@ #include "RTC.h" #include "configuration.h" -#include "detect/LoRaRadioType.h" #include "main.h" #include "mesh-pb-constants.h" #include "meshUtils.h" @@ -267,6 +266,13 @@ ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src) } } + // If someone asks for acks on broadcast, we need the hop limit to be at least one, so that first node that receives our + // message will rebroadcast. But asking for hop_limit 0 in that context means the client app has no preference on hop + // counts and we want this message to get through the whole mesh, so use the default. + if (src == RX_SRC_USER && p->want_ack && p->hop_limit == 0) { + p->hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); + } + return send(p); } } @@ -614,15 +620,19 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) !(p->pki_encrypted != true && (strcasecmp(channels.getName(chIndex), Channels::serialChannel) == 0 || strcasecmp(channels.getName(chIndex), Channels::gpioChannel) == 0)) && // Check for valid keys and single node destination - config.security.private_key.size == 32 && !isBroadcast(p->to) && node != nullptr && - // Check for a known public key for the destination - (node->user.public_key.size == 32) && + config.security.private_key.size == 32 && !isBroadcast(p->to) && // Some portnums either make no sense to send with PKC p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP && p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && p->decoded.portnum != meshtastic_PortNum_POSITION_APP) { LOG_DEBUG("Use PKI!"); if (numbytes + MESHTASTIC_HEADER_LENGTH + MESHTASTIC_PKC_OVERHEAD > MAX_LORA_PAYLOAD_LEN) return meshtastic_Routing_Error_TOO_LARGE; + // Check for a known public key for the destination + if (node == nullptr || node->user.public_key.size != 32) { + LOG_WARN("Unknown public key for destination node 0x%08x (portnum %d), refusing to send legacy DM", p->to, + p->decoded.portnum); + return meshtastic_Routing_Error_PKI_SEND_FAIL_PUBLIC_KEY; + } if (p->pki_encrypted && !memfll(p->public_key.bytes, 0, 32) && memcmp(p->public_key.bytes, node->user.public_key.bytes, 32) != 0) { LOG_WARN("Client public key differs from requested: 0x%02x, stored key begins 0x%02x", *p->public_key.bytes, diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 498496a3b..9dfc46bee 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -269,8 +269,12 @@ template void SX126xInterface::setStandby() if (err != RADIOLIB_ERR_NONE) LOG_DEBUG("SX126x standby %s%d", radioLibErr, err); +#ifdef ARCH_PORTDUINO + if (err != RADIOLIB_ERR_NONE) + portduino_status.LoRa_in_error = true; +#else assert(err == RADIOLIB_ERR_NONE); - +#endif isReceiving = false; // If we were receiving, not any more activeReceiveStart = 0; disableInterrupt(); @@ -313,7 +317,12 @@ template void SX126xInterface::startReceive() int err = lora.startReceiveDutyCycleAuto(preambleLength, 8, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS); if (err != RADIOLIB_ERR_NONE) LOG_ERROR("SX126X startReceiveDutyCycleAuto %s%d", radioLibErr, err); +#ifdef ARCH_PORTDUINO + if (err != RADIOLIB_ERR_NONE) + portduino_status.LoRa_in_error = true; +#else assert(err == RADIOLIB_ERR_NONE); +#endif RadioLibInterface::startReceive(); @@ -341,7 +350,12 @@ template bool SX126xInterface::isChannelActive() return true; if (result != RADIOLIB_CHANNEL_FREE) LOG_ERROR("SX126X scanChannel %s%d", radioLibErr, result); +#ifdef ARCH_PORTDUINO + if (result == RADIOLIB_ERR_WRONG_MODEM) + portduino_status.LoRa_in_error = true; +#else assert(result != RADIOLIB_ERR_WRONG_MODEM); +#endif return false; } diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index b4278c636..9fcedfe49 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -69,6 +69,8 @@ template bool SX128xInterface::init() int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength); // \todo Display actual typename of the adapter, not just `SX128x` LOG_INFO("SX128x init result %d", res); + if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED) + return false; if ((config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (res == RADIOLIB_ERR_INVALID_FREQUENCY)) { LOG_WARN("Radio only supports 2.4GHz LoRa. Adjusting Region and rebooting"); @@ -124,7 +126,7 @@ template bool SX128xInterface::reconfigure() if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); - err = lora.setCodingRate(cr); + err = lora.setCodingRate(cr, cr != 7); // use long interleaving except if CR is 4/7 which doesn't support it if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); diff --git a/src/mesh/generated/meshtastic/admin.pb.cpp b/src/mesh/generated/meshtastic/admin.pb.cpp index e358bc96d..01d3fa910 100644 --- a/src/mesh/generated/meshtastic/admin.pb.cpp +++ b/src/mesh/generated/meshtastic/admin.pb.cpp @@ -27,6 +27,15 @@ PB_BIND(meshtastic_SharedContact, meshtastic_SharedContact, AUTO) PB_BIND(meshtastic_KeyVerificationAdmin, meshtastic_KeyVerificationAdmin, AUTO) +PB_BIND(meshtastic_SensorConfig, meshtastic_SensorConfig, AUTO) + + +PB_BIND(meshtastic_SCD4X_config, meshtastic_SCD4X_config, AUTO) + + +PB_BIND(meshtastic_SEN5X_config, meshtastic_SEN5X_config, AUTO) + + diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 26b4343e9..f545ed9bf 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -77,7 +77,9 @@ typedef enum _meshtastic_AdminMessage_ModuleConfigType { /* TODO: REPLACE */ meshtastic_AdminMessage_ModuleConfigType_DETECTIONSENSOR_CONFIG = 11, /* TODO: REPLACE */ - meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12 + meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12, + /* TODO: REPLACE */ + meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG = 13 } meshtastic_AdminMessage_ModuleConfigType; typedef enum _meshtastic_AdminMessage_BackupLocation { @@ -169,6 +171,48 @@ typedef struct _meshtastic_KeyVerificationAdmin { uint32_t security_number; } meshtastic_KeyVerificationAdmin; +typedef struct _meshtastic_SCD4X_config { + /* Set Automatic self-calibration enabled */ + bool has_set_asc; + bool set_asc; + /* Recalibration target CO2 concentration in ppm (FRC or ASC) */ + bool has_set_target_co2_conc; + uint32_t set_target_co2_conc; + /* Reference temperature in degC */ + bool has_set_temperature; + float set_temperature; + /* Altitude of sensor in meters above sea level. 0 - 3000m (overrides ambient pressure) */ + bool has_set_altitude; + uint32_t set_altitude; + /* Sensor ambient pressure in Pa. 70000 - 120000 Pa (overrides altitude) */ + bool has_set_ambient_pressure; + uint32_t set_ambient_pressure; + /* Perform a factory reset of the sensor */ + bool has_factory_reset; + bool factory_reset; + /* Power mode for sensor (true for low power, false for normal) */ + bool has_set_power_mode; + bool set_power_mode; +} meshtastic_SCD4X_config; + +typedef struct _meshtastic_SEN5X_config { + /* Reference temperature in degC */ + bool has_set_temperature; + float set_temperature; + /* One-shot mode (true for low power - one-shot mode, false for normal - continuous mode) */ + bool has_set_one_shot_mode; + bool set_one_shot_mode; +} meshtastic_SEN5X_config; + +typedef struct _meshtastic_SensorConfig { + /* SCD4X CO2 Sensor configuration */ + bool has_scd4x_config; + meshtastic_SCD4X_config scd4x_config; + /* SEN5X PM Sensor configuration */ + bool has_sen5x_config; + meshtastic_SEN5X_config sen5x_config; +} meshtastic_SensorConfig; + typedef PB_BYTES_ARRAY_T(8) meshtastic_AdminMessage_session_passkey_t; /* This message is handled by the Admin module and is responsible for all settings/channel read/write operations. This message is used to do settings operations to both remote AND local nodes. @@ -301,6 +345,8 @@ typedef struct _meshtastic_AdminMessage { bool nodedb_reset; /* Tell the node to reset into the OTA Loader */ meshtastic_AdminMessage_OTAEvent ota_request; + /* Parameters and sensor configuration */ + meshtastic_SensorConfig sensor_config; }; /* The node generates this key and sends it with any get_x_response packets. The client MUST include the same key with any set_x commands. Key expires after 300 seconds. @@ -323,8 +369,8 @@ extern "C" { #define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG+1)) #define _meshtastic_AdminMessage_ModuleConfigType_MIN meshtastic_AdminMessage_ModuleConfigType_MQTT_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG+1)) +#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG +#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG+1)) #define _meshtastic_AdminMessage_BackupLocation_MIN meshtastic_AdminMessage_BackupLocation_FLASH #define _meshtastic_AdminMessage_BackupLocation_MAX meshtastic_AdminMessage_BackupLocation_SD @@ -349,6 +395,9 @@ extern "C" { #define meshtastic_KeyVerificationAdmin_message_type_ENUMTYPE meshtastic_KeyVerificationAdmin_MessageType + + + /* Initializer values for message structs */ #define meshtastic_AdminMessage_init_default {0, {0}, {0, {0}}} #define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0} @@ -357,6 +406,9 @@ extern "C" { #define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}} #define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0} #define meshtastic_KeyVerificationAdmin_init_default {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0} +#define meshtastic_SensorConfig_init_default {false, meshtastic_SCD4X_config_init_default, false, meshtastic_SEN5X_config_init_default} +#define meshtastic_SCD4X_config_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_SEN5X_config_init_default {false, 0, false, 0} #define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}} #define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0} #define meshtastic_AdminMessage_OTAEvent_init_zero {_meshtastic_OTAMode_MIN, {0, {0}}} @@ -364,6 +416,9 @@ extern "C" { #define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}} #define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0} #define meshtastic_KeyVerificationAdmin_init_zero {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0} +#define meshtastic_SensorConfig_init_zero {false, meshtastic_SCD4X_config_init_zero, false, meshtastic_SEN5X_config_init_zero} +#define meshtastic_SCD4X_config_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_SEN5X_config_init_zero {false, 0, false, 0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_AdminMessage_InputEvent_event_code_tag 1 @@ -385,6 +440,17 @@ extern "C" { #define meshtastic_KeyVerificationAdmin_remote_nodenum_tag 2 #define meshtastic_KeyVerificationAdmin_nonce_tag 3 #define meshtastic_KeyVerificationAdmin_security_number_tag 4 +#define meshtastic_SCD4X_config_set_asc_tag 1 +#define meshtastic_SCD4X_config_set_target_co2_conc_tag 2 +#define meshtastic_SCD4X_config_set_temperature_tag 3 +#define meshtastic_SCD4X_config_set_altitude_tag 4 +#define meshtastic_SCD4X_config_set_ambient_pressure_tag 5 +#define meshtastic_SCD4X_config_factory_reset_tag 6 +#define meshtastic_SCD4X_config_set_power_mode_tag 7 +#define meshtastic_SEN5X_config_set_temperature_tag 1 +#define meshtastic_SEN5X_config_set_one_shot_mode_tag 2 +#define meshtastic_SensorConfig_scd4x_config_tag 1 +#define meshtastic_SensorConfig_sen5x_config_tag 2 #define meshtastic_AdminMessage_get_channel_request_tag 1 #define meshtastic_AdminMessage_get_channel_response_tag 2 #define meshtastic_AdminMessage_get_owner_request_tag 3 @@ -441,6 +507,7 @@ extern "C" { #define meshtastic_AdminMessage_factory_reset_config_tag 99 #define meshtastic_AdminMessage_nodedb_reset_tag 100 #define meshtastic_AdminMessage_ota_request_tag 102 +#define meshtastic_AdminMessage_sensor_config_tag 103 #define meshtastic_AdminMessage_session_passkey_tag 101 /* Struct field encoding specification for nanopb */ @@ -501,7 +568,8 @@ X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_se X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,nodedb_reset,nodedb_reset), 100) \ X(a, STATIC, SINGULAR, BYTES, session_passkey, 101) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 102) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 102) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,sensor_config,sensor_config), 103) #define meshtastic_AdminMessage_CALLBACK NULL #define meshtastic_AdminMessage_DEFAULT NULL #define meshtastic_AdminMessage_payload_variant_get_channel_response_MSGTYPE meshtastic_Channel @@ -523,6 +591,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 10 #define meshtastic_AdminMessage_payload_variant_add_contact_MSGTYPE meshtastic_SharedContact #define meshtastic_AdminMessage_payload_variant_key_verification_MSGTYPE meshtastic_KeyVerificationAdmin #define meshtastic_AdminMessage_payload_variant_ota_request_MSGTYPE meshtastic_AdminMessage_OTAEvent +#define meshtastic_AdminMessage_payload_variant_sensor_config_MSGTYPE meshtastic_SensorConfig #define meshtastic_AdminMessage_InputEvent_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, event_code, 1) \ @@ -569,6 +638,31 @@ X(a, STATIC, OPTIONAL, UINT32, security_number, 4) #define meshtastic_KeyVerificationAdmin_CALLBACK NULL #define meshtastic_KeyVerificationAdmin_DEFAULT NULL +#define meshtastic_SensorConfig_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, scd4x_config, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, sen5x_config, 2) +#define meshtastic_SensorConfig_CALLBACK NULL +#define meshtastic_SensorConfig_DEFAULT NULL +#define meshtastic_SensorConfig_scd4x_config_MSGTYPE meshtastic_SCD4X_config +#define meshtastic_SensorConfig_sen5x_config_MSGTYPE meshtastic_SEN5X_config + +#define meshtastic_SCD4X_config_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, BOOL, set_asc, 1) \ +X(a, STATIC, OPTIONAL, UINT32, set_target_co2_conc, 2) \ +X(a, STATIC, OPTIONAL, FLOAT, set_temperature, 3) \ +X(a, STATIC, OPTIONAL, UINT32, set_altitude, 4) \ +X(a, STATIC, OPTIONAL, UINT32, set_ambient_pressure, 5) \ +X(a, STATIC, OPTIONAL, BOOL, factory_reset, 6) \ +X(a, STATIC, OPTIONAL, BOOL, set_power_mode, 7) +#define meshtastic_SCD4X_config_CALLBACK NULL +#define meshtastic_SCD4X_config_DEFAULT NULL + +#define meshtastic_SEN5X_config_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, FLOAT, set_temperature, 1) \ +X(a, STATIC, OPTIONAL, BOOL, set_one_shot_mode, 2) +#define meshtastic_SEN5X_config_CALLBACK NULL +#define meshtastic_SEN5X_config_DEFAULT NULL + extern const pb_msgdesc_t meshtastic_AdminMessage_msg; extern const pb_msgdesc_t meshtastic_AdminMessage_InputEvent_msg; extern const pb_msgdesc_t meshtastic_AdminMessage_OTAEvent_msg; @@ -576,6 +670,9 @@ extern const pb_msgdesc_t meshtastic_HamParameters_msg; extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePinsResponse_msg; extern const pb_msgdesc_t meshtastic_SharedContact_msg; extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg; +extern const pb_msgdesc_t meshtastic_SensorConfig_msg; +extern const pb_msgdesc_t meshtastic_SCD4X_config_msg; +extern const pb_msgdesc_t meshtastic_SEN5X_config_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_AdminMessage_fields &meshtastic_AdminMessage_msg @@ -585,6 +682,9 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg; #define meshtastic_NodeRemoteHardwarePinsResponse_fields &meshtastic_NodeRemoteHardwarePinsResponse_msg #define meshtastic_SharedContact_fields &meshtastic_SharedContact_msg #define meshtastic_KeyVerificationAdmin_fields &meshtastic_KeyVerificationAdmin_msg +#define meshtastic_SensorConfig_fields &meshtastic_SensorConfig_msg +#define meshtastic_SCD4X_config_fields &meshtastic_SCD4X_config_msg +#define meshtastic_SEN5X_config_fields &meshtastic_SEN5X_config_msg /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_ADMIN_PB_H_MAX_SIZE meshtastic_AdminMessage_size @@ -594,6 +694,9 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg; #define meshtastic_HamParameters_size 31 #define meshtastic_KeyVerificationAdmin_size 25 #define meshtastic_NodeRemoteHardwarePinsResponse_size 496 +#define meshtastic_SCD4X_config_size 29 +#define meshtastic_SEN5X_config_size 7 +#define meshtastic_SensorConfig_size 40 #define meshtastic_SharedContact_size 127 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 409805d24..57e7df8fc 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -361,7 +361,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; /* Maximum encoded size of messages (where known) */ /* meshtastic_NodeDatabase_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size -#define meshtastic_BackupPreferences_size 2279 +#define meshtastic_BackupPreferences_size 2362 #define meshtastic_ChannelFile_size 718 #define meshtastic_DeviceState_size 1737 #define meshtastic_NodeInfoLite_size 196 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 2b44d0c9a..f11b13419 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -87,6 +87,9 @@ typedef struct _meshtastic_LocalModuleConfig { /* Paxcounter Config */ bool has_paxcounter; meshtastic_ModuleConfig_PaxcounterConfig paxcounter; + /* StatusMessage Config */ + bool has_statusmessage; + meshtastic_ModuleConfig_StatusMessageConfig statusmessage; } meshtastic_LocalModuleConfig; @@ -96,9 +99,9 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_LocalConfig_init_default {false, meshtastic_Config_DeviceConfig_init_default, false, meshtastic_Config_PositionConfig_init_default, false, meshtastic_Config_PowerConfig_init_default, false, meshtastic_Config_NetworkConfig_init_default, false, meshtastic_Config_DisplayConfig_init_default, false, meshtastic_Config_LoRaConfig_init_default, false, meshtastic_Config_BluetoothConfig_init_default, 0, false, meshtastic_Config_SecurityConfig_init_default} -#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default} +#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default} #define meshtastic_LocalConfig_init_zero {false, meshtastic_Config_DeviceConfig_init_zero, false, meshtastic_Config_PositionConfig_init_zero, false, meshtastic_Config_PowerConfig_init_zero, false, meshtastic_Config_NetworkConfig_init_zero, false, meshtastic_Config_DisplayConfig_init_zero, false, meshtastic_Config_LoRaConfig_init_zero, false, meshtastic_Config_BluetoothConfig_init_zero, 0, false, meshtastic_Config_SecurityConfig_init_zero} -#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero} +#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_LocalConfig_device_tag 1 @@ -124,6 +127,7 @@ extern "C" { #define meshtastic_LocalModuleConfig_ambient_lighting_tag 12 #define meshtastic_LocalModuleConfig_detection_sensor_tag 13 #define meshtastic_LocalModuleConfig_paxcounter_tag 14 +#define meshtastic_LocalModuleConfig_statusmessage_tag 15 /* Struct field encoding specification for nanopb */ #define meshtastic_LocalConfig_FIELDLIST(X, a) \ @@ -161,7 +165,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, remote_hardware, 10) \ X(a, STATIC, OPTIONAL, MESSAGE, neighbor_info, 11) \ X(a, STATIC, OPTIONAL, MESSAGE, ambient_lighting, 12) \ X(a, STATIC, OPTIONAL, MESSAGE, detection_sensor, 13) \ -X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) +X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) \ +X(a, STATIC, OPTIONAL, MESSAGE, statusmessage, 15) #define meshtastic_LocalModuleConfig_CALLBACK NULL #define meshtastic_LocalModuleConfig_DEFAULT NULL #define meshtastic_LocalModuleConfig_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig @@ -177,6 +182,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) #define meshtastic_LocalModuleConfig_ambient_lighting_MSGTYPE meshtastic_ModuleConfig_AmbientLightingConfig #define meshtastic_LocalModuleConfig_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig #define meshtastic_LocalModuleConfig_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig +#define meshtastic_LocalModuleConfig_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig extern const pb_msgdesc_t meshtastic_LocalConfig_msg; extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; @@ -186,9 +192,9 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; #define meshtastic_LocalModuleConfig_fields &meshtastic_LocalModuleConfig_msg /* Maximum encoded size of messages (where known) */ -#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size +#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size #define meshtastic_LocalConfig_size 749 -#define meshtastic_LocalModuleConfig_size 675 +#define meshtastic_LocalModuleConfig_size 758 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index d8eee1203..7f1a738c6 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -30,6 +30,9 @@ PB_BIND(meshtastic_StoreForwardPlusPlus, meshtastic_StoreForwardPlusPlus, 2) PB_BIND(meshtastic_Waypoint, meshtastic_Waypoint, AUTO) +PB_BIND(meshtastic_StatusMessage, meshtastic_StatusMessage, AUTO) + + PB_BIND(meshtastic_MqttClientProxyMessage, meshtastic_MqttClientProxyMessage, 2) diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 68552ede5..aeae4bd84 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -858,6 +858,11 @@ typedef struct _meshtastic_Waypoint { uint32_t icon; } meshtastic_Waypoint; +/* Message for node status */ +typedef struct _meshtastic_StatusMessage { + char status[80]; +} meshtastic_StatusMessage; + typedef PB_BYTES_ARRAY_T(435) meshtastic_MqttClientProxyMessage_data_t; /* This message will be proxied over the PhoneAPI for the client to deliver to the MQTT server */ typedef struct _meshtastic_MqttClientProxyMessage { @@ -1402,6 +1407,7 @@ extern "C" { + #define meshtastic_MeshPacket_priority_ENUMTYPE meshtastic_MeshPacket_Priority #define meshtastic_MeshPacket_delayed_ENUMTYPE meshtastic_MeshPacket_Delayed #define meshtastic_MeshPacket_transport_mechanism_ENUMTYPE meshtastic_MeshPacket_TransportMechanism @@ -1444,6 +1450,7 @@ extern "C" { #define meshtastic_KeyVerification_init_default {0, {0, {0}}, {0, {0}}} #define meshtastic_StoreForwardPlusPlus_init_default {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0} +#define meshtastic_StatusMessage_init_default {""} #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN} #define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0} @@ -1476,6 +1483,7 @@ extern "C" { #define meshtastic_KeyVerification_init_zero {0, {0, {0}}, {0, {0}}} #define meshtastic_StoreForwardPlusPlus_init_zero {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0} +#define meshtastic_StatusMessage_init_zero {""} #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN} #define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0} @@ -1571,6 +1579,7 @@ extern "C" { #define meshtastic_Waypoint_name_tag 6 #define meshtastic_Waypoint_description_tag 7 #define meshtastic_Waypoint_icon_tag 8 +#define meshtastic_StatusMessage_status_tag 1 #define meshtastic_MqttClientProxyMessage_topic_tag 1 #define meshtastic_MqttClientProxyMessage_data_tag 2 #define meshtastic_MqttClientProxyMessage_text_tag 3 @@ -1806,6 +1815,11 @@ X(a, STATIC, SINGULAR, FIXED32, icon, 8) #define meshtastic_Waypoint_CALLBACK NULL #define meshtastic_Waypoint_DEFAULT NULL +#define meshtastic_StatusMessage_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, status, 1) +#define meshtastic_StatusMessage_CALLBACK NULL +#define meshtastic_StatusMessage_DEFAULT NULL + #define meshtastic_MqttClientProxyMessage_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, STRING, topic, 1) \ X(a, STATIC, ONEOF, BYTES, (payload_variant,data,payload_variant.data), 2) \ @@ -2072,6 +2086,7 @@ extern const pb_msgdesc_t meshtastic_Data_msg; extern const pb_msgdesc_t meshtastic_KeyVerification_msg; extern const pb_msgdesc_t meshtastic_StoreForwardPlusPlus_msg; extern const pb_msgdesc_t meshtastic_Waypoint_msg; +extern const pb_msgdesc_t meshtastic_StatusMessage_msg; extern const pb_msgdesc_t meshtastic_MqttClientProxyMessage_msg; extern const pb_msgdesc_t meshtastic_MeshPacket_msg; extern const pb_msgdesc_t meshtastic_NodeInfo_msg; @@ -2106,6 +2121,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_KeyVerification_fields &meshtastic_KeyVerification_msg #define meshtastic_StoreForwardPlusPlus_fields &meshtastic_StoreForwardPlusPlus_msg #define meshtastic_Waypoint_fields &meshtastic_Waypoint_msg +#define meshtastic_StatusMessage_fields &meshtastic_StatusMessage_msg #define meshtastic_MqttClientProxyMessage_fields &meshtastic_MqttClientProxyMessage_msg #define meshtastic_MeshPacket_fields &meshtastic_MeshPacket_msg #define meshtastic_NodeInfo_fields &meshtastic_NodeInfo_msg @@ -2161,6 +2177,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_QueueStatus_size 23 #define meshtastic_RouteDiscovery_size 256 #define meshtastic_Routing_size 259 +#define meshtastic_StatusMessage_size 81 #define meshtastic_StoreForwardPlusPlus_size 377 #define meshtastic_ToRadio_size 504 #define meshtastic_User_size 115 diff --git a/src/mesh/generated/meshtastic/module_config.pb.cpp b/src/mesh/generated/meshtastic/module_config.pb.cpp index f262df6a3..bb57c3f2d 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.cpp +++ b/src/mesh/generated/meshtastic/module_config.pb.cpp @@ -51,6 +51,9 @@ PB_BIND(meshtastic_ModuleConfig_CannedMessageConfig, meshtastic_ModuleConfig_Can PB_BIND(meshtastic_ModuleConfig_AmbientLightingConfig, meshtastic_ModuleConfig_AmbientLightingConfig, AUTO) +PB_BIND(meshtastic_ModuleConfig_StatusMessageConfig, meshtastic_ModuleConfig_StatusMessageConfig, AUTO) + + PB_BIND(meshtastic_RemoteHardwarePin, meshtastic_RemoteHardwarePin, AUTO) diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index dd0151e3f..46a7164d2 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -409,6 +409,12 @@ typedef struct _meshtastic_ModuleConfig_AmbientLightingConfig { uint8_t blue; } meshtastic_ModuleConfig_AmbientLightingConfig; +/* StatusMessage config - Allows setting a status message for a node to periodically rebroadcast */ +typedef struct _meshtastic_ModuleConfig_StatusMessageConfig { + /* The actual status string */ + char node_status[80]; +} meshtastic_ModuleConfig_StatusMessageConfig; + /* A GPIO pin definition for remote hardware module */ typedef struct _meshtastic_RemoteHardwarePin { /* GPIO Pin number (must match Arduino) */ @@ -460,6 +466,8 @@ typedef struct _meshtastic_ModuleConfig { meshtastic_ModuleConfig_DetectionSensorConfig detection_sensor; /* TODO: REPLACE */ meshtastic_ModuleConfig_PaxcounterConfig paxcounter; + /* TODO: REPLACE */ + meshtastic_ModuleConfig_StatusMessageConfig statusmessage; } payload_variant; } meshtastic_ModuleConfig; @@ -515,6 +523,7 @@ extern "C" { #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_event_press_ENUMTYPE meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar + #define meshtastic_RemoteHardwarePin_type_ENUMTYPE meshtastic_RemoteHardwarePinType @@ -534,6 +543,7 @@ extern "C" { #define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0} +#define meshtastic_ModuleConfig_StatusMessageConfig_init_default {""} #define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN} #define meshtastic_ModuleConfig_init_zero {0, {meshtastic_ModuleConfig_MQTTConfig_init_zero}} #define meshtastic_ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0, 0, "", 0, 0, false, meshtastic_ModuleConfig_MapReportSettings_init_zero} @@ -550,6 +560,7 @@ extern "C" { #define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_zero {0, 0, 0, 0, 0} +#define meshtastic_ModuleConfig_StatusMessageConfig_init_zero {""} #define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN} /* Field tags (for use in manual encoding/decoding) */ @@ -653,6 +664,7 @@ extern "C" { #define meshtastic_ModuleConfig_AmbientLightingConfig_red_tag 3 #define meshtastic_ModuleConfig_AmbientLightingConfig_green_tag 4 #define meshtastic_ModuleConfig_AmbientLightingConfig_blue_tag 5 +#define meshtastic_ModuleConfig_StatusMessageConfig_node_status_tag 1 #define meshtastic_RemoteHardwarePin_gpio_pin_tag 1 #define meshtastic_RemoteHardwarePin_name_tag 2 #define meshtastic_RemoteHardwarePin_type_tag 3 @@ -672,6 +684,7 @@ extern "C" { #define meshtastic_ModuleConfig_ambient_lighting_tag 11 #define meshtastic_ModuleConfig_detection_sensor_tag 12 #define meshtastic_ModuleConfig_paxcounter_tag 13 +#define meshtastic_ModuleConfig_statusmessage_tag 14 /* Struct field encoding specification for nanopb */ #define meshtastic_ModuleConfig_FIELDLIST(X, a) \ @@ -687,7 +700,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,remote_hardware,payload_vari X(a, STATIC, ONEOF, MESSAGE, (payload_variant,neighbor_info,payload_variant.neighbor_info), 10) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ambient_lighting,payload_variant.ambient_lighting), 11) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,detection_sensor,payload_variant.detection_sensor), 12) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.paxcounter), 13) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.paxcounter), 13) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,statusmessage,payload_variant.statusmessage), 14) #define meshtastic_ModuleConfig_CALLBACK NULL #define meshtastic_ModuleConfig_DEFAULT NULL #define meshtastic_ModuleConfig_payload_variant_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig @@ -703,6 +717,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.p #define meshtastic_ModuleConfig_payload_variant_ambient_lighting_MSGTYPE meshtastic_ModuleConfig_AmbientLightingConfig #define meshtastic_ModuleConfig_payload_variant_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig #define meshtastic_ModuleConfig_payload_variant_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig +#define meshtastic_ModuleConfig_payload_variant_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig #define meshtastic_ModuleConfig_MQTTConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ @@ -865,6 +880,11 @@ X(a, STATIC, SINGULAR, UINT32, blue, 5) #define meshtastic_ModuleConfig_AmbientLightingConfig_CALLBACK NULL #define meshtastic_ModuleConfig_AmbientLightingConfig_DEFAULT NULL +#define meshtastic_ModuleConfig_StatusMessageConfig_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, node_status, 1) +#define meshtastic_ModuleConfig_StatusMessageConfig_CALLBACK NULL +#define meshtastic_ModuleConfig_StatusMessageConfig_DEFAULT NULL + #define meshtastic_RemoteHardwarePin_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, gpio_pin, 1) \ X(a, STATIC, SINGULAR, STRING, name, 2) \ @@ -887,6 +907,7 @@ extern const pb_msgdesc_t meshtastic_ModuleConfig_RangeTestConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_TelemetryConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_CannedMessageConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_AmbientLightingConfig_msg; +extern const pb_msgdesc_t meshtastic_ModuleConfig_StatusMessageConfig_msg; extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ @@ -905,6 +926,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_TelemetryConfig_fields &meshtastic_ModuleConfig_TelemetryConfig_msg #define meshtastic_ModuleConfig_CannedMessageConfig_fields &meshtastic_ModuleConfig_CannedMessageConfig_msg #define meshtastic_ModuleConfig_AmbientLightingConfig_fields &meshtastic_ModuleConfig_AmbientLightingConfig_msg +#define meshtastic_ModuleConfig_StatusMessageConfig_fields &meshtastic_ModuleConfig_StatusMessageConfig_msg #define meshtastic_RemoteHardwarePin_fields &meshtastic_RemoteHardwarePin_msg /* Maximum encoded size of messages (where known) */ @@ -921,6 +943,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_RangeTestConfig_size 12 #define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96 #define meshtastic_ModuleConfig_SerialConfig_size 28 +#define meshtastic_ModuleConfig_StatusMessageConfig_size 81 #define meshtastic_ModuleConfig_StoreForwardConfig_size 24 #define meshtastic_ModuleConfig_TelemetryConfig_size 50 #define meshtastic_ModuleConfig_size 227 diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index 6b89c6a37..d31daa4b2 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -91,6 +91,11 @@ typedef enum _meshtastic_PortNum { This module is specifically for Native Linux nodes, and provides a Git-style chain of messages. */ meshtastic_PortNum_STORE_FORWARD_PLUSPLUS_APP = 35, + /* Node Status module + ENCODING: protobuf + This module allows setting an extra string of status for a node. + Broadcasts on change and on a timer, possibly once a day. */ + meshtastic_PortNum_NODE_STATUS_APP = 36, /* Provides a hardware serial interface to send and receive from the Meshtastic network. Connect to the RX/TX pins of a device with 38400 8N1. Packets received from the Meshtastic network is forwarded to the RX pin while sending a packet to TX will go out to the Mesh network. diff --git a/src/mesh/generated/meshtastic/telemetry.pb.cpp b/src/mesh/generated/meshtastic/telemetry.pb.cpp index 345d7a157..fff75ebc1 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.cpp +++ b/src/mesh/generated/meshtastic/telemetry.pb.cpp @@ -33,6 +33,9 @@ PB_BIND(meshtastic_Telemetry, meshtastic_Telemetry, 2) PB_BIND(meshtastic_Nau7802Config, meshtastic_Nau7802Config, AUTO) +PB_BIND(meshtastic_SEN5XState, meshtastic_SEN5XState, AUTO) + + diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index dec89ba15..dc9d876dc 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -361,6 +361,8 @@ typedef struct _meshtastic_LocalStats { uint32_t heap_free_bytes; /* Number of packets that were dropped because the transmit queue was full. */ uint16_t num_tx_dropped; + /* Noise floor value measured in dBm */ + int32_t noise_floor; } meshtastic_LocalStats; /* Health telemetry metrics */ @@ -433,6 +435,25 @@ typedef struct _meshtastic_Nau7802Config { float calibrationFactor; } meshtastic_Nau7802Config; +/* SEN5X State, for saving to flash */ +typedef struct _meshtastic_SEN5XState { + /* Last cleaning time for SEN5X */ + uint32_t last_cleaning_time; + /* Last cleaning time for SEN5X - valid flag */ + bool last_cleaning_valid; + /* Config flag for one-shot mode (see admin.proto) */ + bool one_shot_mode; + /* Last VOC state time for SEN55 */ + bool has_voc_state_time; + uint32_t voc_state_time; + /* Last VOC state validity flag for SEN55 */ + bool has_voc_state_valid; + bool voc_state_valid; + /* VOC state array (8x uint8t) for SEN55 */ + bool has_voc_state_array; + uint64_t voc_state_array; +} meshtastic_SEN5XState; + #ifdef __cplusplus extern "C" { @@ -453,25 +474,28 @@ extern "C" { + /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} -#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_HealthMetrics_init_default {false, 0, false, 0, false, 0} #define meshtastic_HostMetrics_init_default {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_Nau7802Config_init_default {0, 0} +#define meshtastic_SEN5XState_init_default {0, 0, 0, false, 0, false, 0, false, 0} #define meshtastic_DeviceMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} -#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_HealthMetrics_init_zero {false, 0, false, 0, false, 0} #define meshtastic_HostMetrics_init_zero {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} #define meshtastic_Nau7802Config_init_zero {0, 0} +#define meshtastic_SEN5XState_init_zero {0, 0, 0, false, 0, false, 0, false, 0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_DeviceMetrics_battery_level_tag 1 @@ -556,6 +580,7 @@ extern "C" { #define meshtastic_LocalStats_heap_total_bytes_tag 12 #define meshtastic_LocalStats_heap_free_bytes_tag 13 #define meshtastic_LocalStats_num_tx_dropped_tag 14 +#define meshtastic_LocalStats_noise_floor_tag 15 #define meshtastic_HealthMetrics_heart_bpm_tag 1 #define meshtastic_HealthMetrics_spO2_tag 2 #define meshtastic_HealthMetrics_temperature_tag 3 @@ -578,6 +603,12 @@ extern "C" { #define meshtastic_Telemetry_host_metrics_tag 8 #define meshtastic_Nau7802Config_zeroOffset_tag 1 #define meshtastic_Nau7802Config_calibrationFactor_tag 2 +#define meshtastic_SEN5XState_last_cleaning_time_tag 1 +#define meshtastic_SEN5XState_last_cleaning_valid_tag 2 +#define meshtastic_SEN5XState_one_shot_mode_tag 3 +#define meshtastic_SEN5XState_voc_state_time_tag 4 +#define meshtastic_SEN5XState_voc_state_valid_tag 5 +#define meshtastic_SEN5XState_voc_state_array_tag 6 /* Struct field encoding specification for nanopb */ #define meshtastic_DeviceMetrics_FIELDLIST(X, a) \ @@ -678,7 +709,8 @@ X(a, STATIC, SINGULAR, UINT32, num_tx_relay, 10) \ X(a, STATIC, SINGULAR, UINT32, num_tx_relay_canceled, 11) \ X(a, STATIC, SINGULAR, UINT32, heap_total_bytes, 12) \ X(a, STATIC, SINGULAR, UINT32, heap_free_bytes, 13) \ -X(a, STATIC, SINGULAR, UINT32, num_tx_dropped, 14) +X(a, STATIC, SINGULAR, UINT32, num_tx_dropped, 14) \ +X(a, STATIC, SINGULAR, INT32, noise_floor, 15) #define meshtastic_LocalStats_CALLBACK NULL #define meshtastic_LocalStats_DEFAULT NULL @@ -727,6 +759,16 @@ X(a, STATIC, SINGULAR, FLOAT, calibrationFactor, 2) #define meshtastic_Nau7802Config_CALLBACK NULL #define meshtastic_Nau7802Config_DEFAULT NULL +#define meshtastic_SEN5XState_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, last_cleaning_time, 1) \ +X(a, STATIC, SINGULAR, BOOL, last_cleaning_valid, 2) \ +X(a, STATIC, SINGULAR, BOOL, one_shot_mode, 3) \ +X(a, STATIC, OPTIONAL, UINT32, voc_state_time, 4) \ +X(a, STATIC, OPTIONAL, BOOL, voc_state_valid, 5) \ +X(a, STATIC, OPTIONAL, FIXED64, voc_state_array, 6) +#define meshtastic_SEN5XState_CALLBACK NULL +#define meshtastic_SEN5XState_DEFAULT NULL + extern const pb_msgdesc_t meshtastic_DeviceMetrics_msg; extern const pb_msgdesc_t meshtastic_EnvironmentMetrics_msg; extern const pb_msgdesc_t meshtastic_PowerMetrics_msg; @@ -736,6 +778,7 @@ extern const pb_msgdesc_t meshtastic_HealthMetrics_msg; extern const pb_msgdesc_t meshtastic_HostMetrics_msg; extern const pb_msgdesc_t meshtastic_Telemetry_msg; extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; +extern const pb_msgdesc_t meshtastic_SEN5XState_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_DeviceMetrics_fields &meshtastic_DeviceMetrics_msg @@ -747,6 +790,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; #define meshtastic_HostMetrics_fields &meshtastic_HostMetrics_msg #define meshtastic_Telemetry_fields &meshtastic_Telemetry_msg #define meshtastic_Nau7802Config_fields &meshtastic_Nau7802Config_msg +#define meshtastic_SEN5XState_fields &meshtastic_SEN5XState_msg /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size @@ -755,9 +799,10 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; #define meshtastic_EnvironmentMetrics_size 113 #define meshtastic_HealthMetrics_size 11 #define meshtastic_HostMetrics_size 264 -#define meshtastic_LocalStats_size 76 +#define meshtastic_LocalStats_size 87 #define meshtastic_Nau7802Config_size 16 #define meshtastic_PowerMetrics_size 81 +#define meshtastic_SEN5XState_size 27 #define meshtastic_Telemetry_size 272 #ifdef __cplusplus diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 7b7ebb595..ea8d6af8e 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -173,7 +173,7 @@ void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res) if (req->getMethod() == "OPTIONS") { res->setStatusCode(204); // Success with no content - // res->print(""); @todo remove + res->print(""); return; } @@ -223,7 +223,7 @@ void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res) if (req->getMethod() == "OPTIONS") { res->setStatusCode(204); // Success with no content - // res->print(""); @todo remove + res->print(""); return; } diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 75a6ebff8..6d3d616ac 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -235,22 +235,46 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta } case meshtastic_AdminMessage_ota_request_tag: { #if defined(ARCH_ESP32) + LOG_INFO("OTA Requested"); + if (r->ota_request.ota_hash.size != 32) { suppressRebootBanner = true; - LOG_INFO("OTA Failed: Invalid `ota_hash` provided"); + sendWarningAndLog("Cannot start OTA: Invalid `ota_hash` provided."); break; } meshtastic_OTAMode mode = r->ota_request.reboot_ota_mode; + const char *mode_name = (mode == METHOD_OTA_BLE ? "BLE" : "WiFi"); + + // Check that we have an OTA partition + const esp_partition_t *part = MeshtasticOTA::getAppPartition(); + if (part == NULL) { + suppressRebootBanner = true; + sendWarningAndLog("Cannot start OTA: Cannot find OTA Loader partition."); + break; + } + + static esp_app_desc_t app_desc; + if (!MeshtasticOTA::getAppDesc(part, &app_desc)) { + suppressRebootBanner = true; + sendWarningAndLog("Cannot start OTA: Device does have a valid OTA Loader."); + break; + } + + if (!MeshtasticOTA::checkOTACapability(&app_desc, mode)) { + suppressRebootBanner = true; + sendWarningAndLog("OTA Loader does not support %s", mode_name); + break; + } + if (MeshtasticOTA::trySwitchToOTA()) { - LOG_INFO("OTA Requested"); suppressRebootBanner = true; if (screen) screen->startFirmwareUpdateScreen(); MeshtasticOTA::saveConfig(&config.network, mode, r->ota_request.ota_hash.bytes); - LOG_INFO("Rebooting to WiFi OTA"); + sendWarningAndLog("Rebooting to %s OTA", mode_name); } else { - LOG_INFO("WIFI OTA Failed"); + sendWarningAndLog("Unable to switch to the OTA partition."); } #endif int s = 1; // Reboot in 1 second, hard coded @@ -881,10 +905,11 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) { + bool shouldReboot = true; // If we are in an open transaction or configuring MQTT or Serial (which have validation), defer disabling Bluetooth // Otherwise, disable Bluetooth to prevent the phone from interfering with the config - if (!hasOpenEditTransaction && - !IS_ONE_OF(c.which_payload_variant, meshtastic_ModuleConfig_mqtt_tag, meshtastic_ModuleConfig_serial_tag)) { + if (!hasOpenEditTransaction && !IS_ONE_OF(c.which_payload_variant, meshtastic_ModuleConfig_mqtt_tag, + meshtastic_ModuleConfig_serial_tag, meshtastic_ModuleConfig_statusmessage_tag)) { disableBluetooth(); } @@ -976,8 +1001,14 @@ bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) moduleConfig.has_paxcounter = true; moduleConfig.paxcounter = c.payload_variant.paxcounter; break; + case meshtastic_ModuleConfig_statusmessage_tag: + LOG_INFO("Set module config: StatusMessage"); + moduleConfig.has_statusmessage = true; + moduleConfig.statusmessage = c.payload_variant.statusmessage; + shouldReboot = false; + break; } - saveChanges(SEGMENT_MODULECONFIG); + saveChanges(SEGMENT_MODULECONFIG, shouldReboot); return true; } @@ -1156,6 +1187,11 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag; res.get_module_config_response.payload_variant.paxcounter = moduleConfig.paxcounter; break; + case meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG: + LOG_INFO("Get module config: StatusMessage"); + res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_statusmessage_tag; + res.get_module_config_response.payload_variant.statusmessage = moduleConfig.statusmessage; + break; } // NOTE: The phone app needs to know the ls_secsvalue so it can properly expect sleep behavior. @@ -1472,15 +1508,43 @@ void AdminModule::handleSendInputEvent(const meshtastic_AdminMessage_InputEvent #endif } -void AdminModule::sendWarning(const char *message) +void AdminModule::sendWarning(const char *format, ...) { meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); + if (!cn) + return; + cn->level = meshtastic_LogRecord_Level_WARNING; cn->time = getValidTime(RTCQualityFromNet); - strncpy(cn->message, message, sizeof(cn->message)); + + va_list args; + va_start(args, format); + // Format the arguments directly into the notification object + vsnprintf(cn->message, sizeof(cn->message), format, args); + va_end(args); + service->sendClientNotification(cn); } +void AdminModule::sendWarningAndLog(const char *format, ...) +{ + // We need a temporary buffer to hold the formatted text so we can log it + // Using 250 bytes as a safe upper limit for typical text notifications + char buf[250]; + + va_list args; + va_start(args, format); + vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + LOG_WARN(buf); + // 2. Call sendWarning + // SECURITY NOTE: We pass "%s", buf instead of just 'buf'. + // If 'buf' contained a % symbol (e.g. "Battery 50%"), passing it directly + // would crash sendWarning. "%s" treats it purely as text. + sendWarning("%s", buf); +} + void disableBluetooth() { #if HAS_BLUETOOTH diff --git a/src/modules/AdminModule.h b/src/modules/AdminModule.h index 867751f49..c446887b3 100644 --- a/src/modules/AdminModule.h +++ b/src/modules/AdminModule.h @@ -1,7 +1,9 @@ -#include - #pragma once +#ifdef ESP_PLATFORM +#include +#endif #include "ProtobufModule.h" +#include #if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif @@ -71,7 +73,8 @@ class AdminModule : public ProtobufModule, public Obser bool messageIsResponse(const meshtastic_AdminMessage *r); bool messageIsRequest(const meshtastic_AdminMessage *r); - void sendWarning(const char *message); + void sendWarning(const char *format, ...) __attribute__((format(printf, 2, 3))); + void sendWarningAndLog(const char *format, ...) __attribute__((format(printf, 2, 3))); }; static constexpr const char *licensedModeMessage = diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 8d1ba6346..7d7b3cdb1 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -130,8 +130,7 @@ CannedMessageModule::CannedMessageModule() : SinglePortModule("canned", meshtastic_PortNum_TEXT_MESSAGE_APP), concurrency::OSThread("CannedMessage") { this->loadProtoForModule(); - if ((this->splitConfiguredMessages() <= 0) && (cardkb_found.address == 0x00) && !INPUTBROKER_MATRIX_TYPE && - !CANNED_MESSAGE_MODULE_ENABLE) { + if ((this->splitConfiguredMessages() <= 0) && (cardkb_found.address == 0x00) && !INPUTBROKER_MATRIX_TYPE) { LOG_INFO("CannedMessageModule: No messages are configured. Module is disabled"); this->runState = CANNED_MESSAGE_RUN_STATE_DISABLED; disable(); diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index 3d7c09d87..65715dd22 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -27,10 +27,6 @@ enum CannedMessageModuleIconType { shift, backspace, space, enter }; #define CANNED_MESSAGE_MODULE_MESSAGE_MAX_COUNT 50 #define CANNED_MESSAGE_MODULE_MESSAGES_SIZE 800 -#ifndef CANNED_MESSAGE_MODULE_ENABLE -#define CANNED_MESSAGE_MODULE_ENABLE 0 -#endif - // ============================ // Data Structures // ============================ diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 04fcd8e73..5e6985bdf 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -442,6 +442,7 @@ ExternalNotificationModule::ExternalNotificationModule() ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshPacket &mp) { + // Trigger external notification if enabled and not muted; isSilenced is from temporary mute toggles if (moduleConfig.external_notification.enabled && !isSilenced) { #ifdef T_WATCH_S3 drv.setWaveform(0, 75); @@ -456,99 +457,57 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP for (size_t i = 0; i < p.payload.size; i++) { if (p.payload.bytes[i] == ASCII_BELL) { containsBell = true; + break; } } meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from); - bool mutedNode = false; - if (sender) { - mutedNode = (sender->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK); - } meshtastic_Channel ch = channels.getByIndex(mp.channel ? mp.channel : channels.getPrimaryIndex()); - if (moduleConfig.external_notification.alert_bell) { - if (containsBell) { - LOG_INFO("externalNotificationModule - Notification Bell"); + // If we receive a broadcast message, apply channel mute setting + // If we receive a direct message and the receipent is us, apply DM mute setting + // Else we just handle it as not muted. + const bool isDmToUs = !isBroadcast(mp.to) && isToUs(&mp); + bool is_muted = isDmToUs ? (sender && ((sender->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0)) + : (ch.settings.has_module_settings && ch.settings.module_settings.is_muted); + + const bool buzzerModeIsDirectOnly = + (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY); + + if (containsBell || !is_muted) { + if (moduleConfig.external_notification.alert_bell || moduleConfig.external_notification.alert_message || + moduleConfig.external_notification.alert_bell_vibra || + moduleConfig.external_notification.alert_message_vibra || + ((moduleConfig.external_notification.alert_bell_buzzer || + moduleConfig.external_notification.alert_message_buzzer) && + canBuzz())) { + nagCycleCutoff = millis() + (moduleConfig.external_notification.nag_timeout + ? (moduleConfig.external_notification.nag_timeout * 1000) + : moduleConfig.external_notification.output_ms); + LOG_INFO("Toggling nagCycleCutoff to %lu", nagCycleCutoff); isNagging = true; + } + + if (moduleConfig.external_notification.alert_bell || moduleConfig.external_notification.alert_message) { + LOG_INFO("externalNotificationModule - Notification Module or Bell"); setExternalState(0, true); - if (moduleConfig.external_notification.nag_timeout) { - nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; - } else { - nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; - } } - } - if (moduleConfig.external_notification.alert_bell_vibra) { - if (containsBell) { - LOG_INFO("externalNotificationModule - Notification Bell (Vibra)"); - isNagging = true; + if (moduleConfig.external_notification.alert_bell_vibra || + moduleConfig.external_notification.alert_message_vibra) { + LOG_INFO("externalNotificationModule - Notification Module or Bell (Vibra)"); setExternalState(1, true); - if (moduleConfig.external_notification.nag_timeout) { - nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; + } + + if ((moduleConfig.external_notification.alert_bell_buzzer || + moduleConfig.external_notification.alert_message_buzzer) && + canBuzz()) { + LOG_INFO("externalNotificationModule - Notification Module or Bell (Buzzer)"); + if (buzzerModeIsDirectOnly && !isDmToUs && !containsBell) { + LOG_INFO("Message buzzer was suppressed because buzzer mode DIRECT_MSG_ONLY"); } else { - nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; - } - } - } - - if (moduleConfig.external_notification.alert_bell_buzzer && canBuzz()) { - if (containsBell) { - LOG_INFO("externalNotificationModule - Notification Bell (Buzzer)"); - isNagging = true; - if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) { - setExternalState(2, true); - } else { -#ifdef HAS_I2S - if (moduleConfig.external_notification.use_i2s_as_buzzer) { - audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); - } else -#endif - if (moduleConfig.external_notification.use_pwm) { - rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); - } - } - if (moduleConfig.external_notification.nag_timeout) { - nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; - } else { - nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; - } - } - } - - if (moduleConfig.external_notification.alert_message && !mutedNode && - (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) { - LOG_INFO("externalNotificationModule - Notification Module"); - isNagging = true; - setExternalState(0, true); - if (moduleConfig.external_notification.nag_timeout) { - nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; - } else { - nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; - } - } - - if (moduleConfig.external_notification.alert_message_vibra && !mutedNode && - (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) { - LOG_INFO("externalNotificationModule - Notification Module (Vibra)"); - isNagging = true; - setExternalState(1, true); - if (moduleConfig.external_notification.nag_timeout) { - nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; - } else { - nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; - } - } - - if (moduleConfig.external_notification.alert_message_buzzer && !mutedNode && - (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) { - LOG_INFO("externalNotificationModule - Notification Module (Buzzer)"); - if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY || - (!isBroadcast(mp.to) && isToUs(&mp))) { - // Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us - isNagging = true; + // Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us #ifdef T_LORA_PAGER - if (canBuzz()) { drv.setWaveform(0, 16); // Long buzzer 100% drv.setWaveform(1, 0); // Pause drv.setWaveform(2, 16); @@ -558,11 +517,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP drv.setWaveform(6, 16); drv.setWaveform(7, 0); drv.go(); - } #endif - if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) { - setExternalState(2, true); - } else { #ifdef HAS_I2S if (moduleConfig.external_notification.use_i2s_as_buzzer) { audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); @@ -570,18 +525,13 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP #endif if (moduleConfig.external_notification.use_pwm) { rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); + } else { + setExternalState(2, true); } } - if (moduleConfig.external_notification.nag_timeout) { - nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; - } else { - nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; - } - } else { - // Don't beep if buzzer mode is "direct messages only" and it is no direct message - LOG_INFO("Message buzzer was suppressed because buzzer mode DIRECT_MSG_ONLY"); } } + setIntervalFromNow(0); // run once so we know if we should do something } } else { diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index e17868baf..e8fa4775a 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -1,24 +1,8 @@ #include "configuration.h" #if !MESHTASTIC_EXCLUDE_INPUTBROKER #include "buzz/BuzzerFeedbackThread.h" -#include "input/ExpressLRSFiveWay.h" -#include "input/InputBroker.h" -#include "input/RotaryEncoderImpl.h" -#include "input/RotaryEncoderInterruptImpl1.h" -#include "input/SerialKeyboardImpl.h" -#include "input/UpDownInterruptImpl1.h" -#include "input/i2cButton.h" -#include "modules/SystemCommandsModule.h" -#if HAS_TRACKBALL -#include "input/TrackballInterruptImpl1.h" -#endif - #include "modules/StatusLEDModule.h" - -#if !MESHTASTIC_EXCLUDE_I2C -#include "input/cardKbI2cImpl.h" -#endif -#include "input/kbMatrixImpl.h" +#include "modules/SystemCommandsModule.h" #endif #if !MESHTASTIC_EXCLUDE_PKI #include "KeyVerificationModule.h" @@ -59,8 +43,6 @@ #include "modules/WaypointModule.h" #endif #if ARCH_PORTDUINO -#include "input/LinuxInputImpl.h" -#include "input/SeesawRotary.h" #include "modules/Telemetry/HostMetrics.h" #if !MESHTASTIC_EXCLUDE_STOREFORWARD #include "modules/StoreForwardModule.h" @@ -108,7 +90,13 @@ #if !MESHTASTIC_EXCLUDE_DROPZONE #include "modules/DropzoneModule.h" #endif +#if !MESHTASTIC_EXCLUDE_STATUS +#include "modules/StatusMessageModule.h" +#endif +#if defined(HAS_HARDWARE_WATCHDOG) +#include "watchdog/watchdogThread.h" +#endif /** * Create module instances here. If you are adding a new module, you must 'new' it here (or somewhere else) */ @@ -165,6 +153,9 @@ void setupModules() #if !MESHTASTIC_EXCLUDE_DROPZONE dropzoneModule = new DropzoneModule(); #endif +#if !MESHTASTIC_EXCLUDE_STATUS + statusMessageModule = new StatusMessageModule(); +#endif #if !MESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE new GenericThreadModule(); #endif @@ -179,63 +170,6 @@ void setupModules() #endif // Example: Put your module here // new ReplyModule(); -#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER - if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { -#if defined(T_LORA_PAGER) - // use a special FSM based rotary encoder version for T-LoRa Pager - rotaryEncoderImpl = new RotaryEncoderImpl(); - if (!rotaryEncoderImpl->init()) { - delete rotaryEncoderImpl; - rotaryEncoderImpl = nullptr; - } -#elif defined(INPUTDRIVER_ENCODER_TYPE) && (INPUTDRIVER_ENCODER_TYPE == 2) - upDownInterruptImpl1 = new UpDownInterruptImpl1(); - if (!upDownInterruptImpl1->init()) { - delete upDownInterruptImpl1; - upDownInterruptImpl1 = nullptr; - } -#else - rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1(); - if (!rotaryEncoderInterruptImpl1->init()) { - delete rotaryEncoderInterruptImpl1; - rotaryEncoderInterruptImpl1 = nullptr; - } -#endif - cardKbI2cImpl = new CardKbI2cImpl(); - cardKbI2cImpl->init(); -#if defined(M5STACK_UNITC6L) - i2cButton = new i2cButtonThread("i2cButtonThread"); -#endif -#ifdef INPUTBROKER_MATRIX_TYPE - kbMatrixImpl = new KbMatrixImpl(); - kbMatrixImpl->init(); -#endif // INPUTBROKER_MATRIX_TYPE -#ifdef INPUTBROKER_SERIAL_TYPE - aSerialKeyboardImpl = new SerialKeyboardImpl(); - aSerialKeyboardImpl->init(); -#endif // INPUTBROKER_MATRIX_TYPE - } -#endif // HAS_BUTTON -#if ARCH_PORTDUINO - if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR && portduino_config.i2cdev != "") { - seesawRotary = new SeesawRotary("SeesawRotary"); - if (!seesawRotary->init()) { - delete seesawRotary; - seesawRotary = nullptr; - } - aLinuxInputImpl = new LinuxInputImpl(); - aLinuxInputImpl->init(); - } -#endif -#if !MESHTASTIC_EXCLUDE_INPUTBROKER && HAS_TRACKBALL - if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { - trackballInterruptImpl1 = new TrackballInterruptImpl1(); - trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS); - } -#endif -#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE - expressLRSFiveWayInput = new ExpressLRSFiveWay(); -#endif #if HAS_SCREEN && !MESHTASTIC_EXCLUDE_CANNEDMESSAGES if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { cannedMessageModule = new CannedMessageModule(); @@ -304,6 +238,9 @@ void setupModules() #if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS if (moduleConfig.has_range_test && moduleConfig.range_test.enabled) new RangeTestModule(); +#endif +#if defined(HAS_HARDWARE_WATCHDOG) + watchdogThread = new WatchdogThread(); #endif // NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra // acks diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index 7db8b66cc..a568505ae 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -2,6 +2,7 @@ #include "Default.h" #include "MeshService.h" #include "NodeDB.h" +#include "NodeStatus.h" #include "RTC.h" #include "Router.h" #include "configuration.h" @@ -129,14 +130,17 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply() LOG_DEBUG("Skip send NodeInfo > 40%% ch. util"); return NULL; } - // If we sent our NodeInfo less than 5 min. ago, don't send it again as it may be still underway. - if (!shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 5 * 60 * 1000)) { - LOG_DEBUG("Skip send NodeInfo since we sent it <5min ago"); + + // Use graduated scaling based on active mesh size (10 minute base, scales with congestion coefficient) + uint32_t timeoutMs = Default::getConfiguredOrDefaultMsScaled(0, 10 * 60, nodeStatus->getNumOnline()); + if (!shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, timeoutMs)) { + LOG_DEBUG("Skip send NodeInfo since we sent it <%us ago", timeoutMs / 1000); ignoreRequest = true; // Mark it as ignored for MeshModule return NULL; } else if (shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 60 * 1000)) { + // For interactive/urgent requests (e.g., user-triggered or implicit requests), use a shorter 60s timeout LOG_DEBUG("Skip send NodeInfo since we sent it <60s ago"); - ignoreRequest = true; // Mark it as ignored for MeshModule + ignoreRequest = true; return NULL; } else { ignoreRequest = false; // Don't ignore requests anymore diff --git a/src/modules/ReplyBotModule.cpp b/src/modules/ReplyBotModule.cpp new file mode 100644 index 000000000..90223cacb --- /dev/null +++ b/src/modules/ReplyBotModule.cpp @@ -0,0 +1,183 @@ +#if !MESHTASTIC_EXCLUDE_REPLYBOT +/* + * ReplyBotModule.cpp + * + * This module implements a simple reply bot for the Meshtastic firmware. It listens for + * specific text commands ("/ping", "/hello" and "/test") delivered either via a direct + * message (DM) or a broadcast on the primary channel. When a supported command is + * received the bot responds with a short status message that includes the hop count + * (minimum number of relays), RSSI and SNR of the received packet. To avoid spamming + * the network it enforces a per‑sender cooldown between responses. By default the + * module is enabled; define MESHTASTIC_EXCLUDE_REPLYBOT at build time to exclude it + * entirely. See the official firmware documentation for guidance on adding modules. + */ + +#include "ReplyBotModule.h" +#include "Channels.h" +#include "MeshService.h" +#include "NodeDB.h" +#include "configuration.h" +#include "mesh/MeshTypes.h" + +#include +#include +#include + +// +// Rate limiting data structures +// +// Each sender is tracked in a small ring buffer. When a message arrives from a +// sender we check the last time we responded to them. If the difference is +// less than the configured cooldown (different values for DM vs broadcast) +// the message is ignored; otherwise we update the last response time and +// proceed with replying. + +struct ReplyBotCooldownEntry { + uint32_t from = 0; + uint32_t lastMs = 0; +}; + +static constexpr uint8_t REPLYBOT_COOLDOWN_SLOTS = 8; // ring buffer size +static constexpr uint32_t REPLYBOT_DM_COOLDOWN_MS = 15 * 1000; // 15 seconds for DMs +static constexpr uint32_t REPLYBOT_LF_COOLDOWN_MS = 60 * 1000; // 60 seconds for LongFast broadcasts + +static ReplyBotCooldownEntry replybotCooldown[REPLYBOT_COOLDOWN_SLOTS]; +static uint8_t replybotCooldownIdx = 0; + +// Return true if a reply should be rate‑limited for this sender, updating the +// entry table as needed. +static bool replybotRateLimited(uint32_t from, uint32_t cooldownMs) +{ + const uint32_t now = millis(); + for (auto &e : replybotCooldown) { + if (e.from == from) { + // Found existing entry; check if cooldown expired + if ((uint32_t)(now - e.lastMs) < cooldownMs) { + return true; + } + e.lastMs = now; + return false; + } + } + // No entry found – insert new sender into the ring + replybotCooldown[replybotCooldownIdx].from = from; + replybotCooldown[replybotCooldownIdx].lastMs = now; + replybotCooldownIdx = (replybotCooldownIdx + 1) % REPLYBOT_COOLDOWN_SLOTS; + return false; +} + +// Constructor – registers a single text port and marks the module promiscuous +// so that broadcast messages on the primary channel are visible. +ReplyBotModule::ReplyBotModule() : SinglePortModule("replybot", meshtastic_PortNum_TEXT_MESSAGE_APP) +{ + isPromiscuous = true; +} + +void ReplyBotModule::setup() +{ + // In future we may add a protobuf configuration; for now the module is + // always enabled when compiled in. +} + +// Determine whether we want to process this packet. We only care about +// plain text messages addressed to our port. +bool ReplyBotModule::wantPacket(const meshtastic_MeshPacket *p) +{ + return (p && p->decoded.portnum == ourPortNum); +} + +ProcessMessage ReplyBotModule::handleReceived(const meshtastic_MeshPacket &mp) +{ + // Accept only direct messages to us or broadcasts on the Primary channel + // (regardless of modem preset: LongFast, MediumFast, etc). + + const uint32_t ourNode = nodeDB->getNodeNum(); + const bool isDM = (mp.to == ourNode); + const bool isPrimaryChannel = (mp.channel == channels.getPrimaryIndex()) && isBroadcast(mp.to); + if (!isDM && !isPrimaryChannel) { + return ProcessMessage::CONTINUE; + } + + // Ignore empty payloads + if (mp.decoded.payload.size == 0) { + return ProcessMessage::CONTINUE; + } + + // Copy payload into a null‑terminated buffer + char buf[260]; + memset(buf, 0, sizeof(buf)); + size_t n = mp.decoded.payload.size; + if (n > sizeof(buf) - 1) + n = sizeof(buf) - 1; + memcpy(buf, mp.decoded.payload.bytes, n); + + // React only to supported slash commands + if (!isCommand(buf)) { + return ProcessMessage::CONTINUE; + } + + // Apply rate limiting per sender depending on DM/broadcast + const uint32_t cooldownMs = isDM ? REPLYBOT_DM_COOLDOWN_MS : REPLYBOT_LF_COOLDOWN_MS; + if (replybotRateLimited(mp.from, cooldownMs)) { + return ProcessMessage::CONTINUE; + } + + // Compute hop count indicator – if the relay_node is non‑zero we know + // there was at least one relay. Some firmware builds support a hop_start + // field which could be used for more accurate counts, but here we use + // the available relay_node flag only. + // int hopsAway = mp.hop_start - mp.hop_limit; + int hopsAway = getHopsAway(mp); + + // Normalize RSSI: if positive adjust down by 200 to align with typical values + int rssi = mp.rx_rssi; + if (rssi > 0) { + rssi -= 200; + } + float snr = mp.rx_snr; + + // Build the reply message and send it back via DM + char reply[96]; + snprintf(reply, sizeof(reply), "🎙️ Mic Check : %d Hops away | RSSI %d | SNR %.1f", hopsAway, rssi, snr); + sendDm(mp, reply); + return ProcessMessage::CONTINUE; +} + +// Check if the message starts with one of the supported commands. Leading +// whitespace is skipped and commands must be followed by end‑of‑string or +// whitespace. +bool ReplyBotModule::isCommand(const char *msg) const +{ + if (!msg) + return false; + while (*msg == ' ' || *msg == '\t') + msg++; + auto isEndOrSpace = [](char c) { return c == '\0' || std::isspace(static_cast(c)); }; + if (strncmp(msg, "/ping", 5) == 0 && isEndOrSpace(msg[5])) + return true; + if (strncmp(msg, "/hello", 6) == 0 && isEndOrSpace(msg[6])) + return true; + if (strncmp(msg, "/test", 5) == 0 && isEndOrSpace(msg[5])) + return true; + return false; +} + +// Send a direct message back to the originating node. +void ReplyBotModule::sendDm(const meshtastic_MeshPacket &rx, const char *text) +{ + if (!text) + return; + meshtastic_MeshPacket *p = allocDataPacket(); + p->to = rx.from; + p->channel = rx.channel; + p->want_ack = false; + p->decoded.want_response = false; + size_t len = strlen(text); + if (len > sizeof(p->decoded.payload.bytes)) { + len = sizeof(p->decoded.payload.bytes); + } + p->decoded.payload.size = len; + memcpy(p->decoded.payload.bytes, text, len); + service->sendToMesh(p); +} +#endif // MESHTASTIC_EXCLUDE_REPLYBOT \ No newline at end of file diff --git a/src/modules/ReplyBotModule.h b/src/modules/ReplyBotModule.h new file mode 100644 index 000000000..7413667ca --- /dev/null +++ b/src/modules/ReplyBotModule.h @@ -0,0 +1,18 @@ +#pragma once +#if !MESHTASTIC_EXCLUDE_REPLYBOT +#include "SinglePortModule.h" +#include "mesh/generated/meshtastic/mesh.pb.h" + +class ReplyBotModule : public SinglePortModule +{ + public: + ReplyBotModule(); + void setup() override; + bool wantPacket(const meshtastic_MeshPacket *p) override; + ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; + + protected: + bool isCommand(const char *msg) const; + void sendDm(const meshtastic_MeshPacket &rx, const char *text); +}; +#endif // MESHTASTIC_EXCLUDE_REPLYBOT \ No newline at end of file diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp index e9e1fc786..d87cf3a44 100644 --- a/src/modules/RoutingModule.cpp +++ b/src/modules/RoutingModule.cpp @@ -67,6 +67,8 @@ uint8_t RoutingModule::getHopLimitForResponse(const meshtastic_MeshPacket &mp) #if !(EVENTMODE) // This falls through to the default. return hopsUsed; // If the request used more hops than the limit, use the same amount of hops #endif + } else if (mp.hop_start == 0) { + return 0; // The requesting node wanted 0 hops, so the response also uses a direct/local path. } else if ((uint8_t)(hopsUsed + 2) < config.lora.hop_limit) { return hopsUsed + 2; // Use only the amount of hops needed with some margin as the way back may be different } diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 5699f3be6..7a969343e 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -63,29 +63,26 @@ SerialModule *serialModule; SerialModuleRadio *serialModuleRadio; -#if defined(TTGO_T_ECHO) || defined(TTGO_T_ECHO_PLUS) || defined(CANARYONE) || defined(MESHLINK) || \ - defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M4) || defined(ELECROW_ThinkNode_M5) || \ - defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) || defined(ELECROW_ThinkNode_M3) || defined(MUZI_BASE) - -SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") -{ - api_type = TYPE_SERIAL; -} -static Print *serialPrint = &Serial; -#elif defined(CONFIG_IDF_TARGET_ESP32C6) || defined(RAK3172) || defined(EBYTE_E77_MBL) -SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial") -{ - api_type = TYPE_SERIAL; -} -static Print *serialPrint = &Serial1; -#else -SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("Serial") -{ - api_type = TYPE_SERIAL; -} -static Print *serialPrint = &Serial2; +#ifndef SERIAL_PRINT_PORT +#define SERIAL_PRINT_PORT 2 #endif +#if SERIAL_PRINT_PORT == 0 +#define SERIAL_PRINT_OBJECT Serial +#elif SERIAL_PRINT_PORT == 1 +#define SERIAL_PRINT_OBJECT Serial1 +#elif SERIAL_PRINT_PORT == 2 +#define SERIAL_PRINT_OBJECT Serial2 +#else +#error "Unsupported SERIAL_PRINT_PORT value. Allowed values are 0, 1, or 2." +#endif + +SerialModule::SerialModule() : StreamAPI(&SERIAL_PRINT_OBJECT), concurrency::OSThread("Serial") +{ + api_type = TYPE_SERIAL; +} +static Print *serialPrint = &SERIAL_PRINT_OBJECT; + char serialBytes[512]; size_t serialPayloadSize; @@ -205,9 +202,7 @@ int32_t SerialModule::runOnce() Serial.begin(baud); Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); } -#elif !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && \ - !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M4) && \ - !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE) +#elif SERIAL_PRINT_PORT != 0 if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { #ifdef ARCH_RP2040 @@ -264,9 +259,7 @@ int32_t SerialModule::runOnce() } } -#if !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \ - !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M4) && \ - !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE) +#if SERIAL_PRINT_PORT != 0 else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) { processWXSerial(); @@ -540,11 +533,7 @@ ParsedLine parseLine(const char *line) */ void SerialModule::processWXSerial() { -#if !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && \ - !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && \ - !defined(ELECROW_ThinkNode_M3) && \ - !defined(ELECROW_ThinkNode_M4) && \ - !defined(ELECROW_ThinkNode_M5) && !defined(ARCH_STM32WL) && !defined(MUZI_BASE) +#if SERIAL_PRINT_PORT != 0 && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6) static unsigned int lastAveraged = 0; static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded. diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp index 33aa58127..e7a405bdf 100644 --- a/src/modules/StatusLEDModule.cpp +++ b/src/modules/StatusLEDModule.cpp @@ -50,9 +50,10 @@ int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg) break; } case meshtastic::BluetoothStatus::ConnectionState::CONNECTED: { - ble_state = connected; - PAIRING_LED_starttime = millis(); - break; + if (ble_state != connected) { + ble_state = connected; + PAIRING_LED_starttime = millis(); + } } } @@ -129,7 +130,6 @@ int32_t StatusLEDModule::runOnce() #ifdef LED_CHARGE digitalWrite(LED_CHARGE, CHARGE_LED_state); #endif - // digitalWrite(green_LED_PIN, LED_STATE_OFF); #ifdef LED_PAIRING digitalWrite(LED_PAIRING, PAIRING_LED_state); #endif diff --git a/src/modules/StatusMessageModule.cpp b/src/modules/StatusMessageModule.cpp new file mode 100644 index 000000000..139a74d8e --- /dev/null +++ b/src/modules/StatusMessageModule.cpp @@ -0,0 +1,41 @@ +#if !MESHTASTIC_EXCLUDE_STATUS + +#include "StatusMessageModule.h" +#include "MeshService.h" +#include "ProtobufModule.h" + +StatusMessageModule *statusMessageModule; + +int32_t StatusMessageModule::runOnce() +{ + if (moduleConfig.has_statusmessage && moduleConfig.statusmessage.node_status[0] != '\0') { + // create and send message with the status message set + meshtastic_StatusMessage ourStatus = meshtastic_StatusMessage_init_zero; + strncpy(ourStatus.status, moduleConfig.statusmessage.node_status, sizeof(ourStatus.status)); + ourStatus.status[sizeof(ourStatus.status) - 1] = '\0'; // ensure null termination + meshtastic_MeshPacket *p = allocDataPacket(); + p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), + meshtastic_StatusMessage_fields, &ourStatus); + p->to = NODENUM_BROADCAST; + p->decoded.want_response = false; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; + p->channel = 0; + service->sendToMesh(p); + } + + return 1000 * 12 * 60 * 60; +} + +ProcessMessage StatusMessageModule::handleReceived(const meshtastic_MeshPacket &mp) +{ + if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + meshtastic_StatusMessage incomingMessage; + if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_StatusMessage_fields, + &incomingMessage)) { + LOG_INFO("Received a NodeStatus message %s", incomingMessage.status); + } + } + return ProcessMessage::CONTINUE; +} + +#endif \ No newline at end of file diff --git a/src/modules/StatusMessageModule.h b/src/modules/StatusMessageModule.h new file mode 100644 index 000000000..c9ff54018 --- /dev/null +++ b/src/modules/StatusMessageModule.h @@ -0,0 +1,35 @@ +#pragma once +#if !MESHTASTIC_EXCLUDE_STATUS +#include "SinglePortModule.h" +#include "configuration.h" + +class StatusMessageModule : public SinglePortModule, private concurrency::OSThread +{ + + public: + /** Constructor + * name is for debugging output + */ + StatusMessageModule() + : SinglePortModule("statusMessage", meshtastic_PortNum_NODE_STATUS_APP), concurrency::OSThread("StatusMessage") + { + if (moduleConfig.has_statusmessage && moduleConfig.statusmessage.node_status[0] != '\0') { + this->setInterval(2 * 60 * 1000); + } else { + this->setInterval(1000 * 12 * 60 * 60); + } + // TODO: If we have a string, set the initial delay (15 minutes maybe) + } + + virtual int32_t runOnce() override; + + protected: + /** Called to handle a particular incoming message + */ + virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; + + private: +}; + +extern StatusMessageModule *statusMessageModule; +#endif \ No newline at end of file diff --git a/src/modules/StoreForwardModule.cpp b/src/modules/StoreForwardModule.cpp index b8a710bf5..023a1c798 100644 --- a/src/modules/StoreForwardModule.cpp +++ b/src/modules/StoreForwardModule.cpp @@ -513,7 +513,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, LOG_DEBUG("StoreAndForward_RequestResponse_ROUTER_BUSY"); // retry in messages_saved * packetTimeMax ms retry_delay = millis() + getNumAvailablePackets(this->busyTo, this->last_time) * packetTimeMax * - (meshtastic_StoreAndForward_RequestResponse_ROUTER_ERROR ? 2 : 1); + (p->rr == meshtastic_StoreAndForward_RequestResponse_ROUTER_ERROR ? 2 : 1); } break; diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index ec6fe4799..140c2c17e 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -53,7 +53,7 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c #include "Sensor/LTR390UVSensor.h" #endif -#if __has_include(MESHTASTIC_BME680_HEADER) +#if __has_include() || __has_include() #include "Sensor/BME680Sensor.h" #endif @@ -187,7 +187,7 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) #if __has_include() addSensor(i2cScanner, ScanI2C::DeviceType::LTR390UV); #endif -#if __has_include(MESHTASTIC_BME680_HEADER) +#if __has_include() || __has_include() addSensor(i2cScanner, ScanI2C::DeviceType::BME_680); #endif #if __has_include() @@ -529,37 +529,46 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m) { - bool valid = true; + bool valid = false; bool hasSensor = false; + // getMetrics() doesn't always get evaluated because of + // short-circuit evaluation rules in c++ + bool get_metrics; m->time = getTime(); m->which_variant = meshtastic_Telemetry_environment_metrics_tag; m->variant.environment_metrics = meshtastic_EnvironmentMetrics_init_zero; for (TelemetrySensor *sensor : sensors) { - valid = valid && sensor->getMetrics(m); + get_metrics = sensor->getMetrics(m); // avoid short-circuit evaluation rules + valid = valid || get_metrics; hasSensor = true; } #ifndef T1000X_SENSOR_EN if (ina219Sensor.hasSensor()) { - valid = valid && ina219Sensor.getMetrics(m); + get_metrics = ina219Sensor.getMetrics(m); + valid = valid || get_metrics; hasSensor = true; } if (ina260Sensor.hasSensor()) { - valid = valid && ina260Sensor.getMetrics(m); + get_metrics = ina260Sensor.getMetrics(m); + valid = valid || get_metrics; hasSensor = true; } if (ina3221Sensor.hasSensor()) { - valid = valid && ina3221Sensor.getMetrics(m); + get_metrics = ina3221Sensor.getMetrics(m); + valid = valid || get_metrics; hasSensor = true; } if (max17048Sensor.hasSensor()) { - valid = valid && max17048Sensor.getMetrics(m); + get_metrics = max17048Sensor.getMetrics(m); + valid = valid || get_metrics; hasSensor = true; } #endif #ifdef HAS_RAKPROT - valid = valid && rak9154Sensor.getMetrics(m); + get_metrics = rak9154Sensor.getMetrics(m); + valid = valid || get_metrics; hasSensor = true; #endif return valid && hasSensor; diff --git a/src/modules/Telemetry/HealthTelemetry.cpp b/src/modules/Telemetry/HealthTelemetry.cpp index 572f0281a..bb3555062 100644 --- a/src/modules/Telemetry/HealthTelemetry.cpp +++ b/src/modules/Telemetry/HealthTelemetry.cpp @@ -168,18 +168,21 @@ bool HealthTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket & bool HealthTelemetryModule::getHealthTelemetry(meshtastic_Telemetry *m) { - bool valid = true; + bool valid = false; bool hasSensor = false; + bool get_metrics; m->time = getTime(); m->which_variant = meshtastic_Telemetry_health_metrics_tag; m->variant.health_metrics = meshtastic_HealthMetrics_init_zero; if (max30102Sensor.hasSensor()) { - valid = valid && max30102Sensor.getMetrics(m); + get_metrics = max30102Sensor.getMetrics(m); + valid = valid || get_metrics; // avoid short-circuit evaluation rules hasSensor = true; } if (mlx90614Sensor.hasSensor()) { - valid = valid && mlx90614Sensor.getMetrics(m); + get_metrics = mlx90614Sensor.getMetrics(m); + valid = valid || get_metrics; hasSensor = true; } diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp index 22330ca75..3a1eb9532 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp @@ -1,6 +1,6 @@ #include "configuration.h" -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER) +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && (__has_include() || __has_include()) #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "BME680Sensor.h" @@ -10,7 +10,7 @@ BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {} -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() int32_t BME680Sensor::runOnce() { if (!bme680.run()) { @@ -18,13 +18,13 @@ int32_t BME680Sensor::runOnce() } return 35; } -#endif // defined(MESHTASTIC_BME680_BSEC2_SUPPORTED) +#endif bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { status = 0; -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() if (!bme680.begin(dev->address.address, *bus)) checkStatus("begin"); @@ -56,7 +56,7 @@ bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) status = 1; -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif initI2CSensor(); return status; @@ -64,7 +64,7 @@ bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement) { -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() if (bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal == 0) return false; @@ -98,11 +98,11 @@ bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement) measurement->variant.environment_metrics.barometric_pressure = bme680->readPressure() / 100.0F; measurement->variant.environment_metrics.gas_resistance = bme680->readGas() / 1000.0; -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif return true; } -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() void BME680Sensor::loadState() { #ifdef FSCom @@ -179,6 +179,6 @@ void BME680Sensor::checkStatus(const char *functionName) else if (bme680.sensor.status > BME68X_OK) LOG_WARN("%s BME68X code: %d", functionName, bme680.sensor.status); } -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif #endif diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.h b/src/modules/Telemetry/Sensor/BME680Sensor.h index 9bef56e1e..eaeceb848 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.h +++ b/src/modules/Telemetry/Sensor/BME680Sensor.h @@ -1,29 +1,29 @@ #include "configuration.h" -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER) +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && (__has_include() || __has_include()) #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() #include #include #else #include #include -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif #define STATE_SAVE_PERIOD UINT32_C(360 * 60 * 1000) // That's 6 hours worth of millis() -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() const uint8_t bsec_config[] = { #include "config/bme680/bme680_iaq_33v_3s_4d/bsec_iaq.txt" }; -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif class BME680Sensor : public TelemetrySensor { private: -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() Bsec2 bme680; #else using BME680Ptr = std::unique_ptr; @@ -31,10 +31,10 @@ class BME680Sensor : public TelemetrySensor static BME680Ptr makeBME680(TwoWire *bus) { return std::make_unique(bus); } BME680Ptr bme680; -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif protected: -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() const char *bsecConfigFileName = "/prefs/bsec.dat"; uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0}; uint8_t accuracy = 0; @@ -51,13 +51,13 @@ class BME680Sensor : public TelemetrySensor void loadState(); void updateState(); void checkStatus(const char *functionName); -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif public: BME680Sensor(); -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() virtual int32_t runOnce() override; -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif virtual bool getMetrics(meshtastic_Telemetry *measurement) override; virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; diff --git a/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp b/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp index ff0628cc3..4f3150b25 100644 --- a/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp +++ b/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp @@ -4,6 +4,15 @@ #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "RAK12035Sensor.h" +// The RAK12035 library's sensor_sleep() sets WB_IO2 (GPIO 34) LOW, which controls +// the 3.3V switched power rail (PIN_3V3_EN). This turns off power to ALL peripherals +// including GPS. We need to restore power after the library turns it off. +#ifdef PIN_3V3_EN +#define RESTORE_3V3_POWER() digitalWrite(PIN_3V3_EN, HIGH) +#else +#define RESTORE_3V3_POWER() +#endif + RAK12035Sensor::RAK12035Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RAK12035, "RAK12035") {} bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) @@ -13,16 +22,15 @@ bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) delay(100); sensor.begin(dev->address.address); - // Get sensor firmware version uint8_t data = 0; sensor.get_sensor_version(&data); if (data != 0) { LOG_INFO("Init sensor: %s", sensorName); - LOG_INFO("RAK12035Sensor Init Succeed \nSensor1 Firmware version: %i, Sensor Name: %s", data, sensorName); + LOG_INFO("RAK12035Sensor Init Succeed \nSensor Firmware version: %i, Sensor Name: %s", data, sensorName); status = true; sensor.sensor_sleep(); + RESTORE_3V3_POWER(); } else { - // If we reach here, it means the sensor did not initialize correctly. LOG_INFO("Init sensor: %s", sensorName); LOG_ERROR("RAK12035Sensor Init Failed"); status = false; @@ -38,39 +46,44 @@ bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) void RAK12035Sensor::setup() { - // Set the calibration values - // Reading the saved calibration values from the sensor. // TODO:: Check for and run calibration check for up to 2 additional sensors if present. uint16_t zero_val = 0; uint16_t hundred_val = 0; - uint16_t default_zero_val = 550; - uint16_t default_hundred_val = 420; + const uint16_t default_zero_val = 510; + const uint16_t default_hundred_val = 390; + sensor.sensor_on(); + sensor.begin(); delay(200); sensor.get_dry_cal(&zero_val); + delay(200); sensor.get_wet_cal(&hundred_val); delay(200); - if (zero_val == 0 || zero_val <= hundred_val) { - LOG_INFO("Dry calibration value is %d", zero_val); - LOG_INFO("Wet calibration value is %d", hundred_val); - LOG_INFO("This does not make sense. You can recalibrate this sensor using the calibration sketch included here: " - "https://github.com/RAKWireless/RAK12035_SoilMoisture."); - LOG_INFO("For now, setting default calibration value for Dry Calibration: %d", default_zero_val); + + bool calibrationReset = false; + + if (zero_val == 0) { + LOG_INFO("Dry calibration not set, using default: %d", default_zero_val); sensor.set_dry_cal(default_zero_val); - sensor.get_dry_cal(&zero_val); - LOG_INFO("Dry calibration reset complete. New value is %d", zero_val); + delay(200); + zero_val = default_zero_val; + calibrationReset = true; } if (hundred_val == 0 || hundred_val >= zero_val) { - LOG_INFO("Dry calibration value is %d", zero_val); - LOG_INFO("Wet calibration value is %d", hundred_val); - LOG_INFO("This does not make sense. You can recalibrate this sensor using the calibration sketch included here: " - "https://github.com/RAKWireless/RAK12035_SoilMoisture."); - LOG_INFO("For now, setting default calibration value for Wet Calibration: %d", default_hundred_val); + LOG_INFO("Wet calibration not set, using default: %d", default_hundred_val); sensor.set_wet_cal(default_hundred_val); - sensor.get_wet_cal(&hundred_val); - LOG_INFO("Wet calibration reset complete. New value is %d", hundred_val); + delay(200); + hundred_val = default_hundred_val; + calibrationReset = true; } + if (calibrationReset) { + LOG_INFO("Default calibration values applied. Consider running the calibration sketch for better accuracy: " + "https://github.com/RAKWireless/RAK12035_SoilMoisture"); + } + + LOG_INFO("Dry calibration value: %d, Wet calibration value: %d", zero_val, hundred_val); sensor.sensor_sleep(); + RESTORE_3V3_POWER(); delay(200); LOG_INFO("Dry calibration value is %d", zero_val); LOG_INFO("Wet calibration value is %d", hundred_val); @@ -79,10 +92,6 @@ void RAK12035Sensor::setup() bool RAK12035Sensor::getMetrics(meshtastic_Telemetry *measurement) { // TODO:: read and send metrics for up to 2 additional soil monitors if present. - // -- how to do this.. this could get a little complex.. - // ie - 1> we combine them into an average and send that, 2> we send them as separate metrics - // ^-- these scenarios would require different handling of the metrics in the receiving end and maybe a setting in the - // device ui and an additional proto for that? measurement->variant.environment_metrics.has_soil_temperature = true; measurement->variant.environment_metrics.has_soil_moisture = true; @@ -97,6 +106,7 @@ bool RAK12035Sensor::getMetrics(meshtastic_Telemetry *measurement) success &= sensor.get_sensor_temperature(&temp); delay(200); sensor.sensor_sleep(); + RESTORE_3V3_POWER(); if (success == false) { LOG_ERROR("Failed to read sensor data"); diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 4c2c0fe1b..18a4f913e 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -475,8 +475,10 @@ bool MQTT::publish(const char *topic, const char *payload, bool retained) if (moduleConfig.mqtt.proxy_to_client_enabled) { meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); msg->which_payload_variant = meshtastic_MqttClientProxyMessage_text_tag; - strcpy(msg->topic, topic); - strcpy(msg->payload_variant.text, payload); + strncpy(msg->topic, topic, sizeof(msg->topic)); + msg->topic[sizeof(msg->topic) - 1] = '\0'; + strncpy(msg->payload_variant.text, payload, sizeof(msg->payload_variant.text)); + msg->payload_variant.text[sizeof(msg->payload_variant.text) - 1] = '\0'; msg->retained = retained; service->sendMqttMessageToClientProxy(msg); return true; diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index fc1f27ea2..89d74dbd7 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -686,6 +686,9 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks #ifdef NIMBLE_TWO if (ble->isDeInit) return; +#else + if (nimbleBluetooth && nimbleBluetooth->isDeInit) + return; #endif meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); @@ -754,11 +757,7 @@ void NimbleBluetooth::deinit() isDeInit = true; #ifdef BLE_LED -#ifdef BLE_LED_INVERTED - digitalWrite(BLE_LED, HIGH); -#else - digitalWrite(BLE_LED, LOW); -#endif + digitalWrite(BLE_LED, LED_STATE_OFF); #endif #ifndef NIMBLE_TWO NimBLEDevice::deinit(); diff --git a/src/platform/esp32/MeshtasticOTA.cpp b/src/platform/esp32/MeshtasticOTA.cpp index b8cb052ef..4ca074723 100644 --- a/src/platform/esp32/MeshtasticOTA.cpp +++ b/src/platform/esp32/MeshtasticOTA.cpp @@ -1,13 +1,17 @@ #include "MeshtasticOTA.h" #include "configuration.h" +#ifdef ESP_PLATFORM #include #include +#endif namespace MeshtasticOTA { static const char *nvsNamespace = "MeshtasticOTA"; -static const char *appProjectName = "MeshtasticOTA"; +static const char *combinedAppProjectName = "MeshtasticOTA"; +static const char *bleOnlyAppProjectName = "MeshtasticOTA-BLE"; +static const char *wifiOnlyAppProjectName = "MeshtasticOTA-WiFi"; static bool updated = false; @@ -68,21 +72,44 @@ bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc) LOG_INFO("esp_ota_get_partition_description failed"); return false; } - if (strcmp(app_desc->project_name, appProjectName) != 0) { - LOG_INFO("app_desc->project_name == 0"); - return false; - } return true; } +bool checkOTACapability(esp_app_desc_t *app_desc, uint8_t method) +{ + // Combined loader supports all (both) transports, BLE and WiFi + if (strcmp(app_desc->project_name, combinedAppProjectName) == 0) { + LOG_INFO("OTA partition contains combined BLE/WiFi OTA Loader"); + return true; + } + if (method == METHOD_OTA_BLE && strcmp(app_desc->project_name, bleOnlyAppProjectName) == 0) { + LOG_INFO("OTA partition contains BLE-only OTA Loader"); + return true; + } + if (method == METHOD_OTA_WIFI && strcmp(app_desc->project_name, wifiOnlyAppProjectName) == 0) { + LOG_INFO("OTA partition contains WiFi-only OTA Loader"); + return true; + } + LOG_INFO("OTA partition does not contain a known OTA loader"); + return false; +} + bool trySwitchToOTA() { const esp_partition_t *part = getAppPartition(); - esp_app_desc_t app_desc; - if (!getAppDesc(part, &app_desc)) + + if (part == NULL) { + LOG_WARN("Unable to get app partition in preparation of OTA reboot"); return false; - if (esp_ota_set_boot_partition(part) != ESP_OK) + } + + uint8_t result = esp_ota_set_boot_partition(part); + // Partition and app checks should now be done in the AdminModule before this is called + if (result != ESP_OK) { + LOG_WARN("Unable to switch to OTA partiton. (Reason %d)", result); return false; + } + return true; } diff --git a/src/platform/esp32/MeshtasticOTA.h b/src/platform/esp32/MeshtasticOTA.h index 001eba039..7c158775f 100644 --- a/src/platform/esp32/MeshtasticOTA.h +++ b/src/platform/esp32/MeshtasticOTA.h @@ -3,12 +3,20 @@ #include "mesh-pb-constants.h" #include +#ifdef ESP_PLATFORM +#include +#endif + +#define METHOD_OTA_BLE 1 +#define METHOD_OTA_WIFI 2 namespace MeshtasticOTA { void initialize(); bool isUpdated(); - +const esp_partition_t *getAppPartition(); +bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc); +bool checkOTACapability(esp_app_desc_t *app_desc, uint8_t method); void recoverConfig(meshtastic_Config_NetworkConfig *network); void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t *ota_hash); bool trySwitchToOTA(); diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index f34f1fc65..7aee45f81 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -33,9 +33,6 @@ #ifndef HAS_RADIO #define HAS_RADIO 1 #endif -#ifndef HAS_RTC -#define HAS_RTC 1 -#endif #ifndef HAS_CPU_SHUTDOWN #define HAS_CPU_SHUTDOWN 1 #endif diff --git a/src/platform/extra_variants/README.md b/src/platform/extra_variants/README.md index e558502f0..838014c4f 100644 --- a/src/platform/extra_variants/README.md +++ b/src/platform/extra_variants/README.md @@ -5,7 +5,7 @@ This directory tree is designed to solve two problems. - The ESP32 arduino/platformio project doesn't support the nice "if initVariant() is found, call that after init" behavior of the nrf52 builds (they use initVariant() internally). - Over the years a lot of 'board specific' init code has been added to init() in main.cpp. It would be great to have a general/clean mechanism to allow developers to specify board specific/unique code in a clean fashion without mucking in main. -So we are borrowing the initVariant() ideas here (by using weak gcc references). You can now define lateInitVariant() if your board needs it. +So we are borrowing the initVariant() ideas here (by using weak gcc references). You can now define earlyInitVariant() and lateInitVariant() if your board needs them. earlyInitVariant() runs at the beginning of setup() directly after waitUntilPowerLevelSafe(); while lateInitVariant() runs after the LoRa radio is initialized. If you'd like a board specific variant to be run, add the variant.cpp file to an appropriately named subdirectory and check for \_VARIANT_boardname in the cpp file (so that your code is only built for your board). diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 4f7fb4776..6a552f236 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -119,7 +119,7 @@ void startAdv(void) Bluefruit.Advertising.addService(meshBleService); /* Start Advertising * - Enable auto advertising if disconnected - * - Interval: fast mode = 20 ms, slow mode = 152.5 ms + * - Interval: fast mode = 20 ms, slow mode = 417,5 ms * - Timeout for fast mode is 30 seconds * - Start(timeout) with timeout = 0 will advertise forever (until connected) * @@ -127,7 +127,7 @@ void startAdv(void) * https://developer.apple.com/library/content/qa/qa1931/_index.html */ Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setInterval(32, 668); // in unit of 0.625 ms Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds. FIXME, we should stop advertising after X } @@ -240,6 +240,12 @@ int NRF52Bluetooth::getRssi() { return 0; // FIXME figure out where to source this } + +// Valid BLE TX power levels as per nRF52840 Product Specification are: "-20 to +8 dBm TX power, configurable in 4 dB steps". +// See https://docs.nordicsemi.com/bundle/ps_nrf52840/page/keyfeatures_html5.html +#define VALID_BLE_TX_POWER(x) \ + ((x) == -20 || (x) == -16 || (x) == -12 || (x) == -8 || (x) == -4 || (x) == 0 || (x) == 4 || (x) == 8) + void NRF52Bluetooth::setup() { // Initialise the Bluefruit module @@ -251,6 +257,9 @@ void NRF52Bluetooth::setup() Bluefruit.Advertising.stop(); Bluefruit.Advertising.clearData(); Bluefruit.ScanResponse.clearData(); +#if defined(NRF52_BLE_TX_POWER) && VALID_BLE_TX_POWER(NRF52_BLE_TX_POWER) + Bluefruit.setTxPower(NRF52_BLE_TX_POWER); +#endif if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { configuredPasskey = config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_FIXED_PIN ? config.bluetooth.fixed_pin @@ -272,6 +281,29 @@ void NRF52Bluetooth::setup() // Set the connect/disconnect callback handlers Bluefruit.Periph.setConnectCallback(onConnect); Bluefruit.Periph.setDisconnectCallback(onDisconnect); + + // Do not change Slave Latency to value other than 0 !!! + // There is probably a bug in SoftDevice + certain Apple iOS versions being + // brain damaged causing connectivity problems. + + // On one side it seems SoftDevice is using SlaveLatency value even + // if connection parameter negotation failed and phone sees it as connectivity errors. + + // On the other hand Apple can randomly refuse any parameter negotiation and shutdown connection + // even if you meet Apple Developer Guidelines for BLE devices. Because f* you, that's why. + + // While this API call sets preferred connection parameters (PPCP) - many phones ignore it (yeah) and it seems SoftDevice + // will try to renegotiate connection parameters based on those values after phone connection. + // So those are relatively safe values so Apple braindead firmware won't get angry and at least we may try + // to negotiate some longer connection interval to save battery. + + // See https://github.com/meshtastic/firmware/pull/8858 for measurements. We are dealing with microamp savings anyway so not + // worth dying on a hill here. + + Bluefruit.Periph.setConnSlaveLatency(0); + // 1.25 ms units - so min, max is 15, 100 ms range. + Bluefruit.Periph.setConnInterval(12, 80); + #ifndef BLE_DFU_SECURE bledfu.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); bledfu.begin(); // Install the DFU helper @@ -300,7 +332,7 @@ void NRF52Bluetooth::setup() void NRF52Bluetooth::resumeAdvertising() { Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setInterval(32, 668); // in unit of 0.625 ms Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode Bluefruit.Advertising.start(0); } diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 7734c0020..d1965f03e 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -5,6 +5,25 @@ // // defaults for NRF52 architecture // + +/* + * Internal Reference is +/-0.6V, with an adjustable gain of 1/6, 1/5, 1/4, + * 1/3, 1/2 or 1, meaning 3.6, 3.0, 2.4, 1.8, 1.2 or 0.6V for the ADC levels. + * + * External Reference is VDD/4, with an adjustable gain of 1, 2 or 4, meaning + * VDD/4, VDD/2 or VDD for the ADC levels. + * + * Default settings are internal reference with 1/6 gain (GND..3.6V ADC range) + * Some variants overwrite it. + */ +#ifndef AREF_VOLTAGE +#define AREF_VOLTAGE 3.6 +#endif + +#ifndef BATTERY_SENSE_RESOLUTION_BITS +#define BATTERY_SENSE_RESOLUTION_BITS 10 +#endif + #ifndef HAS_BLUETOOTH #define HAS_BLUETOOTH 1 #endif diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 472107229..0376a1dad 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -9,12 +9,12 @@ #define NRFX_WDT_ENABLED 1 #define NRFX_WDT0_ENABLED 1 #define NRFX_WDT_CONFIG_NO_IRQ 1 -#include -#include - +#include "nrfx_power.h" #include #include #include +#include +#include #include // #include #include "NodeDB.h" @@ -23,6 +23,7 @@ #include "main.h" #include "meshUtils.h" #include "power.h" +#include #include @@ -30,6 +31,21 @@ #include "BQ25713.h" #endif +// WARNING! THRESHOLD + HYSTERESIS should be less than regulated VDD voltage - which depends on board +// and is 3.0 or 3.3V. Also VDD likes to read values like 2.9999 so make sure you account for that +// otherwise board will not boot at all. Before you modify this part - please triple read NRF52840 power design +// section in datasheet and you understand how REG0 and REG1 regulators work together. +#ifndef SAFE_VDD_VOLTAGE_THRESHOLD +#define SAFE_VDD_VOLTAGE_THRESHOLD 2.7 +#endif + +// hysteresis value +#ifndef SAFE_VDD_VOLTAGE_THRESHOLD_HYST +#define SAFE_VDD_VOLTAGE_THRESHOLD_HYST 0.2 +#endif + +uint16_t getVDDVoltage(); + // Weak empty variant initialization function. // May be redefined by variant files. void variant_shutdown() __attribute__((weak)); @@ -38,12 +54,95 @@ void variant_shutdown() {} static nrfx_wdt_t nrfx_wdt = NRFX_WDT_INSTANCE(0); static nrfx_wdt_channel_id nrfx_wdt_channel_id_nrf52_main; +// This is a public global so that the debugger can set it to false automatically from our gdbinit +// @phaseloop comment: most part of codebase, including filesystem flash driver depend on softdevice +// methods so disabling it may actually crash thing. Proceed with caution. + +bool useSoftDevice = true; // Set to false for easier debugging + static inline void debugger_break(void) { __asm volatile("bkpt #0x01\n\t" "mov pc, lr\n\t"); } +// PowerHAL NRF52 specific function implementations +bool powerHAL_isVBUSConnected() +{ + return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk; +} + +bool powerHAL_isPowerLevelSafe() +{ + + static bool powerLevelSafe = true; + + uint16_t threshold = SAFE_VDD_VOLTAGE_THRESHOLD * 1000; // convert V to mV + uint16_t hysteresis = SAFE_VDD_VOLTAGE_THRESHOLD_HYST * 1000; + + if (powerLevelSafe) { + if (getVDDVoltage() < threshold) { + powerLevelSafe = false; + } + } else { + // power level is only safe again when it raises above threshold + hysteresis + if (getVDDVoltage() >= (threshold + hysteresis)) { + powerLevelSafe = true; + } + } + + return powerLevelSafe; +} + +void powerHAL_platformInit() +{ + + // Enable POF power failure comparator. It will prevent writing to NVMC flash when supply voltage is too low. + // Set to some low value as last resort - powerHAL_isPowerLevelSafe uses different method and should manage proper node + // behaviour on its own. + + // POFWARN is pretty useless for node power management because it triggers only once and clearing this event will not + // re-trigger it again until voltage rises to safe level and drops again. So we will use SAADC routed to VDD to read safely + // voltage. + + // @phaseloop: I disable POFCON for now because it seems to be unreliable or buggy. Even when set at 2.0V it + // triggers below 2.8V and corrupts data when pairing bluetooth - because it prevents filesystem writes and + // adafruit BLE library triggers lfs_assert which reboots node and formats filesystem. + // I did experiments with bench power supply and no matter what is set to POFCON, it always triggers right below + // 2.8V. I compared raw registry values with datasheet. + + NRF_POWER->POFCON = + ((POWER_POFCON_THRESHOLD_V22 << POWER_POFCON_THRESHOLD_Pos) | (POWER_POFCON_POF_Enabled << POWER_POFCON_POF_Pos)); + + // remember to always match VBAT_AR_INTERNAL with AREF_VALUE in variant definition file +#ifdef VBAT_AR_INTERNAL + analogReference(VBAT_AR_INTERNAL); +#else + analogReference(AR_INTERNAL); // 3.6V +#endif +} + +// get VDD voltage (in millivolts) +uint16_t getVDDVoltage() +{ + // we use the same values as regular battery read so there is no conflict on SAADC + analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS); + + // VDD range on NRF52840 is 1.8-3.3V so we need to remap analog reference to 3.6V + // let's hope battery reading runs in same task and we don't have race condition + analogReference(AR_INTERNAL); + + uint16_t vddADCRead = analogReadVDD(); + float voltage = ((1000 * 3.6) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * vddADCRead; + +// restore default battery reading reference +#ifdef VBAT_AR_INTERNAL + analogReference(VBAT_AR_INTERNAL); +#endif + + return voltage; +} + bool loopCanSleep() { // turn off sleep only while connected via USB @@ -72,22 +171,6 @@ void getMacAddr(uint8_t *dmac) dmac[0] = src[5] | 0xc0; // MSB high two bits get set elsewhere in the bluetooth stack } -static void initBrownout() -{ - auto vccthresh = POWER_POFCON_THRESHOLD_V24; - - auto err_code = sd_power_pof_enable(POWER_POFCON_POF_Enabled); - assert(err_code == NRF_SUCCESS); - - err_code = sd_power_pof_threshold_set(vccthresh); - assert(err_code == NRF_SUCCESS); - - // We don't bother with setting up brownout if soft device is disabled - because during production we always use softdevice -} - -// This is a public global so that the debugger can set it to false automatically from our gdbinit -bool useSoftDevice = true; // Set to false for easier debugging - #if !MESHTASTIC_EXCLUDE_BLUETOOTH void setBluetoothEnable(bool enable) { @@ -106,7 +189,6 @@ void setBluetoothEnable(bool enable) if (!initialized) { nrf52Bluetooth = new NRF52Bluetooth(); nrf52Bluetooth->startDisabled(); - initBrownout(); initialized = true; } return; @@ -120,9 +202,6 @@ void setBluetoothEnable(bool enable) LOG_DEBUG("Init NRF52 Bluetooth"); nrf52Bluetooth = new NRF52Bluetooth(); nrf52Bluetooth->setup(); - - // We delay brownout init until after BLE because BLE starts soft device - initBrownout(); } // Already setup, apparently else @@ -192,9 +271,24 @@ extern "C" void lfs_assert(const char *reason) delay(500); // Give the serial port a bit of time to output that last message. // Try setting GPREGRET with the SoftDevice first. If that fails (perhaps because the SD hasn't been initialize yet) then set // NRF_POWER->GPREGRET directly. - if (!(sd_power_gpregret_clr(0, 0xFF) == NRF_SUCCESS && sd_power_gpregret_set(0, NRF52_MAGIC_LFS_IS_CORRUPT) == NRF_SUCCESS)) { - NRF_POWER->GPREGRET = NRF52_MAGIC_LFS_IS_CORRUPT; + + // TODO: this will/can crash CPU if bluetooth stack is not compiled in or bluetooth is not initialized + // (regardless if enabled or disabled) - as there is no live SoftDevice stack + // implement "safe" functions detecting softdevice stack state and using proper method to set registers + + // do not set GPREGRET if POFWARN is triggered because it means lfs_assert reports flash undervoltage protection + // and not data corruption. Reboot is fine as boot procedure will wait until power level is safe again + + if (!NRF_POWER->EVENTS_POFWARN) { + if (!(sd_power_gpregret_clr(0, 0xFF) == NRF_SUCCESS && + sd_power_gpregret_set(0, NRF52_MAGIC_LFS_IS_CORRUPT) == NRF_SUCCESS)) { + NRF_POWER->GPREGRET = NRF52_MAGIC_LFS_IS_CORRUPT; + } } + + // TODO: this should not be done when SoftDevice is enabled as device will not boot back on soft reset + // as some data is retained in RAM which will prevent re-enabling bluetooth stack + // Google what Nordic has to say about NVIC_* + SoftDevice NVIC_SystemReset(); } @@ -336,15 +430,6 @@ void cpuDeepSleep(uint32_t msecToWake) Serial1.end(); #endif -#ifdef TTGO_T_ECHO - // To power off the T-Echo, the display must be set - // as an input pin; otherwise, there will be leakage current. - pinMode(PIN_EINK_CS, INPUT); - pinMode(PIN_EINK_DC, INPUT); - pinMode(PIN_EINK_RES, INPUT); - pinMode(PIN_EINK_BUSY, INPUT); -#endif - setBluetoothEnable(false); #ifdef RAK4630 @@ -355,57 +440,8 @@ void cpuDeepSleep(uint32_t msecToWake) // RAK-12039 set pin for Air quality sensor digitalWrite(AQ_SET_PIN, LOW); #endif -#ifdef RAK14014 - // GPIO restores input status, otherwise there will be leakage current - nrf_gpio_cfg_default(TFT_BL); - nrf_gpio_cfg_default(TFT_DC); - nrf_gpio_cfg_default(TFT_CS); - nrf_gpio_cfg_default(TFT_SCLK); - nrf_gpio_cfg_default(TFT_MOSI); - nrf_gpio_cfg_default(TFT_MISO); - nrf_gpio_cfg_default(SCREEN_TOUCH_INT); - nrf_gpio_cfg_default(WB_I2C1_SCL); - nrf_gpio_cfg_default(WB_I2C1_SDA); - - // nrf_gpio_cfg_default(WB_I2C2_SCL); - // nrf_gpio_cfg_default(WB_I2C2_SDA); -#endif -#endif -#ifdef MESHLINK -#ifdef PIN_WD_EN - digitalWrite(PIN_WD_EN, LOW); -#endif -#endif - -#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_MESH_SOLAR) - nrf_gpio_cfg_default(PIN_GPS_PPS); - detachInterrupt(PIN_GPS_PPS); - detachInterrupt(PIN_BUTTON1); -#endif - -#ifdef ELECROW_ThinkNode_M1 - for (int pin = 0; pin < 48; pin++) { - if (pin == 17 || pin == 19 || pin == 20 || pin == 22 || pin == 23 || pin == 24 || pin == 25 || pin == 9 || pin == 10 || - pin == PIN_BUTTON1 || pin == PIN_BUTTON2) { - continue; - } - pinMode(pin, OUTPUT); - } - for (int pin = 0; pin < 48; pin++) { - if (pin == 17 || pin == 19 || pin == 20 || pin == 22 || pin == 23 || pin == 24 || pin == 25 || pin == 9 || pin == 10 || - pin == PIN_BUTTON1 || pin == PIN_BUTTON2) { - continue; - } - digitalWrite(pin, LOW); - } - for (int pin = 0; pin < 48; pin++) { - if (pin == 17 || pin == 19 || pin == 20 || pin == 22 || pin == 23 || pin == 24 || pin == 25 || pin == 9 || pin == 10 || - pin == PIN_BUTTON1 || pin == PIN_BUTTON2) { - continue; - } - NRF_GPIO->DIRCLR = (1 << pin); - } #endif + // Run shutdown code if specified in variant.cpp variant_shutdown(); // Sleepy trackers or sensors can low power "sleep" @@ -428,22 +464,6 @@ void cpuDeepSleep(uint32_t msecToWake) // FIXME, use non-init RAM per // https://devzone.nordicsemi.com/f/nordic-q-a/48919/ram-retention-settings-with-softdevice-enabled -#ifdef ELECROW_ThinkNode_M1 - nrf_gpio_cfg_input(PIN_BUTTON1, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input - nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW; - nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense); - - nrf_gpio_cfg_input(PIN_BUTTON2, NRF_GPIO_PIN_PULLUP); - nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW; - nrf_gpio_cfg_sense_set(PIN_BUTTON2, sense1); -#endif - -#ifdef PROMICRO_DIY_TCXO - nrf_gpio_cfg_input(BUTTON_PIN, NRF_GPIO_PIN_PULLUP); // Enable internal pull-up on the button pin - nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW; // Configure SENSE signal on low edge - nrf_gpio_cfg_sense_set(BUTTON_PIN, sense); // Apply SENSE to wake up the device from the deep sleep -#endif - #ifdef BATTERY_LPCOMP_INPUT // Wake up if power rises again nrf_lpcomp_config_t c; diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 7430c2eae..9579bef45 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -29,6 +30,7 @@ #include "platform/portduino/USBHal.h" portduino_config_struct portduino_config; +portduino_status_struct portduino_status; std::ofstream traceFile; std::ofstream JSONFile; Ch341Hal *ch341Hal = nullptr; @@ -55,15 +57,18 @@ void cpuDeepSleep(uint32_t msecs) void updateBatteryLevel(uint8_t level) NOT_IMPLEMENTED("updateBatteryLevel"); int TCPPort = SERVER_API_DEFAULT_PORT; +bool checkConfigPort = true; static error_t parse_opt(int key, char *arg, struct argp_state *state) { switch (key) { case 'p': - if (sscanf(arg, "%d", &TCPPort) < 1) + if (sscanf(arg, "%d", &TCPPort) < 1) { return ARGP_ERR_UNKNOWN; - else + } else { + checkConfigPort = false; printf("Using config file %d\n", TCPPort); + } break; case 'c': configPath = arg; @@ -397,6 +402,11 @@ void portduinoSetup() if (found_hat) { product_config = cleanupNameForAutoconf("lora-hat-" + std::string(hat_vendor) + "-" + autoconf_product + ".yaml"); + if (strncmp(hat_vendor, "RAK", strlen("RAK")) == 0 && + strncmp(autoconf_product, "6421 Pi Hat", strlen("6421 Pi Hat")) == 0) { + std::cout << "autoconf: Setting hardwareModel to RAK6421" << std::endl; + portduino_status.hardwareModel = meshtastic_HardwareModel_RAK6421; + } } else if (found_ch341) { product_config = cleanupNameForAutoconf("lora-usb-" + std::string(autoconf_product) + ".yaml"); // look for more data after the null terminator @@ -405,6 +415,10 @@ void portduinoSetup() memcpy(portduino_config.device_id, autoconf_product + len + 1, 16); if (!memfll(portduino_config.device_id, '\0', 16) && !memfll(portduino_config.device_id, 0xff, 16)) { portduino_config.has_device_id = true; + if (strncmp(autoconf_product, "MESHSTICK 1262", strlen("MESHSTICK 1262")) == 0) { + std::cout << "autoconf: Setting hardwareModel to Meshstick 1262" << std::endl; + portduino_status.hardwareModel = meshtastic_HardwareModel_MESHSTICK_1262; + } } } } @@ -647,6 +661,19 @@ bool loadConfig(const char *configPath) if (yamlConfig["Lora"]["RF95_MAX_POWER"]) portduino_config.rf95_max_power = yamlConfig["Lora"]["RF95_MAX_POWER"].as(20); + if (yamlConfig["Lora"]["TX_GAIN_LORA"]) { + YAML::Node tx_gain_node = yamlConfig["Lora"]["TX_GAIN_LORA"]; + if (tx_gain_node.IsSequence() && tx_gain_node.size() != 0) { + portduino_config.num_pa_points = min(tx_gain_node.size(), std::size(portduino_config.tx_gain_lora)); + for (int i = 0; i < portduino_config.num_pa_points; i++) { + portduino_config.tx_gain_lora[i] = tx_gain_node[i].as(); + } + } else { + portduino_config.num_pa_points = 1; + portduino_config.tx_gain_lora[0] = tx_gain_node.as(0); + } + } + if (portduino_config.lora_module != use_autoconf && portduino_config.lora_module != use_simradio && !portduino_config.force_simradio) { portduino_config.dio2_as_rf_switch = yamlConfig["Lora"]["DIO2_AS_RF_SWITCH"].as(false); @@ -845,6 +872,7 @@ bool loadConfig(const char *configPath) } if (yamlConfig["Config"]) { + portduino_config.has_config_overrides = true; if (yamlConfig["Config"]["DisplayMode"]) { portduino_config.has_configDisplayMode = true; if ((yamlConfig["Config"]["DisplayMode"]).as("") == "TWOCOLOR") { @@ -857,6 +885,13 @@ bool loadConfig(const char *configPath) portduino_config.configDisplayMode = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT; } } + if (yamlConfig["Config"]["StatusMessage"]) { + portduino_config.has_statusMessage = true; + portduino_config.statusMessage = (yamlConfig["Config"]["StatusMessage"]).as(""); + } + if ((yamlConfig["Config"]["EnableUDP"]).as(false)) { + portduino_config.enable_UDP = true; + } } if (yamlConfig["General"]) { @@ -870,6 +905,12 @@ bool loadConfig(const char *configPath) std::cout << "Cannot set both MACAddress and MACAddressSource!" << std::endl; exit(EXIT_FAILURE); } + if (checkConfigPort) { + portduino_config.api_port = (yamlConfig["General"]["APIPort"]).as(-1); + if (portduino_config.api_port != -1 && portduino_config.api_port > 1023 && portduino_config.api_port < 65536) { + TCPPort = (portduino_config.api_port); + } + } portduino_config.mac_address = (yamlConfig["General"]["MACAddress"]).as(""); if (portduino_config.mac_address != "") { portduino_config.mac_address_explicit = true; diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 8992f5f1a..990803e79 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -1,15 +1,22 @@ #pragma once #include #include +#include #include #include #include "LR11x0Interface.h" #include "Module.h" #include "mesh/generated/meshtastic/mesh.pb.h" -#include "platform/portduino/USBHal.h" #include "yaml-cpp/yaml.h" +extern struct portduino_status_struct { + bool LoRa_in_error = false; + _meshtastic_HardwareModel hardwareModel = meshtastic_HardwareModel_PORTDUINO; +} portduino_status; + +#include "platform/portduino/USBHal.h" + // Product strings for auto-configuration // {"PRODUCT_STRING", "CONFIG.YAML"} // YAML paths are relative to `meshtastic/available.d` @@ -91,6 +98,8 @@ extern struct portduino_config_struct { int lora_usb_pid = 0x5512; int lora_usb_vid = 0x1A86; int spiSpeed = 2000000; + int num_pa_points = 1; // default to 1 point, with 0 gain + uint16_t tx_gain_lora[22] = {0}; pinMapping lora_cs_pin = {"Lora", "CS"}; pinMapping lora_irq_pin = {"Lora", "IRQ"}; pinMapping lora_busy_pin = {"Lora", "Busy"}; @@ -168,13 +177,18 @@ extern struct portduino_config_struct { int hostMetrics_channel = 0; // config + bool has_config_overrides = false; int configDisplayMode = 0; bool has_configDisplayMode = false; + std::string statusMessage = ""; + bool has_statusMessage = false; + bool enable_UDP = false; // General std::string mac_address = ""; bool mac_address_explicit = false; std::string mac_address_source = ""; + int api_port = -1; std::string config_directory = ""; std::string available_directory = "/etc/meshtasticd/available.d/"; int maxtophone = 100; @@ -230,6 +244,17 @@ extern struct portduino_config_struct { out << YAML::Key << "LR1120_MAX_POWER" << YAML::Value << lr1120_max_power; if (rf95_max_power != 20) out << YAML::Key << "RF95_MAX_POWER" << YAML::Value << rf95_max_power; + + if (num_pa_points > 1) { + out << YAML::Key << "TX_GAIN_LORA" << YAML::Value << YAML::Flow << YAML::BeginSeq; + for (int i = 0; i < num_pa_points; i++) { + out << YAML::Value << tx_gain_lora[i]; + } + out << YAML::EndSeq; + } else if (tx_gain_lora[0] != 0) { + out << YAML::Key << "TX_GAIN_LORA" << YAML::Value << tx_gain_lora[0]; + } + out << YAML::Key << "DIO2_AS_RF_SWITCH" << YAML::Value << dio2_as_rf_switch; if (dio3_tcxo_voltage != 0) out << YAML::Key << "DIO3_TCXO_VOLTAGE" << YAML::Value << YAML::Precision(3) << (float)dio3_tcxo_voltage / 1000; @@ -484,21 +509,30 @@ extern struct portduino_config_struct { } // config - if (has_configDisplayMode) { + if (has_config_overrides) { out << YAML::Key << "Config" << YAML::Value << YAML::BeginMap; - switch (configDisplayMode) { - case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR: - out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR"; - break; - case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED: - out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED"; - break; - case meshtastic_Config_DisplayConfig_DisplayMode_COLOR: - out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR"; - break; - case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT: - out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT"; - break; + if (has_configDisplayMode) { + + switch (configDisplayMode) { + case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR: + out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR"; + break; + case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED: + out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED"; + break; + case meshtastic_Config_DisplayConfig_DisplayMode_COLOR: + out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR"; + break; + case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT: + out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT"; + break; + } + } + if (has_statusMessage) { + out << YAML::Key << "StatusMessage" << YAML::Value << statusMessage; + } + if (enable_UDP) { + out << YAML::Key << "EnableUDP" << YAML::Value << true; } out << YAML::EndMap; // Config @@ -508,6 +542,8 @@ extern struct portduino_config_struct { out << YAML::Key << "General" << YAML::Value << YAML::BeginMap; if (config_directory != "") out << YAML::Key << "ConfigDirectory" << YAML::Value << config_directory; + if (api_port != -1) + out << YAML::Key << "TCPPort" << YAML::Value << api_port; if (mac_address_explicit) out << YAML::Key << "MACAddress" << YAML::Value << mac_address; if (mac_address_source != "") @@ -519,4 +555,4 @@ extern struct portduino_config_struct { out << YAML::EndMap; // General return out.c_str(); } -} portduino_config; \ No newline at end of file +} portduino_config; diff --git a/src/platform/portduino/USBHal.h b/src/platform/portduino/USBHal.h index ecc292430..441f75b10 100644 --- a/src/platform/portduino/USBHal.h +++ b/src/platform/portduino/USBHal.h @@ -9,6 +9,8 @@ #include #include +extern uint32_t rebootAtMsec; + // include the library for Raspberry GPIO pins #define PI_RISING (PINEDIO_INT_MODE_RISING) @@ -45,7 +47,7 @@ class Ch341Hal : public RadioLibHal int32_t ret = pinedio_init(&pinedio, NULL); if (ret != 0) { std::string s = "Could not open SPI: "; - throw(s + std::to_string(ret)); + throw std::runtime_error(s + std::to_string(ret)); } pinedio_set_option(&pinedio, PINEDIO_OPTION_AUTO_CS, 0); @@ -74,30 +76,55 @@ class Ch341Hal : public RadioLibHal // RADIOLIB_NC as an alias for non-connected pins void pinMode(uint32_t pin, uint32_t mode) override { + if (checkError()) { + return; + } if (pin == RADIOLIB_NC) { return; } - pinedio_set_pin_mode(&pinedio, pin, mode); + auto res = pinedio_set_pin_mode(&pinedio, pin, mode); + if (res < 0 && rebootAtMsec == 0) { + LOG_ERROR("USBHal pinMode: Could not set pin %u mode to %u: %d", pin, mode, res); + } } void digitalWrite(uint32_t pin, uint32_t value) override { + if (checkError()) { + return; + } if (pin == RADIOLIB_NC) { return; } - pinedio_digital_write(&pinedio, pin, value); + auto res = pinedio_digital_write(&pinedio, pin, value); + if (res < 0 && rebootAtMsec == 0) { + LOG_ERROR("USBHal digitalWrite: Could not write pin %u: %d", pin, res); + portduino_status.LoRa_in_error = true; + } } uint32_t digitalRead(uint32_t pin) override { + if (checkError()) { + return 0; + } if (pin == RADIOLIB_NC) { return 0; } - return pinedio_digital_read(&pinedio, pin); + auto res = pinedio_digital_read(&pinedio, pin); + if (res < 0 && rebootAtMsec == 0) { + LOG_ERROR("USBHal digitalRead: Could not read pin %u: %d", pin, res); + portduino_status.LoRa_in_error = true; + return 0; + } + return res; } void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override { + if (checkError()) { + return; + } if (interruptNum == RADIOLIB_NC) { return; } @@ -107,6 +134,9 @@ class Ch341Hal : public RadioLibHal void detachInterrupt(uint32_t interruptNum) override { + if (checkError()) { + return; + } if (interruptNum == RADIOLIB_NC) { return; } @@ -152,6 +182,9 @@ class Ch341Hal : public RadioLibHal void spiTransfer(uint8_t *out, size_t len, uint8_t *in) { + if (checkError()) { + return; + } int32_t ret = pinedio_transceive(&this->pinedio, out, in, len); if (ret < 0) { std::cerr << "Could not perform SPI transfer: " << ret << std::endl; @@ -160,9 +193,22 @@ class Ch341Hal : public RadioLibHal void spiEndTransaction() {} void spiEnd() {} + bool checkError() + { + if (pinedio.in_error) { + if (!has_warned) + LOG_ERROR("USBHal: libch341 in_error detected"); + portduino_status.LoRa_in_error = true; + has_warned = true; + return true; + } + has_warned = false; + return false; + } private: pinedio_inst pinedio = {0}; + bool has_warned = false; }; #endif diff --git a/src/platform/portduino/architecture.h b/src/platform/portduino/architecture.h index e10519d21..b1698a4eb 100644 --- a/src/platform/portduino/architecture.h +++ b/src/platform/portduino/architecture.h @@ -6,7 +6,7 @@ // set HW_VENDOR // -#define HW_VENDOR meshtastic_HardwareModel_PORTDUINO +#define HW_VENDOR portduino_status.hardwareModel #ifndef HAS_BUTTON #define HAS_BUTTON 1 @@ -17,9 +17,6 @@ #ifndef HAS_RADIO #define HAS_RADIO 1 #endif -#ifndef HAS_RTC -#define HAS_RTC 1 -#endif #ifndef HAS_TELEMETRY #define HAS_TELEMETRY 1 #endif diff --git a/src/power.h b/src/power.h index 5f887c36b..e4b456d3b 100644 --- a/src/power.h +++ b/src/power.h @@ -15,20 +15,8 @@ // Device specific curves go in variant.h #ifndef OCV_ARRAY -#ifdef CELL_TYPE_LIFEPO4 -#define OCV_ARRAY 3400, 3350, 3320, 3290, 3270, 3260, 3250, 3230, 3200, 3120, 3000 -#elif defined(CELL_TYPE_LEADACID) -#define OCV_ARRAY 2120, 2090, 2070, 2050, 2030, 2010, 1990, 1980, 1970, 1960, 1950 -#elif defined(CELL_TYPE_ALKALINE) -#define OCV_ARRAY 1580, 1400, 1350, 1300, 1280, 1250, 1230, 1190, 1150, 1100, 1000 -#elif defined(CELL_TYPE_NIMH) -#define OCV_ARRAY 1400, 1300, 1280, 1270, 1260, 1250, 1240, 1230, 1210, 1150, 1000 -#elif defined(CELL_TYPE_LTO) -#define OCV_ARRAY 2700, 2560, 2540, 2520, 2500, 2460, 2420, 2400, 2380, 2320, 1500 -#else // LiIon #define OCV_ARRAY 4190, 4050, 3990, 3890, 3800, 3720, 3630, 3530, 3420, 3300, 3100 #endif -#endif /*Note: 12V lead acid is 6 cells, most board accept only 1 cell LiIon/LiPo*/ #ifndef NUM_CELLS diff --git a/src/power/PowerHAL.cpp b/src/power/PowerHAL.cpp new file mode 100644 index 000000000..0a8d5f10b --- /dev/null +++ b/src/power/PowerHAL.cpp @@ -0,0 +1,19 @@ + +#include "PowerHAL.h" + +void powerHAL_init() +{ + return powerHAL_platformInit(); +} + +__attribute__((weak, noinline)) void powerHAL_platformInit() {} + +__attribute__((weak, noinline)) bool powerHAL_isPowerLevelSafe() +{ + return true; +} + +__attribute__((weak, noinline)) bool powerHAL_isVBUSConnected() +{ + return false; +} diff --git a/src/power/PowerHAL.h b/src/power/PowerHAL.h new file mode 100644 index 000000000..318b06810 --- /dev/null +++ b/src/power/PowerHAL.h @@ -0,0 +1,26 @@ + +/* + +Power Hardware Abstraction Layer. Set of API calls to offload power management, measurements, reboots, etc +to the platform and variant code to avoid #ifdef spaghetti hell and limitless device-based edge cases +in the main firmware code + +Functions declared here (with exception of powerHAL_init) should be defined in platform specific codebase. +Default function body does usually nothing. + +*/ + +// Initialize HAL layer. Call it as early as possible during device boot +// do not overwrite it as it's not declared with "weak" attribute. +void powerHAL_init(); + +// platform specific init code if needed to be run early on boot +void powerHAL_platformInit(); + +// Return true if current battery level is safe for device operation (for example flash writes). +// This should be reported by power failure comparator (NRF52) or similar circuits on other platforms. +// Do not use battery ADC as improper ADC configuration may prevent device from booting. +bool powerHAL_isPowerLevelSafe(); + +// return if USB voltage is connected +bool powerHAL_isVBUSConnected(); diff --git a/src/sleep.cpp b/src/sleep.cpp index 756582c74..8b30a5352 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -241,7 +241,6 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN #ifdef PIN_POWER_EN digitalWrite(PIN_POWER_EN, LOW); pinMode(PIN_POWER_EN, INPUT); // power off peripherals - // pinMode(PIN_POWER_EN1, INPUT_PULLDOWN); #endif #ifdef RAK_WISMESH_TAP_V2 diff --git a/src/watchdog/watchdogThread.cpp b/src/watchdog/watchdogThread.cpp new file mode 100644 index 000000000..3e8a5466f --- /dev/null +++ b/src/watchdog/watchdogThread.cpp @@ -0,0 +1,37 @@ +#include "watchdogThread.h" +#include "configuration.h" + +#ifdef HAS_HARDWARE_WATCHDOG +WatchdogThread *watchdogThread; + +WatchdogThread::WatchdogThread() : OSThread("Watchdog") +{ + setup(); +} + +void WatchdogThread::feedDog(void) +{ + digitalWrite(HARDWARE_WATCHDOG_DONE, HIGH); + delay(1); + digitalWrite(HARDWARE_WATCHDOG_DONE, LOW); +} + +int32_t WatchdogThread::runOnce() +{ + LOG_DEBUG("Feeding hardware watchdog"); + feedDog(); + return HARDWARE_WATCHDOG_TIMEOUT_MS; +} + +bool WatchdogThread::setup() +{ + LOG_DEBUG("init hardware watchdog"); + pinMode(HARDWARE_WATCHDOG_WAKE, INPUT); + pinMode(HARDWARE_WATCHDOG_DONE, OUTPUT); + delay(1); + digitalWrite(HARDWARE_WATCHDOG_DONE, LOW); + delay(1); + feedDog(); + return true; +} +#endif \ No newline at end of file diff --git a/src/watchdog/watchdogThread.h b/src/watchdog/watchdogThread.h new file mode 100644 index 000000000..3a3830aa4 --- /dev/null +++ b/src/watchdog/watchdogThread.h @@ -0,0 +1,17 @@ +#pragma once + +#include "concurrency/OSThread.h" +#include + +#ifdef HAS_HARDWARE_WATCHDOG +class WatchdogThread : private concurrency::OSThread +{ + public: + WatchdogThread(); + void feedDog(void); + virtual bool setup(); + virtual int32_t runOnce() override; +}; + +extern WatchdogThread *watchdogThread; +#endif diff --git a/test/test_radio/test_main.cpp b/test/test_radio/test_main.cpp new file mode 100644 index 000000000..fbe2b1b13 --- /dev/null +++ b/test/test_radio/test_main.cpp @@ -0,0 +1,100 @@ +#include "MeshRadio.h" +#include "RadioInterface.h" +#include "TestUtil.h" +#include + +#include "meshtastic/config.pb.h" + +static void test_bwCodeToKHz_specialMappings() +{ + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 31.25f, bwCodeToKHz(31)); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 62.5f, bwCodeToKHz(62)); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 203.125f, bwCodeToKHz(200)); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 406.25f, bwCodeToKHz(400)); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 812.5f, bwCodeToKHz(800)); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 1625.0f, bwCodeToKHz(1600)); +} + +static void test_bwCodeToKHz_passthrough() +{ + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 125.0f, bwCodeToKHz(125)); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 250.0f, bwCodeToKHz(250)); +} + +static void test_bootstrapLoRaConfigFromPreset_noopWhenUsePresetFalse() +{ + meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero; + cfg.use_preset = false; + cfg.region = meshtastic_Config_LoRaConfig_RegionCode_US; + cfg.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST; + cfg.bandwidth = 123; + cfg.spread_factor = 8; + + RadioInterface::bootstrapLoRaConfigFromPreset(cfg); + + TEST_ASSERT_EQUAL_UINT16(123, cfg.bandwidth); + TEST_ASSERT_EQUAL_UINT32(8, cfg.spread_factor); + TEST_ASSERT_EQUAL(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST, cfg.modem_preset); +} + +static void test_bootstrapLoRaConfigFromPreset_setsDerivedFields_nonWideRegion() +{ + meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero; + cfg.use_preset = true; + cfg.region = meshtastic_Config_LoRaConfig_RegionCode_US; + cfg.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST; + + RadioInterface::bootstrapLoRaConfigFromPreset(cfg); + + TEST_ASSERT_EQUAL_UINT16(250, cfg.bandwidth); + TEST_ASSERT_EQUAL_UINT32(9, cfg.spread_factor); +} + +static void test_bootstrapLoRaConfigFromPreset_setsDerivedFields_wideRegion() +{ + meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero; + cfg.use_preset = true; + cfg.region = meshtastic_Config_LoRaConfig_RegionCode_LORA_24; + cfg.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST; + + RadioInterface::bootstrapLoRaConfigFromPreset(cfg); + + TEST_ASSERT_EQUAL_UINT16(800, cfg.bandwidth); + TEST_ASSERT_EQUAL_UINT32(9, cfg.spread_factor); +} + +static void test_bootstrapLoRaConfigFromPreset_fallsBackIfBandwidthExceedsRegionSpan() +{ + meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero; + cfg.use_preset = true; + cfg.region = meshtastic_Config_LoRaConfig_RegionCode_EU_868; + cfg.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO; + + RadioInterface::bootstrapLoRaConfigFromPreset(cfg); + + TEST_ASSERT_EQUAL(meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST, cfg.modem_preset); + TEST_ASSERT_EQUAL_UINT16(250, cfg.bandwidth); + TEST_ASSERT_EQUAL_UINT32(11, cfg.spread_factor); +} + +void setUp(void) {} +void tearDown(void) {} + +void setup() +{ + delay(10); + delay(2000); + + initializeTestEnvironment(); + + UNITY_BEGIN(); + RUN_TEST(test_bwCodeToKHz_specialMappings); + RUN_TEST(test_bwCodeToKHz_passthrough); + RUN_TEST(test_bootstrapLoRaConfigFromPreset_noopWhenUsePresetFalse); + RUN_TEST(test_bootstrapLoRaConfigFromPreset_setsDerivedFields_nonWideRegion); + RUN_TEST(test_bootstrapLoRaConfigFromPreset_setsDerivedFields_wideRegion); + RUN_TEST(test_bootstrapLoRaConfigFromPreset_fallsBackIfBandwidthExceedsRegionSpan); + exit(UNITY_END()); +} + +void loop() {} diff --git a/variants/esp32/chatter2/platformio.ini b/variants/esp32/chatter2/platformio.ini index 94a846bc9..62d23b1e6 100644 --- a/variants/esp32/chatter2/platformio.ini +++ b/variants/esp32/chatter2/platformio.ini @@ -6,8 +6,11 @@ build_flags = ${esp32_base.build_flags} -D CHATTER_2 -I variants/esp32/chatter2 + -DMESHTASTIC_EXCLUDE_WEBSERVER=1 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 + -ULED_BUILTIN lib_deps = ${esp32_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 diff --git a/variants/esp32/chatter2/variant.h b/variants/esp32/chatter2/variant.h index 0c1ef6967..28ce64f91 100644 --- a/variants/esp32/chatter2/variant.h +++ b/variants/esp32/chatter2/variant.h @@ -79,7 +79,7 @@ // lower dB for lower voltage rnage #define ADC_MULTIPLIER 5.0 // VBATT---10k--pin34---2.5K---GND // Chatter2 uses 3 AAA cells -#define CELL_TYPE_ALKALINE +#define OCV_ARRAY 1580, 1400, 1350, 1300, 1280, 1250, 1230, 1190, 1150, 1100, 1000 #define NUM_CELLS 3 #undef EXT_PWR_DETECT @@ -98,7 +98,6 @@ #define KB_LOAD 21 // load values from the switch and store in shift register #define KB_CLK 22 // clock pin for serial data out #define KB_DATA 23 // data pin -#define CANNED_MESSAGE_MODULE_ENABLE 1 ///////////////////////////////////////////////////////////////////////////////// // // diff --git a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini index 809599212..2ddc5a2db 100644 --- a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini +++ b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini @@ -10,3 +10,6 @@ build_flags = -D EBYTE_E22 -D EBYTE_E22_900M30S ; Assume Tx power curve is identical to 900M30S as there is no documentation -I variants/esp32/diy/9m2ibr_aprs_lora_tracker +build_src_filter = + ${esp32_base.build_src_filter} + +<../variants/esp32/diy/9m2ibr_aprs_lora_tracker> \ No newline at end of file diff --git a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.cpp b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.cpp new file mode 100644 index 000000000..ef90d5a54 --- /dev/null +++ b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.cpp @@ -0,0 +1,8 @@ +#include "variant.h" +#include "Arduino.h" + +void earlyInitVariant() +{ + pinMode(USER_LED, OUTPUT); + digitalWrite(USER_LED, HIGH ^ LED_STATE_ON); +} \ No newline at end of file diff --git a/variants/esp32/diy/hydra/platformio.ini b/variants/esp32/diy/hydra/platformio.ini index 3afd17e01..f23224f0b 100644 --- a/variants/esp32/diy/hydra/platformio.ini +++ b/variants/esp32/diy/hydra/platformio.ini @@ -14,3 +14,4 @@ build_flags = ${esp32_base.build_flags} -D DIY_V1 -I variants/esp32/diy/hydra + -ULED_BUILTIN diff --git a/variants/esp32/diy/v1/platformio.ini b/variants/esp32/diy/v1/platformio.ini index 3d31fc24a..6be2bfd09 100644 --- a/variants/esp32/diy/v1/platformio.ini +++ b/variants/esp32/diy/v1/platformio.ini @@ -17,3 +17,4 @@ build_flags = -D DIY_V1 -D EBYTE_E22 -I variants/esp32/diy/v1 + -ULED_BUILTIN diff --git a/variants/esp32/esp32-common.ini b/variants/esp32/esp32-common.ini index 3ee2b9516..bbbcd3cbe 100644 --- a/variants/esp32/esp32-common.ini +++ b/variants/esp32/esp32-common.ini @@ -59,13 +59,13 @@ lib_deps = ${environmental_extra.lib_deps} ${radiolib_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-esp32_https_server packageName=https://github.com/meshtastic/esp32_https_server gitBranch=master - https://github.com/meshtastic/esp32_https_server/archive/3223704846752e6d545139204837bdb2a55459ca.zip + https://github.com/meshtastic/esp32_https_server/archive/b0f3960b3e8444563280656d88e22b5899481884.zip # renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino h2zero/NimBLE-Arduino@^1.4.3 # renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip # renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib - https://github.com/lewisxhe/XPowersLib/archive/v0.3.2.zip + https://github.com/lewisxhe/XPowersLib/archive/v0.3.3.zip # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto rweather/Crypto@0.4.0 diff --git a/variants/esp32/esp32.ini b/variants/esp32/esp32.ini index 5999bc098..20ce38fae 100644 --- a/variants/esp32/esp32.ini +++ b/variants/esp32/esp32.ini @@ -7,3 +7,22 @@ custom_esp32_kind = esp32 build_flags = ${esp32_common.build_flags} -DMESHTASTIC_EXCLUDE_AUDIO=1 +; Override lib_deps to use environmental_extra_no_bsec instead of environmental_extra +; BSEC library uses ~3.5KB DRAM which causes overflow on original ESP32 targets +lib_deps = + ${arduino_base.lib_deps} + ${networking_base.lib_deps} + ${networking_extra.lib_deps} + ${environmental_base.lib_deps} + ${environmental_extra_no_bsec.lib_deps} + ${radiolib_base.lib_deps} + # renovate: datasource=git-refs depName=meshtastic-esp32_https_server packageName=https://github.com/meshtastic/esp32_https_server gitBranch=master + https://github.com/meshtastic/esp32_https_server/archive/b0f3960b3e8444563280656d88e22b5899481884.zip + # renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino + h2zero/NimBLE-Arduino@^1.4.3 + # renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master + https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip + # renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib + https://github.com/lewisxhe/XPowersLib/archive/v0.3.3.zip + # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto + rweather/Crypto@0.4.0 \ No newline at end of file diff --git a/variants/esp32/m5stack_core/platformio.ini b/variants/esp32/m5stack_core/platformio.ini index d6c5c5b4c..8fbbae895 100644 --- a/variants/esp32/m5stack_core/platformio.ini +++ b/variants/esp32/m5stack_core/platformio.ini @@ -16,6 +16,8 @@ build_flags = ${esp32_base.build_flags} -I variants/esp32/m5stack_core -DM5STACK + -DMESHTASTIC_EXCLUDE_WEBSERVER=1 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 -DUSER_SETUP_LOADED -DTFT_SDA_READ -DTFT_DRIVER=0x9341 @@ -28,10 +30,9 @@ build_flags = -DTFT_BL=32 -DSPI_FREQUENCY=40000000 -DSPI_READ_FREQUENCY=16000000 - -DDISABLE_ALL_LIBRARY_WARNINGS lib_ignore = m5stack-core lib_deps = ${esp32_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 diff --git a/variants/esp32/m5stack_coreink/platformio.ini b/variants/esp32/m5stack_coreink/platformio.ini index a4d44a15e..af1535f59 100644 --- a/variants/esp32/m5stack_coreink/platformio.ini +++ b/variants/esp32/m5stack_coreink/platformio.ini @@ -19,9 +19,9 @@ build_flags = lib_deps = ${esp32_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 lib_ignore = m5stack-coreink monitor_filters = esp32_exception_decoder diff --git a/variants/esp32/m5stack_coreink/variant.h b/variants/esp32/m5stack_coreink/variant.h index b1708352b..9bf45f2ff 100644 --- a/variants/esp32/m5stack_coreink/variant.h +++ b/variants/esp32/m5stack_coreink/variant.h @@ -15,7 +15,6 @@ // PCF8563 RTC Module #define PCF8563_RTC 0x51 -#define HAS_RTC 1 // Wheel // Down 37 diff --git a/variants/esp32/nano-g1-explorer/platformio.ini b/variants/esp32/nano-g1-explorer/platformio.ini index 703bb9d09..16ecb99cc 100644 --- a/variants/esp32/nano-g1-explorer/platformio.ini +++ b/variants/esp32/nano-g1-explorer/platformio.ini @@ -14,3 +14,4 @@ build_flags = ${esp32_base.build_flags} -D NANO_G1_EXPLORER -I variants/esp32/nano-g1-explorer + -ULED_BUILTIN diff --git a/variants/esp32/nano-g1/platformio.ini b/variants/esp32/nano-g1/platformio.ini index b0ebd191c..724e008c7 100644 --- a/variants/esp32/nano-g1/platformio.ini +++ b/variants/esp32/nano-g1/platformio.ini @@ -14,3 +14,4 @@ build_flags = ${esp32_base.build_flags} -D NANO_G1 -I variants/esp32/nano-g1 + -ULED_BUILTIN diff --git a/variants/esp32/radiomaster_900_bandit/platformio.ini b/variants/esp32/radiomaster_900_bandit/platformio.ini index 6729235ed..0012f49d3 100644 --- a/variants/esp32/radiomaster_900_bandit/platformio.ini +++ b/variants/esp32/radiomaster_900_bandit/platformio.ini @@ -9,6 +9,7 @@ build_flags = -DHAS_STK8XXX=1 -O2 -I variants/esp32/radiomaster_900_bandit + -ULED_BUILTIN board_build.f_cpu = 240000000L upload_protocol = esptool lib_deps = diff --git a/variants/esp32/radiomaster_900_bandit_micro/platformio.ini b/variants/esp32/radiomaster_900_bandit_micro/platformio.ini index 32e9280e1..e58d06f1e 100644 --- a/variants/esp32/radiomaster_900_bandit_micro/platformio.ini +++ b/variants/esp32/radiomaster_900_bandit_micro/platformio.ini @@ -13,5 +13,6 @@ build_flags = -DCONFIG_DISABLE_HAL_LOCKS=1 -O2 -I variants/esp32/radiomaster_900_bandit_nano + -ULED_BUILTIN board_build.f_cpu = 240000000L upload_protocol = esptool diff --git a/variants/esp32/radiomaster_900_bandit_nano/platformio.ini b/variants/esp32/radiomaster_900_bandit_nano/platformio.ini index 924447ee4..7b3d187bf 100644 --- a/variants/esp32/radiomaster_900_bandit_nano/platformio.ini +++ b/variants/esp32/radiomaster_900_bandit_nano/platformio.ini @@ -16,5 +16,6 @@ build_flags = -DCONFIG_DISABLE_HAL_LOCKS=1 -O2 -I variants/esp32/radiomaster_900_bandit_nano + -ULED_BUILTIN board_build.f_cpu = 240000000L upload_protocol = esptool diff --git a/variants/esp32/rak11200/pins_arduino.h b/variants/esp32/rak11200/pins_arduino.h index f383d54a7..263fbd5a0 100644 --- a/variants/esp32/rak11200/pins_arduino.h +++ b/variants/esp32/rak11200/pins_arduino.h @@ -6,8 +6,6 @@ #define LED_GREEN 12 #define LED_BLUE 2 -#define LED_BUILTIN LED_GREEN - static const uint8_t TX = 1; static const uint8_t RX = 3; diff --git a/variants/esp32/rak11200/variant.h b/variants/esp32/rak11200/variant.h index 01edb8b73..fe7d05676 100644 --- a/variants/esp32/rak11200/variant.h +++ b/variants/esp32/rak11200/variant.h @@ -6,8 +6,6 @@ #define LED_GREEN 12 #define LED_BLUE 2 -#define LED_BUILTIN LED_GREEN - static const uint8_t TX = 1; static const uint8_t RX = 3; diff --git a/variants/esp32/station-g1/platformio.ini b/variants/esp32/station-g1/platformio.ini index ab7fcac2b..fad003b20 100644 --- a/variants/esp32/station-g1/platformio.ini +++ b/variants/esp32/station-g1/platformio.ini @@ -14,3 +14,4 @@ build_flags = ${esp32_base.build_flags} -D STATION_G1 -I variants/esp32/station-g1 + -ULED_BUILTIN diff --git a/variants/esp32/tbeam/platformio.ini b/variants/esp32/tbeam/platformio.ini index 51952457a..dbaccee8f 100644 --- a/variants/esp32/tbeam/platformio.ini +++ b/variants/esp32/tbeam/platformio.ini @@ -18,6 +18,7 @@ build_flags = ${esp32_base.build_flags} -I variants/esp32/tbeam -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue + -ULED_BUILTIN upload_speed = 921600 [env:tbeam-displayshield] @@ -32,4 +33,4 @@ lib_deps = # renovate: datasource=github-tags depName=meshtastic-st7796 packageName=meshtastic/st7796 https://github.com/meshtastic/st7796/archive/1.0.5.zip # renovate: datasource=custom.pio depName=lewisxhe-SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 diff --git a/variants/esp32/tbeam/variant.h b/variants/esp32/tbeam/variant.h index 2d144a888..2c1e61c49 100644 --- a/variants/esp32/tbeam/variant.h +++ b/variants/esp32/tbeam/variant.h @@ -57,7 +57,6 @@ #ifndef TOUCH_IRQ #define TOUCH_IRQ -1 #endif -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define USE_VIRTUAL_KEYBOARD 1 #define ST7796_NSS 25 diff --git a/variants/esp32/tlora_v2_1_16/platformio.ini b/variants/esp32/tlora_v2_1_16/platformio.ini index d9cb8ed3b..dfdbcb152 100644 --- a/variants/esp32/tlora_v2_1_16/platformio.ini +++ b/variants/esp32/tlora_v2_1_16/platformio.ini @@ -12,7 +12,7 @@ extends = esp32_base board = ttgo-lora32-v21 board_check = true build_flags = - ${esp32_base.build_flags} -D TLORA_V2_1_16 -I variants/esp32/tlora_v2_1_16 + ${esp32_base.build_flags} -D TLORA_V2_1_16 -I variants/esp32/tlora_v2_1_16 -ULED_BUILTIN upload_speed = 115200 [env:sugarcube] diff --git a/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini b/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini index 1258fd8b7..38f14ffc5 100644 --- a/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini +++ b/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini @@ -6,4 +6,5 @@ build_flags = -D TLORA_V2_1_16 -I variants/esp32/tlora_v2_1_16 -D LORA_TCXO_GPIO=12 - -D BUTTON_PIN=0 \ No newline at end of file + -D BUTTON_PIN=0 + -ULED_BUILTIN \ No newline at end of file diff --git a/variants/esp32/trackerd/variant.h b/variants/esp32/trackerd/variant.h index c4dfb9e93..0996e85ac 100644 --- a/variants/esp32/trackerd/variant.h +++ b/variants/esp32/trackerd/variant.h @@ -10,7 +10,6 @@ #define LED_PIN 13 // 13 red, 2 blue, 15 red -// #define HAS_BUTTON 0 #define BUTTON_PIN 0 #define BUTTON_NEED_PULLUP diff --git a/variants/esp32/wiphone/platformio.ini b/variants/esp32/wiphone/platformio.ini index fcf36a23c..fbd77be75 100644 --- a/variants/esp32/wiphone/platformio.ini +++ b/variants/esp32/wiphone/platformio.ini @@ -11,7 +11,7 @@ build_flags = lib_deps = ${esp32_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 # renovate: datasource=custom.pio depName=SX1509 IO Expander packageName=sparkfun/library/SX1509 IO Expander sparkfun/SX1509 IO Expander@3.0.6 # renovate: datasource=custom.pio depName=APA102 packageName=pololu/library/APA102 diff --git a/variants/esp32/wiphone/variant.h b/variants/esp32/wiphone/variant.h index 619ac622a..5baeb3936 100644 --- a/variants/esp32/wiphone/variant.h +++ b/variants/esp32/wiphone/variant.h @@ -26,7 +26,6 @@ #undef GPS_TX_PIN #define NO_GPS 1 #define HAS_GPS 0 -#define NO_SCREEN #define HAS_SCREEN 0 // Default SPI1 will be mapped to the display diff --git a/variants/esp32c6/esp32c6.ini b/variants/esp32c6/esp32c6.ini index c1dfa4d28..9ee8591be 100644 --- a/variants/esp32c6/esp32c6.ini +++ b/variants/esp32c6/esp32c6.ini @@ -28,7 +28,7 @@ lib_deps = ${environmental_extra.lib_deps} ${radiolib_base.lib_deps} # renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib - lewisxhe/XPowersLib@0.3.2 + lewisxhe/XPowersLib@0.3.3 # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto diff --git a/variants/esp32c6/m5stack_unitc6l/platformio.ini b/variants/esp32c6/m5stack_unitc6l/platformio.ini index 3054d342a..ed26598d2 100644 --- a/variants/esp32c6/m5stack_unitc6l/platformio.ini +++ b/variants/esp32c6/m5stack_unitc6l/platformio.ini @@ -10,6 +10,8 @@ custom_meshtastic_tags = M5Stack extends = esp32c6_base board = esp32-c6-devkitc-1 +board_upload.flash_size = 16MB +board_build.partitions = default_16MB.csv ;OpenOCD flash method ;upload_protocol = esp-builtin ;Normal method diff --git a/variants/esp32c6/m5stack_unitc6l/variant.h b/variants/esp32c6/m5stack_unitc6l/variant.h index d973aa281..1654ee590 100644 --- a/variants/esp32c6/m5stack_unitc6l/variant.h +++ b/variants/esp32c6/m5stack_unitc6l/variant.h @@ -50,3 +50,5 @@ void c6l_init(); #endif #define SCREEN_TRANSITION_FRAMERATE 10 #define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness + +#define SERIAL_PRINT_PORT 1 diff --git a/variants/esp32c6/tlora_c6/platformio.ini b/variants/esp32c6/tlora_c6/platformio.ini index 6b402d7c5..174e5e297 100644 --- a/variants/esp32c6/tlora_c6/platformio.ini +++ b/variants/esp32c6/tlora_c6/platformio.ini @@ -8,3 +8,4 @@ build_flags = -I variants/esp32c6/tlora_c6 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 + -ULED_BUILTIN diff --git a/variants/esp32c6/tlora_c6/variant.h b/variants/esp32c6/tlora_c6/variant.h index 55635fe13..4a0d40232 100644 --- a/variants/esp32c6/tlora_c6/variant.h +++ b/variants/esp32c6/tlora_c6/variant.h @@ -19,3 +19,5 @@ #define SX126X_TXEN 14 #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +#define SERIAL_PRINT_PORT 1 diff --git a/variants/esp32s3/CDEBYTE_EoRa-Hub/rfswitch.h b/variants/esp32s3/CDEBYTE_EoRa-Hub/rfswitch.h index 1448b1d74..9bb3af45a 100644 --- a/variants/esp32s3/CDEBYTE_EoRa-Hub/rfswitch.h +++ b/variants/esp32s3/CDEBYTE_EoRa-Hub/rfswitch.h @@ -8,7 +8,8 @@ // DIO6 -> RFSW1_V2 // DIO7 -> not connected on E80 module - note that GNSS and Wifi scanning are not possible. -static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_LR11X0_DIO7, RADIOLIB_NC, RADIOLIB_NC}; +static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_LR11X0_DIO7, RADIOLIB_NC, + RADIOLIB_NC}; static const Module::RfSwitchMode_t rfswitch_table[] = { // mode DIO5 DIO6 DIO7 diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini b/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini index 9994cf665..ee51018d4 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini @@ -11,6 +11,10 @@ custom_meshtastic_requires_dfu = false extends = esp32s3_base board = ESP32-S3-WROOM-1-N4 +build_src_filter = + ${esp32s3_base.build_src_filter} + +<../variants/esp32s3/ELECROW-ThinkNode-M5> + build_flags = ${esp32s3_base.build_flags} -D ELECROW_ThinkNode_M5 diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp new file mode 100644 index 000000000..4b485a1a3 --- /dev/null +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp @@ -0,0 +1,12 @@ +#include "variant.h" +#include + +PCA9557 io(0x18, &Wire); + +void earlyInitVariant() +{ + Wire.begin(48, 47); + io.pinMode(PCA_PIN_EINK_EN, OUTPUT); + io.pinMode(PCA_PIN_POWER_EN, OUTPUT); + io.digitalWrite(PCA_PIN_POWER_EN, HIGH); +} diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h index 5f5133e61..353741d91 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h @@ -4,6 +4,8 @@ #define UART_TX 43 #define UART_RX 44 +#define HAS_PCA9557 + // LED // Both of these are on the GPIO expander #define PCA_LED_USER 1 // the Blue LED @@ -79,4 +81,6 @@ #define BUTTON_PIN PIN_BUTTON1 #define BUTTON_PIN_ALT PIN_BUTTON2 + +#define SERIAL_PRINT_PORT 0 #endif diff --git a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini index 95e909b91..f44f26006 100644 --- a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini +++ b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini @@ -11,7 +11,7 @@ upload_speed = 921600 lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel adafruit/Adafruit NeoPixel@1.15.2 build_unflags = diff --git a/variants/esp32s3/esp32-s3-pico/platformio.ini b/variants/esp32s3/esp32-s3-pico/platformio.ini index 8b65c3ca3..db0c038e6 100644 --- a/variants/esp32s3/esp32-s3-pico/platformio.ini +++ b/variants/esp32s3/esp32-s3-pico/platformio.ini @@ -23,6 +23,6 @@ build_flags = ${esp32s3_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel adafruit/Adafruit NeoPixel@1.15.2 diff --git a/variants/esp32s3/esp32-s3-pico/variant.h b/variants/esp32s3/esp32-s3-pico/variant.h index bfcb6059d..65732171a 100644 --- a/variants/esp32s3/esp32-s3-pico/variant.h +++ b/variants/esp32s3/esp32-s3-pico/variant.h @@ -8,7 +8,6 @@ #define EXT_NOTIFY_OUT 22 #define BUTTON_PIN 0 // 17 -// #define LED_PIN PIN_LED // Board has RGB LED 21 #define HAS_NEOPIXEL // Enable the use of neopixels #define NEOPIXEL_COUNT 1 // How many neopixels are connected diff --git a/variants/esp32s3/hackaday-communicator/platformio.ini b/variants/esp32s3/hackaday-communicator/platformio.ini index 29b2c2305..8fd275c0e 100644 --- a/variants/esp32s3/hackaday-communicator/platformio.ini +++ b/variants/esp32s3/hackaday-communicator/platformio.ini @@ -6,6 +6,10 @@ board_check = true board_build.partitions = default_16MB.csv upload_protocol = esptool +build_src_filter = + ${esp32s3_base.build_src_filter} + +<../variants/esp32s3/hackaday-communicator> + build_flags = ${esp32s3_base.build_flags} -D HACKADAY_COMMUNICATOR -D BOARD_HAS_PSRAM @@ -13,4 +17,4 @@ build_flags = ${esp32s3_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-Arduino_GFX packageName=https://github.com/meshtastic/Arduino_GFX gitBranch=master - https://github.com/meshtastic/Arduino_GFX/archive/054e81ffaf23784830a734e3c184346789349406.zip + https://github.com/meshtastic/Arduino_GFX/archive/054e81ffaf23784830a734e3c184346789349406.zip \ No newline at end of file diff --git a/variants/esp32s3/hackaday-communicator/variant.cpp b/variants/esp32s3/hackaday-communicator/variant.cpp new file mode 100644 index 000000000..d85b2abb5 --- /dev/null +++ b/variants/esp32s3/hackaday-communicator/variant.cpp @@ -0,0 +1,6 @@ +#include "variant.h" +#include "Arduino.h" +void earlyInitVariant() +{ + pinMode(KB_INT, INPUT); +} \ No newline at end of file diff --git a/variants/esp32s3/hackaday-communicator/variant.h b/variants/esp32s3/hackaday-communicator/variant.h index a127f548f..cca408622 100644 --- a/variants/esp32s3/hackaday-communicator/variant.h +++ b/variants/esp32s3/hackaday-communicator/variant.h @@ -16,22 +16,12 @@ #define SLEEP_TIME 120 #define GPS_DEFAULT_NOT_PRESENT 1 -// #define GPS_RX_PIN 44 -// #define GPS_TX_PIN 43 - -// #define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -// ratio of voltage divider = 2.0 (RD2=100k, RD3=100k) -// #define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. -// #define ADC_CHANNEL ADC1_GPIO4_CHANNEL // keyboard #define I2C_SDA 47 // I2C pins for this board #define I2C_SCL 14 -// #define KB_POWERON -1 // must be set to HIGH -// #define KB_SLAVE_ADDRESS TDECK_KB_ADDR // 0x55 // #define KB_BL_PIN 46 // not used for now #define KB_INT 13 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define TFT_DC 39 #define TFT_CS 41 @@ -44,11 +34,9 @@ #define LORA_MOSI 3 #define LORA_CS 17 -// #define LORA_DIO0 -1 // a No connect on the SX1262 module #define LORA_RESET 18 #define LORA_DIO1 16 // SX1262 IRQ #define LORA_DIO2 15 // SX1262 BUSY -// #define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled #define SX126X_CS LORA_CS #define SX126X_DIO1 LORA_DIO1 @@ -58,4 +46,5 @@ #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 -// #define LED_PIN 1 \ No newline at end of file +#define LED_NOTIFICATION 1 +#define LED_STATE_ON 0 diff --git a/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini b/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini index 0bb21581a..6dd828433 100644 --- a/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini +++ b/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini @@ -6,4 +6,5 @@ board_build.partitions = default_8MB.csv build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/heltec_capsule_sensor_v3 -D HELTEC_CAPSULE_SENSOR_V3 + -ULED_BUILTIN ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output diff --git a/variants/esp32s3/heltec_sensor_hub/platformio.ini b/variants/esp32s3/heltec_sensor_hub/platformio.ini index ded0c22fe..3f93c7ad2 100644 --- a/variants/esp32s3/heltec_sensor_hub/platformio.ini +++ b/variants/esp32s3/heltec_sensor_hub/platformio.ini @@ -7,6 +7,7 @@ build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/heltec_sensor_hub -D HELTEC_SENSOR_HUB + -ULED_BUILTIN lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel diff --git a/variants/esp32s3/heltec_v3/platformio.ini b/variants/esp32s3/heltec_v3/platformio.ini index 2f53c8756..fe31df094 100644 --- a/variants/esp32s3/heltec_v3/platformio.ini +++ b/variants/esp32s3/heltec_v3/platformio.ini @@ -18,3 +18,4 @@ build_flags = ${esp32s3_base.build_flags} -D HELTEC_V3 -I variants/esp32s3/heltec_v3 + -ULED_BUILTIN diff --git a/variants/esp32s3/heltec_v4/pins_arduino.h b/variants/esp32s3/heltec_v4/pins_arduino.h index d4485016d..32fd8a8e4 100644 --- a/variants/esp32s3/heltec_v4/pins_arduino.h +++ b/variants/esp32s3/heltec_v4/pins_arduino.h @@ -6,10 +6,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 35; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN // allow testing #ifdef LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini index 4495a409f..20b3b4606 100644 --- a/variants/esp32s3/heltec_v4/platformio.ini +++ b/variants/esp32s3/heltec_v4/platformio.ini @@ -126,7 +126,7 @@ build_flags = lib_deps = ${heltec_v4_base.lib_deps} ; ${device-ui_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.0 + lovyan03/LovyanGFX@1.2.19 # renovate: datasource=git-refs depName=Quency-D_chsc6x packageName=https://github.com/Quency-D/chsc6x gitBranch=master https://github.com/Quency-D/chsc6x/archive/5cbead829d6b432a8d621ed1aafd4eb474fd4f27.zip ; TODO revert to official device-ui (when merged) diff --git a/variants/esp32s3/heltec_vision_master_e213/pins_arduino.h b/variants/esp32s3/heltec_vision_master_e213/pins_arduino.h index 56f5ef157..5cf3c6453 100644 --- a/variants/esp32s3/heltec_vision_master_e213/pins_arduino.h +++ b/variants/esp32s3/heltec_vision_master_e213/pins_arduino.h @@ -3,10 +3,6 @@ #include -static const uint8_t LED_BUILTIN = 45; // LED is not populated on earliest board variant -#define BUILTIN_LED LED_BUILTIN // Backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_vision_master_e290/pins_arduino.h b/variants/esp32s3/heltec_vision_master_e290/pins_arduino.h index 56f5ef157..5cf3c6453 100644 --- a/variants/esp32s3/heltec_vision_master_e290/pins_arduino.h +++ b/variants/esp32s3/heltec_vision_master_e290/pins_arduino.h @@ -3,10 +3,6 @@ #include -static const uint8_t LED_BUILTIN = 45; // LED is not populated on earliest board variant -#define BUILTIN_LED LED_BUILTIN // Backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_vision_master_t190/pins_arduino.h b/variants/esp32s3/heltec_vision_master_t190/pins_arduino.h index eeef95ff1..b8a33f721 100644 --- a/variants/esp32s3/heltec_vision_master_t190/pins_arduino.h +++ b/variants/esp32s3/heltec_vision_master_t190/pins_arduino.h @@ -3,10 +3,6 @@ #include -static const uint8_t LED_BUILTIN = 35; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_wireless_paper/pins_arduino.h b/variants/esp32s3/heltec_wireless_paper/pins_arduino.h index 3e36d98f5..886cab254 100644 --- a/variants/esp32s3/heltec_wireless_paper/pins_arduino.h +++ b/variants/esp32s3/heltec_wireless_paper/pins_arduino.h @@ -3,10 +3,6 @@ #include -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_wireless_paper_v1/pins_arduino.h b/variants/esp32s3/heltec_wireless_paper_v1/pins_arduino.h index 2bb44161a..0c486eebc 100644 --- a/variants/esp32s3/heltec_wireless_paper_v1/pins_arduino.h +++ b/variants/esp32s3/heltec_wireless_paper_v1/pins_arduino.h @@ -3,10 +3,6 @@ #include -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t KEY_BUILTIN = 0; static const uint8_t TX = 43; diff --git a/variants/esp32s3/heltec_wireless_tracker/pins_arduino.h b/variants/esp32s3/heltec_wireless_tracker/pins_arduino.h index 1052af961..93fd5d9c2 100644 --- a/variants/esp32s3/heltec_wireless_tracker/pins_arduino.h +++ b/variants/esp32s3/heltec_wireless_tracker/pins_arduino.h @@ -11,10 +11,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_wireless_tracker/platformio.ini b/variants/esp32s3/heltec_wireless_tracker/platformio.ini index c2dab0c93..33643c541 100644 --- a/variants/esp32s3/heltec_wireless_tracker/platformio.ini +++ b/variants/esp32s3/heltec_wireless_tracker/platformio.ini @@ -24,4 +24,4 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 diff --git a/variants/esp32s3/heltec_wireless_tracker_V1_0/pins_arduino.h b/variants/esp32s3/heltec_wireless_tracker_V1_0/pins_arduino.h index 28b982012..7a1f43eee 100644 --- a/variants/esp32s3/heltec_wireless_tracker_V1_0/pins_arduino.h +++ b/variants/esp32s3/heltec_wireless_tracker_V1_0/pins_arduino.h @@ -11,10 +11,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini index efeb63b8e..ab6592afb 100644 --- a/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini +++ b/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini @@ -22,4 +22,4 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h b/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h index 61c319109..9fb825002 100644 --- a/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h +++ b/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h @@ -10,10 +10,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini index 8bdb71541..a5277ba19 100644 --- a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini +++ b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini @@ -1,8 +1,17 @@ [env:heltec-wireless-tracker-v2] +custom_meshtastic_support_level = 1 +custom_meshtastic_images = heltec_wireless_tracker_v2.svg +custom_meshtastic_tags = Heltec + extends = esp32s3_base board = heltec_wireless_tracker_v2 board_build.partitions = default_8MB.csv upload_protocol = esptool +custom_meshtastic_hw_model = 113 +custom_meshtastic_hw_model_slug = HELTEC_WIRELESS_TRACKER_V2 +custom_meshtastic_architecture = esp32s3 +custom_meshtastic_display_name = Heltec Wireless Tracker V2 +custom_meshtastic_actively_supported = true build_flags = ${esp32s3_base.build_flags} @@ -11,4 +20,4 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 diff --git a/variants/esp32s3/heltec_wsl_v3/platformio.ini b/variants/esp32s3/heltec_wsl_v3/platformio.ini index 0903a6bc7..873300c3c 100644 --- a/variants/esp32s3/heltec_wsl_v3/platformio.ini +++ b/variants/esp32s3/heltec_wsl_v3/platformio.ini @@ -17,3 +17,4 @@ build_flags = ${esp32s3_base.build_flags} -D HELTEC_WSL_V3 -I variants/esp32s3/heltec_wsl_v3 + -ULED_BUILTIN diff --git a/variants/esp32s3/m5stack_cores3/pins_arduino.h b/variants/esp32s3/m5stack_cores3/pins_arduino.h index 78e936990..ff7d35993 100644 --- a/variants/esp32s3/m5stack_cores3/pins_arduino.h +++ b/variants/esp32s3/m5stack_cores3/pins_arduino.h @@ -10,10 +10,7 @@ // Some boards have too low voltage on this pin (board design bug) // Use different pin with 3V and connect with 48 // and change this setup for the chosen pin (for example 38) -static const uint8_t LED_BUILTIN = SOC_GPIO_PIN_COUNT + 48; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN -#define RGB_BUILTIN LED_BUILTIN +#define RGB_BUILTIN SOC_GPIO_PIN_COUNT + 48 #define RGB_BRIGHTNESS 64 static const uint8_t TX = 43; diff --git a/variants/esp32s3/mesh-tab/pins_arduino.h b/variants/esp32s3/mesh-tab/pins_arduino.h index c995f638c..d980e1a49 100644 --- a/variants/esp32s3/mesh-tab/pins_arduino.h +++ b/variants/esp32s3/mesh-tab/pins_arduino.h @@ -49,10 +49,6 @@ static const uint8_t T14 = 14; static const uint8_t VBAT_SENSE = 2; static const uint8_t VBUS_SENSE = 34; -// User LED -#define LED_BUILTIN 13 -#define BUILTIN_LED LED_BUILTIN // backward compatibility - static const uint8_t RGB_DATA = 40; // RGB_BUILTIN and RGB_BRIGHTNESS can be used in new Arduino API neopixelWrite() #define RGB_BUILTIN (RGB_DATA + SOC_GPIO_PIN_COUNT) diff --git a/variants/esp32s3/mesh-tab/platformio.ini b/variants/esp32s3/mesh-tab/platformio.ini index 21d8fb432..ecf5498b3 100644 --- a/variants/esp32s3/mesh-tab/platformio.ini +++ b/variants/esp32s3/mesh-tab/platformio.ini @@ -51,7 +51,7 @@ lib_deps = ${esp32s3_base.lib_deps} ${device-ui_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 [mesh_tab_xpt2046] extends = mesh_tab_base diff --git a/variants/esp32s3/nugget_s3_lora/variant.h b/variants/esp32s3/nugget_s3_lora/variant.h index 8e6057d5b..1354d0837 100644 --- a/variants/esp32s3/nugget_s3_lora/variant.h +++ b/variants/esp32s3/nugget_s3_lora/variant.h @@ -12,7 +12,7 @@ #define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use // Button A (44), B (43), R (12), U (13), L (11), D (18) -#define BUTTON_PIN 44 // If defined, this will be used for user button presses +#define BUTTON_PIN 43 // If defined, this will be used for user button presses #define BUTTON_NEED_PULLUP #define USE_RF95 @@ -20,8 +20,19 @@ #define LORA_MISO 7 #define LORA_MOSI 8 #define LORA_CS 9 -#define LORA_DIO0 16 // a No connect on the SX1262 module +#define LORA_DIO0 16 #define LORA_RESET 4 #define LORA_DIO1 RADIOLIB_NC -#define LORA_DIO2 RADIOLIB_NC \ No newline at end of file +#define LORA_DIO2 RADIOLIB_NC + +// jk, its not really a trackball but we're gonna pretend! +#define HAS_TRACKBALL 1 +#define TB_UP 13 +#define TB_DOWN 18 +#define TB_LEFT 11 +#define TB_RIGHT 12 +#define TB_PRESS 44 // BUTTON_PIN +#define TB_DIRECTION FALLING + +#define ENABLE_AMBIENTLIGHTING diff --git a/variants/esp32s3/picomputer-s3/platformio.ini b/variants/esp32s3/picomputer-s3/platformio.ini index bef7b19a0..82147f222 100644 --- a/variants/esp32s3/picomputer-s3/platformio.ini +++ b/variants/esp32s3/picomputer-s3/platformio.ini @@ -24,7 +24,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 build_src_filter = ${esp32s3_base.build_src_filter} diff --git a/variants/esp32s3/picomputer-s3/variant.h b/variants/esp32s3/picomputer-s3/variant.h index 275da1b61..7b6218f87 100644 --- a/variants/esp32s3/picomputer-s3/variant.h +++ b/variants/esp32s3/picomputer-s3/variant.h @@ -52,8 +52,6 @@ // Picomputer gets a white on black display #define TFT_MESH_OVERRIDE COLOR565(255, 255, 255) -#define CANNED_MESSAGE_MODULE_ENABLE 1 - #define INPUTBROKER_MATRIX_TYPE 1 #define KEYS_COLS \ diff --git a/variants/esp32s3/rak3312/pins_arduino.h b/variants/esp32s3/rak3312/pins_arduino.h index dc5d30d61..600c619cf 100644 --- a/variants/esp32s3/rak3312/pins_arduino.h +++ b/variants/esp32s3/rak3312/pins_arduino.h @@ -24,9 +24,6 @@ static const uint8_t SCK = 13; #define SPI_MISO (10) #define SPI_CS (12) -// LEDs -#define LED_BUILTIN LED_GREEN - #ifdef _VARIANT_RAK3112_ /* * Serial interfaces diff --git a/variants/esp32s3/rak3312/variant.h b/variants/esp32s3/rak3312/variant.h index 1f8eb9e39..6431f1fd0 100644 --- a/variants/esp32s3/rak3312/variant.h +++ b/variants/esp32s3/rak3312/variant.h @@ -22,9 +22,8 @@ #define LED_BLUE 45 #define PIN_LED1 LED_GREEN -#define PIN_LED2 LED_BLUE +#define LED_NOTIFICATION LED_BLUE -#define LED_CONN LED_BLUE #define LED_PIN LED_GREEN #define ledOff(pin) pinMode(pin, INPUT) diff --git a/variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h b/variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h index 15a26e991..b51cd214e 100644 --- a/variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h +++ b/variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h @@ -22,7 +22,4 @@ static const uint8_t SCK = 13; #define SPI_MISO (10) #define SPI_CS (12) -// LEDs -#define LED_BUILTIN LED_GREEN - #endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini b/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini index e4ff9812c..8423bb4df 100644 --- a/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini +++ b/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini @@ -16,7 +16,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 [ft5x06] extends = mesh_tab_base diff --git a/variants/esp32s3/rak_wismesh_tap_v2/variant.h b/variants/esp32s3/rak_wismesh_tap_v2/variant.h index 2fc056557..7d263165c 100644 --- a/variants/esp32s3/rak_wismesh_tap_v2/variant.h +++ b/variants/esp32s3/rak_wismesh_tap_v2/variant.h @@ -30,9 +30,8 @@ #define LED_BLUE 45 #define PIN_LED1 LED_GREEN -#define PIN_LED2 LED_BLUE +#define LED_NOTIFICATION LED_BLUE -#define LED_CONN LED_BLUE #define LED_PIN LED_GREEN #define ledOff(pin) pinMode(pin, INPUT) @@ -47,10 +46,8 @@ #define SPI_MISO (10) #define SPI_CS (12) -#define HAS_BUTTON 1 #define BUTTON_PIN 0 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define USE_VIRTUAL_KEYBOARD 1 #define BATTERY_PIN 1 diff --git a/variants/esp32s3/seeed-sensecap-indicator/pins_arduino.h b/variants/esp32s3/seeed-sensecap-indicator/pins_arduino.h index 300f0e0f5..88c233491 100644 --- a/variants/esp32s3/seeed-sensecap-indicator/pins_arduino.h +++ b/variants/esp32s3/seeed-sensecap-indicator/pins_arduino.h @@ -3,8 +3,6 @@ #include -// static const uint8_t LED_BUILTIN = -1; - // static const uint8_t TX = 43; // static const uint8_t RX = 44; diff --git a/variants/esp32s3/t-deck-pro/platformio.ini b/variants/esp32s3/t-deck-pro/platformio.ini index b2c91dcf5..5ba82d045 100644 --- a/variants/esp32s3/t-deck-pro/platformio.ini +++ b/variants/esp32s3/t-deck-pro/platformio.ini @@ -15,6 +15,10 @@ board = t-deck-pro board_check = true upload_protocol = esptool +build_src_filter = + ${esp32s3_base.build_src_filter} + +<../variants/esp32s3/t-deck-pro> + build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/t-deck-pro -D T_DECK_PRO @@ -29,7 +33,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 # renovate: datasource=git-refs depName=CSE_Touch packageName=https://github.com/CIRCUITSTATE/CSE_Touch gitBranch=main https://github.com/CIRCUITSTATE/CSE_Touch/archive/b44f23b6f870b848f1fbe453c190879bc6cfaafa.zip # renovate: datasource=github-tags depName=CSE_CST328 packageName=CIRCUITSTATE/CSE_CST328 diff --git a/variants/esp32s3/t-deck-pro/variant.cpp b/variants/esp32s3/t-deck-pro/variant.cpp new file mode 100644 index 000000000..509726c52 --- /dev/null +++ b/variants/esp32s3/t-deck-pro/variant.cpp @@ -0,0 +1,14 @@ +#include "variant.h" +#include "Arduino.h" + +void earlyInitVariant() +{ + pinMode(LORA_EN, OUTPUT); + digitalWrite(LORA_EN, HIGH); + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, HIGH); + pinMode(SDCARD_CS, OUTPUT); + digitalWrite(SDCARD_CS, HIGH); + pinMode(PIN_EINK_CS, OUTPUT); + digitalWrite(PIN_EINK_CS, HIGH); +} \ No newline at end of file diff --git a/variants/esp32s3/t-deck-pro/variant.h b/variants/esp32s3/t-deck-pro/variant.h index 36a1310f1..d95f07f3a 100644 --- a/variants/esp32s3/t-deck-pro/variant.h +++ b/variants/esp32s3/t-deck-pro/variant.h @@ -43,7 +43,6 @@ // TCA8418 keyboard #define KB_BL_PIN 42 -#define CANNED_MESSAGE_MODULE_ENABLE 1 // microphone PCM5102A #define PCM5102A_SCK 47 diff --git a/variants/esp32s3/t-deck/pins_arduino.h b/variants/esp32s3/t-deck/pins_arduino.h index cb429d776..c358b988e 100644 --- a/variants/esp32s3/t-deck/pins_arduino.h +++ b/variants/esp32s3/t-deck/pins_arduino.h @@ -6,8 +6,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -// static const uint8_t LED_BUILTIN = -1; - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/t-deck/platformio.ini b/variants/esp32s3/t-deck/platformio.ini index 58335796a..c216ec595 100644 --- a/variants/esp32s3/t-deck/platformio.ini +++ b/variants/esp32s3/t-deck/platformio.ini @@ -17,6 +17,10 @@ board_check = true board_build.partitions = default_16MB.csv upload_protocol = esptool +build_src_filter = + ${esp32s3_base.build_src_filter} + +<../variants/esp32s3/t-deck> + build_flags = ${esp32s3_base.build_flags} -D T_DECK -D BOARD_HAS_PSRAM @@ -24,7 +28,7 @@ build_flags = ${esp32s3_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 # renovate: datasource=custom.pio depName=ESP8266Audio packageName=earlephilhower/library/ESP8266Audio earlephilhower/ESP8266Audio@1.9.9 # renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM diff --git a/variants/esp32s3/t-deck/variant.cpp b/variants/esp32s3/t-deck/variant.cpp new file mode 100644 index 000000000..6b68f142c --- /dev/null +++ b/variants/esp32s3/t-deck/variant.cpp @@ -0,0 +1,23 @@ +#include "variant.h" +#include "Arduino.h" + +void earlyInitVariant() +{ + // GPIO10 manages all peripheral power supplies + // Turn on peripheral power immediately after MUC starts. + // If some boards are turned on late, ESP32 will reset due to low voltage. + // ESP32-C3(Keyboard) , MAX98357A(Audio Power Amplifier) , + // TF Card , Display backlight(AW9364DNR) , AN48841B(Trackball) , ES7210(Decoder) + pinMode(KB_POWERON, OUTPUT); + digitalWrite(KB_POWERON, HIGH); + // T-Deck has all three SPI peripherals (TFT, SD, LoRa) attached to the same SPI bus + // We need to initialize all CS pins in advance otherwise there will be SPI communication issues + // e.g. when detecting the SD card + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, HIGH); + pinMode(SDCARD_CS, OUTPUT); + digitalWrite(SDCARD_CS, HIGH); + pinMode(TFT_CS, OUTPUT); + digitalWrite(TFT_CS, HIGH); + delay(100); +} \ No newline at end of file diff --git a/variants/esp32s3/t-deck/variant.h b/variants/esp32s3/t-deck/variant.h index 8d2996131..5d885579a 100644 --- a/variants/esp32s3/t-deck/variant.h +++ b/variants/esp32s3/t-deck/variant.h @@ -61,7 +61,6 @@ #define KB_POWERON 10 // must be set to HIGH #define KB_SLAVE_ADDRESS TDECK_KB_ADDR // 0x55 #define KB_BL_PIN 46 // not used for now -#define CANNED_MESSAGE_MODULE_ENABLE 1 // trackball #define HAS_TRACKBALL 1 @@ -71,6 +70,7 @@ #define TB_RIGHT 2 #define TB_PRESS 0 // BUTTON_PIN #define TB_DIRECTION FALLING +#define TB_THRESHOLD 3 // microphone #define ES7210_SCK 47 diff --git a/variants/esp32s3/t-watch-s3/pins_arduino.h b/variants/esp32s3/t-watch-s3/pins_arduino.h index 35f0e933e..f4585ace8 100644 --- a/variants/esp32s3/t-watch-s3/pins_arduino.h +++ b/variants/esp32s3/t-watch-s3/pins_arduino.h @@ -3,8 +3,6 @@ #include -// static const uint8_t LED_BUILTIN = -1; - // static const uint8_t TX = 43; // static const uint8_t RX = 44; diff --git a/variants/esp32s3/t-watch-s3/platformio.ini b/variants/esp32s3/t-watch-s3/platformio.ini index 7d7b07ff6..8c79ca097 100644 --- a/variants/esp32s3/t-watch-s3/platformio.ini +++ b/variants/esp32s3/t-watch-s3/platformio.ini @@ -22,9 +22,9 @@ build_flags = ${esp32s3_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 # renovate: datasource=custom.pio depName=Adafruit DRV2605 packageName=adafruit/library/Adafruit DRV2605 Library adafruit/Adafruit DRV2605 Library@1.2.4 # renovate: datasource=custom.pio depName=ESP8266Audio packageName=earlephilhower/library/ESP8266Audio diff --git a/variants/esp32s3/t-watch-s3/variant.h b/variants/esp32s3/t-watch-s3/variant.h index 216dda589..df275c31d 100644 --- a/variants/esp32s3/t-watch-s3/variant.h +++ b/variants/esp32s3/t-watch-s3/variant.h @@ -45,7 +45,6 @@ // PCF8563 RTC Module #define PCF8563_RTC 0x51 -#define HAS_RTC 1 #define I2C_SDA 10 // For QMC6310 sensors and screens #define I2C_SCL 11 // For QMC6310 sensors and screens diff --git a/variants/esp32s3/tbeam-s3-core/platformio.ini b/variants/esp32s3/tbeam-s3-core/platformio.ini index c0a32c49c..512cf3202 100644 --- a/variants/esp32s3/tbeam-s3-core/platformio.ini +++ b/variants/esp32s3/tbeam-s3-core/platformio.ini @@ -19,7 +19,7 @@ board_check = true lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 build_flags = ${esp32s3_base.build_flags} diff --git a/variants/esp32s3/tbeam-s3-core/variant.h b/variants/esp32s3/tbeam-s3-core/variant.h index 1f900fcae..9ce4aade9 100644 --- a/variants/esp32s3/tbeam-s3-core/variant.h +++ b/variants/esp32s3/tbeam-s3-core/variant.h @@ -55,7 +55,6 @@ // PCF8563 RTC Module #define PCF8563_RTC 0x51 -#define HAS_RTC 1 // Specify the PMU as Wire1. In the t-beam-s3 core, PCF8563 and PMU share the bus #define PMU_USE_WIRE1 diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini index 5973db1d0..b5c9fd579 100644 --- a/variants/esp32s3/tlora-pager/platformio.ini +++ b/variants/esp32s3/tlora-pager/platformio.ini @@ -17,6 +17,10 @@ board_check = true board_build.partitions = default_16MB.csv upload_protocol = esptool +build_src_filter = + ${esp32s3_base.build_src_filter} + +<../variants/esp32s3/tlora-pager> + build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/tlora-pager -D T_LORA_PAGER @@ -29,7 +33,7 @@ build_flags = ${esp32s3_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 # renovate: datasource=custom.pio depName=ESP8266Audio packageName=earlephilhower/library/ESP8266Audio earlephilhower/ESP8266Audio@1.9.9 # renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM @@ -39,9 +43,9 @@ lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=PCF8563 packageName=lewisxhe/library/PCF8563_Library lewisxhe/PCF8563_Library@1.0.1 # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 # renovate: datasource=github-tags depName=pschatzmann_arduino-audio-driver packageName=pschatzmann/arduino-audio-driver - https://github.com/pschatzmann/arduino-audio-driver/archive/v0.2.0.zip + https://github.com/pschatzmann/arduino-audio-driver/archive/v0.2.1.zip # TODO renovate https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip # TODO renovate diff --git a/variants/esp32s3/tlora-pager/variant.cpp b/variants/esp32s3/tlora-pager/variant.cpp new file mode 100644 index 000000000..7b0cbdfec --- /dev/null +++ b/variants/esp32s3/tlora-pager/variant.cpp @@ -0,0 +1,31 @@ +#include "variant.h" +#include "ExtensionIOXL9555.hpp" +extern ExtensionIOXL9555 io; + +void earlyInitVariant() +{ + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, HIGH); + pinMode(SDCARD_CS, OUTPUT); + digitalWrite(SDCARD_CS, HIGH); + pinMode(TFT_CS, OUTPUT); + digitalWrite(TFT_CS, HIGH); + pinMode(KB_INT, INPUT_PULLUP); + // io expander + io.begin(Wire, XL9555_SLAVE_ADDRESS0, SDA, SCL); + io.pinMode(EXPANDS_DRV_EN, OUTPUT); + io.digitalWrite(EXPANDS_DRV_EN, HIGH); + io.pinMode(EXPANDS_AMP_EN, OUTPUT); + io.digitalWrite(EXPANDS_AMP_EN, LOW); + io.pinMode(EXPANDS_LORA_EN, OUTPUT); + io.digitalWrite(EXPANDS_LORA_EN, HIGH); + io.pinMode(EXPANDS_GPS_EN, OUTPUT); + io.digitalWrite(EXPANDS_GPS_EN, HIGH); + io.pinMode(EXPANDS_KB_EN, OUTPUT); + io.digitalWrite(EXPANDS_KB_EN, HIGH); + io.pinMode(EXPANDS_SD_EN, OUTPUT); + io.digitalWrite(EXPANDS_SD_EN, HIGH); + io.pinMode(EXPANDS_GPIO_EN, OUTPUT); + io.digitalWrite(EXPANDS_GPIO_EN, HIGH); + io.pinMode(EXPANDS_SD_PULLEN, INPUT); +} \ No newline at end of file diff --git a/variants/esp32s3/tlora-pager/variant.h b/variants/esp32s3/tlora-pager/variant.h index 565f4f580..d97f864c3 100644 --- a/variants/esp32s3/tlora-pager/variant.h +++ b/variants/esp32s3/tlora-pager/variant.h @@ -40,7 +40,6 @@ // PCF85063 RTC Module #define PCF85063_RTC 0x51 -#define HAS_RTC 1 // Rotary #define ROTARY_A (40) @@ -61,7 +60,6 @@ #define I2C_NO_RESCAN #define KB_BL_PIN 46 #define KB_INT 6 -#define CANNED_MESSAGE_MODULE_ENABLE 1 // audio codec ES8311 #define HAS_I2S diff --git a/variants/esp32s3/tracksenger/internal/pins_arduino.h b/variants/esp32s3/tracksenger/internal/pins_arduino.h index 1052af961..93fd5d9c2 100644 --- a/variants/esp32s3/tracksenger/internal/pins_arduino.h +++ b/variants/esp32s3/tracksenger/internal/pins_arduino.h @@ -11,10 +11,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/tracksenger/internal/variant.h b/variants/esp32s3/tracksenger/internal/variant.h index 2287dfe0b..ba3e281c8 100644 --- a/variants/esp32s3/tracksenger/internal/variant.h +++ b/variants/esp32s3/tracksenger/internal/variant.h @@ -78,7 +78,6 @@ // keyboard changes #define PIN_BUZZER 43 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define INPUTBROKER_MATRIX_TYPE 1 diff --git a/variants/esp32s3/tracksenger/lcd/pins_arduino.h b/variants/esp32s3/tracksenger/lcd/pins_arduino.h index 1052af961..93fd5d9c2 100644 --- a/variants/esp32s3/tracksenger/lcd/pins_arduino.h +++ b/variants/esp32s3/tracksenger/lcd/pins_arduino.h @@ -11,10 +11,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/tracksenger/lcd/variant.h b/variants/esp32s3/tracksenger/lcd/variant.h index f42a5b19f..a9bb89d68 100644 --- a/variants/esp32s3/tracksenger/lcd/variant.h +++ b/variants/esp32s3/tracksenger/lcd/variant.h @@ -102,7 +102,6 @@ // keyboard changes #define PIN_BUZZER 43 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define INPUTBROKER_MATRIX_TYPE 1 diff --git a/variants/esp32s3/tracksenger/oled/pins_arduino.h b/variants/esp32s3/tracksenger/oled/pins_arduino.h index 1052af961..93fd5d9c2 100644 --- a/variants/esp32s3/tracksenger/oled/pins_arduino.h +++ b/variants/esp32s3/tracksenger/oled/pins_arduino.h @@ -11,10 +11,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/tracksenger/oled/variant.h b/variants/esp32s3/tracksenger/oled/variant.h index 85cc019c4..689864b32 100644 --- a/variants/esp32s3/tracksenger/oled/variant.h +++ b/variants/esp32s3/tracksenger/oled/variant.h @@ -79,7 +79,6 @@ // keyboard changes #define PIN_BUZZER 43 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define INPUTBROKER_MATRIX_TYPE 1 diff --git a/variants/esp32s3/tracksenger/platformio.ini b/variants/esp32s3/tracksenger/platformio.ini index 419a3539b..c006cf835 100644 --- a/variants/esp32s3/tracksenger/platformio.ini +++ b/variants/esp32s3/tracksenger/platformio.ini @@ -22,7 +22,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 [env:tracksenger-lcd] custom_meshtastic_hw_model = 48 @@ -48,7 +48,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 [env:tracksenger-oled] custom_meshtastic_hw_model = 48 diff --git a/variants/esp32s3/unphone/pins_arduino.h b/variants/esp32s3/unphone/pins_arduino.h index 74067359f..8a62e3d42 100644 --- a/variants/esp32s3/unphone/pins_arduino.h +++ b/variants/esp32s3/unphone/pins_arduino.h @@ -6,9 +6,6 @@ #define USB_VID 0x16D0 #define USB_PID 0x1178 -#define LED_BUILTIN 13 -#define BUILTIN_LED LED_BUILTIN // backward compatibility - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/unphone/platformio.ini b/variants/esp32s3/unphone/platformio.ini index 28be1f3e1..924dfa74f 100644 --- a/variants/esp32s3/unphone/platformio.ini +++ b/variants/esp32s3/unphone/platformio.ini @@ -37,7 +37,7 @@ build_src_filter = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.0 + lovyan03/LovyanGFX@1.2.19 # TODO renovate https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic@9.0.0 # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel diff --git a/variants/native/portduino-buildroot/variant.h b/variants/native/portduino-buildroot/variant.h index affd83051..ac6421b14 100644 --- a/variants/native/portduino-buildroot/variant.h +++ b/variants/native/portduino-buildroot/variant.h @@ -1,6 +1,5 @@ #define HAS_SCREEN 1 #define USE_TFTDISPLAY 1 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define HAS_GPS 1 #define MAX_RX_TOPHONE portduino_config.maxtophone #define MAX_NUM_NODES portduino_config.MaxNodes diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index cc6c39aa2..b86420291 100644 --- a/variants/native/portduino.ini +++ b/variants/native/portduino.ini @@ -27,9 +27,9 @@ lib_deps = # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto rweather/Crypto@0.4.0 # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main - https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip + https://github.com/pine64/libch341-spi-userspace/archive/23c42319a69cffcb65868e3c72e6bed83974a393.zip # renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library adafruit/Adafruit seesaw Library@1.7.9 # renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main diff --git a/variants/native/portduino/variant.h b/variants/native/portduino/variant.h index 972443450..cba4aaedd 100644 --- a/variants/native/portduino/variant.h +++ b/variants/native/portduino/variant.h @@ -2,7 +2,6 @@ #define HAS_SCREEN 1 #endif #define USE_TFTDISPLAY 1 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define HAS_GPS 1 #define MAX_RX_TOPHONE portduino_config.maxtophone #define MAX_NUM_NODES portduino_config.MaxNodes diff --git a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini index 880871e13..093c3732d 100644 --- a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini +++ b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini @@ -12,5 +12,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/Dongle_ lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 debug_tool = jlink diff --git a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h index 2318450eb..8baf25e87 100644 --- a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h +++ b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h @@ -50,9 +50,6 @@ extern "C" { #define RGBLED_BLUE (0 + 12) // Blue of RGB P0.12 #define RGBLED_CA // comment out this line if you have a common cathode type, as defined use common anode logic -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp index cae079b74..04f86e2d4 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp @@ -32,13 +32,31 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - // LED1 & LED2 pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - - pinMode(PIN_LED3, OUTPUT); - ledOff(PIN_LED3); } + +void variant_shutdown() +{ + for (int pin = 0; pin < 48; pin++) { + if (pin == SX126X_BUSY || pin == PIN_SPI_SCK || pin == SX126X_DIO1 || pin == PIN_SPI_MOSI || pin == PIN_SPI_MISO || + pin == SX126X_CS || pin == SX126X_RESET || pin == PIN_NFC1 || pin == PIN_NFC2 || pin == PIN_BUTTON1 || + pin == PIN_BUTTON2) { + continue; + } + pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); + if (pin >= 32) { + NRF_P1->DIRCLR = (1 << (pin - 32)); + } else { + NRF_GPIO->DIRCLR = (1 << pin); + } + } + nrf_gpio_cfg_input(PIN_BUTTON1, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input + nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW; + nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense); + + nrf_gpio_cfg_input(PIN_BUTTON2, NRF_GPIO_PIN_PULLUP); + nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW; + nrf_gpio_cfg_sense_set(PIN_BUTTON2, sense1); +} \ No newline at end of file diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h index cde0f49c1..e00e56785 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h @@ -41,22 +41,15 @@ extern "C" { #define NUM_ANALOG_INPUTS (1) #define NUM_ANALOG_OUTPUTS (0) -#define PIN_LED2 -1 -#define PIN_LED3 -1 - // LED #define PIN_LED1 (32 + 6) // red #define LED_POWER (32 + 4) -#define USER_LED (0 + 13) // green +#define LED_NOTIFICATION (0 + 13) // green // USB_CHECK #define EXT_PWR_DETECT (32 + 3) #define ADC_V (0 + 8) -#define LED_RED PIN_LED3 #define LED_BLUE PIN_LED1 -#define LED_GREEN PIN_LED2 -#define LED_BUILTIN LED_BLUE -#define LED_CONN PIN_GREEN #define LED_STATE_ON 0 // State when LED is lit // LED灯亮时的状态 #define PIN_BUZZER (0 + 6) /* @@ -159,6 +152,8 @@ External serial flash WP25R1635FZUIL0 #define PIN_SERIAL1_TX GPS_TX_PIN #define PIN_SERIAL1_RX GPS_RX_PIN +#define SERIAL_PRINT_PORT 0 + /* * SPI Interfaces */ @@ -169,8 +164,6 @@ External serial flash WP25R1635FZUIL0 #define PIN_SPI_MOSI (0 + 22) #define PIN_SPI_SCK (0 + 19) -#define PIN_PWR_EN (0 + 6) - // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini index bf9492075..7a87c2a21 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini @@ -25,4 +25,4 @@ lib_deps = # renovate: datasource=custom.pio depName=nRF52_PWM packageName=khoih-prog/library/nRF52_PWM khoih-prog/nRF52_PWM@1.0.1 ; # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - ; lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp index 9769e3edd..45a64ad3b 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp @@ -37,8 +37,8 @@ void initVariant() digitalWrite(KEY_POWER, HIGH); pinMode(RGB_POWER, OUTPUT); digitalWrite(RGB_POWER, HIGH); - pinMode(green_LED_PIN, OUTPUT); - digitalWrite(green_LED_PIN, LED_STATE_OFF); + pinMode(LED_GREEN, OUTPUT); + digitalWrite(LED_GREEN, LED_STATE_OFF); pinMode(LED_BLUE, OUTPUT); pinMode(PIN_POWER_USB, INPUT); pinMode(PIN_POWER_DONE, INPUT); @@ -63,8 +63,8 @@ void initVariant() // called from main-nrf52.cpp during the cpuDeepSleep() function void variant_shutdown() { - digitalWrite(red_LED_PIN, HIGH); - digitalWrite(green_LED_PIN, HIGH); + digitalWrite(LED_RED, HIGH); + digitalWrite(LED_GREEN, HIGH); digitalWrite(LED_BLUE, HIGH); digitalWrite(PIN_EN1, LOW); @@ -81,8 +81,8 @@ void variant_shutdown() if (pin == PIN_POWER_USB || pin == BUTTON_PIN || pin == PIN_EN1 || pin == PIN_EN2 || pin == DHT_POWER || pin == ACC_POWER || pin == Battery_POWER || pin == GPS_POWER || pin == LR1110_SPI_MISO_PIN || pin == LR1110_SPI_MOSI_PIN || pin == LR1110_SPI_SCK_PIN || pin == LR1110_SPI_NSS_PIN || pin == LR1110_BUSY_PIN || - pin == LR1110_NRESET_PIN || pin == LR1110_IRQ_PIN || pin == GPS_TX_PIN || pin == GPS_RX_PIN || pin == green_LED_PIN || - pin == red_LED_PIN || pin == LED_BLUE) { + pin == LR1110_NRESET_PIN || pin == LR1110_IRQ_PIN || pin == GPS_TX_PIN || pin == GPS_RX_PIN || pin == LED_GREEN || + pin == LED_RED || pin == LED_BLUE) { continue; } pinMode(pin, OUTPUT); diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h index 29a6c85fd..50944b6d7 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h @@ -50,15 +50,14 @@ extern "C" { #define EEPROM_POWER 7 // LED -#define red_LED_PIN 33 -#define LED_POWER red_LED_PIN +#define LED_RED 33 +#define LED_POWER LED_RED #define LED_CHARGE LED_POWER // Signals the Status LED Module to handle this LED -#define green_LED_PIN 35 -#define PIN_LED2 green_LED_PIN +#define LED_GREEN 35 +#define LED_NOTIFICATION LED_GREEN #define LED_BLUE 37 #define LED_PAIRING LED_BLUE // Signals the Status LED Module to handle this LED -#define LED_BUILTIN -1 #define LED_STATE_ON LOW #define LED_STATE_OFF HIGH @@ -113,9 +112,11 @@ extern "C" { #define LR11X0_DIO3_TCXO_VOLTAGE 3.3 #define LR11X0_DIO_AS_RF_SWITCH +#define SERIAL_PRINT_PORT 0 + // PCF8563 RTC Module -// REVISIT https://github.com/meshtastic/firmware/pull/9084 -// #define PCF8563_RTC 0x51 +#define PCF8563_RTC 0x51 +#define HAS_RTC 1 #ifdef __cplusplus } diff --git a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp index af9bed998..999f326db 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp @@ -32,9 +32,6 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - pinMode(LED_PAIRING, OUTPUT); ledOff(LED_PAIRING); @@ -49,3 +46,21 @@ void initVariant() pinMode(Battery_LED_4, OUTPUT); ledOff(Battery_LED_4); } + +/// called from main-nrf52.cpp during the cpuDeepSleep() function +void variant_shutdown() +{ + for (int pin = 0; pin < 48; pin++) { + if (pin == PIN_GPS_EN || pin == PIN_BUTTON1) { + continue; + } + pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); + if (pin >= 32) { + NRF_P1->DIRCLR = (1 << (pin - 32)); + } else { + NRF_GPIO->DIRCLR = (1 << pin); + } + } + digitalWrite(PIN_GPS_EN, HIGH); +} diff --git a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h index faca5b075..2cfe948e3 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h @@ -40,9 +40,8 @@ extern "C" { #define NUM_ANALOG_OUTPUTS (0) // LEDs -#define LED_BUILTIN -1 #define LED_BLUE -1 -#define PIN_LED2 (32 + 9) +#define LED_NOTIFICATION (32 + 9) #define LED_PAIRING (13) #define Battery_LED_1 (15) @@ -135,6 +134,8 @@ static const uint8_t A0 = PIN_A0; #define PIN_SERIAL1_RX GPS_RX_PIN #define PIN_SERIAL1_TX GPS_TX_PIN +#define SERIAL_PRINT_PORT 0 + #ifdef __cplusplus } #endif diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini index 413eb4fab..329111ce6 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini @@ -22,4 +22,4 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW lib_deps = ${nrf52840_base.lib_deps} ; # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - ; lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp index 9c7b521ef..bc0381a48 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp @@ -48,7 +48,7 @@ void variant_shutdown() // This sets the pin to OUTPUT and LOW for the pins *not* in the if block. for (int pin = 0; pin < 48; pin++) { if (pin == PIN_GPS_EN || pin == ADC_CTRL || pin == PIN_BUTTON1 || pin == PIN_SPI_MISO || pin == PIN_SPI_MOSI || - pin == PIN_SPI_SCK) { + pin == PIN_SPI_SCK || pin == SX126X_CS || pin == SX126X_RESET || pin == SX126X_BUSY || pin == SX126X_DIO1) { continue; } pinMode(pin, OUTPUT); @@ -67,4 +67,8 @@ void variant_shutdown() nrf_gpio_cfg_input(PIN_BUTTON1, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW; nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense1); + + nrf_gpio_cfg_input(EXT_CHRG_DETECT, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input + nrf_gpio_pin_sense_t sense2 = NRF_GPIO_PIN_SENSE_LOW; + nrf_gpio_cfg_sense_set(EXT_CHRG_DETECT, sense2); } diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h index e46391207..ba6aa14ab 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h @@ -40,11 +40,10 @@ extern "C" { #define NUM_ANALOG_OUTPUTS (0) // LEDs -#define LED_BUILTIN -1 #define LED_BLUE -1 #define LED_CHARGE (12) #define LED_PAIRING (7) -#define PIN_LED2 LED_PAIRING +#define LED_NOTIFICATION LED_PAIRING #define LED_STATE_ON HIGH #define LED_STATE_OFF LOW @@ -121,8 +120,8 @@ static const uint8_t A0 = PIN_A0; #define PIN_SERIAL2_TX (24) // PCF8563 RTC Module -// REVISIT https://github.com/meshtastic/firmware/pull/9084 -// #define PCF8563_RTC 0x51 +#define PCF8563_RTC 0x51 +#define HAS_RTC 1 // SPI #define SPI_INTERFACES_COUNT 1 diff --git a/variants/nrf52840/ME25LS01-4Y10TD/variant.h b/variants/nrf52840/ME25LS01-4Y10TD/variant.h index e772069da..a920b02e5 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD/variant.h +++ b/variants/nrf52840/ME25LS01-4Y10TD/variant.h @@ -51,7 +51,6 @@ extern "C" { #define PIN_LED1 (32 + 7) // P1.07 Blue D2 #define LED_PIN PIN_LED1 -#define LED_BUILTIN -1 #define LED_BLUE -1 #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini index 025e28078..5951a37a3 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini +++ b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini @@ -16,7 +16,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ME25LS0 lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) upload_protocol = nrfutil ;upload_port = /dev/ttyACM1 diff --git a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h index 797394ce6..683669160 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h +++ b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h @@ -51,7 +51,6 @@ extern "C" { #define PIN_LED1 (32 + 7) // P1.07 Blue D2 #define LED_PIN PIN_LED1 -#define LED_BUILTIN -1 #define LED_BLUE -1 #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/MS24SF1/variant.h b/variants/nrf52840/MS24SF1/variant.h index d26dcebc2..a71be99fd 100644 --- a/variants/nrf52840/MS24SF1/variant.h +++ b/variants/nrf52840/MS24SF1/variant.h @@ -51,7 +51,6 @@ extern "C" { #define PIN_LED1 (-1) #define LED_PIN PIN_LED1 -#define LED_BUILTIN -1 #define LED_BLUE -1 #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini b/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini index d823a7230..ecb630bb7 100644 --- a/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini +++ b/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini @@ -15,6 +15,6 @@ lib_deps = # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 debug_tool = jlink ;upload_port = /dev/ttyACM4 \ No newline at end of file diff --git a/variants/nrf52840/MakePython_nRF52840_eink/variant.cpp b/variants/nrf52840/MakePython_nRF52840_eink/variant.cpp index 8c6bf039c..04cda84ac 100644 --- a/variants/nrf52840/MakePython_nRF52840_eink/variant.cpp +++ b/variants/nrf52840/MakePython_nRF52840_eink/variant.cpp @@ -32,7 +32,4 @@ void initVariant() // LED1 & LED2 pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); } diff --git a/variants/nrf52840/MakePython_nRF52840_eink/variant.h b/variants/nrf52840/MakePython_nRF52840_eink/variant.h index 00c8dc199..64cca4916 100644 --- a/variants/nrf52840/MakePython_nRF52840_eink/variant.h +++ b/variants/nrf52840/MakePython_nRF52840_eink/variant.h @@ -27,15 +27,10 @@ extern "C" { // LEDs #define PIN_LED1 (32 + 10) // LED P1.15 -#define PIN_LED2 (-1) // - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 -#define LED_STATE_ON 0 // State when LED is litted +#define LED_STATE_ON 0 // State when LED is lit /* * Buttons diff --git a/variants/nrf52840/MakePython_nRF52840_oled/variant.cpp b/variants/nrf52840/MakePython_nRF52840_oled/variant.cpp index 8c6bf039c..04cda84ac 100644 --- a/variants/nrf52840/MakePython_nRF52840_oled/variant.cpp +++ b/variants/nrf52840/MakePython_nRF52840_oled/variant.cpp @@ -32,7 +32,4 @@ void initVariant() // LED1 & LED2 pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); } diff --git a/variants/nrf52840/MakePython_nRF52840_oled/variant.h b/variants/nrf52840/MakePython_nRF52840_oled/variant.h index 28d941171..2f035e7da 100644 --- a/variants/nrf52840/MakePython_nRF52840_oled/variant.h +++ b/variants/nrf52840/MakePython_nRF52840_oled/variant.h @@ -27,15 +27,10 @@ extern "C" { // LEDs #define PIN_LED1 (32 + 10) // LED P1.15 -#define PIN_LED2 (-1) // - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 -#define LED_STATE_ON 0 // State when LED is litted +#define LED_STATE_ON 0 // State when LED is lit /* * Buttons diff --git a/variants/nrf52840/TWC_mesh_v4/platformio.ini b/variants/nrf52840/TWC_mesh_v4/platformio.ini index e196029ec..d93f179c2 100644 --- a/variants/nrf52840/TWC_mesh_v4/platformio.ini +++ b/variants/nrf52840/TWC_mesh_v4/platformio.ini @@ -9,5 +9,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/TWC_mes lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 debug_tool = jlink diff --git a/variants/nrf52840/TWC_mesh_v4/variant.h b/variants/nrf52840/TWC_mesh_v4/variant.h index 6a6f541e6..875040b5f 100644 --- a/variants/nrf52840/TWC_mesh_v4/variant.h +++ b/variants/nrf52840/TWC_mesh_v4/variant.h @@ -34,9 +34,6 @@ extern "C" { // #define PIN_LED1 (32 + 9) Green // #define PIN_LED1 (0 + 12) Blue -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 diff --git a/variants/nrf52840/canaryone/variant.h b/variants/nrf52840/canaryone/variant.h index 61d1e8df9..3f45b618a 100644 --- a/variants/nrf52840/canaryone/variant.h +++ b/variants/nrf52840/canaryone/variant.h @@ -52,9 +52,6 @@ extern "C" { #define LED_BLUE PIN_LED1 -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED3 - #define LED_STATE_ON 0 // State when LED is lit /* @@ -170,6 +167,8 @@ static const uint8_t A0 = PIN_A0; #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER (2.0F) +#define SERIAL_PRINT_PORT 0 + #ifdef __cplusplus } #endif diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.cpp b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.cpp index 5869ed1d4..384c618e2 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.cpp +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.cpp @@ -36,3 +36,10 @@ void initVariant() pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); } + +void variant_shutdown() +{ + nrf_gpio_cfg_input(BUTTON_PIN, NRF_GPIO_PIN_PULLUP); // Enable internal pull-up on the button pin + nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW; // Configure SENSE signal on low edge + nrf_gpio_cfg_sense_set(BUTTON_PIN, sense); // Apply SENSE to wake up the device from the deep sleep +} diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h index 63af1fe79..8e10141f5 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h @@ -81,7 +81,6 @@ NRF52 PRO MICRO PIN ASSIGNMENT // LED #define PIN_LED1 (0 + 15) // P0.15 -#define LED_BUILTIN PIN_LED1 // Actually red #define LED_BLUE PIN_LED1 #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h index 277377d71..c132eba5b 100644 --- a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h +++ b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h @@ -35,10 +35,6 @@ extern "C" { #define PIN_LED2 LED_BLUE #define PIN_LED3 LED_RED -#define PIN_LED PIN_LED1 -#define LED_PWR (PINS_COUNT) - -#define LED_BUILTIN PIN_LED #define LED_STATE_ON 1 // State when LED is lit // XIAO Wio-SX1262 Shield User button diff --git a/variants/nrf52840/dls_Minimesh_Lite/platformio.ini b/variants/nrf52840/dls_Minimesh_Lite/platformio.ini new file mode 100644 index 000000000..763ff5477 --- /dev/null +++ b/variants/nrf52840/dls_Minimesh_Lite/platformio.ini @@ -0,0 +1,9 @@ +[env:minimesh_lite] +extends = nrf52840_base +board = minimesh_lite +board_level = extra +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/dls_Minimesh_Lite + -DPRIVATE_HW +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/dls_Minimesh_Lite> +debug_tool = jlink diff --git a/variants/nrf52840/dls_Minimesh_Lite/variant.cpp b/variants/nrf52840/dls_Minimesh_Lite/variant.cpp new file mode 100644 index 000000000..5869ed1d4 --- /dev/null +++ b/variants/nrf52840/dls_Minimesh_Lite/variant.cpp @@ -0,0 +1,38 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); +} diff --git a/variants/nrf52840/dls_Minimesh_Lite/variant.h b/variants/nrf52840/dls_Minimesh_Lite/variant.h new file mode 100644 index 000000000..32c16f06d --- /dev/null +++ b/variants/nrf52840/dls_Minimesh_Lite/variant.h @@ -0,0 +1,103 @@ +#ifndef _VARIANT_MINIMESH_LITE_ +#define _VARIANT_MINIMESH_LITE_ + +#define VARIANT_MCK (64000000ul) +#define USE_LFRC + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MINIMESH_LITE + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +#define PIN_3V3_EN (0 + 13) // P0.13 + +// Analog pins +#define BATTERY_PIN (0 + 31) // P0.31 Battery ADC +#define ADC_CHANNEL ADC1_GPIO4_CHANNEL +#define ADC_RESOLUTION 14 +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#define VBAT_MV_PER_LSB (0.73242188F) +#define VBAT_DIVIDER (0.6F) +#define VBAT_DIVIDER_COMP (1.73) +#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER VBAT_DIVIDER_COMP +#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) + +// WIRE IC AND IIC PINS +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (32 + 4) +#define PIN_WIRE_SCL (0 + 11) + +// LED +#define PIN_LED1 (0 + 15) +// Actually red +#define LED_BLUE PIN_LED1 +#define LED_STATE_ON 1 + +// Button +#define BUTTON_PIN (32 + 0) + +// GPS +#define GPS_TX_PIN (0 + 20) +#define GPS_RX_PIN (0 + 22) + +#define PIN_GPS_EN (0 + 24) +#define GPS_UBLOX +// define GPS_DEBUG + +// UART interfaces +#define PIN_SERIAL1_TX GPS_TX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN + +#define PIN_SERIAL2_RX (0 + 6) +#define PIN_SERIAL2_TX (0 + 8) + +// Serial interfaces +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (0 + 2) +#define PIN_SPI_MOSI (32 + 15) +#define PIN_SPI_SCK (32 + 11) + +#define LORA_MISO PIN_SPI_MISO +#define LORA_MOSI PIN_SPI_MOSI +#define LORA_SCK PIN_SPI_SCK +#define LORA_CS (32 + 13) + +// LORA MODULES +#define USE_LLCC68 +#define USE_SX1262 +#define USE_SX1268 + +// SX126X CONFIG +#define SX126X_CS (32 + 13) +#define SX126X_DIO1 (0 + 10) +#define SX126X_DIO2_AS_RF_SWITCH + +#define SX126X_BUSY (0 + 29) +#define SX126X_RESET (0 + 9) +#define SX126X_RXEN (0 + 17) +#define SX126X_TXEN RADIOLIB_NC + +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL + +#ifdef __cplusplus +} +#endif + +#endif // _VARIANT_MINIMESH_LITE_ diff --git a/variants/nrf52840/feather_diy/variant.h b/variants/nrf52840/feather_diy/variant.h index 1c0979f82..a816e7867 100644 --- a/variants/nrf52840/feather_diy/variant.h +++ b/variants/nrf52840/feather_diy/variant.h @@ -49,12 +49,10 @@ extern "C" { #define PIN_LED1 (32 + 15) // P1.15 3 #define PIN_LED2 (32 + 10) // P1.10 4 -#define LED_BUILTIN PIN_LED1 - #define LED_GREEN PIN_LED2 // Actually red #define LED_BLUE PIN_LED1 -#define LED_STATE_ON 1 // State when LED is litted +#define LED_STATE_ON 1 // State when LED is lit #define BUTTON_PIN (32 + 2) // P1.02 7 diff --git a/variants/nrf52840/gat562_mesh_trial_tracker/variant.cpp b/variants/nrf52840/gat562_mesh_trial_tracker/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/gat562_mesh_trial_tracker/variant.cpp +++ b/variants/nrf52840/gat562_mesh_trial_tracker/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/gat562_mesh_trial_tracker/variant.h b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h index 6337ac70c..b5632a7fb 100644 --- a/variants/nrf52840/gat562_mesh_trial_tracker/variant.h +++ b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h @@ -46,13 +46,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted @@ -63,8 +60,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins @@ -264,8 +259,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 -// #define HAS_RTC 1 - // #define HAS_ETHERNET 1 // #define RAK_4631 1 diff --git a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.cpp b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.cpp index 85c9f4a72..b8b0e21c5 100644 --- a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.cpp +++ b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.cpp @@ -19,6 +19,7 @@ */ #include "variant.h" +#include "Arduino.h" #include "nrf.h" #include "wiring_constants.h" #include "wiring_digital.h" @@ -36,3 +37,10 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); } + +void variant_shutdown() +{ + nrf_gpio_cfg_default(PIN_GPS_PPS); + detachInterrupt(PIN_GPS_PPS); + detachInterrupt(PIN_BUTTON1); +} \ No newline at end of file diff --git a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h index 14170d5f3..2802e4c1d 100644 --- a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h @@ -30,7 +30,6 @@ extern "C" { #define PIN_LED1 (32 + 3) // green (confirmed on 1.0 board) #define LED_BLUE PIN_LED1 // fake for bluefruit library #define LED_GREEN PIN_LED1 -#define LED_BUILTIN LED_GREEN #define LED_STATE_ON 0 // State when LED is lit #define HAS_NEOPIXEL // Enable the use of neopixels @@ -138,8 +137,6 @@ No longer populated on PCB #define PIN_SPI1_MOSI PIN_EINK_MOSI #define PIN_SPI1_SCK PIN_EINK_SCLK -// #define PIN_PWR_EN (0 + 6) - // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.cpp b/variants/nrf52840/heltec_mesh_node_t114/variant.cpp index 85c9f4a72..b8b0e21c5 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.cpp +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.cpp @@ -19,6 +19,7 @@ */ #include "variant.h" +#include "Arduino.h" #include "nrf.h" #include "wiring_constants.h" #include "wiring_digital.h" @@ -36,3 +37,10 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); } + +void variant_shutdown() +{ + nrf_gpio_cfg_default(PIN_GPS_PPS); + detachInterrupt(PIN_GPS_PPS); + detachInterrupt(PIN_BUTTON1); +} \ No newline at end of file diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h index bad488b35..e7385c4bb 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h @@ -74,7 +74,6 @@ extern "C" { #define PIN_LED1 (32 + 3) // green (confirmed on 1.0 board) #define LED_BLUE PIN_LED1 // fake for bluefruit library #define LED_GREEN PIN_LED1 -#define LED_BUILTIN LED_GREEN #define LED_STATE_ON 0 // State when LED is lit #define HAS_NEOPIXEL // Enable the use of neopixels @@ -150,6 +149,14 @@ No longer populated on PCB #define PIN_SPI1_MOSI ST7789_SDA #define PIN_SPI1_SCK ST7789_SCK +/* + * Bluetooth + */ + +// The bluetooth transmit power on the nRF52840 is adjustable from -20dB to +8dB in steps of 4dB +// so NRF52_BLE_TX_POWER can be set to -20, -16, -12, -8, -4, 0 (default), 4, and 8. +// #define NRF52_BLE_TX_POWER 8 + /* * GPS pins */ @@ -185,8 +192,6 @@ No longer populated on PCB #define PIN_SPI_MOSI (0 + 22) #define PIN_SPI_SCK (0 + 19) -// #define PIN_PWR_EN (0 + 6) - // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER @@ -218,7 +223,6 @@ No longer populated on PCB // VBAT=4.04V #define BATTERY_LPCOMP_THRESHOLD NRF_LPCOMP_REF_SUPPLY_2_8 -#define HAS_RTC 0 #ifdef __cplusplus } #endif diff --git a/variants/nrf52840/heltec_mesh_pocket/variant.h b/variants/nrf52840/heltec_mesh_pocket/variant.h index 7ec9b88ea..a86faf575 100644 --- a/variants/nrf52840/heltec_mesh_pocket/variant.h +++ b/variants/nrf52840/heltec_mesh_pocket/variant.h @@ -26,8 +26,6 @@ extern "C" { #define LED_RED PIN_LED1 #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED1 -#define LED_BUILTIN LED_BLUE -#define LED_CONN LED_BLUE #define LED_STATE_ON 0 // State when LED is lit /* @@ -100,8 +98,6 @@ No longer populated on PCB #define PIN_SPI_MOSI (0 + 5) #define PIN_SPI_SCK (0 + 4) -// #define PIN_PWR_EN (0 + 6) - // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER diff --git a/variants/nrf52840/heltec_mesh_solar/variant.cpp b/variants/nrf52840/heltec_mesh_solar/variant.cpp index c13f006d7..3b2612da8 100644 --- a/variants/nrf52840/heltec_mesh_solar/variant.cpp +++ b/variants/nrf52840/heltec_mesh_solar/variant.cpp @@ -19,6 +19,7 @@ */ #include "variant.h" +#include "Arduino.h" #include "nrf.h" #include "wiring_constants.h" #include "wiring_digital.h" @@ -38,3 +39,10 @@ void initVariant() digitalWrite(PIN_SCREEN_VDD_CTL, LOW); // Start with power on #endif } + +void variant_shutdown() +{ + nrf_gpio_cfg_default(PIN_GPS_PPS); + detachInterrupt(PIN_GPS_PPS); + detachInterrupt(PIN_BUTTON1); +} \ No newline at end of file diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h index 112bcd8b3..8d4db4bea 100644 --- a/variants/nrf52840/heltec_mesh_solar/variant.h +++ b/variants/nrf52840/heltec_mesh_solar/variant.h @@ -39,16 +39,15 @@ extern "C" { #define NUM_ANALOG_INPUTS (1) #define NUM_ANALOG_OUTPUTS (0) -#define PIN_LED1 (0 + 4) // green (confirmed on 1.0 board) -#define LED_BLUE PIN_LED1 // fake for bluefruit library +#define PIN_LED1 (32 + 15) // green (confirmed on 1.0 board) +#define LED_BLUE PIN_LED1 // fake for bluefruit library #define LED_GREEN PIN_LED1 -#define LED_BUILTIN LED_GREEN #define LED_STATE_ON 0 // State when LED is lit -#define HAS_NEOPIXEL // Enable the use of neopixels -#define NEOPIXEL_COUNT 1 // How many neopixels are connected -#define NEOPIXEL_DATA (32 + 15) // gpio pin used to send data to the neopixels -#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use +// #define HAS_NEOPIXEL // Enable the use of neopixels +// #define NEOPIXEL_COUNT 1 // How many neopixels are connected +// #define NEOPIXEL_DATA (32 + 15) // gpio pin used to send data to the neopixels +// #define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use /* * Buttons @@ -60,8 +59,8 @@ extern "C" { /* No longer populated on PCB */ -#define PIN_SERIAL2_RX (0 + 9) -#define PIN_SERIAL2_TX (0 + 10) +#define PIN_SERIAL2_RX (-1) +#define PIN_SERIAL2_TX (-1) /* * I2C @@ -133,15 +132,21 @@ No longer populated on PCB #define PIN_SPI_MOSI (0 + 22) #define PIN_SPI_SCK (0 + 19) -// #define PIN_PWR_EN (0 + 6) - // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER +// Hardware watchdog +#define HAS_HARDWARE_WATCHDOG +#define HARDWARE_WATCHDOG_DONE (0 + 9) +#define HARDWARE_WATCHDOG_WAKE (0 + 10) +#define HARDWARE_WATCHDOG_TIMEOUT_MS (6 * 60 * 1000) // 6 minute watchdog + #define BQ4050_SDA_PIN (32 + 1) // I2C data line pin #define BQ4050_SCL_PIN (32 + 0) // I2C clock line pin #define BQ4050_EMERGENCY_SHUTDOWN_PIN (32 + 3) // Emergency shutdown pin +#define SERIAL_PRINT_PORT 0 + #ifdef __cplusplus } #endif diff --git a/variants/nrf52840/meshlink/variant.cpp b/variants/nrf52840/meshlink/variant.cpp index 81a5097c4..f35bbd1af 100644 --- a/variants/nrf52840/meshlink/variant.cpp +++ b/variants/nrf52840/meshlink/variant.cpp @@ -20,4 +20,11 @@ void initVariant() pinMode(PIN_WD_EN, OUTPUT); digitalWrite(PIN_WD_EN, HIGH); // Enable the Watchdog at boot #endif +} + +void variant_shutdown() +{ +#ifdef PIN_WD_EN + digitalWrite(PIN_WD_EN, LOW); +#endif } \ No newline at end of file diff --git a/variants/nrf52840/meshlink/variant.h b/variants/nrf52840/meshlink/variant.h index d1dba574f..00107ac34 100644 --- a/variants/nrf52840/meshlink/variant.h +++ b/variants/nrf52840/meshlink/variant.h @@ -31,10 +31,8 @@ extern "C" { // LEDs #define PIN_LED1 (24) // Built in white led for status #define LED_BLUE PIN_LED1 -#define LED_BUILTIN PIN_LED1 -#define LED_STATE_ON 0 // State when LED is litted -#define LED_INVERTED 1 +#define LED_STATE_ON 0 // State when LED is lit // Testing USB detection // #define NRF_APM @@ -54,6 +52,7 @@ extern "C" { */ #define PIN_SERIAL1_RX (32 + 8) #define PIN_SERIAL1_TX (7) +#define SERIAL_PRINT_PORT 0 /* * SPI Interfaces @@ -150,4 +149,4 @@ static const uint8_t SCK = PIN_SPI_SCK; /*---------------------------------------------------------------------------- * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif \ No newline at end of file +#endif diff --git a/variants/nrf52840/meshtiny/variant.cpp b/variants/nrf52840/meshtiny/variant.cpp index 2e8b00e4b..aae3da9f2 100644 --- a/variants/nrf52840/meshtiny/variant.cpp +++ b/variants/nrf52840/meshtiny/variant.cpp @@ -32,12 +32,12 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - // LED1 & LED2 + // LED1 & LED_BLUE pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); + pinMode(LED_BLUE, OUTPUT); + ledOff(LED_BLUE); // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); diff --git a/variants/nrf52840/meshtiny/variant.h b/variants/nrf52840/meshtiny/variant.h index 8d634ba60..4289fef4c 100644 --- a/variants/nrf52840/meshtiny/variant.h +++ b/variants/nrf52840/meshtiny/variant.h @@ -47,13 +47,9 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 #define LED_STATE_ON 1 // State when LED is litted @@ -66,8 +62,6 @@ extern "C" { #define INPUTDRIVER_ENCODER_BTN 28 #define UPDOWN_LONG_PRESS_REPEAT_INTERVAL 150 -#define CANNED_MESSAGE_MODULE_ENABLE 1 - /* * Buzzer - PWM */ diff --git a/variants/nrf52840/monteops_hw1/variant.cpp b/variants/nrf52840/monteops_hw1/variant.cpp index 75cca1dc3..81ae9f482 100644 --- a/variants/nrf52840/monteops_hw1/variant.cpp +++ b/variants/nrf52840/monteops_hw1/variant.cpp @@ -35,7 +35,4 @@ void initVariant() // LED1 & LED2 pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); } diff --git a/variants/nrf52840/monteops_hw1/variant.h b/variants/nrf52840/monteops_hw1/variant.h index 97536b169..a7fa7125b 100644 --- a/variants/nrf52840/monteops_hw1/variant.h +++ b/variants/nrf52840/monteops_hw1/variant.h @@ -50,13 +50,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) // Connected to WWAN host LED (if present) - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 +#define LED_BLUE (36) // Connected to WWAN host LED (if present) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted @@ -67,8 +64,6 @@ extern "C" { // #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins @@ -213,8 +208,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER (1.73F) -// #define HAS_RTC 1 - #define HAS_ETHERNET 1 #define PIN_ETHERNET_RESET 21 diff --git a/variants/nrf52840/muzi_base/variant.cpp b/variants/nrf52840/muzi_base/variant.cpp index da01de974..e6178a968 100644 --- a/variants/nrf52840/muzi_base/variant.cpp +++ b/variants/nrf52840/muzi_base/variant.cpp @@ -63,8 +63,8 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); digitalWrite(PIN_LED1, HIGH); - pinMode(PIN_LED2, OUTPUT); - digitalWrite(PIN_LED2, HIGH); + pinMode(LED_BLUE, OUTPUT); + digitalWrite(LED_BLUE, HIGH); // Initialize LoRa pins pinMode(SX126X_RESET, OUTPUT); diff --git a/variants/nrf52840/muzi_base/variant.h b/variants/nrf52840/muzi_base/variant.h index 96604c400..0cbd0e3ef 100644 --- a/variants/nrf52840/muzi_base/variant.h +++ b/variants/nrf52840/muzi_base/variant.h @@ -38,15 +38,13 @@ extern "C" { #define COMPASS_ORIENTATION meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270 #define HAS_ICM20948 // forces the i2c address to be seen as this sensor -#define HAS_RTC 1 #define RX8130CE_RTC 0x32 // LEDs #define PIN_LED1 (32 + 3) // P1.03, Green -#define PIN_LED2 (32 + 4) // P1.04, Blue +#define LED_BLUE (32 + 4) // P1.04, Blue -#define LED_BUILTIN -1 // PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 0 // State when LED is lit // Buttons @@ -176,6 +174,8 @@ extern "C" { #define EXTERNAL_FLASH_DEVICES W25Q32JVSS #define EXTERNAL_FLASH_USE_QSPI +#define SERIAL_PRINT_PORT 0 + // NFC is disabled via CONFIG_NFCT_PINS_AS_GPIOS=1 build flag // This configures P0.09 and P0.10 as regular GPIO pins instead of NFC pins diff --git a/variants/nrf52840/nano-g2-ultra/platformio.ini b/variants/nrf52840/nano-g2-ultra/platformio.ini index 0748b7e38..7e593a49a 100644 --- a/variants/nrf52840/nano-g2-ultra/platformio.ini +++ b/variants/nrf52840/nano-g2-ultra/platformio.ini @@ -20,5 +20,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/nano-g2 lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 ;upload_protocol = fs diff --git a/variants/nrf52840/nano-g2-ultra/variant.h b/variants/nrf52840/nano-g2-ultra/variant.h index d8f41a68c..631af72d8 100644 --- a/variants/nrf52840/nano-g2-ultra/variant.h +++ b/variants/nrf52840/nano-g2-ultra/variant.h @@ -41,18 +41,6 @@ extern "C" { #define NUM_ANALOG_INPUTS (1) #define NUM_ANALOG_OUTPUTS (0) -// LEDs -#define PIN_LED1 (-1) -#define PIN_LED2 (-1) -#define PIN_LED3 (-1) - -#define LED_RED PIN_LED3 -#define LED_BLUE PIN_LED1 -#define LED_GREEN PIN_LED2 - -#define LED_BUILTIN LED_BLUE -#define LED_CONN PIN_GREEN - #define LED_STATE_ON 0 // State when LED is lit /* @@ -141,7 +129,6 @@ External serial flash W25Q16JV_IQ // PCF8563 RTC Module #define PIN_RTC_INT (0 + 14) // Interrupt from the PCF8563 RTC #define PCF8563_RTC 0x51 -#define HAS_RTC 1 /* * SPI Interfaces @@ -153,8 +140,6 @@ External serial flash W25Q16JV_IQ #define PIN_SPI_MOSI (0 + 11) #define PIN_SPI_SCK (0 + 12) -// #define PIN_PWR_EN (0 + 6) - // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER diff --git a/variants/nrf52840/nrf52.ini b/variants/nrf52840/nrf52.ini index a07fefb2f..727d2c741 100644 --- a/variants/nrf52840/nrf52.ini +++ b/variants/nrf52840/nrf52.ini @@ -7,7 +7,7 @@ extends = arduino_base platform_packages = ; our custom Git version until they merge our PR # TODO renovate - platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#c770c8a16a351b55b86e347a3d9d7b74ad0bbf39 + platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#74096746e5f167a2ff22e483d8e79bb1aef00591 ; Don't renovate toolchain-gccarmnoneeabi platformio/toolchain-gccarmnoneeabi@~1.90301.0 diff --git a/variants/nrf52840/r1-neo/variant.cpp b/variants/nrf52840/r1-neo/variant.cpp index f87c041aa..c36e88602 100644 --- a/variants/nrf52840/r1-neo/variant.cpp +++ b/variants/nrf52840/r1-neo/variant.cpp @@ -36,10 +36,15 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail // pinMode(PIN_3V3_EN, OUTPUT); // digitalWrite(PIN_3V3_EN, HIGH); } + +void earlyInitVariant() +{ + pinMode(DCDC_EN_HOLD, OUTPUT); + digitalWrite(DCDC_EN_HOLD, HIGH); + pinMode(NRF_ON, OUTPUT); + digitalWrite(NRF_ON, HIGH); +} diff --git a/variants/nrf52840/r1-neo/variant.h b/variants/nrf52840/r1-neo/variant.h index b1d96ebd0..42d44d673 100644 --- a/variants/nrf52840/r1-neo/variant.h +++ b/variants/nrf52840/r1-neo/variant.h @@ -45,13 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (32 + 4) // P1.04 Controls Green LED -#define PIN_LED2 (28) // P0.28 Controls Blue LED - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 +#define LED_BLUE (28) // P0.28 Controls Blue LED #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted @@ -135,8 +132,6 @@ static const uint8_t SCK = PIN_SPI_SCK; #define ADC_MULTIPLIER 1.667 #define OCV_ARRAY 4120, 4020, 4000, 3940, 3870, 3820, 3750, 3630, 3550, 3450, 3100 -#define HAS_RTC 1 - #define RX8130CE_RTC 0x32 #ifdef __cplusplus diff --git a/variants/nrf52840/rak2560/variant.cpp b/variants/nrf52840/rak2560/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak2560/variant.cpp +++ b/variants/nrf52840/rak2560/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak2560/variant.h b/variants/nrf52840/rak2560/variant.h index f922e8a61..acd1ae60e 100644 --- a/variants/nrf52840/rak2560/variant.h +++ b/variants/nrf52840/rak2560/variant.h @@ -46,13 +46,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted @@ -63,8 +60,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins @@ -249,8 +244,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 -#define HAS_RTC 1 - #define RAK_4631 1 #define HALF_UART_PIN PIN_SERIAL1_RX diff --git a/variants/nrf52840/rak3401_1watt/variant.cpp b/variants/nrf52840/rak3401_1watt/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak3401_1watt/variant.cpp +++ b/variants/nrf52840/rak3401_1watt/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak3401_1watt/variant.h b/variants/nrf52840/rak3401_1watt/variant.h index d4bb1a175..80b09cf69 100644 --- a/variants/nrf52840/rak3401_1watt/variant.h +++ b/variants/nrf52840/rak3401_1watt/variant.h @@ -45,13 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted @@ -211,8 +208,6 @@ static const uint8_t SCK = PIN_SPI_SCK; #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 -#define HAS_RTC 1 - #define RAK_4631 1 #ifdef __cplusplus diff --git a/variants/nrf52840/rak4631/variant.cpp b/variants/nrf52840/rak4631/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak4631/variant.cpp +++ b/variants/nrf52840/rak4631/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak4631/variant.h b/variants/nrf52840/rak4631/variant.h index 302e531d5..6a6b32f27 100644 --- a/variants/nrf52840/rak4631/variant.h +++ b/variants/nrf52840/rak4631/variant.h @@ -45,13 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted @@ -62,8 +59,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins @@ -253,9 +248,13 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define RV3028_RTC (uint8_t)0b1010010 // RAK18001 Buzzer in Slot C -// #define PIN_BUZZER 21 // IO3 is PWM2 +#define PIN_BUZZER 21 // IO3 is PWM2 // NEW: set this via protobuf instead! +// RAK4631 custom ringtone +#undef USERPREFS_RINGTONE_RTTTL +#define USERPREFS_RINGTONE_RTTTL "Rak:d=32,o=5,b=200:b7,p,b7,4p,p" + // Battery // The battery sense is hooked to pin A0 (5) #define BATTERY_PIN PIN_A0 @@ -281,8 +280,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG // VDD=3.3V AIN3=6/8*VDD=2.47V VBAT=1.66*AIN3=4.1V #define BATTERY_LPCOMP_THRESHOLD NRF_LPCOMP_REF_SUPPLY_11_16 -#define HAS_RTC 1 - #define HAS_ETHERNET 1 #define RAK_4631 1 diff --git a/variants/nrf52840/rak4631_epaper/platformio.ini b/variants/nrf52840/rak4631_epaper/platformio.ini index bc21b7912..f0da832cb 100644 --- a/variants/nrf52840/rak4631_epaper/platformio.ini +++ b/variants/nrf52840/rak4631_epaper/platformio.ini @@ -15,7 +15,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631 lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 # renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028 melopero/Melopero RV3028@1.2.0 # renovate: datasource=custom.pio depName=RAK NCP5623 RGB LED packageName=rakwireless/library/RAKwireless NCP5623 RGB LED library diff --git a/variants/nrf52840/rak4631_epaper/variant.cpp b/variants/nrf52840/rak4631_epaper/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak4631_epaper/variant.cpp +++ b/variants/nrf52840/rak4631_epaper/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak4631_epaper/variant.h b/variants/nrf52840/rak4631_epaper/variant.h index c1e11bee5..82c26af7b 100644 --- a/variants/nrf52840/rak4631_epaper/variant.h +++ b/variants/nrf52840/rak4631_epaper/variant.h @@ -45,13 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted @@ -62,8 +59,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins @@ -222,8 +217,6 @@ static const uint8_t SCK = PIN_SPI_SCK; #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 -#define HAS_RTC 1 - #define RAK_4631 1 #ifdef __cplusplus diff --git a/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini b/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini index d0bca377b..112ddfc29 100644 --- a/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini +++ b/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini @@ -17,7 +17,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631 lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 # renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028 melopero/Melopero RV3028@1.2.0 # renovate: datasource=custom.pio depName=RAK NCP5623 RGB LED packageName=rakwireless/library/RAKwireless NCP5623 RGB LED library diff --git a/variants/nrf52840/rak4631_epaper_onrxtx/variant.cpp b/variants/nrf52840/rak4631_epaper_onrxtx/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak4631_epaper_onrxtx/variant.cpp +++ b/variants/nrf52840/rak4631_epaper_onrxtx/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak4631_epaper_onrxtx/variant.h b/variants/nrf52840/rak4631_epaper_onrxtx/variant.h index 1f8257e8e..2d34ab84c 100644 --- a/variants/nrf52840/rak4631_epaper_onrxtx/variant.h +++ b/variants/nrf52840/rak4631_epaper_onrxtx/variant.h @@ -27,13 +27,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted @@ -195,8 +192,6 @@ static const uint8_t SCK = PIN_SPI_SCK; // #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 // #define ADC_MULTIPLIER 1.73 -// #define HAS_RTC 1 - #ifdef __cplusplus } #endif diff --git a/variants/nrf52840/rak4631_eth_gw/variant.cpp b/variants/nrf52840/rak4631_eth_gw/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak4631_eth_gw/variant.cpp +++ b/variants/nrf52840/rak4631_eth_gw/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak4631_eth_gw/variant.h b/variants/nrf52840/rak4631_eth_gw/variant.h index c8a2f83ae..eb1d558ea 100644 --- a/variants/nrf52840/rak4631_eth_gw/variant.h +++ b/variants/nrf52840/rak4631_eth_gw/variant.h @@ -45,13 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted @@ -62,8 +59,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins @@ -254,8 +249,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 -#define HAS_RTC 1 - #define HAS_ETHERNET 1 #define RAK_4631 1 diff --git a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.cpp b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.cpp +++ b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h index 51baf3ada..aea497305 100644 --- a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h +++ b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h @@ -45,13 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted @@ -62,8 +59,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins @@ -249,8 +244,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 -#define HAS_RTC 0 - #define HAS_ETHERNET 0 #define RAK_4631 1 diff --git a/variants/nrf52840/rak_wismeshtag/variant.cpp b/variants/nrf52840/rak_wismeshtag/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak_wismeshtag/variant.cpp +++ b/variants/nrf52840/rak_wismeshtag/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak_wismeshtag/variant.h b/variants/nrf52840/rak_wismeshtag/variant.h index fa3e252ab..5b20e4d93 100644 --- a/variants/nrf52840/rak_wismeshtag/variant.h +++ b/variants/nrf52840/rak_wismeshtag/variant.h @@ -45,13 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted @@ -62,8 +59,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins diff --git a/variants/nrf52840/rak_wismeshtap/variant.cpp b/variants/nrf52840/rak_wismeshtap/variant.cpp index 5a3587982..73d2fac04 100644 --- a/variants/nrf52840/rak_wismeshtap/variant.cpp +++ b/variants/nrf52840/rak_wismeshtap/variant.cpp @@ -36,10 +36,24 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); +} + +void variant_shutdown() +{ + // GPIO restores input status, otherwise there will be leakage current + nrf_gpio_cfg_default(TFT_BL); + nrf_gpio_cfg_default(TFT_DC); + nrf_gpio_cfg_default(TFT_CS); + nrf_gpio_cfg_default(TFT_SCLK); + nrf_gpio_cfg_default(TFT_MOSI); + nrf_gpio_cfg_default(TFT_MISO); + nrf_gpio_cfg_default(SCREEN_TOUCH_INT); + nrf_gpio_cfg_default(WB_I2C1_SCL); + nrf_gpio_cfg_default(WB_I2C1_SDA); + + // nrf_gpio_cfg_default(WB_I2C2_SCL); + // nrf_gpio_cfg_default(WB_I2C2_SDA); } \ No newline at end of file diff --git a/variants/nrf52840/rak_wismeshtap/variant.h b/variants/nrf52840/rak_wismeshtap/variant.h index a7b9290a5..358117cd5 100644 --- a/variants/nrf52840/rak_wismeshtap/variant.h +++ b/variants/nrf52840/rak_wismeshtap/variant.h @@ -45,13 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted @@ -62,8 +59,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion such as the RAK14014 or RAK14015 TFT modules #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins @@ -271,8 +266,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER (1.73F) -#define HAS_RTC 1 - #define RAK_4631 1 #define AQ_SET_PIN 10 @@ -283,7 +276,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define RAK14014 // Tell it we have a RAK14014 #define USER_SETUP_LOADED 1 -#define DISABLE_ALL_LIBRARY_WARNINGS 1 #define ST7789_DRIVER 1 #define TFT_WIDTH 240 #define TFT_HEIGHT 320 @@ -311,7 +303,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define USE_POWERSAVE #define SLEEP_TIME 120 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define USE_VIRTUAL_KEYBOARD 1 /*---------------------------------------------------------------------------- * Arduino objects - C++ only diff --git a/variants/nrf52840/seeed_solar_node/variant.h b/variants/nrf52840/seeed_solar_node/variant.h index b2a1e6dff..69736a9e0 100644 --- a/variants/nrf52840/seeed_solar_node/variant.h +++ b/variants/nrf52840/seeed_solar_node/variant.h @@ -23,12 +23,8 @@ #define PIN_LED1 (12) // LED P1.15 #define PIN_LED2 (11) // -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 -// #define LED_PIN PIN_LED2 #define LED_STATE_ON 1 // State when LED is litted // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Button Configuration diff --git a/variants/nrf52840/seeed_wio_tracker_L1/variant.h b/variants/nrf52840/seeed_wio_tracker_L1/variant.h index b62b65161..a1ec2508a 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1/variant.h +++ b/variants/nrf52840/seeed_wio_tracker_L1/variant.h @@ -23,13 +23,9 @@ #define PIN_LED1 (11) // LED P1.15 #define PIN_LED2 (12) // -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 -// #define LED_PIN PIN_LED2 -#define LED_STATE_ON 1 // State when LED is litted +#define LED_STATE_ON 1 // State when LED is lit // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Button Configuration // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -158,8 +154,6 @@ static const uint8_t SCL = PIN_WIRE_SCL; // joystick // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -#define CANNED_MESSAGE_MODULE_ENABLE 1 - #define CANNED_MESSAGE_ADD_CONFIRMATION 1 // trackball diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h index ae20f3c36..495c4ace8 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h @@ -23,13 +23,9 @@ #define PIN_LED1 (11) // LED P1.15 #define PIN_LED2 (12) // -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 -// #define LED_PIN PIN_LED2 -#define LED_STATE_ON 1 // State when LED is litted +#define LED_STATE_ON 1 // State when LED is lit // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Button Configuration // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -177,7 +173,6 @@ static const uint8_t SCL = PIN_WIRE_SCL; #define TB_PRESS 29 #define TB_DIRECTION FALLING -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define CANNED_MESSAGE_ADD_CONFIRMATION 1 // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ diff --git a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h index 0844595da..0d599d313 100644 --- a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h +++ b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h @@ -69,11 +69,6 @@ static const uint8_t A5 = PIN_A5; #define PIN_LED2 LED_BLUE #define PIN_LED3 LED_RED -#define LED_BUILTIN LED_RED // LED_BUILTIN is used by framework-arduinoadafruitnrf52 to indicate flash writes - -#define LED_PWR LED_RED -#define USER_LED LED_BLUE - /* * Buttons */ diff --git a/variants/nrf52840/t-echo-lite/variant.h b/variants/nrf52840/t-echo-lite/variant.h index 0748f6d48..54c7bdfb5 100644 --- a/variants/nrf52840/t-echo-lite/variant.h +++ b/variants/nrf52840/t-echo-lite/variant.h @@ -51,9 +51,6 @@ extern "C" { #define LED_GREEN PIN_LED1 #define BLE_LED LED_BLUE -#define BLE_LED_INVERTED 1 -#define LED_BUILTIN LED_GREEN -#define LED_CONN LED_GREEN #define LED_STATE_ON 0 // State when LED is lit // Buttons @@ -171,6 +168,8 @@ static const uint8_t A0 = PIN_A0; #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER (2.0F) +#define SERIAL_PRINT_PORT 0 + // #define NO_EXT_GPIO 1 // PINs back side // Batt & solar connector left up corner diff --git a/variants/nrf52840/t-echo-plus/variant.h b/variants/nrf52840/t-echo-plus/variant.h index 226f6d6fe..4038ce6ef 100644 --- a/variants/nrf52840/t-echo-plus/variant.h +++ b/variants/nrf52840/t-echo-plus/variant.h @@ -25,9 +25,6 @@ extern "C" { #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED2 -#define LED_BUILTIN LED_BLUE -#define LED_CONN LED_GREEN - #define LED_STATE_ON 0 // Buttons / touch @@ -138,6 +135,8 @@ static const uint8_t A0 = PIN_A0; // Battery / ADC already defined above #define HAS_RTC 1 +#define SERIAL_PRINT_PORT 0 + #ifdef __cplusplus } #endif diff --git a/variants/nrf52840/t-echo/platformio.ini b/variants/nrf52840/t-echo/platformio.ini index 9a66890a7..4acd70b02 100644 --- a/variants/nrf52840/t-echo/platformio.ini +++ b/variants/nrf52840/t-echo/platformio.ini @@ -32,7 +32,7 @@ lib_deps = # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 ;upload_protocol = fs [env:t-echo-inkhud] @@ -53,4 +53,4 @@ lib_deps = ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 diff --git a/variants/nrf52840/t-echo/variant.cpp b/variants/nrf52840/t-echo/variant.cpp index cae079b74..cb64530f6 100644 --- a/variants/nrf52840/t-echo/variant.cpp +++ b/variants/nrf52840/t-echo/variant.cpp @@ -42,3 +42,13 @@ void initVariant() pinMode(PIN_LED3, OUTPUT); ledOff(PIN_LED3); } + +void variant_shutdown() +{ + // To power off the T-Echo, the display must be set + // as an input pin; otherwise, there will be leakage current. + pinMode(PIN_EINK_CS, INPUT); + pinMode(PIN_EINK_DC, INPUT); + pinMode(PIN_EINK_RES, INPUT); + pinMode(PIN_EINK_BUSY, INPUT); +} \ No newline at end of file diff --git a/variants/nrf52840/t-echo/variant.h b/variants/nrf52840/t-echo/variant.h index 9244fc6c3..f4644c6de 100644 --- a/variants/nrf52840/t-echo/variant.h +++ b/variants/nrf52840/t-echo/variant.h @@ -52,9 +52,6 @@ extern "C" { #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED2 -#define LED_BUILTIN LED_BLUE -#define LED_CONN PIN_GREEN - #define LED_STATE_ON 0 // State when LED is lit /* @@ -88,6 +85,7 @@ static const uint8_t A0 = PIN_A0; /* * Serial interfaces */ +#define SERIAL_PRINT_PORT 0 /* No longer populated on PCB @@ -163,7 +161,6 @@ External serial flash WP25R1635FZUIL0 // Controls power for all peripherals (eink + GPS + LoRa + Sensor) #define PIN_POWER_EN (0 + 12) -// #define PIN_POWER_EN1 (0 + 13) #define PIN_SPI1_MISO \ (32 + 7) // FIXME not really needed, but for now the SPI code requires something to be defined, pick an used GPIO @@ -191,7 +188,6 @@ External serial flash WP25R1635FZUIL0 // PCF8563 RTC Module #define PIN_RTC_INT (0 + 16) // Interrupt from the PCF8563 RTC #define PCF8563_RTC 0x51 -#define HAS_RTC 1 /* * SPI Interfaces diff --git a/variants/nrf52840/tracker-t1000-e/variant.h b/variants/nrf52840/tracker-t1000-e/variant.h index 5b6719e12..143b7d4ad 100644 --- a/variants/nrf52840/tracker-t1000-e/variant.h +++ b/variants/nrf52840/tracker-t1000-e/variant.h @@ -48,7 +48,6 @@ extern "C" { #define PIN_LED1 (0 + 24) // P0.24 #define LED_PIN PIN_LED1 -#define LED_BUILTIN -1 #define LED_BLUE -1 // Actually green #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/wio-sdk-wm1110/variant.h b/variants/nrf52840/wio-sdk-wm1110/variant.h index b6e5c79df..d802d20f6 100644 --- a/variants/nrf52840/wio-sdk-wm1110/variant.h +++ b/variants/nrf52840/wio-sdk-wm1110/variant.h @@ -58,8 +58,6 @@ extern "C" { #define PIN_LED1 (0 + 13) // P0.13 #define PIN_LED2 (0 + 14) // P0.14 -#define LED_BUILTIN PIN_LED1 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 // Actually red diff --git a/variants/nrf52840/wio-t1000-s/variant.h b/variants/nrf52840/wio-t1000-s/variant.h index 02f8a20b2..3b8103d85 100644 --- a/variants/nrf52840/wio-t1000-s/variant.h +++ b/variants/nrf52840/wio-t1000-s/variant.h @@ -48,7 +48,6 @@ extern "C" { #define PIN_LED1 (0 + 24) // P0.24 #define LED_PIN PIN_LED1 -#define LED_BUILTIN -1 #define LED_BLUE -1 // Actually green #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/wio-tracker-wm1110/variant.cpp b/variants/nrf52840/wio-tracker-wm1110/variant.cpp index 5a3587982..0d0856773 100644 --- a/variants/nrf52840/wio-tracker-wm1110/variant.cpp +++ b/variants/nrf52840/wio-tracker-wm1110/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/wio-tracker-wm1110/variant.h b/variants/nrf52840/wio-tracker-wm1110/variant.h index 807ca8dbb..647bd47d8 100644 --- a/variants/nrf52840/wio-tracker-wm1110/variant.h +++ b/variants/nrf52840/wio-tracker-wm1110/variant.h @@ -51,13 +51,8 @@ extern "C" { #define PIN_WIRE_SDA (0 + 5) // P0.05 #define PIN_WIRE_SCL (0 + 4) // P0.04 -#define PIN_LED1 (0 + 6) // P0.06 -#define PIN_LED2 (PINS_COUNT) // P0.14 - -#define LED_BUILTIN PIN_LED1 - +#define PIN_LED1 (0 + 6) // P0.06 #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 #define LED_STATE_ON 0 diff --git a/variants/rp2040/challenger_2040_lora/pins_arduino.h b/variants/rp2040/challenger_2040_lora/pins_arduino.h index ac472c07e..b27cc0297 100644 --- a/variants/rp2040/challenger_2040_lora/pins_arduino.h +++ b/variants/rp2040/challenger_2040_lora/pins_arduino.h @@ -7,7 +7,7 @@ #define ADC_RESOLUTION (12u) // LEDs -#define PIN_LED (24u) +#define LED_PIN (24u) // Serial #define PIN_SERIAL1_TX (16u) @@ -45,8 +45,6 @@ #define SPI_HOWMANY (2u) #define WIRE_HOWMANY (1u) -#define LED_BUILTIN PIN_LED - static const uint8_t D0 = (16u); static const uint8_t D1 = (17u); static const uint8_t D2 = (20u); diff --git a/variants/rp2040/challenger_2040_lora/variant.h b/variants/rp2040/challenger_2040_lora/variant.h index 552f90720..f5126cfff 100644 --- a/variants/rp2040/challenger_2040_lora/variant.h +++ b/variants/rp2040/challenger_2040_lora/variant.h @@ -5,8 +5,6 @@ #define EXT_NOTIFY_OUT 0xFFFFFFFF #define BUTTON_PIN 0xFFFFFFFF -#define LED_PIN PIN_LED - #define USE_RF95 // RFM95/SX127x #undef LORA_SCK diff --git a/variants/rp2040/rak11310/pins_arduino.h b/variants/rp2040/rak11310/pins_arduino.h index 0e2808b19..59290bbdb 100644 --- a/variants/rp2040/rak11310/pins_arduino.h +++ b/variants/rp2040/rak11310/pins_arduino.h @@ -23,10 +23,8 @@ static const uint8_t A2 = PIN_A2; static const uint8_t A3 = PIN_A3; // LEDs -#define PIN_LED (23u) -#define PIN_LED1 PIN_LED -#define PIN_LED2 (24u) -#define LED_BUILTIN PIN_LED +#define PIN_LED1 (23u) +#define LED_NOTIFICATION (24u) #define ADC_RESOLUTION 12 diff --git a/variants/rp2040/rak11310/variant.h b/variants/rp2040/rak11310/variant.h index 2400d56a7..cf49ff491 100644 --- a/variants/rp2040/rak11310/variant.h +++ b/variants/rp2040/rak11310/variant.h @@ -10,8 +10,7 @@ #define I2C_SDA1 2 #define I2C_SCL1 3 -#define LED_CONN PIN_LED2 -#define LED_PIN LED_BUILTIN +#define LED_PIN PIN_LED1 #define ledOff(pin) pinMode(pin, INPUT) #define BUTTON_PIN 9 diff --git a/variants/rp2040/rpipicow/platformio.ini b/variants/rp2040/rpipicow/platformio.ini index 99e02a1aa..9b4b29a5b 100644 --- a/variants/rp2040/rpipicow/platformio.ini +++ b/variants/rp2040/rpipicow/platformio.ini @@ -22,6 +22,7 @@ build_flags = -D HW_SPI1_DEVICE -D HAS_UDP_MULTICAST=1 -fexceptions # for exception handling in MQTT + -ULED_BUILTIN build_src_filter = ${rp2040_base.build_src_filter} + lib_deps = ${rp2040_base.lib_deps} diff --git a/variants/rp2040/rpipicow/variant.h b/variants/rp2040/rpipicow/variant.h index 24da8f932..fe94e615d 100644 --- a/variants/rp2040/rpipicow/variant.h +++ b/variants/rp2040/rpipicow/variant.h @@ -19,7 +19,7 @@ #define EXT_NOTIFY_OUT 22 #define BUTTON_PIN 17 -#define LED_PIN LED_BUILTIN +#define LED_PIN PIN_LED #define BATTERY_PIN 26 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) diff --git a/variants/rp2040/senselora_rp2040/pins_arduino.h b/variants/rp2040/senselora_rp2040/pins_arduino.h index bb0ee637e..575839cbc 100644 --- a/variants/rp2040/senselora_rp2040/pins_arduino.h +++ b/variants/rp2040/senselora_rp2040/pins_arduino.h @@ -11,9 +11,7 @@ static const uint8_t A2 = PIN_A2; static const uint8_t A3 = PIN_A3; // LEDs -#define PIN_LED (23u) -#define PIN_LED1 PIN_LED -#define LED_BUILTIN PIN_LED +#define PIN_LED1 (23u) #define ADC_RESOLUTION 12 diff --git a/variants/rp2040/senselora_rp2040/variant.h b/variants/rp2040/senselora_rp2040/variant.h index cc90284b7..04e21e073 100644 --- a/variants/rp2040/senselora_rp2040/variant.h +++ b/variants/rp2040/senselora_rp2040/variant.h @@ -5,7 +5,7 @@ #define BUTTON_PIN 2 #define BUTTON_NEED_PULLUP -#define LED_PIN PIN_LED +#define LED_PIN PIN_LED1 #define ledOff(pin) pinMode(pin, INPUT) #undef BATTERY_PIN diff --git a/variants/stm32/CDEBYTE_E77-MBL/variant.h b/variants/stm32/CDEBYTE_E77-MBL/variant.h index e3d111a33..2cf355a61 100644 --- a/variants/stm32/CDEBYTE_E77-MBL/variant.h +++ b/variants/stm32/CDEBYTE_E77-MBL/variant.h @@ -19,5 +19,7 @@ Do not expect a working Meshtastic device with this target. // #define LED_PIN PB3 // LED2 #define LED_STATE_ON 1 +#define SERIAL_PRINT_PORT 1 + #define EBYTE_E77_MBL #endif diff --git a/variants/stm32/milesight_gs301/platformio.ini b/variants/stm32/milesight_gs301/platformio.ini new file mode 100644 index 000000000..73b9cf7ea --- /dev/null +++ b/variants/stm32/milesight_gs301/platformio.ini @@ -0,0 +1,24 @@ +; Milesight GS301 Bathroom Odor Detector +; https://www.milesight.com/iot/product/lorawan-sensor/gs301 +[env:milesight_gs301] +extends = stm32_base +board = wiscore_rak3172 ; Convenient choice as the same USART is used for programming/debug +board_level = extra +board_upload.maximum_size = 233472 ; reserve the last 28KB for filesystem +build_flags = + ${stm32_base.build_flags} + -Ivariants/stm32/milesight_gs301 + -DPRIVATE_HW + -DMESHTASTIC_EXCLUDE_GPS=1 + -DMESHTASTIC_EXCLUDE_I2C=1 # Analog ADuCM355 (unsupported) so no point building support for I2C in + -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1 + -DMESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR=1 +build_unflags = + -DDEBUG_MUTE # We have space for debug output until sensor support is added +build_src_filter = + ${stm32_base.build_src_filter} + +<../variants/stm32/milesight_gs301> +lib_deps = + ${stm32_base.lib_deps} + +upload_port = stlink diff --git a/variants/stm32/milesight_gs301/rfswitch.h b/variants/stm32/milesight_gs301/rfswitch.h new file mode 100644 index 000000000..d9f60038a --- /dev/null +++ b/variants/stm32/milesight_gs301/rfswitch.h @@ -0,0 +1,7 @@ +// Seems to use the same RF switch pins as RAK3172… getting Tx/Rx SNR +11dB with a nearby node +// PB8, PC13 + +static const RADIOLIB_PIN_TYPE rfswitch_pins[5] = {PB8, PC13, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; + +static const Module::RfSwitchMode_t rfswitch_table[4] = { + {STM32WLx::MODE_IDLE, {LOW, LOW}}, {STM32WLx::MODE_RX, {HIGH, LOW}}, {STM32WLx::MODE_TX_HP, {LOW, HIGH}}, END_OF_MODE_TABLE}; diff --git a/variants/stm32/milesight_gs301/variant.cpp b/variants/stm32/milesight_gs301/variant.cpp new file mode 100644 index 000000000..ef90d5a54 --- /dev/null +++ b/variants/stm32/milesight_gs301/variant.cpp @@ -0,0 +1,8 @@ +#include "variant.h" +#include "Arduino.h" + +void earlyInitVariant() +{ + pinMode(USER_LED, OUTPUT); + digitalWrite(USER_LED, HIGH ^ LED_STATE_ON); +} \ No newline at end of file diff --git a/variants/stm32/milesight_gs301/variant.h b/variants/stm32/milesight_gs301/variant.h new file mode 100644 index 000000000..e86e93fc4 --- /dev/null +++ b/variants/stm32/milesight_gs301/variant.h @@ -0,0 +1,41 @@ +#ifndef _VARIANT_MILESIGHT_GS301_ +#define _VARIANT_MILESIGHT_GS301_ + +#define USE_STM32WLx + +// I/O +#define LED_STATE_ON 1 +#define PIN_LED1 PA0 // Green LED +#define LED_PIN PIN_LED1 +#define PIN_LED2 PA0 // Red LED +#define USER_LED PIN_LED2 +#define BUTTON_PIN PC13 +#define BUTTON_ACTIVE_LOW true +#define BUTTON_ACTIVE_PULLUP false +#define PIN_BUZZER PA6 + +// EC Sense DGM10 Double Gas Module +// Analog ADuCM355 (unsupported); SHTC3 is connected to ADuCM355 and not directly accessible +#define PIN_WIRE_SDA PB7 +#define PIN_WIRE_SCL PB8 +// Commented out to keep sensor powered down due to lack of support +/* +#define VEXT_ENABLE PB12 // TI TPS61291DRV VSEL - set LOW before ENable for Vout = 3.3V +#define VEXT_ON_VALUE LOW +#define SENSOR_POWER_CTRL_PIN PB2 // TI TPS61291DRV EN pin +#define SENSOR_POWER_ON HIGH +#define HAS_SENSOR 1 +*/ + +#define ENABLE_HWSERIAL1 +#define PIN_SERIAL1_RX NC +#define PIN_SERIAL1_TX PB6 + +// LoRa +#define SX126X_DIO3_TCXO_VOLTAGE 3.0 + +// Required to avoid Serial1 conflicts due to board definition here: +// https://github.com/stm32duino/Arduino_Core_STM32/blob/main/variants/STM32WLxx/WL54CCU_WL55CCU_WLE4C(8-B-C)U_WLE5C(8-B-C)U/variant_RAK3172_MODULE.h +#define RAK3172 + +#endif diff --git a/variants/stm32/rak3172/variant.h b/variants/stm32/rak3172/variant.h index 30d2b57b4..b3f6cbcda 100644 --- a/variants/stm32/rak3172/variant.h +++ b/variants/stm32/rak3172/variant.h @@ -17,5 +17,6 @@ Do not expect a working Meshtastic device with this target. #define LED_STATE_ON 1 #define RAK3172 +#define SERIAL_PRINT_PORT 1 #endif diff --git a/variants/stm32/russell/platformio.ini b/variants/stm32/russell/platformio.ini new file mode 100644 index 000000000..0dd57a2c7 --- /dev/null +++ b/variants/stm32/russell/platformio.ini @@ -0,0 +1,21 @@ +; Russell is a board designed to mount on an ER34615/IFR32700 cell and go Up! on a balloon +; Hardware repository: https://github.com/Meshtastic-Malaysia/russell +; - RAK3172 STM32WLE5CCU6 MCU + integrated SX1262 LoRa +; - CDtop CD-PA1010D GPS +; - Bosch Sensortec BME280 sensor +; - Consonance CN3158 LiFePO4 solar charger +[env:russell] +extends = stm32_base +board = wiscore_rak3172 +board_level = extra +board_upload.maximum_size = 233472 ; reserve the last 28KB for filesystem +build_flags = + ${stm32_base.build_flags} + -Ivariants/stm32/russell + -DPRIVATE_HW +lib_deps = + ${stm32_base.lib_deps} + # renovate: datasource=custom.pio depName=Adafruit BME280 packageName=adafruit/library/Adafruit BME280 Library + adafruit/Adafruit BME280 Library@2.3.0 + +upload_port = stlink diff --git a/variants/stm32/russell/rfswitch.h b/variants/stm32/russell/rfswitch.h new file mode 100644 index 000000000..ec4829de6 --- /dev/null +++ b/variants/stm32/russell/rfswitch.h @@ -0,0 +1,7 @@ +// Pins from https://forum.rakwireless.com/t/rak3172-internal-schematic/4557/2 +// PB8, PC13 + +static const RADIOLIB_PIN_TYPE rfswitch_pins[5] = {PB8, PC13, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; + +static const Module::RfSwitchMode_t rfswitch_table[4] = { + {STM32WLx::MODE_IDLE, {LOW, LOW}}, {STM32WLx::MODE_RX, {HIGH, LOW}}, {STM32WLx::MODE_TX_HP, {LOW, HIGH}}, END_OF_MODE_TABLE}; diff --git a/variants/stm32/russell/variant.h b/variants/stm32/russell/variant.h new file mode 100644 index 000000000..796302d34 --- /dev/null +++ b/variants/stm32/russell/variant.h @@ -0,0 +1,41 @@ +#ifndef _VARIANT_RUSSELL_ +#define _VARIANT_RUSSELL_ + +#define USE_STM32WLx + +// I/O +#define LED_PIN PA0 // Red LED +#define LED_STATE_ON 1 +#define BUTTON_PIN PH3 // Shared with BOOT0 +#define BUTTON_NEED_PULLUP +// Charger IC charge/standby pins are open-drain with no hardware pull-up: +// Internal pull-up is needed on STM32 (TODO) +// #define EXT_CHRG_DETECT PA5 +// #define EXT_PWR_DETECT PA4 + +// Bosch Sensortec BME280 +#define HAS_SENSOR 1 + +// CDtop CD-PA1010D +#define ENABLE_HWSERIAL1 +#define PIN_SERIAL1_RX PB7 +#define PIN_SERIAL1_TX PB6 +#define HAS_GPS 1 +#define PIN_GPS_STANDBY PA15 +#define GPS_RX_PIN PB7 +#define GPS_TX_PIN PB6 + +// LoRa +/* + * RAK3172 (-20–85°C) -> No TCXO + * RAK3172-T (-40–85°C) -> 3.0V TCXO + * https://github.com/RAKWireless/RAK-STM32-RUI/blob/e5a28be8fab1a492bd9223dd425ca33a8a297d90/variants/WisDuo_RAK3172-T_Board/radio_conf.h#L91 + */ +#define TCXO_OPTIONAL +#define SX126X_DIO3_TCXO_VOLTAGE 3.0 + +// Required to avoid Serial1 conflicts due to board definition here: +// https://github.com/stm32duino/Arduino_Core_STM32/blob/main/variants/STM32WLxx/WL54CCU_WL55CCU_WLE4C(8-B-C)U_WLE5C(8-B-C)U/variant_RAK3172_MODULE.h +#define RAK3172 + +#endif diff --git a/variants/stm32/wio-e5/variant.h b/variants/stm32/wio-e5/variant.h index a312b31bd..2b20eb2a6 100644 --- a/variants/stm32/wio-e5/variant.h +++ b/variants/stm32/wio-e5/variant.h @@ -19,9 +19,4 @@ Do not expect a working Meshtastic device with this target. #define WIO_E5 -#if (defined(LED_BUILTIN) && LED_BUILTIN == PNUM_NOT_DEFINED) -#undef LED_BUILTIN -#define LED_BUILTIN (LED_PIN) -#endif - #endif diff --git a/version.properties b/version.properties index 0a028eff0..62145da14 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 18 +build = 19