Compare commits

...

60 Commits

Author SHA1 Message Date
Jonathan Bennett
0f0e704f29 Merge branch 'develop' into esp32-h2 2025-12-10 11:11:57 -06:00
Jason P
2032ff1c32 Create new screen colors for BaseUI (#8921)
* Create new colors for BaseUI

* Update Ice color
2025-12-10 11:09:37 -06:00
Alex Samorukov
5910cc2e26 Use PSRAM to reduce heap usage percentage on ESP32 with PSRAM (#8891)
* Use PSRAM for malloc > 256bytes to get more heap memory

* Use dynamic allocator on boards with PSRAM to free more heap

* Apply suggestion from @Copilot

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

* Move heap_caps_malloc_extmem_enable() to the top of the init

* Update src/main.cpp

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

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-10 06:23:23 -06:00
Jonathan Bennett
23aaee737a Merge branch 'develop' into esp32-h2 2025-12-09 20:57:47 -06:00
Austin
aa72e397f2 PIO: Fix closedcube lib reference (#8920)
Fixes ClosedCube reinstalling on every build
2025-12-09 16:40:37 -06:00
Austin
c55bea8460 ARCtastic (#8904) -- Do It Live!
Actions Runner Controller

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2025-12-09 15:11:07 -06:00
Austin
aa605fc4a2 Actions: Fix release manifest formating (#8918) 2025-12-09 14:27:13 -06:00
Igor Danilov
d75680a2dd Fix #8915 [Bug]: Exception Decoder does not recognize the backtrace (#8917) 2025-12-09 12:24:41 -06:00
Ben Meadors
decd58cd5c Merge pull request #8913 from meshtastic/revert-8858-nrf52-power-saving-1
Revert "Cut NRF52 bluetooth power usage by 300% - testers needed!"
2025-12-09 08:02:29 -06:00
Ben Meadors
e691bd9732 Revert "Cut NRF52 bluetooth power usage by 300% - testers needed! (#8858)"
This reverts commit ae8d3fbb3d.
2025-12-09 08:02:04 -06:00
Ben Meadors
6bad81f8dd Merge pull request #8911 from vidplace7/fix-chmod
Fix apply device-install permissions
2025-12-09 06:50:19 -06:00
Austin Lane
69b9977fc1 Fix apply device-install permissions
device-install.sh doesn't exist for non-esp32 targets
2025-12-09 07:48:30 -05:00
Ben Meadors
8e63dcf59a Merge branch 'master' into develop 2025-12-09 05:59:15 -06:00
Lewis He
042543eb25 Fixed the issue where T-Echo did not completely shut down peripherals upon power-off. (#8524)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-12-09 05:39:27 -06:00
phaseloop
ae8d3fbb3d Cut NRF52 bluetooth power usage by 300% - testers needed! (#8858)
* Improve NRF52 bluetooth power efficiency

* test T114 bad LFXO

* T1000 test

* force BLE param negotiation

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-12-08 19:59:14 -06:00
Austin
928739e0fb Renovate: fix malformed comment for wollewald/BH1750_WE (#8767) 2025-12-08 19:31:28 -06:00
Ben Meadors
8be7915fc7 Fix wm111111110 2025-12-08 19:19:10 -06:00
Ben Meadors
c052963395 Guard 2M PHY mode for NimBLE (#8890)
* Guard 2M PHY mode for NimBLE

* Update src/nimble/NimbleBluetooth.cpp

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

* Apply suggestion from @Copilot

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

* Apply suggestion from @Copilot

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

* Another #endif snuck in there

* Move endif

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-08 18:48:28 -06:00
Jonathan Bennett
65c418d4e1 Update protobuf name of FRIED_CHICKEN (#8903) 2025-12-09 11:13:59 +11:00
Jonathan Bennett
c3a69a2742 Fix backwards buttons on Thinknode-M1 (#8901) 2025-12-08 17:58:23 -06:00
Austin
66ff1536f3 Meshtastic build manifest (#8248) 2025-12-08 17:21:23 -06:00
Jonathan Bennett
42c46cad41 Merge branch 'develop' into esp32-h2 2025-12-08 16:52:00 -06:00
Jonathan Bennett
f0b72a4b4b Move the esp32-h2 .ini 2025-12-08 16:49:01 -06:00
simon-muzi
5671e9d96f Improved R1 Neo & muzi-base buzzer beeps for GPS on/off (#8870)
Matched the resonant frequency of the hardware buzzer to maximize volume for the turn on beep.

Further distinguished ON beep from OFF beep, making it easier for users to understand the state change.
2025-12-08 13:50:05 -06:00
Manuel
bd4bcb94f0 tryfix eink parameters (#8898) 2025-12-08 13:14:24 -06:00
Igor Danilov
4b2f241478 Disable vibration if needed (#8895) 2025-12-08 06:03:20 -06:00
Wilson
eb087849c0 OnScreenKeyboard Improvement with Joystick and UpDown Encoder (#8379)
* Add mesh/Default.h include.

* Reflacter OnScreenKeyBoard Module, do not interrupt keyboard when new message comes.

* feat: Add long press scrolling for Joystick and upDown Encoder on baseUI frames and menus.

* refactor: Clean up code formatting and improve readability in Screen and OnScreenKeyboardModule

* Fix navigation on UpDownEncoder, default was RotaryEncoder while bringing the T_LORA_PAGER

* Update src/graphics/draw/MenuHandler.cpp

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

* Update src/modules/OnScreenKeyboardModule.h

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

* Update src/graphics/draw/NotificationRenderer.cpp

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

* Optimize the detection logic for repeated events of the arrow keys.

* Fixed parameter names in the OnScreenKeyboardModule::start

* Trunk fix

* Reflator OnScreenKeyboard Input checking, make it simple

* Simplify long press logic in OnScreenKeyboardModule.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-08 05:40:30 -06:00
Igor Danilov
94aedff6ae Resolve #8887 (T-LoRaPager Vibration on New Message Delivery) (#8888)
* Resolve #8887

* Update src/modules/ExternalNotificationModule.cpp

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

* Update src/modules/ExternalNotificationModule.cpp

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

* use canBuzz method

* trunk fmt

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-07 20:29:18 -06:00
Igor Danilov
2ae391197f Fix #8883 (lora-Pager fter playing the notification, voltage does not disappear from the speaker) (#8884)
* Fix #8883

* Fix crash when delete not inicialized rtttlFile
2025-12-07 15:50:53 -06:00
Clive Blackledge
2a17c3b5d4 Change ARDUINO_USB_MODE from 0 to 1 in the board definition. This switches to the ESP32-S3's Hardware CDC and JTAG mode, which properly handles the reset signals for automatic reboot after firmware updates. (#8881) 2025-12-06 18:56:56 -06:00
Jonathan Bennett
24ca4602b1 roughed in support for esp32-h2 via Waveshare dev board 2025-12-06 12:42:05 -06:00
Tom
8060134224 promicro doesn't need these. (#8873) 2025-12-06 13:25:57 +11:00
Donato
2f4eb25b2f Optimization flags for all NRF52 targets to reduce code size (#8854)
* changes of variants/nrf52840/nrf52.ini and variants/nrf52840/rak4631/platformio.ini

* try for nrf52 size reduction, faketec exclude unused radios and meshlink refactor

* can't exclude LR11X0 as in schematic there's option for LR1121

* remove -Map flag and -Wl

* removed spaces causing error

---------

Co-authored-by: macvenez <macvenez@gmail.com>
2025-12-04 15:32:42 -06:00
Tom
aa85fbbcc4 Promicro documentation update (#8864)
* Delete variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts.pdf

remove old file

* Add updated schematic

* Update GPS TX and RX pin definitions after swap

* Update GPS pin definitions and schematic link

Updated the schematic link to reflect GPS pin definition changes.
2025-12-04 13:35:50 -06:00
Benjamin Faershtein
61e41a8beb Don't scale up the frequency of telemetry sending (#8664) 2025-12-02 13:59:05 -06:00
Jonathan Bennett
525c048354 Move device specific OCV curves to their respective device.h (#8834) 2025-12-02 05:46:24 -06:00
Jonathan Bennett
41cbd77db3 Move everything from /arch to /variant (#8831) 2025-12-02 08:56:55 +01:00
github-actions[bot]
f3e38a425f Automated version bumps (#8786)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-12-01 19:31:58 -06:00
rbomze
a11152e545 Commented out the definition of BATTERY_LPCOMP_INPUT in the Helltec T114 variant, due to power leakage of 2.9mA in off state. See bug #8801 (#8800)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-12-01 19:21:49 -06:00
Ben Meadors
859ae4d3d2 Plain RAK4631 should not compile EInk and TFT display code (#8811)
* Plain RAK4631 should not compile EInk and TFT display code

* Add USE_TFTDISPLAY to variant files.

* Derp

* Undo the platformio.ini changes to heltec_v4

* Drop unneeded src_filter lines

---------

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
Co-authored-by: Jason P <applewiz@mac.com>
2025-12-01 19:19:50 -06:00
Ben Meadors
03600b1252 Merge branch 'master' into develop 2025-12-01 15:40:16 -06:00
Chloe Bethel
ee6c9101c7 Make GPS_TX_PIN the serial TX and GPS_RX_PIN the serial RX for all NRF variants (#8772) 2025-11-30 21:57:25 -06:00
HarukiToreda
34f8300288 Initial Chatter 2.0 fix for baseUI (#8615)
* Initial Chatter 2.0 fix for baseUI

* trunk fix

---------

Co-authored-by: Jason P <applewiz@mac.com>
2025-11-30 21:32:51 -06:00
Riker
09bbfce625 Enabled MQTT and WEBSERVER by default (#8679)
Signed-off-by: kur1k0 <zhuzirun@m5stack.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2025-11-30 20:27:45 -06:00
Jonathan Bennett
5b1b420cad Add initial support for Hackaday Communicator (#8771)
* Add initial support for Hackaday Communicator

* Fork it!

* Trunk

* Remove unused elements from the HackadayCommunicatorKeyboard

* Don't divide by zero.
2025-11-30 17:21:10 -06:00
Jonathan Bennett
8899487c2f Modify power saving condition for WiFi (#8815)
Update preprocessor directive to require both HAS_WIFI and MESHTASTIC_EXCLUDE_WIFI conditions.
2025-11-30 17:18:03 -06:00
Jason P
430d55e5e8 Add WiFi Toggle to System frame to re-enable (#8802)
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2025-11-30 17:17:00 -06:00
Jonathan Bennett
5ef3ff7116 rework screen.cpp ifdefs (#8816) 2025-11-30 15:33:29 -06:00
Jason P
0081cec207 Fix ifdef statement after ST7796 merge to resolve screen color issues (#8796) 2025-11-28 20:24:39 -06:00
Jonathan Bennett
94db3506bd Add LOG_POWERFSM and LOG_INPUT debug macros (#8791) 2025-11-28 19:58:52 -06:00
Jonathan Bennett
2f0fe4e5da Use the dedicated isVbusIn() function for detecting USB plug 2025-11-28 16:42:14 -06:00
Jonathan Bennett
de26dfe468 Remove screen activation in powerExit function (#8779)
This seems to be a potential source of unintended screen wakes.
2025-11-28 05:23:07 -06:00
Ben Meadors
1c43d71067 Merge branch 'master' into develop 2025-11-27 06:39:26 -06:00
Jason P
c3a7ad2865 More GPS pin flips for devices (#8760) 2025-11-26 13:53:26 -06:00
Quency-D
06dac12a73 Swap the GPS serial port pins. (#8756)
* Swap the GPS serial port pins.

* Trunk fixes

---------

Co-authored-by: Jason P <applewiz@mac.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-11-26 11:10:21 -06:00
Ben Meadors
d60b263a00 Merge branch 'master' into develop 2025-11-26 06:24:41 -06:00
Tom
486fa74549 Actions: Remove native from build_one (#8685)
* Remove native from the build, and remove the required permissions

* Delete .github/workflows/build_one_arch.yml

Its borken and not really needed. one_target is the goal.
2025-11-25 16:18:55 -05:00
Jason P
faa6af74af Swapping GPS pins for GPS TX/RX (#8751) 2025-11-25 13:55:28 -06:00
Ben Meadors
2baa9ccbe0 Merge pull request #8741 from meshtastic/master
Master to develop
2025-11-25 04:52:54 -06:00
Chloe Bethel
5d7da6868e Support overriding GPS serial pins on all architectures (#8486) 2025-11-24 09:40:27 +08:00
153 changed files with 5713 additions and 3920 deletions

View File

@@ -51,7 +51,7 @@ for f in .clusterfuzzlite/*_fuzzer.cpp; do
fuzzer=$(basename "$f" .cpp)
cp -f "$f" src/fuzzer.cpp
pio run -vvv --environment "$PIO_ENV"
program="$PLATFORMIO_WORKSPACE_DIR/build/$PIO_ENV/program"
program="$PLATFORMIO_WORKSPACE_DIR/build/$PIO_ENV/meshtasticd"
cp "$program" "$OUT/$fuzzer"
# Copy shared libraries used by the fuzzer.

View File

@@ -2,4 +2,5 @@
self-hosted-runner:
# Labels of self-hosted runner in array of strings.
labels:
- arctastic
- test-runner

View File

@@ -102,7 +102,7 @@ runs:
- name: Store binaries as an artifact
uses: actions/upload-artifact@v5
with:
name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.long }}
overwrite: true
path: |
${{ inputs.artifact-paths }}

View File

@@ -18,7 +18,8 @@ permissions: read-all
jobs:
pio-build:
name: build-${{ inputs.platform }}
runs-on: ubuntu-24.04
# Use 'arctastic' self-hosted runner pool when building in the main repo
runs-on: ${{ github.repository_owner == 'meshtastic' && 'arctastic' || 'ubuntu-latest' }}
outputs:
artifact-id: ${{ steps.upload.outputs.artifact-id }}
steps:
@@ -55,15 +56,29 @@ jobs:
ota_firmware_source: ${{ steps.ota_dir.outputs.src || '' }}
ota_firmware_target: ${{ steps.ota_dir.outputs.tgt || '' }}
- name: Echo manifest from release/firmware-*.mt.json to job summary
if: ${{ always() }}
env:
PIO_ENV: ${{ inputs.pio_env }}
run: |
echo "## Manifest: \`$PIO_ENV\`" >> $GITHUB_STEP_SUMMARY
echo '```json' >> $GITHUB_STEP_SUMMARY
cat release/firmware-*.mt.json >> $GITHUB_STEP_SUMMARY
echo '' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
- name: Store binaries as an artifact
uses: actions/upload-artifact@v5
id: upload
with:
name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}.zip
name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}
overwrite: true
path: |
release/*.mt.json
release/*.bin
release/*.elf
release/*.uf2
release/*.hex
release/*-ota.zip
release/*.zip
release/device-*.sh
release/device-*.bat

View File

@@ -1,176 +0,0 @@
name: Build One Arch
on:
workflow_dispatch:
inputs:
# trunk-ignore(checkov/CKV_GHA_7)
arch:
type: choice
options:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
- native
permissions: read-all
env:
INPUT_ARCH: ${{ github.event.inputs.arch }}
jobs:
setup:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: 3.x
cache: pip
- run: pip install -U platformio
- name: Generate matrix
id: jsonStep
run: |
TARGETS=$(./bin/generate_ci_matrix.py $INPUT_ARCH --level extra)
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
echo "selected_arch=$TARGETS" >> $GITHUB_OUTPUT
outputs:
selected_arch: ${{ steps.jsonStep.outputs.selected_arch }}
version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Get release version string
run: |
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT
id: version
env:
BUILD_LOCATION: local
outputs:
long: ${{ steps.version.outputs.long }}
deb: ${{ steps.version.outputs.deb }}
build:
if: ${{ github.event_name != 'workflow_dispatch' }}
needs: [setup, version]
strategy:
fail-fast: false
matrix:
build: ${{ fromJson(needs.setup.outputs.selected_arch) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.build.board }}
platform: ${{ matrix.build.arch }}
build-debian-src:
if: ${{ github.repository == 'meshtastic/firmware' && github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
uses: ./.github/workflows/build_debian_src.yml
with:
series: UNRELEASED
build_location: local
secrets: inherit
package-pio-deps-native-tft:
if: ${{ inputs.arch == 'native' }}
uses: ./.github/workflows/package_pio_deps.yml
with:
pio_env: native-tft
secrets: inherit
test-native:
if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' }}
uses: ./.github/workflows/test_native.yml
gather-artifacts:
permissions:
contents: write
pull-requests: write
strategy:
fail-fast: false
matrix:
arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
runs-on: ubuntu-latest
needs: [version, build]
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- uses: actions/download-artifact@v6
with:
path: ./
pattern: firmware-${{inputs.arch}}-*
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
- name: Move files up
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
- name: Repackage in single firmware zip
uses: actions/upload-artifact@v5
with:
name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
overwrite: true
path: |
./firmware-*.bin
./firmware-*.uf2
./firmware-*.hex
./firmware-*-ota.zip
./device-*.sh
./device-*.bat
./littlefs-*.bin
./bleota*bin
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30
- uses: actions/download-artifact@v6
with:
name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./output
# For diagnostics
- name: Show artifacts
run: ls -lR
- name: Device scripts permissions
run: |
chmod +x ./output/device-install.sh
chmod +x ./output/device-update.sh
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output
- name: Repackage in single elfs zip
uses: actions/upload-artifact@v5
with:
name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
overwrite: true
path: ./*.elf
retention-days: 30
- uses: scruplelesswizard/comment-artifact@main
if: ${{ github.event_name == 'pull_request' }}
with:
name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
description: "Download firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -15,7 +15,6 @@ on:
- rp2040
- rp2350
- stm32
- native
target:
type: string
required: false
@@ -42,7 +41,6 @@ jobs:
- rp2040
- rp2350
- stm32
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
@@ -60,7 +58,7 @@ jobs:
echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY
echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY
echo "Targets:" >> $GITHUB_STEP_SUMMARY
echo $TARGETS >> $GITHUB_STEP_SUMMARY
echo $TARGETS | jq -r 'sort_by(.board) |.[] | "- " + .board' >> $GITHUB_STEP_SUMMARY
version:
if: ${{ inputs.target != '' }}
@@ -87,25 +85,6 @@ jobs:
pio_env: ${{ inputs.target }}
platform: ${{ inputs.arch }}
build-debian-src:
if: ${{ github.repository == 'meshtastic/firmware' && inputs.arch == 'native' }}
uses: ./.github/workflows/build_debian_src.yml
with:
series: UNRELEASED
build_location: local
secrets: inherit
package-pio-deps-native-tft:
if: ${{ inputs.arch == 'native' }}
uses: ./.github/workflows/package_pio_deps.yml
with:
pio_env: native-tft
secrets: inherit
test-native:
if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' && inputs.target != '' }}
uses: ./.github/workflows/test_native.yml
gather-artifacts:
permissions:
contents: write
@@ -140,7 +119,7 @@ jobs:
./firmware-*.bin
./firmware-*.uf2
./firmware-*.hex
./firmware-*-ota.zip
./firmware-*.zip
./device-*.sh
./device-*.bat
./littlefs-*.bin
@@ -160,8 +139,8 @@ jobs:
- name: Device scripts permissions
run: |
chmod +x ./output/device-install.sh
chmod +x ./output/device-update.sh
chmod +x ./output/device-install.sh || true
chmod +x ./output/device-update.sh || true
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output

View File

@@ -177,19 +177,17 @@ jobs:
- name: Display structure of downloaded files
run: ls -R
- name: Move files up
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
- name: Repackage in single firmware zip
uses: actions/upload-artifact@v5
with:
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true
path: |
./firmware-*.mt.json
./firmware-*.bin
./firmware-*.uf2
./firmware-*.hex
./firmware-*-ota.zip
./firmware-*.zip
./device-*.sh
./device-*.bat
./littlefs-*.bin
@@ -209,8 +207,8 @@ jobs:
- name: Device scripts permissions
run: |
chmod +x ./output/device-install.sh
chmod +x ./output/device-update.sh
chmod +x ./output/device-install.sh || true
chmod +x ./output/device-update.sh || true
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
@@ -218,7 +216,7 @@ jobs:
- name: Repackage in single elfs zip
uses: actions/upload-artifact@v5
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true
path: ./*.elf
retention-days: 30
@@ -236,6 +234,7 @@ jobs:
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
needs:
- setup
- version
- gather-artifacts
- build-debian-src
@@ -244,11 +243,6 @@ jobs:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- name: Create release
uses: softprops/action-gh-release@v2
id: create_release
@@ -284,10 +278,25 @@ jobs:
- name: Display structure of downloaded files
run: ls -lR
- name: Add Linux sources to GtiHub Release
- name: Generate Release manifest
run: |
jq -n --arg ver "${{ needs.version.outputs.long }}" --argjson targets ${{ toJson(needs.setup.outputs.all) }} '{
"version": $ver,
"targets": $targets
}' > firmware-${{ needs.version.outputs.long }}.json
- name: Save Release manifest artifact
uses: actions/upload-artifact@v5
with:
name: manifest-${{ needs.version.outputs.long }}
overwrite: true
path: firmware-${{ needs.version.outputs.long }}.json
- name: Add sources to GitHub Release
# Only run when targeting master branch with workflow_dispatch
if: ${{ github.ref_name == 'master' }}
run: |
gh release upload v${{ needs.version.outputs.long }} ./firmware-${{ needs.version.outputs.long }}.json
gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
env:
@@ -329,15 +338,15 @@ jobs:
- name: Device scripts permissions
run: |
chmod +x ./output/device-install.sh
chmod +x ./output/device-update.sh
chmod +x ./output/device-install.sh || true
chmod +x ./output/device-update.sh || true
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- uses: actions/download-artifact@v6
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./elfs
@@ -373,12 +382,19 @@ jobs:
with:
python-version: 3.x
- uses: actions/download-artifact@v6
- name: Get firmware artifacts
uses: actions/download-artifact@v6
with:
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./publish
- name: Get manifest artifact
uses: actions/download-artifact@v6
with:
pattern: manifest-${{ needs.version.outputs.long }}
path: ./publish
- name: Publish firmware to meshtastic.github.io
uses: peaceiris/actions-gh-pages@v4
env:

View File

@@ -168,7 +168,7 @@ jobs:
./firmware-*.bin
./firmware-*.uf2
./firmware-*.hex
./firmware-*-ota.zip
./firmware-*.zip
./device-*.sh
./device-*.bat
./littlefs-*.bin
@@ -188,8 +188,8 @@ jobs:
- name: Device scripts permissions
run: |
chmod +x ./output/device-install.sh
chmod +x ./output/device-update.sh
chmod +x ./output/device-install.sh || true
chmod +x ./output/device-update.sh || true
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
@@ -197,7 +197,7 @@ jobs:
- name: Repackage in single elfs zip
uses: actions/upload-artifact@v5
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
overwrite: true
path: ./*.elf
retention-days: 30
@@ -223,11 +223,6 @@ jobs:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
- name: Create release
uses: softprops/action-gh-release@v2
id: create_release
@@ -308,15 +303,15 @@ jobs:
- name: Device scripts permissions
run: |
chmod +x ./output/device-install.sh
chmod +x ./output/device-update.sh
chmod +x ./output/device-install.sh || true
chmod +x ./output/device-update.sh || true
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
- uses: actions/download-artifact@v6
with:
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./elfs

View File

@@ -52,7 +52,7 @@ jobs:
if: needs.native-tests.result != 'skipped'
uses: actions/download-artifact@v6
with:
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
name: platformio-test-report-${{ steps.version.outputs.long }}
merge-multiple: true
- name: Parse test results and create detailed summary

View File

@@ -40,7 +40,7 @@ jobs:
- name: Integration test
run: |
.pio/build/coverage/program -s &
.pio/build/coverage/meshtasticd -s &
PID=$!
timeout 20 bash -c "until ls -al /proc/$PID/fd | grep socket; do sleep 1; done"
echo "Simulator started, launching python test..."
@@ -62,7 +62,7 @@ jobs:
uses: actions/upload-artifact@v5
if: always() # run this step even if previous step failed
with:
name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.long }}.zip
name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.long }}
overwrite: true
path: ./coverage_*.info
@@ -96,7 +96,7 @@ jobs:
if: always() # run this step even if previous step failed
uses: actions/upload-artifact@v5
with:
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
name: platformio-test-report-${{ steps.version.outputs.long }}
overwrite: true
path: ./testreport.xml
@@ -111,7 +111,7 @@ jobs:
uses: actions/upload-artifact@v5
if: always() # run this step even if previous step failed
with:
name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.long }}.zip
name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.long }}
overwrite: true
path: ./coverage_*.info
@@ -139,7 +139,7 @@ jobs:
- name: Download test artifacts
uses: actions/download-artifact@v6
with:
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
name: platformio-test-report-${{ steps.version.outputs.long }}
merge-multiple: true
- name: Test Report
@@ -152,7 +152,7 @@ jobs:
- name: Download coverage artifacts
uses: actions/download-artifact@v6
with:
pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}.zip
pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}
path: code-coverage-report
merge-multiple: true
@@ -165,5 +165,5 @@ jobs:
- name: Save Code Coverage Report
uses: actions/upload-artifact@v5
with:
name: code-coverage-report-${{ steps.version.outputs.long }}.zip
name: code-coverage-report-${{ steps.version.outputs.long }}
path: code-coverage-report

View File

@@ -22,7 +22,7 @@ jobs:
- name: Checkout code
uses: actions/checkout@v6
# - uses: actions/setup-python@v5
# - uses: actions/setup-python@v6
# with:
# python-version: '3.10'

View File

@@ -5,7 +5,8 @@ set -e
VERSION=`bin/buildinfo.py long`
SHORT_VERSION=`bin/buildinfo.py short`
OUTDIR=release/
BUILDDIR=.pio/build/$1
OUTDIR=release
rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true
@@ -14,7 +15,7 @@ rm -r $OUTDIR/* || true
platformio pkg install -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.*
rm -f $BUILDDIR/firmware*
# The shell vars the build tool expects to find
export APP_VERSION=$VERSION
@@ -22,16 +23,14 @@ export APP_VERSION=$VERSION
basename=firmware-$1-$VERSION
pio run --environment $1 # -v
SRCELF=.pio/build/$1/firmware.elf
cp $SRCELF $OUTDIR/$basename.elf
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
echo "Copying ESP32 bin file"
SRCBIN=.pio/build/$1/firmware.factory.bin
cp $SRCBIN $OUTDIR/$basename.bin
cp $BUILDDIR/$basename.factory.bin $OUTDIR/$basename.factory.bin
echo "Copying ESP32 update bin file"
SRCBIN=.pio/build/$1/firmware.bin
cp $SRCBIN $OUTDIR/$basename-update.bin
cp $BUILDDIR/$basename.bin $OUTDIR/$basename.bin
echo "Building Filesystem for ESP32 targets"
# If you want to build the webui, uncomment the following lines
@@ -40,7 +39,13 @@ echo "Building Filesystem for ESP32 targets"
# # Remove webserver files from the filesystem and rebuild
# ls -l data/static # Diagnostic list of files
# rm -rf data/static
pio run --environment $1 -t buildfs
cp .pio/build/$1/littlefs.bin $OUTDIR/littlefs-$1-$VERSION.bin
cp bin/device-install.* $OUTDIR
cp bin/device-update.* $OUTDIR
pio run --environment $1 -t buildfs --disable-auto-clean
cp $BUILDDIR/littlefs-$1-$VERSION.bin $OUTDIR/littlefs-$1-$VERSION.bin
cp bin/device-install.* $OUTDIR/
cp bin/device-update.* $OUTDIR/
# Generate the manifest file
echo "Generating Meshtastic manifest"
TIMEFORMAT="Generated manifest in %E seconds"
time pio run --environment $1 -t mtjson --silent --disable-auto-clean
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json

View File

@@ -17,15 +17,19 @@ VERSION=$(bin/buildinfo.py long)
SHORT_VERSION=$(bin/buildinfo.py short)
PIO_ENV=${1:-native}
OUTDIR=release/
BUILDDIR=.pio/build/$PIO_ENV
OUTDIR=release
rm -f $OUTDIR/firmware*
rm -f $OUTDIR/meshtasticd*
mkdir -p $OUTDIR/
rm -r $OUTDIR/* || true
basename=meshtasticd-$1-$VERSION
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
pio pkg install --environment "$PIO_ENV" || platformioFailed
pio run --environment "$PIO_ENV" || platformioFailed
cp ".pio/build/$PIO_ENV/program" "$OUTDIR/meshtasticd_linux_$(uname -m)"
cp bin/native-install.* $OUTDIR
cp "$BUILDDIR/meshtasticd" "$OUTDIR/meshtasticd_linux_$(uname -m)"
cp bin/native-install.* $OUTDIR/

View File

@@ -5,7 +5,8 @@ set -e
VERSION=$(bin/buildinfo.py long)
SHORT_VERSION=$(bin/buildinfo.py short)
OUTDIR=release/
BUILDDIR=.pio/build/$1
OUTDIR=release
rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true
@@ -14,7 +15,7 @@ rm -r $OUTDIR/* || true
platformio pkg install -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.*
rm -f $BUILDDIR/firmware*
# The shell vars the build tool expects to find
export APP_VERSION=$VERSION
@@ -22,32 +23,32 @@ export APP_VERSION=$VERSION
basename=firmware-$1-$VERSION
pio run --environment $1 # -v
SRCELF=.pio/build/$1/firmware.elf
cp $SRCELF $OUTDIR/$basename.elf
echo "Generating NRF52 dfu file"
DFUPKG=.pio/build/$1/firmware.zip
cp $DFUPKG $OUTDIR/$basename-ota.zip
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
echo "Generating NRF52 uf2 file"
SRCHEX=.pio/build/$1/firmware.hex
echo "Copying NRF52 dfu (OTA) file"
cp $BUILDDIR/$basename.zip $OUTDIR/$basename.zip
# if WM1110 target, merge hex with softdevice 7.3.0
echo "Copying NRF52 UF2 file"
cp $BUILDDIR/$basename.uf2 $OUTDIR/$basename.uf2
cp bin/*.uf2 $OUTDIR/
SRCHEX=$BUILDDIR/$basename.hex
# if WM1110 target, copy the merged.hex
if (echo $1 | grep -q "wio-sdk-wm1110"); then
echo "Merging with softdevice"
bin/mergehex -m bin/s140_nrf52_7.3.0_softdevice.hex $SRCHEX -o .pio/build/$1/$basename.hex
SRCHEX=.pio/build/$1/$basename.hex
bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840
cp $SRCHEX $OUTDIR
cp bin/*.uf2 $OUTDIR
else
bin/uf2conv.py $SRCHEX -c -o $OUTDIR/$basename.uf2 -f 0xADA52840
cp bin/device-install.* $OUTDIR
cp bin/device-update.* $OUTDIR
cp bin/*.uf2 $OUTDIR
echo "Copying .merged.hex file"
SRCHEX=$BUILDDIR/$basename.merged.hex
cp $SRCHEX $OUTDIR/
fi
if (echo $1 | grep -q "rak4631"); then
echo "Copying hex file"
cp .pio/build/$1/firmware.hex $OUTDIR/$basename.hex
fi
echo "Copying .hex file"
cp $SRCHEX $OUTDIR/
fi
# Generate the manifest file
echo "Generating Meshtastic manifest"
TIMEFORMAT="Generated manifest in %E seconds"
time pio run --environment $1 -t mtjson --silent --disable-auto-clean
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json

View File

@@ -5,7 +5,8 @@ set -e
VERSION=`bin/buildinfo.py long`
SHORT_VERSION=`bin/buildinfo.py short`
OUTDIR=release/
BUILDDIR=.pio/build/$1
OUTDIR=release
rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true
@@ -14,7 +15,7 @@ rm -r $OUTDIR/* || true
platformio pkg install -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.*
rm -f $BUILDDIR/firmware*
# The shell vars the build tool expects to find
export APP_VERSION=$VERSION
@@ -22,12 +23,14 @@ export APP_VERSION=$VERSION
basename=firmware-$1-$VERSION
pio run --environment $1 # -v
SRCELF=.pio/build/$1/firmware.elf
cp $SRCELF $OUTDIR/$basename.elf
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
echo "Copying uf2 file"
SRCBIN=.pio/build/$1/firmware.uf2
cp $SRCBIN $OUTDIR/$basename.uf2
cp $BUILDDIR/$basename.uf2 $OUTDIR/$basename.uf2
cp bin/device-install.* $OUTDIR
cp bin/device-update.* $OUTDIR
# Generate the manifest file
echo "Generating Meshtastic manifest"
TIMEFORMAT="Generated manifest in %E seconds"
time pio run --environment $1 -t mtjson --silent --disable-auto-clean
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json

View File

@@ -5,7 +5,8 @@ set -e
VERSION=$(bin/buildinfo.py long)
SHORT_VERSION=$(bin/buildinfo.py short)
OUTDIR=release/
BUILDDIR=.pio/build/$1
OUTDIR=release
rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true
@@ -14,7 +15,7 @@ rm -r $OUTDIR/* || true
platformio pkg install -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.*
rm -f $BUILDDIR/firmware*
# The shell vars the build tool expects to find
export APP_VERSION=$VERSION
@@ -22,8 +23,14 @@ export APP_VERSION=$VERSION
basename=firmware-$1-$VERSION
pio run --environment $1 # -v
SRCELF=.pio/build/$1/firmware.elf
cp $SRCELF $OUTDIR/$basename.elf
SRCBIN=.pio/build/$1/firmware.bin
cp $SRCBIN $OUTDIR/$basename.bin
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
echo "Copying STM32 bin file"
cp $BUILDDIR/$basename.bin $OUTDIR/$basename.bin
# Generate the manifest file
echo "Generating Meshtastic manifest"
TIMEFORMAT="Generated manifest in %E seconds"
time pio run --environment $1 -t mtjson --silent --disable-auto-clean
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json

View File

@@ -5,22 +5,14 @@ TITLE Meshtastic device-install
SET "SCRIPT_NAME=%~nx0"
SET "DEBUG=0"
SET "PYTHON="
SET "TFT_BUILD=0"
SET "BIGDB8=0"
SET "MUIDB8=0"
SET "BIGDB16=0"
SET "ESPTOOL_BAUD=115200"
SET "ESPTOOL_CMD="
SET "LOGCOUNTER=0"
SET "BPS_RESET=0"
@REM FIXME: Determine mcu from PlatformIO variant, this is unmaintainable.
SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone t-eth-elite tlora-pager mesh-tab dreamcatcher ESP32-S3-Pico seeed-sensecap-indicator heltec_capsule_sensor_v3 vision-master icarus tracksenger elecrow-adv heltec-v4"
SET "C3=esp32c3"
@REM FIXME: Determine flash size from PlatformIO variant, this is unmaintainable.
SET "BIGDB_8MB=crowpanel-esp32s3 heltec_capsule_sensor_v3 heltec-v3 heltec-vision-master-e213 heltec-vision-master-e290 heltec-vision-master-t190 heltec-wireless-paper heltec-wireless-tracker heltec-wsl-v3 icarus seeed-xiao-s3 tbeam-s3-core tracksenger"
SET "MUIDB_8MB=picomputer-s3 unphone seeed-sensecap-indicator"
SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite tlora-pager t-watch-s3 elecrow-adv heltec-v4"
@REM Default offsets.
@REM https://github.com/meshtastic/web-flasher/blob/main/stores/firmwareStore.ts#L202
SET "OTA_OFFSET=0x260000"
SET "SPIFFS_OFFSET=0x300000"
GOTO getopts
:help
@@ -29,7 +21,7 @@ ECHO.
ECHO Usage: %SCRIPT_NAME% -f filename [-p PORT] [-P python] [--1200bps-reset]
ECHO.
ECHO Options:
ECHO -f filename The firmware .bin file to flash. Custom to your device type and region. (required)
ECHO -f filename The firmware .factory.bin file to flash. Custom to your device type and region. (required)
ECHO The file must be located in this current directory.
ECHO -p PORT Set the environment variable for ESPTOOL_PORT.
ECHO If not set, ESPTOOL iterates all ports (Dangerous).
@@ -40,12 +32,12 @@ ECHO --1200bps-reset Attempt to place the device in correct mode. (1200bps
ECHO Some hardware requires this twice.
ECHO.
ECHO Example: %SCRIPT_NAME% -p COM17 --1200bps-reset
ECHO Example: %SCRIPT_NAME% -f firmware-t-deck-tft-2.6.0.0b106d4.bin -p COM11
ECHO Example: %SCRIPT_NAME% -f firmware-unphone-2.6.0.0b106d4.bin -p COM11
ECHO Example: %SCRIPT_NAME% -f firmware-t-deck-tft-2.6.0.0b106d4.factory.bin -p COM11
ECHO Example: %SCRIPT_NAME% -f firmware-unphone-2.6.0.0b106d4.factory.bin -p COM11
GOTO eof
:version
ECHO %SCRIPT_NAME% [Version 2.6.2]
ECHO %SCRIPT_NAME% [Version 2.7.0]
ECHO Meshtastic
GOTO eof
@@ -78,8 +70,8 @@ IF "__!FILENAME!__"=="____" (
CALL :LOG_MESSAGE ERROR "Filename containing spaces are not supported."
GOTO help
)
IF "__!FILENAME:firmware-=!__"=="__!FILENAME!__" (
CALL :LOG_MESSAGE ERROR "Filename must be a firmware-* file."
IF NOT "__!FILENAME:.factory.bin=!__"=="__!FILENAME!__" (
CALL :LOG_MESSAGE ERROR "Filename must be a firmware-*.factory.bin file."
GOTO help
)
@REM Remove ".\" or "./" file prefix if present.
@@ -93,12 +85,26 @@ IF NOT EXIST !FILENAME! (
GOTO eof
)
IF NOT "!FILENAME:update=!"=="!FILENAME!" (
CALL :LOG_MESSAGE DEBUG "We are working with a *update* file. !FILENAME!"
CALL :LOG_MESSAGE INFO "Use script device-update.bat to flash update !FILENAME!."
GOTO eof
CALL :LOG_MESSAGE DEBUG "Checking for metadata..."
@REM Derive metadata filename from firmware filename.
SET "METAFILE=!FILENAME:.factory.bin=!.mt.json"
IF EXIST !METAFILE! (
@REM Print parsed json with powershell
CALL :LOG_MESSAGE INFO "Firmware metadata: !METAFILE!"
powershell -NoProfile -Command "(Get-Content '!METAFILE!' | ConvertFrom-Json | Out-String).Trim()"
@REM Save metadata values to variables for later use.
FOR /f "usebackq" %%A IN (`powershell -NoProfile -Command ^
"(Get-Content '!METAFILE!' | ConvertFrom-Json).mcu"`) DO SET "MCU=%%A"
FOR /f "usebackq" %%A IN (`powershell -NoProfile -Command ^
"(Get-Content '!METAFILE!' | ConvertFrom-Json).part | Where-Object { $_.subtype -eq 'ota_1' } | Select-Object -ExpandProperty offset"`
) DO SET "OTA_OFFSET=%%A"
FOR /f "usebackq" %%A IN (`powershell -NoProfile -Command ^
"(Get-Content '!METAFILE!' | ConvertFrom-Json).part | Where-Object { $_.subtype -eq 'spiffs' } | Select-Object -ExpandProperty offset"`
) DO SET "SPIFFS_OFFSET=%%A"
) ELSE (
CALL :LOG_MESSAGE DEBUG "We are NOT working with a *update* file. !FILENAME!"
CALL :LOG_MESSAGE ERROR "No metadata file found: !METAFILE!"
GOTO eof
)
:skip-filename
@@ -108,7 +114,7 @@ IF NOT "__%PYTHON%__"=="____" (
SET "ESPTOOL_CMD=!PYTHON! -m esptool"
CALL :LOG_MESSAGE DEBUG "Python interpreter supplied."
) ELSE (
CALL :LOG_MESSAGE DEBUG "Python interpreter NOT supplied. Looking for esptool...
CALL :LOG_MESSAGE DEBUG "Python interpreter NOT supplied. Looking for esptool..."
WHERE esptool >nul 2>&1
IF %ERRORLEVEL% EQU 0 (
@REM WHERE exits with code 0 if esptool is found.
@@ -146,100 +152,26 @@ IF %BPS_RESET% EQU 1 (
GOTO eof
)
@REM Check if FILENAME contains "-tft-" and set target partitionScheme accordingly.
@REM https://github.com/meshtastic/web-flasher/blob/main/types/resources.ts#L3
IF NOT "!FILENAME:-tft-=!"=="!FILENAME!" (
CALL :LOG_MESSAGE DEBUG "We are working with a *-tft-* file. !FILENAME!"
SET "TFT_BUILD=1"
@REM Extract PROGNAME from %FILENAME% for later use.
SET "PROGNAME=!FILENAME:.factory.bin=!"
CALL :LOG_MESSAGE DEBUG "Computed PROGNAME: !PROGNAME!"
IF "__!MCU!__" == "__esp32s3__" (
@REM We are working with ESP32-S3
SET "OTA_FILENAME=bleota-s3.bin"
) ELSE IF "__!MCU!__" == "__esp32c3__" (
@REM We are working with ESP32-C3
SET "OTA_FILENAME=bleota-c3.bin"
) ELSE (
CALL :LOG_MESSAGE DEBUG "We are NOT working with a *-tft-* file. !FILENAME!"
@REM Everything else
SET "OTA_FILENAME=bleota.bin"
)
FOR %%a IN (%BIGDB_8MB%) DO (
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
@REM We are working with any of %BIGDB_8MB%.
SET "BIGDB8=1"
GOTO end_loop_bigdb_8mb
)
)
:end_loop_bigdb_8mb
FOR %%a IN (%MUIDB_8MB%) DO (
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
@REM We are working with any of %MUIDB_8MB%.
SET "MUIDB8=1"
GOTO end_loop_muidb_8mb
)
)
:end_loop_muidb_8mb
FOR %%a IN (%BIGDB_16MB%) DO (
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
@REM We are working with any of %BIGDB_16MB%.
SET "BIGDB16=1"
GOTO end_loop_bigdb_16mb
)
)
:end_loop_bigdb_16mb
IF %BIGDB8% EQU 1 CALL :LOG_MESSAGE INFO "BigDB 8mb partition selected."
IF %MUIDB8% EQU 1 CALL :LOG_MESSAGE INFO "MUIDB 8mb partition selected."
IF %BIGDB16% EQU 1 CALL :LOG_MESSAGE INFO "BigDB 16mb partition selected."
@REM Extract BASENAME from %FILENAME% for later use.
SET "BASENAME=!FILENAME:firmware-=!"
CALL :LOG_MESSAGE DEBUG "Computed firmware basename: !BASENAME!"
@REM Account for S3 and C3 board's different OTA partition.
FOR %%a IN (%S3%) DO (
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
@REM We are working with any of %S3%.
SET "OTA_FILENAME=bleota-s3.bin"
GOTO :end_loop_s3
)
)
FOR %%a IN (%C3%) DO (
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
@REM We are working with any of %C3%.
SET "OTA_FILENAME=bleota-c3.bin"
GOTO :end_loop_c3
)
)
@REM Everything else
SET "OTA_FILENAME=bleota.bin"
:end_loop_s3
:end_loop_c3
CALL :LOG_MESSAGE DEBUG "Set OTA_FILENAME to: !OTA_FILENAME!"
@REM Set SPIFFS filename with "littlefs-" prefix.
SET "SPIFFS_FILENAME=littlefs-%BASENAME%"
SET "SPIFFS_FILENAME=littlefs-!PROGNAME:firmware-=!.bin"
CALL :LOG_MESSAGE DEBUG "Set SPIFFS_FILENAME to: !SPIFFS_FILENAME!"
@REM Default offsets.
@REM https://github.com/meshtastic/web-flasher/blob/main/stores/firmwareStore.ts#L202
SET "OTA_OFFSET=0x260000"
SET "SPIFFS_OFFSET=0x300000"
@REM Offsets for BigDB 8mb.
IF %BIGDB8% EQU 1 (
SET "OTA_OFFSET=0x340000"
SET "SPIFFS_OFFSET=0x670000"
)
@REM Offsets for MUIDB 8mb.
IF %MUIDB8% EQU 1 (
SET "OTA_OFFSET=0x5D0000"
SET "SPIFFS_OFFSET=0x670000"
)
@REM Offsets for BigDB 16mb.
IF %BIGDB16% EQU 1 (
SET "OTA_OFFSET=0x650000"
SET "SPIFFS_OFFSET=0xc90000"
)
CALL :LOG_MESSAGE DEBUG "Set OTA_OFFSET to: !OTA_OFFSET!"
CALL :LOG_MESSAGE DEBUG "Set SPIFFS_OFFSET to: !SPIFFS_OFFSET!"

View File

@@ -2,69 +2,15 @@
PYTHON=${PYTHON:-$(which python3 python | head -n 1)}
BPS_RESET=false
TFT_BUILD=false
MCU=""
# Constants
RESET_BAUD=1200
FIRMWARE_OFFSET=0x00
# Variant groups
BIGDB_8MB=(
"crowpanel-esp32s3"
"heltec_capsule_sensor_v3"
"heltec-v3"
"heltec-vision-master-e213"
"heltec-vision-master-e290"
"heltec-vision-master-t190"
"heltec-wireless-paper"
"heltec-wireless-tracker"
"heltec-wsl-v3"
"icarus"
"seeed-xiao-s3"
"tbeam-s3-core"
"tracksenger"
)
MUIDB_8MB=(
"picomputer-s3"
"unphone"
"seeed-sensecap-indicator"
)
BIGDB_16MB=(
"dreamcatcher"
"elecrow-adv"
"ESP32-S3-Pico"
"heltec-v4"
"m5stack-cores3"
"mesh-tab"
"station-g2"
"t-deck"
"t-energy-s3"
"t-eth-elite"
"t-watch-s3"
"tlora-pager"
)
S3_VARIANTS=(
"s3"
"-v3"
"-v4"
"t-deck"
"wireless-paper"
"wireless-tracker"
"station-g2"
"unphone"
"t-eth-elite"
"tlora-pager"
"mesh-tab"
"dreamcatcher"
"ESP32-S3-Pico"
"seeed-sensecap-indicator"
"heltec_capsule_sensor_v3"
"vision-master"
"icarus"
"tracksenger"
"elecrow-adv"
)
# Default littlefs* offset.
OFFSET=0x300000
# Default OTA Offset
OTA_OFFSET=0x260000
# Determine the correct esptool command to use
if "$PYTHON" -m esptool version >/dev/null 2>&1; then
@@ -78,6 +24,14 @@ else
exit 1
fi
# Check for jq
if ! command -v jq >/dev/null 2>&1; then
echo "Error: jq not found" >&2
echo "Install jq with your package manager." >&2
echo "e.g. 'apt install jq', 'dnf install jq', 'brew install jq', etc." >&2
exit 1
fi
set -e
# Usage info
@@ -89,7 +43,7 @@ Flash image file to device, but first erasing and writing system information.
-h Display this help and exit.
-p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerous).
-P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: "$PYTHON")
-f FILENAME The firmware .bin file to flash. Custom to your device type and region.
-f FILENAME The firmware *.factory.bin file to flash. Custom to your device type and region.
--1200bps-reset Attempt to place the device in correct mode. Some hardware requires this twice. (1200bps Reset)
EOF
@@ -138,69 +92,43 @@ fi
shift
}
if [[ "$FILENAME" != firmware-* ]]; then
echo "Filename must be a firmware-* file."
if [[ $(basename "$FILENAME") != firmware-*.factory.bin ]]; then
echo "Filename must be a firmware-*.factory.bin file."
exit 1
fi
# Check if FILENAME contains "-tft-" and set target partitionScheme accordingly.
if [[ "${FILENAME//-tft-/}" != "$FILENAME" ]]; then
TFT_BUILD=true
fi
# Extract PROGNAME from %FILENAME% for later use.
PROGNAME="${FILENAME/.factory.bin/}"
# Derive metadata filename from %PROGNAME%.
METAFILE="${PROGNAME}.mt.json"
# Extract BASENAME from %FILENAME% for later use.
BASENAME="${FILENAME/firmware-/}"
if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
# Default littlefs* offset.
OFFSET=0x300000
# Default OTA Offset
OTA_OFFSET=0x260000
# littlefs* offset for BigDB 8mb and OTA OFFSET.
for variant in "${BIGDB_8MB[@]}"; do
if [ -z "${FILENAME##*"$variant"*}" ]; then
OFFSET=0x670000
OTA_OFFSET=0x340000
fi
done
for variant in "${MUIDB_8MB[@]}"; do
if [ -z "${FILENAME##*"$variant"*}" ]; then
OFFSET=0x670000
OTA_OFFSET=0x5D0000
fi
done
# littlefs* offset for BigDB 16mb and OTA OFFSET.
for variant in "${BIGDB_16MB[@]}"; do
if [ -z "${FILENAME##*"$variant"*}" ]; then
OFFSET=0xc90000
OTA_OFFSET=0x650000
fi
done
# Account for S3 board's different OTA partition
# FIXME: Use PlatformIO info to determine MCU type, this is unmaintainable
for variant in "${S3_VARIANTS[@]}"; do
if [ -z "${FILENAME##*"$variant"*}" ]; then
MCU="esp32s3"
fi
done
if [ "$MCU" != "esp32s3" ]; then
if [ -n "${FILENAME##*"esp32c3"*}" ]; then
OTAFILE=bleota.bin
else
OTAFILE=bleota-c3.bin
if [[ -f "$FILENAME" && "$FILENAME" == *.factory.bin ]]; then
# Display metadata if it exists
if [[ -f "$METAFILE" ]]; then
echo "Firmware metadata: ${METAFILE}"
jq . "$METAFILE"
# Extract relevant fields from metadata
if [[ $(jq -r '.part' "$METAFILE") != "null" ]]; then
OTA_OFFSET=$(jq -r '.part[] | select(.subtype == "ota_1") | .offset' "$METAFILE")
SPIFFS_OFFSET=$(jq -r '.part[] | select(.subtype == "spiffs") | .offset' "$METAFILE")
fi
MCU=$(jq -r '.mcu' "$METAFILE")
else
echo "ERROR: No metadata file found at ${METAFILE}"
exit 1
fi
# Determine OTA filename based on MCU type
if [ "$MCU" == "esp32s3" ]; then
OTAFILE=bleota-s3.bin
elif [ "$MCU" == "esp32c3" ]; then
OTAFILE=bleota-c3.bin
else
OTAFILE=bleota.bin
fi
# Set SPIFFS filename with "littlefs-" prefix.
SPIFFSFILE=littlefs-${BASENAME}
SPIFFSFILE="littlefs-${PROGNAME/firmware-/}.bin"
if [[ ! -f "$FILENAME" ]]; then
echo "Error: file ${FILENAME} wasn't found. Terminating."

View File

@@ -30,11 +30,11 @@ ECHO --change-mode Attempt to place the device in correct mode. (1200bps
ECHO Some hardware requires this twice.
ECHO.
ECHO Example: %SCRIPT_NAME% -p COM17 --change-mode
ECHO Example: %SCRIPT_NAME% -f firmware-t-deck-tft-2.6.0.0b106d4-update.bin -p COM11
ECHO Example: %SCRIPT_NAME% -f firmware-t-deck-tft-2.6.0.0b106d4.bin -p COM11
GOTO eof
:version
ECHO %SCRIPT_NAME% [Version 2.6.2]
ECHO %SCRIPT_NAME% [Version 2.7.0]
ECHO Meshtastic
GOTO eof
@@ -78,12 +78,12 @@ IF NOT EXIST !FILENAME! (
GOTO eof
)
IF "!FILENAME:update=!"=="!FILENAME!" (
CALL :LOG_MESSAGE DEBUG "We are NOT working with a *update* file. !FILENAME!"
IF NOT "__!FILENAME:.factory.bin=!__"=="__!FILENAME!__" (
CALL :LOG_MESSAGE DEBUG "We are working with a *.factory.bin* file. !FILENAME!"
CALL :LOG_MESSAGE INFO "Use script device-install.bat to flash !FILENAME!."
GOTO eof
) ELSE (
CALL :LOG_MESSAGE DEBUG "We are working with a *update* file. !FILENAME!"
CALL :LOG_MESSAGE DEBUG "We are not working with a *.factory.bin* file. !FILENAME!"
)
:skip-filename

View File

@@ -29,7 +29,7 @@ Flash image file to device, leave existing system intact."
-h Display this help and exit
-p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerous).
-P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: "$PYTHON")
-f FILENAME The *update.bin file to flash. Custom to your device type.
-f FILENAME The *.bin file to flash. Custom to your device type.
--change-mode Attempt to place the device in correct mode. Some hardware requires this twice. (1200bps Reset)
EOF
@@ -78,7 +78,7 @@ fi
shift
}
if [ -f "${FILENAME}" ] && [ -z "${FILENAME##*"update"*}" ]; then
if [[ -f "$FILENAME" && "$FILENAME" != *.factory.bin ]]; then
echo "Trying to flash update ${FILENAME}"
$ESPTOOL_CMD --baud $FLASH_BAUD write-flash $UPDATE_OFFSET "${FILENAME}"
else

View File

@@ -75,7 +75,7 @@ TOOLS = {
}
BACKTRACE_REGEX = re.compile(
r"(?:\s+(0x40[0-2](?:\d|[a-f]|[A-F]){5}):0x(?:\d|[a-f]|[A-F]){8})\b"
r"\b(0x4[0-9a-fA-F]{7,8}):0x[0-9a-fA-F]{8}\b"
)
EXCEPTION_REGEX = re.compile("^Exception \\((?P<exc>[0-9]*)\\):$")
COUNTER_REGEX = re.compile(
@@ -89,7 +89,7 @@ POINTER_REGEX = re.compile(
STACK_BEGIN = ">>>stack>>>"
STACK_END = "<<<stack<<<"
STACK_REGEX = re.compile(
"^(?P<off>[0-9a-f]+):\W+(?P<c1>[0-9a-f]+) (?P<c2>[0-9a-f]+) (?P<c3>[0-9a-f]+) (?P<c4>[0-9a-f]+)(\W.*)?$"
r"^(?P<off>[0-9a-f]+):\W+(?P<c1>[0-9a-f]+) (?P<c2>[0-9a-f]+) (?P<c3>[0-9a-f]+) (?P<c4>[0-9a-f]+)(\W.*)?$"
)
StackLine = namedtuple("StackLine", ["offset", "content"])
@@ -223,7 +223,7 @@ class AddressResolver(object):
if match is None:
if last is not None and line.startswith("(inlined by)"):
line = line[12:].strip()
self._address_map[last] += "\n \-> inlined by: " + line
self._address_map[last] += "\n \\-> inlined by: " + line
continue
if match.group("result") == "?? ??:0":

View File

@@ -2,4 +2,4 @@
set -e
pio run --environment native
gdbserver --once localhost:2345 .pio/build/native/program "$@"
gdbserver --once localhost:2345 .pio/build/native/meshtasticd "$@"

View File

@@ -2,4 +2,4 @@
set -e
pio run --environment native
.pio/build/native/program "$@"
.pio/build/native/meshtasticd "$@"

View File

@@ -87,6 +87,9 @@
</screenshots>
<releases>
<release version="2.7.17" date="2025-11-28">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.17</url>
</release>
<release version="2.7.16" date="2025-11-19">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.16</url>
</release>

View File

@@ -2,98 +2,77 @@
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
import sys
from os.path import join
from os.path import join, basename, isfile
import subprocess
import json
import re
import time
from datetime import datetime
from readprops import readProps
Import("env")
platform = env.PioPlatform()
progname = env.get("PROGNAME")
lfsbin = f"{progname.replace('firmware-', 'littlefs-')}.bin"
def esp32_create_combined_bin(source, target, env):
# this sub is borrowed from ESPEasy build toolchain. It's licensed under GPL V3
# https://github.com/letscontrolit/ESPEasy/blob/mega/tools/pio/post_esp32.py
print("Generating combined binary for serial flashing")
app_offset = 0x10000
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin")
sections = env.subst(env.get("FLASH_EXTRA_IMAGES"))
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
chip = env.get("BOARD_MCU")
flash_size = env.BoardConfig().get("upload.flash_size")
flash_freq = env.BoardConfig().get("build.f_flash", "40m")
flash_freq = flash_freq.replace("000000L", "m")
flash_mode = env.BoardConfig().get("build.flash_mode", "dio")
memory_type = env.BoardConfig().get("build.arduino.memory_type", "qio_qspi")
if flash_mode == "qio" or flash_mode == "qout":
flash_mode = "dio"
if memory_type == "opi_opi" or memory_type == "opi_qspi":
flash_mode = "dout"
cmd = [
"--chip",
chip,
"merge_bin",
"-o",
new_file_name,
"--flash_mode",
flash_mode,
"--flash_freq",
flash_freq,
"--flash_size",
flash_size,
def manifest_gather(source, target, env):
out = []
check_paths = [
progname,
f"{progname}.elf",
f"{progname}.bin",
f"{progname}.factory.bin",
f"{progname}.hex",
f"{progname}.merged.hex",
f"{progname}.uf2",
f"{progname}.factory.uf2",
f"{progname}.zip",
lfsbin
]
for p in check_paths:
f = env.File(env.subst(f"$BUILD_DIR/{p}"))
if f.exists():
d = {
"name": p,
"md5": f.get_content_hash(), # Returns MD5 hash
"bytes": f.get_size() # Returns file size in bytes
}
out.append(d)
print(d)
manifest_write(out, env)
print(" Offset | File")
for section in sections:
sect_adr, sect_file = section.split(" ", 1)
print(f" - {sect_adr} | {sect_file}")
cmd += [sect_adr, sect_file]
def manifest_write(files, env):
manifest = {
"version": verObj["long"],
"build_epoch": build_epoch,
"board": env.get("PIOENV"),
"mcu": env.get("BOARD_MCU"),
"repo": repo_owner,
"files": files,
"part": None,
"has_mui": False,
"has_inkhud": False,
}
# Get partition table (generated in esp32_pre.py) if it exists
if env.get("custom_mtjson_part"):
# custom_mtjson_part is a JSON string, convert it back to a dict
pj = json.loads(env.get("custom_mtjson_part"))
manifest["part"] = pj
# Enable has_mui for TFT builds
if ("HAS_TFT", 1) in env.get("CPPDEFINES", []):
manifest["has_mui"] = True
if "MESHTASTIC_INCLUDE_INKHUD" in env.get("CPPDEFINES", []):
manifest["has_inkhud"] = True
print(f" - {hex(app_offset)} | {firmware_name}")
cmd += [hex(app_offset), firmware_name]
print("Using esptool.py arguments: %s" % " ".join(cmd))
esptool.main(cmd)
if platform.name == "espressif32":
sys.path.append(join(platform.get_package_dir("tool-esptoolpy")))
import esptool
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin)
esp32_kind = env.GetProjectOption("custom_esp32_kind")
if esp32_kind == "esp32":
# Free up some IRAM by removing auxiliary SPI flash chip drivers.
# Wrapped stub symbols are defined in src/platform/esp32/iram-quirk.c.
env.Append(
LINKFLAGS=[
"-Wl,--wrap=esp_flash_chip_gd",
"-Wl,--wrap=esp_flash_chip_issi",
"-Wl,--wrap=esp_flash_chip_winbond",
]
)
else:
# For newer ESP32 targets, using newlib nano works better.
env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"])
if platform.name == "nordicnrf52":
env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex",
env.VerboseAction(f"\"{sys.executable}\" ./bin/uf2conv.py \"$BUILD_DIR/firmware.hex\" -c -f 0xADA52840 -o \"$BUILD_DIR/firmware.uf2\"",
"Generating UF2 file"))
# Write the manifest to the build directory
with open(env.subst("$BUILD_DIR/${PROGNAME}.mt.json"), "w") as f:
json.dump(manifest, f, indent=2)
Import("projenv")
prefsLoc = projenv["PROJECT_DIR"] + "/version.properties"
verObj = readProps(prefsLoc)
print("Using meshtastic platformio-custom.py, firmware version " + verObj["long"] + " on " + env.get("PIOENV"))
print(f"Using meshtastic platformio-custom.py, firmware version {verObj['long']} on {env.get('PIOENV')}")
# get repository owner if git is installed
try:
@@ -139,10 +118,10 @@ flags = [
"-DBUILD_EPOCH=" + str(build_epoch),
] + pref_flags
print ("Using flags:")
print("Using flags:")
for flag in flags:
print(flag)
projenv.Append(
CCFLAGS=flags,
)
@@ -181,3 +160,19 @@ def load_boot_logo(source, target, env):
# Load the boot logo on TFT builds
if ("HAS_TFT", 1) in env.get("CPPDEFINES", []):
env.AddPreAction('$BUILD_DIR/littlefs.bin', load_boot_logo)
# Rename (mv) littlefs.bin to include the PROGNAME
# This ensures the littlefs.bin is named consistently with the firmware
env.AddPostAction('$BUILD_DIR/littlefs.bin', env.VerboseAction(
f'mv $BUILD_DIR/littlefs.bin $BUILD_DIR/{lfsbin}',
f'Renaming littlefs.bin to {lfsbin}'
))
env.AddCustomTarget(
name="mtjson",
dependencies=None,
actions=[manifest_gather],
title="Meshtastic Manifest",
description="Generating Meshtastic manifest JSON + Checksums",
always_build=True,
)

16
bin/platformio-pre.py Normal file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env python3
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
Import("env")
platform = env.PioPlatform()
if platform.name == "native":
env.Replace(PROGNAME="meshtasticd")
else:
from readprops import readProps
prefsLoc = env["PROJECT_DIR"] + "/version.properties"
verObj = readProps(prefsLoc)
env.Replace(PROGNAME=f"firmware-{env.get('PIOENV')}-{verObj['long']}")
# Print the new program name for verification
print(f"PROGNAME: {env.get('PROGNAME')}")

View File

@@ -3,7 +3,7 @@
set -e
echo "Starting simulator"
.pio/build/native/program &
.pio/build/native/meshtasticd -s &
sleep 20 # 5 seconds was not enough
echo "Simulator started, launching python test..."

View File

@@ -0,0 +1,41 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "hackaday-communicator"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": ["esp-builtin"],
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "hackaday-communicator (16 MB FLASH, 8 MB PSRAM)",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 1500000
},
"url": "hackaday.com",
"vendor": "hackaday"
}

View File

@@ -9,7 +9,7 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],

6
debian/changelog vendored
View File

@@ -1,3 +1,9 @@
meshtasticd (2.7.17.0) unstable; urgency=medium
* Version 2.7.17
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Fri, 28 Nov 2025 15:11:34 +0000
meshtasticd (2.7.16.0) unstable; urgency=medium
* Version 2.7.16

1
debian/rules vendored
View File

@@ -28,5 +28,4 @@ override_dh_auto_build:
# Build with platformio
$(PIO_ENV) platformio run -e native-tft
# Move the binary and default config to the correct name
mv .pio/build/native-tft/program .pio/build/native-tft/meshtasticd
cp bin/config-dist.yaml bin/config.yaml

View File

@@ -1,10 +1,9 @@
#!/usr/bin/env python3
# trunk-ignore-all(flake8/F821)
# trunk-ignore-all(ruff/F821)
Import("env")
# NOTE: This is not currently used, but can serve as an example on how to write extra_scripts
# print("Current CLI targets", COMMAND_LINE_TARGETS)
# print("Current Build targets", BUILD_TARGETS)
# print("CPP defs", env.get("CPPDEFINES"))

80
extra_scripts/esp32_extra.py Executable file
View File

@@ -0,0 +1,80 @@
#!/usr/bin/env python3
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
# trunk-ignore-all(ruff/E402): Hacky esptool import
# trunk-ignore-all(flake8/E402): Hacky esptool import
import sys
from os.path import join
Import("env")
platform = env.PioPlatform()
sys.path.append(join(platform.get_package_dir("tool-esptoolpy")))
import esptool
def esp32_create_combined_bin(source, target, env):
# this sub is borrowed from ESPEasy build toolchain. It's licensed under GPL V3
# https://github.com/letscontrolit/ESPEasy/blob/mega/tools/pio/post_esp32.py
print("Generating combined binary for serial flashing")
app_offset = 0x10000
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin")
sections = env.subst(env.get("FLASH_EXTRA_IMAGES"))
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
chip = env.get("BOARD_MCU")
board = env.BoardConfig()
flash_size = board.get("upload.flash_size")
flash_freq = board.get("build.f_flash", "40m")
flash_freq = flash_freq.replace("000000L", "m")
flash_mode = board.get("build.flash_mode", "dio")
memory_type = board.get("build.arduino.memory_type", "qio_qspi")
if flash_mode == "qio" or flash_mode == "qout":
flash_mode = "dio"
if memory_type == "opi_opi" or memory_type == "opi_qspi":
flash_mode = "dout"
cmd = [
"--chip",
chip,
"merge_bin",
"-o",
new_file_name,
"--flash_mode",
flash_mode,
"--flash_freq",
flash_freq,
"--flash_size",
flash_size,
]
print(" Offset | File")
for section in sections:
sect_adr, sect_file = section.split(" ", 1)
print(f" - {sect_adr} | {sect_file}")
cmd += [sect_adr, sect_file]
print(f" - {hex(app_offset)} | {firmware_name}")
cmd += [hex(app_offset), firmware_name]
print("Using esptool.py arguments: %s" % " ".join(cmd))
esptool.main(cmd)
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin)
esp32_kind = env.GetProjectOption("custom_esp32_kind")
if esp32_kind == "esp32":
# Free up some IRAM by removing auxiliary SPI flash chip drivers.
# Wrapped stub symbols are defined in src/platform/esp32/iram-quirk.c.
env.Append(
LINKFLAGS=[
"-Wl,--wrap=esp_flash_chip_gd",
"-Wl,--wrap=esp_flash_chip_issi",
"-Wl,--wrap=esp_flash_chip_winbond",
]
)
else:
# For newer ESP32 targets, using newlib nano works better.
env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"])

73
extra_scripts/esp32_pre.py Executable file
View File

@@ -0,0 +1,73 @@
#!/usr/bin/env python3
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
import json
import sys
from os.path import isfile
Import("env")
# From https://github.com/platformio/platform-espressif32/blob/develop/builder/main.py
def _parse_size(value):
if isinstance(value, int):
return value
elif value.isdigit():
return int(value)
elif value.startswith("0x"):
return int(value, 16)
elif value[-1].upper() in ("K", "M"):
base = 1024 if value[-1].upper() == "K" else 1024 * 1024
return int(value[:-1]) * base
return value
def _parse_partitions(env):
partitions_csv = env.subst("$PARTITIONS_TABLE_CSV")
if not isfile(partitions_csv):
sys.stderr.write(
"Could not find the file %s with partitions " "table.\n" % partitions_csv
)
env.Exit(1)
return
result = []
# The first offset is 0x9000 because partition table is flashed to 0x8000 and
# occupies an entire flash sector, which size is 0x1000
next_offset = 0x9000
with open(partitions_csv) as fp:
for line in fp.readlines():
line = line.strip()
if not line or line.startswith("#"):
continue
tokens = [t.strip() for t in line.split(",")]
if len(tokens) < 5:
continue
bound = 0x10000 if tokens[1] in ("0", "app") else 4
calculated_offset = (next_offset + bound - 1) & ~(bound - 1)
partition = {
"name": tokens[0],
"type": tokens[1],
"subtype": tokens[2],
"offset": tokens[3] or calculated_offset,
"size": tokens[4],
"flags": tokens[5] if len(tokens) > 5 else None,
}
result.append(partition)
next_offset = _parse_size(partition["offset"]) + _parse_size(
partition["size"]
)
return result
def mtjson_esp32_part(target, source, env):
part = _parse_partitions(env)
pj = json.dumps(part)
# print(f"JSON_PARTITIONS: {pj}")
# Dump json string to 'custom_mtjson_part' variable to use later when writing the manifest
env.Replace(custom_mtjson_part=pj)
env.AddPreAction("mtjson", mtjson_esp32_part)

50
extra_scripts/nrf52_extra.py Executable file
View File

@@ -0,0 +1,50 @@
#!/usr/bin/env python3
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
import sys
from os.path import basename
Import("env")
# Custom HEX from ELF
# Convert hex to uf2 for nrf52
def nrf52_hex_to_uf2(source, target, env):
hex_path = target[0].get_abspath()
# When using merged hex, drop 'merged' from uf2 filename
uf2_path = hex_path.replace(".merged.", ".")
uf2_path = uf2_path.replace(".hex", ".uf2")
env.Execute(
env.VerboseAction(
f'"{sys.executable}" ./bin/uf2conv.py "{hex_path}" -c -f 0xADA52840 -o "{uf2_path}"',
f"Generating UF2 file from {basename(hex_path)}",
)
)
def nrf52_mergehex(source, target, env):
hex_path = target[0].get_abspath()
merged_hex_path = hex_path.replace(".hex", ".merged.hex")
merge_with = None
if "wio-sdk-wm1110" == str(env.get("PIOENV")):
merge_with = env.subst("$PROJECT_DIR/bin/s140_nrf52_7.3.0_softdevice.hex")
else:
print("merge_with not defined for this target")
if merge_with is not None:
env.Execute(
env.VerboseAction(
f'"$PROJECT_DIR/bin/mergehex" -m "{hex_path}" "{merge_with}" -o "{merged_hex_path}"',
"Merging HEX with SoftDevice",
)
)
print(f'Merged file saved at "{basename(merged_hex_path)}"')
nrf52_hex_to_uf2([hex_path, merge_with], [env.File(merged_hex_path)], env)
# if WM1110 target, merge hex with softdevice 7.3.0
if "wio-sdk-wm1110" == env.get("PIOENV"):
env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", nrf52_mergehex)
else:
env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", nrf52_hex_to_uf2)

View File

@@ -1,7 +1,9 @@
#!/usr/bin/env python3
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
Import("env")
# Custom HEX from ELF
env.AddPostAction(
"$BUILD_DIR/${PROGNAME}.elf",

View File

@@ -76,7 +76,7 @@ platformio run -e native-tft
%install
# Install meshtasticd binary
mkdir -p %{buildroot}%{_bindir}
install -m 0755 .pio/build/native-tft/program %{buildroot}%{_bindir}/meshtasticd
install -m 0755 .pio/build/native-tft/meshtasticd %{buildroot}%{_bindir}/meshtasticd
# Install portduino VFS dir
install -p -d -m 0770 %{buildroot}%{_localstatedir}/lib/meshtasticd

View File

@@ -5,7 +5,7 @@
default_envs = tbeam
extra_configs =
arch/*/*.ini
variants/*/*.ini
variants/*/*/platformio.ini
variants/*/diy/*/platformio.ini
src/graphics/niche/InkHUD/PlatformioConfig.ini
@@ -14,7 +14,9 @@ description = Meshtastic
[env]
test_build_src = true
extra_scripts = bin/platformio-custom.py
extra_scripts =
pre:bin/platformio-pre.py
bin/platformio-custom.py
; note: we add src to our include search path so that lmic_project_config can override
; note: TINYGPS_OPTION_NO_CUSTOM_FIELDS is VERY important. We don't use custom fields and somewhere in that pile
; of code is a heap corruption bug!
@@ -182,8 +184,8 @@ lib_deps =
dfrobot/DFRobot_BMM150@1.0.0
# renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561
adafruit/Adafruit TSL2561@1.1.2
# renovate: datasource=custom.pio depName=BH1750_WE packageName=wollewald/BH1750_WE@^1.1.10
wollewald/BH1750_WE@^1.1.10
# renovate: datasource=custom.pio depName=BH1750_WE packageName=wollewald/library/BH1750_WE
wollewald/BH1750_WE@1.1.10
; (not included in native / portduino)
[environmental_extra]
@@ -205,7 +207,7 @@ lib_deps =
# 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 OPT3001@1.1.2
closedcube/ClosedCube OPT3001@1.1.2
# renovate: datasource=custom.pio depName=Bosch BSEC2 packageName=boschsensortec/library/bsec2
boschsensortec/bsec2@1.10.2610
# renovate: datasource=custom.pio depName=Bosch BME68x packageName=boschsensortec/library/BME68x Sensor Library

View File

@@ -50,8 +50,11 @@ class AudioThread : public concurrency::OSThread
delete i2sRtttl;
i2sRtttl = nullptr;
}
delete rtttlFile;
rtttlFile = nullptr;
if (rtttlFile != nullptr) {
delete rtttlFile;
rtttlFile = nullptr;
}
setCPUFast(false);
#ifdef T_LORA_PAGER
@@ -99,9 +102,9 @@ class AudioThread : public concurrency::OSThread
};
AudioGeneratorRTTTL *i2sRtttl = nullptr;
AudioOutputI2S *audioOut;
AudioOutputI2S *audioOut = nullptr;
AudioFileSourcePROGMEM *rtttlFile;
AudioFileSourcePROGMEM *rtttlFile = nullptr;
};
#endif

View File

@@ -1453,7 +1453,7 @@ class LipoCharger : public HasBatteryLevel
/**
* return true if there is an external power source detected
*/
virtual bool isVbusIn() override { return PPM->getVbusVoltage() > 0; }
virtual bool isVbusIn() override { return PPM->isVbusIn(); }
/**
* return true if the battery is currently charging

View File

@@ -57,21 +57,21 @@ static bool isPowered()
static void sdsEnter()
{
LOG_DEBUG("State: SDS");
LOG_POWERFSM("State: SDS");
// FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw
doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false, false);
}
static void lowBattSDSEnter()
{
LOG_DEBUG("State: Lower batt SDS");
LOG_POWERFSM("State: Lower batt SDS");
doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false, true);
}
extern Power *power;
static void shutdownEnter()
{
LOG_DEBUG("State: SHUTDOWN");
LOG_POWERFSM("State: SHUTDOWN");
shutdownAtMsec = millis();
}
@@ -81,7 +81,7 @@ static uint32_t secsSlept;
static void lsEnter()
{
LOG_INFO("lsEnter begin, ls_secs=%u", config.power.ls_secs);
LOG_POWERFSM("lsEnter begin, ls_secs=%u", config.power.ls_secs);
if (screen)
screen->setOn(false);
secsSlept = 0; // How long have we been sleeping this time
@@ -155,12 +155,12 @@ static void lsIdle()
static void lsExit()
{
LOG_INFO("Exit state: LS");
LOG_POWERFSM("State: lsExit");
}
static void nbEnter()
{
LOG_DEBUG("State: NB");
LOG_POWERFSM("State: nbEnter");
if (screen)
screen->setOn(false);
#ifdef ARCH_ESP32
@@ -173,6 +173,7 @@ static void nbEnter()
static void darkEnter()
{
LOG_POWERFSM("State: darkEnter");
setBluetoothEnable(true);
if (screen)
screen->setOn(false);
@@ -180,7 +181,7 @@ static void darkEnter()
static void serialEnter()
{
LOG_DEBUG("State: SERIAL");
LOG_POWERFSM("State: serialEnter");
setBluetoothEnable(false);
if (screen) {
screen->setOn(true);
@@ -189,13 +190,14 @@ static void serialEnter()
static void serialExit()
{
LOG_POWERFSM("State: serialExit");
// Turn bluetooth back on when we leave serial stream API
setBluetoothEnable(true);
}
static void powerEnter()
{
// LOG_DEBUG("State: POWER");
LOG_POWERFSM("State: powerEnter");
if (!isPowered()) {
// If we got here, we are in the wrong state - we should be in powered, let that state handle things
LOG_INFO("Loss of power in Powered");
@@ -210,6 +212,7 @@ static void powerEnter()
static void powerIdle()
{
// LOG_POWERFSM("State: powerIdle"); // very chatty
if (!isPowered()) {
// If we got here, we are in the wrong state
LOG_INFO("Loss of power in Powered");
@@ -219,14 +222,13 @@ static void powerIdle()
static void powerExit()
{
if (screen)
screen->setOn(true);
LOG_POWERFSM("State: powerExit");
setBluetoothEnable(true);
}
static void onEnter()
{
LOG_DEBUG("State: ON");
LOG_POWERFSM("State: onEnter");
if (screen)
screen->setOn(true);
setBluetoothEnable(true);
@@ -234,6 +236,7 @@ static void onEnter()
static void onIdle()
{
LOG_POWERFSM("State: onIdle");
if (isPowered()) {
// If we got here, we are in the wrong state - we should be in powered, let that state handle things
powerFSM.trigger(EVENT_POWER_CONNECTED);
@@ -242,7 +245,7 @@ static void onIdle()
static void bootEnter()
{
LOG_DEBUG("State: BOOT");
LOG_POWERFSM("State: bootEnter");
}
State stateSHUTDOWN(shutdownEnter, NULL, NULL, "SHUTDOWN");
@@ -319,11 +322,6 @@ void PowerFSM_setup()
// if any packet destined for phone arrives, turn on bluetooth at least
powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone");
// Removed 2.7: we don't show the nodes individually for every node on the screen anymore
// powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
// powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
// powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
// Show the received text message
powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text");
powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text");
@@ -372,7 +370,7 @@ void PowerFSM_setup()
// Don't add power saving transitions if we are a power saving tracker or sensor or have Wifi enabled. Sleep will be initiated
// through the modules
#if HAS_WIFI || !defined(MESHTASTIC_EXCLUDE_WIFI)
#if HAS_WIFI && !defined(MESHTASTIC_EXCLUDE_WIFI)
bool isTrackerOrSensor = config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR;

View File

@@ -2,6 +2,12 @@
#include "configuration.h"
#ifdef PowerFSMDebug
#define LOG_POWERFSM(...) LOG_DEBUG(__VA_ARGS__)
#else
#define LOG_POWERFSM(...)
#endif
// See sw-design.md for documentation
#define EVENT_PRESS 1

View File

@@ -16,6 +16,7 @@ struct ToneDuration {
};
// Some common frequencies.
#define NOTE_SILENT 1
#define NOTE_C3 131
#define NOTE_CS3 139
#define NOTE_D3 147
@@ -29,11 +30,16 @@ struct ToneDuration {
#define NOTE_AS3 233
#define NOTE_B3 247
#define NOTE_CS4 277
#define NOTE_B4 494
#define NOTE_F5 698
#define NOTE_G6 1568
#define NOTE_E7 2637
const int DURATION_1_16 = 62; // 1/16 note
const int DURATION_1_8 = 125; // 1/8 note
const int DURATION_1_4 = 250; // 1/4 note
const int DURATION_1_2 = 500; // 1/2 note
const int DURATION_3_4 = 750; // 1/4 note
const int DURATION_3_4 = 750; // 3/4 note
const int DURATION_1_1 = 1000; // 1/1 note
void playTones(const ToneDuration *tone_durations, int size)
@@ -71,13 +77,24 @@ void playLongBeep()
void playGPSEnableBeep()
{
#if defined(R1_NEO) || defined(MUZI_BASE)
ToneDuration melody[] = {
{NOTE_F5, DURATION_1_2}, {NOTE_G6, DURATION_1_8}, {NOTE_E7, DURATION_1_4}, {NOTE_SILENT, DURATION_1_2}};
#else
ToneDuration melody[] = {{NOTE_C3, DURATION_1_8}, {NOTE_FS3, DURATION_1_4}, {NOTE_CS4, DURATION_1_4}};
#endif
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}
void playGPSDisableBeep()
{
#if defined(R1_NEO) || defined(MUZI_BASE)
ToneDuration melody[] = {{NOTE_B4, DURATION_1_16}, {NOTE_B4, DURATION_1_16}, {NOTE_SILENT, DURATION_1_8},
{NOTE_F3, DURATION_1_16}, {NOTE_F3, DURATION_1_16}, {NOTE_SILENT, DURATION_1_8},
{NOTE_C3, DURATION_1_1}, {NOTE_SILENT, DURATION_1_1}};
#else
ToneDuration melody[] = {{NOTE_CS4, DURATION_1_8}, {NOTE_FS3, DURATION_1_4}, {NOTE_C3, DURATION_1_4}};
#endif
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}

View File

@@ -36,6 +36,29 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* Offer chance for variant-specific defines */
#include "variant.h"
// -----------------------------------------------------------------------------
// Display feature overrides
// -----------------------------------------------------------------------------
// Allow build environments to opt-in explicitly to the E-Ink UI stack while
// keeping headless targets slim by default. Existing variants that already
// define USE_EINK continue to work without additional flags.
#ifndef MESHTASTIC_USE_EINK_UI
#ifdef USE_EINK
#define MESHTASTIC_USE_EINK_UI 1
#else
#define MESHTASTIC_USE_EINK_UI 0
#endif
#endif
#if MESHTASTIC_USE_EINK_UI
#ifndef USE_EINK
#define USE_EINK
#endif
#else
#undef USE_EINK
#endif
// -----------------------------------------------------------------------------
// Version
// -----------------------------------------------------------------------------
@@ -371,6 +394,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef HAS_BLUETOOTH
#define HAS_BLUETOOTH 0
#endif
#ifndef USE_TFTDISPLAY
#define USE_TFTDISPLAY 0
#endif
#ifndef HW_VENDOR
#error HW_VENDOR must be defined

View File

@@ -38,14 +38,16 @@ template <typename T, std::size_t N> std::size_t array_count(const T (&)[N])
return N;
}
#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
#if defined(GPS_SERIAL_PORT)
HardwareSerial *GPS::_serial_gps = &GPS_SERIAL_PORT;
#else
HardwareSerial *GPS::_serial_gps = &Serial1;
#ifndef GPS_SERIAL_PORT
#define GPS_SERIAL_PORT Serial1
#endif
#if defined(ARCH_NRF52)
Uart *GPS::_serial_gps = &GPS_SERIAL_PORT;
#elif defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
HardwareSerial *GPS::_serial_gps = &GPS_SERIAL_PORT;
#elif defined(ARCH_RP2040)
SerialUART *GPS::_serial_gps = &Serial1;
SerialUART *GPS::_serial_gps = &GPS_SERIAL_PORT;
#else
HardwareSerial *GPS::_serial_gps = nullptr;
#endif
@@ -1525,10 +1527,7 @@ GPS *GPS::createGps()
int8_t _rx_gpio = config.position.rx_gpio;
int8_t _tx_gpio = config.position.tx_gpio;
int8_t _en_gpio = config.position.gps_en_gpio;
#if HAS_GPS && !defined(ARCH_ESP32)
_rx_gpio = 1; // We only specify GPS serial ports on ESP32. Otherwise, these are just flags.
_tx_gpio = 1;
#endif
#if defined(GPS_RX_PIN)
if (!_rx_gpio)
_rx_gpio = GPS_RX_PIN;
@@ -1602,16 +1601,28 @@ GPS *GPS::createGps()
_serial_gps->setRxBufferSize(SERIAL_BUFFER_SIZE); // the default is 256
#endif
// ESP32 has a special set of parameters vs other arduino ports
#if defined(ARCH_ESP32)
LOG_DEBUG("Use GPIO%d for GPS RX", new_gps->rx_gpio);
LOG_DEBUG("Use GPIO%d for GPS TX", new_gps->tx_gpio);
// ESP32 has a special set of parameters vs other arduino ports
#if defined(ARCH_ESP32)
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, new_gps->rx_gpio, new_gps->tx_gpio);
#elif defined(ARCH_RP2040)
_serial_gps->setPinout(new_gps->tx_gpio, new_gps->rx_gpio);
_serial_gps->setFIFOSize(256);
_serial_gps->begin(GPS_BAUDRATE);
#else
#elif defined(ARCH_NRF52)
_serial_gps->setPins(new_gps->rx_gpio, new_gps->tx_gpio);
_serial_gps->begin(GPS_BAUDRATE);
#elif defined(ARCH_STM32WL)
_serial_gps->setTx(new_gps->tx_gpio);
_serial_gps->setRx(new_gps->rx_gpio);
_serial_gps->begin(GPS_BAUDRATE);
#elif defined(ARCH_PORTDUINO)
// Portduino can't set the GPS pins directly.
_serial_gps->begin(GPS_BAUDRATE);
#else
#error Unsupported architecture!
#endif
}
return new_gps;

View File

@@ -194,6 +194,8 @@ class GPS : private concurrency::OSThread
/** If !NULL we will use this serial port to construct our GPS */
#if defined(ARCH_RP2040)
static SerialUART *_serial_gps;
#elif defined(ARCH_NRF52)
static Uart *_serial_gps;
#else
static HardwareSerial *_serial_gps;
#endif

View File

@@ -69,7 +69,11 @@ using graphics::Emote;
using graphics::emotes;
using graphics::numEmotes;
#if USE_TFTDISPLAY
extern uint16_t TFT_MESH;
#else
uint16_t TFT_MESH = COLOR565(0x67, 0xEA, 0x94);
#endif
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
#include "mesh/wifi/WiFiAPClient.h"
@@ -226,24 +230,9 @@ void Screen::showTextInput(const char *header, const char *initialText, uint32_t
{
LOG_INFO("showTextInput called with header='%s', durationMs=%d", header ? header : "NULL", durationMs);
if (NotificationRenderer::virtualKeyboard) {
delete NotificationRenderer::virtualKeyboard;
NotificationRenderer::virtualKeyboard = nullptr;
}
NotificationRenderer::textInputCallback = nullptr;
NotificationRenderer::virtualKeyboard = new VirtualKeyboard();
if (header) {
NotificationRenderer::virtualKeyboard->setHeader(header);
}
if (initialText) {
NotificationRenderer::virtualKeyboard->setInputText(initialText);
}
// Set up callback with safer cleanup mechanism
// Start OnScreenKeyboardModule session (non-touch variant)
OnScreenKeyboardModule::instance().start(header, initialText, durationMs, textCallback);
NotificationRenderer::textInputCallback = textCallback;
NotificationRenderer::virtualKeyboard->setCallback([textCallback](const std::string &text) { textCallback(text); });
// Store the message and set the expiration timestamp (use same pattern as other notifications)
strncpy(NotificationRenderer::alertBannerMessage, header ? header : "Text Input", 255);
@@ -363,11 +352,6 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
#else
dispdev = new ST7796Spi(&SPI1, ST7796_RESET, ST7796_RS, ST7796_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT);
#endif
#if defined(USE_ST7789)
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
#elif defined(USE_ST7796)
static_cast<ST7796Spi *>(dispdev)->setRGB(TFT_MESH);
#endif
#elif defined(USE_SSD1306)
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
@@ -380,7 +364,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
LOG_INFO("SSD1306 init success");
}
#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(HACKADAY_COMMUNICATOR)
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
@@ -410,6 +394,12 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
isAUTOOled = true;
#endif
#if defined(USE_ST7789)
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
#elif defined(USE_ST7796)
static_cast<ST7796Spi *>(dispdev)->setRGB(TFT_MESH);
#endif
ui = new OLEDDisplayUi(dispdev);
cmdQueue.setReader(this);
}
@@ -655,7 +645,7 @@ void Screen::setup()
#else
if (!config.display.flip_screen) {
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(HACKADAY_COMMUNICATOR)
static_cast<TFTDisplay *>(dispdev)->flipScreenVertically();
#elif defined(USE_ST7789)
static_cast<ST7789Spi *>(dispdev)->flipScreenVertically();
@@ -1508,14 +1498,14 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
// Incoming message
devicestate.has_rx_text_message = true; // Needed to include the message frame
hasUnreadMessage = true; // Enables mail icon in the header
setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view
setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view (no-op during text_input)
// Only wake/force display if the configuration allows it
if (shouldWakeOnReceivedMessage()) {
setOn(true); // Wake up the screen first
forceDisplay(); // Forces screen redraw
}
// === Prepare banner content ===
// === Prepare banner/popup content ===
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
const meshtastic_Channel channel =
channels.getByIndex(packet->channel ? packet->channel : channels.getPrimaryIndex());
@@ -1539,38 +1529,84 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
// Unlike generic messages, alerts (when enabled via the ext notif module) ignore any
// 'mute' preferences set to any specific node or channel.
if (isAlert) {
if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
} else {
strcpy(banner, "Alert Received");
// If on-screen keyboard is active, show a transient popup over keyboard instead of interrupting it
if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
// Wake and force redraw so popup is visible immediately
if (shouldWakeOnReceivedMessage()) {
setOn(true);
forceDisplay();
}
screen->showSimpleBanner(banner, 3000);
} else if (!channel.settings.has_module_settings || !channel.settings.module_settings.is_muted) {
if (longName && longName[0]) {
#if defined(M5STACK_UNITC6L)
strcpy(banner, "New Message");
#else
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
#endif
// Build popup: title = message source name, content = message text (sanitized)
// Title
char titleBuf[64] = {0};
if (longName && longName[0]) {
// Sanitize sender name
std::string t = sanitizeString(longName);
strncpy(titleBuf, t.c_str(), sizeof(titleBuf) - 1);
} else {
strcpy(banner, "New Message");
strncpy(titleBuf, "Message", sizeof(titleBuf) - 1);
}
// Content: payload bytes may not be null-terminated, remove ASCII_BELL and sanitize
char content[256] = {0};
{
std::string raw;
raw.reserve(packet->decoded.payload.size);
for (size_t i = 0; i < packet->decoded.payload.size; ++i) {
char c = msgRaw[i];
if (c == ASCII_BELL)
continue; // strip bell
raw.push_back(c);
}
std::string sanitized = sanitizeString(raw);
strncpy(content, sanitized.c_str(), sizeof(content) - 1);
}
NotificationRenderer::showKeyboardMessagePopupWithTitle(titleBuf, content, 3000);
// Maintain existing buzzer behavior on M5 if applicable
#if defined(M5STACK_UNITC6L)
screen->setOn(true);
screen->showSimpleBanner(banner, 1500);
if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
(isAlert && moduleConfig.external_notification.alert_bell_buzzer) ||
(!isBroadcast(packet->to) && isToUs(packet))) {
// Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either
// - packet contains an alert and alert bell buzzer is enabled
// - packet is a non-broadcast that is addressed to this node
playLongBeep();
}
#else
screen->showSimpleBanner(banner, 3000);
#endif
} else {
// No keyboard active: use regular banner flow, respecting mute settings
if (isAlert) {
if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
} else {
strcpy(banner, "Alert Received");
}
screen->showSimpleBanner(banner, 3000);
} else if (!channel.settings.has_module_settings || !channel.settings.module_settings.is_muted) {
if (longName && longName[0]) {
#if defined(M5STACK_UNITC6L)
strcpy(banner, "New Message");
#else
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
#endif
} else {
strcpy(banner, "New Message");
}
#if defined(M5STACK_UNITC6L)
screen->setOn(true);
screen->showSimpleBanner(banner, 1500);
if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
(isAlert && moduleConfig.external_notification.alert_bell_buzzer) ||
(!isBroadcast(packet->to) && isToUs(packet))) {
// Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either
// - packet contains an alert and alert bell buzzer is enabled
// - packet is a non-broadcast that is addressed to this node
playLongBeep();
}
#else
screen->showSimpleBanner(banner, 3000);
#endif
}
}
}
}
@@ -1605,6 +1641,7 @@ int Screen::handleUIFrameEvent(const UIFrameEvent *event)
int Screen::handleInputEvent(const InputEvent *event)
{
LOG_INPUT("Screen Input event %u! kb %u", event->inputEvent, event->kbchar);
if (!screenOn)
return 0;
@@ -1652,6 +1689,12 @@ int Screen::handleInputEvent(const InputEvent *event)
showPrevFrame();
} else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) {
showNextFrame();
} else if (event->inputEvent == INPUT_BROKER_UP_LONG) {
// Long press up button for fast frame switching
showPrevFrame();
} else if (event->inputEvent == INPUT_BROKER_DOWN_LONG) {
// Long press down button for fast frame switching
showNextFrame();
} else if (event->inputEvent == INPUT_BROKER_SELECT) {
if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) {
menuHandler::homeBaseMenu();

View File

@@ -73,7 +73,8 @@
#endif
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_ST7796)) && \
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \
defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
// The screen is bigger so use bigger fonts
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19

View File

@@ -17,6 +17,12 @@ namespace graphics
void determineResolution(int16_t screenheight, int16_t screenwidth)
{
#ifdef FORCE_LOW_RES
isHighResolution = false;
return;
#endif
if (screenwidth > 128) {
isHighResolution = true;
}
@@ -24,11 +30,6 @@ void determineResolution(int16_t screenheight, int16_t screenwidth)
if (screenwidth > 128 && screenheight <= 64) {
isHighResolution = false;
}
// Special case for Heltec Wireless Tracker v1.1
if (screenwidth == 160 && screenheight == 80) {
isHighResolution = false;
}
}
// === Shared External State ===

View File

@@ -1,5 +1,6 @@
#include "configuration.h"
#include "main.h"
#if USE_TFTDISPLAY
#if ARCH_PORTDUINO
#include "platform/portduino/PortduinoGlue.h"
@@ -123,6 +124,11 @@ static void rak14014_tpIntHandle(void)
_rak14014_touch_int = true;
}
#elif defined(HACKADAY_COMMUNICATOR)
#include <Arduino_GFX_Library.h>
Arduino_DataBus *bus = nullptr;
Arduino_GFX *tft = nullptr;
#elif defined(ST72xx_DE)
#include <LovyanGFX.hpp>
#include <TCA9534.h>
@@ -1133,9 +1139,6 @@ static LGFX *tft = nullptr;
#endif
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ST7796_CS) || defined(ILI9341_DRIVER) || \
defined(ILI9342_DRIVER) || defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST72xx_DE) || \
(ARCH_PORTDUINO && HAS_SCREEN != 0)
#include "SPILock.h"
#include "TFTDisplay.h"
#include <SPI.h>
@@ -1271,12 +1274,15 @@ void TFTDisplay::display(bool fromBlank)
x_LastPixelUpdate = x;
}
}
#if defined(HACKADAY_COMMUNICATOR)
tft->draw16bitBeRGBBitmap(x_FirstPixelUpdate, y, &linePixelBuffer[x_FirstPixelUpdate],
(x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1);
#else
// Step 4: Send the changed pixels on this line to the screen as a single block transfer.
// This function accepts pixel data MSB first so it can dump the memory straight out the SPI port.
tft->pushRect(x_FirstPixelUpdate, y, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1,
&linePixelBuffer[x_FirstPixelUpdate]);
#endif
somethingChanged = true;
}
y++;
@@ -1340,6 +1346,8 @@ void TFTDisplay::sendCommand(uint8_t com)
display(true);
if (portduino_config.displayBacklight.pin > 0)
digitalWrite(portduino_config.displayBacklight.pin, TFT_BACKLIGHT_ON);
#elif defined(HACKADAY_COMMUNICATOR)
tft->displayOn();
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
tft->wakeup();
tft->powerSaveOff();
@@ -1352,7 +1360,8 @@ void TFTDisplay::sendCommand(uint8_t com)
unphone.backlight(true); // using unPhone library
#endif
#ifdef RAK14014
#elif !defined(M5STACK) && !defined(ST7789_CS) // T-Deck gets brightness set in Screen.cpp in the handleSetOn function
#elif !defined(M5STACK) && !defined(ST7789_CS) && \
!defined(HACKADAY_COMMUNICATOR) // T-Deck gets brightness set in Screen.cpp in the handleSetOn function
tft->setBrightness(172);
#endif
break;
@@ -1364,6 +1373,8 @@ void TFTDisplay::sendCommand(uint8_t com)
tft->clear();
if (portduino_config.displayBacklight.pin > 0)
digitalWrite(portduino_config.displayBacklight.pin, !TFT_BACKLIGHT_ON);
#elif defined(HACKADAY_COMMUNICATOR)
tft->displayOff();
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
tft->sleep();
tft->powerSaveOn();
@@ -1376,7 +1387,7 @@ void TFTDisplay::sendCommand(uint8_t com)
unphone.backlight(false); // using unPhone library
#endif
#ifdef RAK14014
#elif !defined(M5STACK)
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR)
tft->setBrightness(0);
#endif
break;
@@ -1392,7 +1403,7 @@ void TFTDisplay::setDisplayBrightness(uint8_t _brightness)
{
#ifdef RAK14014
// todo
#else
#elif !defined(HACKADAY_COMMUNICATOR)
tft->setBrightness(_brightness);
LOG_DEBUG("Brightness is set to value: %i ", _brightness);
#endif
@@ -1410,7 +1421,7 @@ bool TFTDisplay::hasTouch(void)
{
#ifdef RAK14014
return true;
#elif !defined(M5STACK)
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR)
return tft->touch() != nullptr;
#else
return false;
@@ -1429,7 +1440,7 @@ bool TFTDisplay::getTouch(int16_t *x, int16_t *y)
} else {
return false;
}
#elif !defined(M5STACK)
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR)
return tft->getTouch(x, y);
#else
return false;
@@ -1448,6 +1459,12 @@ bool TFTDisplay::connect()
LOG_INFO("Do TFT init");
#ifdef RAK14014
tft = new TFT_eSPI;
#elif defined(HACKADAY_COMMUNICATOR)
bus = new Arduino_ESP32SPI(TFT_DC, TFT_CS, 38 /* SCK */, 21 /* MOSI */, GFX_NOT_DEFINED /* MISO */, HSPI /* spi_num */);
tft = new Arduino_NV3007(bus, 40, 0 /* rotation */, false /* IPS */, 142 /* width */, 428 /* height */, 12 /* col offset 1 */,
0 /* row offset 1 */, 14 /* col offset 2 */, 0 /* row offset 2 */, nv3007_279_init_operations,
sizeof(nv3007_279_init_operations));
#else
tft = new LGFX;
#endif
@@ -1458,8 +1475,15 @@ bool TFTDisplay::connect()
#ifdef UNPHONE
unphone.backlight(true); // using unPhone library
#endif
#ifdef HACKADAY_COMMUNICATOR
bool beginStatus = tft->begin();
if (beginStatus)
LOG_DEBUG("TFT Success!");
else
LOG_ERROR("TFT Fail!");
#else
tft->init();
#endif
#if defined(M5STACK)
tft->setRotation(0);
@@ -1492,4 +1516,4 @@ bool TFTDisplay::connect()
return true;
}
#endif
#endif // USE_TFTDISPLAY

View File

@@ -97,8 +97,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
(storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \
defined(USE_ST7796) || \
ARCH_PORTDUINO) && \
defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796) || ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12,
8, imgQuestionL1);
@@ -110,7 +109,8 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
#endif
} else {
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || defined(USE_ST7796)) && \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \
defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16,
8, imgSFL1);
@@ -126,8 +126,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
// TODO: Raspberry Pi supports more than just the one screen size
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \
defined(USE_ST7796) || \
ARCH_PORTDUINO) && \
defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796) || ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
imgInfoL1);

View File

@@ -13,6 +13,7 @@
#include "input/RotaryEncoderInterruptImpl1.h"
#include "input/UpDownInterruptImpl1.h"
#include "main.h"
#include "mesh/Default.h"
#include "mesh/MeshTypes.h"
#include "modules/AdminModule.h"
#include "modules/CannedMessageModule.h"
@@ -576,7 +577,7 @@ void menuHandler::textMessageBaseMenu()
void menuHandler::systemBaseMenu()
{
enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, Test, enumEnd };
enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, WiFiToggle, PowerMenu, Test, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
@@ -592,6 +593,10 @@ void menuHandler::systemBaseMenu()
optionsArray[options] = "Bluetooth Toggle";
#endif
optionsEnumArray[options++] = Bluetooth;
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
optionsArray[options] = "WiFi Toggle";
optionsEnumArray[options++] = WiFiToggle;
#endif
#if defined(M5STACK_UNITC6L)
optionsArray[options] = "Power";
#else
@@ -629,6 +634,11 @@ void menuHandler::systemBaseMenu()
} else if (selected == Bluetooth) {
menuQueue = bluetooth_toggle_menu;
screen->runNow();
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
} else if (selected == WiFiToggle) {
menuQueue = wifi_toggle_menu;
screen->runNow();
#endif
} else if (selected == Back && !test_enabled) {
test_count++;
if (test_count > 4) {
@@ -1031,14 +1041,16 @@ void menuHandler::switchToMUIMenu()
void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
{
static const char *optionsArray[] = {"Back", "Default", "Meshtastic Green", "Yellow", "Red", "Orange", "Purple", "Teal",
"Pink", "White"};
static const char *optionsArray[] = {
"Back", "Default", "Meshtastic Green", "Yellow", "Red", "Orange", "Purple", "Blue", "Teal", "Cyan", "Ice", "Pink",
"White", "Gray"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Select Screen Color";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 10;
bannerOptions.optionsCount = 14;
bannerOptions.bannerCallback = [display](int selected) -> void {
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || \
HAS_TFT || defined(HACKADAY_COMMUNICATOR)
uint8_t TFT_MESH_r = 0;
uint8_t TFT_MESH_g = 0;
uint8_t TFT_MESH_b = 0;
@@ -1071,20 +1083,40 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
TFT_MESH_g = 153;
TFT_MESH_b = 255;
} else if (selected == 7) {
LOG_INFO("Setting color to Teal");
TFT_MESH_r = 64;
TFT_MESH_g = 224;
TFT_MESH_b = 208;
LOG_INFO("Setting color to Blue");
TFT_MESH_r = 0;
TFT_MESH_g = 0;
TFT_MESH_b = 255;
} else if (selected == 8) {
LOG_INFO("Setting color to Teal");
TFT_MESH_r = 16;
TFT_MESH_g = 102;
TFT_MESH_b = 102;
} else if (selected == 9) {
LOG_INFO("Setting color to Cyan");
TFT_MESH_r = 0;
TFT_MESH_g = 255;
TFT_MESH_b = 255;
} else if (selected == 10) {
LOG_INFO("Setting color to Ice");
TFT_MESH_r = 173;
TFT_MESH_g = 216;
TFT_MESH_b = 230;
} else if (selected == 11) {
LOG_INFO("Setting color to Pink");
TFT_MESH_r = 255;
TFT_MESH_g = 105;
TFT_MESH_b = 180;
} else if (selected == 9) {
} else if (selected == 12) {
LOG_INFO("Setting color to White");
TFT_MESH_r = 255;
TFT_MESH_g = 255;
TFT_MESH_b = 255;
} else if (selected == 13) {
LOG_INFO("Setting color to Gray");
TFT_MESH_r = 128;
TFT_MESH_g = 128;
TFT_MESH_b = 128;
} else {
menuQueue = system_base_menu;
screen->runNow();
@@ -1278,19 +1310,28 @@ void menuHandler::wifiBaseMenu()
void menuHandler::wifiToggleMenu()
{
enum optionsNumbers { Back, Wifi_toggle };
enum optionsNumbers { Back, Wifi_disable, Wifi_enable };
static const char *optionsArray[] = {"Back", "Disable"};
static const char *optionsArray[] = {"Back", "WiFi Disabled", "WiFi Enabled"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Disable Wifi and\nEnable Bluetooth?";
bannerOptions.message = "WiFi Actions";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
bannerOptions.optionsCount = 3;
if (config.network.wifi_enabled == true)
bannerOptions.InitialSelected = 2;
else
bannerOptions.InitialSelected = 1;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Wifi_toggle) {
if (selected == Wifi_disable) {
config.network.wifi_enabled = false;
config.bluetooth.enabled = true;
service->reloadConfig(SEGMENT_CONFIG);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
} else if (selected == Wifi_enable) {
config.network.wifi_enabled = true;
config.bluetooth.enabled = false;
service->reloadConfig(SEGMENT_CONFIG);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
}
};
screen->showOverlayBanner(bannerOptions);
@@ -1338,7 +1379,7 @@ void menuHandler::screenOptionsMenu()
static int optionsEnumArray[5] = {Back};
int options = 1;
#if defined(T_DECK) || defined(T_LORA_PAGER)
#if defined(T_DECK) || defined(T_LORA_PAGER) || defined(HACKADAY_COMMUNICATOR)
optionsArray[options] = "Show Long/Short Name";
optionsEnumArray[options++] = NodeNameLength;
#endif
@@ -1350,7 +1391,8 @@ void menuHandler::screenOptionsMenu()
}
// Only show screen color for TFT displays
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || \
HAS_TFT || defined(HACKADAY_COMMUNICATOR)
optionsArray[options] = "Screen Color";
optionsEnumArray[options++] = ScreenColor;
#endif

View File

@@ -85,9 +85,13 @@ void NotificationRenderer::drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiStat
void NotificationRenderer::resetBanner()
{
notificationTypeEnum previousType = current_notification_type;
alertBannerMessage[0] = '\0';
current_notification_type = notificationTypeEnum::none;
OnScreenKeyboardModule::instance().clearPopup();
inEvent.inputEvent = INPUT_BROKER_NONE;
inEvent.kbchar = 0;
curSelected = 0;
@@ -100,6 +104,13 @@ void NotificationRenderer::resetBanner()
currentNumber = 0;
nodeDB->pause_sort(false);
// If we're exiting from text_input (virtual keyboard), stop module and trigger frame update
// to ensure any messages received during keyboard use are now displayed
if (previousType == notificationTypeEnum::text_input && screen) {
OnScreenKeyboardModule::instance().stop(false);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
}
void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state)
@@ -163,13 +174,15 @@ void NotificationRenderer::drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiS
// modulo to extract
uint8_t this_digit = (currentNumber % (pow_of_10(numDigits - curSelected))) / (pow_of_10(numDigits - curSelected - 1));
// Handle input
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS ||
inEvent.inputEvent == INPUT_BROKER_UP_LONG) {
if (this_digit == 9) {
currentNumber -= 9 * (pow_of_10(numDigits - curSelected - 1));
} else {
currentNumber += (pow_of_10(numDigits - curSelected - 1));
}
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS ||
inEvent.inputEvent == INPUT_BROKER_DOWN_LONG) {
if (this_digit == 0) {
currentNumber += 9 * (pow_of_10(numDigits - curSelected - 1));
} else {
@@ -251,10 +264,10 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
// Handle input
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_LEFT ||
inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
inEvent.inputEvent == INPUT_BROKER_ALT_PRESS || inEvent.inputEvent == INPUT_BROKER_UP_LONG) {
curSelected--;
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_RIGHT ||
inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
inEvent.inputEvent == INPUT_BROKER_USER_PRESS || inEvent.inputEvent == INPUT_BROKER_DOWN_LONG) {
curSelected++;
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
alertBannerCallback(selectedNodenum);
@@ -368,10 +381,10 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
// Handle input
if (alertBannerOptions > 0) {
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_LEFT ||
inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
inEvent.inputEvent == INPUT_BROKER_ALT_PRESS || inEvent.inputEvent == INPUT_BROKER_UP_LONG) {
curSelected--;
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_RIGHT ||
inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
inEvent.inputEvent == INPUT_BROKER_USER_PRESS || inEvent.inputEvent == INPUT_BROKER_DOWN_LONG) {
curSelected++;
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
if (optionsEnumPtr != nullptr) {
@@ -769,40 +782,8 @@ void NotificationRenderer::drawTextInput(OLEDDisplay *display, OLEDDisplayUiStat
}
if (inEvent.inputEvent != INPUT_BROKER_NONE) {
if (inEvent.inputEvent == INPUT_BROKER_UP) {
// high frequency for move cursor left/right than up/down with encoders
extern ::RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
extern ::UpDownInterruptImpl1 *upDownInterruptImpl1;
if (::rotaryEncoderInterruptImpl1 || ::upDownInterruptImpl1) {
virtualKeyboard->moveCursorLeft();
} else {
virtualKeyboard->moveCursorUp();
}
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN) {
extern ::RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
extern ::UpDownInterruptImpl1 *upDownInterruptImpl1;
if (::rotaryEncoderInterruptImpl1 || ::upDownInterruptImpl1) {
virtualKeyboard->moveCursorRight();
} else {
virtualKeyboard->moveCursorDown();
}
} else if (inEvent.inputEvent == INPUT_BROKER_LEFT) {
virtualKeyboard->moveCursorLeft();
} else if (inEvent.inputEvent == INPUT_BROKER_RIGHT) {
virtualKeyboard->moveCursorRight();
} else if (inEvent.inputEvent == INPUT_BROKER_UP_LONG) {
virtualKeyboard->moveCursorUp();
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN_LONG) {
virtualKeyboard->moveCursorDown();
} else if (inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
virtualKeyboard->moveCursorLeft();
} else if (inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
virtualKeyboard->moveCursorRight();
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
virtualKeyboard->handlePress();
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT_LONG) {
virtualKeyboard->handleLongPress();
} else if (inEvent.inputEvent == INPUT_BROKER_CANCEL) {
bool handled = OnScreenKeyboardModule::processVirtualKeyboardInput(inEvent, virtualKeyboard);
if (!handled && inEvent.inputEvent == INPUT_BROKER_CANCEL) {
auto callback = textInputCallback;
delete virtualKeyboard;
virtualKeyboard = nullptr;
@@ -821,12 +802,28 @@ void NotificationRenderer::drawTextInput(OLEDDisplay *display, OLEDDisplayUiStat
inEvent.inputEvent = INPUT_BROKER_NONE;
}
// Re-check pointer before drawing to avoid use-after-free and crashes
if (!virtualKeyboard) {
// Ensure we exit text_input state and restore frames
if (current_notification_type == notificationTypeEnum::text_input) {
resetBanner();
}
if (screen) {
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
// If screen is null, do nothing (safe fallback)
return;
}
// Clear the screen to avoid overlapping with underlying frames or overlays
display->setColor(BLACK);
display->fillRect(0, 0, display->getWidth(), display->getHeight());
display->setColor(WHITE);
// Draw the virtual keyboard
virtualKeyboard->draw(display, 0, 0);
// Draw transient popup overlay (if any) managed by OnScreenKeyboardModule
OnScreenKeyboardModule::instance().drawPopupOverlay(display);
} else {
// If virtualKeyboard is null, reset the banner to avoid getting stuck
LOG_INFO("Virtual keyboard is null - resetting banner");
@@ -839,5 +836,12 @@ bool NotificationRenderer::isOverlayBannerShowing()
return strlen(alertBannerMessage) > 0 && (alertBannerUntil == 0 || millis() <= alertBannerUntil);
}
void NotificationRenderer::showKeyboardMessagePopupWithTitle(const char *title, const char *content, uint32_t durationMs)
{
if (!title || !content || current_notification_type != notificationTypeEnum::text_input)
return;
OnScreenKeyboardModule::instance().showPopup(title, content, durationMs);
}
} // namespace graphics
#endif

View File

@@ -4,6 +4,7 @@
#include "OLEDDisplayUi.h"
#include "graphics/Screen.h"
#include "graphics/VirtualKeyboard.h"
#include "modules/OnScreenKeyboardModule.h"
#include <functional>
#include <string>
#define MAX_LINES 5
@@ -31,6 +32,7 @@ class NotificationRenderer
static bool pauseBanner;
static void resetBanner();
static void showKeyboardMessagePopupWithTitle(const char *title, const char *content, uint32_t durationMs);
static void drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state);
static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state);
static void drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiState *state);

View File

@@ -257,7 +257,8 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes
}
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || defined(USE_ST7796)) && \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \
defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
if (isHighResolution) {

View File

@@ -27,7 +27,8 @@ const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03
0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f};
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(USE_ST7796) || defined(ST7796_CS) || ARCH_PORTDUINO) && \
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \
defined(USE_ST7796) || defined(HACKADAY_COMMUNICATOR) || ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff};
const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f};

View File

@@ -0,0 +1,217 @@
#if defined(HACKADAY_COMMUNICATOR)
#include "HackadayCommunicatorKeyboard.h"
#include "main.h"
#define _TCA8418_COLS 10
#define _TCA8418_ROWS 8
#define _TCA8418_NUM_KEYS 80
#define _TCA8418_MULTI_TAP_THRESHOLD 1500
using Key = TCA8418KeyboardBase::TCA8418Key;
constexpr uint8_t modifierRightShiftKey = 30;
constexpr uint8_t modifierRightShift = 0b0001;
constexpr uint8_t modifierLeftShiftKey = 76; // keynum -1
constexpr uint8_t modifierLeftShift = 0b0001;
// constexpr uint8_t modifierSymKey = 42;
// constexpr uint8_t modifierSym = 0b0010;
// 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, 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] = {{},
{},
{'+'},
{'9'},
{'8'},
{'7'},
{'2'},
{'3'},
{'4'},
{'5'},
{Key::ESC},
{'q', 'Q'},
{'w', 'W'},
{'e', 'E'},
{'r', 'R'},
{'t', 'T'},
{'y', 'Y'},
{'u', 'U'},
{'i', 'I'},
{'o', 'O'},
{Key::TAB},
{'a', 'A'},
{'s', 'S'},
{'d', 'D'},
{'f', 'F'},
{'g', 'G'},
{'h', 'H'},
{'j', 'J'},
{'k', 'K'},
{'l', 'L'},
{},
{'z', 'Z'},
{'x', 'X'},
{'c', 'C'},
{'v', 'V'},
{'b', 'B'},
{'n', 'N'},
{'m', 'M'},
{',', '<'},
{'.', '>'},
{},
{},
{},
{'\\'},
{' '},
{},
{Key::RIGHT},
{Key::DOWN},
{Key::LEFT},
{},
{},
{},
{'-'},
{'6', '^'},
{'5', '%'},
{'4', '$'},
{'[', '{'},
{']', '}'},
{'p', 'P'},
{},
{},
{},
{'*'},
{'3', '#'},
{'2', '@'},
{'1', '!'},
{Key::SELECT},
{'\'', '"'},
{';', ':'},
{},
{},
{},
{'/', '?'},
{'='},
{'.', '>'},
{'0', ')'},
{},
{Key::UP},
{Key::BSP},
{}};
HackadayCommunicatorKeyboard::HackadayCommunicatorKeyboard()
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1),
last_tap(0L), char_idx(0), tap_interval(0)
{
reset();
}
void HackadayCommunicatorKeyboard::reset(void)
{
TCA8418KeyboardBase::reset();
enableInterrupts();
}
// handle multi-key presses (shift and alt)
void HackadayCommunicatorKeyboard::trigger()
{
uint8_t count = keyCount();
if (count == 0)
return;
for (uint8_t i = 0; i < count; ++i) {
uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i);
uint8_t key = k & 0x7F;
if (k & 0x80) {
pressed(key);
} else {
released();
state = Idle;
}
}
}
void HackadayCommunicatorKeyboard::pressed(uint8_t key)
{
if (state == Init || state == Busy) {
return;
}
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
modifierFlag = 0;
}
uint8_t next_key = 0;
int row = (key - 1) / 10;
int col = (key - 1) % 10;
if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) {
return; // Invalid key
}
next_key = row * _TCA8418_COLS + col;
state = Held;
uint32_t now = millis();
tap_interval = now - last_tap;
updateModifierFlag(next_key);
if (isModifierKey(next_key)) {
last_modifier_time = now;
}
if (tap_interval < 0) {
last_tap = 0;
state = Busy;
return;
}
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
char_idx = 0;
} else {
char_idx += 1;
}
last_key = next_key;
last_tap = now;
}
void HackadayCommunicatorKeyboard::released()
{
if (state != Held) {
return;
}
if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) {
last_key = -1;
state = Idle;
return;
}
uint32_t now = millis();
last_tap = now;
if (HackadayCommunicatorTapMod[last_key])
queueEvent(HackadayCommunicatorTapMap[last_key][modifierFlag % HackadayCommunicatorTapMod[last_key]]);
if (isModifierKey(last_key) == false)
modifierFlag = 0;
}
void HackadayCommunicatorKeyboard::updateModifierFlag(uint8_t key)
{
if (key == modifierRightShiftKey) {
modifierFlag ^= modifierRightShift;
} else if (key == modifierLeftShiftKey) {
modifierFlag ^= modifierLeftShift;
}
}
bool HackadayCommunicatorKeyboard::isModifierKey(uint8_t key)
{
return (key == modifierRightShiftKey || key == modifierLeftShiftKey);
}
#endif

View File

@@ -0,0 +1,26 @@
#include "TCA8418KeyboardBase.h"
class HackadayCommunicatorKeyboard : public TCA8418KeyboardBase
{
public:
HackadayCommunicatorKeyboard();
void reset(void);
void trigger(void) override;
virtual ~HackadayCommunicatorKeyboard() {}
protected:
void pressed(uint8_t key) override;
void released(void) override;
void updateModifierFlag(uint8_t key);
bool isModifierKey(uint8_t key);
private:
uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed
uint32_t last_modifier_time; // Timestamp of the last modifier key press
int8_t last_key;
int8_t next_key;
uint32_t last_tap;
uint8_t char_idx;
int32_t tap_interval;
};

View File

@@ -3,6 +3,12 @@
#include "Observer.h"
#include "freertosinc.h"
#ifdef InputBrokerDebug
#define LOG_INPUT(...) LOG_DEBUG(__VA_ARGS__)
#else
#define LOG_INPUT(...)
#endif
enum input_broker_event {
INPUT_BROKER_NONE = 0,
INPUT_BROKER_SELECT = 10,

View File

@@ -2,6 +2,8 @@
#include "configuration.h"
#include <Throttle.h>
SerialKeyboard *globalSerialKeyboard = nullptr;
#ifdef INPUTBROKER_SERIAL_TYPE
#define CANNED_MESSAGE_MODULE_ENABLE 1 // in case it's not set in the variant file
@@ -25,6 +27,8 @@ unsigned char KeyMap[3][4][10] = {{{'.', 'a', 'd', 'g', 'j', 'm', 'p', 't', 'w',
SerialKeyboard::SerialKeyboard(const char *name) : concurrency::OSThread(name)
{
this->_originName = name;
globalSerialKeyboard = this;
}
void SerialKeyboard::erase()
@@ -85,9 +89,21 @@ int32_t SerialKeyboard::runOnce()
e.source = this->_originName;
// SELECT OR SEND OR CANCEL EVENT
if (!(shiftRegister2 & (1 << 3))) {
e.inputEvent = INPUT_BROKER_UP;
if (shift > 0) {
e.inputEvent = INPUT_BROKER_ANYKEY; // REQUIRED
e.kbchar = 0x09; // TAB
shift = 0; // reset shift after TAB
} else {
e.inputEvent = INPUT_BROKER_LEFT;
}
} else if (!(shiftRegister2 & (1 << 2))) {
e.inputEvent = INPUT_BROKER_RIGHT;
if (shift > 0) {
e.inputEvent = INPUT_BROKER_ANYKEY; // REQUIRED
e.kbchar = 0x09; // TAB
shift = 0; // reset shift after TAB
} else {
e.inputEvent = INPUT_BROKER_RIGHT;
}
e.kbchar = 0;
} else if (!(shiftRegister2 & (1 << 1))) {
e.inputEvent = INPUT_BROKER_SELECT;

View File

@@ -8,6 +8,8 @@ class SerialKeyboard : public Observable<const InputEvent *>, public concurrency
public:
explicit SerialKeyboard(const char *name);
uint8_t getShift() const { return shift; }
protected:
virtual int32_t runOnce() override;
void erase();
@@ -22,4 +24,6 @@ class SerialKeyboard : public Observable<const InputEvent *>, public concurrency
int lastKeyPressed = 13;
int quickPress = 0;
unsigned long lastPressTime = 0;
};
};
extern SerialKeyboard *globalSerialKeyboard;

View File

@@ -88,6 +88,50 @@ int32_t TrackballInterruptBase::runOnce()
}
}
if (directionDetected && directionStartTime > 0) {
uint32_t directionDuration = millis() - directionStartTime;
uint8_t directionPressedNow = 0;
directionInterval++;
if (!digitalRead(_pinUp)) {
directionPressedNow = TB_ACTION_UP;
} else if (!digitalRead(_pinDown)) {
directionPressedNow = TB_ACTION_DOWN;
} else if (!digitalRead(_pinLeft)) {
directionPressedNow = TB_ACTION_LEFT;
} else if (!digitalRead(_pinRight)) {
directionPressedNow = TB_ACTION_RIGHT;
}
const uint8_t DIRECTION_REPEAT_THRESHOLD = 3;
if (directionPressedNow == TB_ACTION_NONE) {
// Reset state
directionDetected = false;
directionStartTime = 0;
directionInterval = 0;
this->action = TB_ACTION_NONE;
} else if (directionDuration >= LONG_PRESS_DURATION && directionInterval >= DIRECTION_REPEAT_THRESHOLD) {
// repeat event when long press these direction.
switch (directionPressedNow) {
case TB_ACTION_UP:
e.inputEvent = this->_eventUp;
break;
case TB_ACTION_DOWN:
e.inputEvent = this->_eventDown;
break;
case TB_ACTION_LEFT:
e.inputEvent = this->_eventLeft;
break;
case TB_ACTION_RIGHT:
e.inputEvent = this->_eventRight;
break;
}
directionInterval = 0;
}
}
#if defined(T_DECK) // T-deck gets a super-simple debounce on trackball
if (this->action == TB_ACTION_PRESSED && !pressDetected) {
// Start long press detection
@@ -113,17 +157,22 @@ int32_t TrackballInterruptBase::runOnce()
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 && !digitalRead(_pinUp)) {
// LOG_DEBUG("Trackball event UP");
} else if (this->action == TB_ACTION_UP && !digitalRead(_pinUp) && !directionDetected) {
directionDetected = true;
directionStartTime = millis();
e.inputEvent = this->_eventUp;
} else if (this->action == TB_ACTION_DOWN && !digitalRead(_pinDown)) {
// LOG_DEBUG("Trackball event DOWN");
// send event first,will automatically trigger every 50ms * 3 after 500ms
} else if (this->action == TB_ACTION_DOWN && !digitalRead(_pinDown) && !directionDetected) {
directionDetected = true;
directionStartTime = millis();
e.inputEvent = this->_eventDown;
} else if (this->action == TB_ACTION_LEFT && !digitalRead(_pinLeft)) {
// LOG_DEBUG("Trackball event LEFT");
} else if (this->action == TB_ACTION_LEFT && !digitalRead(_pinLeft) && !directionDetected) {
directionDetected = true;
directionStartTime = millis();
e.inputEvent = this->_eventLeft;
} else if (this->action == TB_ACTION_RIGHT && !digitalRead(_pinRight)) {
// LOG_DEBUG("Trackball event RIGHT");
} else if (this->action == TB_ACTION_RIGHT && !digitalRead(_pinRight) && !directionDetected) {
directionDetected = true;
directionStartTime = millis();
e.inputEvent = this->_eventRight;
}
#endif

View File

@@ -49,10 +49,14 @@ class TrackballInterruptBase : public Observable<const InputEvent *>, public con
// Long press detection for press button
uint32_t pressStartTime = 0;
uint32_t directionStartTime = 0;
uint8_t directionInterval = 0;
bool pressDetected = false;
bool directionDetected = false;
uint32_t lastLongPressEventTime = 0;
uint32_t lastDirectionPressEventTime = 0;
static const uint32_t LONG_PRESS_DURATION = 500; // ms
static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 500; // ms - interval between repeated long press events
static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 300; // ms - interval between repeated long press events
private:
input_broker_event _eventDown = INPUT_BROKER_NONE;

View File

@@ -3,6 +3,14 @@
#include "InputBroker.h"
#include "mesh/NodeDB.h"
#ifndef UPDOWN_LONG_PRESS_DURATION
#define UPDOWN_LONG_PRESS_DURATION 300
#endif
#ifndef UPDOWN_LONG_PRESS_REPEAT_INTERVAL
#define UPDOWN_LONG_PRESS_REPEAT_INTERVAL 300
#endif
class UpDownInterruptBase : public Observable<const InputEvent *>, public concurrency::OSThread
{
public:
@@ -40,8 +48,8 @@ class UpDownInterruptBase : public Observable<const InputEvent *>, public concur
uint32_t lastPressLongEventTime = 0;
uint32_t lastUpLongEventTime = 0;
uint32_t lastDownLongEventTime = 0;
static const uint32_t LONG_PRESS_DURATION = 300;
static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 300;
static const uint32_t LONG_PRESS_DURATION = UPDOWN_LONG_PRESS_DURATION;
static const uint32_t LONG_PRESS_REPEAT_INTERVAL = UPDOWN_LONG_PRESS_REPEAT_INTERVAL;
private:
uint8_t _pinDown = 0;

View File

@@ -7,6 +7,8 @@
#include "TDeckProKeyboard.h"
#elif defined(T_LORA_PAGER)
#include "TLoraPagerKeyboard.h"
#elif defined(HACKADAY_COMMUNICATOR)
#include "HackadayCommunicatorKeyboard.h"
#else
#include "TCA8418Keyboard.h"
#endif
@@ -20,6 +22,8 @@ KbI2cBase::KbI2cBase(const char *name)
TCAKeyboard(*(new TDeckProKeyboard()))
#elif defined(T_LORA_PAGER)
TCAKeyboard(*(new TLoraPagerKeyboard()))
#elif defined(HACKADAY_COMMUNICATOR)
TCAKeyboard(*(new HackadayCommunicatorKeyboard()))
#else
TCAKeyboard(*(new TCA8418Keyboard()))
#endif
@@ -328,7 +332,7 @@ int32_t KbI2cBase::runOnce()
break;
}
if (e.inputEvent != INPUT_BROKER_NONE) {
LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar);
// LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar);
this->notifyObservers(&e);
}
TCAKeyboard.trigger();

View File

@@ -394,7 +394,10 @@ void setup()
io.pinMode(EXPANDS_GPIO_EN, OUTPUT);
io.digitalWrite(EXPANDS_GPIO_EN, HIGH);
io.pinMode(EXPANDS_SD_PULLEN, INPUT);
#elif defined(HACKADAY_COMMUNICATOR)
pinMode(KB_INT, INPUT);
#endif
concurrency::hasBeenSetup = true;
#if ARCH_PORTDUINO
SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0);
@@ -436,6 +439,11 @@ void setup()
LOG_INFO("\n\n//\\ E S H T /\\ S T / C\n");
#if defined(ARCH_ESP32) && defined(BOARD_HAS_PSRAM)
// use PSRAM for malloc calls > 256 bytes
heap_caps_malloc_extmem_enable(256);
#endif
#if defined(DEBUG_MUTE) && defined(DEBUG_PORT)
DEBUG_PORT.printf("\r\n\r\n//\\ E S H T /\\ S T / C\r\n");
DEBUG_PORT.printf("Version %s for %s from %s\r\n", optstr(APP_VERSION), optstr(APP_ENV), optstr(APP_REPO));
@@ -877,8 +885,8 @@ void setup()
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_ST7796) || \
defined(USE_SPISSD1306)
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \
defined(USE_SPISSD1306) || defined(USE_ST7796) || defined(HACKADAY_COMMUNICATOR)
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
#elif defined(ARCH_PORTDUINO)
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || portduino_config.displayPanel) &&
@@ -1154,8 +1162,8 @@ void setup()
// Don't call screen setup until after nodedb is setup (because we need
// the current region name)
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_ST7796) || \
defined(USE_SPISSD1306)
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \
defined(USE_ST7796) || defined(USE_SPISSD1306) || defined(HACKADAY_COMMUNICATOR)
if (screen)
screen->setup();
#elif defined(ARCH_PORTDUINO)

View File

@@ -57,14 +57,7 @@ class Default
// Note: Kept as uint32_t to match the public API parameter type
static float congestionScalingCoefficient(uint32_t numOnlineNodes)
{
// Increase frequency of broadcasts for small networks regardless of preset
if (numOnlineNodes <= 10) {
return 0.6;
} else if (numOnlineNodes <= 20) {
return 0.7;
} else if (numOnlineNodes <= 30) {
return 0.8;
} else if (numOnlineNodes <= 40) {
if (numOnlineNodes <= 40) {
return 1.0;
} else {
float throttlingFactor = 0.075;

View File

@@ -664,7 +664,8 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.bluetooth.fixed_pin = defaultBLEPin;
#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_SPISSD1306) || defined(USE_ST7796)
defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_SPISSD1306) || \
defined(USE_ST7796) || defined(HACKADAY_COMMUNICATOR)
bool hasScreen = true;
#ifdef HELTEC_MESH_NODE_T114
uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET);

View File

@@ -37,8 +37,8 @@
static MemoryDynamic<meshtastic_MeshPacket> dynamicPool;
Allocator<meshtastic_MeshPacket> &packetPool = dynamicPool;
#elif defined(ARCH_STM32WL)
// On STM32 there isn't enough heap left over for the rest of the firmware if we allocate this statically.
#elif defined(ARCH_STM32WL) || defined(BOARD_HAS_PSRAM)
// On STM32 and boards with PSRAM, there isn't enough heap left over for the rest of the firmware if we allocate this statically.
// For now, make it dynamic again.
#define MAX_PACKETS \
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \

View File

@@ -16,6 +16,7 @@
#include "graphics/draw/NotificationRenderer.h"
#include "graphics/emotes.h"
#include "graphics/images.h"
#include "input/SerialKeyboard.h"
#include "main.h" // for cardkb_found
#include "mesh/generated/meshtastic/cannedmessages.pb.h"
#include "modules/AdminModule.h"
@@ -1017,8 +1018,7 @@ int32_t CannedMessageModule::runOnce()
// Clean up virtual keyboard if needed when going inactive
if (graphics::NotificationRenderer::virtualKeyboard && graphics::NotificationRenderer::textInputCallback == nullptr) {
LOG_INFO("Performing delayed virtual keyboard cleanup");
delete graphics::NotificationRenderer::virtualKeyboard;
graphics::NotificationRenderer::virtualKeyboard = nullptr;
graphics::OnScreenKeyboardModule::instance().stop(false);
}
temporaryMessage = "";
@@ -1035,9 +1035,7 @@ int32_t CannedMessageModule::runOnce()
// Clean up virtual keyboard after sending
if (graphics::NotificationRenderer::virtualKeyboard) {
LOG_INFO("Cleaning up virtual keyboard after message send");
delete graphics::NotificationRenderer::virtualKeyboard;
graphics::NotificationRenderer::virtualKeyboard = nullptr;
graphics::NotificationRenderer::textInputCallback = nullptr;
graphics::OnScreenKeyboardModule::instance().stop(false);
graphics::NotificationRenderer::resetBanner();
}
@@ -1095,9 +1093,7 @@ int32_t CannedMessageModule::runOnce()
// Clean up virtual keyboard if it exists during timeout
if (graphics::NotificationRenderer::virtualKeyboard) {
LOG_INFO("Cleaning up virtual keyboard due to module timeout");
delete graphics::NotificationRenderer::virtualKeyboard;
graphics::NotificationRenderer::virtualKeyboard = nullptr;
graphics::NotificationRenderer::textInputCallback = nullptr;
graphics::OnScreenKeyboardModule::instance().stop(false);
graphics::NotificationRenderer::resetBanner();
}
@@ -1848,7 +1844,88 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
display->drawString(x + display->getWidth() - display->getStringWidth(buffer), y + 0, buffer);
}
// --- Draw Free Text input with multi-emote support and proper line wrapping ---
#if INPUTBROKER_SERIAL_TYPE == 1
// Chatter Modifier key mode label (right side)
{
uint8_t mode = globalSerialKeyboard ? globalSerialKeyboard->getShift() : 0;
const char *label = (mode == 0) ? "a" : (mode == 1) ? "A" : "#";
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_LEFT);
const int16_t th = FONT_HEIGHT_SMALL;
const int16_t tw = display->getStringWidth(label);
const int16_t padX = 3;
const int16_t padY = 2;
const int16_t r = 3;
const int16_t bw = tw + padX * 2;
const int16_t bh = th + padY * 2;
const int16_t bx = x + display->getWidth() - bw - 2;
const int16_t by = y + display->getHeight() - bh - 2;
display->setColor(WHITE);
display->fillRect(bx + r, by, bw - r * 2, bh);
display->fillRect(bx, by + r, r, bh - r * 2);
display->fillRect(bx + bw - r, by + r, r, bh - r * 2);
display->fillCircle(bx + r, by + r, r);
display->fillCircle(bx + bw - r - 1, by + r, r);
display->fillCircle(bx + r, by + bh - r - 1, r);
display->fillCircle(bx + bw - r - 1, by + bh - r - 1, r);
display->setColor(BLACK);
display->drawString(bx + padX, by + padY, label);
}
// LEFT-SIDE DESTINATION-HINT BOX (“Dest: Shift + ◄”)
{
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_LEFT);
const char *label = "Dest: Shift + ";
int16_t labelW = display->getStringWidth(label);
// triangle size visually matches glyph height, not full line height
const int triH = FONT_HEIGHT_SMALL - 3;
const int triW = triH * 0.7;
const int16_t padX = 3;
const int16_t padY = 2;
const int16_t r = 3;
const int16_t bw = labelW + triW + padX * 2 + 2;
const int16_t bh = FONT_HEIGHT_SMALL + padY * 2;
const int16_t bx = x + 2;
const int16_t by = y + display->getHeight() - bh - 2;
// Rounded white box
display->setColor(WHITE);
display->fillRect(bx + r, by, bw - (r * 2), bh);
display->fillRect(bx, by + r, r, bh - (r * 2));
display->fillRect(bx + bw - r, by + r, r, bh - (r * 2));
display->fillCircle(bx + r, by + r, r);
display->fillCircle(bx + bw - r - 1, by + r, r);
display->fillCircle(bx + r, by + bh - r - 1, r);
display->fillCircle(bx + bw - r - 1, by + bh - r - 1, r);
// Draw text
display->setColor(BLACK);
display->drawString(bx + padX, by + padY, label);
// Perfectly center triangle on text baseline
int16_t tx = bx + padX + labelW;
int16_t ty = by + padY + (FONT_HEIGHT_SMALL / 2) - (triH / 2) - 1; // -1 for optical centering
// ◄ Left-pointing triangle
display->fillTriangle(tx + triW, ty, // top-right
tx, ty + triH / 2, // left center
tx + triW, ty + triH // bottom-right
);
}
#endif
// Draw Free Text input with multi-emote support and proper line wrapping
display->setColor(WHITE);
{
int inputY = 0 + y + FONT_HEIGHT_SMALL;

View File

@@ -168,7 +168,7 @@ int32_t ExternalNotificationModule::runOnce()
delay = EXT_NOTIFICATION_FAST_THREAD_MS;
#endif
#ifdef T_WATCH_S3
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
drv.go();
#endif
}
@@ -283,7 +283,7 @@ void ExternalNotificationModule::setExternalState(uint8_t index, bool on)
#ifdef UNPHONE
unphone.rgb(red, green, blue);
#endif
#ifdef T_WATCH_S3
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
if (on) {
drv.go();
} else {
@@ -310,8 +310,7 @@ void ExternalNotificationModule::stopNow()
rtttl::stop();
#ifdef HAS_I2S
LOG_INFO("Stop audioThread playback");
if (audioThread->isPlaying())
audioThread->stop();
audioThread->stop();
#endif
// Turn off all outputs
LOG_INFO("Turning off setExternalStates");
@@ -320,7 +319,7 @@ void ExternalNotificationModule::stopNow()
externalTurnedOn[i] = 0;
}
setIntervalFromNow(0);
#ifdef T_WATCH_S3
#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
drv.stop();
#endif
@@ -542,6 +541,19 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
(!isBroadcast(mp.to) && isToUs(&mp))) {
// Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us
isNagging = true;
#ifdef T_LORA_PAGER
if (canBuzz()) {
drv.setWaveform(0, 16); // Long buzzer 100%
drv.setWaveform(1, 0); // Pause
drv.setWaveform(2, 16);
drv.setWaveform(3, 0);
drv.setWaveform(4, 16);
drv.setWaveform(5, 0);
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 {

View File

@@ -5,7 +5,8 @@
#include "configuration.h"
#include "input/InputBroker.h"
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6)
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6) && \
!defined(CONFIG_IDF_TARGET_ESP32H2)
#include <NonBlockingRtttl.h>
#else
// Noop class for portduino.

View File

@@ -181,25 +181,25 @@ void setupModules()
// new ReplyModule();
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
#ifndef T_LORA_PAGER
rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
if (!rotaryEncoderInterruptImpl1->init()) {
delete rotaryEncoderInterruptImpl1;
rotaryEncoderInterruptImpl1 = nullptr;
}
#elif defined(T_LORA_PAGER)
#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;
}
#else
#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();

