kopia lustrzana https://github.com/meshtastic/firmware
Merge branch 'master' into store-and-forward
commit
d237d4f311
|
@ -29,7 +29,11 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
|||
gpg \
|
||||
gnupg2 \
|
||||
libusb-1.0-0-dev \
|
||||
libuv1-dev \
|
||||
libi2c-dev \
|
||||
libxcb-xkb-dev \
|
||||
libxkbcommon-dev \
|
||||
libinput-dev \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN pipx install platformio
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
git submodule update --init
|
||||
|
||||
pip install --no-cache-dir setuptools
|
||||
pipx install esptool
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
* text=auto eol=lf
|
||||
*.{cmd,[cC][mM][dD]} text eol=crlf
|
||||
*.{bat,[bB][aA][tT]} text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
*.bat text eol=crlf
|
||||
*.ps1 text eol=crlf
|
||||
*.{sh,[sS][hH]} text eol=lf
|
||||
|
|
|
@ -72,6 +72,15 @@ body:
|
|||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: mui
|
||||
attributes:
|
||||
label: Is this bug report about any UI component firmware like InkHUD or Meshtatic UI (MUI)?
|
||||
options:
|
||||
- label: Meshtastic UI aka MUI colorTFT
|
||||
- label: InkHUD ePaper
|
||||
- label: OLED slide UI on any display
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
|
|
|
@ -11,4 +11,4 @@ runs:
|
|||
- name: Install libs needed for native build
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev
|
||||
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev libuv1-dev
|
||||
|
|
|
@ -19,6 +19,8 @@ updates:
|
|||
interval: daily
|
||||
time: "05:00"
|
||||
timezone: US/Pacific
|
||||
ignore:
|
||||
- dependency-name: protobufs
|
||||
- package-ecosystem: github-actions
|
||||
directory: /.github/workflows
|
||||
schedule:
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
## 🙏 Thank you for sending in a pull request, here's some tips to get started!
|
||||
|
||||
### ❌ (Please delete all these tips and replace them with your text) ❌
|
||||
|
||||
## Thank you for sending in a pull request, here's some tips to get started!
|
||||
|
||||
- Before starting on some new big chunk of code, it it is optional but highly recommended to open an issue first
|
||||
to say "Hey, I think this idea X should be implemented and I'm starting work on it. My general plan is Y, any feedback
|
||||
is appreciated." This will allow other devs to potentially save you time by not accidentially duplicating work etc...
|
||||
|
@ -12,4 +11,17 @@
|
|||
- If your PR fixes a bug, mention "fixes #bugnum" somewhere in your pull request description.
|
||||
- If your other co-developers have comments on your PR please tweak as needed.
|
||||
- Please also enable "Allow edits by maintainers".
|
||||
- Please do not submit untested code.
|
||||
- If you do not have the affected hardware to test your code changes adequately against regressions, please indicate this, so that contributors and commnunity members can help test your changes.
|
||||
- If your PR gets accepted you can request a "Contributor" role in the Meshtastic Discord
|
||||
|
||||
|
||||
## 🤝 Attestations
|
||||
- [ ] I have tested that my proposed changes behave as described.
|
||||
- [ ] I have tested that my proposed changes do not cause any obvious regressions on the following devices:
|
||||
- [ ] Heltec (Lora32) V3
|
||||
- [ ] LilyGo T-Deck
|
||||
- [ ] LilyGo T-Beam
|
||||
- [ ] RAK WisBlock 4631
|
||||
- [ ] Seeed Studio T-1000E tracker card
|
||||
- [ ] Other (please specify below)
|
||||
|
|
|
@ -4,7 +4,7 @@ on:
|
|||
workflow_call:
|
||||
secrets:
|
||||
PPA_GPG_PRIVATE_KEY:
|
||||
required: true
|
||||
required: false
|
||||
inputs:
|
||||
series:
|
||||
description: Ubuntu/Debian series to target
|
||||
|
|
|
@ -136,6 +136,7 @@ jobs:
|
|||
secrets: inherit
|
||||
|
||||
package-pio-deps-native-tft:
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/package_pio_deps.yml
|
||||
with:
|
||||
pio_env: native-tft
|
||||
|
@ -329,13 +330,13 @@ jobs:
|
|||
with:
|
||||
pattern: platformio-deps-native-tft-${{ steps.version.outputs.long }}
|
||||
merge-multiple: true
|
||||
path: ./output/pio-deps-native
|
||||
path: ./output/pio-deps-native-tft
|
||||
|
||||
- name: Zip linux sources
|
||||
working-directory: output
|
||||
run: |
|
||||
zip -j -9 -r ./meshtasticd-${{ steps.version.outputs.deb }}-src.zip ./debian-src
|
||||
zip -9 -r ./platformio-deps-native-${{ steps.version.outputs.long }}.zip ./pio-deps-native
|
||||
zip -9 -r ./platformio-deps-native-tft-${{ steps.version.outputs.long }}.zip ./pio-deps-native-tft
|
||||
|
||||
# For diagnostics
|
||||
- name: Display structure of downloaded files
|
||||
|
@ -344,32 +345,10 @@ jobs:
|
|||
- name: Add linux sources to release
|
||||
run: |
|
||||
gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd-${{ steps.version.outputs.deb }}-src.zip
|
||||
gh release upload v${{ steps.version.outputs.long }} ./output/platformio-deps-native-${{ steps.version.outputs.long }}.zip
|
||||
gh release upload v${{ steps.version.outputs.long }} ./output/platformio-deps-native-tft-${{ steps.version.outputs.long }}.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Bump version.properties
|
||||
run: >-
|
||||
bin/bump_version.py
|
||||
|
||||
- name: Ensure debian deps are installed
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update -y --fix-missing
|
||||
sudo apt-get install -y devscripts
|
||||
|
||||
- name: Update debian changelog
|
||||
run: >-
|
||||
debian/ci_changelog.sh
|
||||
|
||||
- name: Create version.properties pull request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
title: Bump version.properties
|
||||
add-paths: |
|
||||
version.properties
|
||||
debian/changelog
|
||||
|
||||
release-firmware:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
|
|
@ -43,3 +43,49 @@ jobs:
|
|||
copr_project: |-
|
||||
${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
|
||||
secrets: inherit
|
||||
|
||||
# Create a PR to bump version when a release is Published
|
||||
bump-version:
|
||||
if: ${{ github.event.release.published }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- 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
|
||||
|
||||
- name: Bump version.properties
|
||||
run: >-
|
||||
bin/bump_version.py
|
||||
|
||||
- name: Ensure debian deps are installed
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update -y --fix-missing
|
||||
sudo apt-get install -y devscripts
|
||||
|
||||
- name: Update debian changelog
|
||||
run: >-
|
||||
debian/ci_changelog.sh
|
||||
|
||||
- name: Create version.properties pull request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
title: Bump version.properties
|
||||
add-paths: |
|
||||
version.properties
|
||||
debian/changelog
|
||||
|
|
|
@ -6,11 +6,14 @@ on:
|
|||
schedule:
|
||||
- cron: 0 1 * * 6
|
||||
|
||||
permissions: read-all
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
semgrep-full:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
container:
|
||||
image: semgrep/semgrep
|
||||
|
||||
|
|
|
@ -18,5 +18,6 @@ jobs:
|
|||
- name: Stale PR+Issues
|
||||
uses: actions/stale@v9.1.0
|
||||
with:
|
||||
days-before-stale: 45
|
||||
exempt-issue-labels: pinned,3.0
|
||||
exempt-pr-labels: pinned,3.0
|
||||
|
|
|
@ -143,7 +143,7 @@ jobs:
|
|||
merge-multiple: true
|
||||
|
||||
- name: Test Report
|
||||
uses: dorny/test-reporter@v1.9.1
|
||||
uses: dorny/test-reporter@v2.0.0
|
||||
with:
|
||||
name: PlatformIO Tests
|
||||
path: testreport.xml
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
[submodule "protobufs"]
|
||||
path = protobufs
|
||||
url = https://github.com/meshtastic/protobufs.git
|
||||
[submodule "lib/device-ui"]
|
||||
path = lib/device-ui
|
||||
url = https://github.com/meshtastic/device-ui.git
|
||||
[submodule "meshtestic"]
|
||||
path = meshtestic
|
||||
url = https://github.com/meshtastic/meshTestic
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
Checks: >-
|
||||
bugprone-*,
|
||||
cppcoreguidelines-*,
|
||||
google-*,
|
||||
misc-*,
|
||||
modernize-*,
|
||||
performance-*,
|
||||
readability-*,
|
||||
-bugprone-lambda-function-name,
|
||||
-bugprone-reserved-identifier,
|
||||
-cppcoreguidelines-avoid-goto,
|
||||
-cppcoreguidelines-avoid-magic-numbers,
|
||||
-cppcoreguidelines-avoid-non-const-global-variables,
|
||||
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||
-cppcoreguidelines-pro-type-vararg,
|
||||
-google-readability-braces-around-statements,
|
||||
-google-readability-function-size,
|
||||
-misc-no-recursion,
|
||||
-modernize-return-braced-init-list,
|
||||
-modernize-use-nodiscard,
|
||||
-modernize-use-trailing-return-type,
|
||||
-performance-unnecessary-value-param,
|
||||
-readability-magic-numbers,
|
||||
|
||||
CheckOptions:
|
||||
- key: readability-function-cognitive-complexity.Threshold
|
||||
value: 100
|
||||
- key: readability-function-cognitive-complexity.IgnoreMacros
|
||||
value: true
|
||||
# Set naming conventions for your style below (there are dozens of naming settings possible):
|
||||
# See https://clang.llvm.org/extra/clang-tidy/checks/readability/identifier-naming.html
|
||||
# - key: readability-identifier-naming.ClassCase
|
||||
# value: CamelCase
|
||||
# - key: readability-identifier-naming.NamespaceCase
|
||||
# value: lower_case
|
||||
# - key: readability-identifier-naming.PrivateMemberSuffix
|
||||
# value: _
|
||||
# - key: readability-identifier-naming.StructCase
|
||||
# value: CamelCase
|
|
@ -1,6 +1,6 @@
|
|||
version: 0.1
|
||||
cli:
|
||||
version: 1.22.10
|
||||
version: 1.22.11
|
||||
plugins:
|
||||
sources:
|
||||
- id: trunk
|
||||
|
@ -8,15 +8,15 @@ plugins:
|
|||
uri: https://github.com/trunk-io/plugins
|
||||
lint:
|
||||
enabled:
|
||||
- prettier@3.5.2
|
||||
- trufflehog@3.88.14
|
||||
- yamllint@1.35.1
|
||||
- prettier@3.5.3
|
||||
- trufflehog@3.88.18
|
||||
- yamllint@1.37.0
|
||||
- bandit@1.8.3
|
||||
- checkov@3.2.378
|
||||
- checkov@3.2.392
|
||||
- terrascan@1.19.9
|
||||
- trivy@0.59.1
|
||||
- trivy@0.60.0
|
||||
- taplo@0.9.3
|
||||
- ruff@0.9.7
|
||||
- ruff@0.11.2
|
||||
- isort@6.0.1
|
||||
- markdownlint@0.44.0
|
||||
- oxipng@9.1.4
|
||||
|
@ -28,9 +28,8 @@ lint:
|
|||
- shellcheck@0.10.0
|
||||
- black@25.1.0
|
||||
- git-diff-check
|
||||
- gitleaks@8.24.0
|
||||
- gitleaks@8.24.2
|
||||
- clang-format@16.0.3
|
||||
- clang-tidy@16.0.3
|
||||
ignore:
|
||||
- linters: [ALL]
|
||||
paths:
|
||||
|
|
|
@ -7,5 +7,8 @@
|
|||
"cmake.configureOnOpen": false,
|
||||
"[cpp]": {
|
||||
"editor.defaultFormatter": "trunk.io"
|
||||
},
|
||||
"[powershell]": {
|
||||
"editor.defaultFormatter": "ms-vscode.powershell"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ ENV TZ=Etc/UTC
|
|||
ENV PIP_ROOT_USER_ACTION=ignore
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
wget g++ zip git ca-certificates \
|
||||
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev \
|
||||
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
|
||||
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev pkg-config \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
&& pip install --no-cache-dir -U platformio \
|
||||
|
@ -38,7 +38,7 @@ ENV TZ=Etc/UTC
|
|||
USER root
|
||||
|
||||
RUN apt-get update && apt-get --no-install-recommends -y install \
|
||||
libc-bin libc6 libgpiod2 libyaml-cpp0.7 libi2c0 libulfius2.7 libusb-1.0-0-dev liborcania2.3 libssl3 \
|
||||
libc-bin libc6 libgpiod2 libyaml-cpp0.7 libi2c0 libuv1 libusb-1.0-0-dev liborcania2.3 libulfius2.7 libssl3 \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /var/lib/meshtasticd \
|
||||
&& mkdir -p /etc/meshtasticd/config.d \
|
||||
|
|
|
@ -9,7 +9,7 @@ FROM python:3.13-alpine3.21 AS builder
|
|||
ENV PIP_ROOT_USER_ACTION=ignore
|
||||
RUN apk --no-cache add \
|
||||
bash g++ libstdc++-dev linux-headers zip git ca-certificates libgpiod-dev yaml-cpp-dev bluez-dev \
|
||||
libusb-dev i2c-tools-dev openssl-dev pkgconf argp-standalone \
|
||||
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& pip install --no-cache-dir -U platformio \
|
||||
&& mkdir /tmp/firmware
|
||||
|
@ -32,7 +32,7 @@ FROM alpine:3.21
|
|||
USER root
|
||||
|
||||
RUN apk --no-cache add \
|
||||
libstdc++ libgpiod yaml-cpp libusb i2c-tools \
|
||||
libstdc++ libgpiod yaml-cpp libusb i2c-tools libuv \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& mkdir -p /var/lib/meshtasticd \
|
||||
&& mkdir -p /etc/meshtasticd/config.d \
|
||||
|
|
|
@ -17,7 +17,6 @@ build_flags =
|
|||
-DLFS_NO_ASSERT ; Disable LFS assertions , see https://github.com/meshtastic/firmware/pull/3818
|
||||
-DMESHTASTIC_EXCLUDE_AUDIO=1
|
||||
-DMESHTASTIC_EXCLUDE_PAXCOUNTER=1
|
||||
-DMAX_NUM_NODES=80
|
||||
|
||||
build_src_filter =
|
||||
${arduino_base.build_src_filter} -<platform/esp32/> -<platform/stm32wl> -<nimble/> -<mesh/wifi/> -<mesh/api/> -<mesh/http/> -<modules/esp32> -<platform/rp2xx0> -<mesh/eth/> -<mesh/raspihttp>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
; The Portduino based 'native' environment. Currently supported on Linux targets with real LoRa hardware (or simulated).
|
||||
[portduino_base]
|
||||
platform = https://github.com/meshtastic/platform-native.git#562d189828f09fbf4c4093b3c0104bae9d8e9ff9
|
||||
platform = https://github.com/meshtastic/platform-native.git#e82ba1a19b6cd1dc55cbde29b33ea8dd0640014f
|
||||
framework = arduino
|
||||
|
||||
build_src_filter =
|
||||
|
@ -34,10 +34,12 @@ build_flags =
|
|||
-Isrc/platform/portduino
|
||||
-DRADIOLIB_EEPROM_UNSUPPORTED
|
||||
-DPORTDUINO_LINUX_HARDWARE
|
||||
-DHAS_UDP_MULTICAST
|
||||
-lpthread
|
||||
-lstdc++fs
|
||||
-lbluetooth
|
||||
-lgpiod
|
||||
-lyaml-cpp
|
||||
-li2c
|
||||
-luv
|
||||
-std=c++17
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
; Common settings for rp2040 Processor based targets
|
||||
[rp2040_base]
|
||||
platform = https://github.com/maxgerhardt/platform-raspberrypi.git#19e30129fb1428b823be585c787dcb4ac0d9014c ; For arduino-pico >=4.2.1
|
||||
platform = https://github.com/maxgerhardt/platform-raspberrypi.git#76ecf3c7e9dd4503af0331154c4ca1cddc4b03e5 ; For arduino-pico >= 4.4.3
|
||||
extends = arduino_base
|
||||
platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#6024e9a7e82a72e38dd90f42029ba3748835eb2e ; 4.3.0 with fix MDNS
|
||||
platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#4.4.3
|
||||
|
||||
board_build.core = earlephilhower
|
||||
board_build.filesystem_size = 0.5m
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
[stm32_base]
|
||||
extends = arduino_base
|
||||
platform = platformio/ststm32
|
||||
platform_packages = platformio/framework-arduinoststm32@^4.20900.0
|
||||
platform = ststm32
|
||||
platform_packages = platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32.git#2.9.0
|
||||
extra_scripts =
|
||||
${env.extra_scripts}
|
||||
post:extra_scripts/extra_stm32.py
|
||||
|
||||
build_type = release
|
||||
|
||||
;board_build.flash_offset = 0x08000000
|
||||
|
||||
build_flags =
|
||||
build_flags =
|
||||
${arduino_base.build_flags}
|
||||
-flto
|
||||
-Isrc/platform/stm32wl -g
|
||||
|
@ -18,27 +19,24 @@ build_flags =
|
|||
-DMESHTASTIC_EXCLUDE_SCREEN
|
||||
-DMESHTASTIC_EXCLUDE_MQTT
|
||||
-DMESHTASTIC_EXCLUDE_BLUETOOTH
|
||||
-DMESHTASTIC_EXCLUDE_PKI
|
||||
-DMESHTASTIC_EXCLUDE_GPS
|
||||
; -DVECT_TAB_OFFSET=0x08000000
|
||||
-DconfigUSE_CMSIS_RTOS_V2=1
|
||||
; -DSPI_MODE_0=SPI_MODE0
|
||||
;-DDEBUG_MUTE
|
||||
-fmerge-all-constants
|
||||
-ffunction-sections
|
||||
-fdata-sections
|
||||
|
||||
build_src_filter =
|
||||
|
||||
build_src_filter =
|
||||
${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<mesh/api/> -<mesh/wifi/> -<mesh/http/> -<modules/esp32> -<mesh/eth/> -<input> -<buzz> -<modules/RemoteHardwareModule.cpp> -<platform/nrf52> -<platform/portduino> -<platform/rp2xx0> -<mesh/raspihttp>
|
||||
|
||||
board_upload.offset_address = 0x08000000
|
||||
upload_protocol = stlink
|
||||
debug_tool = stlink
|
||||
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
charlesbaynham/OSFS@^1.2.3
|
||||
jgromes/RadioLib@7.0.2
|
||||
https://github.com/caveman99/Crypto.git#f61ae26a53f7a2d0ba5511625b8bf8eff3a35d5e
|
||||
${radiolib_base.lib_deps}
|
||||
https://github.com/caveman99/Crypto.git#eae9c768054118a9399690f8af202853d1ae8516
|
||||
|
||||
lib_ignore =
|
||||
mathertel/OneButton@2.6.1
|
||||
Wire
|
||||
Wire
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Module: RF95 # Adafruit RFM9x
|
||||
# Reset: 25
|
||||
# CS: 7
|
||||
# IRQ: 22
|
||||
# Busy: 23
|
||||
Lora:
|
||||
Module: RF95 # Adafruit RFM9x
|
||||
Reset: 25
|
||||
CS: 7
|
||||
IRQ: 22
|
||||
# Busy: 23
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
Lora:
|
||||
|
||||
### Raxda Rock 2F running Armbian Linux 6.1.99-vendor-rk35xx
|
||||
### https://github.com/markbirss/rock-2f
|
||||
### https://github.com/markbirss/lora-starter-edition-sx1262-i2c
|
||||
### https://github.com/radxa-pkg/radxa-overlays/blob/main/arch/arm64/boot/dts/rockchip/overlays/rk3528-spi0-cs1-spidev.dts
|
||||
### Require install of https://github.com/radxa-pkg/radxa-overlays and rk3528-spi0-cs1-spidev.dtbo copied to /boot/dtb/rockchip/overlay and enabled
|
||||
### in /boot/armbianEnv.txt - overlays=rk3528-spi0-cs1-spidev
|
||||
### The Radxa Rock 2F employs multiple gpio chips.
|
||||
### Each gpio pin must be unique, but can be assigned to a specific gpio chip and line.
|
||||
### In case solely a no. is given, the default gpio chip and pin == line will be employed.
|
||||
###
|
||||
Module: sx1262 # Radxa Rock 2F + Starter Edition SX1262 HAT by Mark Birss
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
DIO3_TCXO_VOLTAGE: 1.8
|
||||
spidev: spidev0.1
|
||||
CS: # NSS PIN_24 -> chip 4, line 14
|
||||
pin: 24
|
||||
gpiochip: 4
|
||||
line: 14
|
||||
SCK: # SCK PIN_23 -> chip 4, line 12
|
||||
pin: 23
|
||||
gpiochip: 4
|
||||
line: 12
|
||||
Busy: # BUSY PIN_7 -> chip 4, line 6
|
||||
pin: 7
|
||||
gpiochip: 4
|
||||
line: 6
|
||||
MOSI: # MOSI PIN_19 -> chip 4, line 10
|
||||
pin: 19
|
||||
gpiochip: 4
|
||||
line: 10
|
||||
MISO: # MISO PIN_21 -> chip 4, line 11
|
||||
pin: 21
|
||||
gpiochip: 4
|
||||
line: 11
|
||||
Reset: # NRST PIN_12 -> chip 1, line 13
|
||||
pin: 12
|
||||
gpiochip: 1
|
||||
line: 13
|
||||
IRQ: # DIO1 PIN_15 -> chip 4, line 22
|
||||
pin: 15
|
||||
gpiochip: 4
|
||||
line: 22
|
||||
# RXen: # RXEN PIN_22 -> chip 3!, line 17
|
||||
# pin: 22
|
||||
# gpiochip: 3
|
||||
# line: 17
|
||||
# TXen: RADIOLIB_NC # TXEN no PIN, no line, fallback to default gpio chip
|
|
@ -0,0 +1,17 @@
|
|||
Lora:
|
||||
Module: sx1262
|
||||
CS: 0
|
||||
IRQ: 6
|
||||
Reset: 2
|
||||
Busy: 4
|
||||
RXen: 1
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
spidev: ch341
|
||||
USB_PID: 0x5512
|
||||
USB_VID: 0x1A86
|
||||
# Optional: Reduce power to 10 dBm to
|
||||
# avoid over-drawing the USB port
|
||||
# SX126X_MAX_POWER: 10
|
||||
# Optional: Set the serial number for multi-radio support
|
||||
# USB_Serialnum: 13374201
|
|
@ -1,72 +1,295 @@
|
|||
@ECHO OFF
|
||||
SETLOCAL EnableDelayedExpansion
|
||||
TITLE Meshtastic device-install
|
||||
|
||||
set PYTHON=python
|
||||
set WEB_APP=0
|
||||
SET "SCRIPT_NAME=%~nx0"
|
||||
SET "DEBUG=0"
|
||||
SET "PYTHON="
|
||||
SET "WEB_APP=0"
|
||||
SET "TFT_BUILD=0"
|
||||
SET "BIGDB8=0"
|
||||
SET "BIGDB16=0"
|
||||
SET "ESPTOOL_BAUD=115200"
|
||||
SET "ESPTOOL_CMD="
|
||||
SET "LOGCOUNTER=0"
|
||||
|
||||
:: Determine the correct esptool command to use
|
||||
where esptool >nul 2>&1
|
||||
if %ERRORLEVEL% EQU 0 (
|
||||
set "ESPTOOL_CMD=esptool"
|
||||
) else (
|
||||
set "ESPTOOL_CMD=%PYTHON% -m esptool"
|
||||
)
|
||||
@REM FIXME: Determine mcu from PlatformIO variant, this is unmaintainable.
|
||||
SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone"
|
||||
SET "C3=esp32c3"
|
||||
@REM FIXME: Determine flash size from PlatformIO variant, this is unmaintainable.
|
||||
SET "BIGDB_8MB=picomputer-s3 unphone seeed-sensecap-indicator 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 t-watch-s3 tracksenger"
|
||||
SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite"
|
||||
|
||||
goto GETOPTS
|
||||
:HELP
|
||||
echo Usage: %~nx0 [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME^|FILENAME] [--web]
|
||||
echo Flash image file to device, but first erasing and writing system information
|
||||
echo.
|
||||
echo -h Display this help and exit
|
||||
echo -p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerrous).
|
||||
echo -P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: %PYTHON%)
|
||||
echo -f FILENAME The .bin file to flash. Custom to your device type and region.
|
||||
echo --web Flash WEB APP.
|
||||
goto EOF
|
||||
GOTO getopts
|
||||
:help
|
||||
ECHO Flash image file to device, but first erasing and writing system information.
|
||||
ECHO.
|
||||
ECHO Usage: %SCRIPT_NAME% -f filename [-p PORT] [-P python] (--web)
|
||||
ECHO.
|
||||
ECHO Options:
|
||||
ECHO -f filename The firmware .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).
|
||||
ECHO -P python Specify alternate python interpreter to use to invoke esptool. (default: python)
|
||||
ECHO If supplied the script will use python.
|
||||
ECHO If not supplied the script will try to find esptool in Path.
|
||||
ECHO --web Enable WebUI. (default: false)
|
||||
ECHO.
|
||||
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 --web
|
||||
GOTO eof
|
||||
|
||||
:GETOPTS
|
||||
if /I "%1"=="-h" goto HELP
|
||||
if /I "%1"=="--help" goto HELP
|
||||
if /I "%1"=="-F" set "FILENAME=%2" & SHIFT
|
||||
if /I "%1"=="-p" set ESPTOOL_PORT=%2 & SHIFT
|
||||
if /I "%1"=="-P" set PYTHON=%2 & SHIFT
|
||||
if /I "%1"=="--web" set WEB_APP=1 & SHIFT
|
||||
:version
|
||||
ECHO %SCRIPT_NAME% [Version 2.6.1]
|
||||
ECHO Meshtastic
|
||||
GOTO eof
|
||||
|
||||
:getopts
|
||||
IF "%~1"=="" GOTO endopts
|
||||
IF /I "%~1"=="-?" GOTO help
|
||||
IF /I "%~1"=="-h" GOTO help
|
||||
IF /I "%~1"=="--help" GOTO help
|
||||
IF /I "%~1"=="-v" GOTO version
|
||||
IF /I "%~1"=="--version" GOTO version
|
||||
IF /I "%~1"=="--debug" SET "DEBUG=1" & CALL :LOG_MESSAGE DEBUG "DEBUG mode: enabled."
|
||||
IF /I "%~1"=="-f" SET "FILENAME=%~2" & SHIFT
|
||||
IF "%~1"=="-p" SET "ESPTOOL_PORT=%~2" & SHIFT
|
||||
IF /I "%~1"=="--port" SET "ESPTOOL_PORT=%~2" & SHIFT
|
||||
IF "%~1"=="-P" SET "PYTHON=%~2" & SHIFT
|
||||
IF /I "%~1"=="--web" SET "WEB_APP=1"
|
||||
SHIFT
|
||||
IF NOT "__%1__"=="____" goto GETOPTS
|
||||
GOTO getopts
|
||||
:endopts
|
||||
|
||||
IF "__%FILENAME%__" == "____" (
|
||||
echo "Missing FILENAME"
|
||||
goto HELP
|
||||
)
|
||||
IF EXIST %FILENAME% IF x%FILENAME:update=%==x%FILENAME% (
|
||||
echo Trying to flash update %FILENAME%, but first erasing and writing system information"
|
||||
%ESPTOOL_CMD% --baud 115200 erase_flash
|
||||
%ESPTOOL_CMD% --baud 115200 write_flash 0x00 %FILENAME%
|
||||
|
||||
@REM Account for S3 and C3 board's different OTA partition
|
||||
IF x%FILENAME:s3=%==x%FILENAME% IF x%FILENAME:v3=%==x%FILENAME% IF x%FILENAME:t-deck=%==x%FILENAME% IF x%FILENAME:wireless-paper=%==x%FILENAME% IF x%FILENAME:wireless-tracker=%==x%FILENAME% IF x%FILENAME:station-g2=%==x%FILENAME% IF x%FILENAME:unphone=%==x%FILENAME% (
|
||||
IF x%FILENAME:esp32c3=%==x%FILENAME% (
|
||||
%ESPTOOL_CMD% --baud 115200 write_flash 0x260000 bleota.bin
|
||||
) else (
|
||||
%ESPTOOL_CMD% --baud 115200 write_flash 0x260000 bleota-c3.bin
|
||||
)
|
||||
) else (
|
||||
%ESPTOOL_CMD% --baud 115200 write_flash 0x260000 bleota-s3.bin
|
||||
CALL :LOG_MESSAGE DEBUG "Checking FILENAME parameter..."
|
||||
IF "__!FILENAME!__"=="____" (
|
||||
CALL :LOG_MESSAGE DEBUG "Missing -f filename input."
|
||||
GOTO help
|
||||
) ELSE (
|
||||
CALL :LOG_MESSAGE DEBUG "Filename: !FILENAME!"
|
||||
IF NOT "__!FILENAME: =!__"=="__!FILENAME!__" (
|
||||
CALL :LOG_MESSAGE ERROR "Filename containing spaces are not supported."
|
||||
GOTO help
|
||||
)
|
||||
IF %WEB_APP%==1 (
|
||||
for %%f in (littlefswebui-*.bin) do (
|
||||
%ESPTOOL_CMD% --baud 115200 write_flash 0x300000 %%f
|
||||
)
|
||||
) else (
|
||||
for %%f in (littlefs-*.bin) do (
|
||||
%ESPTOOL_CMD% --baud 115200 write_flash 0x300000 %%f
|
||||
)
|
||||
IF "__!FILENAME:firmware-=!__"=="__!FILENAME!__" (
|
||||
CALL :LOG_MESSAGE ERROR "Filename must be a firmware-* file."
|
||||
GOTO help
|
||||
)
|
||||
) else (
|
||||
echo "Invalid file: %FILENAME%"
|
||||
goto HELP
|
||||
) else (
|
||||
echo "Invalid file: %FILENAME%"
|
||||
goto HELP
|
||||
@REM Remove ".\" or "./" file prefix if present.
|
||||
SET "FILENAME=!FILENAME:.\=!"
|
||||
SET "FILENAME=!FILENAME:./=!"
|
||||
)
|
||||
|
||||
:EOF
|
||||
CALL :LOG_MESSAGE DEBUG "Checking if !FILENAME! exists..."
|
||||
IF NOT EXIST !FILENAME! (
|
||||
CALL :LOG_MESSAGE ERROR "File does not exist: !FILENAME!. Terminating."
|
||||
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
|
||||
) ELSE (
|
||||
CALL :LOG_MESSAGE DEBUG "We are NOT working with a *update* file. !FILENAME!"
|
||||
)
|
||||
|
||||
CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
|
||||
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...
|
||||
WHERE esptool >nul 2>&1
|
||||
IF %ERRORLEVEL% EQU 0 (
|
||||
@REM WHERE exits with code 0 if esptool is found.
|
||||
SET "ESPTOOL_CMD=esptool"
|
||||
) ELSE (
|
||||
SET "ESPTOOL_CMD=python -m esptool"
|
||||
CALL :RESET_ERROR
|
||||
)
|
||||
)
|
||||
|
||||
CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..."
|
||||
!ESPTOOL_CMD! >nul 2>&1
|
||||
IF %ERRORLEVEL% GEQ 2 (
|
||||
@REM esptool exits with code 1 if help is displayed.
|
||||
CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!"
|
||||
EXIT /B 1
|
||||
GOTO eof
|
||||
)
|
||||
IF %DEBUG% EQU 1 (
|
||||
CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps."
|
||||
SET "ESPTOOL_CMD=REM !ESPTOOL_CMD!"
|
||||
)
|
||||
|
||||
CALL :LOG_MESSAGE DEBUG "Using esptool command: !ESPTOOL_CMD!"
|
||||
IF "__!ESPTOOL_PORT!__" == "____" (
|
||||
CALL :LOG_MESSAGE WARN "Using esptool port: UNSET."
|
||||
) ELSE (
|
||||
SET "ESPTOOL_CMD=!ESPTOOL_CMD! --port !ESPTOOL_PORT!"
|
||||
CALL :LOG_MESSAGE INFO "Using esptool port: !ESPTOOL_PORT!."
|
||||
)
|
||||
CALL :LOG_MESSAGE INFO "Using esptool baud: !ESPTOOL_BAUD!."
|
||||
|
||||
@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!"
|
||||
IF %WEB_APP% EQU 1 (
|
||||
CALL :LOG_MESSAGE ERROR "Cannot enable WebUI (--web) and MUI." & GOTO eof
|
||||
)
|
||||
SET "TFT_BUILD=1"
|
||||
) ELSE (
|
||||
CALL :LOG_MESSAGE DEBUG "We are NOT working with a *-tft-* file. !FILENAME!"
|
||||
)
|
||||
|
||||
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 (%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 %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 Check if (--web) is enabled and prefix BASENAME with "littlefswebui-" else "littlefs-".
|
||||
IF %WEB_APP% EQU 1 (
|
||||
CALL :LOG_MESSAGE INFO "WebUI selected."
|
||||
SET "SPIFFS_FILENAME=littlefswebui-%BASENAME%"
|
||||
) ELSE (
|
||||
SET "SPIFFS_FILENAME=littlefs-%BASENAME%"
|
||||
)
|
||||
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 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!"
|
||||
|
||||
@REM Ensure target files exist before flashing operations.
|
||||
IF NOT EXIST !FILENAME! CALL :LOG_MESSAGE ERROR "File does not exist: "!FILENAME!". Terminating." & EXIT /B 2 & GOTO eof
|
||||
IF NOT EXIST !OTA_FILENAME! CALL :LOG_MESSAGE ERROR "File does not exist: "!OTA_FILENAME!". Terminating." & EXIT /B 2 & GOTO eof
|
||||
IF NOT EXIST !SPIFFS_FILENAME! CALL :LOG_MESSAGE ERROR "File does not exist: "!SPIFFS_FILENAME!". Terminating." & EXIT /B 2 & GOTO eof
|
||||
|
||||
@REM Flashing operations.
|
||||
CALL :LOG_MESSAGE INFO "Trying to flash "!FILENAME!", but first erasing and writing system information..."
|
||||
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! erase_flash || GOTO eof
|
||||
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash 0x00 "!FILENAME!" || GOTO eof
|
||||
|
||||
CALL :LOG_MESSAGE INFO "Trying to flash BLEOTA "!OTA_FILENAME!" at OTA_OFFSET !OTA_OFFSET!..."
|
||||
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash !OTA_OFFSET! "!OTA_FILENAME!" || GOTO eof
|
||||
|
||||
CALL :LOG_MESSAGE INFO "Trying to flash SPIFFS "!SPIFFS_FILENAME!" at SPIFFS_OFFSET !SPIFFS_OFFSET!..."
|
||||
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash !SPIFFS_OFFSET! "!SPIFFS_FILENAME!" || GOTO eof
|
||||
|
||||
CALL :LOG_MESSAGE INFO "Script complete!."
|
||||
|
||||
:eof
|
||||
ENDLOCAL
|
||||
EXIT /B %ERRORLEVEL%
|
||||
|
||||
|
||||
:RUN_ESPTOOL
|
||||
@REM Subroutine used to run ESPTOOL_CMD with arguments.
|
||||
@REM Also handles %ERRORLEVEL%.
|
||||
@REM CALL :RUN_ESPTOOL [Baud] [erase_flash|write_flash] [OFFSET] [Filename]
|
||||
@REM.
|
||||
@REM Example:: CALL :RUN_ESPTOOL 115200 write_flash 0x10000 "firmwarefile.bin"
|
||||
IF %DEBUG% EQU 1 CALL :LOG_MESSAGE DEBUG "About to run command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
|
||||
CALL :RESET_ERROR
|
||||
!ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4
|
||||
IF %ERRORLEVEL% NEQ 0 (
|
||||
CALL :LOG_MESSAGE ERROR "Error running command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
|
||||
EXIT /B %ERRORLEVEL%
|
||||
)
|
||||
GOTO :eof
|
||||
|
||||
:LOG_MESSAGE
|
||||
@REM Subroutine used to print log messages in four different levels.
|
||||
@REM DEBUG messages only get printed if [-d] flag is passed to script.
|
||||
@REM CALL :LOG_MESSAGE [ERROR|INFO|WARN|DEBUG] "Message"
|
||||
@REM.
|
||||
@REM Example:: CALL :LOG_MESSAGE INFO "Message."
|
||||
SET /A LOGCOUNTER=LOGCOUNTER+1
|
||||
IF "%1" == "ERROR" CALL :GET_TIMESTAMP & ECHO [91m%1 [0m[37m^| !TIMESTAMP! !LOGCOUNTER! [0m[91m%~2[0m
|
||||
IF "%1" == "INFO" CALL :GET_TIMESTAMP & ECHO [32m%1 [0m[37m^| !TIMESTAMP! !LOGCOUNTER! [0m[32m%~2[0m
|
||||
IF "%1" == "WARN" CALL :GET_TIMESTAMP & ECHO [33m%1 [0m[37m^| !TIMESTAMP! !LOGCOUNTER! [0m[33m%~2[0m
|
||||
IF "%1" == "DEBUG" IF %DEBUG% EQU 1 CALL :GET_TIMESTAMP & ECHO [34m%1 [0m[37m^| !TIMESTAMP! !LOGCOUNTER! [0m[34m%~2[0m
|
||||
GOTO :eof
|
||||
|
||||
:GET_TIMESTAMP
|
||||
@REM Subroutine used to set !TIMESTAMP! to HH:MM:ss.
|
||||
@REM CALL :GET_TIMESTAMP
|
||||
@REM.
|
||||
@REM Updates: !TIMESTAMP!
|
||||
FOR /F "tokens=1,2,3 delims=:,." %%a IN ("%TIME%") DO (
|
||||
SET "HH=%%a"
|
||||
SET "MM=%%b"
|
||||
SET "ss=%%c"
|
||||
)
|
||||
SET "TIMESTAMP=!HH!:!MM!:!ss!"
|
||||
GOTO :eof
|
||||
|
||||
:RESET_ERROR
|
||||
@REM Subroutine to reset %ERRORLEVEL% to 0.
|
||||
@REM CALL :RESET_ERROR
|
||||
@REM.
|
||||
@REM Updates: %ERRORLEVEL%
|
||||
EXIT /B 0
|
||||
GOTO :eof
|
||||
|
|
|
@ -1,18 +1,60 @@
|
|||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
|
||||
PYTHON=${PYTHON:-$(which python3 python | head -n 1)}
|
||||
WEB_APP=false
|
||||
TFT_BUILD=false
|
||||
MCU=""
|
||||
|
||||
# Variant groups
|
||||
BIGDB_8MB=(
|
||||
"picomputer-s3"
|
||||
"unphone"
|
||||
"seeed-sensecap-indicator"
|
||||
"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"
|
||||
"t-watch-s3"
|
||||
"tracksenger"
|
||||
)
|
||||
BIGDB_16MB=(
|
||||
"t-deck"
|
||||
"mesh-tab"
|
||||
"t-energy-s3"
|
||||
"dreamcatcher"
|
||||
"ESP32-S3-Pico"
|
||||
"m5stack-cores3"
|
||||
"station-g2"
|
||||
"t-eth-elite"
|
||||
)
|
||||
S3_VARIANTS=(
|
||||
"s3"
|
||||
"-v3"
|
||||
"t-deck"
|
||||
"wireless-paper"
|
||||
"wireless-tracker"
|
||||
"station-g2"
|
||||
"unphone"
|
||||
)
|
||||
|
||||
# Determine the correct esptool command to use
|
||||
if "$PYTHON" -m esptool version >/dev/null 2>&1; then
|
||||
ESPTOOL_CMD="$PYTHON -m esptool"
|
||||
ESPTOOL_CMD="$PYTHON -m esptool"
|
||||
elif command -v esptool >/dev/null 2>&1; then
|
||||
ESPTOOL_CMD="esptool"
|
||||
ESPTOOL_CMD="esptool"
|
||||
elif command -v esptool.py >/dev/null 2>&1; then
|
||||
ESPTOOL_CMD="esptool.py"
|
||||
ESPTOOL_CMD="esptool.py"
|
||||
else
|
||||
echo "Error: esptool not found"
|
||||
exit 1
|
||||
echo "Error: esptool not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -e
|
||||
|
@ -20,75 +62,141 @@ set -e
|
|||
# Usage info
|
||||
show_help() {
|
||||
cat <<EOF
|
||||
Usage: $(basename $0) [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME|FILENAME] [--web]
|
||||
Flash image file to device, but first erasing and writing system information"
|
||||
Usage: $(basename $0) [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME] [--web]
|
||||
Flash image file to device, but first erasing and writing system information.
|
||||
|
||||
-h Display this help and exit
|
||||
-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 .bin file to flash. Custom to your device type and region.
|
||||
--web Flash WEB APP.
|
||||
-f FILENAME The firmware .bin file to flash. Custom to your device type and region.
|
||||
--web Enable WebUI. (Default: false)
|
||||
|
||||
EOF
|
||||
}
|
||||
# Preprocess long options like --web
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--web)
|
||||
WEB_APP=true
|
||||
shift # Remove this argument from the list
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
while getopts ":hp:P:f:" opt; do
|
||||
case "${opt}" in
|
||||
h)
|
||||
# Parse arguments using a single while loop
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-h | --help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
p)
|
||||
export ESPTOOL_PORT=${OPTARG}
|
||||
-p)
|
||||
ESPTOOL_CMD="$ESPTOOL_CMD --port $2"
|
||||
shift
|
||||
;;
|
||||
P)
|
||||
PYTHON=${OPTARG}
|
||||
-P)
|
||||
PYTHON="$2"
|
||||
shift
|
||||
;;
|
||||
f)
|
||||
FILENAME=${OPTARG}
|
||||
-f)
|
||||
FILENAME="$2"
|
||||
shift
|
||||
;;
|
||||
--web)
|
||||
WEB_APP=true
|
||||
;;
|
||||
--) # Stop parsing options
|
||||
shift
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "Invalid flag."
|
||||
show_help >&2
|
||||
echo "Unknown argument: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift # Move to the next argument
|
||||
done
|
||||
shift "$((OPTIND - 1))"
|
||||
|
||||
[ -z "$FILENAME" -a -n "$1" ] && {
|
||||
FILENAME=$1
|
||||
shift
|
||||
}
|
||||
|
||||
if [[ $FILENAME != firmware-* ]]; then
|
||||
echo "Filename must be a firmware-* file."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if FILENAME contains "-tft-" and prevent web/mui comingling.
|
||||
if [[ ${FILENAME//-tft-/} != "$FILENAME" ]]; then
|
||||
TFT_BUILD=true
|
||||
if [[ $WEB_APP == true ]] && [[ $TFT_BUILD == true ]]; then
|
||||
echo "Cannot enable WebUI (--web) and MUI."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Extract BASENAME from %FILENAME% for later use.
|
||||
BASENAME="${FILENAME/firmware-/}"
|
||||
|
||||
if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
|
||||
# Default littlefs* offset (--web).
|
||||
OFFSET=0x300000
|
||||
|
||||
# Default OTA Offset
|
||||
OTA_OFFSET=0x260000
|
||||
|
||||
# littlefs* offset for BigDB 8mb and OTA OFFSET.
|
||||
for variant in "${BIGDB_8MB[@]}"; do
|
||||
if [ -n "${FILENAME##*"$variant"*}" ]; then
|
||||
OFFSET=0x670000
|
||||
OTA_OFFSET=0x340000
|
||||
fi
|
||||
done
|
||||
|
||||
# littlefs* offset for BigDB 16mb and OTA OFFSET.
|
||||
for variant in "${BIGDB_16MB[@]}"; do
|
||||
if [ -n "${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 [ -n "${FILENAME##*"$variant"*}" ]; then
|
||||
MCU="esp32s3"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$MCU" != "esp32s3" ]; then
|
||||
if [ -n "${FILENAME##*"esp32c3"*}" ]; then
|
||||
OTAFILE=bleota.bin
|
||||
else
|
||||
OTAFILE=bleota-c3.bin
|
||||
fi
|
||||
else
|
||||
OTAFILE=bleota-s3.bin
|
||||
fi
|
||||
|
||||
# Check if WEB_APP (--web) is enabled and add "littlefswebui-" to BASENAME else "littlefs-".
|
||||
if [ "$WEB_APP" = true ]; then
|
||||
SPIFFSFILE=littlefswebui-${BASENAME}
|
||||
else
|
||||
SPIFFSFILE=littlefs-${BASENAME}
|
||||
fi
|
||||
|
||||
if [[ ! -f $FILENAME ]]; then
|
||||
echo "Error: file ${FILENAME} wasn't found. Terminating."
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f $OTAFILE ]]; then
|
||||
echo "Error: file ${OTAFILE} wasn't found. Terminating."
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f $SPIFFSFILE ]]; then
|
||||
echo "Error: file ${SPIFFSFILE} wasn't found. Terminating."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
|
||||
$ESPTOOL_CMD erase_flash
|
||||
$ESPTOOL_CMD write_flash 0x00 "${FILENAME}"
|
||||
# Account for S3 board's different OTA partition
|
||||
if [ -n "${FILENAME##*"s3"*}" ] && [ -n "${FILENAME##*"-v3"*}" ] && [ -n "${FILENAME##*"t-deck"*}" ] && [ -n "${FILENAME##*"wireless-paper"*}" ] && [ -n "${FILENAME##*"wireless-tracker"*}" ] && [ -n "${FILENAME##*"station-g2"*}" ] && [ -n "${FILENAME##*"unphone"*}" ]; then
|
||||
if [ -n "${FILENAME##*"esp32c3"*}" ]; then
|
||||
$ESPTOOL_CMD write_flash 0x260000 bleota.bin
|
||||
else
|
||||
$ESPTOOL_CMD write_flash 0x260000 bleota-c3.bin
|
||||
fi
|
||||
else
|
||||
$ESPTOOL_CMD write_flash 0x260000 bleota-s3.bin
|
||||
fi
|
||||
if [ "$WEB_APP" = true ]; then
|
||||
$ESPTOOL_CMD write_flash 0x300000 littlefswebui-*.bin
|
||||
else
|
||||
$ESPTOOL_CMD write_flash 0x300000 littlefs-*.bin
|
||||
fi
|
||||
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
|
||||
$ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}"
|
||||
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
|
||||
$ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}"
|
||||
|
||||
else
|
||||
show_help
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Unit-test for .\device-install.bat.
|
||||
|
||||
.DESCRIPTION
|
||||
This script performs a positive unit-test on .\device-install.bat by creating the expected .bin
|
||||
files for a device followed by running the .bat script without flashing the firmware (--debug).
|
||||
If any errors are hit they are presented in the standard output. Investigate accordingly.
|
||||
|
||||
This script needs to be placed in the same directory as .\device-install.bat.
|
||||
|
||||
.EXAMPLE
|
||||
.\device-install_test.ps1
|
||||
|
||||
.EXAMPLE
|
||||
.\device-install_test.ps1 -Verbose
|
||||
|
||||
.LINK
|
||||
.\device-install.bat --help
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
function New-EmptyFile() {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Position = 0, Mandatory = $true)]
|
||||
# Specifies the file name.
|
||||
[string]$FileName,
|
||||
[Parameter(Position = 1)]
|
||||
# Specifies the target path. (Get-Location).Path is the default.
|
||||
[string]$Directory = (Get-Location).Path
|
||||
)
|
||||
|
||||
$filePath = Join-Path -Path $Directory -ChildPath $FileName
|
||||
|
||||
Write-Verbose -Message "Create empty test file if it doesn't exist: $($FileName)"
|
||||
New-Item -Path "$filePath" -ItemType File -ErrorAction SilentlyContinue | Out-Null
|
||||
}
|
||||
|
||||
function Remove-EmptyFile() {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Position = 0, Mandatory = $true)]
|
||||
# Specifies the file name.
|
||||
[string]$FileName,
|
||||
[Parameter(Position = 1)]
|
||||
# Specifies the target path. (Get-Location).Path is the default.
|
||||
[string]$Directory = (Get-Location).Path
|
||||
)
|
||||
|
||||
$filePath = Join-Path -Path $Directory -ChildPath $FileName
|
||||
|
||||
Write-Verbose -Message "Deleted empty test file: $($FileName)"
|
||||
Remove-Item -Path "$filePath" | Out-Null
|
||||
}
|
||||
|
||||
|
||||
$TestCases = New-Object -TypeName PSObject -Property @{
|
||||
# Use this PSObject to define testcases according to this syntax:
|
||||
# "testname" = @("firmware-testname","bleota","littlefs-testname","args")
|
||||
"t-deck" = @("firmware-t-deck-2.6.0.0b106d4.bin", "bleota-s3.bin", "littlefs-t-deck-2.6.0.0b106d4.bin", "")
|
||||
"t-deck_web" = @("firmware-t-deck-2.6.0.0b106d4.bin", "bleota-s3.bin", "littlefswebui-t-deck-2.6.0.0b106d4.bin", "--web")
|
||||
"t-deck-tft" = @("firmware-t-deck-tft-2.6.0.0b106d4.bin", "bleota-s3.bin", "littlefs-t-deck-tft-2.6.0.0b106d4.bin", "")
|
||||
"heltec-ht62-esp32c3" = @("firmware-heltec-ht62-esp32c3-sx1262-2.6.0.0b106d4.bin", "bleota-c3.bin", "littlefs-heltec-ht62-esp32c3-sx1262-2.6.0.0b106d4.bin", "")
|
||||
"tlora-c6" = @("firmware-tlora-c6-2.6.0.0b106d4.bin", "bleota.bin", "littlefs-tlora-c6-2.6.0.0b106d4.bin", "")
|
||||
"heltec-v3_web" = @("firmware-heltec-v3-2.6.0.0b106d4.bin", "bleota-s3.bin", "littlefswebui-heltec-v3-2.6.0.0b106d4.bin", "--web")
|
||||
"seeed-sensecap-indicator-tft" = @("firmware-seeed-sensecap-indicator-tft-2.6.0.0b106d4.bin", "bleota.bin", "littlefs-seeed-sensecap-indicator-tft-2.6.0.0b106d4.bin", "")
|
||||
"picomputer-s3-tft" = @("firmware-picomputer-s3-tft-2.6.0.0b106d4.bin", "bleota-s3.bin", "littlefs-picomputer-s3-tft-2.6.0.0b106d4.bin", "")
|
||||
}
|
||||
|
||||
foreach ($TestCase in $TestCases.PSObject.Properties) {
|
||||
$Name = $TestCase.Name
|
||||
$Files = $TestCase.Value
|
||||
$Errors = $null
|
||||
$Counter = 0
|
||||
|
||||
Write-Host -Object "Testcase: $Name`:" -ForegroundColor Green
|
||||
foreach ($File in $Files) {
|
||||
if ($File.EndsWith(".bin")) {
|
||||
New-EmptyFile -FileName $File
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host -Object "Performing test on $Name..." -ForegroundColor Blue
|
||||
$Test = Invoke-Expression -Command "cmd /c .\device-install.bat --debug -f $($TestCases."$Name"[0]) $($TestCases."$Name"[3])"
|
||||
|
||||
foreach ($Line in $Test) {
|
||||
if ($Line -match "Set OTA_OFFSET to" -or `
|
||||
$Line -match "Set SPIFFS_OFFSET to") {
|
||||
Write-Host -Object "$($Line -replace "^.*?Set","Set")" -ForegroundColor Blue
|
||||
}
|
||||
elseif ($VerbosePreference -eq "Continue") {
|
||||
Write-Host -Object $Line
|
||||
}
|
||||
if ($Line -match "ERROR") {
|
||||
$Errors += $Line
|
||||
$Counter++
|
||||
}
|
||||
}
|
||||
if ($null -ne $Errors) {
|
||||
Write-Host -Object "$Counter ERROR(s) detected!" -ForegroundColor Red
|
||||
if (-not ($VerbosePreference -eq "Continue")) { Write-Host -Object $Errors }
|
||||
}
|
||||
|
||||
foreach ($File in $Files) {
|
||||
if ($File.EndsWith(".bin")) {
|
||||
Remove-EmptyFile -FileName $File
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,48 +1,176 @@
|
|||
@ECHO OFF
|
||||
SETLOCAL EnableDelayedExpansion
|
||||
TITLE Meshtastic device-update
|
||||
|
||||
set PYTHON=python
|
||||
SET "SCRIPT_NAME=%~nx0"
|
||||
SET "DEBUG=0"
|
||||
SET "PYTHON="
|
||||
SET "ESPTOOL_BAUD=115200"
|
||||
SET "ESPTOOL_CMD="
|
||||
SET "LOGCOUNTER=0"
|
||||
|
||||
:: Determine the correct esptool command to use
|
||||
where esptool >nul 2>&1
|
||||
if %ERRORLEVEL% EQU 0 (
|
||||
set "ESPTOOL_CMD=esptool"
|
||||
) else (
|
||||
set "ESPTOOL_CMD=%PYTHON% -m esptool"
|
||||
)
|
||||
GOTO getopts
|
||||
:help
|
||||
ECHO Flash image file to device, but leave existing system intact.
|
||||
ECHO.
|
||||
ECHO Usage: %SCRIPT_NAME% -f filename [-p PORT] [-P python]
|
||||
ECHO.
|
||||
ECHO Options:
|
||||
ECHO -f filename The update .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).
|
||||
ECHO -P python Specify alternate python interpreter to use to invoke esptool. (default: python)
|
||||
ECHO If supplied the script will use python.
|
||||
ECHO If not supplied the script will try to find esptool in Path.
|
||||
ECHO.
|
||||
ECHO Example: %SCRIPT_NAME% -f firmware-t-deck-tft-2.6.0.0b106d4-update.bin -p COM11
|
||||
GOTO eof
|
||||
|
||||
goto GETOPTS
|
||||
:HELP
|
||||
echo Usage: %~nx0 [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME^|FILENAME]
|
||||
echo Flash image file to device, leave existing system intact.
|
||||
echo.
|
||||
echo -h Display this help and exit
|
||||
echo -p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerrous).
|
||||
echo -P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: %PYTHON%)
|
||||
echo -f FILENAME The *update.bin file to flash. Custom to your device type.
|
||||
goto EOF
|
||||
:version
|
||||
ECHO %SCRIPT_NAME% [Version 2.6.1]
|
||||
ECHO Meshtastic
|
||||
GOTO eof
|
||||
|
||||
:GETOPTS
|
||||
if /I "%1"=="-h" goto HELP
|
||||
if /I "%1"=="--help" goto HELP
|
||||
if /I "%1"=="-F" set "FILENAME=%2" & SHIFT
|
||||
if /I "%1"=="-p" set ESPTOOL_PORT=%2 & SHIFT
|
||||
if /I "%1"=="-P" set PYTHON=%2 & SHIFT
|
||||
:getopts
|
||||
IF "%~1"=="" GOTO endopts
|
||||
IF /I "%~1"=="-?" GOTO help
|
||||
IF /I "%~1"=="-h" GOTO help
|
||||
IF /I "%~1"=="--help" GOTO help
|
||||
IF /I "%~1"=="-v" GOTO version
|
||||
IF /I "%~1"=="--version" GOTO version
|
||||
IF /I "%~1"=="--debug" SET "DEBUG=1" & CALL :LOG_MESSAGE DEBUG "DEBUG mode: enabled."
|
||||
IF /I "%~1"=="-f" SET "FILENAME=%~2" & SHIFT
|
||||
IF "%~1"=="-p" SET "ESPTOOL_PORT=%~2" & SHIFT
|
||||
IF /I "%~1"=="--port" SET "ESPTOOL_PORT=%~2" & SHIFT
|
||||
IF "%~1"=="-P" SET "PYTHON=%~2" & SHIFT
|
||||
SHIFT
|
||||
IF NOT "__%1__"=="____" goto GETOPTS
|
||||
GOTO getopts
|
||||
:endopts
|
||||
|
||||
IF "__%FILENAME%__" == "____" (
|
||||
echo "Missing FILENAME"
|
||||
goto HELP
|
||||
)
|
||||
IF EXIST %FILENAME% IF NOT x%FILENAME:update=%==x%FILENAME% (
|
||||
echo Trying to flash update %FILENAME%
|
||||
%ESPTOOL_CMD% --baud 115200 write_flash 0x10000 %FILENAME%
|
||||
) else (
|
||||
echo "Invalid file: %FILENAME%"
|
||||
goto HELP
|
||||
) else (
|
||||
echo "Invalid file: %FILENAME%"
|
||||
goto HELP
|
||||
CALL :LOG_MESSAGE DEBUG "Checking FILENAME parameter..."
|
||||
IF "__!FILENAME!__"=="____" (
|
||||
CALL :LOG_MESSAGE DEBUG "Missing -f filename input."
|
||||
GOTO help
|
||||
) ELSE (
|
||||
CALL :LOG_MESSAGE DEBUG "Filename: !FILENAME!"
|
||||
IF NOT "__!FILENAME: =!__"=="__!FILENAME!__" (
|
||||
CALL :LOG_MESSAGE ERROR "Filename containing spaces are not supported."
|
||||
GOTO help
|
||||
)
|
||||
@REM Remove ".\" or "./" file prefix if present.
|
||||
SET "FILENAME=!FILENAME:.\=!"
|
||||
SET "FILENAME=!FILENAME:./=!"
|
||||
)
|
||||
|
||||
:EOF
|
||||
CALL :LOG_MESSAGE DEBUG "Checking if !FILENAME! exists..."
|
||||
IF NOT EXIST !FILENAME! (
|
||||
CALL :LOG_MESSAGE ERROR "File does not exist: !FILENAME!. Terminating."
|
||||
GOTO eof
|
||||
)
|
||||
|
||||
IF "!FILENAME:update=!"=="!FILENAME!" (
|
||||
CALL :LOG_MESSAGE DEBUG "We are NOT working with a *update* 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 "Determine the correct esptool command to use..."
|
||||
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...
|
||||
WHERE esptool >nul 2>&1
|
||||
IF %ERRORLEVEL% EQU 0 (
|
||||
@REM WHERE exits with code 0 if esptool is found.
|
||||
SET "ESPTOOL_CMD=esptool"
|
||||
) ELSE (
|
||||
SET "ESPTOOL_CMD=python -m esptool"
|
||||
CALL :RESET_ERROR
|
||||
)
|
||||
)
|
||||
|
||||
CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..."
|
||||
!ESPTOOL_CMD! >nul 2>&1
|
||||
IF %ERRORLEVEL% GEQ 2 (
|
||||
@REM esptool exits with code 1 if help is displayed.
|
||||
CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!"
|
||||
EXIT /B 1
|
||||
GOTO eof
|
||||
)
|
||||
IF %DEBUG% EQU 1 (
|
||||
CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps."
|
||||
SET "ESPTOOL_CMD=REM !ESPTOOL_CMD!"
|
||||
)
|
||||
|
||||
CALL :LOG_MESSAGE DEBUG "Using esptool command: !ESPTOOL_CMD!"
|
||||
IF "__!ESPTOOL_PORT!__" == "____" (
|
||||
CALL :LOG_MESSAGE WARN "Using esptool port: UNSET."
|
||||
) ELSE (
|
||||
SET "ESPTOOL_CMD=!ESPTOOL_CMD! --port !ESPTOOL_PORT!"
|
||||
CALL :LOG_MESSAGE INFO "Using esptool port: !ESPTOOL_PORT!."
|
||||
)
|
||||
CALL :LOG_MESSAGE INFO "Using esptool baud: !ESPTOOL_BAUD!."
|
||||
|
||||
@REM Flashing operations.
|
||||
CALL :LOG_MESSAGE INFO "Trying to flash update "!FILENAME!" at OFFSET 0x10000..."
|
||||
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash 0x10000 "!FILENAME!" || GOTO eof
|
||||
|
||||
CALL :LOG_MESSAGE INFO "Script complete!."
|
||||
|
||||
:eof
|
||||
ENDLOCAL
|
||||
EXIT /B %ERRORLEVEL%
|
||||
|
||||
|
||||
:RUN_ESPTOOL
|
||||
@REM Subroutine used to run ESPTOOL_CMD with arguments.
|
||||
@REM Also handles %ERRORLEVEL%.
|
||||
@REM CALL :RUN_ESPTOOL [Baud] [erase_flash|write_flash] [OFFSET] [Filename]
|
||||
@REM.
|
||||
@REM Example:: CALL :RUN_ESPTOOL 115200 write_flash 0x10000 "firmwarefile.bin"
|
||||
IF %DEBUG% EQU 1 CALL :LOG_MESSAGE DEBUG "About to run command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
|
||||
CALL :RESET_ERROR
|
||||
!ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4
|
||||
IF %ERRORLEVEL% NEQ 0 (
|
||||
CALL :LOG_MESSAGE ERROR "Error running command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
|
||||
EXIT /B %ERRORLEVEL%
|
||||
)
|
||||
GOTO :eof
|
||||
|
||||
:LOG_MESSAGE
|
||||
@REM Subroutine used to print log messages in four different levels.
|
||||
@REM DEBUG messages only get printed if [-d] flag is passed to script.
|
||||
@REM CALL :LOG_MESSAGE [ERROR|INFO|WARN|DEBUG] "Message"
|
||||
@REM.
|
||||
@REM Example:: CALL :LOG_MESSAGE INFO "Message."
|
||||
SET /A LOGCOUNTER=LOGCOUNTER+1
|
||||
IF "%1" == "ERROR" CALL :GET_TIMESTAMP & ECHO [91m%1 [0m[37m^| !TIMESTAMP! !LOGCOUNTER! [0m[91m%~2[0m
|
||||
IF "%1" == "INFO" CALL :GET_TIMESTAMP & ECHO [32m%1 [0m[37m^| !TIMESTAMP! !LOGCOUNTER! [0m[32m%~2[0m
|
||||
IF "%1" == "WARN" CALL :GET_TIMESTAMP & ECHO [33m%1 [0m[37m^| !TIMESTAMP! !LOGCOUNTER! [0m[33m%~2[0m
|
||||
IF "%1" == "DEBUG" IF %DEBUG% EQU 1 CALL :GET_TIMESTAMP & ECHO [34m%1 [0m[37m^| !TIMESTAMP! !LOGCOUNTER! [0m[34m%~2[0m
|
||||
GOTO :eof
|
||||
|
||||
:GET_TIMESTAMP
|
||||
@REM Subroutine used to set !TIMESTAMP! to HH:MM:ss.
|
||||
@REM CALL :GET_TIMESTAMP
|
||||
@REM.
|
||||
@REM Updates: !TIMESTAMP!
|
||||
FOR /F "tokens=1,2,3 delims=:,." %%a IN ("%TIME%") DO (
|
||||
SET "HH=%%a"
|
||||
SET "MM=%%b"
|
||||
SET "ss=%%c"
|
||||
)
|
||||
SET "TIMESTAMP=!HH!:!MM!:!ss!"
|
||||
GOTO :eof
|
||||
|
||||
:RESET_ERROR
|
||||
@REM Subroutine to reset %ERRORLEVEL% to 0.
|
||||
@REM CALL :RESET_ERROR
|
||||
@REM.
|
||||
@REM Updates: %ERRORLEVEL%
|
||||
EXIT /B 0
|
||||
GOTO :eof
|
||||
|
|
|
@ -35,8 +35,8 @@ while getopts ":hp:P:f:" opt; do
|
|||
show_help
|
||||
exit 0
|
||||
;;
|
||||
p) export ESPTOOL_PORT=${OPTARG}
|
||||
;;
|
||||
p) ESPTOOL_CMD="$ESPTOOL_CMD --port ${OPTARG}"
|
||||
;;
|
||||
P) PYTHON=${OPTARG}
|
||||
;;
|
||||
f) FILENAME=${OPTARG}
|
||||
|
|
|
@ -83,7 +83,7 @@ if platform.name == "espressif32":
|
|||
|
||||
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",
|
||||
env.VerboseAction(f"\"{sys.executable}\" ./bin/uf2conv.py $BUILD_DIR/firmware.hex -c -f 0xADA52840 -o $BUILD_DIR/firmware.uf2",
|
||||
"Generating UF2 file"))
|
||||
|
||||
Import("projenv")
|
||||
|
@ -125,4 +125,9 @@ for flag in flags:
|
|||
|
||||
projenv.Append(
|
||||
CCFLAGS=flags,
|
||||
)
|
||||
)
|
||||
|
||||
for lb in env.GetLibBuilders():
|
||||
if lb.name == "meshtastic-device-ui":
|
||||
lb.env.Append(CPPDEFINES=[("APP_VERSION", verObj["long"])])
|
||||
break
|
||||
|
|
|
@ -1 +1,10 @@
|
|||
cd protobufs && ..\nanopb-0.4.9\generator-bin\protoc.exe --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:..\src\mesh\generated" -I=..\protobufs\ ..\protobufs\meshtastic\*.proto
|
||||
@ECHO OFF
|
||||
SETLOCAL
|
||||
|
||||
cd protobufs
|
||||
..\nanopb-0.4.9\generator-bin\protoc.exe --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:..\src\mesh\generated" -I=..\protobufs\ ..\protobufs\meshtastic\*.proto
|
||||
GOTO eof
|
||||
|
||||
:eof
|
||||
ENDLOCAL
|
||||
EXIT /B %ERRORLEVEL%
|
||||
|
|
|
@ -1,2 +1,124 @@
|
|||
@echo off
|
||||
if [%1]==[] (echo "Please specify a platformio NRF target (i.e. rak4631) as the first argument.") else (python3 .\bin\uf2conv.py .\.pio\build\%1\firmware.hex -c -o .\.pio\build\%1\firmware.uf2 -f 0xADA52840)
|
||||
@ECHO OFF
|
||||
SETLOCAL EnableDelayedExpansion
|
||||
TITLE Meshtastic uf2-convert
|
||||
|
||||
SET "SCRIPT_NAME=%~nx0"
|
||||
SET "DEBUG=0"
|
||||
SET "NRF=0"
|
||||
SET "UF2CONV_CMD=python3 .\bin\uf2conv.py"
|
||||
|
||||
GOTO getopts
|
||||
:help
|
||||
ECHO.
|
||||
ECHO Usage: %SCRIPT_NAME% -t [t-echo^|rak4631^|nano-g2-ultra^|wio-tracker-wm1110^|canaryone^|
|
||||
ECHO heltec-mesh-node-t114^|tracker-t1000-e^|rak_wismeshtap^|rak2560^|
|
||||
ECHO nrf52_promicro_diy_tcxo]
|
||||
ECHO.
|
||||
ECHO Options:
|
||||
ECHO -t target Specify a platformio NRF target to build for. (required)
|
||||
ECHO.
|
||||
ECHO Example: %SCRIPT_NAME% -t rak4631
|
||||
GOTO eof
|
||||
|
||||
:version
|
||||
ECHO %SCRIPT_NAME% [Version 2.6.0]
|
||||
ECHO Meshtastic
|
||||
GOTO eof
|
||||
|
||||
:getopts
|
||||
IF "%~1"=="" GOTO endopts
|
||||
IF /I "%~1"=="-?" GOTO help
|
||||
IF /I "%~1"=="-h" GOTO help
|
||||
IF /I "%~1"=="--help" GOTO help
|
||||
IF /I "%~1"=="-v" GOTO version
|
||||
IF /I "%~1"=="--version" GOTO version
|
||||
IF /I "%~1"=="--debug" SET "DEBUG=1" & CALL :LOG_MESSAGE DEBUG "DEBUG mode: enabled."
|
||||
IF /I "%~1"=="-t" SET "TARGETNAME=%~2" & SHIFT
|
||||
IF /I "%~1"=="--target" SET "TARGETNAME=%~2" & SHIFT
|
||||
SHIFT
|
||||
GOTO getopts
|
||||
:endopts
|
||||
|
||||
CALL :LOG_MESSAGE DEBUG "Checking TARGETNAME parameter..."
|
||||
IF "__!TARGETNAME!__"=="____" (
|
||||
CALL :LOG_MESSAGE DEBUG "Missing -t target input."
|
||||
GOTO help
|
||||
)
|
||||
|
||||
IF %DEBUG% EQU 1 SET "UF2CONV_CMD=REM python3 .\bin\uf2conv.py"
|
||||
|
||||
SET "NRFTARGETS=t-echo rak4631 nano-g2-ultra wio-tracker-wm1110 canaryone heltec-mesh-node-t114 tracker-t1000-e rak_wismeshtap rak2560 nrf52_promicro_diy_tcxo"
|
||||
FOR %%a IN (%NRFTARGETS%) DO (
|
||||
IF /I "%%a"=="!TARGETNAME!" (
|
||||
@REM We are working with any of %NRFTARGETS%.
|
||||
SET "NRF=1"
|
||||
GOTO end_loop_nrf
|
||||
)
|
||||
)
|
||||
:end_loop_nrf
|
||||
|
||||
@REM Building operations.
|
||||
IF !NRF! EQU 1 (
|
||||
CALL :LOG_MESSAGE INFO "Trying to build for !TARGETNAME!..."
|
||||
CALL :RUN_UF2CONV !TARGETNAME! || GOTO eof
|
||||
) ELSE (
|
||||
CALL :LOG_MESSAGE WARN "!TARGETNAME! is not supported..."
|
||||
GOTO eof
|
||||
)
|
||||
|
||||
CALL :LOG_MESSAGE INFO "Script complete!."
|
||||
|
||||
|
||||
:eof
|
||||
ENDLOCAL
|
||||
EXIT /B %ERRORLEVEL%
|
||||
|
||||
|
||||
:RUN_UF2CONV
|
||||
@REM Subroutine used to run .\bin\uf2conv.py with arguments.
|
||||
@REM Also handles %ERRORLEVEL%.
|
||||
@REM CALL :RUN_UF2CONV [target]
|
||||
@REM.
|
||||
@REM Example:: CALL :RUN_UF2CONV rak4631
|
||||
IF %DEBUG% EQU 1 CALL :LOG_MESSAGE DEBUG "About to run command: !UF2CONV_CMD! .\.pio\build\%~1\firmware.hex -c -o .\.pio\build\%~1\firmware.uf2 -f 0xADA52840"
|
||||
CALL :RESET_ERROR
|
||||
!UF2CONV_CMD! .\.pio\build\%~1\firmware.hex -c -o .\.pio\build\%~1\firmware.uf2 -f 0xADA52840
|
||||
IF %ERRORLEVEL% NEQ 0 (
|
||||
CALL :LOG_MESSAGE ERROR "Error running command: !UF2CONV_CMD! .\.pio\build\%~1\firmware.hex -c -o .\.pio\build\%~1\firmware.uf2 -f 0xADA52840"
|
||||
EXIT /B %ERRORLEVEL%
|
||||
)
|
||||
GOTO :eof
|
||||
|
||||
:LOG_MESSAGE
|
||||
@REM Subroutine used to print log messages in four different levels.
|
||||
@REM DEBUG messages only get printed if [-d] flag is passed to script.
|
||||
@REM CALL :LOG_MESSAGE [ERROR|INFO|WARN|DEBUG] "Message"
|
||||
@REM.
|
||||
@REM Example:: CALL :LOG_MESSAGE INFO "Message."
|
||||
SET /A LOGCOUNTER=LOGCOUNTER+1
|
||||
IF "%1" == "ERROR" CALL :GET_TIMESTAMP & ECHO [91m%1 [0m[37m^| !TIMESTAMP! !LOGCOUNTER! [0m[91m%~2[0m
|
||||
IF "%1" == "INFO" CALL :GET_TIMESTAMP & ECHO [32m%1 [0m[37m^| !TIMESTAMP! !LOGCOUNTER! [0m[32m%~2[0m
|
||||
IF "%1" == "WARN" CALL :GET_TIMESTAMP & ECHO [33m%1 [0m[37m^| !TIMESTAMP! !LOGCOUNTER! [0m[33m%~2[0m
|
||||
IF "%1" == "DEBUG" IF %DEBUG% EQU 1 CALL :GET_TIMESTAMP & ECHO [34m%1 [0m[37m^| !TIMESTAMP! !LOGCOUNTER! [0m[34m%~2[0m
|
||||
GOTO :eof
|
||||
|
||||
:GET_TIMESTAMP
|
||||
@REM Subroutine used to set !TIMESTAMP! to HH:MM:ss.
|
||||
@REM CALL :GET_TIMESTAMP
|
||||
@REM.
|
||||
@REM Updates: !TIMESTAMP!
|
||||
FOR /F "tokens=1,2,3 delims=:,." %%a IN ("%TIME%") DO (
|
||||
SET "HH=%%a"
|
||||
SET "MM=%%b"
|
||||
SET "ss=%%c"
|
||||
)
|
||||
SET "TIMESTAMP=!HH!:!MM!:!ss!"
|
||||
GOTO :eof
|
||||
|
||||
:RESET_ERROR
|
||||
@REM Subroutine to reset %ERRORLEVEL% to 0.
|
||||
@REM CALL :RESET_ERROR
|
||||
@REM.
|
||||
@REM Updates: %ERRORLEVEL%
|
||||
EXIT /B 0
|
||||
GOTO :eof
|
||||
|
|
|
@ -7,13 +7,15 @@
|
|||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DARDUINO_ESP32S3_DEV",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DBOARD_HAS_PSRAM"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"psram_type": "qio",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "esp32s3"
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v7.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_MDBT50Q_RX -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x2886", "0x0166"]
|
||||
],
|
||||
"usb_product": "XIAO-BOOT",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "seeed_xiao_nrf52840_kit",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "7.3.0",
|
||||
"sd_fwid": "0x0123"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "seeed_xiao_nrf52840_kit",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": [
|
||||
"jlink",
|
||||
"nrfjprog",
|
||||
"nrfutil",
|
||||
"stlink",
|
||||
"cmsis-dap",
|
||||
"blackmagic"
|
||||
],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://www.seeedstudio.com/XIAO-nRF52840-Wio-SX1262-Kit-for-Meshtastic-p-6400.html",
|
||||
"vendor": "seeed"
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"earlephilhower": {
|
||||
"boot2_source": "boot2_w25q080_2_padded_checksum.S",
|
||||
"usb_vid": "0x2E8A",
|
||||
"usb_pid": "0x000A"
|
||||
}
|
||||
},
|
||||
"core": "earlephilhower",
|
||||
"cpu": "cortex-m0plus",
|
||||
"extra_flags": "-DARDUINO_GENERIC_RP2040 -DRASPBERRY_PI_PICO -DARDUINO_ARCH_RP2040 -DUSBD_MAX_POWER_MA=250",
|
||||
"f_cpu": "133000000L",
|
||||
"hwids": [
|
||||
["0x2E8A", "0x00C0"],
|
||||
["0x2E8A", "0x000A"]
|
||||
],
|
||||
"mcu": "rp2040",
|
||||
"variant": "WisBlock_RAK11300_Board"
|
||||
},
|
||||
"debug": {
|
||||
"jlink_device": "RP2040_M0_0",
|
||||
"openocd_target": "rp2040.cfg",
|
||||
"svd_path": "rp2040.svd"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "WisBlock RAK11300",
|
||||
"upload": {
|
||||
"maximum_ram_size": 270336,
|
||||
"maximum_size": 2097152,
|
||||
"require_upload_port": true,
|
||||
"native_usb": true,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": false,
|
||||
"protocol": "picotool",
|
||||
"protocols": ["cmsis-dap", "raspberrypi-swd", "picotool", "picoprobe"]
|
||||
},
|
||||
"url": "https://docs.rakwireless.com/",
|
||||
"vendor": "RAKwireless"
|
||||
}
|
|
@ -17,6 +17,7 @@ Build-Depends: debhelper-compat (= 13),
|
|||
libbluetooth-dev,
|
||||
libusb-1.0-0-dev,
|
||||
libi2c-dev,
|
||||
libuv1-dev,
|
||||
openssl,
|
||||
libssl-dev,
|
||||
libulfius-dev,
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# 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",
|
||||
env.VerboseAction(
|
||||
" ".join(
|
||||
[
|
||||
"$OBJCOPY",
|
||||
"-O",
|
||||
"ihex",
|
||||
"-R",
|
||||
".eeprom",
|
||||
"$BUILD_DIR/${PROGNAME}.elf",
|
||||
"$BUILD_DIR/${PROGNAME}.hex",
|
||||
]
|
||||
),
|
||||
"Building $BUILD_DIR/${PROGNAME}.hex",
|
||||
),
|
||||
)
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 5c6156d2aa10d62cca3e57ffc117b934ef2fbffe
|
|
@ -36,6 +36,7 @@ BuildRequires: pkgconfig(libgpiod)
|
|||
BuildRequires: pkgconfig(bluez)
|
||||
BuildRequires: pkgconfig(libusb-1.0)
|
||||
BuildRequires: libi2c-devel
|
||||
BuildRequires: pkgconfig(libuv)
|
||||
# Web components:
|
||||
BuildRequires: pkgconfig(openssl)
|
||||
BuildRequires: pkgconfig(liborcania)
|
||||
|
|
|
@ -7,6 +7,8 @@ default_envs = tbeam
|
|||
extra_configs =
|
||||
arch/*/*.ini
|
||||
variants/*/platformio.ini
|
||||
src/graphics/niche/InkHUD/PlatformioConfig.ini
|
||||
|
||||
description = Meshtastic
|
||||
|
||||
[env]
|
||||
|
@ -58,7 +60,7 @@ lib_deps =
|
|||
mathertel/OneButton@2.6.1
|
||||
https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159
|
||||
https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4
|
||||
https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0
|
||||
https://github.com/meshtastic/ArduinoThread.git#7c3ee9e1951551b949763b1f5280f8db1fa4068d
|
||||
nanopb/Nanopb@0.4.91
|
||||
erriez/ErriezCRC32@1.0.1
|
||||
|
||||
|
@ -77,7 +79,7 @@ lib_deps =
|
|||
${env.lib_deps}
|
||||
end2endzone/NonBlockingRTTTL@1.3.0
|
||||
build_flags = ${env.build_flags} -Os
|
||||
build_src_filter = ${env.build_src_filter} -<platform/portduino/>
|
||||
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/>
|
||||
|
||||
; Common libs for communicating over TCP/IP networks such as MQTT
|
||||
[networking_base]
|
||||
|
@ -90,6 +92,10 @@ lib_deps =
|
|||
lib_deps =
|
||||
jgromes/RadioLib@7.1.2
|
||||
|
||||
[device-ui_base]
|
||||
lib_deps =
|
||||
https://github.com/meshtastic/device-ui.git#7a6ffba3c86901b0e3234b6c056aa803b4cd8854
|
||||
|
||||
; Common libs for environmental measurements in telemetry module
|
||||
; (not included in native / portduino)
|
||||
[environmental_base]
|
||||
|
@ -100,6 +106,7 @@ lib_deps =
|
|||
adafruit/Adafruit BMP085 Library@1.2.4
|
||||
adafruit/Adafruit BME280 Library@2.2.4
|
||||
adafruit/Adafruit BMP3XX Library@2.1.5
|
||||
adafruit/Adafruit DPS310@1.1.5
|
||||
adafruit/Adafruit MCP9808 Library@2.0.2
|
||||
adafruit/Adafruit INA260 Library@1.5.2
|
||||
adafruit/Adafruit INA219@1.2.3
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 2a3a67f0431926dc3f32a8b216d264daab09b9bf
|
||||
Subproject commit b4044f8f9f3681d4d20521dbe13ee42c96eae353
|
|
@ -41,10 +41,8 @@ class AudioThread : public concurrency::OSThread
|
|||
delete i2sRtttl;
|
||||
i2sRtttl = nullptr;
|
||||
}
|
||||
if (rtttlFile != nullptr) {
|
||||
delete rtttlFile;
|
||||
rtttlFile = nullptr;
|
||||
}
|
||||
delete rtttlFile;
|
||||
rtttlFile = nullptr;
|
||||
|
||||
setCPUFast(false);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ class BluetoothStatus : public Status
|
|||
BluetoothStatus() { statusType = STATUS_TYPE_BLUETOOTH; }
|
||||
|
||||
// New BluetoothStatus: connected or disconnected
|
||||
BluetoothStatus(ConnectionState state)
|
||||
explicit BluetoothStatus(ConnectionState state)
|
||||
{
|
||||
assert(state != ConnectionState::PAIRING); // If pairing, use constructor which specifies passkey
|
||||
statusType = STATUS_TYPE_BLUETOOTH;
|
||||
|
@ -38,7 +38,7 @@ class BluetoothStatus : public Status
|
|||
}
|
||||
|
||||
// New BluetoothStatus: pairing, with passkey
|
||||
BluetoothStatus(std::string passkey) : Status()
|
||||
explicit BluetoothStatus(const std::string &passkey) : Status()
|
||||
{
|
||||
statusType = STATUS_TYPE_BLUETOOTH;
|
||||
this->state = ConnectionState::PAIRING;
|
||||
|
|
|
@ -47,7 +47,7 @@ ButtonThread::ButtonThread() : OSThread("Button")
|
|||
#ifdef USERPREFS_BUTTON_PIN
|
||||
int pin = config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN; // Resolved button pin
|
||||
#endif
|
||||
#if defined(HELTEC_CAPSULE_SENSOR_V3)
|
||||
#if defined(HELTEC_CAPSULE_SENSOR_V3) || defined(HELTEC_SENSOR_HUB)
|
||||
this->userButton = OneButton(pin, false, false);
|
||||
#elif defined(BUTTON_ACTIVE_LOW)
|
||||
this->userButton = OneButton(pin, BUTTON_ACTIVE_LOW, BUTTON_ACTIVE_PULLUP);
|
||||
|
|
|
@ -121,10 +121,15 @@ extern "C" void logLegacy(const char *level, const char *fmt, ...);
|
|||
// Default Bluetooth PIN
|
||||
#define defaultBLEPin 123456
|
||||
|
||||
#if HAS_ETHERNET
|
||||
#if HAS_ETHERNET && !defined(USE_WS5500)
|
||||
#include <RAK13800_W5100S.h>
|
||||
#endif // HAS_ETHERNET
|
||||
|
||||
#if HAS_ETHERNET && defined(USE_WS5500)
|
||||
#include <ETHClass2.h>
|
||||
#define ETH ETH2
|
||||
#endif // HAS_ETHERNET
|
||||
|
||||
#if HAS_WIFI
|
||||
#include <WiFi.h>
|
||||
#endif // HAS_WIFI
|
||||
|
@ -164,4 +169,4 @@ class Syslog
|
|||
bool vlogf(uint16_t pri, const char *appName, const char *fmt, va_list args) __attribute__((format(printf, 3, 0)));
|
||||
};
|
||||
|
||||
#endif // HAS_ETHERNET || HAS_WIFI
|
||||
#endif // HAS_NETWORKING
|
|
@ -35,6 +35,7 @@ SPIClass SDHandler = SPIClass(VSPI);
|
|||
#endif // HAS_SDCARD
|
||||
|
||||
|
||||
|
||||
#if defined(ARCH_STM32WL)
|
||||
|
||||
uint16_t OSFS::startOfEEPROM = 1;
|
||||
|
@ -68,33 +69,7 @@ void OSFS::writeNBytes(uint16_t address, unsigned int num, const byte *input)
|
|||
*/
|
||||
bool copyFile(const char *from, const char *to)
|
||||
{
|
||||
#ifdef ARCH_STM32WL
|
||||
unsigned char cbuffer[2048];
|
||||
|
||||
// Var to hold the result of actions
|
||||
OSFS::result r;
|
||||
|
||||
r = OSFS::getFile(from, cbuffer);
|
||||
|
||||
if (r == notfound) {
|
||||
LOG_ERROR("Failed to open source file %s", from);
|
||||
return false;
|
||||
} else if (r == noerr) {
|
||||
r = OSFS::newFile(to, cbuffer, true);
|
||||
if (r == noerr) {
|
||||
return true;
|
||||
} else {
|
||||
LOG_ERROR("OSFS Error %d", r);
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
LOG_ERROR("OSFS Error %d", r);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
#elif defined(FSCom)
|
||||
#ifdef FSCom
|
||||
// take SPI Lock
|
||||
concurrency::LockGuard g(spiLock);
|
||||
unsigned char cbuffer[16];
|
||||
|
@ -133,13 +108,7 @@ bool copyFile(const char *from, const char *to)
|
|||
*/
|
||||
bool renameFile(const char *pathFrom, const char *pathTo)
|
||||
{
|
||||
#ifdef ARCH_STM32WL
|
||||
if (copyFile(pathFrom, pathTo) && (OSFS::deleteFile(pathFrom) == OSFS::result::NO_ERROR)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
#elif defined(FSCom)
|
||||
#ifdef FSCom
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// take SPI Lock
|
||||
|
|
|
@ -15,13 +15,11 @@
|
|||
#endif
|
||||
|
||||
#if defined(ARCH_STM32WL)
|
||||
// STM32WL series 2 Kbytes (8 rows of 256 bytes)
|
||||
#include <EEPROM.h>
|
||||
#include <OSFS.h>
|
||||
|
||||
// Useful consts
|
||||
const OSFS::result noerr = OSFS::result::NO_ERROR;
|
||||
const OSFS::result notfound = OSFS::result::FILE_NOT_FOUND;
|
||||
// STM32WL
|
||||
#include "LittleFS.h"
|
||||
#define FSCom InternalFS
|
||||
#define FSBegin() FSCom.begin()
|
||||
using namespace STM32_LittleFS_Namespace;
|
||||
#endif
|
||||
|
||||
#if defined(ARCH_RP2040)
|
||||
|
|
|
@ -32,6 +32,11 @@
|
|||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
#if HAS_ETHERNET && defined(USE_WS5500)
|
||||
#include <ETHClass2.h>
|
||||
#define ETH ETH2
|
||||
#endif // HAS_ETHERNET
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef DELAY_FOREVER
|
||||
|
@ -386,7 +391,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||
virtual bool isVbusIn() override
|
||||
{
|
||||
#ifdef EXT_PWR_DETECT
|
||||
#ifdef HELTEC_CAPSULE_SENSOR_V3
|
||||
#if defined(HELTEC_CAPSULE_SENSOR_V3) || defined(HELTEC_SENSOR_HUB)
|
||||
// if external powered that pin will be pulled down
|
||||
if (digitalRead(EXT_PWR_DETECT) == LOW) {
|
||||
return true;
|
||||
|
@ -536,7 +541,7 @@ Power::Power() : OSThread("Power")
|
|||
bool Power::analogInit()
|
||||
{
|
||||
#ifdef EXT_PWR_DETECT
|
||||
#ifdef HELTEC_CAPSULE_SENSOR_V3
|
||||
#if defined(HELTEC_CAPSULE_SENSOR_V3) || defined(HELTEC_SENSOR_HUB)
|
||||
pinMode(EXT_PWR_DETECT, INPUT_PULLUP);
|
||||
#else
|
||||
pinMode(EXT_PWR_DETECT, INPUT);
|
||||
|
|
|
@ -11,12 +11,18 @@ static File openFile(const char *filename, bool fullAtomic)
|
|||
FSCom.remove(filename);
|
||||
return FSCom.open(filename, FILE_O_WRITE);
|
||||
#endif
|
||||
if (!fullAtomic)
|
||||
if (!fullAtomic) {
|
||||
FSCom.remove(filename); // Nuke the old file to make space (ignore if it !exists)
|
||||
}
|
||||
|
||||
String filenameTmp = filename;
|
||||
filenameTmp += ".tmp";
|
||||
|
||||
// FIXME: If we are doing a full atomic write, we may need to remove the old tmp file now
|
||||
// if (fullAtomic) {
|
||||
// FSCom.remove(filename);
|
||||
// }
|
||||
|
||||
// clear any previous LFS errors
|
||||
return FSCom.open(filenameTmp.c_str(), FILE_O_WRITE);
|
||||
}
|
||||
|
|
|
@ -135,6 +135,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#define LPS22HB_ADDR 0x5C
|
||||
#define LPS22HB_ADDR_ALT 0x5D
|
||||
#define SHT31_4x_ADDR 0x44
|
||||
#define SHT31_4x_ADDR_ALT 0x45
|
||||
#define PMSA0031_ADDR 0x12
|
||||
#define QMA6100P_ADDR 0x12
|
||||
#define AHT10_ADDR 0x38
|
||||
|
@ -150,6 +151,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#define MAX30102_ADDR 0x57
|
||||
#define MLX90614_ADDR_DEF 0x5A
|
||||
#define CGRADSENS_ADDR 0x66
|
||||
#define LTR390UV_ADDR 0x53
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// ACCELEROMETER
|
||||
|
|
|
@ -67,6 +67,8 @@ class ScanI2C
|
|||
INA226,
|
||||
NXP_SE050,
|
||||
DFROBOT_RAIN,
|
||||
DPS310,
|
||||
LTR390UV,
|
||||
} DeviceType;
|
||||
|
||||
// typedef uint8_t DeviceAddress;
|
||||
|
|
|
@ -237,6 +237,16 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||
logFoundDevice("BMP085/BMP180", (uint8_t)addr.address);
|
||||
type = BMP_085;
|
||||
break;
|
||||
case 0x00:
|
||||
// do we have a DPS310 instead?
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0D), 1);
|
||||
switch (registerValue) {
|
||||
case 0x10:
|
||||
logFoundDevice("DPS310", (uint8_t)addr.address);
|
||||
type = DPS310;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); // GET_ID
|
||||
switch (registerValue) {
|
||||
|
@ -339,7 +349,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||
}
|
||||
break;
|
||||
}
|
||||
case SHT31_4x_ADDR:
|
||||
case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT
|
||||
case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
|
||||
if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c) {
|
||||
type = SHT4X;
|
||||
|
@ -412,11 +423,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||
SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(TSL25911_ADDR, TSL2591, "TSL2591", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(OPT3001_ADDR, OPT3001, "OPT3001", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
|
||||
#ifdef HAS_TPS65233
|
||||
SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233", (uint8_t)addr.address);
|
||||
#endif
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
#include <cstring> // Include for strstr
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "configuration.h"
|
||||
#if !MESHTASTIC_EXCLUDE_GPS
|
||||
#include "Default.h"
|
||||
|
@ -1100,12 +1104,16 @@ int32_t GPS::runOnce()
|
|||
return (powerState == GPS_ACTIVE) ? GPS_THREAD_INTERVAL : 5000;
|
||||
}
|
||||
|
||||
// clear the GPS rx buffer as quickly as possible
|
||||
// clear the GPS rx/tx buffer as quickly as possible
|
||||
void GPS::clearBuffer()
|
||||
{
|
||||
#ifdef ARCH_ESP32
|
||||
_serial_gps->flush(false);
|
||||
#else
|
||||
int x = _serial_gps->available();
|
||||
while (x--)
|
||||
_serial_gps->read();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
||||
|
@ -1117,7 +1125,7 @@ int GPS::prepareDeepSleep(void *unused)
|
|||
}
|
||||
|
||||
static const char *PROBE_MESSAGE = "Trying %s (%s)...";
|
||||
static const char *DETECTED_MESSAGE = "%s detected, using %s Module";
|
||||
static const char *DETECTED_MESSAGE = "%s detected";
|
||||
|
||||
#define PROBE_SIMPLE(CHIP, TOWRITE, RESPONSE, DRIVER, TIMEOUT, ...) \
|
||||
do { \
|
||||
|
@ -1125,11 +1133,22 @@ static const char *DETECTED_MESSAGE = "%s detected, using %s Module";
|
|||
clearBuffer(); \
|
||||
_serial_gps->write(TOWRITE "\r\n"); \
|
||||
if (getACK(RESPONSE, TIMEOUT) == GNSS_RESPONSE_OK) { \
|
||||
LOG_INFO(DETECTED_MESSAGE, CHIP, #DRIVER); \
|
||||
LOG_INFO(DETECTED_MESSAGE, CHIP); \
|
||||
return DRIVER; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define PROBE_FAMILY(FAMILY_NAME, COMMAND, RESPONSE_MAP, TIMEOUT) \
|
||||
do { \
|
||||
LOG_DEBUG(PROBE_MESSAGE, COMMAND, FAMILY_NAME); \
|
||||
clearBuffer(); \
|
||||
_serial_gps->write(COMMAND "\r\n"); \
|
||||
GnssModel_t detectedDriver = getProbeResponse(TIMEOUT, RESPONSE_MAP); \
|
||||
if (detectedDriver != GNSS_MODEL_UNKNOWN) { \
|
||||
return detectedDriver; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
GnssModel_t GPS::probe(int serialSpeed)
|
||||
{
|
||||
#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
|
||||
|
@ -1160,31 +1179,34 @@ GnssModel_t GPS::probe(int serialSpeed)
|
|||
delay(20);
|
||||
|
||||
// Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A
|
||||
PROBE_SIMPLE("UC6580", "$PDTINFO", "UC6580", GNSS_MODEL_UC6580, 500);
|
||||
PROBE_SIMPLE("UM600", "$PDTINFO", "UM600", GNSS_MODEL_UC6580, 500);
|
||||
PROBE_SIMPLE("ATGM336H", "$PCAS06,1*1A", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H, 500);
|
||||
/* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS))
|
||||
based on AT6558 */
|
||||
PROBE_SIMPLE("ATGM332D", "$PCAS06,1*1A", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H, 500);
|
||||
std::vector<ChipInfo> unicore = {{"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}};
|
||||
PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500);
|
||||
|
||||
std::vector<ChipInfo> atgm = {
|
||||
{"ATGM336H", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H},
|
||||
/* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) based on AT6558 */
|
||||
{"ATGM332D", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H}};
|
||||
PROBE_FAMILY("ATGM33xx Family", "$PCAS06,1*1A", atgm, 500);
|
||||
|
||||
/* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */
|
||||
_serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF to reduce volume
|
||||
_serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume
|
||||
_serial_gps->write("$PAIR513*3D\r\n"); // save configuration
|
||||
PROBE_SIMPLE("AG3335", "$PAIR021*39", "$PAIR021,AG3335", GNSS_MODEL_AG3335, 500);
|
||||
PROBE_SIMPLE("AG3352", "$PAIR021*39", "$PAIR021,AG3352", GNSS_MODEL_AG3352, 500);
|
||||
PROBE_SIMPLE("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500);
|
||||
std::vector<ChipInfo> airoha = {{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335},
|
||||
{"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
|
||||
{"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352}};
|
||||
PROBE_FAMILY("Airoha Family", "$PAIR021*39", airoha, 1000);
|
||||
|
||||
PROBE_SIMPLE("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500);
|
||||
PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 500);
|
||||
|
||||
// Close all NMEA sentences, valid for L76B MTK platform (Waveshare Pico GPS)
|
||||
// Close all NMEA sentences, valid for MTK3333 and MTK3339 platforms
|
||||
_serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n");
|
||||
delay(20);
|
||||
|
||||
PROBE_SIMPLE("L76B", "$PMTK605*31", "Quectel-L76B", GNSS_MODEL_MTK_L76B, 500);
|
||||
PROBE_SIMPLE("PA1616S", "$PMTK605*31", "1616S", GNSS_MODEL_MTK_PA1616S, 500);
|
||||
|
||||
PROBE_SIMPLE("LS20031", "$PMTK605*31", "MC-1513", GNSS_MODEL_LS20031, 500);
|
||||
std::vector<ChipInfo> mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B},
|
||||
{"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S},
|
||||
{"LS20031", "MC-1513", GNSS_MODEL_MTK_L76B}};
|
||||
PROBE_FAMILY("MTK Family", "$PMTK605*31", mtk, 500);
|
||||
|
||||
uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
|
||||
UBXChecksum(cfg_rate, sizeof(cfg_rate));
|
||||
|
@ -1281,6 +1303,38 @@ GnssModel_t GPS::probe(int serialSpeed)
|
|||
return GNSS_MODEL_UNKNOWN;
|
||||
}
|
||||
|
||||
GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap)
|
||||
{
|
||||
String response = "";
|
||||
unsigned long start = millis();
|
||||
while (millis() - start < timeout) {
|
||||
if (_serial_gps->available()) {
|
||||
response += (char)_serial_gps->read();
|
||||
|
||||
if (response.endsWith(",") || response.endsWith("\r\n")) {
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_DEBUG(response.c_str());
|
||||
#endif
|
||||
// check if we can see our chips
|
||||
for (const auto &chipInfo : responseMap) {
|
||||
if (strstr(response.c_str(), chipInfo.detectionString.c_str()) != nullptr) {
|
||||
LOG_INFO("%s detected", chipInfo.chipName.c_str());
|
||||
return chipInfo.driver;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (response.endsWith("\r\n")) {
|
||||
response.trim();
|
||||
response = ""; // Reset the response string for the next potential message
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_DEBUG(response.c_str());
|
||||
#endif
|
||||
return GNSS_MODEL_UNKNOWN; // Return empty string on timeout
|
||||
}
|
||||
|
||||
GPS *GPS::createGps()
|
||||
{
|
||||
int8_t _rx_gpio = config.position.rx_gpio;
|
||||
|
|
|
@ -48,6 +48,11 @@ enum GPSPowerState : uint8_t {
|
|||
GPS_OFF // Powered off indefinitely
|
||||
};
|
||||
|
||||
struct ChipInfo {
|
||||
String chipName; // The name of the chip (for logging)
|
||||
String detectionString; // The string to match in the response
|
||||
GnssModel_t driver; // The driver to use
|
||||
};
|
||||
/**
|
||||
* A gps class that only reads from the GPS periodically and keeps the gps powered down except when reading
|
||||
*
|
||||
|
@ -230,6 +235,8 @@ class GPS : private concurrency::OSThread
|
|||
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
GnssModel_t getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap);
|
||||
|
||||
// Get GNSS model
|
||||
GnssModel_t probe(int serialSpeed);
|
||||
|
||||
|
|
|
@ -166,7 +166,8 @@ bool EInkDisplay::connect()
|
|||
}
|
||||
|
||||
#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) || \
|
||||
defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER)
|
||||
defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) || \
|
||||
defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER)
|
||||
{
|
||||
// Start HSPI
|
||||
hspi = new SPIClass(HSPI);
|
||||
|
@ -182,6 +183,9 @@ bool EInkDisplay::connect()
|
|||
// Init GxEPD2
|
||||
adafruitDisplay->init();
|
||||
adafruitDisplay->setRotation(3);
|
||||
#if defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(CROWPANEL_ESP32S3_4_EPAPER)
|
||||
adafruitDisplay->setRotation(0);
|
||||
#endif
|
||||
}
|
||||
#elif defined(PCA10059) || defined(ME25LS01)
|
||||
{
|
||||
|
|
|
@ -68,7 +68,8 @@ class EInkDisplay : public OLEDDisplay
|
|||
|
||||
// If display uses HSPI
|
||||
#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \
|
||||
defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER)
|
||||
defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) || \
|
||||
defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER)
|
||||
SPIClass *hspi = NULL;
|
||||
#endif
|
||||
|
||||
|
@ -77,4 +78,4 @@ class EInkDisplay : public OLEDDisplay
|
|||
uint32_t lastDrawMsec = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -324,6 +324,14 @@ void EInkDynamicDisplay::checkConsecutiveFastRefreshes()
|
|||
if (refresh != UNSPECIFIED)
|
||||
return;
|
||||
|
||||
// Bypass limit if UNLIMITED_FAST mode is active
|
||||
if (frameFlags & UNLIMITED_FAST) {
|
||||
refresh = FAST;
|
||||
reason = NO_OBJECTIONS;
|
||||
LOG_DEBUG("refresh=FAST, reason=UNLIMITED_FAST_MODE_ACTIVE, frameFlags=0x%x", frameFlags);
|
||||
return;
|
||||
}
|
||||
|
||||
// If too many FAST refreshes consecutively - force a FULL refresh
|
||||
if (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) {
|
||||
refresh = FULL;
|
||||
|
|
|
@ -23,6 +23,10 @@ class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWo
|
|||
EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus);
|
||||
~EInkDynamicDisplay();
|
||||
|
||||
// Methods to enable or disable unlimited fast refresh mode
|
||||
void enableUnlimitedFastMode() { addFrameFlag(UNLIMITED_FAST); }
|
||||
void disableUnlimitedFastMode() { frameFlags = (frameFlagTypes)(frameFlags & ~UNLIMITED_FAST); }
|
||||
|
||||
// What kind of frame is this
|
||||
enum frameFlagTypes : uint8_t {
|
||||
BACKGROUND = (1 << 0), // For frames via display()
|
||||
|
@ -30,6 +34,7 @@ class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWo
|
|||
COSMETIC = (1 << 2), // For splashes
|
||||
DEMAND_FAST = (1 << 3), // Special case only
|
||||
BLOCKING = (1 << 4), // Modifier - block while refresh runs
|
||||
UNLIMITED_FAST = (1 << 5)
|
||||
};
|
||||
void addFrameFlag(frameFlagTypes flag);
|
||||
|
||||
|
|
|
@ -73,6 +73,16 @@
|
|||
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
|
||||
#endif
|
||||
|
||||
#if defined(CROWPANEL_ESP32S3_5_EPAPER)
|
||||
#include "graphics/fonts/EinkDisplayFonts.h"
|
||||
#undef FONT_SMALL
|
||||
#undef FONT_MEDIUM
|
||||
#undef FONT_LARGE
|
||||
#define FONT_SMALL FONT_LARGE_LOCAL // Height: 30
|
||||
#define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 30
|
||||
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 30
|
||||
#endif
|
||||
|
||||
#define _fontHeight(font) ((font)[1] + 1) // height is position 1
|
||||
|
||||
#define FONT_HEIGHT_SMALL _fontHeight(FONT_SMALL)
|
||||
|
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,14 @@
|
|||
#ifndef EINKDISPLAYFONTS_h
|
||||
#define EINKDISPLAYFONTS_h
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <Arduino.h>
|
||||
#elif __MBED__
|
||||
#define PROGMEM
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Monospaced Plain 30
|
||||
*/
|
||||
extern const uint8_t Monospaced_plain_30[] PROGMEM;
|
||||
#endif
|
|
@ -40,13 +40,11 @@ void LatchingBacklight::setPin(uint8_t pin, bool activeWhen)
|
|||
// Ensures the backlight is off
|
||||
int LatchingBacklight::beforeDeepSleep(void *unused)
|
||||
{
|
||||
// We shouldn't need to guard the block like this
|
||||
// Contingency for:
|
||||
// - settings corruption: settings.optionalMenuItems.backlight guards backlight code in MenuApplet
|
||||
// - improper use in the future
|
||||
// Contingency only
|
||||
// - pin wasn't set
|
||||
if (pin != (uint8_t)-1) {
|
||||
off();
|
||||
pinMode(pin, INPUT); // High impedence - unnecessary?
|
||||
pinMode(pin, INPUT); // High impedance - unnecessary?
|
||||
} else
|
||||
LOG_WARN("LatchingBacklight instantiated, but pin not set");
|
||||
return 0; // Continue with deep sleep
|
||||
|
|
|
@ -12,7 +12,7 @@ EInk::EInk(uint16_t width, uint16_t height, UpdateTypes supported)
|
|||
}
|
||||
|
||||
// Used by NicheGraphics implementations to check if a display supports a specific refresh operation.
|
||||
// Whether or the update type is supported is specified in the constructor
|
||||
// Whether or not the update type is supported is specified in the constructor
|
||||
bool EInk::supports(UpdateTypes type)
|
||||
{
|
||||
// The EInkUpdateTypes enum assigns each type a unique bit. We are checking if that bit is set.
|
||||
|
|
|
@ -31,7 +31,7 @@ class EInk : private concurrency::OSThread
|
|||
virtual void begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_busy, uint8_t pin_rst = -1) = 0;
|
||||
virtual void update(uint8_t *imageData, UpdateTypes type) = 0; // Change the display image
|
||||
void await(); // Wait for an in-progress update to complete before proceeding
|
||||
bool supports(UpdateTypes type); // Can display perfom a certain update type
|
||||
bool supports(UpdateTypes type); // Can display perform a certain update type
|
||||
bool busy() { return updateRunning; } // Display able to update right now?
|
||||
|
||||
const uint16_t width; // Public so that NicheGraphics implementations can access. Safe because const.
|
||||
|
@ -47,8 +47,8 @@ class EInk : private concurrency::OSThread
|
|||
|
||||
const UpdateTypes supportedUpdateTypes; // Capabilities of a derived display class
|
||||
bool updateRunning = false; // see EInk::busy()
|
||||
uint32_t updateBegunAt; // For initial pause before polling for update completion
|
||||
uint32_t pollingInterval; // How often to check if update complete (ms)
|
||||
uint32_t updateBegunAt = 0; // For initial pause before polling for update completion
|
||||
uint32_t pollingInterval = 0; // How often to check if update complete (ms)
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::Drivers
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
using namespace NicheGraphics::Drivers;
|
||||
|
||||
// Map the display controller IC's output to the conected panel
|
||||
// Map the display controller IC's output to the connected panel
|
||||
void GDEY0154D67::configScanning()
|
||||
{
|
||||
// "Driver output control"
|
||||
|
|
|
@ -98,6 +98,7 @@ void LCMEN213EFC1::begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t
|
|||
reset();
|
||||
}
|
||||
|
||||
// Display an image on the display
|
||||
void LCMEN213EFC1::update(uint8_t *imageData, UpdateTypes type)
|
||||
{
|
||||
this->updateType = type;
|
||||
|
@ -161,13 +162,6 @@ void LCMEN213EFC1::sendCommand(const uint8_t command)
|
|||
|
||||
void LCMEN213EFC1::sendData(uint8_t data)
|
||||
{
|
||||
// spi->beginTransaction(spiSettings);
|
||||
// digitalWrite(pin_dc, HIGH); // DC pin HIGH indicates data, instead of command
|
||||
// digitalWrite(pin_cs, LOW);
|
||||
// spi->transfer(data);
|
||||
// digitalWrite(pin_cs, HIGH);
|
||||
// digitalWrite(pin_dc, HIGH);
|
||||
// spi->endTransaction();
|
||||
sendData(&data, 1);
|
||||
}
|
||||
|
||||
|
|
|
@ -45,21 +45,24 @@ class LCMEN213EFC1 : public EInk
|
|||
void configFull(); // Configure display for FULL refresh
|
||||
void configFast(); // Configure display for FAST refresh
|
||||
void writeNewImage();
|
||||
void writeOldImage();
|
||||
void writeOldImage(); // Used for "differential update", aka FAST refresh
|
||||
|
||||
void detachFromUpdate();
|
||||
bool isUpdateDone();
|
||||
void finalizeUpdate();
|
||||
|
||||
protected:
|
||||
uint8_t bufferOffsetX; // In bytes. Panel x=0 does not always align with controller x=0. Quirky internal wiring?
|
||||
uint8_t bufferRowSize; // In bytes. Rows store 8 pixels per byte. Rounded up to fit (e.g. 122px would require 16 bytes)
|
||||
uint32_t bufferSize; // In bytes. Rows * Columns
|
||||
uint8_t *buffer;
|
||||
UpdateTypes updateType;
|
||||
uint8_t bufferOffsetX = 0; // In bytes. Panel x=0 does not always align with controller x=0. Quirky internal wiring?
|
||||
uint8_t bufferRowSize = 0; // In bytes. Rows store 8 pixels per byte. Rounded up to fit (e.g. 122px would require 16 bytes)
|
||||
uint32_t bufferSize = 0; // In bytes. Rows * Columns
|
||||
uint8_t *buffer = nullptr;
|
||||
UpdateTypes updateType = UpdateTypes::UNSPECIFIED;
|
||||
|
||||
uint8_t pin_dc, pin_cs, pin_busy, pin_rst;
|
||||
SPIClass *spi;
|
||||
uint8_t pin_dc = -1;
|
||||
uint8_t pin_cs = -1;
|
||||
uint8_t pin_busy = -1;
|
||||
uint8_t pin_rst = -1;
|
||||
SPIClass *spi = nullptr;
|
||||
SPISettings spiSettings = SPISettings(6000000, MSBFIRST, SPI_MODE0);
|
||||
};
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
A driver for E-Ink SPI displays. Suitable for re-use by various NicheGraphics UIs.
|
||||
|
||||
Your UI should use the class `NicheGraphics::Drivers::EInk` .
|
||||
When you set up a hardware variant, you will use one of specific display model classes, which extend the EInk class.
|
||||
When you set up a hardware variant, you will use one of the specific display model classes, which extend the EInk class.
|
||||
|
||||
An example setup might look like this:
|
||||
|
||||
|
@ -30,7 +30,7 @@ void setupNicheGraphics()
|
|||
|
||||
## Methods
|
||||
|
||||
### `update(uint8_t *imageData, UpdateTypes type, bool async=true)`
|
||||
### `update(uint8_t *imageData, UpdateTypes type)`
|
||||
|
||||
Update the image on the display
|
||||
|
||||
|
@ -39,7 +39,6 @@ Update the image on the display
|
|||
- `FULL`
|
||||
- `FAST`
|
||||
- (Other custom types may be possible)
|
||||
- _`async`_ whether to wait for update to complete, or continue code execution
|
||||
|
||||
The imageData is a 1-bit image. X-Pixels are 8-per byte, with the MSB being the leftmost pixel. This was not an InkHUD design decision; it is the raw format accepted by the E-Ink display controllers ICs.
|
||||
|
||||
|
@ -63,6 +62,10 @@ uint8_t xBits = (7-x) % 8;
|
|||
image[yByte + xByte] |= (1 << xBits); // Set pixel x=12, y=2
|
||||
```
|
||||
|
||||
### `await()`
|
||||
|
||||
Wait for an in-progress update to complete before continuing
|
||||
|
||||
### `supports(UpdateTypes type)`
|
||||
|
||||
Check if display supports a specific update type. `true` if supported.
|
||||
|
@ -75,7 +78,7 @@ Check if display is already performing an `update()`. `true` if already updating
|
|||
|
||||
### `width()`
|
||||
|
||||
Width of the display, in pixels. Note: most displays are portait. Your UI will need to implement rotation in software.
|
||||
Width of the display, in pixels. Note: most displays are portrait. Your UI will need to implement rotation in software.
|
||||
|
||||
### `height()`
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ void SSD16XX::begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_b
|
|||
pinMode(pin_busy, INPUT);
|
||||
|
||||
// If using a reset pin, hold high
|
||||
// Reset is active low for solmon systech ICs
|
||||
// Reset is active low for Solomon Systech ICs
|
||||
if (pin_rst != 0xFF)
|
||||
pinMode(pin_rst, INPUT_PULLUP);
|
||||
|
||||
|
@ -72,13 +72,6 @@ void SSD16XX::sendCommand(const uint8_t command)
|
|||
|
||||
void SSD16XX::sendData(uint8_t data)
|
||||
{
|
||||
// spi->beginTransaction(spiSettings);
|
||||
// digitalWrite(pin_dc, HIGH); // DC pin HIGH indicates data, instead of command
|
||||
// digitalWrite(pin_cs, LOW);
|
||||
// spi->transfer(data);
|
||||
// digitalWrite(pin_cs, HIGH);
|
||||
// digitalWrite(pin_dc, HIGH);
|
||||
// spi->endTransaction();
|
||||
sendData(&data, 1);
|
||||
}
|
||||
|
||||
|
|
|
@ -39,21 +39,24 @@ class SSD16XX : public EInk
|
|||
virtual void configUpdateSequence(); // Tell controller IC which operations to run
|
||||
|
||||
virtual void writeNewImage();
|
||||
virtual void writeOldImage();
|
||||
virtual void writeOldImage(); // Image which can be used at *next* update for "differential refresh"
|
||||
|
||||
virtual void detachFromUpdate();
|
||||
virtual bool isUpdateDone() override;
|
||||
virtual void finalizeUpdate() override;
|
||||
|
||||
protected:
|
||||
uint8_t bufferOffsetX; // In bytes. Panel x=0 does not always align with controller x=0. Quirky internal wiring?
|
||||
uint8_t bufferRowSize; // In bytes. Rows store 8 pixels per byte. Rounded up to fit (e.g. 122px would require 16 bytes)
|
||||
uint32_t bufferSize; // In bytes. Rows * Columns
|
||||
uint8_t *buffer;
|
||||
UpdateTypes updateType;
|
||||
uint8_t bufferOffsetX = 0; // In bytes. Panel x=0 does not always align with controller x=0. Quirky internal wiring?
|
||||
uint8_t bufferRowSize = 0; // In bytes. Rows store 8 pixels per byte. Rounded up to fit (e.g. 122px would require 16 bytes)
|
||||
uint32_t bufferSize = 0; // In bytes. Rows * Columns
|
||||
uint8_t *buffer = nullptr;
|
||||
UpdateTypes updateType = UpdateTypes::UNSPECIFIED;
|
||||
|
||||
uint8_t pin_dc, pin_cs, pin_busy, pin_rst;
|
||||
SPIClass *spi;
|
||||
uint8_t pin_dc = -1;
|
||||
uint8_t pin_cs = -1;
|
||||
uint8_t pin_busy = -1;
|
||||
uint8_t pin_rst = -1;
|
||||
SPIClass *spi = nullptr;
|
||||
SPISettings spiSettings = SPISettings(4000000, MSBFIRST, SPI_MODE0);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# NicheGraphics - Drivers
|
||||
|
||||
Common drivers which can be used by various NicheGrapihcs UIs
|
||||
Common drivers which can be used by various NicheGraphics UIs
|
||||
|
|
|
@ -119,7 +119,7 @@ template <typename T> class FlashData
|
|||
// Calculate a hash of the data
|
||||
uint32_t hash = getHash(data);
|
||||
|
||||
f.write((uint8_t *)data, sizeof(T)); // Write the actualy data
|
||||
f.write((uint8_t *)data, sizeof(T)); // Write the actual data
|
||||
f.write((uint8_t *)&hash, sizeof(hash)); // Append the hash
|
||||
|
||||
// f.flush();
|
||||
|
|
|
@ -4,7 +4,7 @@ Uses Windows-1251 encoding to map translingual Cyrillic characters to range betw
|
|||
https://en.wikipedia.org/wiki/Windows-1251
|
||||
|
||||
Cyrillic characters present to the firmware as UTF8.
|
||||
A Niche Graphics implementation needs to identify these, and subsitute the appropriate Windows-1251 char value.
|
||||
A NicheGraphics implementation needs to identify these, and substitute the appropriate Windows-1251 char value.
|
||||
|
||||
*/
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include "./Applet.h"
|
||||
|
||||
#include "main.h"
|
||||
|
||||
#include "RTC.h"
|
||||
|
||||
using namespace NicheGraphics;
|
||||
|
@ -16,10 +18,15 @@ InkHUD::Applet::Applet() : GFX(0, 0)
|
|||
// The width and height will change dynamically, depending on Applet tiling
|
||||
// If you're getting a "divide by zero error", consider it an assert:
|
||||
// WindowManager should be the only one controlling the rendering
|
||||
|
||||
inkhud = InkHUD::getInstance();
|
||||
settings = &inkhud->persistence->settings;
|
||||
latestMessage = &inkhud->persistence->latestMessage;
|
||||
}
|
||||
|
||||
// The raw pixel output generated by AdafruitGFX drawing
|
||||
// Hand off to the applet's tile, which will in-turn pass to the window manager
|
||||
// Draw a single pixel
|
||||
// The raw pixel output generated by AdafruitGFX drawing all passes through here
|
||||
// Hand off to the applet's tile, which will in-turn pass to the renderer
|
||||
void InkHUD::Applet::drawPixel(int16_t x, int16_t y, uint16_t color)
|
||||
{
|
||||
// Only render pixels if they fall within user's cropped region
|
||||
|
@ -27,9 +34,10 @@ void InkHUD::Applet::drawPixel(int16_t x, int16_t y, uint16_t color)
|
|||
assignedTile->handleAppletPixel(x, y, (Color)color);
|
||||
}
|
||||
|
||||
// Sets which tile the applet renders for
|
||||
// Link our applet to a tile
|
||||
// This can only be called by Tile::assignApplet
|
||||
// The tile determines the applets dimensions
|
||||
// Pixel output is passed to tile during render()
|
||||
// This should only be called by Tile::assignApplet
|
||||
void InkHUD::Applet::setTile(Tile *t)
|
||||
{
|
||||
// If we're setting (not clearing), make sure the link is "reciprocal"
|
||||
|
@ -39,25 +47,32 @@ void InkHUD::Applet::setTile(Tile *t)
|
|||
assignedTile = t;
|
||||
}
|
||||
|
||||
// Which tile will the applet render() to?
|
||||
// The tile to which our applet is assigned
|
||||
InkHUD::Tile *InkHUD::Applet::getTile()
|
||||
{
|
||||
return assignedTile;
|
||||
}
|
||||
|
||||
// Draw the applet
|
||||
void InkHUD::Applet::render()
|
||||
{
|
||||
assert(assignedTile); // Ensure that we have a tile
|
||||
assert(assignedTile->getAssignedApplet() == this); // Ensure that we have a reciprocal link with the tile
|
||||
|
||||
wantRender = false; // Clear the flag set by requestUpdate
|
||||
wantAutoshow = false; // If we're rendering now, it means our request was considered. It may or may not have been granted.
|
||||
wantUpdateType = Drivers::EInk::UpdateTypes::UNSPECIFIED; // Our requested type has been considered by now. Tidy up.
|
||||
// WindowManager::update has now consumed the info about our update request
|
||||
// Clear everything for future requests
|
||||
wantRender = false; // Flag set by requestUpdate
|
||||
wantAutoshow = false; // Flag set by requestAutoShow. May or may not have been honored.
|
||||
wantUpdateType = Drivers::EInk::UpdateTypes::UNSPECIFIED; // Update type we wanted. May on may not have been granted.
|
||||
|
||||
updateDimensions();
|
||||
resetDrawingSpace();
|
||||
onRender(); // Derived applet's drawing takes place here
|
||||
|
||||
// Handle "Tile Highlighting"
|
||||
// Some devices may use an auxiliary button to switch between tiles
|
||||
// When this happens, we temporarily highlight the newly focused tile with a border
|
||||
|
||||
// If our tile is (or was) highlighted, to indicate a change in focus
|
||||
if (Tile::highlightTarget == assignedTile) {
|
||||
// Draw the highlight
|
||||
|
@ -77,7 +92,8 @@ void InkHUD::Applet::render()
|
|||
}
|
||||
|
||||
// Does the applet want to render now?
|
||||
// Checks whether the applet called requestUpdate() recently, in response to an event
|
||||
// Checks whether the applet called requestUpdate recently, in response to an event
|
||||
// Used by WindowManager::update
|
||||
bool InkHUD::Applet::wantsToRender()
|
||||
{
|
||||
return wantRender;
|
||||
|
@ -85,18 +101,21 @@ bool InkHUD::Applet::wantsToRender()
|
|||
|
||||
// Does the applet want to be moved to foreground before next render, to show new data?
|
||||
// User specifies whether an applet has permission for this, using the on-screen menu
|
||||
// Used by WindowManager::update
|
||||
bool InkHUD::Applet::wantsToAutoshow()
|
||||
{
|
||||
return wantAutoshow;
|
||||
}
|
||||
|
||||
// Which technique would this applet prefer that the display use to change the image?
|
||||
// Used by WindowManager::update
|
||||
Drivers::EInk::UpdateTypes InkHUD::Applet::wantsUpdateType()
|
||||
{
|
||||
return wantUpdateType;
|
||||
}
|
||||
|
||||
// Get size of the applet's drawing space from its tile
|
||||
// Performed immediately before derived applet's drawing code runs
|
||||
void InkHUD::Applet::updateDimensions()
|
||||
{
|
||||
assert(assignedTile);
|
||||
|
@ -113,19 +132,20 @@ void InkHUD::Applet::resetDrawingSpace()
|
|||
setTextColor(BLACK); // Reset text params
|
||||
setCursor(0, 0);
|
||||
setTextWrap(false);
|
||||
setFont(AppletFont()); // Restore the default AdafruitGFX font
|
||||
setFont(fontSmall);
|
||||
}
|
||||
|
||||
// Tell the window manager that we want to render now
|
||||
// Tell InkHUD::Renderer that we want to render now
|
||||
// Applets should internally listen for events they are interested in, via MeshModule, CallbackObserver etc
|
||||
// When an applet decides it has heard something important, and wants to redraw, it calls this method
|
||||
// Once the window manager has given other applets a chance to process whatever event we just detected,
|
||||
// it will run Applet::render(), which may draw our applet to screen, if it is shown (forgeround)
|
||||
// Once the renderer has given other applets a chance to process whatever event we just detected,
|
||||
// it will run Applet::render(), which may draw our applet to screen, if it is shown (foreground)
|
||||
// We should requestUpdate even if our applet is currently background, because this might be changed by autoshow
|
||||
void InkHUD::Applet::requestUpdate(Drivers::EInk::UpdateTypes type)
|
||||
{
|
||||
wantRender = true;
|
||||
wantUpdateType = type;
|
||||
WindowManager::getInstance()->requestUpdate();
|
||||
inkhud->requestUpdate();
|
||||
}
|
||||
|
||||
// Ask window manager to move this applet to foreground at start of next render
|
||||
|
@ -138,7 +158,7 @@ void InkHUD::Applet::requestAutoshow()
|
|||
// Called when an Applet begins running
|
||||
// Active applets are considered "enabled"
|
||||
// They should now listen for events, and request their own updates
|
||||
// They may also be force rendered by the window manager at any time
|
||||
// They may also be unexpectedly renderer at any time by other InkHUD components
|
||||
// Applets can be activated at run-time through the on-screen menu
|
||||
void InkHUD::Applet::activate()
|
||||
{
|
||||
|
@ -146,7 +166,7 @@ void InkHUD::Applet::activate()
|
|||
active = true;
|
||||
}
|
||||
|
||||
// Called when an Applet stop running
|
||||
// Called when an Applet stops running
|
||||
// Inactive applets are considered "disabled"
|
||||
// They should not listen for events, process data
|
||||
// They will not be rendered
|
||||
|
@ -173,7 +193,7 @@ bool InkHUD::Applet::isActive()
|
|||
|
||||
// Begin showing the Applet
|
||||
// It will be rendered immediately to whichever tile it is assigned
|
||||
// The window manager will also now honor requestUpdate() calls from this applet
|
||||
// The Renderer will also now honor requestUpdate() calls from this applet
|
||||
void InkHUD::Applet::bringToForeground()
|
||||
{
|
||||
if (!foreground) {
|
||||
|
@ -186,7 +206,7 @@ void InkHUD::Applet::bringToForeground()
|
|||
|
||||
// Stop showing the Applet
|
||||
// Calls to requestUpdate() will no longer be honored
|
||||
// When one applet moves to background, another should move to foreground
|
||||
// When one applet moves to background, another should move to foreground (exception: some system applets)
|
||||
void InkHUD::Applet::sendToBackground()
|
||||
{
|
||||
if (foreground) {
|
||||
|
@ -196,6 +216,10 @@ void InkHUD::Applet::sendToBackground()
|
|||
}
|
||||
|
||||
// Is the applet currently displayed on a tile
|
||||
// Note: in some uncommon situations, an applet may be "foreground", and still not visible.
|
||||
// This can occur when a system applet is covering the screen (e.g. during BLE pairing)
|
||||
// This is not our applets responsibility to handle,
|
||||
// as in those situations, the system applet will have "locked" rendering
|
||||
bool InkHUD::Applet::isForeground()
|
||||
{
|
||||
return foreground;
|
||||
|
@ -248,7 +272,7 @@ void InkHUD::Applet::printAt(int16_t x, int16_t y, std::string text, HorizontalA
|
|||
// Custom font
|
||||
// - set with AppletFont::addSubstitution
|
||||
// - find certain UTF8 chars
|
||||
// - replace with glpyh from custom font (or suitable ASCII addSubstitution?)
|
||||
// - replace with glyph from custom font (or suitable ASCII addSubstitution?)
|
||||
getFont().applySubstitutions(&text);
|
||||
|
||||
// We do still have to run getTextBounds to find the width
|
||||
|
@ -271,8 +295,7 @@ void InkHUD::Applet::printAt(int16_t x, int16_t y, std::string text, HorizontalA
|
|||
break;
|
||||
}
|
||||
|
||||
// We're using a fixed line height (getFontDimensions), rather than sizing to text (getTextBounds)
|
||||
// Note: the FontDimensions values for this are unsigned
|
||||
// We're using a fixed line height, rather than sizing to text (getTextBounds)
|
||||
|
||||
switch (va) {
|
||||
case TOP:
|
||||
|
@ -291,7 +314,7 @@ void InkHUD::Applet::printAt(int16_t x, int16_t y, std::string text, HorizontalA
|
|||
}
|
||||
|
||||
// Set which font should be used for subsequent drawing
|
||||
// This is AppletFont type, which is a wrapper for AdfruitGFX font, with some precalculated dimension data
|
||||
// This is AppletFont type, which is a wrapper for AdafruitGFX font, with some precalculated dimension data
|
||||
void InkHUD::Applet::setFont(AppletFont f)
|
||||
{
|
||||
GFX::setFont(f.gfxFont);
|
||||
|
@ -299,20 +322,12 @@ void InkHUD::Applet::setFont(AppletFont f)
|
|||
}
|
||||
|
||||
// Get which font is currently being used for drawing
|
||||
// This is AppletFont type, which is a wrapper for AdfruitGFX font, with some precalculated dimension data
|
||||
// This is AppletFont type, which is a wrapper for AdafruitGFX font, with some precalculated dimension data
|
||||
InkHUD::AppletFont InkHUD::Applet::getFont()
|
||||
{
|
||||
return currentFont;
|
||||
}
|
||||
|
||||
// Set two general-purpose fonts, which are reused by many applets
|
||||
// Applets are also permitted to use other fonts, if they can justify the flash usage
|
||||
void InkHUD::Applet::setDefaultFonts(AppletFont large, AppletFont small)
|
||||
{
|
||||
Applet::fontSmall = small;
|
||||
Applet::fontLarge = large;
|
||||
}
|
||||
|
||||
// Gets rendered width of a string
|
||||
// Wrapper for getTextBounds
|
||||
uint16_t InkHUD::Applet::getTextWidth(const char *text)
|
||||
|
@ -327,7 +342,7 @@ uint16_t InkHUD::Applet::getTextWidth(const char *text)
|
|||
}
|
||||
|
||||
// Gets rendered width of a string
|
||||
// Wrappe for getTextBounds
|
||||
// Wrapper for getTextBounds
|
||||
uint16_t InkHUD::Applet::getTextWidth(std::string text)
|
||||
{
|
||||
getFont().applySubstitutions(&text);
|
||||
|
@ -338,7 +353,7 @@ uint16_t InkHUD::Applet::getTextWidth(std::string text)
|
|||
// Evaluate SNR and RSSI to qualify signal strength at one of four discrete levels
|
||||
// Roughly comparable to values used by the iOS app;
|
||||
// I didn't actually go look up the code, just fit to a sample graphic I have of the iOS signal indicator
|
||||
InkHUD::SignalStrength InkHUD::Applet::getSignalStrength(float snr, float rssi)
|
||||
InkHUD::Applet::SignalStrength InkHUD::Applet::getSignalStrength(float snr, float rssi)
|
||||
{
|
||||
uint8_t score = 0;
|
||||
|
||||
|
@ -376,12 +391,14 @@ std::string InkHUD::Applet::hexifyNodeNum(NodeNum num)
|
|||
return std::string(nodeIdHex);
|
||||
}
|
||||
|
||||
// Print text, with word wrapping
|
||||
// Avoids splitting words in half, instead moving the entire word to a new line wherever possible
|
||||
void InkHUD::Applet::printWrapped(int16_t left, int16_t top, uint16_t width, std::string text)
|
||||
{
|
||||
// Custom font glyphs
|
||||
// - set with AppletFont::addSubstitution
|
||||
// - find certain UTF8 chars
|
||||
// - replace with glpyh from custom font (or suitable ASCII addSubstitution?)
|
||||
// - replace with glyph from custom font (or suitable ASCII addSubstitution?)
|
||||
getFont().applySubstitutions(&text);
|
||||
|
||||
// Place the AdafruitGFX cursor to suit our "top" coord
|
||||
|
@ -528,7 +545,7 @@ std::string InkHUD::Applet::getTimeString(uint32_t epochSeconds)
|
|||
#ifdef BUILD_EPOCH
|
||||
constexpr uint32_t validAfterEpoch = BUILD_EPOCH - (SEC_PER_DAY * 30 * 6); // 6 Months prior to build
|
||||
#else
|
||||
constexpr uint32_t validAfterEpoch = 1727740800 - (SEC_PER_DAY * 30 * 6); // 6 Months prior to October 1, 2024 12:00:00 AM GMT
|
||||
constexpr uint32_t validAfterEpoch = 1738368000 - (SEC_PER_DAY * 30 * 6); // 6 Months prior to Feb 1, 2025 12:00:00 AM GMT
|
||||
#endif
|
||||
|
||||
uint32_t epochNow = getValidTime(RTCQuality::RTCQualityDevice, true);
|
||||
|
@ -538,23 +555,17 @@ std::string InkHUD::Applet::getTimeString(uint32_t epochSeconds)
|
|||
|
||||
// Times are invalid: rtc is much older than when code was built
|
||||
// Don't give any human readable string
|
||||
if (epochNow <= validAfterEpoch) {
|
||||
LOG_DEBUG("RTC prior to buildtime");
|
||||
if (epochNow <= validAfterEpoch)
|
||||
return "";
|
||||
}
|
||||
|
||||
// Times are invalid: argument time is significantly ahead of RTC
|
||||
// Don't give any human readable string
|
||||
if (daysAgo < -2) {
|
||||
LOG_DEBUG("RTC in future");
|
||||
if (daysAgo < -2)
|
||||
return "";
|
||||
}
|
||||
|
||||
// Times are probably invalid: more than 6 months ago
|
||||
if (daysAgo > 6 * 30) {
|
||||
LOG_DEBUG("RTC val > 6 months old");
|
||||
if (daysAgo > 6 * 30)
|
||||
return "";
|
||||
}
|
||||
|
||||
if (daysAgo > 1)
|
||||
return to_string(daysAgo) + " days ago";
|
||||
|
@ -602,7 +613,7 @@ uint16_t InkHUD::Applet::getActiveNodeCount()
|
|||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||
|
||||
// Check if heard recently, and not our own node
|
||||
if (sinceLastSeen(node) < settings.recentlyActiveSeconds && node->num != nodeDB->getNodeNum())
|
||||
if (sinceLastSeen(node) < settings->recentlyActiveSeconds && node->num != nodeDB->getNodeNum())
|
||||
count++;
|
||||
}
|
||||
|
||||
|
@ -619,7 +630,7 @@ std::string InkHUD::Applet::localizeDistance(uint32_t meters)
|
|||
// Resulting string
|
||||
std::string localized;
|
||||
|
||||
// Imeperial
|
||||
// Imperial
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||
uint32_t feet = meters * FEET_PER_METER;
|
||||
// Distant (miles, rounded)
|
||||
|
@ -651,6 +662,7 @@ std::string InkHUD::Applet::localizeDistance(uint32_t meters)
|
|||
return localized;
|
||||
}
|
||||
|
||||
// Print text with a "faux bold" effect, by drawing it multiple times, offsetting slightly
|
||||
void InkHUD::Applet::printThick(int16_t xCenter, int16_t yCenter, std::string text, uint8_t thicknessX, uint8_t thicknessY)
|
||||
{
|
||||
// How many times to draw along x axis
|
||||
|
@ -703,17 +715,24 @@ void InkHUD::Applet::printThick(int16_t xCenter, int16_t yCenter, std::string te
|
|||
// Asked before a notification is shown via the NotificationApplet
|
||||
// An applet might want to suppress a notification if the applet itself already displays this info
|
||||
// Example: AllMessageApplet should not approve notifications for messages, if it is in foreground
|
||||
bool InkHUD::Applet::approveNotification(InkHUD::Notification &n)
|
||||
bool InkHUD::Applet::approveNotification(NicheGraphics::InkHUD::Notification &n)
|
||||
{
|
||||
// By default, no objection
|
||||
return true;
|
||||
}
|
||||
|
||||
// Draw the standard header, used by most Applets
|
||||
/*
|
||||
┌───────────────────────────────┐
|
||||
│ Applet::name here │
|
||||
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
└───────────────────────────────┘
|
||||
*/
|
||||
void InkHUD::Applet::drawHeader(std::string text)
|
||||
{
|
||||
setFont(fontSmall);
|
||||
|
||||
// Y position for divider
|
||||
// - between header text and messages
|
||||
constexpr int16_t padDivH = 2;
|
||||
|
@ -771,6 +790,15 @@ uint16_t InkHUD::Applet::getLogoHeight(uint16_t limitWidth, uint16_t limitHeight
|
|||
// Draw a scalable Meshtastic logo
|
||||
// Make sure to provide dimensions which have the correct aspect ratio (~2)
|
||||
// Three paths, drawn thick using quads, with one corner "radiused"
|
||||
/*
|
||||
- ^
|
||||
/- /-\
|
||||
// // \\
|
||||
// // \\
|
||||
// // \\
|
||||
// // \\
|
||||
|
||||
*/
|
||||
void InkHUD::Applet::drawLogo(int16_t centerX, int16_t centerY, uint16_t width, uint16_t height)
|
||||
{
|
||||
struct Point {
|
||||
|
@ -788,6 +816,17 @@ void InkHUD::Applet::drawLogo(int16_t centerX, int16_t centerY, uint16_t width,
|
|||
int16_t logoB = logoT + logoH - 1;
|
||||
|
||||
// Points for paths (a, b, and c)
|
||||
/*
|
||||
+-----------------------------+
|
||||
--| a2 b2/c1 |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
--| a1 b1 c2 |
|
||||
+-----------------------------+
|
||||
| | | |
|
||||
*/
|
||||
|
||||
Point a1 = {map(0, 0, 3, logoL, logoR), logoB};
|
||||
Point a2 = {map(1, 0, 3, logoL, logoR), logoT};
|
||||
Point b1 = {map(1, 0, 3, logoL, logoR), logoB};
|
||||
|
@ -795,17 +834,72 @@ void InkHUD::Applet::drawLogo(int16_t centerX, int16_t centerY, uint16_t width,
|
|||
Point c1 = {map(2, 0, 3, logoL, logoR), logoT};
|
||||
Point c2 = {map(3, 0, 3, logoL, logoR), logoB};
|
||||
|
||||
// Find right-angle to the path
|
||||
// Find angle of the path(s)
|
||||
// Used to thicken the single pixel paths
|
||||
/*
|
||||
+-------------------------------+
|
||||
| a2 |
|
||||
| -| |
|
||||
| -/ | |
|
||||
| -/ | |
|
||||
| -/# | |
|
||||
| -/ # | |
|
||||
| / # | |
|
||||
| a1---------- |
|
||||
+-------------------------------+
|
||||
*/
|
||||
|
||||
Distance deltaA = {abs(a2.x - a1.x), abs(a2.y - a1.y)};
|
||||
float angle = tanh((float)deltaA.y / deltaA.x);
|
||||
|
||||
// Distance {at right angle from the paths), which will give corners for our "quads"
|
||||
// Distance (at right angle to the paths), which will give corners for our "quads"
|
||||
// The distance is unsigned. We will vary the signedness of the x and y components to suit the path and corner
|
||||
/*
|
||||
| a2
|
||||
| .
|
||||
| ..
|
||||
| aq1 ..
|
||||
| # ..
|
||||
| | # ..
|
||||
|fromPath.y | # ..
|
||||
| +----a1
|
||||
|
|
||||
| fromPath.x
|
||||
+--------------------------------
|
||||
*/
|
||||
|
||||
Distance fromPath;
|
||||
fromPath.x = cos(radians(90) - angle) * logoTh * 0.5;
|
||||
fromPath.y = sin(radians(90) - angle) * logoTh * 0.5;
|
||||
|
||||
// Make the paths thick
|
||||
// Corner points for the rectangles (quads):
|
||||
/*
|
||||
|
||||
aq2
|
||||
a2
|
||||
/ aq3
|
||||
/
|
||||
/
|
||||
aq1 /
|
||||
a1
|
||||
aq3
|
||||
*/
|
||||
|
||||
// Filled as two triangles per quad:
|
||||
/*
|
||||
aq2 #
|
||||
# ###
|
||||
## # aq3
|
||||
## ### -
|
||||
## #### -/
|
||||
## ### -/
|
||||
## #### -/
|
||||
aq1 ## -/
|
||||
--- -/
|
||||
\---aq4
|
||||
*/
|
||||
|
||||
// Make the path thick: path a becomes quad a
|
||||
Point aq1{a1.x - fromPath.x, a1.y - fromPath.y};
|
||||
Point aq2{a2.x - fromPath.x, a2.y - fromPath.y};
|
||||
|
@ -822,7 +916,7 @@ void InkHUD::Applet::drawLogo(int16_t centerX, int16_t centerY, uint16_t width,
|
|||
fillTriangle(bq1.x, bq1.y, bq2.x, bq2.y, bq3.x, bq3.y, BLACK);
|
||||
fillTriangle(bq1.x, bq1.y, bq3.x, bq3.y, bq4.x, bq4.y, BLACK);
|
||||
|
||||
// Make the path hick: path c becomes quad c
|
||||
// Make the path thick: path c becomes quad c
|
||||
Point cq1{c1.x - fromPath.x, c1.y + fromPath.y};
|
||||
Point cq2{c2.x - fromPath.x, c2.y + fromPath.y};
|
||||
Point cq3{c2.x + fromPath.x, c2.y - fromPath.y};
|
||||
|
@ -831,10 +925,21 @@ void InkHUD::Applet::drawLogo(int16_t centerX, int16_t centerY, uint16_t width,
|
|||
fillTriangle(cq1.x, cq1.y, cq3.x, cq3.y, cq4.x, cq4.y, BLACK);
|
||||
|
||||
// Radius the intersection of quad b and quad c
|
||||
/*
|
||||
b2 / c1
|
||||
####
|
||||
## ##
|
||||
/ \
|
||||
/ \/ \
|
||||
/ /\ \
|
||||
/ / \ \
|
||||
|
||||
*/
|
||||
|
||||
// Don't attempt if logo is tiny
|
||||
if (logoTh > 3) {
|
||||
// The radius for the cap *should* be the same as logoTh, but it's not, due to accumulated rounding
|
||||
// We get better results just rederiving it
|
||||
// We get better results just re-deriving it
|
||||
int16_t capRad = sqrt(pow(fromPath.x, 2) + pow(fromPath.y, 2));
|
||||
fillCircle(b2.x, b2.y, capRad, BLACK);
|
||||
}
|
||||
|
|
|
@ -7,103 +7,21 @@
|
|||
|
||||
An applet is one "program" which may show info on the display.
|
||||
|
||||
===================================
|
||||
Preliminary notes, for the curious
|
||||
===================================
|
||||
|
||||
(This info to be streamlined, and moved to a more official documentation)
|
||||
|
||||
User Applets vs System Applets
|
||||
-------------------------------
|
||||
|
||||
There are either "User Applets", or "System Applets".
|
||||
This concept is only for our understanding; as far at the code is concerned, both are just "Applets"
|
||||
|
||||
User applets are the "normal" applets.
|
||||
User applets are applets like "AllMessageApplet", or "MapApplet".
|
||||
User applets may be enabled / disabled by user, via the on-screen menu.
|
||||
Incorporating new UserApplets is easy: just add them during setupNicheGraphics
|
||||
If a UserApplet is not added during setupNicheGraphics, it will not be built.
|
||||
The set of available UserApplets is allowed to vary from device to device.
|
||||
|
||||
|
||||
Examples of system applets include "NotificationApplet" and "MenuApplet".
|
||||
For their own reasons, system applets each require some amount of special handling.
|
||||
|
||||
Drawing
|
||||
--------
|
||||
|
||||
*All* drawing must be performed by an Applet.
|
||||
Applets implement the onRender() method, where all drawing takes place.
|
||||
Applets are told how wide and tall they are, and are expected to draw to suit this size.
|
||||
When an applet draws, it uses co-ordinates in "Applet Space": between 0 and applet width/height.
|
||||
|
||||
Event-driven rendering
|
||||
-----------------------
|
||||
|
||||
Applets don't render unless something on the display needs to change.
|
||||
An applet is expected to determine for itself when it has new info to display.
|
||||
It should interact with the firmware via the MeshModule API, via Observables, etc.
|
||||
Please don't directly add hooks throughout the existing firmware code.
|
||||
|
||||
When an applet decides it would like to update the display, it should call requestUpdate()
|
||||
The WindowManager will shortly call the onRender() method for all affected applets
|
||||
|
||||
An Applet may be unexpectedly asked to render at any point in time.
|
||||
|
||||
Applets should cache their data, but not their pixel output: they should re-render when onRender runs.
|
||||
An Applet's dimensions are not know until onRender is called, so pre-rendering of UI elements is prohibited.
|
||||
|
||||
Tiles
|
||||
-----
|
||||
|
||||
Applets are assigned to "Tiles".
|
||||
Assigning an applet to a tile creates a reciprocal link between the two.
|
||||
When an applet renders, it passes pixels to its tile.
|
||||
The tile translates these to the correct position, to be placed into the fullscreen framebuffer.
|
||||
User applets don't get to choose their own tile; the multiplexing is handled by the WindowManager.
|
||||
System applets might do strange things though.
|
||||
|
||||
Foreground and Background
|
||||
-------------------------
|
||||
|
||||
The user can cycle between applets by short-pressing the user button.
|
||||
Any applets which are currently displayed on the display are "foreground".
|
||||
When the user button is short pressed, and an applet is hidden, it becomes "background".
|
||||
|
||||
Although the WindowManager will not render background applets, they should still collect data,
|
||||
so they are ready to display when they are brought to foreground again.
|
||||
Even if they are in background, Applets should still request updates when an event affects them,
|
||||
as the user may have given them permission to "autoshow"; bringing themselves foreground automatically
|
||||
|
||||
Applets can implement the onForeground and onBackground methods to handle this change in state.
|
||||
They can also check their state by calling isForeground() at any time.
|
||||
|
||||
Active and Inactive
|
||||
-------------------
|
||||
|
||||
The user can select which applets are available, using the onscreen applet selection menu.
|
||||
Applets which are enabled in this menu are "active"; otherwise they are "inactive".
|
||||
|
||||
An inactive applet is expected not collect data; not to consume resources.
|
||||
Applets are activated at boot, or when enabled via the menu.
|
||||
They are deactivated at shutdown, or when disabled via the menu.
|
||||
|
||||
Applets can implement the onActivation and onDeactivation methods to handle this change in state.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "configuration.h"
|
||||
|
||||
#include <GFX.h>
|
||||
#include <GFX.h> // GFXRoot drawing lib
|
||||
|
||||
#include "mesh/MeshTypes.h"
|
||||
|
||||
#include "./AppletFont.h"
|
||||
#include "./Applets/System/Notification/Notification.h"
|
||||
#include "./Applets/System/Notification/Notification.h" // The notification object, not the applet
|
||||
#include "./InkHUD.h"
|
||||
#include "./Persistence.h"
|
||||
#include "./Tile.h"
|
||||
#include "./Types.h"
|
||||
#include "./WindowManager.h"
|
||||
#include "graphics/niche/Drivers/EInk/EInk.h"
|
||||
|
||||
namespace NicheGraphics::InkHUD
|
||||
|
@ -112,37 +30,57 @@ namespace NicheGraphics::InkHUD
|
|||
using NicheGraphics::Drivers::EInk;
|
||||
using std::to_string;
|
||||
|
||||
class Tile;
|
||||
class WindowManager;
|
||||
|
||||
class Applet : public GFX
|
||||
{
|
||||
public:
|
||||
// Which edge Applet::printAt will place on the Y parameter
|
||||
enum VerticalAlignment : uint8_t {
|
||||
TOP,
|
||||
MIDDLE,
|
||||
BOTTOM,
|
||||
};
|
||||
|
||||
// Which edge Applet::printAt will place on the X parameter
|
||||
enum HorizontalAlignment : uint8_t {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
CENTER,
|
||||
};
|
||||
|
||||
// An easy-to-understand interpretation of SNR and RSSI
|
||||
// Calculate with Applet::getSignalStrength
|
||||
enum SignalStrength : int8_t {
|
||||
SIGNAL_UNKNOWN = -1,
|
||||
SIGNAL_NONE,
|
||||
SIGNAL_BAD,
|
||||
SIGNAL_FAIR,
|
||||
SIGNAL_GOOD,
|
||||
};
|
||||
|
||||
Applet();
|
||||
|
||||
void setTile(Tile *t); // Applets draw via a tile (for multiplexing)
|
||||
Tile *getTile();
|
||||
void setTile(Tile *t); // Should only be called via Tile::setApplet
|
||||
Tile *getTile(); // Tile with which this applet is linked
|
||||
|
||||
void render();
|
||||
bool wantsToRender(); // Check whether applet wants to render
|
||||
bool wantsToAutoshow(); // Check whether applets wants to become foreground, to show new data, if permitted
|
||||
// Rendering
|
||||
|
||||
void render(); // Draw the applet
|
||||
bool wantsToRender(); // Check whether applet wants to render
|
||||
bool wantsToAutoshow(); // Check whether applet wants to become foreground
|
||||
Drivers::EInk::UpdateTypes wantsUpdateType(); // Check which display update type the applet would prefer
|
||||
void updateDimensions(); // Get current size from tile
|
||||
void resetDrawingSpace(); // Makes sure every render starts with same parameters
|
||||
|
||||
// Change the applet's state
|
||||
|
||||
void activate();
|
||||
void deactivate();
|
||||
void bringToForeground();
|
||||
void sendToBackground();
|
||||
|
||||
// Info about applet's state
|
||||
// State of the applet
|
||||
|
||||
void activate(); // Begin running
|
||||
void deactivate(); // Stop running
|
||||
void bringToForeground(); // Show
|
||||
void sendToBackground(); // Hide
|
||||
bool isActive();
|
||||
bool isForeground();
|
||||
|
||||
// Allow derived applets to handle changes in state
|
||||
// Event handlers
|
||||
|
||||
virtual void onRender() = 0; // All drawing happens here
|
||||
virtual void onActivate() {}
|
||||
|
@ -150,62 +88,62 @@ class Applet : public GFX
|
|||
virtual void onForeground() {}
|
||||
virtual void onBackground() {}
|
||||
virtual void onShutdown() {}
|
||||
virtual void onButtonShortPress() {} // For use by System Applets only
|
||||
virtual void onButtonLongPress() {} // For use by System Applets only
|
||||
virtual void onLockAvailable() {} // For use by System Applets only
|
||||
virtual void onButtonShortPress() {} // (System Applets only)
|
||||
virtual void onButtonLongPress() {} // (System Applets only)
|
||||
|
||||
virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification
|
||||
|
||||
static void setDefaultFonts(AppletFont large, AppletFont small); // Set the general purpose fonts
|
||||
static uint16_t getHeaderHeight(); // How tall is the "standard" applet header
|
||||
static uint16_t getHeaderHeight(); // How tall the "standard" applet header is
|
||||
|
||||
const char *name = nullptr; // Shown in applet selection menu
|
||||
static AppletFont fontSmall, fontLarge; // The general purpose fonts, used by all applets
|
||||
|
||||
const char *name = nullptr; // Shown in applet selection menu. Also used as an identifier by InkHUD::getSystemApplet
|
||||
|
||||
protected:
|
||||
// Place a single pixel. All drawing methods output through here
|
||||
void drawPixel(int16_t x, int16_t y, uint16_t color) override;
|
||||
void drawPixel(int16_t x, int16_t y, uint16_t color) override; // Place a single pixel. All drawing output passes through here
|
||||
|
||||
// Tell WindowManager to update display
|
||||
void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED);
|
||||
|
||||
// Ask for applet to be moved to foreground
|
||||
void requestAutoshow();
|
||||
void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED); // Ask WindowManager to schedule a display update
|
||||
void requestAutoshow(); // Ask for applet to be moved to foreground
|
||||
|
||||
uint16_t X(float f); // Map applet width, mapped from 0 to 1.0
|
||||
uint16_t Y(float f); // Map applet height, mapped from 0 to 1.0
|
||||
void setCrop(int16_t left, int16_t top, uint16_t width, uint16_t height); // Ignore pixels drawn outside a certain region
|
||||
void resetCrop(); // Removes setCrop()
|
||||
|
||||
// Text
|
||||
|
||||
void setFont(AppletFont f);
|
||||
AppletFont getFont();
|
||||
|
||||
uint16_t getTextWidth(std::string text);
|
||||
uint16_t getTextWidth(const char *text);
|
||||
|
||||
uint32_t getWrappedTextHeight(int16_t left, uint16_t width, std::string text); // Result of printWrapped
|
||||
void printAt(int16_t x, int16_t y, const char *text, HorizontalAlignment ha = LEFT, VerticalAlignment va = TOP);
|
||||
void printAt(int16_t x, int16_t y, std::string text, HorizontalAlignment ha = LEFT, VerticalAlignment va = TOP);
|
||||
void printThick(int16_t xCenter, int16_t yCenter, std::string text, uint8_t thicknessX, uint8_t thicknessY);
|
||||
|
||||
// Print text, with per-word line wrapping
|
||||
void printWrapped(int16_t left, int16_t top, uint16_t width, std::string text);
|
||||
uint32_t getWrappedTextHeight(int16_t left, uint16_t width, std::string text);
|
||||
void printThick(int16_t xCenter, int16_t yCenter, std::string text, uint8_t thicknessX, uint8_t thicknessY); // Faux bold
|
||||
void printWrapped(int16_t left, int16_t top, uint16_t width, std::string text); // Per-word line wrapping
|
||||
|
||||
void hatchRegion(int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t spacing, Color color); // Fill with sparse lines
|
||||
void drawHeader(std::string text); // Draw the standard applet header
|
||||
|
||||
// Meshtastic Logo
|
||||
|
||||
static constexpr float LOGO_ASPECT_RATIO = 1.9; // Width:Height for drawing the Meshtastic logo
|
||||
uint16_t getLogoWidth(uint16_t limitWidth, uint16_t limitHeight); // Size Meshtastic logo to fit within region
|
||||
uint16_t getLogoHeight(uint16_t limitWidth, uint16_t limitHeight); // Size Meshtastic logo to fit within region
|
||||
void drawLogo(int16_t centerX, int16_t centerY, uint16_t width, uint16_t height); // Draw the meshtastic logo
|
||||
|
||||
std::string hexifyNodeNum(NodeNum num);
|
||||
std::string hexifyNodeNum(NodeNum num); // Style as !0123abdc
|
||||
SignalStrength getSignalStrength(float snr, float rssi); // Interpret SNR and RSSI, as an easy to understand value
|
||||
std::string getTimeString(uint32_t epochSeconds); // Human readable
|
||||
std::string getTimeString(); // Current time, human readable
|
||||
uint16_t getActiveNodeCount(); // Duration determined by user, in onscreen menu
|
||||
std::string localizeDistance(uint32_t meters); // Human readable distance, imperial or metric
|
||||
|
||||
static AppletFont fontSmall, fontLarge; // General purpose fonts, used cross-applet
|
||||
// Convenient references
|
||||
|
||||
InkHUD *inkhud = nullptr;
|
||||
Persistence::Settings *settings = nullptr;
|
||||
Persistence::LatestMessage *latestMessage = nullptr;
|
||||
|
||||
private:
|
||||
Tile *assignedTile = nullptr; // Rendered pixels are fed into a Tile object, which translates them, then passes to WM
|
||||
|
@ -223,10 +161,10 @@ class Applet : public GFX
|
|||
AppletFont currentFont; // As passed to setFont
|
||||
|
||||
// As set by setCrop
|
||||
int16_t cropLeft;
|
||||
int16_t cropTop;
|
||||
uint16_t cropWidth;
|
||||
uint16_t cropHeight;
|
||||
int16_t cropLeft = 0;
|
||||
int16_t cropTop = 0;
|
||||
uint16_t cropWidth = 0;
|
||||
uint16_t cropHeight = 0;
|
||||
};
|
||||
|
||||
}; // namespace NicheGraphics::InkHUD
|
||||
|
|
|
@ -12,7 +12,7 @@ InkHUD::AppletFont::AppletFont()
|
|||
InkHUD::AppletFont::AppletFont(const GFXfont &adafruitGFXFont) : gfxFont(&adafruitGFXFont)
|
||||
{
|
||||
// AdafruitGFX fonts are drawn relative to a "cursor line";
|
||||
// they print as if the glyphs resting on the line of piece of ruled paper.
|
||||
// they print as if the glyphs are resting on the line of piece of ruled paper.
|
||||
// The glyphs also each have a different height.
|
||||
|
||||
// To simplify drawing, we will scan the entire font now, and determine an appropriate height for a line of text
|
||||
|
@ -42,6 +42,19 @@ InkHUD::AppletFont::AppletFont(const GFXfont &adafruitGFXFont) : gfxFont(&adafru
|
|||
spaceCharWidth = gfxFont->glyph[(uint8_t)' ' - gfxFont->first].xAdvance;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
▲ ##### # ▲
|
||||
│ # # │
|
||||
lineHeight │ ### # │
|
||||
│ # # # # │ heightAboveCursor
|
||||
│ # # # # │
|
||||
│ # # #### │
|
||||
│ -----------------#----
|
||||
│ # │ heightBelowCursor
|
||||
▼ ### ▼
|
||||
*/
|
||||
|
||||
uint8_t InkHUD::AppletFont::lineHeight()
|
||||
{
|
||||
return this->height;
|
||||
|
@ -78,7 +91,7 @@ void InkHUD::AppletFont::addSubstitution(const char *from, const char *to)
|
|||
substitutions.push_back({.from = from, .to = to});
|
||||
}
|
||||
|
||||
// Run all registered subtitutions on a string
|
||||
// Run all registered substitutions on a string
|
||||
// Used to swap out UTF8 special chars
|
||||
void InkHUD::AppletFont::applySubstitutions(std::string *text)
|
||||
{
|
||||
|
@ -87,7 +100,7 @@ void InkHUD::AppletFont::applySubstitutions(std::string *text)
|
|||
|
||||
// Find and replace
|
||||
// - search for Substitution::from
|
||||
// - replace with Subsitution::to
|
||||
// - replace with Substitution::to
|
||||
size_t i = text->find(s.from);
|
||||
while (i != std::string::npos) {
|
||||
text->replace(i, strlen(s.from), s.to);
|
||||
|
@ -97,7 +110,7 @@ void InkHUD::AppletFont::applySubstitutions(std::string *text)
|
|||
}
|
||||
|
||||
// Apply a set of substitutions which remap UTF8 for a Windows-1251 font
|
||||
// Windows-1251 is an 8-bit character encoding, designed to cover languages that use the Cyrillic script
|
||||
// Windows-1251 is an 8-bit character encoding, suitable for several languages which use the Cyrillic script
|
||||
void InkHUD::AppletFont::addSubstitutionsWin1251()
|
||||
{
|
||||
addSubstitution("Ђ", "\x80");
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
#include "configuration.h"
|
||||
|
||||
#include <GFX.h>
|
||||
#include <GFX.h> // GFXRoot drawing lib
|
||||
|
||||
namespace NicheGraphics::InkHUD
|
||||
{
|
||||
|
@ -25,11 +25,12 @@ class AppletFont
|
|||
{
|
||||
public:
|
||||
AppletFont();
|
||||
AppletFont(const GFXfont &adafruitGFXFont);
|
||||
explicit AppletFont(const GFXfont &adafruitGFXFont);
|
||||
|
||||
uint8_t lineHeight();
|
||||
uint8_t heightAboveCursor();
|
||||
uint8_t heightBelowCursor();
|
||||
uint8_t widthBetweenWords();
|
||||
uint8_t widthBetweenWords(); // Width of the space character
|
||||
|
||||
void applySubstitutions(std::string *text); // Run all char-substitution operations, prior to printing
|
||||
void addSubstitution(const char *from, const char *to); // Register a find-replace action, for remapping UTF8 chars
|
||||
|
@ -50,8 +51,7 @@ class AppletFont
|
|||
const char *to;
|
||||
};
|
||||
|
||||
// List of all character substitutions to run, prior to printing a string
|
||||
std::vector<Substitution> substitutions;
|
||||
std::vector<Substitution> substitutions; // List of all character substitutions to run, prior to printing a string
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::InkHUD
|
||||
|
|
|
@ -6,8 +6,6 @@ using namespace NicheGraphics;
|
|||
|
||||
void InkHUD::MapApplet::onRender()
|
||||
{
|
||||
setFont(fontSmall);
|
||||
|
||||
// Abort if no markers to render
|
||||
if (!enoughMarkers()) {
|
||||
printAt(X(0.5), Y(0.5) - (getFont().lineHeight() / 2), "Node positions", CENTER, MIDDLE);
|
||||
|
@ -27,6 +25,7 @@ void InkHUD::MapApplet::onRender()
|
|||
// Set the region shown on the map
|
||||
// - default: fit all nodes, plus padding
|
||||
// - maybe overriden by derived applet
|
||||
// - getMapSize *sets* passed parameters (C-style)
|
||||
getMapSize(&widthMeters, &heightMeters);
|
||||
|
||||
// Set the metersToPx conversion value
|
||||
|
@ -71,7 +70,7 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
|||
// - uses tan to find angles for lat / long degrees
|
||||
// - longitude: triangle formed by x and y (on plane of the equator)
|
||||
// - latitude: triangle formed by z (north south),
|
||||
// and the line along plane of equator which stetches from earth's axis to where point xyz intersects planet's surface
|
||||
// and the line along plane of equator which stretches from earth's axis to where point xyz intersects planet's surface
|
||||
|
||||
// Working totals, averaged after nodeDB processed
|
||||
uint32_t positionCount = 0;
|
||||
|
@ -134,7 +133,7 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
|||
|
||||
*lng = atan2(yAvg, xAvg) * RAD_TO_DEG;
|
||||
|
||||
// Latitude from cartesian cooods
|
||||
// Latitude from cartesian coords
|
||||
// (Angle from 3D coords describing a point on the globe's surface)
|
||||
// As latitude increases, distance from the Earth's north-south axis out to our surface point decreases.
|
||||
// Means we need to first find the hypotenuse which becomes base of our triangle in the second step
|
||||
|
@ -191,8 +190,8 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
|||
|
||||
// Longitude is trickier
|
||||
float lng = node->position.longitude_i * 1e-7;
|
||||
float degEastward = fmod(((lng - lngCenter) + 360), 360); // Degrees travelled east from lngCenter to reach node
|
||||
float degWestward = abs(fmod(((lng - lngCenter) - 360), 360)); // Degrees travelled west from lngCenter to reach node
|
||||
float degEastward = fmod(((lng - lngCenter) + 360), 360); // Degrees traveled east from lngCenter to reach node
|
||||
float degWestward = abs(fmod(((lng - lngCenter) - 360), 360)); // Degrees traveled west from lngCenter to reach node
|
||||
if (degEastward < degWestward)
|
||||
easternmost = max(easternmost, lngCenter + degEastward);
|
||||
else
|
||||
|
@ -258,7 +257,7 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
|||
// Find x and y position based on node's position in nodeDB
|
||||
assert(nodeDB->hasValidPosition(node));
|
||||
Marker m = calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
|
||||
node->position.longitude_i * 1e-7, // Long, convered from Meshtastic's internal int32 style
|
||||
node->position.longitude_i * 1e-7, // Long, converted from Meshtastic's internal int32 style
|
||||
node->has_hops_away, // Is the hopsAway number valid
|
||||
node->hops_away // Hops away
|
||||
);
|
||||
|
@ -288,7 +287,7 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
|||
bool unknownHops = !node->has_hops_away && !isOurNode;
|
||||
|
||||
// We will draw a left or right hand variant, to place text towards screen center
|
||||
// Hopfully avoid text spilling off screen
|
||||
// Hopefully avoid text spilling off screen
|
||||
// Most values are the same, regardless of left-right handedness
|
||||
|
||||
// Pick emblem style
|
||||
|
@ -388,7 +387,7 @@ void InkHUD::MapApplet::calculateAllMarkers()
|
|||
// Calculate marker and store it
|
||||
markers.push_back(
|
||||
calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
|
||||
node->position.longitude_i * 1e-7, // Long, convered from Meshtastic's internal int32 style
|
||||
node->position.longitude_i * 1e-7, // Long, converted from Meshtastic's internal int32 style
|
||||
node->has_hops_away, // Is the hopsAway number valid
|
||||
node->hops_away // Hops away
|
||||
));
|
||||
|
|
|
@ -38,13 +38,12 @@ class MapApplet : public Applet
|
|||
void drawLabeledMarker(meshtastic_NodeInfoLite *node); // Highlight a specific marker
|
||||
|
||||
private:
|
||||
// Position of markers to be drawn, relative to map center
|
||||
// HopsAway info used to determine marker size
|
||||
// Position and size of a marker to be drawn
|
||||
struct Marker {
|
||||
float eastMeters = 0; // Meters east of mapCenter. Negative if west.
|
||||
float northMeters = 0; // Meters north of mapCenter. Negative if south.
|
||||
float eastMeters = 0; // Meters east of map center. Negative if west.
|
||||
float northMeters = 0; // Meters north of map center. Negative if south.
|
||||
bool hasHopsAway = false;
|
||||
uint8_t hopsAway = 0;
|
||||
uint8_t hopsAway = 0; // Determines marker size
|
||||
};
|
||||
|
||||
Marker calculateMarker(float lat, float lng, bool hasHopsAway, uint8_t hopsAway);
|
||||
|
|
|
@ -12,7 +12,7 @@ using namespace NicheGraphics;
|
|||
InkHUD::NodeListApplet::NodeListApplet(const char *name) : MeshModule(name)
|
||||
{
|
||||
// We only need to be promiscuous in order to hear NodeInfo, apparently. See NodeInfoModule
|
||||
// For all other packets, we manually reimplement isPromiscuous=false in wantPacket
|
||||
// For all other packets, we manually act as if isPromiscuous=false, in wantPacket
|
||||
MeshModule::isPromiscuous = true;
|
||||
}
|
||||
|
||||
|
@ -25,17 +25,17 @@ bool InkHUD::NodeListApplet::wantPacket(const meshtastic_MeshPacket *p)
|
|||
&& (isToUs(p) || isBroadcast(p->to) || // Either: intended for us,
|
||||
p->decoded.portnum == meshtastic_PortNum_NODEINFO_APP); // or nodeinfo
|
||||
|
||||
// Note: special handling of NodeInfo is to match NodeInfoModule
|
||||
// To match the behavior seen in the client apps:
|
||||
// - NodeInfoModule's ProtoBufModule base is "promiscuous"
|
||||
// - All other activity is *not* promiscuous
|
||||
// To achieve this, our MeshModule *is* promiscious, and we're manually reimplementing non-promiscuous behavior here,
|
||||
|
||||
// To achieve this, our MeshModule *is* promiscuous, and we're manually reimplementing non-promiscuous behavior here,
|
||||
// to match the code in MeshModule::callModules
|
||||
}
|
||||
|
||||
// MeshModule packets arrive here
|
||||
// Extract the info and pass it to the derived applet
|
||||
// Derived applet will store the CardInfo and perform any required sorting of the CardInfo collection
|
||||
// Derived applet will store the CardInfo, and perform any required sorting of the CardInfo collection
|
||||
// Derived applet might also need to keep other tallies (active nodes count?)
|
||||
ProcessMessage InkHUD::NodeListApplet::handleReceived(const meshtastic_MeshPacket &mp)
|
||||
{
|
||||
|
@ -76,8 +76,8 @@ ProcessMessage InkHUD::NodeListApplet::handleReceived(const meshtastic_MeshPacke
|
|||
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
|
||||
}
|
||||
|
||||
// Maximum number of cards we may ever need to render, in our tallest layout config
|
||||
// May be slightly in excess of the true value: header not accounted for
|
||||
// Calculate maximum number of cards we may ever need to render, in our tallest layout config
|
||||
// Number might be slightly in excess of the true value: applet header text not accounted for
|
||||
uint8_t InkHUD::NodeListApplet::maxCards()
|
||||
{
|
||||
// Cache result. Shouldn't change during execution
|
||||
|
@ -87,7 +87,7 @@ uint8_t InkHUD::NodeListApplet::maxCards()
|
|||
const uint16_t height = Tile::maxDisplayDimension();
|
||||
|
||||
// Use a loop instead of arithmetic, because it's easier for my brain to follow
|
||||
// Add cards one by one, until the latest card (without margin) extends below screen
|
||||
// Add cards one by one, until the latest card extends below screen
|
||||
|
||||
uint16_t y = cardH; // First card: no margin above
|
||||
cards = 1;
|
||||
|
@ -102,7 +102,7 @@ uint8_t InkHUD::NodeListApplet::maxCards()
|
|||
return cards;
|
||||
}
|
||||
|
||||
// Draw using info which derived applet placed into NodeListApplet::cards for us
|
||||
// Draw, using info which derived applet placed into NodeListApplet::cards for us
|
||||
void InkHUD::NodeListApplet::onRender()
|
||||
{
|
||||
|
||||
|
@ -120,9 +120,6 @@ void InkHUD::NodeListApplet::onRender()
|
|||
// Draw the main node list
|
||||
// ========================
|
||||
|
||||
// const uint8_t cardMarginH = fontSmall.lineHeight() / 2; // Gap between cards
|
||||
// const uint16_t cardH = fontLarge.lineHeight() + fontSmall.lineHeight() + cardMarginH;
|
||||
|
||||
// Imaginary vertical line dividing left-side and right-side info
|
||||
// Long-name will crop here
|
||||
const uint16_t dividerX = (width() - 1) - getTextWidth("X Hops");
|
||||
|
@ -215,9 +212,8 @@ void InkHUD::NodeListApplet::onRender()
|
|||
|
||||
// Once we've run out of screen, stop drawing cards
|
||||
// Depending on tiles / rotation, this may be before we hit maxCards
|
||||
if (cardTopY > height()) {
|
||||
if (cardTopY > height())
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -246,20 +242,20 @@ void InkHUD::NodeListApplet::drawSignalIndicator(int16_t x, int16_t y, uint16_t
|
|||
|
||||
constexpr float paddingW = 0.1; // Either side
|
||||
constexpr float paddingH = 0.1; // Above and below
|
||||
constexpr float gutterX = 0.1; // Between bars
|
||||
constexpr float gutterW = 0.1; // Between bars
|
||||
|
||||
constexpr float barHRel[] = {0.3, 0.5, 0.7, 1.0}; // Heights of the signal bars, relative to the talleest
|
||||
constexpr float barHRel[] = {0.3, 0.5, 0.7, 1.0}; // Heights of the signal bars, relative to the tallest
|
||||
constexpr uint8_t barCount = 4; // How many bars we draw. Reference only: changing value won't change the count.
|
||||
|
||||
// Dynamically calculate the width of the bars, and height of the rightmost, relative to other dimensions
|
||||
float barW = (1.0 - (paddingW + ((barCount - 1) * gutterX) + paddingW)) / barCount;
|
||||
float barW = (1.0 - (paddingW + ((barCount - 1) * gutterW) + paddingW)) / barCount;
|
||||
float barHMax = 1.0 - (paddingH + paddingH);
|
||||
|
||||
// Draw signal bar rectangles, then placeholder lines once strength reached
|
||||
for (uint8_t i = 0; i < barCount; i++) {
|
||||
// Co-ords for this specific bar
|
||||
// Coords for this specific bar
|
||||
float barH = barHMax * barHRel[i];
|
||||
float barX = paddingW + (i * (gutterX + barW));
|
||||
float barX = paddingW + (i * (gutterW + barW));
|
||||
float barY = paddingH + (barHMax - barH);
|
||||
|
||||
// Rasterize to px coords at the last moment
|
||||
|
|
|
@ -23,13 +23,16 @@ Used by the "Recents" and "Heard" applets. Possibly more in future?
|
|||
|
||||
#include "graphics/niche/InkHUD/Applet.h"
|
||||
|
||||
#include "main.h"
|
||||
|
||||
namespace NicheGraphics::InkHUD
|
||||
{
|
||||
|
||||
class NodeListApplet : public Applet, public MeshModule
|
||||
{
|
||||
protected:
|
||||
// Info used to draw one card to the node list
|
||||
// Info needed to draw a node card to the list
|
||||
// - generated each time we hear a node
|
||||
struct CardInfo {
|
||||
static constexpr uint8_t HOPS_UNKNOWN = -1;
|
||||
static constexpr uint32_t DISTANCE_UNKNOWN = -1;
|
||||
|
@ -37,31 +40,31 @@ class NodeListApplet : public Applet, public MeshModule
|
|||
NodeNum nodeNum = 0;
|
||||
SignalStrength signal = SignalStrength::SIGNAL_UNKNOWN;
|
||||
uint32_t distanceMeters = DISTANCE_UNKNOWN;
|
||||
uint8_t hopsAway = HOPS_UNKNOWN; // Unknown
|
||||
uint8_t hopsAway = HOPS_UNKNOWN;
|
||||
};
|
||||
|
||||
public:
|
||||
NodeListApplet(const char *name);
|
||||
|
||||
void onRender() override;
|
||||
|
||||
// MeshModule overrides
|
||||
virtual bool wantPacket(const meshtastic_MeshPacket *p) override;
|
||||
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
||||
bool wantPacket(const meshtastic_MeshPacket *p) override;
|
||||
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
||||
|
||||
protected:
|
||||
virtual void handleParsed(CardInfo c) = 0; // Pass extracted info from a new packet to derived class, for sorting and storage
|
||||
virtual std::string getHeaderText() = 0; // Title for the applet's header. Todo: get this info another way?
|
||||
virtual void handleParsed(CardInfo c) = 0; // Tell derived applet that we heard a node
|
||||
virtual std::string getHeaderText() = 0; // Ask derived class what the applet's title should be
|
||||
|
||||
uint8_t maxCards(); // Calculate the maximum number of cards an applet could ever display
|
||||
uint8_t maxCards(); // Max number of cards which could ever fit on screen
|
||||
|
||||
std::deque<CardInfo> cards; // Derived applet places cards here, for this base applet to render
|
||||
std::deque<CardInfo> cards; // Cards to be rendered. Derived applet fills this.
|
||||
|
||||
private:
|
||||
// UI element: a "mobile phone" style signal indicator
|
||||
void drawSignalIndicator(int16_t x, int16_t y, uint16_t w, uint16_t h, SignalStrength signal);
|
||||
void drawSignalIndicator(int16_t x, int16_t y, uint16_t w, uint16_t h,
|
||||
SignalStrength signal); // Draw a "mobile phone" style signal indicator
|
||||
|
||||
// Dimensions for drawing
|
||||
// Used for render, and also for maxCards calc
|
||||
// Card Dimensions
|
||||
// - for rendering and for maxCards calc
|
||||
const uint8_t cardMarginH = fontSmall.lineHeight() / 2; // Gap between cards
|
||||
const uint16_t cardH = fontLarge.lineHeight() + fontSmall.lineHeight() + cardMarginH; // Height of card
|
||||
};
|
||||
|
|
|
@ -36,8 +36,6 @@ ProcessMessage InkHUD::NewMsgExampleApplet::handleReceived(const meshtastic_Mesh
|
|||
// We should always be ready to draw
|
||||
void InkHUD::NewMsgExampleApplet::onRender()
|
||||
{
|
||||
setFont(fontSmall);
|
||||
|
||||
printAt(0, 0, "Example: NewMsg", LEFT, TOP); // Print top-left corner of text at (0,0)
|
||||
|
||||
int16_t centerX = X(0.5); // Same as width() / 2
|
||||
|
|
|
@ -53,7 +53,7 @@ class NewMsgExampleApplet : public Applet, public SinglePortModule
|
|||
|
||||
// Store info from handleReceived
|
||||
bool haveMessage = false;
|
||||
NodeNum fromWho;
|
||||
NodeNum fromWho = 0;
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::InkHUD
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
|
||||
using namespace NicheGraphics;
|
||||
|
||||
void InkHUD::BatteryIconApplet::onActivate()
|
||||
InkHUD::BatteryIconApplet::BatteryIconApplet()
|
||||
{
|
||||
// Show at boot, if user has previously enabled the feature
|
||||
if (settings.optionalFeatures.batteryIcon)
|
||||
if (settings->optionalFeatures.batteryIcon)
|
||||
bringToForeground();
|
||||
|
||||
// Register to our have BatteryIconApplet::onPowerStatusUpdate method called when new power info is available
|
||||
|
@ -15,12 +15,6 @@ void InkHUD::BatteryIconApplet::onActivate()
|
|||
powerStatusObserver.observe(&powerStatus->onNewStatus);
|
||||
}
|
||||
|
||||
void InkHUD::BatteryIconApplet::onDeactivate()
|
||||
{
|
||||
// Stop having onPowerStatusUpdate called
|
||||
powerStatusObserver.unobserve(&powerStatus->onNewStatus);
|
||||
}
|
||||
|
||||
// We handle power status' even when the feature is disabled,
|
||||
// so that we have up to date data ready if the feature is enabled later.
|
||||
// Otherwise could be 30s before new status update, with weird battery value displayed
|
||||
|
@ -41,7 +35,7 @@ int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *sta
|
|||
// If rounded value has changed, trigger a display update
|
||||
// It's okay to requestUpdate before we store the new value, as the update won't run until next loop()
|
||||
// Don't trigger an update if the feature is disabled
|
||||
if (this->socRounded != newSocRounded && settings.optionalFeatures.batteryIcon)
|
||||
if (this->socRounded != newSocRounded && settings->optionalFeatures.batteryIcon)
|
||||
requestUpdate();
|
||||
|
||||
// Store the new value
|
||||
|
|
|
@ -11,24 +11,22 @@ It should be optional, enabled by the on-screen menu
|
|||
|
||||
#include "configuration.h"
|
||||
|
||||
#include "graphics/niche/InkHUD/Applet.h"
|
||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
||||
|
||||
#include "PowerStatus.h"
|
||||
|
||||
namespace NicheGraphics::InkHUD
|
||||
{
|
||||
|
||||
class BatteryIconApplet : public Applet
|
||||
class BatteryIconApplet : public SystemApplet
|
||||
{
|
||||
public:
|
||||
BatteryIconApplet();
|
||||
|
||||
void onRender() override;
|
||||
|
||||
void onActivate() override;
|
||||
void onDeactivate() override;
|
||||
|
||||
int onPowerStatusUpdate(const meshtastic::Status *status); // Called when new info about battery is available
|
||||
|
||||
protected:
|
||||
private:
|
||||
// Get informed when new information about the battery is available (via onPowerStatusUpdate method)
|
||||
CallbackObserver<BatteryIconApplet, const meshtastic::Status *> powerStatusObserver =
|
||||
CallbackObserver<BatteryIconApplet, const meshtastic::Status *>(this, &BatteryIconApplet::onPowerStatusUpdate);
|
||||
|
|
|
@ -2,15 +2,22 @@
|
|||
|
||||
#include "./LogoApplet.h"
|
||||
|
||||
#include "mesh/NodeDB.h"
|
||||
|
||||
using namespace NicheGraphics;
|
||||
|
||||
InkHUD::LogoApplet::LogoApplet() : concurrency::OSThread("LogoApplet")
|
||||
{
|
||||
// Don't autostart the runOnce() timer
|
||||
OSThread::disable();
|
||||
OSThread::setIntervalFromNow(8 * 1000UL);
|
||||
OSThread::enabled = true;
|
||||
|
||||
// Grab the WindowManager singleton, for convenience
|
||||
windowManager = WindowManager::getInstance();
|
||||
textLeft = "";
|
||||
textRight = "";
|
||||
textTitle = xstr(APP_VERSION_SHORT);
|
||||
fontTitle = fontSmall;
|
||||
|
||||
bringToForeground();
|
||||
// This is then drawn with a FULL refresh by Renderer::begin
|
||||
}
|
||||
|
||||
void InkHUD::LogoApplet::onRender()
|
||||
|
@ -48,53 +55,24 @@ void InkHUD::LogoApplet::onRender()
|
|||
|
||||
void InkHUD::LogoApplet::onForeground()
|
||||
{
|
||||
// If another applet has locked the display, ask it to exit
|
||||
Applet *other = windowManager->whoLocked();
|
||||
if (other != nullptr)
|
||||
other->sendToBackground();
|
||||
|
||||
windowManager->claimFullscreen(this); // Take ownership of fullscreen tile
|
||||
windowManager->lock(this); // Prevent other applets from requesting updates
|
||||
SystemApplet::lockRendering = true;
|
||||
SystemApplet::lockRequests = true;
|
||||
SystemApplet::handleInput = true; // We don't actually use this input. Just blocking other applets from using it.
|
||||
}
|
||||
|
||||
void InkHUD::LogoApplet::onBackground()
|
||||
{
|
||||
OSThread::disable(); // Disable auto-dismiss timer, in case applet was dismissed early (sendToBackground from outside class)
|
||||
|
||||
windowManager->releaseFullscreen(); // Relinquish ownership of fullscreen tile
|
||||
windowManager->unlock(this); // Allow normal user applet update requests to resume
|
||||
SystemApplet::lockRendering = false;
|
||||
SystemApplet::lockRequests = false;
|
||||
SystemApplet::handleInput = false;
|
||||
|
||||
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
||||
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
||||
windowManager->forceUpdate(EInk::UpdateTypes::FULL);
|
||||
}
|
||||
|
||||
int32_t InkHUD::LogoApplet::runOnce()
|
||||
{
|
||||
LOG_DEBUG("Sent to background by timer");
|
||||
sendToBackground();
|
||||
return OSThread::disable();
|
||||
}
|
||||
|
||||
// Begin displaying the screen which is shown at startup
|
||||
// Suggest EInk::await after calling this method
|
||||
void InkHUD::LogoApplet::showBootScreen()
|
||||
{
|
||||
OSThread::setIntervalFromNow(8 * 1000UL);
|
||||
OSThread::enabled = true;
|
||||
|
||||
textLeft = "";
|
||||
textRight = "";
|
||||
textTitle = xstr(APP_VERSION_SHORT);
|
||||
fontTitle = fontSmall;
|
||||
|
||||
bringToForeground();
|
||||
requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Already requested, just upgrading to FULL
|
||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||
}
|
||||
|
||||
// Begin displaying the screen which is shown at shutdown
|
||||
// Needs EInk::await after calling this method, to ensure display updates before shutdown
|
||||
void InkHUD::LogoApplet::showShutdownScreen()
|
||||
void InkHUD::LogoApplet::onShutdown()
|
||||
{
|
||||
textLeft = "";
|
||||
textRight = "";
|
||||
|
@ -102,7 +80,13 @@ void InkHUD::LogoApplet::showShutdownScreen()
|
|||
fontTitle = fontLarge;
|
||||
|
||||
bringToForeground();
|
||||
requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Already requested, just upgrading to FULL
|
||||
// This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update
|
||||
}
|
||||
|
||||
int32_t InkHUD::LogoApplet::runOnce()
|
||||
{
|
||||
sendToBackground();
|
||||
return OSThread::disable();
|
||||
}
|
||||
|
||||
#endif
|
|
@ -12,24 +12,19 @@
|
|||
#include "configuration.h"
|
||||
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "graphics/niche/InkHUD/Applet.h"
|
||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
||||
|
||||
namespace NicheGraphics::InkHUD
|
||||
{
|
||||
|
||||
class LogoApplet : public Applet, public concurrency::OSThread
|
||||
class LogoApplet : public SystemApplet, public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
LogoApplet();
|
||||
void onRender() override;
|
||||
void onForeground() override;
|
||||
void onBackground() override;
|
||||
|
||||
// Note: interacting directly with an applet like this is non-standard
|
||||
// Only permitted because this is a "system applet", which has special behavior and interacts directly with WindowManager
|
||||
|
||||
void showBootScreen();
|
||||
void showShutdownScreen();
|
||||
void onShutdown() override;
|
||||
|
||||
protected:
|
||||
int32_t runOnce() override;
|
||||
|
@ -38,8 +33,6 @@ class LogoApplet : public Applet, public concurrency::OSThread
|
|||
std::string textRight;
|
||||
std::string textTitle;
|
||||
AppletFont fontTitle;
|
||||
|
||||
WindowManager *windowManager = nullptr; // For convenience
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::InkHUD
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
|
||||
#include "./MenuApplet.h"
|
||||
|
||||
#include "PowerStatus.h"
|
||||
#include "RTC.h"
|
||||
|
||||
#include "airtime.h"
|
||||
#include "power.h"
|
||||
|
||||
using namespace NicheGraphics;
|
||||
|
||||
static constexpr uint8_t MENU_TIMEOUT_SEC = 60; // How many seconds before menu auto-closes
|
||||
|
@ -17,23 +19,16 @@ InkHUD::MenuApplet::MenuApplet() : concurrency::OSThread("MenuApplet")
|
|||
{
|
||||
// No timer tasks at boot
|
||||
OSThread::disable();
|
||||
}
|
||||
|
||||
void InkHUD::MenuApplet::onActivate()
|
||||
{
|
||||
// Grab pointers to some singleton components which the menu interacts with
|
||||
// We could do this every time we needed them, in place,
|
||||
// but this just makes the code tidier
|
||||
|
||||
this->windowManager = WindowManager::getInstance();
|
||||
|
||||
// Note: don't get instance if we're not actually using the backlight,
|
||||
// or else you will unintentionally instantiate it
|
||||
if (settings.optionalMenuItems.backlight) {
|
||||
if (settings->optionalMenuItems.backlight) {
|
||||
backlight = Drivers::LatchingBacklight::getInstance();
|
||||
}
|
||||
}
|
||||
|
||||
void InkHUD::MenuApplet::onActivate() {}
|
||||
|
||||
void InkHUD::MenuApplet::onForeground()
|
||||
{
|
||||
// We do need this before we render, but we can optimize by just calculating it once now
|
||||
|
@ -45,21 +40,23 @@ void InkHUD::MenuApplet::onForeground()
|
|||
// If device has a backlight which isn't controlled by aux button:
|
||||
// backlight on always when menu opens.
|
||||
// Courtesy to T-Echo users who removed the capacitive touch button
|
||||
if (settings.optionalMenuItems.backlight) {
|
||||
if (settings->optionalMenuItems.backlight) {
|
||||
assert(backlight);
|
||||
if (!backlight->isOn())
|
||||
backlight->peek();
|
||||
}
|
||||
|
||||
// Prevent user applets requested update while menu is open
|
||||
windowManager->lock(this);
|
||||
// Prevent user applets requesting update while menu is open
|
||||
// Handle button input with this applet
|
||||
SystemApplet::lockRequests = true;
|
||||
SystemApplet::handleInput = true;
|
||||
|
||||
// Begin the auto-close timeout
|
||||
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
||||
OSThread::enabled = true;
|
||||
|
||||
// Upgrade the refresh to FAST, for guaranteed responsiveness
|
||||
windowManager->forceUpdate(EInk::UpdateTypes::FAST);
|
||||
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
||||
}
|
||||
|
||||
void InkHUD::MenuApplet::onBackground()
|
||||
|
@ -67,7 +64,7 @@ void InkHUD::MenuApplet::onBackground()
|
|||
// If device has a backlight which isn't controlled by aux button:
|
||||
// Item in options submenu allows keeping backlight on after menu is closed
|
||||
// If this item is deselected we will turn backlight off again, now that menu is closing
|
||||
if (settings.optionalMenuItems.backlight) {
|
||||
if (settings->optionalMenuItems.backlight) {
|
||||
assert(backlight);
|
||||
if (!backlight->isLatched())
|
||||
backlight->off();
|
||||
|
@ -77,7 +74,8 @@ void InkHUD::MenuApplet::onBackground()
|
|||
OSThread::disable();
|
||||
|
||||
// Resume normal rendering and button behavior of user applets
|
||||
windowManager->unlock(this);
|
||||
SystemApplet::lockRequests = false;
|
||||
SystemApplet::handleInput = false;
|
||||
|
||||
// Restore the user applet whose tile we borrowed
|
||||
if (borrowedTileOwner)
|
||||
|
@ -87,8 +85,8 @@ void InkHUD::MenuApplet::onBackground()
|
|||
borrowedTileOwner = nullptr;
|
||||
|
||||
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
||||
// We're only updating here to ugrade from UNSPECIFIED to FAST, to ensure responsiveness when exiting menu
|
||||
windowManager->forceUpdate(EInk::UpdateTypes::FAST);
|
||||
// We're only updating here to upgrade from UNSPECIFIED to FAST, to ensure responsiveness when exiting menu
|
||||
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
||||
}
|
||||
|
||||
// Open the menu
|
||||
|
@ -140,43 +138,35 @@ void InkHUD::MenuApplet::execute(MenuItem item)
|
|||
break;
|
||||
|
||||
case NEXT_TILE:
|
||||
// Note performed manually;
|
||||
// WindowManager::nextTile is raised by aux button press only, and will interact poorly with the menu
|
||||
settings.userTiles.focused = (settings.userTiles.focused + 1) % settings.userTiles.count;
|
||||
windowManager->changeLayout();
|
||||
cursor = 0; // No menu item selected, for quick exit after tile swap
|
||||
cursorShown = false;
|
||||
inkhud->nextTile();
|
||||
break;
|
||||
|
||||
case ROTATE:
|
||||
settings.rotation = (settings.rotation + 1) % 4;
|
||||
windowManager->changeLayout();
|
||||
// requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Would update regardless; just selecting FULL
|
||||
inkhud->rotate();
|
||||
break;
|
||||
|
||||
case LAYOUT:
|
||||
// Todo: smarter incrementing of tile count
|
||||
settings.userTiles.count++;
|
||||
settings->userTiles.count++;
|
||||
|
||||
if (settings.userTiles.count == 3) // Skip 3 tiles: not done yet
|
||||
settings.userTiles.count++;
|
||||
if (settings->userTiles.count == 3) // Skip 3 tiles: not done yet
|
||||
settings->userTiles.count++;
|
||||
|
||||
if (settings.userTiles.count > settings.userTiles.maxCount) // Loop around if tile count now too high
|
||||
settings.userTiles.count = 1;
|
||||
if (settings->userTiles.count > settings->userTiles.maxCount) // Loop around if tile count now too high
|
||||
settings->userTiles.count = 1;
|
||||
|
||||
windowManager->changeLayout();
|
||||
// requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Would update regardless; just selecting FULL
|
||||
inkhud->updateLayout();
|
||||
break;
|
||||
|
||||
case TOGGLE_APPLET:
|
||||
settings.userApplets.active[cursor] = !settings.userApplets.active[cursor];
|
||||
windowManager->changeActivatedApplets();
|
||||
settings->userApplets.active[cursor] = !settings->userApplets.active[cursor];
|
||||
inkhud->updateAppletSelection();
|
||||
// requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Select FULL, seeing how this action doesn't auto exit
|
||||
break;
|
||||
|
||||
case ACTIVATE_APPLETS:
|
||||
// Todo: remove this action? Already handled by TOGGLE_APPLET?
|
||||
windowManager->changeActivatedApplets();
|
||||
inkhud->updateAppletSelection();
|
||||
break;
|
||||
|
||||
case TOGGLE_AUTOSHOW_APPLET:
|
||||
|
@ -185,14 +175,14 @@ void InkHUD::MenuApplet::execute(MenuItem item)
|
|||
break;
|
||||
|
||||
case TOGGLE_NOTIFICATIONS:
|
||||
settings.optionalFeatures.notifications = !settings.optionalFeatures.notifications;
|
||||
settings->optionalFeatures.notifications = !settings->optionalFeatures.notifications;
|
||||
break;
|
||||
|
||||
case SET_RECENTS:
|
||||
// Set value of settings.recentlyActiveSeconds
|
||||
// Uses menu cursor to read RECENTS_OPTIONS_MINUTES array (defined at top of this file)
|
||||
assert(cursor < sizeof(RECENTS_OPTIONS_MINUTES) / sizeof(RECENTS_OPTIONS_MINUTES[0]));
|
||||
settings.recentlyActiveSeconds = RECENTS_OPTIONS_MINUTES[cursor] * 60; // Menu items are in minutes
|
||||
settings->recentlyActiveSeconds = RECENTS_OPTIONS_MINUTES[cursor] * 60; // Menu items are in minutes
|
||||
break;
|
||||
|
||||
case SHUTDOWN:
|
||||
|
@ -202,7 +192,7 @@ void InkHUD::MenuApplet::execute(MenuItem item)
|
|||
break;
|
||||
|
||||
case TOGGLE_BATTERY_ICON:
|
||||
windowManager->toggleBatteryIcon();
|
||||
inkhud->toggleBatteryIcon();
|
||||
break;
|
||||
|
||||
case TOGGLE_BACKLIGHT:
|
||||
|
@ -233,13 +223,13 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||
switch (page) {
|
||||
case ROOT:
|
||||
// Optional: next applet
|
||||
if (settings.optionalMenuItems.nextTile && settings.userTiles.count > 1)
|
||||
if (settings->optionalMenuItems.nextTile && settings->userTiles.count > 1)
|
||||
items.push_back(MenuItem("Next Tile", MenuAction::NEXT_TILE, MenuPage::ROOT)); // Only if multiple applets shown
|
||||
|
||||
// items.push_back(MenuItem("Send", MenuPage::SEND)); // TODO
|
||||
items.push_back(MenuItem("Options", MenuPage::OPTIONS));
|
||||
// items.push_back(MenuItem("Display Off", MenuPage::EXIT)); // TODO
|
||||
items.push_back(MenuItem("Save & Shutdown", MenuAction::SHUTDOWN));
|
||||
items.push_back(MenuItem("Save & Shut Down", MenuAction::SHUTDOWN));
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
|
||||
|
@ -252,7 +242,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||
|
||||
case OPTIONS:
|
||||
// Optional: backlight
|
||||
if (settings.optionalMenuItems.backlight) {
|
||||
if (settings->optionalMenuItems.backlight) {
|
||||
assert(backlight);
|
||||
items.push_back(MenuItem(backlight->isLatched() ? "Backlight Off" : "Keep Backlight On", // Label
|
||||
MenuAction::TOGGLE_BACKLIGHT, // Action
|
||||
|
@ -263,13 +253,13 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||
items.push_back(MenuItem("Applets", MenuPage::APPLETS));
|
||||
items.push_back(MenuItem("Auto-show", MenuPage::AUTOSHOW));
|
||||
items.push_back(MenuItem("Recents Duration", MenuPage::RECENTS));
|
||||
if (settings.userTiles.maxCount > 1)
|
||||
if (settings->userTiles.maxCount > 1)
|
||||
items.push_back(MenuItem("Layout", MenuAction::LAYOUT, MenuPage::OPTIONS));
|
||||
items.push_back(MenuItem("Rotate", MenuAction::ROTATE, MenuPage::OPTIONS));
|
||||
items.push_back(MenuItem("Notifications", MenuAction::TOGGLE_NOTIFICATIONS, MenuPage::OPTIONS,
|
||||
&settings.optionalFeatures.notifications));
|
||||
items.push_back(
|
||||
MenuItem("Battery Icon", MenuAction::TOGGLE_BATTERY_ICON, MenuPage::OPTIONS, &settings.optionalFeatures.batteryIcon));
|
||||
&settings->optionalFeatures.notifications));
|
||||
items.push_back(MenuItem("Battery Icon", MenuAction::TOGGLE_BATTERY_ICON, MenuPage::OPTIONS,
|
||||
&settings->optionalFeatures.batteryIcon));
|
||||
|
||||
// TODO - GPS and Wifi switches
|
||||
/*
|
||||
|
@ -329,9 +319,6 @@ void InkHUD::MenuApplet::onRender()
|
|||
if (items.size() == 0)
|
||||
LOG_ERROR("Empty Menu");
|
||||
|
||||
// Testing only
|
||||
setFont(fontSmall);
|
||||
|
||||
// Dimensions for the slots where we will draw menuItems
|
||||
const float padding = 0.05;
|
||||
const uint16_t itemH = fontSmall.lineHeight() * 2;
|
||||
|
@ -397,7 +384,7 @@ void InkHUD::MenuApplet::onRender()
|
|||
|
||||
// Testing only: circle instead of check box
|
||||
if (item.checkState) {
|
||||
const uint16_t cbWH = fontSmall.lineHeight(); // Checbox: width / height
|
||||
const uint16_t cbWH = fontSmall.lineHeight(); // Checkbox: width / height
|
||||
const int16_t cbL = itemR - X(padding) - cbWH; // Checkbox: left
|
||||
const int16_t cbT = center - (cbWH / 2); // Checkbox : top
|
||||
// Checkbox ticked
|
||||
|
@ -463,9 +450,9 @@ void InkHUD::MenuApplet::populateAppletPage()
|
|||
{
|
||||
assert(items.size() == 0);
|
||||
|
||||
for (uint8_t i = 0; i < windowManager->getAppletCount(); i++) {
|
||||
const char *name = windowManager->getAppletName(i);
|
||||
bool *isActive = &(settings.userApplets.active[i]);
|
||||
for (uint8_t i = 0; i < inkhud->userApplets.size(); i++) {
|
||||
const char *name = inkhud->userApplets.at(i)->name;
|
||||
bool *isActive = &(settings->userApplets.active[i]);
|
||||
items.push_back(MenuItem(name, MenuAction::TOGGLE_APPLET, MenuPage::APPLETS, isActive));
|
||||
}
|
||||
}
|
||||
|
@ -477,11 +464,11 @@ void InkHUD::MenuApplet::populateAutoshowPage()
|
|||
{
|
||||
assert(items.size() == 0);
|
||||
|
||||
for (uint8_t i = 0; i < windowManager->getAppletCount(); i++) {
|
||||
for (uint8_t i = 0; i < inkhud->userApplets.size(); i++) {
|
||||
// Only add a menu item if applet is active
|
||||
if (settings.userApplets.active[i]) {
|
||||
const char *name = windowManager->getAppletName(i);
|
||||
bool *isActive = &(settings.userApplets.autoshow[i]);
|
||||
if (settings->userApplets.active[i]) {
|
||||
const char *name = inkhud->userApplets.at(i)->name;
|
||||
bool *isActive = &(settings->userApplets.autoshow[i]);
|
||||
items.push_back(MenuItem(name, MenuAction::TOGGLE_AUTOSHOW_APPLET, MenuPage::AUTOSHOW, isActive));
|
||||
}
|
||||
}
|
||||
|
@ -599,10 +586,10 @@ void InkHUD::MenuApplet::drawSystemInfoPanel(int16_t left, int16_t top, uint16_t
|
|||
|
||||
// Get the height of the the panel drawn at the top of the menu
|
||||
// This is inefficient, as we do actually have to render the panel to determine the height
|
||||
// It solves a catch-22 situtation, where slotCount needs to know panel height, and panel height needs to know slotCount
|
||||
// It solves a catch-22 situation, where slotCount needs to know panel height, and panel height needs to know slotCount
|
||||
uint16_t InkHUD::MenuApplet::getSystemInfoPanelHeight()
|
||||
{
|
||||
// Render *waay* off screen
|
||||
// Render *far* off screen
|
||||
uint16_t height = 0;
|
||||
drawSystemInfoPanel(INT16_MIN, INT16_MIN, 1, &height);
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
#include "configuration.h"
|
||||
|
||||
#include "graphics/niche/Drivers/Backlight/LatchingBacklight.h"
|
||||
#include "graphics/niche/InkHUD/Applet.h"
|
||||
#include "graphics/niche/InkHUD/WindowManager.h"
|
||||
#include "graphics/niche/InkHUD/InkHUD.h"
|
||||
#include "graphics/niche/InkHUD/Persistence.h"
|
||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
||||
|
||||
#include "./MenuItem.h"
|
||||
#include "./MenuPage.h"
|
||||
|
@ -16,7 +17,7 @@ namespace NicheGraphics::InkHUD
|
|||
|
||||
class Applet;
|
||||
|
||||
class MenuApplet : public Applet, public concurrency::OSThread
|
||||
class MenuApplet : public SystemApplet, public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
MenuApplet();
|
||||
|
@ -30,6 +31,8 @@ class MenuApplet : public Applet, public concurrency::OSThread
|
|||
void show(Tile *t); // Open the menu, onto a user tile
|
||||
|
||||
protected:
|
||||
Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton
|
||||
|
||||
int32_t runOnce() override;
|
||||
|
||||
void execute(MenuItem item); // Perform the MenuAction associated with a MenuItem, if any
|
||||
|
@ -41,7 +44,7 @@ class MenuApplet : public Applet, public concurrency::OSThread
|
|||
void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width,
|
||||
uint16_t *height = nullptr); // Info panel at top of root menu
|
||||
|
||||
MenuPage currentPage;
|
||||
MenuPage currentPage = MenuPage::ROOT;
|
||||
uint8_t cursor = 0; // Which menu item is currently highlighted
|
||||
bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection)
|
||||
|
||||
|
@ -50,9 +53,6 @@ class MenuApplet : public Applet, public concurrency::OSThread
|
|||
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
|
||||
|
||||
Applet *borrowedTileOwner = nullptr; // Which applet we have temporarily replaced while displaying menu
|
||||
|
||||
WindowManager *windowManager = nullptr; // Convenient access to the InkHUD::WindowManager singleton
|
||||
Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::InkHUD
|
||||
|
|
|
@ -3,22 +3,20 @@
|
|||
#include "./NotificationApplet.h"
|
||||
|
||||
#include "./Notification.h"
|
||||
#include "graphics/niche/InkHUD/Persistence.h"
|
||||
|
||||
#include "meshUtils.h"
|
||||
#include "modules/TextMessageModule.h"
|
||||
|
||||
#include "RTC.h"
|
||||
|
||||
using namespace NicheGraphics;
|
||||
|
||||
void InkHUD::NotificationApplet::onActivate()
|
||||
InkHUD::NotificationApplet::NotificationApplet()
|
||||
{
|
||||
textMessageObserver.observe(textMessageModule);
|
||||
}
|
||||
|
||||
// Note: This applet probably won't ever be deactivated
|
||||
void InkHUD::NotificationApplet::onDeactivate()
|
||||
{
|
||||
textMessageObserver.unobserve(textMessageModule);
|
||||
}
|
||||
|
||||
// Collect meta-info about the text message, and ask for approval for the notification
|
||||
// No need to save the message itself; we can use the cached InkHUD::latestMessage data during render()
|
||||
int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p)
|
||||
|
@ -28,7 +26,7 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket
|
|||
|
||||
// Abort if feature disabled
|
||||
// This is a bit clumsy, but avoids complicated handling when the feature is enabled / disabled
|
||||
if (!settings.optionalFeatures.notifications)
|
||||
if (!settings->optionalFeatures.notifications)
|
||||
return 0;
|
||||
|
||||
// Abort if this is an outgoing message
|
||||
|
@ -36,7 +34,7 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket
|
|||
return 0;
|
||||
|
||||
// Abort if message was only an "emoji reaction"
|
||||
// Possibly some implemetation of this in future?
|
||||
// Possibly some implementation of this in future?
|
||||
if (p->decoded.emoji)
|
||||
return 0;
|
||||
|
||||
|
@ -55,13 +53,16 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket
|
|||
n.sender = p->from;
|
||||
}
|
||||
|
||||
// Close an old notification, if shown
|
||||
dismiss();
|
||||
|
||||
// Check if we should display the notification
|
||||
// A foreground applet might already be displaying this info
|
||||
hasNotification = true;
|
||||
currentNotification = n;
|
||||
if (isApproved()) {
|
||||
bringToForeground();
|
||||
WindowManager::getInstance()->forceUpdate();
|
||||
inkhud->forceUpdate();
|
||||
} else
|
||||
hasNotification = false; // Clear the pending notification: it was rejected
|
||||
|
||||
|
@ -76,8 +77,6 @@ void InkHUD::NotificationApplet::onRender()
|
|||
// We do need to do this with the battery though, as it is an "overlay"
|
||||
fillRect(0, 0, width(), height(), WHITE);
|
||||
|
||||
setFont(fontSmall);
|
||||
|
||||
// Padding (horizontal)
|
||||
const uint16_t padW = 4;
|
||||
|
||||
|
@ -137,6 +136,28 @@ void InkHUD::NotificationApplet::onRender()
|
|||
printThick(textM, height() / 2, text, 2, 1);
|
||||
}
|
||||
|
||||
void InkHUD::NotificationApplet::onForeground()
|
||||
{
|
||||
handleInput = true; // Intercept the button input for our applet, so we can dismiss the notification
|
||||
}
|
||||
|
||||
void InkHUD::NotificationApplet::onBackground()
|
||||
{
|
||||
handleInput = false;
|
||||
}
|
||||
|
||||
void InkHUD::NotificationApplet::onButtonShortPress()
|
||||
{
|
||||
dismiss();
|
||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||
}
|
||||
|
||||
void InkHUD::NotificationApplet::onButtonLongPress()
|
||||
{
|
||||
dismiss();
|
||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||
}
|
||||
|
||||
// Ask the WindowManager to check whether any displayed applets are already displaying the info from this notification
|
||||
// Called internally when we first get a "notifiable event", and then again before render,
|
||||
// in case autoshow swapped which applet was displayed
|
||||
|
@ -148,7 +169,13 @@ bool InkHUD::NotificationApplet::isApproved()
|
|||
return false;
|
||||
}
|
||||
|
||||
return WindowManager::getInstance()->approveNotification(currentNotification);
|
||||
// Ask all visible user applets for approval
|
||||
for (Applet *ua : inkhud->userApplets) {
|
||||
if (ua->isForeground() && !ua->approveNotification(currentNotification))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Mark that the notification should no-longer be rendered
|
||||
|
@ -180,7 +207,8 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila
|
|||
bool isBroadcast = currentNotification.type == Notification::Type::NOTIFICATION_MESSAGE_BROADCAST;
|
||||
|
||||
// Pick source of message
|
||||
MessageStore::Message *message = isBroadcast ? &latestMessage.broadcast : &latestMessage.dm;
|
||||
MessageStore::Message *message =
|
||||
isBroadcast ? &inkhud->persistence->latestMessage.broadcast : &inkhud->persistence->latestMessage.dm;
|
||||
|
||||
// Find info about the sender
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(message->sender);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
/*
|
||||
|
||||
Pop-up notification bar, on screen top edge
|
||||
Displays information we feel is important, but which is not shown on currently focussed applet(s)
|
||||
Displays information we feel is important, but which is not shown on currently focused applet(s)
|
||||
E.g.: messages, while viewing map, etc
|
||||
|
||||
Feature should be optional; enable disable via on-screen menu
|
||||
|
@ -16,17 +16,21 @@ Feature should be optional; enable disable via on-screen menu
|
|||
|
||||
#include "concurrency/OSThread.h"
|
||||
|
||||
#include "graphics/niche/InkHUD/Applet.h"
|
||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
||||
|
||||
namespace NicheGraphics::InkHUD
|
||||
{
|
||||
|
||||
class NotificationApplet : public Applet
|
||||
class NotificationApplet : public SystemApplet
|
||||
{
|
||||
public:
|
||||
NotificationApplet();
|
||||
|
||||
void onRender() override;
|
||||
void onActivate() override;
|
||||
void onDeactivate() override;
|
||||
void onForeground() override;
|
||||
void onBackground() override;
|
||||
void onButtonShortPress() override;
|
||||
void onButtonLongPress() override;
|
||||
|
||||
int onReceiveTextMessage(const meshtastic_MeshPacket *p);
|
||||
|
||||
|
@ -40,8 +44,8 @@ class NotificationApplet : public Applet
|
|||
|
||||
std::string getNotificationText(uint16_t widthAvailable); // Get text for notification, to suit screen width
|
||||
|
||||
bool hasNotification = false; // Only used for assert. Todo: remove?
|
||||
Notification currentNotification; // Set when something notification-worthy happens. Used by render()
|
||||
bool hasNotification = false; // Only used for assert. Todo: remove?
|
||||
Notification currentNotification = Notification(); // Set when something notification-worthy happens. Used by render()
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::InkHUD
|
||||
|
|
|
@ -6,8 +6,7 @@ using namespace NicheGraphics;
|
|||
|
||||
InkHUD::PairingApplet::PairingApplet()
|
||||
{
|
||||
// Grab the window manager singleton, for convenience
|
||||
windowManager = WindowManager::getInstance();
|
||||
bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
|
||||
}
|
||||
|
||||
void InkHUD::PairingApplet::onRender()
|
||||
|
@ -31,34 +30,22 @@ void InkHUD::PairingApplet::onRender()
|
|||
printAt(X(0.5), Y(0.75), name, CENTER, MIDDLE);
|
||||
}
|
||||
|
||||
void InkHUD::PairingApplet::onActivate()
|
||||
{
|
||||
bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
|
||||
}
|
||||
|
||||
void InkHUD::PairingApplet::onDeactivate()
|
||||
{
|
||||
bluetoothStatusObserver.unobserve(&bluetoothStatus->onNewStatus);
|
||||
}
|
||||
|
||||
void InkHUD::PairingApplet::onForeground()
|
||||
{
|
||||
// If another applet has locked the display, ask it to exit
|
||||
Applet *other = windowManager->whoLocked();
|
||||
if (other != nullptr)
|
||||
other->sendToBackground();
|
||||
|
||||
windowManager->claimFullscreen(this); // Take ownership of the fullscreen tile
|
||||
windowManager->lock(this); // Prevent user applets from requesting update
|
||||
// Prevent most other applets from requesting update, and skip their rendering entirely
|
||||
// Another system applet with a higher precedence can potentially ignore this
|
||||
SystemApplet::lockRendering = true;
|
||||
SystemApplet::lockRequests = true;
|
||||
}
|
||||
void InkHUD::PairingApplet::onBackground()
|
||||
{
|
||||
windowManager->releaseFullscreen(); // Relinquish ownership of the fullscreen tile
|
||||
windowManager->unlock(this); // Allow normal user applet update requests to resume
|
||||
// Allow normal update behavior to resume
|
||||
SystemApplet::lockRendering = false;
|
||||
SystemApplet::lockRequests = false;
|
||||
|
||||
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
||||
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
||||
windowManager->forceUpdate(EInk::UpdateTypes::FULL);
|
||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||
}
|
||||
|
||||
int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *status)
|
||||
|
@ -75,12 +62,6 @@ int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *sta
|
|||
// Store the passkey for rendering
|
||||
passkey = bluetoothStatus->getPasskey();
|
||||
|
||||
// Make sure no other system applets have a lock on the display
|
||||
// Boot screen, menu, etc
|
||||
Applet *lockOwner = windowManager->whoLocked();
|
||||
if (lockOwner)
|
||||
lockOwner->sendToBackground();
|
||||
|
||||
// Show pairing screen
|
||||
bringToForeground();
|
||||
}
|
||||
|
|
|
@ -10,19 +10,19 @@
|
|||
|
||||
#include "configuration.h"
|
||||
|
||||
#include "graphics/niche/InkHUD/Applet.h"
|
||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
||||
|
||||
#include "main.h"
|
||||
|
||||
namespace NicheGraphics::InkHUD
|
||||
{
|
||||
|
||||
class PairingApplet : public Applet
|
||||
class PairingApplet : public SystemApplet
|
||||
{
|
||||
public:
|
||||
PairingApplet();
|
||||
|
||||
void onRender() override;
|
||||
void onActivate() override;
|
||||
void onDeactivate() override;
|
||||
void onForeground() override;
|
||||
void onBackground() override;
|
||||
|
||||
|
@ -34,8 +34,6 @@ class PairingApplet : public Applet
|
|||
CallbackObserver<PairingApplet, const meshtastic::Status *>(this, &PairingApplet::onBluetoothStatusUpdate);
|
||||
|
||||
std::string passkey = ""; // Passkey. Six digits, possibly with leading zeros
|
||||
|
||||
WindowManager *windowManager = nullptr; // For convenience. Set in constructor.
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::InkHUD
|
||||
|
|
|
@ -4,14 +4,6 @@
|
|||
|
||||
using namespace NicheGraphics;
|
||||
|
||||
InkHUD::PlaceholderApplet::PlaceholderApplet()
|
||||
{
|
||||
// Because this applet sometimes gets processed as if it were a bonafide user applet,
|
||||
// it's probably better that we do give it a human readable name, just in case it comes up later.
|
||||
// For genuine user applets, this is set by WindowManager::addApplet
|
||||
Applet::name = "Placeholder";
|
||||
}
|
||||
|
||||
void InkHUD::PlaceholderApplet::onRender()
|
||||
{
|
||||
// This placeholder applet fills its area with sparse diagonal lines
|
||||
|
|
|
@ -9,20 +9,19 @@ Fills the area with diagonal lines
|
|||
|
||||
#include "configuration.h"
|
||||
|
||||
#include "graphics/niche/InkHUD/Applet.h"
|
||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
||||
|
||||
namespace NicheGraphics::InkHUD
|
||||
{
|
||||
|
||||
class PlaceholderApplet : public Applet
|
||||
class PlaceholderApplet : public SystemApplet
|
||||
{
|
||||
public:
|
||||
PlaceholderApplet();
|
||||
void onRender() override;
|
||||
|
||||
// Note: onForeground, onBackground, and wantsToRender are not meaningful for this applet.
|
||||
// The window manager decides when and where it should be rendered
|
||||
// It may be drawn to several different tiles during on WindowManager::render call
|
||||
// It may be drawn to several different tiles during an Renderer::render call
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::InkHUD
|
||||
|
|
|
@ -2,12 +2,44 @@
|
|||
|
||||
#include "./TipsApplet.h"
|
||||
|
||||
#include "graphics/niche/InkHUD/Persistence.h"
|
||||
|
||||
#include "main.h"
|
||||
|
||||
using namespace NicheGraphics;
|
||||
|
||||
InkHUD::TipsApplet::TipsApplet()
|
||||
{
|
||||
// Grab the window manager singleton, for convenience
|
||||
windowManager = WindowManager::getInstance();
|
||||
// Decide which tips (if any) should be shown to user after the boot screen
|
||||
|
||||
// Welcome screen
|
||||
if (settings->tips.firstBoot)
|
||||
tipQueue.push_back(Tip::WELCOME);
|
||||
|
||||
// Antenna, region, timezone
|
||||
// Shown at boot if region not yet set
|
||||
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET)
|
||||
tipQueue.push_back(Tip::FINISH_SETUP);
|
||||
|
||||
// Shutdown info
|
||||
// Shown until user performs one valid shutdown
|
||||
if (!settings->tips.safeShutdownSeen)
|
||||
tipQueue.push_back(Tip::SAFE_SHUTDOWN);
|
||||
|
||||
// Using the UI
|
||||
if (settings->tips.firstBoot) {
|
||||
tipQueue.push_back(Tip::CUSTOMIZATION);
|
||||
tipQueue.push_back(Tip::BUTTONS);
|
||||
}
|
||||
|
||||
// Catch an incorrect attempt at rotating display
|
||||
if (config.display.flip_screen)
|
||||
tipQueue.push_back(Tip::ROTATION);
|
||||
|
||||
// Applet is foreground immediately at boot, but is obscured by LogoApplet, which is also foreground
|
||||
// LogoApplet can be considered to have a higher Z-index, because it is placed before TipsApplet in the systemApplets vector
|
||||
if (!tipQueue.empty())
|
||||
bringToForeground();
|
||||
}
|
||||
|
||||
void InkHUD::TipsApplet::onRender()
|
||||
|
@ -53,7 +85,7 @@ void InkHUD::TipsApplet::onRender()
|
|||
|
||||
setFont(fontSmall);
|
||||
std::string shutdown;
|
||||
shutdown += "Before removing power, please shutdown from InkHUD menu, or a client app. \n";
|
||||
shutdown += "Before removing power, please shut down from InkHUD menu, or a client app. \n";
|
||||
shutdown += "\n";
|
||||
shutdown += "This ensures data is saved.";
|
||||
printWrapped(0, fontLarge.lineHeight() * 1.5, width(), shutdown);
|
||||
|
@ -153,51 +185,31 @@ void InkHUD::TipsApplet::renderWelcome()
|
|||
printAt(X(0.5), Y(1), "Press button to continue", CENTER, BOTTOM);
|
||||
}
|
||||
|
||||
// Grab fullscreen tile, and lock the window manager, when applet is shown
|
||||
void InkHUD::TipsApplet::onForeground()
|
||||
{
|
||||
windowManager->lock(this);
|
||||
windowManager->claimFullscreen(this);
|
||||
// Prevent most other applets from requesting update, and skip their rendering entirely
|
||||
// Another system applet with a higher precedence can potentially ignore this
|
||||
SystemApplet::lockRendering = true;
|
||||
SystemApplet::lockRequests = true;
|
||||
|
||||
SystemApplet::handleInput = true; // Our applet should handle button input (unless another system applet grabs it first)
|
||||
}
|
||||
|
||||
void InkHUD::TipsApplet::onBackground()
|
||||
{
|
||||
windowManager->releaseFullscreen();
|
||||
windowManager->unlock(this);
|
||||
// Allow normal update behavior to resume
|
||||
SystemApplet::lockRendering = false;
|
||||
SystemApplet::lockRequests = false;
|
||||
SystemApplet::handleInput = false;
|
||||
|
||||
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
||||
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||
}
|
||||
|
||||
void InkHUD::TipsApplet::onActivate()
|
||||
{
|
||||
// Decide which tips (if any) should be shown to user after the boot screen
|
||||
void InkHUD::TipsApplet::onActivate() {}
|
||||
|
||||
// Welcome screen
|
||||
if (settings.tips.firstBoot)
|
||||
tipQueue.push_back(Tip::WELCOME);
|
||||
|
||||
// Antenna, region, timezone
|
||||
// Shown at boot if region not yet set
|
||||
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET)
|
||||
tipQueue.push_back(Tip::FINISH_SETUP);
|
||||
|
||||
// Shutdown info
|
||||
// Shown until user performs one valid shutdown
|
||||
if (!settings.tips.safeShutdownSeen)
|
||||
tipQueue.push_back(Tip::SAFE_SHUTDOWN);
|
||||
|
||||
// Using the UI
|
||||
if (settings.tips.firstBoot) {
|
||||
tipQueue.push_back(Tip::CUSTOMIZATION);
|
||||
tipQueue.push_back(Tip::BUTTONS);
|
||||
}
|
||||
|
||||
// Catch an incorrect attempt at rotating display
|
||||
if (config.display.flip_screen)
|
||||
tipQueue.push_back(Tip::ROTATION);
|
||||
|
||||
// Applet will be brought to foreground when boot screen closes, via TipsApplet::onLockAvailable
|
||||
}
|
||||
|
||||
// While our applet has the window manager locked, we will receive the button input
|
||||
// While our SystemApplet::handleInput flag is true
|
||||
void InkHUD::TipsApplet::onButtonShortPress()
|
||||
{
|
||||
tipQueue.pop_front();
|
||||
|
@ -206,15 +218,15 @@ void InkHUD::TipsApplet::onButtonShortPress()
|
|||
if (tipQueue.empty()) {
|
||||
// Record that user has now seen the "tutorial" set of tips
|
||||
// Don't show them on subsequent boots
|
||||
if (settings.tips.firstBoot) {
|
||||
settings.tips.firstBoot = false;
|
||||
saveDataToFlash();
|
||||
if (settings->tips.firstBoot) {
|
||||
settings->tips.firstBoot = false;
|
||||
inkhud->persistence->saveSettings();
|
||||
}
|
||||
|
||||
// Close applet, and full refresh to clean the screen
|
||||
// Need to force update, because our request would be ignored otherwise, as we are now background
|
||||
sendToBackground();
|
||||
windowManager->forceUpdate(EInk::UpdateTypes::FULL);
|
||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||
}
|
||||
|
||||
// More tips left
|
||||
|
@ -222,13 +234,4 @@ void InkHUD::TipsApplet::onButtonShortPress()
|
|||
requestUpdate();
|
||||
}
|
||||
|
||||
// If the wm lock has just become availale (rendering, input), and we've still got tips, grab it!
|
||||
// This situation would arise if bluetooth pairing occurs while TipsApplet was already shown (after pairing)
|
||||
// Note: this event is only raised when *other* applets unlock the window manager
|
||||
void InkHUD::TipsApplet::onLockAvailable()
|
||||
{
|
||||
if (!tipQueue.empty())
|
||||
bringToForeground();
|
||||
}
|
||||
|
||||
#endif
|
|
@ -12,12 +12,12 @@
|
|||
|
||||
#include "configuration.h"
|
||||
|
||||
#include "graphics/niche/InkHUD/Applet.h"
|
||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
||||
|
||||
namespace NicheGraphics::InkHUD
|
||||
{
|
||||
|
||||
class TipsApplet : public Applet
|
||||
class TipsApplet : public SystemApplet
|
||||
{
|
||||
protected:
|
||||
enum class Tip {
|
||||
|
@ -37,7 +37,6 @@ class TipsApplet : public Applet
|
|||
void onForeground() override;
|
||||
void onBackground() override;
|
||||
void onButtonShortPress() override;
|
||||
void onLockAvailable() override; // Reopen if interrupted by bluetooth pairing
|
||||
|
||||
protected:
|
||||
void renderWelcome(); // Very first screen of tutorial
|
||||
|
|
|
@ -41,14 +41,12 @@ int InkHUD::AllMessageApplet::onReceiveTextMessage(const meshtastic_MeshPacket *
|
|||
|
||||
void InkHUD::AllMessageApplet::onRender()
|
||||
{
|
||||
setFont(fontSmall);
|
||||
|
||||
// Find newest message, regardless of whether DM or broadcast
|
||||
MessageStore::Message *message;
|
||||
if (latestMessage.wasBroadcast)
|
||||
message = &latestMessage.broadcast;
|
||||
if (latestMessage->wasBroadcast)
|
||||
message = &latestMessage->broadcast;
|
||||
else
|
||||
message = &latestMessage.dm;
|
||||
message = &latestMessage->dm;
|
||||
|
||||
// Short circuit: no text message
|
||||
if (!message->sender) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Ładowanie…
Reference in New Issue