View File

@@ -0,0 +1,272 @@
#include "configuration.h"
#if HAS_SCREEN
#include "graphics/SharedUIDisplay.h"
#include "graphics/draw/NotificationRenderer.h"
#include "input/RotaryEncoderInterruptImpl1.h"
#include "input/UpDownInterruptImpl1.h"
#include "modules/OnScreenKeyboardModule.h"
#include <Arduino.h>
#include <algorithm>
namespace graphics
{
OnScreenKeyboardModule &OnScreenKeyboardModule::instance()
{
static OnScreenKeyboardModule inst;
return inst;
}
OnScreenKeyboardModule::~OnScreenKeyboardModule()
{
if (keyboard) {
delete keyboard;
keyboard = nullptr;
}
}
void OnScreenKeyboardModule::start(const char *header, const char *initialText, uint32_t durationMs,
std::function<void(const std::string &)> cb)
{
if (keyboard) {
delete keyboard;
keyboard = nullptr;
}
keyboard = new VirtualKeyboard();
callback = cb;
if (header)
keyboard->setHeader(header);
if (initialText)
keyboard->setInputText(initialText);
// Route VK submission/cancel events back into the module
keyboard->setCallback([this](const std::string &text) {
if (text.empty()) {
this->onCancel();
} else {
this->onSubmit(text);
}
});
// Maintain legacy compatibility hooks
NotificationRenderer::virtualKeyboard = keyboard;
NotificationRenderer::textInputCallback = callback;
}
void OnScreenKeyboardModule::stop(bool callEmptyCallback)
{
auto cb = callback;
callback = nullptr;
if (keyboard) {
delete keyboard;
keyboard = nullptr;
}
// Keep NotificationRenderer legacy pointers in sync
NotificationRenderer::virtualKeyboard = nullptr;
NotificationRenderer::textInputCallback = nullptr;
clearPopup();
if (callEmptyCallback && cb)
cb("");
}
void OnScreenKeyboardModule::handleInput(const InputEvent &event)
{
if (!keyboard)
return;
if (processVirtualKeyboardInput(event, keyboard))
return;
if (event.inputEvent == INPUT_BROKER_CANCEL)
onCancel();
}
bool OnScreenKeyboardModule::processVirtualKeyboardInput(const InputEvent &event, VirtualKeyboard *targetKeyboard)
{
if (!targetKeyboard)
return false;
switch (event.inputEvent) {
case INPUT_BROKER_UP:
case INPUT_BROKER_UP_LONG:
targetKeyboard->moveCursorUp();
return true;
case INPUT_BROKER_DOWN:
case INPUT_BROKER_DOWN_LONG:
targetKeyboard->moveCursorDown();
return true;
case INPUT_BROKER_LEFT:
case INPUT_BROKER_ALT_PRESS:
targetKeyboard->moveCursorLeft();
return true;
case INPUT_BROKER_RIGHT:
case INPUT_BROKER_USER_PRESS:
targetKeyboard->moveCursorRight();
return true;
case INPUT_BROKER_SELECT:
targetKeyboard->handlePress();
return true;
case INPUT_BROKER_SELECT_LONG:
targetKeyboard->handleLongPress();
return true;
default:
return false;
}
}
bool OnScreenKeyboardModule::draw(OLEDDisplay *display)
{
if (!keyboard)
return false;
// Timeout
if (keyboard->isTimedOut()) {
onCancel();
return false;
}
// Clear full screen behind keyboard
display->setColor(BLACK);
display->fillRect(0, 0, display->getWidth(), display->getHeight());
display->setColor(WHITE);
keyboard->draw(display, 0, 0);
// Draw popup overlay if needed
drawPopup(display);
return true;
}
void OnScreenKeyboardModule::onSubmit(const std::string &text)
{
auto cb = callback;
stop(false);
if (cb)
cb(text);
}
void OnScreenKeyboardModule::onCancel()
{
stop(true);
}
void OnScreenKeyboardModule::showPopup(const char *title, const char *content, uint32_t durationMs)
{
if (!title || !content)
return;
strncpy(popupTitle, title, sizeof(popupTitle) - 1);
popupTitle[sizeof(popupTitle) - 1] = '\0';
strncpy(popupMessage, content, sizeof(popupMessage) - 1);
popupMessage[sizeof(popupMessage) - 1] = '\0';
popupUntil = millis() + durationMs;
popupVisible = true;
}
void OnScreenKeyboardModule::clearPopup()
{
popupTitle[0] = '\0';
popupMessage[0] = '\0';
popupUntil = 0;
popupVisible = false;
}
void OnScreenKeyboardModule::drawPopupOverlay(OLEDDisplay *display)
{
// Only render the popup overlay (without drawing the keyboard)
drawPopup(display);
}
void OnScreenKeyboardModule::drawPopup(OLEDDisplay *display)
{
if (!popupVisible)
return;
if (millis() > popupUntil || popupMessage[0] == '\0') {
popupVisible = false;
return;
}
// Build lines and leverage NotificationRenderer inverted box drawing for consistent style
constexpr uint16_t maxContentLines = 3;
const bool hasTitle = popupTitle[0] != '\0';
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_LEFT);
const uint16_t maxWrapWidth = display->width() - 40;
auto wrapText = [&](const char *text, uint16_t availableWidth) -> std::vector<std::string> {
std::vector<std::string> wrapped;
std::string current;
std::string word;
const char *p = text;
while (*p && wrapped.size() < maxContentLines) {
while (*p && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
if (*p == '\n') {
if (!current.empty()) {
wrapped.push_back(current);
current.clear();
if (wrapped.size() >= maxContentLines)
break;
}
}
++p;
}
if (!*p || wrapped.size() >= maxContentLines)
break;
word.clear();
while (*p && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r')
word += *p++;
if (word.empty())
continue;
std::string test = current.empty() ? word : (current + " " + word);
uint16_t w = display->getStringWidth(test.c_str(), test.length(), true);
if (w <= availableWidth)
current = test;
else {
if (!current.empty()) {
wrapped.push_back(current);
current = word;
if (wrapped.size() >= maxContentLines)
break;
} else {
current = word;
while (current.size() > 1 &&
display->getStringWidth(current.c_str(), current.length(), true) > availableWidth)
current.pop_back();
}
}
}
if (!current.empty() && wrapped.size() < maxContentLines)
wrapped.push_back(current);
return wrapped;
};
std::vector<std::string> allLines;
if (hasTitle)
allLines.emplace_back(popupTitle);
char buf[sizeof(popupMessage)];
strncpy(buf, popupMessage, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
char *paragraph = strtok(buf, "\n");
while (paragraph && allLines.size() < maxContentLines + (hasTitle ? 1 : 0)) {
auto wrapped = wrapText(paragraph, maxWrapWidth);
for (const auto &ln : wrapped) {
if (allLines.size() >= maxContentLines + (hasTitle ? 1 : 0))
break;
allLines.push_back(ln);
}
paragraph = strtok(nullptr, "\n");
}
std::vector<const char *> ptrs;
for (const auto &ln : allLines)
ptrs.push_back(ln.c_str());
ptrs.push_back(nullptr);
// Use the standard notification box drawing from NotificationRenderer
NotificationRenderer::drawNotificationBox(display, nullptr, ptrs.data(), allLines.size(), 0, 0);
}
} // namespace graphics
#endif // HAS_SCREEN

View File

@@ -0,0 +1,55 @@
#pragma once
#include "configuration.h"
#if HAS_SCREEN
#include "graphics/Screen.h" // InputEvent
#include "graphics/VirtualKeyboard.h"
#include <OLEDDisplay.h>
#include <functional>
#include <string>
namespace graphics
{
class OnScreenKeyboardModule
{
public:
static OnScreenKeyboardModule &instance();
void start(const char *header, const char *initialText, uint32_t durationMs,
std::function<void(const std::string &)> callback);
void stop(bool callEmptyCallback);
void handleInput(const InputEvent &event);
static bool processVirtualKeyboardInput(const InputEvent &event, VirtualKeyboard *keyboard);
bool draw(OLEDDisplay *display);
void showPopup(const char *title, const char *content, uint32_t durationMs);
void clearPopup();
// Draw only the popup overlay (used when legacy virtualKeyboard draws the keyboard)
void drawPopupOverlay(OLEDDisplay *display);
private:
OnScreenKeyboardModule() = default;
~OnScreenKeyboardModule();
OnScreenKeyboardModule(const OnScreenKeyboardModule &) = delete;
OnScreenKeyboardModule &operator=(const OnScreenKeyboardModule &) = delete;
void onSubmit(const std::string &text);
void onCancel();
void drawPopup(OLEDDisplay *display);
VirtualKeyboard *keyboard = nullptr;
std::function<void(const std::string &)> callback;
char popupTitle[64] = {0};
char popupMessage[256] = {0};
uint32_t popupUntil = 0;
bool popupVisible = false;
};
} // namespace graphics
#endif // HAS_SCREEN

View File

@@ -71,7 +71,7 @@ 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)
#elif defined(CONFIG_IDF_TARGET_ESP32C6) || defined(RAK3172) || defined(EBYTE_E77_MBL) || defined(CONFIG_IDF_TARGET_ESP32H2)
SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial")
{
api_type = TYPE_SERIAL;
@@ -175,7 +175,7 @@ int32_t SerialModule::runOnce()
// Give it a chance to flush out 💩
delay(10);
}
#if defined(CONFIG_IDF_TARGET_ESP32C6)
#if defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2)
if (moduleConfig.serial.rxd && moduleConfig.serial.txd) {
Serial1.setRxBufferSize(RX_BUFFER);
Serial1.begin(baud, SERIAL_8N1, moduleConfig.serial.rxd, moduleConfig.serial.txd);
@@ -277,7 +277,7 @@ int32_t SerialModule::runOnce()
}
#endif
else {
#if defined(CONFIG_IDF_TARGET_ESP32C6)
#if defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2)
while (Serial1.available()) {
serialPayloadSize = Serial1.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
#else
@@ -538,7 +538,7 @@ void SerialModule::processWXSerial()
{
#if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) && \
!defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && \
!defined(ARCH_STM32WL) && !defined(MUZI_BASE)
!defined(ARCH_STM32WL) && !defined(MUZI_BASE) && !defined(CONFIG_IDF_TARGET_ESP32H2)
static unsigned int lastAveraged = 0;
static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded.
static double dir_sum_sin = 0;

View File

@@ -1,4 +1,5 @@
#include "SystemCommandsModule.h"
#include "input/InputBroker.h"
#include "meshUtils.h"
#if HAS_SCREEN
#include "graphics/Screen.h"
@@ -22,7 +23,7 @@ SystemCommandsModule::SystemCommandsModule()
int SystemCommandsModule::handleInputEvent(const InputEvent *event)
{
LOG_INFO("Input event %u! kb %u", event->inputEvent, event->kbchar);
LOG_INPUT("SystemCommands Input event %u! kb %u", event->inputEvent, event->kbchar);
// System commands (all others fall through)
switch (event->kbchar) {
// Fn key symbols

View File

@@ -651,8 +651,8 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
{
LOG_INFO("BLE incoming connection %s", connInfo.getAddress().toString().c_str());
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)
const uint16_t connHandle = connInfo.getConnHandle();
#if NIMBLE_ENABLE_2M_PHY && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6))
int phyResult =
ble_gap_set_prefered_le_phy(connHandle, BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_CODED_ANY);
if (phyResult == 0) {
@@ -660,6 +660,7 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
} else {
LOG_WARN("Failed to prefer 2M PHY for conn %u, rc=%d", connHandle, phyResult);
}
#endif
int dataLenResult = ble_gap_set_data_len(connHandle, kPreferredBleTxOctets, kPreferredBleTxTimeUs);
if (dataLenResult == 0) {
@@ -670,9 +671,10 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
LOG_INFO("BLE conn %u initial MTU %u (target %u)", connHandle, connInfo.getMTU(), kPreferredBleMtu);
pServer->updateConnParams(connHandle, 6, 12, 0, 200);
#endif
}
#endif
#ifdef NIMBLE_TWO
virtual void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason)
{
LOG_INFO("BLE disconnect reason: %d", reason);
@@ -818,7 +820,7 @@ void NimbleBluetooth::setup()
NimBLEDevice::init(getDeviceName());
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)
#if NIMBLE_ENABLE_2M_PHY && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6))
int mtuResult = NimBLEDevice::setMTU(kPreferredBleMtu);
if (mtuResult == 0) {
LOG_INFO("BLE MTU request set to %u", kPreferredBleMtu);

View File

@@ -101,8 +101,6 @@
#define HW_VENDOR meshtastic_HardwareModel_T_WATCH_S3
#elif defined(GENIEBLOCKS)
#define HW_VENDOR meshtastic_HardwareModel_GENIEBLOCKS
#elif defined(PRIVATE_HW)
#define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW
#elif defined(NANO_G1)
#define HW_VENDOR meshtastic_HardwareModel_NANO_G1
#elif defined(M5STACK)
@@ -205,6 +203,8 @@
#define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L
#elif defined(HELTEC_WIRELESS_TRACKER_V2)
#define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2
#else
#define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW
#endif
// -----------------------------------------------------------------------------

View File

@@ -169,6 +169,8 @@ void esp32Setup()
// #define APP_WATCHDOG_SECS 45
#define APP_WATCHDOG_SECS 90
// esp_task_wdt_init returns an unknown error, so skip it on ESP32H2
#ifndef CONFIG_IDF_TARGET_ESP32H2
#ifdef CONFIG_IDF_TARGET_ESP32C6
esp_task_wdt_config_t *wdt_config = (esp_task_wdt_config_t *)malloc(sizeof(esp_task_wdt_config_t));
wdt_config->timeout_ms = APP_WATCHDOG_SECS * 1000;
@@ -181,7 +183,7 @@ void esp32Setup()
#endif
res = esp_task_wdt_add(NULL);
assert(res == ESP_OK);
#endif
#if HAS_32768HZ
enableSlowCLK();
#endif
@@ -223,9 +225,10 @@ void cpuDeepSleep(uint32_t msecToWake)
13,
#endif
34, 35, 37};
#ifndef CONFIG_IDF_TARGET_ESP32H2
for (int i = 0; i < sizeof(rtcGpios); i++)
rtc_gpio_isolate((gpio_num_t)rtcGpios[i]);
#endif
#endif
// FIXME, disable internal rtc pullups/pulldowns on the non isolated pins. for inputs that we aren't using
@@ -258,10 +261,10 @@ void cpuDeepSleep(uint32_t msecToWake)
#endif // #end ESP32S3_WAKE_TYPE
#endif
#ifdef ESP_PD_DOMAIN_RTC_PERIPH
// We want RTC peripherals to stay on
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
#endif
esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs
esp_deep_sleep_start(); // TBD mA sleep current (battery)
}

View File

@@ -109,7 +109,7 @@
#elif defined(HELTEC_MESH_SOLAR)
#define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_SOLAR
#elif defined(MUZI_BASE)
#define HW_VENDOR meshtastic_HardwareModel_RESERVED_FRIED_CHICKEN
#define HW_VENDOR meshtastic_HardwareModel_MUZI_BASE
#else
#define HW_VENDOR meshtastic_HardwareModel_NRF52_UNKNOWN
#endif

View File

@@ -335,6 +335,16 @@ void cpuDeepSleep(uint32_t msecToWake)
if (Serial1) // A straightforward solution to the wake from deepsleep problem
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

View File

@@ -13,6 +13,7 @@
#define NUM_OCV_POINTS 11
#endif
// 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
@@ -24,18 +25,6 @@
#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
#elif defined(TRACKER_T1000_E)
#define OCV_ARRAY 4190, 4042, 3957, 3885, 3820, 3776, 3746, 3725, 3696, 3644, 3100
#elif defined(HELTEC_MESH_POCKET_BATTERY_5000)
#define OCV_ARRAY 4300, 4240, 4120, 4000, 3888, 3800, 3740, 3698, 3655, 3580, 3400
#elif defined(HELTEC_MESH_POCKET_BATTERY_10000)
#define OCV_ARRAY 4100, 4060, 3960, 3840, 3729, 3625, 3550, 3500, 3420, 3345, 3100
#elif defined(SEEED_WIO_TRACKER_L1)
#define OCV_ARRAY 4200, 3876, 3826, 3763, 3713, 3660, 3573, 3485, 3422, 3359, 3300
#elif defined(SEEED_SOLAR_NODE)
#define OCV_ARRAY 4200, 3986, 3922, 3812, 3734, 3645, 3527, 3420, 3281, 3087, 2786
#elif defined(WISMESH_TAG)
#define OCV_ARRAY 4240, 4112, 4029, 3970, 3906, 3846, 3824, 3802, 3776, 3650, 3072
#else // LiIon
#define OCV_ARRAY 4190, 4050, 3990, 3890, 3800, 3720, 3630, 3530, 3420, 3300, 3100
#endif

View File

@@ -388,10 +388,10 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r
uint64_t sleepUsec = sleepMsec * 1000LL;
// NOTE! ESP docs say we must disable bluetooth and wifi before light sleep
#ifdef ESP_PD_DOMAIN_RTC_PERIPH
// We want RTC peripherals to stay on
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
#endif
#if defined(BUTTON_PIN) && defined(BUTTON_NEED_PULLUP)
gpio_pullup_en((gpio_num_t)BUTTON_PIN);
#endif
@@ -523,6 +523,8 @@ void enableModemSleep()
esp32_config.max_freq_mhz = CONFIG_ESP32S2_DEFAULT_CPU_FREQ_MHZ;
#elif CONFIG_IDF_TARGET_ESP32C6
esp32_config.max_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ;
#elif CONFIG_IDF_TARGET_ESP32H2
esp32_config.max_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ;
#elif CONFIG_IDF_TARGET_ESP32C3
esp32_config.max_freq_mhz = CONFIG_ESP32C3_DEFAULT_CPU_FREQ_MHZ;
#else

View File

@@ -62,10 +62,12 @@
#define TFT_OFFSET_X 0
#define TFT_OFFSET_Y 0
#define TFT_INVERT false
#define FORCE_LOW_RES 1
#define SCREEN_ROTATE
#define SCREEN_TRANSITION_FRAMERATE 5 // fps
#define DISPLAY_FORCE_SMALL_FONTS
#define TFT_BACKLIGHT_ON LOW
#define USE_TFTDISPLAY 1
// Battery

View File

@@ -2,10 +2,16 @@
[esp32_base]
extends = arduino_base
custom_esp32_kind = esp32
custom_mtjson_part =
platform =
# renovate: datasource=custom.pio depName=platformio/espressif32 packageName=platformio/platform/espressif32
platformio/espressif32@6.11.0
extra_scripts =
${env.extra_scripts}
pre:extra_scripts/esp32_pre.py
extra_scripts/esp32_extra.py
build_src_filter =
${arduino_base.build_src_filter} -<platform/nrf52/> -<platform/stm32wl> -<platform/rp2xx0> -<mesh/eth/> -<mesh/raspihttp>

View File

@@ -7,7 +7,6 @@ build_src_filter =
build_flags =
${esp32_base.build_flags}
-I variants/esp32/m5stack_core
-DILI9341_DRIVER
-DM5STACK
-DUSER_SETUP_LOADED
-DTFT_SDA_READ

View File

@@ -34,11 +34,13 @@
#define GPS_RX_PIN 16
#define GPS_TX_PIN 17
#define ILI9341_DRIVER
#define TFT_HEIGHT 240
#define TFT_WIDTH 320
#define TFT_OFFSET_X 0
#define TFT_OFFSET_Y 0
#define TFT_BUSY -1
#define USE_TFTDISPLAY 1
// LCD screens are slow, so slowdown the wipe so it looks better
#define SCREEN_TRANSITION_FRAMERATE 1 // fps

View File

@@ -34,6 +34,7 @@
#define ST7789_SCK 18
#define ST7789_CS 5
#define ST7789_RS 26
#define USE_TFTDISPLAY 1
// I don't have a 'wiphone' but this I think should not be defined this way (don't set TFT_BL if we don't have a hw way to control
// it)
// #define ST7789_BL -1 // EXTENDER_PIN(9)

View File

@@ -22,8 +22,6 @@ build_flags =
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MODE=1
-D HAS_BLUETOOTH=1
-D MESHTASTIC_EXCLUDE_WEBSERVER
-D MESHTASTIC_EXCLUDE_MQTT
-DCONFIG_BT_NIMBLE_EXT_ADV=1
-DCONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2
-D NIMBLE_TWO

View File

@@ -0,0 +1,47 @@
[esp32h2_base]
extends = esp32_base
platform =
# Do not renovate until we have switched to pioarduino tagged builds
https://github.com/Jason2866/platform-espressif32/archive/39475a7213fa3a52b0c2048d1a31c215fc85fcf8.zip
build_flags =
${arduino_base.build_flags}
-Wall
-Wextra
-Isrc/platform/esp32
-std=c++11
-DESP_OPENSSL_SUPPRESS_LEGACY_WARNING
-DSERIAL_BUFFER_SIZE=4096
-DLIBPAX_ARDUINO
-DLIBPAX_WIFI
-DLIBPAX_BLE
-DMESHTASTIC_EXCLUDE_WEBSERVER
;-DDEBUG_HEAP
; TEMP
-DHAS_BLUETOOTH=0
-DMESHTASTIC_EXCLUDE_PAXCOUNTER
-DMESHTASTIC_EXCLUDE_BLUETOOTH
lib_deps =
${arduino_base.lib_deps}
${networking_base.lib_deps}
${environmental_base.lib_deps}
${environmental_extra.lib_deps}
${radiolib_base.lib_deps}
# renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
lewisxhe/XPowersLib@0.3.1
# 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
rweather/Crypto@0.4.0
build_src_filter =
${esp32_base.build_src_filter} -<mesh/http>
monitor_speed = 460800
monitor_filters = esp32_h2_exception_decoder
lib_ignore =
NonBlockingRTTTL
NimBLE-Arduino
libpax

View File

@@ -0,0 +1,11 @@
[env:waveshare-esp32h2]
extends = esp32h2_base
board = esp32-h2-devkitm-1
board_build.f_flash = 16000000L
board_level = pr
build_flags =
${esp32h2_base.build_flags}
-I variants/esp32h2/waveshare-esp32-h2
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MODE=1
-DHAS_WIFI=0

View File

@@ -0,0 +1,8 @@
#define HAS_SCREEN 0
#define HAS_WIFI 0
#define LORA_SCK 4
#define LORA_MISO 3
#define LORA_MOSI 2
#define LORA_CS 1

View File

@@ -0,0 +1,59 @@
#ifndef Pins_Arduino_h
#define Pins_Arduino_h
#include <stdint.h>
#define USB_VID 0x303a
#define USB_PID 0x1001
// static const uint8_t TX = 43;
// static const uint8_t RX = 44;
static const uint8_t SDA = 47;
static const uint8_t SCL = 14;
// Default SPI will be mapped to Radio
static const uint8_t SS = 17;
static const uint8_t MOSI = 3;
static const uint8_t MISO = 9;
static const uint8_t SCK = 8;
static const uint8_t A0 = 1;
static const uint8_t A1 = 2;
static const uint8_t A2 = 3;
static const uint8_t A3 = 4;
static const uint8_t A4 = 5;
static const uint8_t A5 = 6;
static const uint8_t A6 = 7;
static const uint8_t A7 = 8;
static const uint8_t A8 = 9;
static const uint8_t A9 = 10;
static const uint8_t A10 = 11;
static const uint8_t A11 = 12;
static const uint8_t A12 = 13;
static const uint8_t A13 = 14;
static const uint8_t A14 = 15;
static const uint8_t A15 = 16;
static const uint8_t A16 = 17;
static const uint8_t A17 = 18;
static const uint8_t A18 = 19;
static const uint8_t A19 = 20;
static const uint8_t T1 = 1;
static const uint8_t T2 = 2;
static const uint8_t T3 = 3;
static const uint8_t T4 = 4;
static const uint8_t T5 = 5;
static const uint8_t T6 = 6;
static const uint8_t T7 = 7;
static const uint8_t T8 = 8;
static const uint8_t T9 = 9;
static const uint8_t T10 = 10;
static const uint8_t T11 = 11;
static const uint8_t T12 = 12;
static const uint8_t T13 = 13;
static const uint8_t T14 = 14;
// static const uint8_t BAT_ADC_PIN = 4;
#endif /* Pins_Arduino_h */

View File

@@ -0,0 +1,15 @@
; Hackaday Communicator
[env:hackaday-communicator]
extends = esp32s3_base
board = hackaday-communicator
board_check = true
board_build.partitions = default_16MB.csv
upload_protocol = esptool
build_flags = ${esp32s3_base.build_flags}
-D HACKADAY_COMMUNICATOR
-D BOARD_HAS_PSRAM
-I variants/esp32s3/hackaday-communicator
lib_deps = ${esp32s3_base.lib_deps}
https://github.com/meshtastic/Arduino_GFX/archive/054e81ffaf23784830a734e3c184346789349406.zip

View File

@@ -0,0 +1,61 @@
#define TFT_BL 2
#define SPI_FREQUENCY 2000000
#define SPI_READ_FREQUENCY 16000000
#define TFT_HEIGHT 142
#define TFT_WIDTH 428
#define TFT_OFFSET_X 0
#define TFT_OFFSET_Y 0
#define TFT_OFFSET_ROTATION 0
#define SCREEN_TRANSITION_FRAMERATE 5
#define HAS_SCREEN 1
#define TFT_BLACK 0
#define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness
#define USE_TFTDISPLAY 1
#define USE_POWERSAVE
#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
// LoRa
#define USE_SX1262
#define LORA_SCK 8
#define LORA_MISO 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
#define SX126X_BUSY LORA_DIO2
#define SX126X_RESET LORA_RESET
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
// #define LED_PIN 1

Some files were not shown because too many files have changed in this diff Show More