mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-16 15:52:34 +00:00
Compare commits
109 Commits
multicast-
...
backup-res
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fce57e74d | ||
|
|
0533ca0eda | ||
|
|
661fcd0e77 | ||
|
|
ac3980e98d | ||
|
|
ff4b7fdce3 | ||
|
|
b933b48898 | ||
|
|
df52bbcae3 | ||
|
|
4a9f6ceb55 | ||
|
|
db84fbed5d | ||
|
|
96663218ee | ||
|
|
51dc2da83c | ||
|
|
19951e3b6a | ||
|
|
0b106d4642 | ||
|
|
70d8da6561 | ||
|
|
5f5fac25fd | ||
|
|
9cdd6b8633 | ||
|
|
f5db94e606 | ||
|
|
3f512976b8 | ||
|
|
ad41f9e013 | ||
|
|
b85d9f988e | ||
|
|
7150a68714 | ||
|
|
6cf8cbcda7 | ||
|
|
4e74c549ed | ||
|
|
065370c6b6 | ||
|
|
f913ce0310 | ||
|
|
261c326da6 | ||
|
|
eb3ffc1922 | ||
|
|
c986c4a742 | ||
|
|
cb0982c2f1 | ||
|
|
ccbec44bfb | ||
|
|
109d66e976 | ||
|
|
320a38f9ac | ||
|
|
488e76d27b | ||
|
|
ada8b96842 | ||
|
|
c2cc3207ba | ||
|
|
f309fba89c | ||
|
|
7dc0330897 | ||
|
|
6c227157b5 | ||
|
|
e4c6aa588e | ||
|
|
164d46facb | ||
|
|
6c6b804b53 | ||
|
|
1a92b7881f | ||
|
|
1961bcaf9d | ||
|
|
a7c4361d7c | ||
|
|
c0145001ef | ||
|
|
01618e99e5 | ||
|
|
a02d538b58 | ||
|
|
ac9cb235a8 | ||
|
|
9ce19c5c1c | ||
|
|
022dc29ea7 | ||
|
|
18410ba80d | ||
|
|
3f3f89c06e | ||
|
|
919085379e | ||
|
|
bdf60d8e4d | ||
|
|
431b067f30 | ||
|
|
4a63b36a7d | ||
|
|
1fe16a0471 | ||
|
|
2b1f45fd8b | ||
|
|
79c0e8168d | ||
|
|
459dbfff23 | ||
|
|
27ddd549f9 | ||
|
|
e5ed913a8a | ||
|
|
93d041b4b0 | ||
|
|
005e8501ce | ||
|
|
6cb3acd79f | ||
|
|
910efd86f0 | ||
|
|
ded45cf17d | ||
|
|
6f9e4d5b1a | ||
|
|
b012e561eb | ||
|
|
9fd100dd92 | ||
|
|
c756bea711 | ||
|
|
068d529298 | ||
|
|
add75f073a | ||
|
|
a00e272afe | ||
|
|
48b00188fb | ||
|
|
62058b650a | ||
|
|
147416045a | ||
|
|
72e991104e | ||
|
|
26a0612e37 | ||
|
|
6cfa5d8f29 | ||
|
|
2e21e49144 | ||
|
|
aad9e352b7 | ||
|
|
368d811ea6 | ||
|
|
2996a9616f | ||
|
|
99e47cf73c | ||
|
|
102c328436 | ||
|
|
0cd4224033 | ||
|
|
20743ae2c4 | ||
|
|
51c5d8ce93 | ||
|
|
b229d351cf | ||
|
|
350b82bc08 | ||
|
|
d21b272680 | ||
|
|
fe5d251393 | ||
|
|
66a98fb062 | ||
|
|
d65d9305d3 | ||
|
|
34e5cf0d96 | ||
|
|
b28db07409 | ||
|
|
f753caf15d | ||
|
|
0c1838dde7 | ||
|
|
b183febd3c | ||
|
|
87601f4760 | ||
|
|
0e3c419652 | ||
|
|
891bf643e2 | ||
|
|
143cdf4572 | ||
|
|
d440dbd522 | ||
|
|
7e063c1dda | ||
|
|
988d8cd1ab | ||
|
|
c98c682cff | ||
|
|
8023fec0ec |
@@ -29,11 +29,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
|||||||
gpg \
|
gpg \
|
||||||
gnupg2 \
|
gnupg2 \
|
||||||
libusb-1.0-0-dev \
|
libusb-1.0-0-dev \
|
||||||
libuv1-dev \
|
|
||||||
libi2c-dev \
|
libi2c-dev \
|
||||||
libxcb-xkb-dev \
|
|
||||||
libxkbcommon-dev \
|
|
||||||
libinput-dev \
|
|
||||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
RUN pipx install platformio
|
RUN pipx install platformio
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
git submodule update --init
|
git submodule update --init
|
||||||
|
|
||||||
pip install --no-cache-dir setuptools
|
|
||||||
pipx install esptool
|
|
||||||
|
|||||||
5
.gitattributes
vendored
5
.gitattributes
vendored
@@ -1,5 +1,4 @@
|
|||||||
* text=auto eol=lf
|
* text=auto eol=lf
|
||||||
*.cmd text eol=crlf
|
*.{cmd,[cC][mM][dD]} text eol=crlf
|
||||||
*.bat text eol=crlf
|
*.{bat,[bB][aA][tT]} text eol=crlf
|
||||||
*.ps1 text eol=crlf
|
|
||||||
*.{sh,[sS][hH]} text eol=lf
|
*.{sh,[sS][hH]} text eol=lf
|
||||||
|
|||||||
2
.github/actions/setup-base/action.yml
vendored
2
.github/actions/setup-base/action.yml
vendored
@@ -20,7 +20,7 @@ runs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get -y update --fix-missing
|
sudo apt-get -y update --fix-missing
|
||||||
sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev libuv1-dev lsb-release
|
sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev lsb-release
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
|
|||||||
2
.github/actions/setup-native/action.yml
vendored
2
.github/actions/setup-native/action.yml
vendored
@@ -11,4 +11,4 @@ runs:
|
|||||||
- name: Install libs needed for native build
|
- name: Install libs needed for native build
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
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 libuv1-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
|
||||||
|
|||||||
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -19,8 +19,6 @@ updates:
|
|||||||
interval: daily
|
interval: daily
|
||||||
time: "05:00"
|
time: "05:00"
|
||||||
timezone: US/Pacific
|
timezone: US/Pacific
|
||||||
ignore:
|
|
||||||
- dependency-name: protobufs
|
|
||||||
- package-ecosystem: github-actions
|
- package-ecosystem: github-actions
|
||||||
directory: /.github/workflows
|
directory: /.github/workflows
|
||||||
schedule:
|
schedule:
|
||||||
|
|||||||
2
.github/workflows/build_debian_src.yml
vendored
2
.github/workflows/build_debian_src.yml
vendored
@@ -4,7 +4,7 @@ on:
|
|||||||
workflow_call:
|
workflow_call:
|
||||||
secrets:
|
secrets:
|
||||||
PPA_GPG_PRIVATE_KEY:
|
PPA_GPG_PRIVATE_KEY:
|
||||||
required: false
|
required: true
|
||||||
inputs:
|
inputs:
|
||||||
series:
|
series:
|
||||||
description: Ubuntu/Debian series to target
|
description: Ubuntu/Debian series to target
|
||||||
|
|||||||
29
.github/workflows/main_matrix.yml
vendored
29
.github/workflows/main_matrix.yml
vendored
@@ -136,7 +136,6 @@ jobs:
|
|||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
package-pio-deps-native-tft:
|
package-pio-deps-native-tft:
|
||||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
|
||||||
uses: ./.github/workflows/package_pio_deps.yml
|
uses: ./.github/workflows/package_pio_deps.yml
|
||||||
with:
|
with:
|
||||||
pio_env: native-tft
|
pio_env: native-tft
|
||||||
@@ -330,13 +329,13 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
pattern: platformio-deps-native-tft-${{ steps.version.outputs.long }}
|
pattern: platformio-deps-native-tft-${{ steps.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
path: ./output/pio-deps-native-tft
|
path: ./output/pio-deps-native
|
||||||
|
|
||||||
- name: Zip linux sources
|
- name: Zip linux sources
|
||||||
working-directory: output
|
working-directory: output
|
||||||
run: |
|
run: |
|
||||||
zip -j -9 -r ./meshtasticd-${{ steps.version.outputs.deb }}-src.zip ./debian-src
|
zip -j -9 -r ./meshtasticd-${{ steps.version.outputs.deb }}-src.zip ./debian-src
|
||||||
zip -9 -r ./platformio-deps-native-tft-${{ steps.version.outputs.long }}.zip ./pio-deps-native-tft
|
zip -9 -r ./platformio-deps-native-${{ steps.version.outputs.long }}.zip ./pio-deps-native
|
||||||
|
|
||||||
# For diagnostics
|
# For diagnostics
|
||||||
- name: Display structure of downloaded files
|
- name: Display structure of downloaded files
|
||||||
@@ -345,10 +344,32 @@ jobs:
|
|||||||
- name: Add linux sources to release
|
- name: Add linux sources to release
|
||||||
run: |
|
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/meshtasticd-${{ steps.version.outputs.deb }}-src.zip
|
||||||
gh release upload v${{ steps.version.outputs.long }} ./output/platformio-deps-native-tft-${{ steps.version.outputs.long }}.zip
|
gh release upload v${{ steps.version.outputs.long }} ./output/platformio-deps-native-${{ steps.version.outputs.long }}.zip
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
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:
|
release-firmware:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|||||||
46
.github/workflows/release_channels.yml
vendored
46
.github/workflows/release_channels.yml
vendored
@@ -43,49 +43,3 @@ jobs:
|
|||||||
copr_project: |-
|
copr_project: |-
|
||||||
${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
|
${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
|
||||||
secrets: inherit
|
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
|
|
||||||
|
|||||||
7
.github/workflows/sec_sast_semgrep_cron.yml
vendored
7
.github/workflows/sec_sast_semgrep_cron.yml
vendored
@@ -6,14 +6,11 @@ on:
|
|||||||
schedule:
|
schedule:
|
||||||
- cron: 0 1 * * 6
|
- cron: 0 1 * * 6
|
||||||
|
|
||||||
permissions:
|
permissions: read-all
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
semgrep-full:
|
semgrep-full:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: semgrep/semgrep
|
image: semgrep/semgrep
|
||||||
|
|
||||||
|
|||||||
1
.github/workflows/stale_bot.yml
vendored
1
.github/workflows/stale_bot.yml
vendored
@@ -18,6 +18,5 @@ jobs:
|
|||||||
- name: Stale PR+Issues
|
- name: Stale PR+Issues
|
||||||
uses: actions/stale@v9.1.0
|
uses: actions/stale@v9.1.0
|
||||||
with:
|
with:
|
||||||
days-before-stale: 45
|
|
||||||
exempt-issue-labels: pinned,3.0
|
exempt-issue-labels: pinned,3.0
|
||||||
exempt-pr-labels: pinned,3.0
|
exempt-pr-labels: pinned,3.0
|
||||||
|
|||||||
2
.github/workflows/test_native.yml
vendored
2
.github/workflows/test_native.yml
vendored
@@ -143,7 +143,7 @@ jobs:
|
|||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Test Report
|
- name: Test Report
|
||||||
uses: dorny/test-reporter@v2.0.0
|
uses: dorny/test-reporter@v1.9.1
|
||||||
with:
|
with:
|
||||||
name: PlatformIO Tests
|
name: PlatformIO Tests
|
||||||
path: testreport.xml
|
path: testreport.xml
|
||||||
|
|||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,6 +1,9 @@
|
|||||||
[submodule "protobufs"]
|
[submodule "protobufs"]
|
||||||
path = protobufs
|
path = protobufs
|
||||||
url = https://github.com/meshtastic/protobufs.git
|
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"]
|
[submodule "meshtestic"]
|
||||||
path = meshtestic
|
path = meshtestic
|
||||||
url = https://github.com/meshtastic/meshTestic
|
url = https://github.com/meshtastic/meshTestic
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
version: 0.1
|
version: 0.1
|
||||||
cli:
|
cli:
|
||||||
version: 1.22.11
|
version: 1.22.10
|
||||||
plugins:
|
plugins:
|
||||||
sources:
|
sources:
|
||||||
- id: trunk
|
- id: trunk
|
||||||
@@ -8,15 +8,15 @@ plugins:
|
|||||||
uri: https://github.com/trunk-io/plugins
|
uri: https://github.com/trunk-io/plugins
|
||||||
lint:
|
lint:
|
||||||
enabled:
|
enabled:
|
||||||
- prettier@3.5.3
|
- prettier@3.5.2
|
||||||
- trufflehog@3.88.17
|
- trufflehog@3.88.14
|
||||||
- yamllint@1.36.0
|
- yamllint@1.35.1
|
||||||
- bandit@1.8.3
|
- bandit@1.8.3
|
||||||
- checkov@3.2.386
|
- checkov@3.2.378
|
||||||
- terrascan@1.19.9
|
- terrascan@1.19.9
|
||||||
- trivy@0.60.0
|
- trivy@0.59.1
|
||||||
- taplo@0.9.3
|
- taplo@0.9.3
|
||||||
- ruff@0.10.0
|
- ruff@0.9.7
|
||||||
- isort@6.0.1
|
- isort@6.0.1
|
||||||
- markdownlint@0.44.0
|
- markdownlint@0.44.0
|
||||||
- oxipng@9.1.4
|
- oxipng@9.1.4
|
||||||
|
|||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -7,8 +7,5 @@
|
|||||||
"cmake.configureOnOpen": false,
|
"cmake.configureOnOpen": false,
|
||||||
"[cpp]": {
|
"[cpp]": {
|
||||||
"editor.defaultFormatter": "trunk.io"
|
"editor.defaultFormatter": "trunk.io"
|
||||||
},
|
|
||||||
"[powershell]": {
|
|
||||||
"editor.defaultFormatter": "ms-vscode.powershell"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ ENV TZ=Etc/UTC
|
|||||||
ENV PIP_ROOT_USER_ACTION=ignore
|
ENV PIP_ROOT_USER_ACTION=ignore
|
||||||
RUN apt-get update && apt-get install --no-install-recommends -y \
|
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||||
wget g++ zip git ca-certificates \
|
wget g++ zip git ca-certificates \
|
||||||
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
|
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev \
|
||||||
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev pkg-config \
|
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev pkg-config \
|
||||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||||
&& pip install --no-cache-dir -U platformio \
|
&& pip install --no-cache-dir -U platformio \
|
||||||
@@ -38,7 +38,7 @@ ENV TZ=Etc/UTC
|
|||||||
USER root
|
USER root
|
||||||
|
|
||||||
RUN apt-get update && apt-get --no-install-recommends -y install \
|
RUN apt-get update && apt-get --no-install-recommends -y install \
|
||||||
libc-bin libc6 libgpiod2 libyaml-cpp0.7 libi2c0 libuv1 libusb-1.0-0-dev liborcania2.3 libulfius2.7 libssl3 \
|
libc-bin libc6 libgpiod2 libyaml-cpp0.7 libi2c0 libulfius2.7 libusb-1.0-0-dev liborcania2.3 libssl3 \
|
||||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||||
&& mkdir -p /var/lib/meshtasticd \
|
&& mkdir -p /var/lib/meshtasticd \
|
||||||
&& mkdir -p /etc/meshtasticd/config.d \
|
&& mkdir -p /etc/meshtasticd/config.d \
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ FROM python:3.13-alpine3.21 AS builder
|
|||||||
ENV PIP_ROOT_USER_ACTION=ignore
|
ENV PIP_ROOT_USER_ACTION=ignore
|
||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add \
|
||||||
bash g++ libstdc++-dev linux-headers zip git ca-certificates libgpiod-dev yaml-cpp-dev bluez-dev \
|
bash g++ libstdc++-dev linux-headers zip git ca-certificates libgpiod-dev yaml-cpp-dev bluez-dev \
|
||||||
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
|
libusb-dev i2c-tools-dev openssl-dev pkgconf argp-standalone \
|
||||||
&& rm -rf /var/cache/apk/* \
|
&& rm -rf /var/cache/apk/* \
|
||||||
&& pip install --no-cache-dir -U platformio \
|
&& pip install --no-cache-dir -U platformio \
|
||||||
&& mkdir /tmp/firmware
|
&& mkdir /tmp/firmware
|
||||||
@@ -32,7 +32,7 @@ FROM alpine:3.21
|
|||||||
USER root
|
USER root
|
||||||
|
|
||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add \
|
||||||
libstdc++ libgpiod yaml-cpp libusb i2c-tools libuv \
|
libstdc++ libgpiod yaml-cpp libusb i2c-tools \
|
||||||
&& rm -rf /var/cache/apk/* \
|
&& rm -rf /var/cache/apk/* \
|
||||||
&& mkdir -p /var/lib/meshtasticd \
|
&& mkdir -p /var/lib/meshtasticd \
|
||||||
&& mkdir -p /etc/meshtasticd/config.d \
|
&& mkdir -p /etc/meshtasticd/config.d \
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
; The Portduino based 'native' environment. Currently supported on Linux targets with real LoRa hardware (or simulated).
|
; The Portduino based 'native' environment. Currently supported on Linux targets with real LoRa hardware (or simulated).
|
||||||
[portduino_base]
|
[portduino_base]
|
||||||
platform = https://github.com/Jorropo/platform-native.git#17fa89daec4402af491512f75278a7fec8a5818c
|
platform = https://github.com/meshtastic/platform-native.git#562d189828f09fbf4c4093b3c0104bae9d8e9ff9
|
||||||
framework = arduino
|
framework = arduino
|
||||||
|
|
||||||
build_src_filter =
|
build_src_filter =
|
||||||
@@ -34,12 +34,10 @@ build_flags =
|
|||||||
-Isrc/platform/portduino
|
-Isrc/platform/portduino
|
||||||
-DRADIOLIB_EEPROM_UNSUPPORTED
|
-DRADIOLIB_EEPROM_UNSUPPORTED
|
||||||
-DPORTDUINO_LINUX_HARDWARE
|
-DPORTDUINO_LINUX_HARDWARE
|
||||||
-DHAS_UDP_MULTICAST
|
|
||||||
-lpthread
|
-lpthread
|
||||||
-lstdc++fs
|
-lstdc++fs
|
||||||
-lbluetooth
|
-lbluetooth
|
||||||
-lgpiod
|
-lgpiod
|
||||||
-lyaml-cpp
|
-lyaml-cpp
|
||||||
-li2c
|
-li2c
|
||||||
-luv
|
|
||||||
-std=c++17
|
-std=c++17
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
; Common settings for rp2040 Processor based targets
|
; Common settings for rp2040 Processor based targets
|
||||||
[rp2040_base]
|
[rp2040_base]
|
||||||
platform = https://github.com/maxgerhardt/platform-raspberrypi.git#76ecf3c7e9dd4503af0331154c4ca1cddc4b03e5 ; For arduino-pico >= 4.4.3
|
platform = https://github.com/maxgerhardt/platform-raspberrypi.git#19e30129fb1428b823be585c787dcb4ac0d9014c ; For arduino-pico >=4.2.1
|
||||||
extends = arduino_base
|
extends = arduino_base
|
||||||
platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#4.4.3
|
platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#6024e9a7e82a72e38dd90f42029ba3748835eb2e ; 4.3.0 with fix MDNS
|
||||||
|
|
||||||
board_build.core = earlephilhower
|
board_build.core = earlephilhower
|
||||||
board_build.filesystem_size = 0.5m
|
board_build.filesystem_size = 0.5m
|
||||||
|
|||||||
@@ -1,296 +1,72 @@
|
|||||||
@ECHO OFF
|
@ECHO OFF
|
||||||
SETLOCAL EnableDelayedExpansion
|
|
||||||
TITLE Meshtastic device-install
|
|
||||||
|
|
||||||
SET "SCRIPT_NAME=%~nx0"
|
set PYTHON=python
|
||||||
SET "DEBUG=0"
|
set WEB_APP=0
|
||||||
SET "PYTHON="
|
|
||||||
SET "WEB_APP=0"
|
|
||||||
SET "TFT_BUILD=0"
|
|
||||||
SET "TFT8=0"
|
|
||||||
SET "TFT16=0"
|
|
||||||
SET "ESPTOOL_BAUD=115200"
|
|
||||||
SET "ESPTOOL_CMD="
|
|
||||||
SET "LOGCOUNTER=0"
|
|
||||||
|
|
||||||
GOTO getopts
|
:: Determine the correct esptool command to use
|
||||||
:help
|
where esptool >nul 2>&1
|
||||||
ECHO Flash image file to device, but first erasing and writing system information.
|
if %ERRORLEVEL% EQU 0 (
|
||||||
ECHO.
|
set "ESPTOOL_CMD=esptool"
|
||||||
ECHO Usage: %SCRIPT_NAME% -f filename [-p PORT] [-P python] (--web)
|
) else (
|
||||||
ECHO.
|
set "ESPTOOL_CMD=%PYTHON% -m esptool"
|
||||||
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
|
|
||||||
|
|
||||||
:version
|
goto GETOPTS
|
||||||
ECHO %SCRIPT_NAME% [Version 2.6.0]
|
:HELP
|
||||||
ECHO Meshtastic
|
echo Usage: %~nx0 [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME^|FILENAME] [--web]
|
||||||
GOTO eof
|
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
|
||||||
|
|
||||||
:getopts
|
:GETOPTS
|
||||||
IF "%~1"=="" GOTO endopts
|
if /I "%1"=="-h" goto HELP
|
||||||
IF /I "%~1"=="-?" GOTO help
|
if /I "%1"=="--help" goto HELP
|
||||||
IF /I "%~1"=="-h" GOTO help
|
if /I "%1"=="-F" set "FILENAME=%2" & SHIFT
|
||||||
IF /I "%~1"=="--help" GOTO help
|
if /I "%1"=="-p" set ESPTOOL_PORT=%2 & SHIFT
|
||||||
IF /I "%~1"=="-v" GOTO version
|
if /I "%1"=="-P" set PYTHON=%2 & SHIFT
|
||||||
IF /I "%~1"=="--version" GOTO version
|
if /I "%1"=="--web" set WEB_APP=1 & SHIFT
|
||||||
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
|
SHIFT
|
||||||
GOTO getopts
|
IF NOT "__%1__"=="____" goto GETOPTS
|
||||||
:endopts
|
|
||||||
|
|
||||||
CALL :LOG_MESSAGE DEBUG "Checking FILENAME parameter..."
|
IF "__%FILENAME%__" == "____" (
|
||||||
IF "__!FILENAME!__"=="____" (
|
echo "Missing FILENAME"
|
||||||
CALL :LOG_MESSAGE DEBUG "Missing -f filename input."
|
goto HELP
|
||||||
GOTO help
|
)
|
||||||
) ELSE (
|
IF EXIST %FILENAME% IF x%FILENAME:update=%==x%FILENAME% (
|
||||||
CALL :LOG_MESSAGE DEBUG "Filename: !FILENAME!"
|
echo Trying to flash update %FILENAME%, but first erasing and writing system information"
|
||||||
IF NOT "__!FILENAME: =!__"=="__!FILENAME!__" (
|
%ESPTOOL_CMD% --baud 115200 erase_flash
|
||||||
CALL :LOG_MESSAGE ERROR "Filename containing spaces are not supported."
|
%ESPTOOL_CMD% --baud 115200 write_flash 0x00 %FILENAME%
|
||||||
GOTO help
|
|
||||||
|
@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
|
||||||
)
|
)
|
||||||
IF "__!FILENAME:firmware-=!__"=="__!FILENAME!__" (
|
IF %WEB_APP%==1 (
|
||||||
CALL :LOG_MESSAGE ERROR "Filename must be a firmware-* file."
|
for %%f in (littlefswebui-*.bin) do (
|
||||||
GOTO help
|
%ESPTOOL_CMD% --baud 115200 write_flash 0x300000 %%f
|
||||||
|
)
|
||||||
|
) else (
|
||||||
|
for %%f in (littlefs-*.bin) do (
|
||||||
|
%ESPTOOL_CMD% --baud 115200 write_flash 0x300000 %%f
|
||||||
|
)
|
||||||
)
|
)
|
||||||
@REM Remove ".\" or "./" file prefix if present.
|
) else (
|
||||||
SET "FILENAME=!FILENAME:.\=!"
|
echo "Invalid file: %FILENAME%"
|
||||||
SET "FILENAME=!FILENAME:./=!"
|
goto HELP
|
||||||
|
) else (
|
||||||
|
echo "Invalid file: %FILENAME%"
|
||||||
|
goto HELP
|
||||||
)
|
)
|
||||||
|
|
||||||
CALL :LOG_MESSAGE DEBUG "Checking if !FILENAME! exists..."
|
:EOF
|
||||||
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% GTR 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 (
|
|
||||||
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"
|
|
||||||
GOTO tft
|
|
||||||
) ELSE (
|
|
||||||
CALL :LOG_MESSAGE DEBUG "We are NOT working with a *-tft-* file. !FILENAME!"
|
|
||||||
GOTO no_tft
|
|
||||||
)
|
|
||||||
|
|
||||||
:tft
|
|
||||||
SET "TFT8MB=picomputer-s3 unphone seeed-sensecap-indicator"
|
|
||||||
FOR %%a IN (%TFT8MB%) DO (
|
|
||||||
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
|
|
||||||
@REM We are working with any of %TFT8MB%.
|
|
||||||
SET "TFT8=1"
|
|
||||||
GOTO end_loop_tft8mb
|
|
||||||
)
|
|
||||||
)
|
|
||||||
:end_loop_tft8mb
|
|
||||||
|
|
||||||
SET "TFT16MB=t-deck"
|
|
||||||
FOR %%a IN (%TFT16MB%) DO (
|
|
||||||
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
|
|
||||||
@REM We are working with any of %TFT16MB%.
|
|
||||||
SET "TFT16=1"
|
|
||||||
GOTO end_loop_tft16mb
|
|
||||||
)
|
|
||||||
)
|
|
||||||
:end_loop_tft16mb
|
|
||||||
|
|
||||||
IF %TFT8% EQU 1 CALL :LOG_MESSAGE INFO "tft and MUI 8mb selected."
|
|
||||||
IF %TFT16% EQU 1 CALL :LOG_MESSAGE INFO "tft and MUI 16mb selected."
|
|
||||||
|
|
||||||
:no_tft
|
|
||||||
|
|
||||||
@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.
|
|
||||||
SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone"
|
|
||||||
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
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
SET "C3=esp32c3"
|
|
||||||
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 MUI 8mb.
|
|
||||||
IF %TFT8% EQU 1 IF %TFT_BUILD% EQU 1 (
|
|
||||||
SET "OTA_OFFSET=0x340000"
|
|
||||||
SET "SPIFFS_OFFSET=0x670000"
|
|
||||||
)
|
|
||||||
|
|
||||||
@REM Offsets for MUI 16mb.
|
|
||||||
IF %TFT16% EQU 1 IF %TFT_BUILD% 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,21 +1,18 @@
|
|||||||
#!/bin/bash
|
#!/bin/sh
|
||||||
|
|
||||||
PYTHON=${PYTHON:-$(which python3 python | head -n 1)}
|
PYTHON=${PYTHON:-$(which python3 python | head -n 1)}
|
||||||
WEB_APP=false
|
WEB_APP=false
|
||||||
TFT8=false
|
|
||||||
TFT16=false
|
|
||||||
TFT_BUILD=false
|
|
||||||
|
|
||||||
# Determine the correct esptool command to use
|
# Determine the correct esptool command to use
|
||||||
if "$PYTHON" -m esptool version >/dev/null 2>&1; then
|
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
|
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
|
elif command -v esptool.py >/dev/null 2>&1; then
|
||||||
ESPTOOL_CMD="esptool.py"
|
ESPTOOL_CMD="esptool.py"
|
||||||
else
|
else
|
||||||
echo "Error: esptool not found"
|
echo "Error: esptool not found"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
@@ -23,138 +20,75 @@ set -e
|
|||||||
# Usage info
|
# Usage info
|
||||||
show_help() {
|
show_help() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Usage: $(basename $0) [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME] [--web]
|
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.
|
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 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")
|
-P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: "$PYTHON")
|
||||||
-f FILENAME The firmware .bin file to flash. Custom to your device type and region.
|
-f FILENAME The .bin file to flash. Custom to your device type and region.
|
||||||
--web Enable WebUI. (Default: false)
|
--web Flash WEB APP.
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
# Parse arguments using a single while loop
|
# Preprocess long options like --web
|
||||||
while [ $# -gt 0 ]; do
|
for arg in "$@"; do
|
||||||
case "$1" in
|
case "$arg" in
|
||||||
-h | --help)
|
--web)
|
||||||
|
WEB_APP=true
|
||||||
|
shift # Remove this argument from the list
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
while getopts ":hp:P:f:" opt; do
|
||||||
|
case "${opt}" in
|
||||||
|
h)
|
||||||
show_help
|
show_help
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
-p)
|
p)
|
||||||
ESPTOOL_PORT="$2"
|
export ESPTOOL_PORT=${OPTARG}
|
||||||
shift # Shift past the option argument
|
|
||||||
;;
|
;;
|
||||||
-P)
|
P)
|
||||||
PYTHON="$2"
|
PYTHON=${OPTARG}
|
||||||
shift
|
|
||||||
;;
|
;;
|
||||||
-f)
|
f)
|
||||||
FILENAME="$2"
|
FILENAME=${OPTARG}
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--web)
|
|
||||||
WEB_APP=true
|
|
||||||
;;
|
|
||||||
--) # Stop parsing options
|
|
||||||
shift
|
|
||||||
break
|
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Unknown argument: $1" >&2
|
echo "Invalid flag."
|
||||||
|
show_help >&2
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
shift # Move to the next argument
|
|
||||||
done
|
done
|
||||||
|
shift "$((OPTIND - 1))"
|
||||||
|
|
||||||
[ -z "$FILENAME" -a -n "$1" ] && {
|
[ -z "$FILENAME" -a -n "$1" ] && {
|
||||||
FILENAME=$1
|
FILENAME=$1
|
||||||
shift
|
shift
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ $FILENAME != firmware-* ]]; then
|
|
||||||
echo "Filename must be a firmware-* file."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if FILENAME contains "-tft-" and set target partitionScheme accordingly.
|
|
||||||
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
|
|
||||||
|
|
||||||
if [[ $FILENAME == *"picomputer-s3"* || $FILENAME == *"unphone"* || $FILENAME == *"seeed-sensecap-indicator"* ]]; then
|
|
||||||
TFT8=true
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $FILENAME == *"t-deck"* ]]; then
|
|
||||||
TFT16=true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract BASENAME from %FILENAME% for later use.
|
|
||||||
BASENAME="${FILENAME/firmware-/}"
|
|
||||||
|
|
||||||
if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
|
if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
|
||||||
# Default littlefs* offset (--web).
|
|
||||||
OFFSET=0x300000
|
|
||||||
|
|
||||||
# Default OTA Offset
|
|
||||||
OTA_OFFSET=0x260000
|
|
||||||
|
|
||||||
# littlefs* offset for MUI 8mb and OTA OFFSET.
|
|
||||||
if [ "$TFT8" = true ] && [ "$TFT_BUILD" = true ]; then
|
|
||||||
OFFSET=0x670000
|
|
||||||
OTA_OFFSET=0x340000
|
|
||||||
fi
|
|
||||||
|
|
||||||
# littlefs* offset for MUI 16mb and OTA OFFSET.
|
|
||||||
if [ "$TFT16" = true ] && [ "$TFT_BUILD" = true ]; then
|
|
||||||
OFFSET=0xc90000
|
|
||||||
OTA_OFFSET=0x650000
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 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
|
|
||||||
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"
|
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
|
||||||
$ESPTOOL_CMD erase_flash
|
$ESPTOOL_CMD erase_flash
|
||||||
$ESPTOOL_CMD write_flash 0x00 "${FILENAME}"
|
$ESPTOOL_CMD write_flash 0x00 "${FILENAME}"
|
||||||
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
|
# Account for S3 board's different OTA partition
|
||||||
$ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}"
|
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
|
||||||
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
|
if [ -n "${FILENAME##*"esp32c3"*}" ]; then
|
||||||
$ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}"
|
$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
|
||||||
|
|
||||||
else
|
else
|
||||||
show_help
|
show_help
|
||||||
|
|||||||
@@ -1,112 +0,0 @@
|
|||||||
<#
|
|
||||||
.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,175 +1,48 @@
|
|||||||
@ECHO OFF
|
@ECHO OFF
|
||||||
SETLOCAL EnableDelayedExpansion
|
|
||||||
TITLE Meshtastic device-update
|
|
||||||
|
|
||||||
SET "SCRIPT_NAME=%~nx0"
|
set PYTHON=python
|
||||||
SET "DEBUG=0"
|
|
||||||
SET "PYTHON="
|
|
||||||
SET "ESPTOOL_BAUD=115200"
|
|
||||||
SET "ESPTOOL_CMD="
|
|
||||||
SET "LOGCOUNTER=0"
|
|
||||||
|
|
||||||
GOTO getopts
|
:: Determine the correct esptool command to use
|
||||||
:help
|
where esptool >nul 2>&1
|
||||||
ECHO Flash image file to device, but leave existing system intact.
|
if %ERRORLEVEL% EQU 0 (
|
||||||
ECHO.
|
set "ESPTOOL_CMD=esptool"
|
||||||
ECHO Usage: %SCRIPT_NAME% -f filename [-p PORT] [-P python]
|
) else (
|
||||||
ECHO.
|
set "ESPTOOL_CMD=%PYTHON% -m esptool"
|
||||||
ECHO Options:
|
)
|
||||||
ECHO -f filename The .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
|
|
||||||
|
|
||||||
:version
|
goto GETOPTS
|
||||||
ECHO %SCRIPT_NAME% [Version 2.6.0]
|
:HELP
|
||||||
ECHO Meshtastic
|
echo Usage: %~nx0 [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME^|FILENAME]
|
||||||
GOTO eof
|
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
|
||||||
|
|
||||||
:getopts
|
:GETOPTS
|
||||||
IF "%~1"=="" GOTO endopts
|
if /I "%1"=="-h" goto HELP
|
||||||
IF /I "%~1"=="-?" GOTO help
|
if /I "%1"=="--help" goto HELP
|
||||||
IF /I "%~1"=="-h" GOTO help
|
if /I "%1"=="-F" set "FILENAME=%2" & SHIFT
|
||||||
IF /I "%~1"=="--help" GOTO help
|
if /I "%1"=="-p" set ESPTOOL_PORT=%2 & SHIFT
|
||||||
IF /I "%~1"=="-v" GOTO version
|
if /I "%1"=="-P" set PYTHON=%2 & SHIFT
|
||||||
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
|
SHIFT
|
||||||
GOTO getopts
|
IF NOT "__%1__"=="____" goto GETOPTS
|
||||||
:endopts
|
|
||||||
|
|
||||||
CALL :LOG_MESSAGE DEBUG "Checking FILENAME parameter..."
|
IF "__%FILENAME%__" == "____" (
|
||||||
IF "__!FILENAME!__"=="____" (
|
echo "Missing FILENAME"
|
||||||
CALL :LOG_MESSAGE DEBUG "Missing -f filename input."
|
goto HELP
|
||||||
GOTO help
|
)
|
||||||
) ELSE (
|
IF EXIST %FILENAME% IF NOT x%FILENAME:update=%==x%FILENAME% (
|
||||||
IF NOT "__!FILENAME: =!__"=="__!FILENAME!__" (
|
echo Trying to flash update %FILENAME%
|
||||||
CALL :LOG_MESSAGE ERROR "Filename containing spaces are not supported."
|
%ESPTOOL_CMD% --baud 115200 write_flash 0x10000 %FILENAME%
|
||||||
GOTO help
|
) else (
|
||||||
)
|
echo "Invalid file: %FILENAME%"
|
||||||
@REM Remove ".\" or "./" file prefix if present.
|
goto HELP
|
||||||
SET "FILENAME=!FILENAME:.\=!"
|
) else (
|
||||||
SET "FILENAME=!FILENAME:./=!"
|
echo "Invalid file: %FILENAME%"
|
||||||
|
goto HELP
|
||||||
)
|
)
|
||||||
|
|
||||||
CALL :LOG_MESSAGE DEBUG "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 update !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% GTR 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 (
|
|
||||||
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
|
|
||||||
|
|||||||
@@ -125,9 +125,4 @@ for flag in flags:
|
|||||||
|
|
||||||
projenv.Append(
|
projenv.Append(
|
||||||
CCFLAGS=flags,
|
CCFLAGS=flags,
|
||||||
)
|
)
|
||||||
|
|
||||||
for lb in env.GetLibBuilders():
|
|
||||||
if lb.name == "meshtastic-device-ui":
|
|
||||||
lb.env.Append(CPPDEFINES=[("APP_VERSION", verObj["long"])])
|
|
||||||
break
|
|
||||||
@@ -1,10 +1 @@
|
|||||||
@ECHO OFF
|
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
|
||||||
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,124 +1,2 @@
|
|||||||
@ECHO OFF
|
@echo off
|
||||||
SETLOCAL EnableDelayedExpansion
|
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)
|
||||||
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,15 +7,13 @@
|
|||||||
"core": "esp32",
|
"core": "esp32",
|
||||||
"extra_flags": [
|
"extra_flags": [
|
||||||
"-DARDUINO_ESP32S3_DEV",
|
"-DARDUINO_ESP32S3_DEV",
|
||||||
|
"-DARDUINO_USB_MODE=1",
|
||||||
"-DARDUINO_RUNNING_CORE=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_cpu": "240000000L",
|
||||||
"f_flash": "80000000L",
|
"f_flash": "80000000L",
|
||||||
"flash_mode": "qio",
|
"flash_mode": "qio",
|
||||||
"psram_type": "qio",
|
|
||||||
"hwids": [["0x303A", "0x1001"]],
|
"hwids": [["0x303A", "0x1001"]],
|
||||||
"mcu": "esp32s3",
|
"mcu": "esp32s3",
|
||||||
"variant": "esp32s3"
|
"variant": "esp32s3"
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
40
boards/wiscore_rak11300.json
Normal file
40
boards/wiscore_rak11300.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
1
debian/control
vendored
1
debian/control
vendored
@@ -17,7 +17,6 @@ Build-Depends: debhelper-compat (= 13),
|
|||||||
libbluetooth-dev,
|
libbluetooth-dev,
|
||||||
libusb-1.0-0-dev,
|
libusb-1.0-0-dev,
|
||||||
libi2c-dev,
|
libi2c-dev,
|
||||||
libuv1-dev,
|
|
||||||
openssl,
|
openssl,
|
||||||
libssl-dev,
|
libssl-dev,
|
||||||
libulfius-dev,
|
libulfius-dev,
|
||||||
|
|||||||
1
lib/device-ui
Submodule
1
lib/device-ui
Submodule
Submodule lib/device-ui added at cbe5c14e8a
@@ -36,7 +36,6 @@ BuildRequires: pkgconfig(libgpiod)
|
|||||||
BuildRequires: pkgconfig(bluez)
|
BuildRequires: pkgconfig(bluez)
|
||||||
BuildRequires: pkgconfig(libusb-1.0)
|
BuildRequires: pkgconfig(libusb-1.0)
|
||||||
BuildRequires: libi2c-devel
|
BuildRequires: libi2c-devel
|
||||||
BuildRequires: pkgconfig(libuv)
|
|
||||||
# Web components:
|
# Web components:
|
||||||
BuildRequires: pkgconfig(openssl)
|
BuildRequires: pkgconfig(openssl)
|
||||||
BuildRequires: pkgconfig(liborcania)
|
BuildRequires: pkgconfig(liborcania)
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ default_envs = tbeam
|
|||||||
extra_configs =
|
extra_configs =
|
||||||
arch/*/*.ini
|
arch/*/*.ini
|
||||||
variants/*/platformio.ini
|
variants/*/platformio.ini
|
||||||
src/graphics/niche/InkHUD/PlatformioConfig.ini
|
|
||||||
|
|
||||||
description = Meshtastic
|
description = Meshtastic
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
@@ -60,7 +58,7 @@ lib_deps =
|
|||||||
mathertel/OneButton@2.6.1
|
mathertel/OneButton@2.6.1
|
||||||
https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159
|
https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159
|
||||||
https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4
|
https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4
|
||||||
https://github.com/meshtastic/ArduinoThread.git#7c3ee9e1951551b949763b1f5280f8db1fa4068d
|
https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0
|
||||||
nanopb/Nanopb@0.4.91
|
nanopb/Nanopb@0.4.91
|
||||||
erriez/ErriezCRC32@1.0.1
|
erriez/ErriezCRC32@1.0.1
|
||||||
|
|
||||||
@@ -79,7 +77,7 @@ lib_deps =
|
|||||||
${env.lib_deps}
|
${env.lib_deps}
|
||||||
end2endzone/NonBlockingRTTTL@1.3.0
|
end2endzone/NonBlockingRTTTL@1.3.0
|
||||||
build_flags = ${env.build_flags} -Os
|
build_flags = ${env.build_flags} -Os
|
||||||
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/>
|
build_src_filter = ${env.build_src_filter} -<platform/portduino/>
|
||||||
|
|
||||||
; Common libs for communicating over TCP/IP networks such as MQTT
|
; Common libs for communicating over TCP/IP networks such as MQTT
|
||||||
[networking_base]
|
[networking_base]
|
||||||
@@ -92,10 +90,6 @@ lib_deps =
|
|||||||
lib_deps =
|
lib_deps =
|
||||||
jgromes/RadioLib@7.1.2
|
jgromes/RadioLib@7.1.2
|
||||||
|
|
||||||
[device-ui_base]
|
|
||||||
lib_deps =
|
|
||||||
https://github.com/meshtastic/device-ui.git#74e739ed4532ca10393df9fc89ae5a22f0bab2b1
|
|
||||||
|
|
||||||
; Common libs for environmental measurements in telemetry module
|
; Common libs for environmental measurements in telemetry module
|
||||||
; (not included in native / portduino)
|
; (not included in native / portduino)
|
||||||
[environmental_base]
|
[environmental_base]
|
||||||
@@ -106,7 +100,6 @@ lib_deps =
|
|||||||
adafruit/Adafruit BMP085 Library@1.2.4
|
adafruit/Adafruit BMP085 Library@1.2.4
|
||||||
adafruit/Adafruit BME280 Library@2.2.4
|
adafruit/Adafruit BME280 Library@2.2.4
|
||||||
adafruit/Adafruit BMP3XX Library@2.1.5
|
adafruit/Adafruit BMP3XX Library@2.1.5
|
||||||
adafruit/Adafruit DPS310@1.1.5
|
|
||||||
adafruit/Adafruit MCP9808 Library@2.0.2
|
adafruit/Adafruit MCP9808 Library@2.0.2
|
||||||
adafruit/Adafruit INA260 Library@1.5.2
|
adafruit/Adafruit INA260 Library@1.5.2
|
||||||
adafruit/Adafruit INA219@1.2.3
|
adafruit/Adafruit INA219@1.2.3
|
||||||
|
|||||||
Submodule protobufs updated: 14ec205865...2a3a67f043
@@ -30,7 +30,7 @@ class BluetoothStatus : public Status
|
|||||||
BluetoothStatus() { statusType = STATUS_TYPE_BLUETOOTH; }
|
BluetoothStatus() { statusType = STATUS_TYPE_BLUETOOTH; }
|
||||||
|
|
||||||
// New BluetoothStatus: connected or disconnected
|
// New BluetoothStatus: connected or disconnected
|
||||||
explicit BluetoothStatus(ConnectionState state)
|
BluetoothStatus(ConnectionState state)
|
||||||
{
|
{
|
||||||
assert(state != ConnectionState::PAIRING); // If pairing, use constructor which specifies passkey
|
assert(state != ConnectionState::PAIRING); // If pairing, use constructor which specifies passkey
|
||||||
statusType = STATUS_TYPE_BLUETOOTH;
|
statusType = STATUS_TYPE_BLUETOOTH;
|
||||||
@@ -38,7 +38,7 @@ class BluetoothStatus : public Status
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New BluetoothStatus: pairing, with passkey
|
// New BluetoothStatus: pairing, with passkey
|
||||||
explicit BluetoothStatus(const std::string &passkey) : Status()
|
BluetoothStatus(std::string passkey) : Status()
|
||||||
{
|
{
|
||||||
statusType = STATUS_TYPE_BLUETOOTH;
|
statusType = STATUS_TYPE_BLUETOOTH;
|
||||||
this->state = ConnectionState::PAIRING;
|
this->state = ConnectionState::PAIRING;
|
||||||
|
|||||||
@@ -121,15 +121,10 @@ extern "C" void logLegacy(const char *level, const char *fmt, ...);
|
|||||||
// Default Bluetooth PIN
|
// Default Bluetooth PIN
|
||||||
#define defaultBLEPin 123456
|
#define defaultBLEPin 123456
|
||||||
|
|
||||||
#if HAS_ETHERNET && !defined(USE_WS5500)
|
#if HAS_ETHERNET
|
||||||
#include <RAK13800_W5100S.h>
|
#include <RAK13800_W5100S.h>
|
||||||
#endif // HAS_ETHERNET
|
#endif // HAS_ETHERNET
|
||||||
|
|
||||||
#if HAS_ETHERNET && defined(USE_WS5500)
|
|
||||||
#include <ETHClass2.h>
|
|
||||||
#define ETH ETH2
|
|
||||||
#endif // HAS_ETHERNET
|
|
||||||
|
|
||||||
#if HAS_WIFI
|
#if HAS_WIFI
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#endif // HAS_WIFI
|
#endif // HAS_WIFI
|
||||||
@@ -169,4 +164,4 @@ class Syslog
|
|||||||
bool vlogf(uint16_t pri, const char *appName, const char *fmt, va_list args) __attribute__((format(printf, 3, 0)));
|
bool vlogf(uint16_t pri, const char *appName, const char *fmt, va_list args) __attribute__((format(printf, 3, 0)));
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // HAS_NETWORKING
|
#endif // HAS_ETHERNET || HAS_WIFI
|
||||||
@@ -32,11 +32,6 @@
|
|||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if HAS_ETHERNET && defined(USE_WS5500)
|
|
||||||
#include <ETHClass2.h>
|
|
||||||
#define ETH ETH2
|
|
||||||
#endif // HAS_ETHERNET
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef DELAY_FOREVER
|
#ifndef DELAY_FOREVER
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ static File openFile(const char *filename, bool fullAtomic)
|
|||||||
{
|
{
|
||||||
concurrency::LockGuard g(spiLock);
|
concurrency::LockGuard g(spiLock);
|
||||||
LOG_DEBUG("Opening %s, fullAtomic=%d", filename, fullAtomic);
|
LOG_DEBUG("Opening %s, fullAtomic=%d", filename, fullAtomic);
|
||||||
|
|
||||||
#ifdef ARCH_NRF52
|
#ifdef ARCH_NRF52
|
||||||
FSCom.remove(filename);
|
FSCom.remove(filename);
|
||||||
return FSCom.open(filename, FILE_O_WRITE);
|
return FSCom.open(filename, FILE_O_WRITE);
|
||||||
@@ -18,10 +19,10 @@ static File openFile(const char *filename, bool fullAtomic)
|
|||||||
String filenameTmp = filename;
|
String filenameTmp = filename;
|
||||||
filenameTmp += ".tmp";
|
filenameTmp += ".tmp";
|
||||||
|
|
||||||
// FIXME: If we are doing a full atomic write, we may need to remove the old tmp file now
|
// If we are doing a full atomic write, remove the old tmp file now
|
||||||
// if (fullAtomic) {
|
if (fullAtomic) {
|
||||||
// FSCom.remove(filename);
|
FSCom.remove(filename);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// clear any previous LFS errors
|
// clear any previous LFS errors
|
||||||
return FSCom.open(filenameTmp.c_str(), FILE_O_WRITE);
|
return FSCom.open(filenameTmp.c_str(), FILE_O_WRITE);
|
||||||
|
|||||||
@@ -135,7 +135,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#define LPS22HB_ADDR 0x5C
|
#define LPS22HB_ADDR 0x5C
|
||||||
#define LPS22HB_ADDR_ALT 0x5D
|
#define LPS22HB_ADDR_ALT 0x5D
|
||||||
#define SHT31_4x_ADDR 0x44
|
#define SHT31_4x_ADDR 0x44
|
||||||
#define SHT31_4x_ADDR_ALT 0x45
|
|
||||||
#define PMSA0031_ADDR 0x12
|
#define PMSA0031_ADDR 0x12
|
||||||
#define QMA6100P_ADDR 0x12
|
#define QMA6100P_ADDR 0x12
|
||||||
#define AHT10_ADDR 0x38
|
#define AHT10_ADDR 0x38
|
||||||
@@ -151,7 +150,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#define MAX30102_ADDR 0x57
|
#define MAX30102_ADDR 0x57
|
||||||
#define MLX90614_ADDR_DEF 0x5A
|
#define MLX90614_ADDR_DEF 0x5A
|
||||||
#define CGRADSENS_ADDR 0x66
|
#define CGRADSENS_ADDR 0x66
|
||||||
#define LTR390UV_ADDR 0x53
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// ACCELEROMETER
|
// ACCELEROMETER
|
||||||
|
|||||||
@@ -67,8 +67,6 @@ class ScanI2C
|
|||||||
INA226,
|
INA226,
|
||||||
NXP_SE050,
|
NXP_SE050,
|
||||||
DFROBOT_RAIN,
|
DFROBOT_RAIN,
|
||||||
DPS310,
|
|
||||||
LTR390UV,
|
|
||||||
} DeviceType;
|
} DeviceType;
|
||||||
|
|
||||||
// typedef uint8_t DeviceAddress;
|
// typedef uint8_t DeviceAddress;
|
||||||
|
|||||||
@@ -237,16 +237,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||||||
logFoundDevice("BMP085/BMP180", (uint8_t)addr.address);
|
logFoundDevice("BMP085/BMP180", (uint8_t)addr.address);
|
||||||
type = BMP_085;
|
type = BMP_085;
|
||||||
break;
|
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:
|
default:
|
||||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); // GET_ID
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); // GET_ID
|
||||||
switch (registerValue) {
|
switch (registerValue) {
|
||||||
@@ -349,8 +339,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT
|
case SHT31_4x_ADDR:
|
||||||
case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
|
|
||||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
|
||||||
if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c) {
|
if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c) {
|
||||||
type = SHT4X;
|
type = SHT4X;
|
||||||
@@ -423,11 +412,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(TCA9555_ADDR, TCA9555, "TCA9555", (uint8_t)addr.address);
|
||||||
SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700", (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(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(MLX90632_ADDR, MLX90632, "MLX90632", (uint8_t)addr.address);
|
||||||
SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802", (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(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(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
|
#ifdef HAS_TPS65233
|
||||||
SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233", (uint8_t)addr.address);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
#include <cstring> // Include for strstr
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#if !MESHTASTIC_EXCLUDE_GPS
|
#if !MESHTASTIC_EXCLUDE_GPS
|
||||||
#include "Default.h"
|
#include "Default.h"
|
||||||
@@ -1104,16 +1100,12 @@ int32_t GPS::runOnce()
|
|||||||
return (powerState == GPS_ACTIVE) ? GPS_THREAD_INTERVAL : 5000;
|
return (powerState == GPS_ACTIVE) ? GPS_THREAD_INTERVAL : 5000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear the GPS rx/tx buffer as quickly as possible
|
// clear the GPS rx buffer as quickly as possible
|
||||||
void GPS::clearBuffer()
|
void GPS::clearBuffer()
|
||||||
{
|
{
|
||||||
#ifdef ARCH_ESP32
|
|
||||||
_serial_gps->flush(false);
|
|
||||||
#else
|
|
||||||
int x = _serial_gps->available();
|
int x = _serial_gps->available();
|
||||||
while (x--)
|
while (x--)
|
||||||
_serial_gps->read();
|
_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
|
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
||||||
@@ -1125,7 +1117,7 @@ int GPS::prepareDeepSleep(void *unused)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static const char *PROBE_MESSAGE = "Trying %s (%s)...";
|
static const char *PROBE_MESSAGE = "Trying %s (%s)...";
|
||||||
static const char *DETECTED_MESSAGE = "%s detected";
|
static const char *DETECTED_MESSAGE = "%s detected, using %s Module";
|
||||||
|
|
||||||
#define PROBE_SIMPLE(CHIP, TOWRITE, RESPONSE, DRIVER, TIMEOUT, ...) \
|
#define PROBE_SIMPLE(CHIP, TOWRITE, RESPONSE, DRIVER, TIMEOUT, ...) \
|
||||||
do { \
|
do { \
|
||||||
@@ -1133,22 +1125,11 @@ static const char *DETECTED_MESSAGE = "%s detected";
|
|||||||
clearBuffer(); \
|
clearBuffer(); \
|
||||||
_serial_gps->write(TOWRITE "\r\n"); \
|
_serial_gps->write(TOWRITE "\r\n"); \
|
||||||
if (getACK(RESPONSE, TIMEOUT) == GNSS_RESPONSE_OK) { \
|
if (getACK(RESPONSE, TIMEOUT) == GNSS_RESPONSE_OK) { \
|
||||||
LOG_INFO(DETECTED_MESSAGE, CHIP); \
|
LOG_INFO(DETECTED_MESSAGE, CHIP, #DRIVER); \
|
||||||
return DRIVER; \
|
return DRIVER; \
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} 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)
|
GnssModel_t GPS::probe(int serialSpeed)
|
||||||
{
|
{
|
||||||
#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
|
#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
|
||||||
@@ -1179,34 +1160,31 @@ GnssModel_t GPS::probe(int serialSpeed)
|
|||||||
delay(20);
|
delay(20);
|
||||||
|
|
||||||
// Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A
|
// Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A
|
||||||
std::vector<ChipInfo> unicore = {{"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}};
|
PROBE_SIMPLE("UC6580", "$PDTINFO", "UC6580", GNSS_MODEL_UC6580, 500);
|
||||||
PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 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);
|
||||||
std::vector<ChipInfo> atgm = {
|
/* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS))
|
||||||
{"ATGM336H", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H},
|
based on AT6558 */
|
||||||
/* 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);
|
||||||
{"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 */
|
/* 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,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("$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume
|
||||||
_serial_gps->write("$PAIR513*3D\r\n"); // save configuration
|
_serial_gps->write("$PAIR513*3D\r\n"); // save configuration
|
||||||
std::vector<ChipInfo> airoha = {{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335},
|
PROBE_SIMPLE("AG3335", "$PAIR021*39", "$PAIR021,AG3335", GNSS_MODEL_AG3335, 500);
|
||||||
{"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
|
PROBE_SIMPLE("AG3352", "$PAIR021*39", "$PAIR021,AG3352", GNSS_MODEL_AG3352, 500);
|
||||||
{"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("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500);
|
||||||
|
|
||||||
PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 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 L76B MTK platform (Waveshare Pico GPS)
|
||||||
_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");
|
_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);
|
delay(20);
|
||||||
std::vector<ChipInfo> mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B},
|
|
||||||
{"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S},
|
PROBE_SIMPLE("L76B", "$PMTK605*31", "Quectel-L76B", GNSS_MODEL_MTK_L76B, 500);
|
||||||
{"LS20031", "MC-1513", GNSS_MODEL_LS20031}};
|
PROBE_SIMPLE("PA1616S", "$PMTK605*31", "1616S", GNSS_MODEL_MTK_PA1616S, 500);
|
||||||
PROBE_FAMILY("MTK Family", "$PMTK605*31", mtk, 500);
|
|
||||||
|
PROBE_SIMPLE("LS20031", "$PMTK605*31", "MC-1513", GNSS_MODEL_LS20031, 500);
|
||||||
|
|
||||||
uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
|
uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
|
||||||
UBXChecksum(cfg_rate, sizeof(cfg_rate));
|
UBXChecksum(cfg_rate, sizeof(cfg_rate));
|
||||||
@@ -1303,38 +1281,6 @@ GnssModel_t GPS::probe(int serialSpeed)
|
|||||||
return GNSS_MODEL_UNKNOWN;
|
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()
|
GPS *GPS::createGps()
|
||||||
{
|
{
|
||||||
int8_t _rx_gpio = config.position.rx_gpio;
|
int8_t _rx_gpio = config.position.rx_gpio;
|
||||||
|
|||||||
@@ -48,11 +48,6 @@ enum GPSPowerState : uint8_t {
|
|||||||
GPS_OFF // Powered off indefinitely
|
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
|
* A gps class that only reads from the GPS periodically and keeps the gps powered down except when reading
|
||||||
*
|
*
|
||||||
@@ -235,8 +230,6 @@ class GPS : private concurrency::OSThread
|
|||||||
|
|
||||||
virtual int32_t runOnce() override;
|
virtual int32_t runOnce() override;
|
||||||
|
|
||||||
GnssModel_t getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap);
|
|
||||||
|
|
||||||
// Get GNSS model
|
// Get GNSS model
|
||||||
GnssModel_t probe(int serialSpeed);
|
GnssModel_t probe(int serialSpeed);
|
||||||
|
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ bool EInkDisplay::connect()
|
|||||||
}
|
}
|
||||||
|
|
||||||
#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) || \
|
#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(CROWPANEL_ESP32S3_5_EPAPER)
|
defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER)
|
||||||
{
|
{
|
||||||
// Start HSPI
|
// Start HSPI
|
||||||
hspi = new SPIClass(HSPI);
|
hspi = new SPIClass(HSPI);
|
||||||
@@ -182,9 +182,6 @@ bool EInkDisplay::connect()
|
|||||||
// Init GxEPD2
|
// Init GxEPD2
|
||||||
adafruitDisplay->init();
|
adafruitDisplay->init();
|
||||||
adafruitDisplay->setRotation(3);
|
adafruitDisplay->setRotation(3);
|
||||||
#if defined(CROWPANEL_ESP32S3_5_EPAPER)
|
|
||||||
adafruitDisplay->setRotation(0);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#elif defined(PCA10059) || defined(ME25LS01)
|
#elif defined(PCA10059) || defined(ME25LS01)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class EInkDisplay : public OLEDDisplay
|
|||||||
|
|
||||||
// If display uses HSPI
|
// If display uses HSPI
|
||||||
#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \
|
#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(CROWPANEL_ESP32S3_5_EPAPER)
|
defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER)
|
||||||
SPIClass *hspi = NULL;
|
SPIClass *hspi = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -77,4 +77,4 @@ class EInkDisplay : public OLEDDisplay
|
|||||||
uint32_t lastDrawMsec = 0;
|
uint32_t lastDrawMsec = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -324,14 +324,6 @@ void EInkDynamicDisplay::checkConsecutiveFastRefreshes()
|
|||||||
if (refresh != UNSPECIFIED)
|
if (refresh != UNSPECIFIED)
|
||||||
return;
|
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 too many FAST refreshes consecutively - force a FULL refresh
|
||||||
if (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) {
|
if (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) {
|
||||||
refresh = FULL;
|
refresh = FULL;
|
||||||
|
|||||||
@@ -23,10 +23,6 @@ class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWo
|
|||||||
EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus);
|
EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus);
|
||||||
~EInkDynamicDisplay();
|
~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
|
// What kind of frame is this
|
||||||
enum frameFlagTypes : uint8_t {
|
enum frameFlagTypes : uint8_t {
|
||||||
BACKGROUND = (1 << 0), // For frames via display()
|
BACKGROUND = (1 << 0), // For frames via display()
|
||||||
@@ -34,7 +30,6 @@ class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWo
|
|||||||
COSMETIC = (1 << 2), // For splashes
|
COSMETIC = (1 << 2), // For splashes
|
||||||
DEMAND_FAST = (1 << 3), // Special case only
|
DEMAND_FAST = (1 << 3), // Special case only
|
||||||
BLOCKING = (1 << 4), // Modifier - block while refresh runs
|
BLOCKING = (1 << 4), // Modifier - block while refresh runs
|
||||||
UNLIMITED_FAST = (1 << 5)
|
|
||||||
};
|
};
|
||||||
void addFrameFlag(frameFlagTypes flag);
|
void addFrameFlag(frameFlagTypes flag);
|
||||||
|
|
||||||
|
|||||||
@@ -73,16 +73,6 @@
|
|||||||
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
|
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
|
||||||
#endif
|
#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 _fontHeight(font) ((font)[1] + 1) // height is position 1
|
||||||
|
|
||||||
#define FONT_HEIGHT_SMALL _fontHeight(FONT_SMALL)
|
#define FONT_HEIGHT_SMALL _fontHeight(FONT_SMALL)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +0,0 @@
|
|||||||
#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,11 +40,13 @@ void LatchingBacklight::setPin(uint8_t pin, bool activeWhen)
|
|||||||
// Ensures the backlight is off
|
// Ensures the backlight is off
|
||||||
int LatchingBacklight::beforeDeepSleep(void *unused)
|
int LatchingBacklight::beforeDeepSleep(void *unused)
|
||||||
{
|
{
|
||||||
// Contingency only
|
// We shouldn't need to guard the block like this
|
||||||
// - pin wasn't set
|
// Contingency for:
|
||||||
|
// - settings corruption: settings.optionalMenuItems.backlight guards backlight code in MenuApplet
|
||||||
|
// - improper use in the future
|
||||||
if (pin != (uint8_t)-1) {
|
if (pin != (uint8_t)-1) {
|
||||||
off();
|
off();
|
||||||
pinMode(pin, INPUT); // High impedance - unnecessary?
|
pinMode(pin, INPUT); // High impedence - unnecessary?
|
||||||
} else
|
} else
|
||||||
LOG_WARN("LatchingBacklight instantiated, but pin not set");
|
LOG_WARN("LatchingBacklight instantiated, but pin not set");
|
||||||
return 0; // Continue with deep sleep
|
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.
|
// Used by NicheGraphics implementations to check if a display supports a specific refresh operation.
|
||||||
// Whether or not the update type is supported is specified in the constructor
|
// Whether or the update type is supported is specified in the constructor
|
||||||
bool EInk::supports(UpdateTypes type)
|
bool EInk::supports(UpdateTypes type)
|
||||||
{
|
{
|
||||||
// The EInkUpdateTypes enum assigns each type a unique bit. We are checking if that bit is set.
|
// 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 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
|
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
|
void await(); // Wait for an in-progress update to complete before proceeding
|
||||||
bool supports(UpdateTypes type); // Can display perform a certain update type
|
bool supports(UpdateTypes type); // Can display perfom a certain update type
|
||||||
bool busy() { return updateRunning; } // Display able to update right now?
|
bool busy() { return updateRunning; } // Display able to update right now?
|
||||||
|
|
||||||
const uint16_t width; // Public so that NicheGraphics implementations can access. Safe because const.
|
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
|
const UpdateTypes supportedUpdateTypes; // Capabilities of a derived display class
|
||||||
bool updateRunning = false; // see EInk::busy()
|
bool updateRunning = false; // see EInk::busy()
|
||||||
uint32_t updateBegunAt = 0; // For initial pause before polling for update completion
|
uint32_t updateBegunAt; // For initial pause before polling for update completion
|
||||||
uint32_t pollingInterval = 0; // How often to check if update complete (ms)
|
uint32_t pollingInterval; // How often to check if update complete (ms)
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace NicheGraphics::Drivers
|
} // namespace NicheGraphics::Drivers
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
using namespace NicheGraphics::Drivers;
|
using namespace NicheGraphics::Drivers;
|
||||||
|
|
||||||
// Map the display controller IC's output to the connected panel
|
// Map the display controller IC's output to the conected panel
|
||||||
void GDEY0154D67::configScanning()
|
void GDEY0154D67::configScanning()
|
||||||
{
|
{
|
||||||
// "Driver output control"
|
// "Driver output control"
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ void LCMEN213EFC1::begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t
|
|||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display an image on the display
|
|
||||||
void LCMEN213EFC1::update(uint8_t *imageData, UpdateTypes type)
|
void LCMEN213EFC1::update(uint8_t *imageData, UpdateTypes type)
|
||||||
{
|
{
|
||||||
this->updateType = type;
|
this->updateType = type;
|
||||||
@@ -162,6 +161,13 @@ void LCMEN213EFC1::sendCommand(const uint8_t command)
|
|||||||
|
|
||||||
void LCMEN213EFC1::sendData(uint8_t data)
|
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);
|
sendData(&data, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,24 +45,21 @@ class LCMEN213EFC1 : public EInk
|
|||||||
void configFull(); // Configure display for FULL refresh
|
void configFull(); // Configure display for FULL refresh
|
||||||
void configFast(); // Configure display for FAST refresh
|
void configFast(); // Configure display for FAST refresh
|
||||||
void writeNewImage();
|
void writeNewImage();
|
||||||
void writeOldImage(); // Used for "differential update", aka FAST refresh
|
void writeOldImage();
|
||||||
|
|
||||||
void detachFromUpdate();
|
void detachFromUpdate();
|
||||||
bool isUpdateDone();
|
bool isUpdateDone();
|
||||||
void finalizeUpdate();
|
void finalizeUpdate();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
uint8_t bufferOffsetX = 0; // In bytes. Panel x=0 does not always align with controller x=0. Quirky internal wiring?
|
uint8_t bufferOffsetX; // 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)
|
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 = 0; // In bytes. Rows * Columns
|
uint32_t bufferSize; // In bytes. Rows * Columns
|
||||||
uint8_t *buffer = nullptr;
|
uint8_t *buffer;
|
||||||
UpdateTypes updateType = UpdateTypes::UNSPECIFIED;
|
UpdateTypes updateType;
|
||||||
|
|
||||||
uint8_t pin_dc = -1;
|
uint8_t pin_dc, pin_cs, pin_busy, pin_rst;
|
||||||
uint8_t pin_cs = -1;
|
SPIClass *spi;
|
||||||
uint8_t pin_busy = -1;
|
|
||||||
uint8_t pin_rst = -1;
|
|
||||||
SPIClass *spi = nullptr;
|
|
||||||
SPISettings spiSettings = SPISettings(6000000, MSBFIRST, SPI_MODE0);
|
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.
|
A driver for E-Ink SPI displays. Suitable for re-use by various NicheGraphics UIs.
|
||||||
|
|
||||||
Your UI should use the class `NicheGraphics::Drivers::EInk` .
|
Your UI should use the class `NicheGraphics::Drivers::EInk` .
|
||||||
When you set up a hardware variant, you will use one of the specific display model classes, which extend the EInk class.
|
When you set up a hardware variant, you will use one of specific display model classes, which extend the EInk class.
|
||||||
|
|
||||||
An example setup might look like this:
|
An example setup might look like this:
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ void setupNicheGraphics()
|
|||||||
|
|
||||||
## Methods
|
## Methods
|
||||||
|
|
||||||
### `update(uint8_t *imageData, UpdateTypes type)`
|
### `update(uint8_t *imageData, UpdateTypes type, bool async=true)`
|
||||||
|
|
||||||
Update the image on the display
|
Update the image on the display
|
||||||
|
|
||||||
@@ -39,6 +39,7 @@ Update the image on the display
|
|||||||
- `FULL`
|
- `FULL`
|
||||||
- `FAST`
|
- `FAST`
|
||||||
- (Other custom types may be possible)
|
- (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.
|
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.
|
||||||
|
|
||||||
@@ -62,10 +63,6 @@ uint8_t xBits = (7-x) % 8;
|
|||||||
image[yByte + xByte] |= (1 << xBits); // Set pixel x=12, y=2
|
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)`
|
### `supports(UpdateTypes type)`
|
||||||
|
|
||||||
Check if display supports a specific update type. `true` if supported.
|
Check if display supports a specific update type. `true` if supported.
|
||||||
@@ -78,7 +75,7 @@ Check if display is already performing an `update()`. `true` if already updating
|
|||||||
|
|
||||||
### `width()`
|
### `width()`
|
||||||
|
|
||||||
Width of the display, in pixels. Note: most displays are portrait. Your UI will need to implement rotation in software.
|
Width of the display, in pixels. Note: most displays are portait. Your UI will need to implement rotation in software.
|
||||||
|
|
||||||
### `height()`
|
### `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);
|
pinMode(pin_busy, INPUT);
|
||||||
|
|
||||||
// If using a reset pin, hold high
|
// If using a reset pin, hold high
|
||||||
// Reset is active low for Solomon Systech ICs
|
// Reset is active low for solmon systech ICs
|
||||||
if (pin_rst != 0xFF)
|
if (pin_rst != 0xFF)
|
||||||
pinMode(pin_rst, INPUT_PULLUP);
|
pinMode(pin_rst, INPUT_PULLUP);
|
||||||
|
|
||||||
@@ -72,6 +72,13 @@ void SSD16XX::sendCommand(const uint8_t command)
|
|||||||
|
|
||||||
void SSD16XX::sendData(uint8_t data)
|
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);
|
sendData(&data, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,24 +39,21 @@ class SSD16XX : public EInk
|
|||||||
virtual void configUpdateSequence(); // Tell controller IC which operations to run
|
virtual void configUpdateSequence(); // Tell controller IC which operations to run
|
||||||
|
|
||||||
virtual void writeNewImage();
|
virtual void writeNewImage();
|
||||||
virtual void writeOldImage(); // Image which can be used at *next* update for "differential refresh"
|
virtual void writeOldImage();
|
||||||
|
|
||||||
virtual void detachFromUpdate();
|
virtual void detachFromUpdate();
|
||||||
virtual bool isUpdateDone() override;
|
virtual bool isUpdateDone() override;
|
||||||
virtual void finalizeUpdate() override;
|
virtual void finalizeUpdate() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
uint8_t bufferOffsetX = 0; // In bytes. Panel x=0 does not always align with controller x=0. Quirky internal wiring?
|
uint8_t bufferOffsetX; // 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)
|
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 = 0; // In bytes. Rows * Columns
|
uint32_t bufferSize; // In bytes. Rows * Columns
|
||||||
uint8_t *buffer = nullptr;
|
uint8_t *buffer;
|
||||||
UpdateTypes updateType = UpdateTypes::UNSPECIFIED;
|
UpdateTypes updateType;
|
||||||
|
|
||||||
uint8_t pin_dc = -1;
|
uint8_t pin_dc, pin_cs, pin_busy, pin_rst;
|
||||||
uint8_t pin_cs = -1;
|
SPIClass *spi;
|
||||||
uint8_t pin_busy = -1;
|
|
||||||
uint8_t pin_rst = -1;
|
|
||||||
SPIClass *spi = nullptr;
|
|
||||||
SPISettings spiSettings = SPISettings(4000000, MSBFIRST, SPI_MODE0);
|
SPISettings spiSettings = SPISettings(4000000, MSBFIRST, SPI_MODE0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
# NicheGraphics - Drivers
|
# NicheGraphics - Drivers
|
||||||
|
|
||||||
Common drivers which can be used by various NicheGraphics UIs
|
Common drivers which can be used by various NicheGrapihcs UIs
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ template <typename T> class FlashData
|
|||||||
// Calculate a hash of the data
|
// Calculate a hash of the data
|
||||||
uint32_t hash = getHash(data);
|
uint32_t hash = getHash(data);
|
||||||
|
|
||||||
f.write((uint8_t *)data, sizeof(T)); // Write the actual data
|
f.write((uint8_t *)data, sizeof(T)); // Write the actualy data
|
||||||
f.write((uint8_t *)&hash, sizeof(hash)); // Append the hash
|
f.write((uint8_t *)&hash, sizeof(hash)); // Append the hash
|
||||||
|
|
||||||
// f.flush();
|
// 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
|
https://en.wikipedia.org/wiki/Windows-1251
|
||||||
|
|
||||||
Cyrillic characters present to the firmware as UTF8.
|
Cyrillic characters present to the firmware as UTF8.
|
||||||
A NicheGraphics implementation needs to identify these, and substitute the appropriate Windows-1251 char value.
|
A Niche Graphics implementation needs to identify these, and subsitute the appropriate Windows-1251 char value.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
#include "./Applet.h"
|
#include "./Applet.h"
|
||||||
|
|
||||||
#include "main.h"
|
|
||||||
|
|
||||||
#include "RTC.h"
|
#include "RTC.h"
|
||||||
|
|
||||||
using namespace NicheGraphics;
|
using namespace NicheGraphics;
|
||||||
@@ -18,15 +16,10 @@ InkHUD::Applet::Applet() : GFX(0, 0)
|
|||||||
// The width and height will change dynamically, depending on Applet tiling
|
// The width and height will change dynamically, depending on Applet tiling
|
||||||
// If you're getting a "divide by zero error", consider it an assert:
|
// If you're getting a "divide by zero error", consider it an assert:
|
||||||
// WindowManager should be the only one controlling the rendering
|
// WindowManager should be the only one controlling the rendering
|
||||||
|
|
||||||
inkhud = InkHUD::getInstance();
|
|
||||||
settings = &inkhud->persistence->settings;
|
|
||||||
latestMessage = &inkhud->persistence->latestMessage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw a single pixel
|
// The raw pixel output generated by AdafruitGFX drawing
|
||||||
// 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 window manager
|
||||||
// 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)
|
void InkHUD::Applet::drawPixel(int16_t x, int16_t y, uint16_t color)
|
||||||
{
|
{
|
||||||
// Only render pixels if they fall within user's cropped region
|
// Only render pixels if they fall within user's cropped region
|
||||||
@@ -34,10 +27,9 @@ void InkHUD::Applet::drawPixel(int16_t x, int16_t y, uint16_t color)
|
|||||||
assignedTile->handleAppletPixel(x, y, (Color)color);
|
assignedTile->handleAppletPixel(x, y, (Color)color);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link our applet to a tile
|
// Sets which tile the applet renders for
|
||||||
// This can only be called by Tile::assignApplet
|
|
||||||
// The tile determines the applets dimensions
|
|
||||||
// Pixel output is passed to tile during render()
|
// Pixel output is passed to tile during render()
|
||||||
|
// This should only be called by Tile::assignApplet
|
||||||
void InkHUD::Applet::setTile(Tile *t)
|
void InkHUD::Applet::setTile(Tile *t)
|
||||||
{
|
{
|
||||||
// If we're setting (not clearing), make sure the link is "reciprocal"
|
// If we're setting (not clearing), make sure the link is "reciprocal"
|
||||||
@@ -47,32 +39,25 @@ void InkHUD::Applet::setTile(Tile *t)
|
|||||||
assignedTile = t;
|
assignedTile = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The tile to which our applet is assigned
|
// Which tile will the applet render() to?
|
||||||
InkHUD::Tile *InkHUD::Applet::getTile()
|
InkHUD::Tile *InkHUD::Applet::getTile()
|
||||||
{
|
{
|
||||||
return assignedTile;
|
return assignedTile;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the applet
|
|
||||||
void InkHUD::Applet::render()
|
void InkHUD::Applet::render()
|
||||||
{
|
{
|
||||||
assert(assignedTile); // Ensure that we have a tile
|
assert(assignedTile); // Ensure that we have a tile
|
||||||
assert(assignedTile->getAssignedApplet() == this); // Ensure that we have a reciprocal link with the tile
|
assert(assignedTile->getAssignedApplet() == this); // Ensure that we have a reciprocal link with the tile
|
||||||
|
|
||||||
// WindowManager::update has now consumed the info about our update request
|
wantRender = false; // Clear the flag set by requestUpdate
|
||||||
// Clear everything for future requests
|
wantAutoshow = false; // If we're rendering now, it means our request was considered. It may or may not have been granted.
|
||||||
wantRender = false; // Flag set by requestUpdate
|
wantUpdateType = Drivers::EInk::UpdateTypes::UNSPECIFIED; // Our requested type has been considered by now. Tidy up.
|
||||||
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();
|
updateDimensions();
|
||||||
resetDrawingSpace();
|
resetDrawingSpace();
|
||||||
onRender(); // Derived applet's drawing takes place here
|
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 our tile is (or was) highlighted, to indicate a change in focus
|
||||||
if (Tile::highlightTarget == assignedTile) {
|
if (Tile::highlightTarget == assignedTile) {
|
||||||
// Draw the highlight
|
// Draw the highlight
|
||||||
@@ -92,8 +77,7 @@ void InkHUD::Applet::render()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Does the applet want to render now?
|
// 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()
|
bool InkHUD::Applet::wantsToRender()
|
||||||
{
|
{
|
||||||
return wantRender;
|
return wantRender;
|
||||||
@@ -101,21 +85,18 @@ bool InkHUD::Applet::wantsToRender()
|
|||||||
|
|
||||||
// Does the applet want to be moved to foreground before next render, to show new data?
|
// 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
|
// User specifies whether an applet has permission for this, using the on-screen menu
|
||||||
// Used by WindowManager::update
|
|
||||||
bool InkHUD::Applet::wantsToAutoshow()
|
bool InkHUD::Applet::wantsToAutoshow()
|
||||||
{
|
{
|
||||||
return wantAutoshow;
|
return wantAutoshow;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Which technique would this applet prefer that the display use to change the image?
|
// Which technique would this applet prefer that the display use to change the image?
|
||||||
// Used by WindowManager::update
|
|
||||||
Drivers::EInk::UpdateTypes InkHUD::Applet::wantsUpdateType()
|
Drivers::EInk::UpdateTypes InkHUD::Applet::wantsUpdateType()
|
||||||
{
|
{
|
||||||
return wantUpdateType;
|
return wantUpdateType;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get size of the applet's drawing space from its tile
|
// Get size of the applet's drawing space from its tile
|
||||||
// Performed immediately before derived applet's drawing code runs
|
|
||||||
void InkHUD::Applet::updateDimensions()
|
void InkHUD::Applet::updateDimensions()
|
||||||
{
|
{
|
||||||
assert(assignedTile);
|
assert(assignedTile);
|
||||||
@@ -132,20 +113,19 @@ void InkHUD::Applet::resetDrawingSpace()
|
|||||||
setTextColor(BLACK); // Reset text params
|
setTextColor(BLACK); // Reset text params
|
||||||
setCursor(0, 0);
|
setCursor(0, 0);
|
||||||
setTextWrap(false);
|
setTextWrap(false);
|
||||||
setFont(fontSmall);
|
setFont(AppletFont()); // Restore the default AdafruitGFX font
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tell InkHUD::Renderer that we want to render now
|
// Tell the window manager that we want to render now
|
||||||
// Applets should internally listen for events they are interested in, via MeshModule, CallbackObserver etc
|
// 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
|
// When an applet decides it has heard something important, and wants to redraw, it calls this method
|
||||||
// Once the renderer has given other applets a chance to process whatever event we just detected,
|
// 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 (foreground)
|
// it will run Applet::render(), which may draw our applet to screen, if it is shown (forgeround)
|
||||||
// We should requestUpdate even if our applet is currently background, because this might be changed by autoshow
|
|
||||||
void InkHUD::Applet::requestUpdate(Drivers::EInk::UpdateTypes type)
|
void InkHUD::Applet::requestUpdate(Drivers::EInk::UpdateTypes type)
|
||||||
{
|
{
|
||||||
wantRender = true;
|
wantRender = true;
|
||||||
wantUpdateType = type;
|
wantUpdateType = type;
|
||||||
inkhud->requestUpdate();
|
WindowManager::getInstance()->requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask window manager to move this applet to foreground at start of next render
|
// Ask window manager to move this applet to foreground at start of next render
|
||||||
@@ -158,7 +138,7 @@ void InkHUD::Applet::requestAutoshow()
|
|||||||
// Called when an Applet begins running
|
// Called when an Applet begins running
|
||||||
// Active applets are considered "enabled"
|
// Active applets are considered "enabled"
|
||||||
// They should now listen for events, and request their own updates
|
// They should now listen for events, and request their own updates
|
||||||
// They may also be unexpectedly renderer at any time by other InkHUD components
|
// They may also be force rendered by the window manager at any time
|
||||||
// Applets can be activated at run-time through the on-screen menu
|
// Applets can be activated at run-time through the on-screen menu
|
||||||
void InkHUD::Applet::activate()
|
void InkHUD::Applet::activate()
|
||||||
{
|
{
|
||||||
@@ -166,7 +146,7 @@ void InkHUD::Applet::activate()
|
|||||||
active = true;
|
active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called when an Applet stops running
|
// Called when an Applet stop running
|
||||||
// Inactive applets are considered "disabled"
|
// Inactive applets are considered "disabled"
|
||||||
// They should not listen for events, process data
|
// They should not listen for events, process data
|
||||||
// They will not be rendered
|
// They will not be rendered
|
||||||
@@ -193,7 +173,7 @@ bool InkHUD::Applet::isActive()
|
|||||||
|
|
||||||
// Begin showing the Applet
|
// Begin showing the Applet
|
||||||
// It will be rendered immediately to whichever tile it is assigned
|
// It will be rendered immediately to whichever tile it is assigned
|
||||||
// The Renderer will also now honor requestUpdate() calls from this applet
|
// The window manager will also now honor requestUpdate() calls from this applet
|
||||||
void InkHUD::Applet::bringToForeground()
|
void InkHUD::Applet::bringToForeground()
|
||||||
{
|
{
|
||||||
if (!foreground) {
|
if (!foreground) {
|
||||||
@@ -206,7 +186,7 @@ void InkHUD::Applet::bringToForeground()
|
|||||||
|
|
||||||
// Stop showing the Applet
|
// Stop showing the Applet
|
||||||
// Calls to requestUpdate() will no longer be honored
|
// Calls to requestUpdate() will no longer be honored
|
||||||
// When one applet moves to background, another should move to foreground (exception: some system applets)
|
// When one applet moves to background, another should move to foreground
|
||||||
void InkHUD::Applet::sendToBackground()
|
void InkHUD::Applet::sendToBackground()
|
||||||
{
|
{
|
||||||
if (foreground) {
|
if (foreground) {
|
||||||
@@ -216,10 +196,6 @@ void InkHUD::Applet::sendToBackground()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Is the applet currently displayed on a tile
|
// 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()
|
bool InkHUD::Applet::isForeground()
|
||||||
{
|
{
|
||||||
return foreground;
|
return foreground;
|
||||||
@@ -272,7 +248,7 @@ void InkHUD::Applet::printAt(int16_t x, int16_t y, std::string text, HorizontalA
|
|||||||
// Custom font
|
// Custom font
|
||||||
// - set with AppletFont::addSubstitution
|
// - set with AppletFont::addSubstitution
|
||||||
// - find certain UTF8 chars
|
// - find certain UTF8 chars
|
||||||
// - replace with glyph from custom font (or suitable ASCII addSubstitution?)
|
// - replace with glpyh from custom font (or suitable ASCII addSubstitution?)
|
||||||
getFont().applySubstitutions(&text);
|
getFont().applySubstitutions(&text);
|
||||||
|
|
||||||
// We do still have to run getTextBounds to find the width
|
// We do still have to run getTextBounds to find the width
|
||||||
@@ -295,7 +271,8 @@ void InkHUD::Applet::printAt(int16_t x, int16_t y, std::string text, HorizontalA
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're using a fixed line height, rather than sizing to text (getTextBounds)
|
// We're using a fixed line height (getFontDimensions), rather than sizing to text (getTextBounds)
|
||||||
|
// Note: the FontDimensions values for this are unsigned
|
||||||
|
|
||||||
switch (va) {
|
switch (va) {
|
||||||
case TOP:
|
case TOP:
|
||||||
@@ -314,7 +291,7 @@ void InkHUD::Applet::printAt(int16_t x, int16_t y, std::string text, HorizontalA
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set which font should be used for subsequent drawing
|
// Set which font should be used for subsequent drawing
|
||||||
// This is AppletFont type, which is a wrapper for AdafruitGFX font, with some precalculated dimension data
|
// This is AppletFont type, which is a wrapper for AdfruitGFX font, with some precalculated dimension data
|
||||||
void InkHUD::Applet::setFont(AppletFont f)
|
void InkHUD::Applet::setFont(AppletFont f)
|
||||||
{
|
{
|
||||||
GFX::setFont(f.gfxFont);
|
GFX::setFont(f.gfxFont);
|
||||||
@@ -322,12 +299,20 @@ void InkHUD::Applet::setFont(AppletFont f)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get which font is currently being used for drawing
|
// Get which font is currently being used for drawing
|
||||||
// This is AppletFont type, which is a wrapper for AdafruitGFX font, with some precalculated dimension data
|
// This is AppletFont type, which is a wrapper for AdfruitGFX font, with some precalculated dimension data
|
||||||
InkHUD::AppletFont InkHUD::Applet::getFont()
|
InkHUD::AppletFont InkHUD::Applet::getFont()
|
||||||
{
|
{
|
||||||
return currentFont;
|
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
|
// Gets rendered width of a string
|
||||||
// Wrapper for getTextBounds
|
// Wrapper for getTextBounds
|
||||||
uint16_t InkHUD::Applet::getTextWidth(const char *text)
|
uint16_t InkHUD::Applet::getTextWidth(const char *text)
|
||||||
@@ -342,7 +327,7 @@ uint16_t InkHUD::Applet::getTextWidth(const char *text)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Gets rendered width of a string
|
// Gets rendered width of a string
|
||||||
// Wrapper for getTextBounds
|
// Wrappe for getTextBounds
|
||||||
uint16_t InkHUD::Applet::getTextWidth(std::string text)
|
uint16_t InkHUD::Applet::getTextWidth(std::string text)
|
||||||
{
|
{
|
||||||
getFont().applySubstitutions(&text);
|
getFont().applySubstitutions(&text);
|
||||||
@@ -353,7 +338,7 @@ uint16_t InkHUD::Applet::getTextWidth(std::string text)
|
|||||||
// Evaluate SNR and RSSI to qualify signal strength at one of four discrete levels
|
// Evaluate SNR and RSSI to qualify signal strength at one of four discrete levels
|
||||||
// Roughly comparable to values used by the iOS app;
|
// 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
|
// I didn't actually go look up the code, just fit to a sample graphic I have of the iOS signal indicator
|
||||||
InkHUD::Applet::SignalStrength InkHUD::Applet::getSignalStrength(float snr, float rssi)
|
InkHUD::SignalStrength InkHUD::Applet::getSignalStrength(float snr, float rssi)
|
||||||
{
|
{
|
||||||
uint8_t score = 0;
|
uint8_t score = 0;
|
||||||
|
|
||||||
@@ -391,14 +376,12 @@ std::string InkHUD::Applet::hexifyNodeNum(NodeNum num)
|
|||||||
return std::string(nodeIdHex);
|
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)
|
void InkHUD::Applet::printWrapped(int16_t left, int16_t top, uint16_t width, std::string text)
|
||||||
{
|
{
|
||||||
// Custom font glyphs
|
// Custom font glyphs
|
||||||
// - set with AppletFont::addSubstitution
|
// - set with AppletFont::addSubstitution
|
||||||
// - find certain UTF8 chars
|
// - find certain UTF8 chars
|
||||||
// - replace with glyph from custom font (or suitable ASCII addSubstitution?)
|
// - replace with glpyh from custom font (or suitable ASCII addSubstitution?)
|
||||||
getFont().applySubstitutions(&text);
|
getFont().applySubstitutions(&text);
|
||||||
|
|
||||||
// Place the AdafruitGFX cursor to suit our "top" coord
|
// Place the AdafruitGFX cursor to suit our "top" coord
|
||||||
@@ -545,7 +528,7 @@ std::string InkHUD::Applet::getTimeString(uint32_t epochSeconds)
|
|||||||
#ifdef BUILD_EPOCH
|
#ifdef BUILD_EPOCH
|
||||||
constexpr uint32_t validAfterEpoch = BUILD_EPOCH - (SEC_PER_DAY * 30 * 6); // 6 Months prior to build
|
constexpr uint32_t validAfterEpoch = BUILD_EPOCH - (SEC_PER_DAY * 30 * 6); // 6 Months prior to build
|
||||||
#else
|
#else
|
||||||
constexpr uint32_t validAfterEpoch = 1738368000 - (SEC_PER_DAY * 30 * 6); // 6 Months prior to Feb 1, 2025 12:00:00 AM GMT
|
constexpr uint32_t validAfterEpoch = 1727740800 - (SEC_PER_DAY * 30 * 6); // 6 Months prior to October 1, 2024 12:00:00 AM GMT
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
uint32_t epochNow = getValidTime(RTCQuality::RTCQualityDevice, true);
|
uint32_t epochNow = getValidTime(RTCQuality::RTCQualityDevice, true);
|
||||||
@@ -555,17 +538,23 @@ std::string InkHUD::Applet::getTimeString(uint32_t epochSeconds)
|
|||||||
|
|
||||||
// Times are invalid: rtc is much older than when code was built
|
// Times are invalid: rtc is much older than when code was built
|
||||||
// Don't give any human readable string
|
// Don't give any human readable string
|
||||||
if (epochNow <= validAfterEpoch)
|
if (epochNow <= validAfterEpoch) {
|
||||||
|
LOG_DEBUG("RTC prior to buildtime");
|
||||||
return "";
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
// Times are invalid: argument time is significantly ahead of RTC
|
// Times are invalid: argument time is significantly ahead of RTC
|
||||||
// Don't give any human readable string
|
// Don't give any human readable string
|
||||||
if (daysAgo < -2)
|
if (daysAgo < -2) {
|
||||||
|
LOG_DEBUG("RTC in future");
|
||||||
return "";
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
// Times are probably invalid: more than 6 months ago
|
// Times are probably invalid: more than 6 months ago
|
||||||
if (daysAgo > 6 * 30)
|
if (daysAgo > 6 * 30) {
|
||||||
|
LOG_DEBUG("RTC val > 6 months old");
|
||||||
return "";
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
if (daysAgo > 1)
|
if (daysAgo > 1)
|
||||||
return to_string(daysAgo) + " days ago";
|
return to_string(daysAgo) + " days ago";
|
||||||
@@ -613,7 +602,7 @@ uint16_t InkHUD::Applet::getActiveNodeCount()
|
|||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||||
|
|
||||||
// Check if heard recently, and not our own node
|
// 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++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,7 +619,7 @@ std::string InkHUD::Applet::localizeDistance(uint32_t meters)
|
|||||||
// Resulting string
|
// Resulting string
|
||||||
std::string localized;
|
std::string localized;
|
||||||
|
|
||||||
// Imperial
|
// Imeperial
|
||||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||||
uint32_t feet = meters * FEET_PER_METER;
|
uint32_t feet = meters * FEET_PER_METER;
|
||||||
// Distant (miles, rounded)
|
// Distant (miles, rounded)
|
||||||
@@ -662,7 +651,6 @@ std::string InkHUD::Applet::localizeDistance(uint32_t meters)
|
|||||||
return localized;
|
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)
|
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
|
// How many times to draw along x axis
|
||||||
@@ -715,24 +703,17 @@ void InkHUD::Applet::printThick(int16_t xCenter, int16_t yCenter, std::string te
|
|||||||
// Asked before a notification is shown via the NotificationApplet
|
// 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
|
// 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
|
// Example: AllMessageApplet should not approve notifications for messages, if it is in foreground
|
||||||
bool InkHUD::Applet::approveNotification(NicheGraphics::InkHUD::Notification &n)
|
bool InkHUD::Applet::approveNotification(InkHUD::Notification &n)
|
||||||
{
|
{
|
||||||
// By default, no objection
|
// By default, no objection
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the standard header, used by most Applets
|
// Draw the standard header, used by most Applets
|
||||||
/*
|
|
||||||
┌───────────────────────────────┐
|
|
||||||
│ Applet::name here │
|
|
||||||
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
└───────────────────────────────┘
|
|
||||||
*/
|
|
||||||
void InkHUD::Applet::drawHeader(std::string text)
|
void InkHUD::Applet::drawHeader(std::string text)
|
||||||
{
|
{
|
||||||
|
setFont(fontSmall);
|
||||||
|
|
||||||
// Y position for divider
|
// Y position for divider
|
||||||
// - between header text and messages
|
// - between header text and messages
|
||||||
constexpr int16_t padDivH = 2;
|
constexpr int16_t padDivH = 2;
|
||||||
@@ -790,15 +771,6 @@ uint16_t InkHUD::Applet::getLogoHeight(uint16_t limitWidth, uint16_t limitHeight
|
|||||||
// Draw a scalable Meshtastic logo
|
// Draw a scalable Meshtastic logo
|
||||||
// Make sure to provide dimensions which have the correct aspect ratio (~2)
|
// Make sure to provide dimensions which have the correct aspect ratio (~2)
|
||||||
// Three paths, drawn thick using quads, with one corner "radiused"
|
// 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)
|
void InkHUD::Applet::drawLogo(int16_t centerX, int16_t centerY, uint16_t width, uint16_t height)
|
||||||
{
|
{
|
||||||
struct Point {
|
struct Point {
|
||||||
@@ -816,17 +788,6 @@ void InkHUD::Applet::drawLogo(int16_t centerX, int16_t centerY, uint16_t width,
|
|||||||
int16_t logoB = logoT + logoH - 1;
|
int16_t logoB = logoT + logoH - 1;
|
||||||
|
|
||||||
// Points for paths (a, b, and c)
|
// Points for paths (a, b, and c)
|
||||||
/*
|
|
||||||
+-----------------------------+
|
|
||||||
--| a2 b2/c1 |
|
|
||||||
| |
|
|
||||||
| |
|
|
||||||
| |
|
|
||||||
--| a1 b1 c2 |
|
|
||||||
+-----------------------------+
|
|
||||||
| | | |
|
|
||||||
*/
|
|
||||||
|
|
||||||
Point a1 = {map(0, 0, 3, logoL, logoR), logoB};
|
Point a1 = {map(0, 0, 3, logoL, logoR), logoB};
|
||||||
Point a2 = {map(1, 0, 3, logoL, logoR), logoT};
|
Point a2 = {map(1, 0, 3, logoL, logoR), logoT};
|
||||||
Point b1 = {map(1, 0, 3, logoL, logoR), logoB};
|
Point b1 = {map(1, 0, 3, logoL, logoR), logoB};
|
||||||
@@ -834,72 +795,17 @@ void InkHUD::Applet::drawLogo(int16_t centerX, int16_t centerY, uint16_t width,
|
|||||||
Point c1 = {map(2, 0, 3, logoL, logoR), logoT};
|
Point c1 = {map(2, 0, 3, logoL, logoR), logoT};
|
||||||
Point c2 = {map(3, 0, 3, logoL, logoR), logoB};
|
Point c2 = {map(3, 0, 3, logoL, logoR), logoB};
|
||||||
|
|
||||||
// Find angle of the path(s)
|
// Find right-angle to the path
|
||||||
// Used to thicken the single pixel paths
|
// Used to thicken the single pixel paths
|
||||||
/*
|
|
||||||
+-------------------------------+
|
|
||||||
| a2 |
|
|
||||||
| -| |
|
|
||||||
| -/ | |
|
|
||||||
| -/ | |
|
|
||||||
| -/# | |
|
|
||||||
| -/ # | |
|
|
||||||
| / # | |
|
|
||||||
| a1---------- |
|
|
||||||
+-------------------------------+
|
|
||||||
*/
|
|
||||||
|
|
||||||
Distance deltaA = {abs(a2.x - a1.x), abs(a2.y - a1.y)};
|
Distance deltaA = {abs(a2.x - a1.x), abs(a2.y - a1.y)};
|
||||||
float angle = tanh((float)deltaA.y / deltaA.x);
|
float angle = tanh((float)deltaA.y / deltaA.x);
|
||||||
|
|
||||||
// Distance (at right angle to the paths), which will give corners for our "quads"
|
// Distance {at right angle from 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
|
// 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;
|
Distance fromPath;
|
||||||
fromPath.x = cos(radians(90) - angle) * logoTh * 0.5;
|
fromPath.x = cos(radians(90) - angle) * logoTh * 0.5;
|
||||||
fromPath.y = sin(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
|
// Make the path thick: path a becomes quad a
|
||||||
Point aq1{a1.x - fromPath.x, a1.y - fromPath.y};
|
Point aq1{a1.x - fromPath.x, a1.y - fromPath.y};
|
||||||
Point aq2{a2.x - fromPath.x, a2.y - fromPath.y};
|
Point aq2{a2.x - fromPath.x, a2.y - fromPath.y};
|
||||||
@@ -916,7 +822,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, bq2.x, bq2.y, bq3.x, bq3.y, BLACK);
|
||||||
fillTriangle(bq1.x, bq1.y, bq3.x, bq3.y, bq4.x, bq4.y, BLACK);
|
fillTriangle(bq1.x, bq1.y, bq3.x, bq3.y, bq4.x, bq4.y, BLACK);
|
||||||
|
|
||||||
// Make the path thick: path c becomes quad c
|
// Make the path hick: path c becomes quad c
|
||||||
Point cq1{c1.x - fromPath.x, c1.y + fromPath.y};
|
Point cq1{c1.x - fromPath.x, c1.y + fromPath.y};
|
||||||
Point cq2{c2.x - fromPath.x, c2.y + fromPath.y};
|
Point cq2{c2.x - fromPath.x, c2.y + fromPath.y};
|
||||||
Point cq3{c2.x + fromPath.x, c2.y - fromPath.y};
|
Point cq3{c2.x + fromPath.x, c2.y - fromPath.y};
|
||||||
@@ -925,21 +831,10 @@ 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);
|
fillTriangle(cq1.x, cq1.y, cq3.x, cq3.y, cq4.x, cq4.y, BLACK);
|
||||||
|
|
||||||
// Radius the intersection of quad b and quad c
|
// Radius the intersection of quad b and quad c
|
||||||
/*
|
|
||||||
b2 / c1
|
|
||||||
####
|
|
||||||
## ##
|
|
||||||
/ \
|
|
||||||
/ \/ \
|
|
||||||
/ /\ \
|
|
||||||
/ / \ \
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Don't attempt if logo is tiny
|
// Don't attempt if logo is tiny
|
||||||
if (logoTh > 3) {
|
if (logoTh > 3) {
|
||||||
// The radius for the cap *should* be the same as logoTh, but it's not, due to accumulated rounding
|
// The radius for the cap *should* be the same as logoTh, but it's not, due to accumulated rounding
|
||||||
// We get better results just re-deriving it
|
// We get better results just rederiving it
|
||||||
int16_t capRad = sqrt(pow(fromPath.x, 2) + pow(fromPath.y, 2));
|
int16_t capRad = sqrt(pow(fromPath.x, 2) + pow(fromPath.y, 2));
|
||||||
fillCircle(b2.x, b2.y, capRad, BLACK);
|
fillCircle(b2.x, b2.y, capRad, BLACK);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,21 +7,103 @@
|
|||||||
|
|
||||||
An applet is one "program" which may show info on the display.
|
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
|
#pragma once
|
||||||
|
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
#include <GFX.h> // GFXRoot drawing lib
|
#include <GFX.h>
|
||||||
|
|
||||||
#include "mesh/MeshTypes.h"
|
|
||||||
|
|
||||||
#include "./AppletFont.h"
|
#include "./AppletFont.h"
|
||||||
#include "./Applets/System/Notification/Notification.h" // The notification object, not the applet
|
#include "./Applets/System/Notification/Notification.h"
|
||||||
#include "./InkHUD.h"
|
|
||||||
#include "./Persistence.h"
|
|
||||||
#include "./Tile.h"
|
#include "./Tile.h"
|
||||||
|
#include "./Types.h"
|
||||||
|
#include "./WindowManager.h"
|
||||||
#include "graphics/niche/Drivers/EInk/EInk.h"
|
#include "graphics/niche/Drivers/EInk/EInk.h"
|
||||||
|
|
||||||
namespace NicheGraphics::InkHUD
|
namespace NicheGraphics::InkHUD
|
||||||
@@ -30,57 +112,37 @@ namespace NicheGraphics::InkHUD
|
|||||||
using NicheGraphics::Drivers::EInk;
|
using NicheGraphics::Drivers::EInk;
|
||||||
using std::to_string;
|
using std::to_string;
|
||||||
|
|
||||||
|
class Tile;
|
||||||
|
class WindowManager;
|
||||||
|
|
||||||
class Applet : public GFX
|
class Applet : public GFX
|
||||||
{
|
{
|
||||||
public:
|
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();
|
Applet();
|
||||||
|
|
||||||
void setTile(Tile *t); // Should only be called via Tile::setApplet
|
void setTile(Tile *t); // Applets draw via a tile (for multiplexing)
|
||||||
Tile *getTile(); // Tile with which this applet is linked
|
Tile *getTile();
|
||||||
|
|
||||||
// Rendering
|
void render();
|
||||||
|
bool wantsToRender(); // Check whether applet wants to render
|
||||||
void render(); // Draw the applet
|
bool wantsToAutoshow(); // Check whether applets wants to become foreground, to show new data, if permitted
|
||||||
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
|
Drivers::EInk::UpdateTypes wantsUpdateType(); // Check which display update type the applet would prefer
|
||||||
void updateDimensions(); // Get current size from tile
|
void updateDimensions(); // Get current size from tile
|
||||||
void resetDrawingSpace(); // Makes sure every render starts with same parameters
|
void resetDrawingSpace(); // Makes sure every render starts with same parameters
|
||||||
|
|
||||||
// State of the applet
|
// Change the applet's state
|
||||||
|
|
||||||
|
void activate();
|
||||||
|
void deactivate();
|
||||||
|
void bringToForeground();
|
||||||
|
void sendToBackground();
|
||||||
|
|
||||||
|
// Info about applet's state
|
||||||
|
|
||||||
void activate(); // Begin running
|
|
||||||
void deactivate(); // Stop running
|
|
||||||
void bringToForeground(); // Show
|
|
||||||
void sendToBackground(); // Hide
|
|
||||||
bool isActive();
|
bool isActive();
|
||||||
bool isForeground();
|
bool isForeground();
|
||||||
|
|
||||||
// Event handlers
|
// Allow derived applets to handle changes in state
|
||||||
|
|
||||||
virtual void onRender() = 0; // All drawing happens here
|
virtual void onRender() = 0; // All drawing happens here
|
||||||
virtual void onActivate() {}
|
virtual void onActivate() {}
|
||||||
@@ -88,62 +150,62 @@ class Applet : public GFX
|
|||||||
virtual void onForeground() {}
|
virtual void onForeground() {}
|
||||||
virtual void onBackground() {}
|
virtual void onBackground() {}
|
||||||
virtual void onShutdown() {}
|
virtual void onShutdown() {}
|
||||||
virtual void onButtonShortPress() {} // (System Applets only)
|
virtual void onButtonShortPress() {} // For use by System Applets only
|
||||||
virtual void onButtonLongPress() {} // (System Applets only)
|
virtual void onButtonLongPress() {} // For use by System Applets only
|
||||||
|
virtual void onLockAvailable() {} // For use by System Applets only
|
||||||
|
|
||||||
virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification
|
virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification
|
||||||
|
|
||||||
static uint16_t getHeaderHeight(); // How tall the "standard" applet header is
|
static void setDefaultFonts(AppletFont large, AppletFont small); // Set the general purpose fonts
|
||||||
|
static uint16_t getHeaderHeight(); // How tall is the "standard" applet header
|
||||||
|
|
||||||
static AppletFont fontSmall, fontLarge; // The general purpose fonts, used by all applets
|
const char *name = nullptr; // Shown in applet selection menu
|
||||||
|
|
||||||
const char *name = nullptr; // Shown in applet selection menu. Also used as an identifier by InkHUD::getSystemApplet
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void drawPixel(int16_t x, int16_t y, uint16_t color) override; // Place a single pixel. All drawing output passes through here
|
// Place a single pixel. All drawing methods output through here
|
||||||
|
void drawPixel(int16_t x, int16_t y, uint16_t color) override;
|
||||||
|
|
||||||
void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED); // Ask WindowManager to schedule a display update
|
// Tell WindowManager to update display
|
||||||
void requestAutoshow(); // Ask for applet to be moved to foreground
|
void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED);
|
||||||
|
|
||||||
|
// Ask for applet to be moved to foreground
|
||||||
|
void requestAutoshow();
|
||||||
|
|
||||||
uint16_t X(float f); // Map applet width, mapped from 0 to 1.0
|
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
|
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 setCrop(int16_t left, int16_t top, uint16_t width, uint16_t height); // Ignore pixels drawn outside a certain region
|
||||||
void resetCrop(); // Removes setCrop()
|
void resetCrop(); // Removes setCrop()
|
||||||
|
|
||||||
// Text
|
|
||||||
|
|
||||||
void setFont(AppletFont f);
|
void setFont(AppletFont f);
|
||||||
AppletFont getFont();
|
AppletFont getFont();
|
||||||
|
|
||||||
uint16_t getTextWidth(std::string text);
|
uint16_t getTextWidth(std::string text);
|
||||||
uint16_t getTextWidth(const char *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, 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 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); // Faux bold
|
void printThick(int16_t xCenter, int16_t yCenter, std::string text, uint8_t thicknessX, uint8_t thicknessY);
|
||||||
void printWrapped(int16_t left, int16_t top, uint16_t width, std::string text); // Per-word line wrapping
|
|
||||||
|
// 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 hatchRegion(int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t spacing, Color color); // Fill with sparse lines
|
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
|
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
|
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 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
|
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
|
void drawLogo(int16_t centerX, int16_t centerY, uint16_t width, uint16_t height); // Draw the meshtastic logo
|
||||||
|
|
||||||
std::string hexifyNodeNum(NodeNum num); // Style as !0123abdc
|
std::string hexifyNodeNum(NodeNum num);
|
||||||
SignalStrength getSignalStrength(float snr, float rssi); // Interpret SNR and RSSI, as an easy to understand value
|
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(uint32_t epochSeconds); // Human readable
|
||||||
std::string getTimeString(); // Current time, human readable
|
std::string getTimeString(); // Current time, human readable
|
||||||
uint16_t getActiveNodeCount(); // Duration determined by user, in onscreen menu
|
uint16_t getActiveNodeCount(); // Duration determined by user, in onscreen menu
|
||||||
std::string localizeDistance(uint32_t meters); // Human readable distance, imperial or metric
|
std::string localizeDistance(uint32_t meters); // Human readable distance, imperial or metric
|
||||||
|
|
||||||
// Convenient references
|
static AppletFont fontSmall, fontLarge; // General purpose fonts, used cross-applet
|
||||||
|
|
||||||
InkHUD *inkhud = nullptr;
|
|
||||||
Persistence::Settings *settings = nullptr;
|
|
||||||
Persistence::LatestMessage *latestMessage = nullptr;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Tile *assignedTile = nullptr; // Rendered pixels are fed into a Tile object, which translates them, then passes to WM
|
Tile *assignedTile = nullptr; // Rendered pixels are fed into a Tile object, which translates them, then passes to WM
|
||||||
@@ -161,10 +223,10 @@ class Applet : public GFX
|
|||||||
AppletFont currentFont; // As passed to setFont
|
AppletFont currentFont; // As passed to setFont
|
||||||
|
|
||||||
// As set by setCrop
|
// As set by setCrop
|
||||||
int16_t cropLeft = 0;
|
int16_t cropLeft;
|
||||||
int16_t cropTop = 0;
|
int16_t cropTop;
|
||||||
uint16_t cropWidth = 0;
|
uint16_t cropWidth;
|
||||||
uint16_t cropHeight = 0;
|
uint16_t cropHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
}; // namespace NicheGraphics::InkHUD
|
}; // namespace NicheGraphics::InkHUD
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ InkHUD::AppletFont::AppletFont()
|
|||||||
InkHUD::AppletFont::AppletFont(const GFXfont &adafruitGFXFont) : gfxFont(&adafruitGFXFont)
|
InkHUD::AppletFont::AppletFont(const GFXfont &adafruitGFXFont) : gfxFont(&adafruitGFXFont)
|
||||||
{
|
{
|
||||||
// AdafruitGFX fonts are drawn relative to a "cursor line";
|
// AdafruitGFX fonts are drawn relative to a "cursor line";
|
||||||
// they print as if the glyphs are resting on the line of piece of ruled paper.
|
// they print as if the glyphs resting on the line of piece of ruled paper.
|
||||||
// The glyphs also each have a different height.
|
// 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
|
// To simplify drawing, we will scan the entire font now, and determine an appropriate height for a line of text
|
||||||
@@ -42,19 +42,6 @@ InkHUD::AppletFont::AppletFont(const GFXfont &adafruitGFXFont) : gfxFont(&adafru
|
|||||||
spaceCharWidth = gfxFont->glyph[(uint8_t)' ' - gfxFont->first].xAdvance;
|
spaceCharWidth = gfxFont->glyph[(uint8_t)' ' - gfxFont->first].xAdvance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
▲ ##### # ▲
|
|
||||||
│ # # │
|
|
||||||
lineHeight │ ### # │
|
|
||||||
│ # # # # │ heightAboveCursor
|
|
||||||
│ # # # # │
|
|
||||||
│ # # #### │
|
|
||||||
│ -----------------#----
|
|
||||||
│ # │ heightBelowCursor
|
|
||||||
▼ ### ▼
|
|
||||||
*/
|
|
||||||
|
|
||||||
uint8_t InkHUD::AppletFont::lineHeight()
|
uint8_t InkHUD::AppletFont::lineHeight()
|
||||||
{
|
{
|
||||||
return this->height;
|
return this->height;
|
||||||
@@ -91,7 +78,7 @@ void InkHUD::AppletFont::addSubstitution(const char *from, const char *to)
|
|||||||
substitutions.push_back({.from = from, .to = to});
|
substitutions.push_back({.from = from, .to = to});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run all registered substitutions on a string
|
// Run all registered subtitutions on a string
|
||||||
// Used to swap out UTF8 special chars
|
// Used to swap out UTF8 special chars
|
||||||
void InkHUD::AppletFont::applySubstitutions(std::string *text)
|
void InkHUD::AppletFont::applySubstitutions(std::string *text)
|
||||||
{
|
{
|
||||||
@@ -100,7 +87,7 @@ void InkHUD::AppletFont::applySubstitutions(std::string *text)
|
|||||||
|
|
||||||
// Find and replace
|
// Find and replace
|
||||||
// - search for Substitution::from
|
// - search for Substitution::from
|
||||||
// - replace with Substitution::to
|
// - replace with Subsitution::to
|
||||||
size_t i = text->find(s.from);
|
size_t i = text->find(s.from);
|
||||||
while (i != std::string::npos) {
|
while (i != std::string::npos) {
|
||||||
text->replace(i, strlen(s.from), s.to);
|
text->replace(i, strlen(s.from), s.to);
|
||||||
@@ -110,7 +97,7 @@ void InkHUD::AppletFont::applySubstitutions(std::string *text)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply a set of substitutions which remap UTF8 for a Windows-1251 font
|
// Apply a set of substitutions which remap UTF8 for a Windows-1251 font
|
||||||
// Windows-1251 is an 8-bit character encoding, suitable for several languages which use the Cyrillic script
|
// Windows-1251 is an 8-bit character encoding, designed to cover languages that use the Cyrillic script
|
||||||
void InkHUD::AppletFont::addSubstitutionsWin1251()
|
void InkHUD::AppletFont::addSubstitutionsWin1251()
|
||||||
{
|
{
|
||||||
addSubstitution("Ђ", "\x80");
|
addSubstitution("Ђ", "\x80");
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
#include <GFX.h> // GFXRoot drawing lib
|
#include <GFX.h>
|
||||||
|
|
||||||
namespace NicheGraphics::InkHUD
|
namespace NicheGraphics::InkHUD
|
||||||
{
|
{
|
||||||
@@ -25,12 +25,11 @@ class AppletFont
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AppletFont();
|
AppletFont();
|
||||||
explicit AppletFont(const GFXfont &adafruitGFXFont);
|
AppletFont(const GFXfont &adafruitGFXFont);
|
||||||
|
|
||||||
uint8_t lineHeight();
|
uint8_t lineHeight();
|
||||||
uint8_t heightAboveCursor();
|
uint8_t heightAboveCursor();
|
||||||
uint8_t heightBelowCursor();
|
uint8_t heightBelowCursor();
|
||||||
uint8_t widthBetweenWords(); // Width of the space character
|
uint8_t widthBetweenWords();
|
||||||
|
|
||||||
void applySubstitutions(std::string *text); // Run all char-substitution operations, prior to printing
|
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
|
void addSubstitution(const char *from, const char *to); // Register a find-replace action, for remapping UTF8 chars
|
||||||
@@ -51,7 +50,8 @@ class AppletFont
|
|||||||
const char *to;
|
const char *to;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<Substitution> substitutions; // List of all character substitutions to run, prior to printing a string
|
// List of all character substitutions to run, prior to printing a string
|
||||||
|
std::vector<Substitution> substitutions;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace NicheGraphics::InkHUD
|
} // namespace NicheGraphics::InkHUD
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ using namespace NicheGraphics;
|
|||||||
|
|
||||||
void InkHUD::MapApplet::onRender()
|
void InkHUD::MapApplet::onRender()
|
||||||
{
|
{
|
||||||
|
setFont(fontSmall);
|
||||||
|
|
||||||
// Abort if no markers to render
|
// Abort if no markers to render
|
||||||
if (!enoughMarkers()) {
|
if (!enoughMarkers()) {
|
||||||
printAt(X(0.5), Y(0.5) - (getFont().lineHeight() / 2), "Node positions", CENTER, MIDDLE);
|
printAt(X(0.5), Y(0.5) - (getFont().lineHeight() / 2), "Node positions", CENTER, MIDDLE);
|
||||||
@@ -25,7 +27,6 @@ void InkHUD::MapApplet::onRender()
|
|||||||
// Set the region shown on the map
|
// Set the region shown on the map
|
||||||
// - default: fit all nodes, plus padding
|
// - default: fit all nodes, plus padding
|
||||||
// - maybe overriden by derived applet
|
// - maybe overriden by derived applet
|
||||||
// - getMapSize *sets* passed parameters (C-style)
|
|
||||||
getMapSize(&widthMeters, &heightMeters);
|
getMapSize(&widthMeters, &heightMeters);
|
||||||
|
|
||||||
// Set the metersToPx conversion value
|
// Set the metersToPx conversion value
|
||||||
@@ -70,7 +71,7 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
|||||||
// - uses tan to find angles for lat / long degrees
|
// - uses tan to find angles for lat / long degrees
|
||||||
// - longitude: triangle formed by x and y (on plane of the equator)
|
// - longitude: triangle formed by x and y (on plane of the equator)
|
||||||
// - latitude: triangle formed by z (north south),
|
// - latitude: triangle formed by z (north south),
|
||||||
// and the line along plane of equator which stretches from earth's axis to where point xyz intersects planet's surface
|
// and the line along plane of equator which stetches from earth's axis to where point xyz intersects planet's surface
|
||||||
|
|
||||||
// Working totals, averaged after nodeDB processed
|
// Working totals, averaged after nodeDB processed
|
||||||
uint32_t positionCount = 0;
|
uint32_t positionCount = 0;
|
||||||
@@ -133,7 +134,7 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
|||||||
|
|
||||||
*lng = atan2(yAvg, xAvg) * RAD_TO_DEG;
|
*lng = atan2(yAvg, xAvg) * RAD_TO_DEG;
|
||||||
|
|
||||||
// Latitude from cartesian coords
|
// Latitude from cartesian cooods
|
||||||
// (Angle from 3D coords describing a point on the globe's surface)
|
// (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.
|
// 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
|
// Means we need to first find the hypotenuse which becomes base of our triangle in the second step
|
||||||
@@ -190,8 +191,8 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
|||||||
|
|
||||||
// Longitude is trickier
|
// Longitude is trickier
|
||||||
float lng = node->position.longitude_i * 1e-7;
|
float lng = node->position.longitude_i * 1e-7;
|
||||||
float degEastward = fmod(((lng - lngCenter) + 360), 360); // Degrees traveled east from lngCenter to reach node
|
float degEastward = fmod(((lng - lngCenter) + 360), 360); // Degrees travelled east from lngCenter to reach node
|
||||||
float degWestward = abs(fmod(((lng - lngCenter) - 360), 360)); // Degrees traveled west from lngCenter to reach node
|
float degWestward = abs(fmod(((lng - lngCenter) - 360), 360)); // Degrees travelled west from lngCenter to reach node
|
||||||
if (degEastward < degWestward)
|
if (degEastward < degWestward)
|
||||||
easternmost = max(easternmost, lngCenter + degEastward);
|
easternmost = max(easternmost, lngCenter + degEastward);
|
||||||
else
|
else
|
||||||
@@ -257,7 +258,7 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
|||||||
// Find x and y position based on node's position in nodeDB
|
// Find x and y position based on node's position in nodeDB
|
||||||
assert(nodeDB->hasValidPosition(node));
|
assert(nodeDB->hasValidPosition(node));
|
||||||
Marker m = calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
|
Marker m = calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
|
||||||
node->position.longitude_i * 1e-7, // Long, converted from Meshtastic's internal int32 style
|
node->position.longitude_i * 1e-7, // Long, convered from Meshtastic's internal int32 style
|
||||||
node->has_hops_away, // Is the hopsAway number valid
|
node->has_hops_away, // Is the hopsAway number valid
|
||||||
node->hops_away // Hops away
|
node->hops_away // Hops away
|
||||||
);
|
);
|
||||||
@@ -287,7 +288,7 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
|||||||
bool unknownHops = !node->has_hops_away && !isOurNode;
|
bool unknownHops = !node->has_hops_away && !isOurNode;
|
||||||
|
|
||||||
// We will draw a left or right hand variant, to place text towards screen center
|
// We will draw a left or right hand variant, to place text towards screen center
|
||||||
// Hopefully avoid text spilling off screen
|
// Hopfully avoid text spilling off screen
|
||||||
// Most values are the same, regardless of left-right handedness
|
// Most values are the same, regardless of left-right handedness
|
||||||
|
|
||||||
// Pick emblem style
|
// Pick emblem style
|
||||||
@@ -387,7 +388,7 @@ void InkHUD::MapApplet::calculateAllMarkers()
|
|||||||
// Calculate marker and store it
|
// Calculate marker and store it
|
||||||
markers.push_back(
|
markers.push_back(
|
||||||
calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
|
calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
|
||||||
node->position.longitude_i * 1e-7, // Long, converted from Meshtastic's internal int32 style
|
node->position.longitude_i * 1e-7, // Long, convered from Meshtastic's internal int32 style
|
||||||
node->has_hops_away, // Is the hopsAway number valid
|
node->has_hops_away, // Is the hopsAway number valid
|
||||||
node->hops_away // Hops away
|
node->hops_away // Hops away
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -38,12 +38,13 @@ class MapApplet : public Applet
|
|||||||
void drawLabeledMarker(meshtastic_NodeInfoLite *node); // Highlight a specific marker
|
void drawLabeledMarker(meshtastic_NodeInfoLite *node); // Highlight a specific marker
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Position and size of a marker to be drawn
|
// Position of markers to be drawn, relative to map center
|
||||||
|
// HopsAway info used to determine marker size
|
||||||
struct Marker {
|
struct Marker {
|
||||||
float eastMeters = 0; // Meters east of map center. Negative if west.
|
float eastMeters = 0; // Meters east of mapCenter. Negative if west.
|
||||||
float northMeters = 0; // Meters north of map center. Negative if south.
|
float northMeters = 0; // Meters north of mapCenter. Negative if south.
|
||||||
bool hasHopsAway = false;
|
bool hasHopsAway = false;
|
||||||
uint8_t hopsAway = 0; // Determines marker size
|
uint8_t hopsAway = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
Marker calculateMarker(float lat, float lng, bool hasHopsAway, uint8_t hopsAway);
|
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)
|
InkHUD::NodeListApplet::NodeListApplet(const char *name) : MeshModule(name)
|
||||||
{
|
{
|
||||||
// We only need to be promiscuous in order to hear NodeInfo, apparently. See NodeInfoModule
|
// We only need to be promiscuous in order to hear NodeInfo, apparently. See NodeInfoModule
|
||||||
// For all other packets, we manually act as if isPromiscuous=false, in wantPacket
|
// For all other packets, we manually reimplement isPromiscuous=false in wantPacket
|
||||||
MeshModule::isPromiscuous = true;
|
MeshModule::isPromiscuous = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,17 +25,17 @@ bool InkHUD::NodeListApplet::wantPacket(const meshtastic_MeshPacket *p)
|
|||||||
&& (isToUs(p) || isBroadcast(p->to) || // Either: intended for us,
|
&& (isToUs(p) || isBroadcast(p->to) || // Either: intended for us,
|
||||||
p->decoded.portnum == meshtastic_PortNum_NODEINFO_APP); // or nodeinfo
|
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:
|
// To match the behavior seen in the client apps:
|
||||||
// - NodeInfoModule's ProtoBufModule base is "promiscuous"
|
// - NodeInfoModule's ProtoBufModule base is "promiscuous"
|
||||||
// - All other activity is *not* 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
|
// to match the code in MeshModule::callModules
|
||||||
}
|
}
|
||||||
|
|
||||||
// MeshModule packets arrive here
|
// MeshModule packets arrive here
|
||||||
// Extract the info and pass it to the derived applet
|
// 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?)
|
// Derived applet might also need to keep other tallies (active nodes count?)
|
||||||
ProcessMessage InkHUD::NodeListApplet::handleReceived(const meshtastic_MeshPacket &mp)
|
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
|
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate maximum number of cards we may ever need to render, in our tallest layout config
|
// 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
|
// May be slightly in excess of the true value: header not accounted for
|
||||||
uint8_t InkHUD::NodeListApplet::maxCards()
|
uint8_t InkHUD::NodeListApplet::maxCards()
|
||||||
{
|
{
|
||||||
// Cache result. Shouldn't change during execution
|
// Cache result. Shouldn't change during execution
|
||||||
@@ -87,7 +87,7 @@ uint8_t InkHUD::NodeListApplet::maxCards()
|
|||||||
const uint16_t height = Tile::maxDisplayDimension();
|
const uint16_t height = Tile::maxDisplayDimension();
|
||||||
|
|
||||||
// Use a loop instead of arithmetic, because it's easier for my brain to follow
|
// Use a loop instead of arithmetic, because it's easier for my brain to follow
|
||||||
// Add cards one by one, until the latest card extends below screen
|
// Add cards one by one, until the latest card (without margin) extends below screen
|
||||||
|
|
||||||
uint16_t y = cardH; // First card: no margin above
|
uint16_t y = cardH; // First card: no margin above
|
||||||
cards = 1;
|
cards = 1;
|
||||||
@@ -102,7 +102,7 @@ uint8_t InkHUD::NodeListApplet::maxCards()
|
|||||||
return cards;
|
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()
|
void InkHUD::NodeListApplet::onRender()
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -120,6 +120,9 @@ void InkHUD::NodeListApplet::onRender()
|
|||||||
// Draw the main node list
|
// 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
|
// Imaginary vertical line dividing left-side and right-side info
|
||||||
// Long-name will crop here
|
// Long-name will crop here
|
||||||
const uint16_t dividerX = (width() - 1) - getTextWidth("X Hops");
|
const uint16_t dividerX = (width() - 1) - getTextWidth("X Hops");
|
||||||
@@ -212,8 +215,9 @@ void InkHUD::NodeListApplet::onRender()
|
|||||||
|
|
||||||
// Once we've run out of screen, stop drawing cards
|
// Once we've run out of screen, stop drawing cards
|
||||||
// Depending on tiles / rotation, this may be before we hit maxCards
|
// Depending on tiles / rotation, this may be before we hit maxCards
|
||||||
if (cardTopY > height())
|
if (cardTopY > height()) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,20 +246,20 @@ void InkHUD::NodeListApplet::drawSignalIndicator(int16_t x, int16_t y, uint16_t
|
|||||||
|
|
||||||
constexpr float paddingW = 0.1; // Either side
|
constexpr float paddingW = 0.1; // Either side
|
||||||
constexpr float paddingH = 0.1; // Above and below
|
constexpr float paddingH = 0.1; // Above and below
|
||||||
constexpr float gutterW = 0.1; // Between bars
|
constexpr float gutterX = 0.1; // Between bars
|
||||||
|
|
||||||
constexpr float barHRel[] = {0.3, 0.5, 0.7, 1.0}; // Heights of the signal bars, relative to the tallest
|
constexpr float barHRel[] = {0.3, 0.5, 0.7, 1.0}; // Heights of the signal bars, relative to the talleest
|
||||||
constexpr uint8_t barCount = 4; // How many bars we draw. Reference only: changing value won't change the count.
|
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
|
// Dynamically calculate the width of the bars, and height of the rightmost, relative to other dimensions
|
||||||
float barW = (1.0 - (paddingW + ((barCount - 1) * gutterW) + paddingW)) / barCount;
|
float barW = (1.0 - (paddingW + ((barCount - 1) * gutterX) + paddingW)) / barCount;
|
||||||
float barHMax = 1.0 - (paddingH + paddingH);
|
float barHMax = 1.0 - (paddingH + paddingH);
|
||||||
|
|
||||||
// Draw signal bar rectangles, then placeholder lines once strength reached
|
// Draw signal bar rectangles, then placeholder lines once strength reached
|
||||||
for (uint8_t i = 0; i < barCount; i++) {
|
for (uint8_t i = 0; i < barCount; i++) {
|
||||||
// Coords for this specific bar
|
// Co-ords for this specific bar
|
||||||
float barH = barHMax * barHRel[i];
|
float barH = barHMax * barHRel[i];
|
||||||
float barX = paddingW + (i * (gutterW + barW));
|
float barX = paddingW + (i * (gutterX + barW));
|
||||||
float barY = paddingH + (barHMax - barH);
|
float barY = paddingH + (barHMax - barH);
|
||||||
|
|
||||||
// Rasterize to px coords at the last moment
|
// Rasterize to px coords at the last moment
|
||||||
|
|||||||
@@ -23,16 +23,13 @@ Used by the "Recents" and "Heard" applets. Possibly more in future?
|
|||||||
|
|
||||||
#include "graphics/niche/InkHUD/Applet.h"
|
#include "graphics/niche/InkHUD/Applet.h"
|
||||||
|
|
||||||
#include "main.h"
|
|
||||||
|
|
||||||
namespace NicheGraphics::InkHUD
|
namespace NicheGraphics::InkHUD
|
||||||
{
|
{
|
||||||
|
|
||||||
class NodeListApplet : public Applet, public MeshModule
|
class NodeListApplet : public Applet, public MeshModule
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
// Info needed to draw a node card to the list
|
// Info used to draw one card to the node list
|
||||||
// - generated each time we hear a node
|
|
||||||
struct CardInfo {
|
struct CardInfo {
|
||||||
static constexpr uint8_t HOPS_UNKNOWN = -1;
|
static constexpr uint8_t HOPS_UNKNOWN = -1;
|
||||||
static constexpr uint32_t DISTANCE_UNKNOWN = -1;
|
static constexpr uint32_t DISTANCE_UNKNOWN = -1;
|
||||||
@@ -40,31 +37,31 @@ class NodeListApplet : public Applet, public MeshModule
|
|||||||
NodeNum nodeNum = 0;
|
NodeNum nodeNum = 0;
|
||||||
SignalStrength signal = SignalStrength::SIGNAL_UNKNOWN;
|
SignalStrength signal = SignalStrength::SIGNAL_UNKNOWN;
|
||||||
uint32_t distanceMeters = DISTANCE_UNKNOWN;
|
uint32_t distanceMeters = DISTANCE_UNKNOWN;
|
||||||
uint8_t hopsAway = HOPS_UNKNOWN;
|
uint8_t hopsAway = HOPS_UNKNOWN; // Unknown
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
NodeListApplet(const char *name);
|
NodeListApplet(const char *name);
|
||||||
|
|
||||||
void onRender() override;
|
void onRender() override;
|
||||||
|
|
||||||
bool wantPacket(const meshtastic_MeshPacket *p) override;
|
// MeshModule overrides
|
||||||
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
virtual bool wantPacket(const meshtastic_MeshPacket *p) override;
|
||||||
|
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void handleParsed(CardInfo c) = 0; // Tell derived applet that we heard a node
|
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; // Ask derived class what the applet's title should be
|
virtual std::string getHeaderText() = 0; // Title for the applet's header. Todo: get this info another way?
|
||||||
|
|
||||||
uint8_t maxCards(); // Max number of cards which could ever fit on screen
|
uint8_t maxCards(); // Calculate the maximum number of cards an applet could ever display
|
||||||
|
|
||||||
std::deque<CardInfo> cards; // Cards to be rendered. Derived applet fills this.
|
std::deque<CardInfo> cards; // Derived applet places cards here, for this base applet to render
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void drawSignalIndicator(int16_t x, int16_t y, uint16_t w, uint16_t h,
|
// UI element: a "mobile phone" style signal indicator
|
||||||
SignalStrength signal); // Draw a "mobile phone" style signal indicator
|
void drawSignalIndicator(int16_t x, int16_t y, uint16_t w, uint16_t h, SignalStrength signal);
|
||||||
|
|
||||||
// Card Dimensions
|
// Dimensions for drawing
|
||||||
// - for rendering and for maxCards calc
|
// Used for render, and also for maxCards calc
|
||||||
const uint8_t cardMarginH = fontSmall.lineHeight() / 2; // Gap between cards
|
const uint8_t cardMarginH = fontSmall.lineHeight() / 2; // Gap between cards
|
||||||
const uint16_t cardH = fontLarge.lineHeight() + fontSmall.lineHeight() + cardMarginH; // Height of card
|
const uint16_t cardH = fontLarge.lineHeight() + fontSmall.lineHeight() + cardMarginH; // Height of card
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ ProcessMessage InkHUD::NewMsgExampleApplet::handleReceived(const meshtastic_Mesh
|
|||||||
// We should always be ready to draw
|
// We should always be ready to draw
|
||||||
void InkHUD::NewMsgExampleApplet::onRender()
|
void InkHUD::NewMsgExampleApplet::onRender()
|
||||||
{
|
{
|
||||||
|
setFont(fontSmall);
|
||||||
|
|
||||||
printAt(0, 0, "Example: NewMsg", LEFT, TOP); // Print top-left corner of text at (0,0)
|
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
|
int16_t centerX = X(0.5); // Same as width() / 2
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class NewMsgExampleApplet : public Applet, public SinglePortModule
|
|||||||
|
|
||||||
// Store info from handleReceived
|
// Store info from handleReceived
|
||||||
bool haveMessage = false;
|
bool haveMessage = false;
|
||||||
NodeNum fromWho = 0;
|
NodeNum fromWho;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace NicheGraphics::InkHUD
|
} // namespace NicheGraphics::InkHUD
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
|
|
||||||
using namespace NicheGraphics;
|
using namespace NicheGraphics;
|
||||||
|
|
||||||
InkHUD::BatteryIconApplet::BatteryIconApplet()
|
void InkHUD::BatteryIconApplet::onActivate()
|
||||||
{
|
{
|
||||||
// Show at boot, if user has previously enabled the feature
|
// Show at boot, if user has previously enabled the feature
|
||||||
if (settings->optionalFeatures.batteryIcon)
|
if (settings.optionalFeatures.batteryIcon)
|
||||||
bringToForeground();
|
bringToForeground();
|
||||||
|
|
||||||
// Register to our have BatteryIconApplet::onPowerStatusUpdate method called when new power info is available
|
// Register to our have BatteryIconApplet::onPowerStatusUpdate method called when new power info is available
|
||||||
@@ -15,6 +15,12 @@ InkHUD::BatteryIconApplet::BatteryIconApplet()
|
|||||||
powerStatusObserver.observe(&powerStatus->onNewStatus);
|
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,
|
// 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.
|
// 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
|
// Otherwise could be 30s before new status update, with weird battery value displayed
|
||||||
@@ -35,7 +41,7 @@ int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *sta
|
|||||||
// If rounded value has changed, trigger a display update
|
// 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()
|
// 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
|
// 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();
|
requestUpdate();
|
||||||
|
|
||||||
// Store the new value
|
// Store the new value
|
||||||
|
|||||||
@@ -11,22 +11,24 @@ It should be optional, enabled by the on-screen menu
|
|||||||
|
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
#include "graphics/niche/InkHUD/Applet.h"
|
||||||
|
|
||||||
#include "PowerStatus.h"
|
#include "PowerStatus.h"
|
||||||
|
|
||||||
namespace NicheGraphics::InkHUD
|
namespace NicheGraphics::InkHUD
|
||||||
{
|
{
|
||||||
|
|
||||||
class BatteryIconApplet : public SystemApplet
|
class BatteryIconApplet : public Applet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BatteryIconApplet();
|
|
||||||
|
|
||||||
void onRender() override;
|
void onRender() override;
|
||||||
|
|
||||||
|
void onActivate() override;
|
||||||
|
void onDeactivate() override;
|
||||||
|
|
||||||
int onPowerStatusUpdate(const meshtastic::Status *status); // Called when new info about battery is available
|
int onPowerStatusUpdate(const meshtastic::Status *status); // Called when new info about battery is available
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
// Get informed when new information about the battery is available (via onPowerStatusUpdate method)
|
// Get informed when new information about the battery is available (via onPowerStatusUpdate method)
|
||||||
CallbackObserver<BatteryIconApplet, const meshtastic::Status *> powerStatusObserver =
|
CallbackObserver<BatteryIconApplet, const meshtastic::Status *> powerStatusObserver =
|
||||||
CallbackObserver<BatteryIconApplet, const meshtastic::Status *>(this, &BatteryIconApplet::onPowerStatusUpdate);
|
CallbackObserver<BatteryIconApplet, const meshtastic::Status *>(this, &BatteryIconApplet::onPowerStatusUpdate);
|
||||||
|
|||||||
@@ -2,22 +2,15 @@
|
|||||||
|
|
||||||
#include "./LogoApplet.h"
|
#include "./LogoApplet.h"
|
||||||
|
|
||||||
#include "mesh/NodeDB.h"
|
|
||||||
|
|
||||||
using namespace NicheGraphics;
|
using namespace NicheGraphics;
|
||||||
|
|
||||||
InkHUD::LogoApplet::LogoApplet() : concurrency::OSThread("LogoApplet")
|
InkHUD::LogoApplet::LogoApplet() : concurrency::OSThread("LogoApplet")
|
||||||
{
|
{
|
||||||
OSThread::setIntervalFromNow(8 * 1000UL);
|
// Don't autostart the runOnce() timer
|
||||||
OSThread::enabled = true;
|
OSThread::disable();
|
||||||
|
|
||||||
textLeft = "";
|
// Grab the WindowManager singleton, for convenience
|
||||||
textRight = "";
|
windowManager = WindowManager::getInstance();
|
||||||
textTitle = xstr(APP_VERSION_SHORT);
|
|
||||||
fontTitle = fontSmall;
|
|
||||||
|
|
||||||
bringToForeground();
|
|
||||||
// This is then drawn with a FULL refresh by Renderer::begin
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::LogoApplet::onRender()
|
void InkHUD::LogoApplet::onRender()
|
||||||
@@ -55,24 +48,53 @@ void InkHUD::LogoApplet::onRender()
|
|||||||
|
|
||||||
void InkHUD::LogoApplet::onForeground()
|
void InkHUD::LogoApplet::onForeground()
|
||||||
{
|
{
|
||||||
SystemApplet::lockRendering = true;
|
// If another applet has locked the display, ask it to exit
|
||||||
SystemApplet::lockRequests = true;
|
Applet *other = windowManager->whoLocked();
|
||||||
SystemApplet::handleInput = true; // We don't actually use this input. Just blocking other applets from using it.
|
if (other != nullptr)
|
||||||
|
other->sendToBackground();
|
||||||
|
|
||||||
|
windowManager->claimFullscreen(this); // Take ownership of fullscreen tile
|
||||||
|
windowManager->lock(this); // Prevent other applets from requesting updates
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::LogoApplet::onBackground()
|
void InkHUD::LogoApplet::onBackground()
|
||||||
{
|
{
|
||||||
SystemApplet::lockRendering = false;
|
OSThread::disable(); // Disable auto-dismiss timer, in case applet was dismissed early (sendToBackground from outside class)
|
||||||
SystemApplet::lockRequests = false;
|
|
||||||
SystemApplet::handleInput = false;
|
windowManager->releaseFullscreen(); // Relinquish ownership of fullscreen tile
|
||||||
|
windowManager->unlock(this); // Allow normal user applet update requests to resume
|
||||||
|
|
||||||
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
// 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
|
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin displaying the screen which is shown at shutdown
|
// Begin displaying the screen which is shown at shutdown
|
||||||
void InkHUD::LogoApplet::onShutdown()
|
// Needs EInk::await after calling this method, to ensure display updates before shutdown
|
||||||
|
void InkHUD::LogoApplet::showShutdownScreen()
|
||||||
{
|
{
|
||||||
textLeft = "";
|
textLeft = "";
|
||||||
textRight = "";
|
textRight = "";
|
||||||
@@ -80,13 +102,7 @@ void InkHUD::LogoApplet::onShutdown()
|
|||||||
fontTitle = fontLarge;
|
fontTitle = fontLarge;
|
||||||
|
|
||||||
bringToForeground();
|
bringToForeground();
|
||||||
// This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update
|
requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Already requested, just upgrading to FULL
|
||||||
}
|
|
||||||
|
|
||||||
int32_t InkHUD::LogoApplet::runOnce()
|
|
||||||
{
|
|
||||||
sendToBackground();
|
|
||||||
return OSThread::disable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -12,19 +12,24 @@
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
#include "concurrency/OSThread.h"
|
#include "concurrency/OSThread.h"
|
||||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
#include "graphics/niche/InkHUD/Applet.h"
|
||||||
|
|
||||||
namespace NicheGraphics::InkHUD
|
namespace NicheGraphics::InkHUD
|
||||||
{
|
{
|
||||||
|
|
||||||
class LogoApplet : public SystemApplet, public concurrency::OSThread
|
class LogoApplet : public Applet, public concurrency::OSThread
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LogoApplet();
|
LogoApplet();
|
||||||
void onRender() override;
|
void onRender() override;
|
||||||
void onForeground() override;
|
void onForeground() override;
|
||||||
void onBackground() override;
|
void onBackground() override;
|
||||||
void onShutdown() 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();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int32_t runOnce() override;
|
int32_t runOnce() override;
|
||||||
@@ -33,6 +38,8 @@ class LogoApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
std::string textRight;
|
std::string textRight;
|
||||||
std::string textTitle;
|
std::string textTitle;
|
||||||
AppletFont fontTitle;
|
AppletFont fontTitle;
|
||||||
|
|
||||||
|
WindowManager *windowManager = nullptr; // For convenience
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace NicheGraphics::InkHUD
|
} // namespace NicheGraphics::InkHUD
|
||||||
|
|||||||
@@ -2,11 +2,9 @@
|
|||||||
|
|
||||||
#include "./MenuApplet.h"
|
#include "./MenuApplet.h"
|
||||||
|
|
||||||
|
#include "PowerStatus.h"
|
||||||
#include "RTC.h"
|
#include "RTC.h"
|
||||||
|
|
||||||
#include "airtime.h"
|
|
||||||
#include "power.h"
|
|
||||||
|
|
||||||
using namespace NicheGraphics;
|
using namespace NicheGraphics;
|
||||||
|
|
||||||
static constexpr uint8_t MENU_TIMEOUT_SEC = 60; // How many seconds before menu auto-closes
|
static constexpr uint8_t MENU_TIMEOUT_SEC = 60; // How many seconds before menu auto-closes
|
||||||
@@ -19,16 +17,23 @@ InkHUD::MenuApplet::MenuApplet() : concurrency::OSThread("MenuApplet")
|
|||||||
{
|
{
|
||||||
// No timer tasks at boot
|
// No timer tasks at boot
|
||||||
OSThread::disable();
|
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,
|
// Note: don't get instance if we're not actually using the backlight,
|
||||||
// or else you will unintentionally instantiate it
|
// or else you will unintentionally instantiate it
|
||||||
if (settings->optionalMenuItems.backlight) {
|
if (settings.optionalMenuItems.backlight) {
|
||||||
backlight = Drivers::LatchingBacklight::getInstance();
|
backlight = Drivers::LatchingBacklight::getInstance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::MenuApplet::onActivate() {}
|
|
||||||
|
|
||||||
void InkHUD::MenuApplet::onForeground()
|
void InkHUD::MenuApplet::onForeground()
|
||||||
{
|
{
|
||||||
// We do need this before we render, but we can optimize by just calculating it once now
|
// We do need this before we render, but we can optimize by just calculating it once now
|
||||||
@@ -40,23 +45,21 @@ void InkHUD::MenuApplet::onForeground()
|
|||||||
// If device has a backlight which isn't controlled by aux button:
|
// If device has a backlight which isn't controlled by aux button:
|
||||||
// backlight on always when menu opens.
|
// backlight on always when menu opens.
|
||||||
// Courtesy to T-Echo users who removed the capacitive touch button
|
// Courtesy to T-Echo users who removed the capacitive touch button
|
||||||
if (settings->optionalMenuItems.backlight) {
|
if (settings.optionalMenuItems.backlight) {
|
||||||
assert(backlight);
|
assert(backlight);
|
||||||
if (!backlight->isOn())
|
if (!backlight->isOn())
|
||||||
backlight->peek();
|
backlight->peek();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent user applets requesting update while menu is open
|
// Prevent user applets requested update while menu is open
|
||||||
// Handle button input with this applet
|
windowManager->lock(this);
|
||||||
SystemApplet::lockRequests = true;
|
|
||||||
SystemApplet::handleInput = true;
|
|
||||||
|
|
||||||
// Begin the auto-close timeout
|
// Begin the auto-close timeout
|
||||||
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
||||||
OSThread::enabled = true;
|
OSThread::enabled = true;
|
||||||
|
|
||||||
// Upgrade the refresh to FAST, for guaranteed responsiveness
|
// Upgrade the refresh to FAST, for guaranteed responsiveness
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
windowManager->forceUpdate(EInk::UpdateTypes::FAST);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::MenuApplet::onBackground()
|
void InkHUD::MenuApplet::onBackground()
|
||||||
@@ -64,7 +67,7 @@ void InkHUD::MenuApplet::onBackground()
|
|||||||
// If device has a backlight which isn't controlled by aux button:
|
// If device has a backlight which isn't controlled by aux button:
|
||||||
// Item in options submenu allows keeping backlight on after menu is closed
|
// 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 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);
|
assert(backlight);
|
||||||
if (!backlight->isLatched())
|
if (!backlight->isLatched())
|
||||||
backlight->off();
|
backlight->off();
|
||||||
@@ -74,8 +77,7 @@ void InkHUD::MenuApplet::onBackground()
|
|||||||
OSThread::disable();
|
OSThread::disable();
|
||||||
|
|
||||||
// Resume normal rendering and button behavior of user applets
|
// Resume normal rendering and button behavior of user applets
|
||||||
SystemApplet::lockRequests = false;
|
windowManager->unlock(this);
|
||||||
SystemApplet::handleInput = false;
|
|
||||||
|
|
||||||
// Restore the user applet whose tile we borrowed
|
// Restore the user applet whose tile we borrowed
|
||||||
if (borrowedTileOwner)
|
if (borrowedTileOwner)
|
||||||
@@ -85,8 +87,8 @@ void InkHUD::MenuApplet::onBackground()
|
|||||||
borrowedTileOwner = nullptr;
|
borrowedTileOwner = nullptr;
|
||||||
|
|
||||||
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
// 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 upgrade from UNSPECIFIED to FAST, to ensure responsiveness when exiting menu
|
// We're only updating here to ugrade from UNSPECIFIED to FAST, to ensure responsiveness when exiting menu
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
|
windowManager->forceUpdate(EInk::UpdateTypes::FAST);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the menu
|
// Open the menu
|
||||||
@@ -138,35 +140,43 @@ void InkHUD::MenuApplet::execute(MenuItem item)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case NEXT_TILE:
|
case NEXT_TILE:
|
||||||
inkhud->nextTile();
|
// 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;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ROTATE:
|
case ROTATE:
|
||||||
inkhud->rotate();
|
settings.rotation = (settings.rotation + 1) % 4;
|
||||||
|
windowManager->changeLayout();
|
||||||
|
// requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Would update regardless; just selecting FULL
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LAYOUT:
|
case LAYOUT:
|
||||||
// Todo: smarter incrementing of tile count
|
// Todo: smarter incrementing of tile count
|
||||||
settings->userTiles.count++;
|
settings.userTiles.count++;
|
||||||
|
|
||||||
if (settings->userTiles.count == 3) // Skip 3 tiles: not done yet
|
if (settings.userTiles.count == 3) // Skip 3 tiles: not done yet
|
||||||
settings->userTiles.count++;
|
settings.userTiles.count++;
|
||||||
|
|
||||||
if (settings->userTiles.count > settings->userTiles.maxCount) // Loop around if tile count now too high
|
if (settings.userTiles.count > settings.userTiles.maxCount) // Loop around if tile count now too high
|
||||||
settings->userTiles.count = 1;
|
settings.userTiles.count = 1;
|
||||||
|
|
||||||
inkhud->updateLayout();
|
windowManager->changeLayout();
|
||||||
|
// requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Would update regardless; just selecting FULL
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TOGGLE_APPLET:
|
case TOGGLE_APPLET:
|
||||||
settings->userApplets.active[cursor] = !settings->userApplets.active[cursor];
|
settings.userApplets.active[cursor] = !settings.userApplets.active[cursor];
|
||||||
inkhud->updateAppletSelection();
|
windowManager->changeActivatedApplets();
|
||||||
// requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Select FULL, seeing how this action doesn't auto exit
|
// requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Select FULL, seeing how this action doesn't auto exit
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ACTIVATE_APPLETS:
|
case ACTIVATE_APPLETS:
|
||||||
// Todo: remove this action? Already handled by TOGGLE_APPLET?
|
// Todo: remove this action? Already handled by TOGGLE_APPLET?
|
||||||
inkhud->updateAppletSelection();
|
windowManager->changeActivatedApplets();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TOGGLE_AUTOSHOW_APPLET:
|
case TOGGLE_AUTOSHOW_APPLET:
|
||||||
@@ -175,14 +185,14 @@ void InkHUD::MenuApplet::execute(MenuItem item)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case TOGGLE_NOTIFICATIONS:
|
case TOGGLE_NOTIFICATIONS:
|
||||||
settings->optionalFeatures.notifications = !settings->optionalFeatures.notifications;
|
settings.optionalFeatures.notifications = !settings.optionalFeatures.notifications;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SET_RECENTS:
|
case SET_RECENTS:
|
||||||
// Set value of settings.recentlyActiveSeconds
|
// Set value of settings.recentlyActiveSeconds
|
||||||
// Uses menu cursor to read RECENTS_OPTIONS_MINUTES array (defined at top of this file)
|
// 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]));
|
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;
|
break;
|
||||||
|
|
||||||
case SHUTDOWN:
|
case SHUTDOWN:
|
||||||
@@ -192,7 +202,7 @@ void InkHUD::MenuApplet::execute(MenuItem item)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case TOGGLE_BATTERY_ICON:
|
case TOGGLE_BATTERY_ICON:
|
||||||
inkhud->toggleBatteryIcon();
|
windowManager->toggleBatteryIcon();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TOGGLE_BACKLIGHT:
|
case TOGGLE_BACKLIGHT:
|
||||||
@@ -223,13 +233,13 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
switch (page) {
|
switch (page) {
|
||||||
case ROOT:
|
case ROOT:
|
||||||
// Optional: next applet
|
// 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("Next Tile", MenuAction::NEXT_TILE, MenuPage::ROOT)); // Only if multiple applets shown
|
||||||
|
|
||||||
// items.push_back(MenuItem("Send", MenuPage::SEND)); // TODO
|
// items.push_back(MenuItem("Send", MenuPage::SEND)); // TODO
|
||||||
items.push_back(MenuItem("Options", MenuPage::OPTIONS));
|
items.push_back(MenuItem("Options", MenuPage::OPTIONS));
|
||||||
// items.push_back(MenuItem("Display Off", MenuPage::EXIT)); // TODO
|
// items.push_back(MenuItem("Display Off", MenuPage::EXIT)); // TODO
|
||||||
items.push_back(MenuItem("Save & Shut Down", MenuAction::SHUTDOWN));
|
items.push_back(MenuItem("Save & Shutdown", MenuAction::SHUTDOWN));
|
||||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -242,7 +252,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
|
|
||||||
case OPTIONS:
|
case OPTIONS:
|
||||||
// Optional: backlight
|
// Optional: backlight
|
||||||
if (settings->optionalMenuItems.backlight) {
|
if (settings.optionalMenuItems.backlight) {
|
||||||
assert(backlight);
|
assert(backlight);
|
||||||
items.push_back(MenuItem(backlight->isLatched() ? "Backlight Off" : "Keep Backlight On", // Label
|
items.push_back(MenuItem(backlight->isLatched() ? "Backlight Off" : "Keep Backlight On", // Label
|
||||||
MenuAction::TOGGLE_BACKLIGHT, // Action
|
MenuAction::TOGGLE_BACKLIGHT, // Action
|
||||||
@@ -253,13 +263,13 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
items.push_back(MenuItem("Applets", MenuPage::APPLETS));
|
items.push_back(MenuItem("Applets", MenuPage::APPLETS));
|
||||||
items.push_back(MenuItem("Auto-show", MenuPage::AUTOSHOW));
|
items.push_back(MenuItem("Auto-show", MenuPage::AUTOSHOW));
|
||||||
items.push_back(MenuItem("Recents Duration", MenuPage::RECENTS));
|
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("Layout", MenuAction::LAYOUT, MenuPage::OPTIONS));
|
||||||
items.push_back(MenuItem("Rotate", MenuAction::ROTATE, MenuPage::OPTIONS));
|
items.push_back(MenuItem("Rotate", MenuAction::ROTATE, MenuPage::OPTIONS));
|
||||||
items.push_back(MenuItem("Notifications", MenuAction::TOGGLE_NOTIFICATIONS, MenuPage::OPTIONS,
|
items.push_back(MenuItem("Notifications", MenuAction::TOGGLE_NOTIFICATIONS, MenuPage::OPTIONS,
|
||||||
&settings->optionalFeatures.notifications));
|
&settings.optionalFeatures.notifications));
|
||||||
items.push_back(MenuItem("Battery Icon", MenuAction::TOGGLE_BATTERY_ICON, MenuPage::OPTIONS,
|
items.push_back(
|
||||||
&settings->optionalFeatures.batteryIcon));
|
MenuItem("Battery Icon", MenuAction::TOGGLE_BATTERY_ICON, MenuPage::OPTIONS, &settings.optionalFeatures.batteryIcon));
|
||||||
|
|
||||||
// TODO - GPS and Wifi switches
|
// TODO - GPS and Wifi switches
|
||||||
/*
|
/*
|
||||||
@@ -319,6 +329,9 @@ void InkHUD::MenuApplet::onRender()
|
|||||||
if (items.size() == 0)
|
if (items.size() == 0)
|
||||||
LOG_ERROR("Empty Menu");
|
LOG_ERROR("Empty Menu");
|
||||||
|
|
||||||
|
// Testing only
|
||||||
|
setFont(fontSmall);
|
||||||
|
|
||||||
// Dimensions for the slots where we will draw menuItems
|
// Dimensions for the slots where we will draw menuItems
|
||||||
const float padding = 0.05;
|
const float padding = 0.05;
|
||||||
const uint16_t itemH = fontSmall.lineHeight() * 2;
|
const uint16_t itemH = fontSmall.lineHeight() * 2;
|
||||||
@@ -384,7 +397,7 @@ void InkHUD::MenuApplet::onRender()
|
|||||||
|
|
||||||
// Testing only: circle instead of check box
|
// Testing only: circle instead of check box
|
||||||
if (item.checkState) {
|
if (item.checkState) {
|
||||||
const uint16_t cbWH = fontSmall.lineHeight(); // Checkbox: width / height
|
const uint16_t cbWH = fontSmall.lineHeight(); // Checbox: width / height
|
||||||
const int16_t cbL = itemR - X(padding) - cbWH; // Checkbox: left
|
const int16_t cbL = itemR - X(padding) - cbWH; // Checkbox: left
|
||||||
const int16_t cbT = center - (cbWH / 2); // Checkbox : top
|
const int16_t cbT = center - (cbWH / 2); // Checkbox : top
|
||||||
// Checkbox ticked
|
// Checkbox ticked
|
||||||
@@ -450,9 +463,9 @@ void InkHUD::MenuApplet::populateAppletPage()
|
|||||||
{
|
{
|
||||||
assert(items.size() == 0);
|
assert(items.size() == 0);
|
||||||
|
|
||||||
for (uint8_t i = 0; i < inkhud->userApplets.size(); i++) {
|
for (uint8_t i = 0; i < windowManager->getAppletCount(); i++) {
|
||||||
const char *name = inkhud->userApplets.at(i)->name;
|
const char *name = windowManager->getAppletName(i);
|
||||||
bool *isActive = &(settings->userApplets.active[i]);
|
bool *isActive = &(settings.userApplets.active[i]);
|
||||||
items.push_back(MenuItem(name, MenuAction::TOGGLE_APPLET, MenuPage::APPLETS, isActive));
|
items.push_back(MenuItem(name, MenuAction::TOGGLE_APPLET, MenuPage::APPLETS, isActive));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -464,11 +477,11 @@ void InkHUD::MenuApplet::populateAutoshowPage()
|
|||||||
{
|
{
|
||||||
assert(items.size() == 0);
|
assert(items.size() == 0);
|
||||||
|
|
||||||
for (uint8_t i = 0; i < inkhud->userApplets.size(); i++) {
|
for (uint8_t i = 0; i < windowManager->getAppletCount(); i++) {
|
||||||
// Only add a menu item if applet is active
|
// Only add a menu item if applet is active
|
||||||
if (settings->userApplets.active[i]) {
|
if (settings.userApplets.active[i]) {
|
||||||
const char *name = inkhud->userApplets.at(i)->name;
|
const char *name = windowManager->getAppletName(i);
|
||||||
bool *isActive = &(settings->userApplets.autoshow[i]);
|
bool *isActive = &(settings.userApplets.autoshow[i]);
|
||||||
items.push_back(MenuItem(name, MenuAction::TOGGLE_AUTOSHOW_APPLET, MenuPage::AUTOSHOW, isActive));
|
items.push_back(MenuItem(name, MenuAction::TOGGLE_AUTOSHOW_APPLET, MenuPage::AUTOSHOW, isActive));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -586,10 +599,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
|
// 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
|
// This is inefficient, as we do actually have to render the panel to determine the height
|
||||||
// It solves a catch-22 situation, where slotCount needs to know panel height, and panel height needs to know slotCount
|
// It solves a catch-22 situtation, where slotCount needs to know panel height, and panel height needs to know slotCount
|
||||||
uint16_t InkHUD::MenuApplet::getSystemInfoPanelHeight()
|
uint16_t InkHUD::MenuApplet::getSystemInfoPanelHeight()
|
||||||
{
|
{
|
||||||
// Render *far* off screen
|
// Render *waay* off screen
|
||||||
uint16_t height = 0;
|
uint16_t height = 0;
|
||||||
drawSystemInfoPanel(INT16_MIN, INT16_MIN, 1, &height);
|
drawSystemInfoPanel(INT16_MIN, INT16_MIN, 1, &height);
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,8 @@
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
#include "graphics/niche/Drivers/Backlight/LatchingBacklight.h"
|
#include "graphics/niche/Drivers/Backlight/LatchingBacklight.h"
|
||||||
#include "graphics/niche/InkHUD/InkHUD.h"
|
#include "graphics/niche/InkHUD/Applet.h"
|
||||||
#include "graphics/niche/InkHUD/Persistence.h"
|
#include "graphics/niche/InkHUD/WindowManager.h"
|
||||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
|
||||||
|
|
||||||
#include "./MenuItem.h"
|
#include "./MenuItem.h"
|
||||||
#include "./MenuPage.h"
|
#include "./MenuPage.h"
|
||||||
@@ -17,7 +16,7 @@ namespace NicheGraphics::InkHUD
|
|||||||
|
|
||||||
class Applet;
|
class Applet;
|
||||||
|
|
||||||
class MenuApplet : public SystemApplet, public concurrency::OSThread
|
class MenuApplet : public Applet, public concurrency::OSThread
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MenuApplet();
|
MenuApplet();
|
||||||
@@ -31,8 +30,6 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
void show(Tile *t); // Open the menu, onto a user tile
|
void show(Tile *t); // Open the menu, onto a user tile
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton
|
|
||||||
|
|
||||||
int32_t runOnce() override;
|
int32_t runOnce() override;
|
||||||
|
|
||||||
void execute(MenuItem item); // Perform the MenuAction associated with a MenuItem, if any
|
void execute(MenuItem item); // Perform the MenuAction associated with a MenuItem, if any
|
||||||
@@ -44,7 +41,7 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width,
|
void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width,
|
||||||
uint16_t *height = nullptr); // Info panel at top of root menu
|
uint16_t *height = nullptr); // Info panel at top of root menu
|
||||||
|
|
||||||
MenuPage currentPage = MenuPage::ROOT;
|
MenuPage currentPage;
|
||||||
uint8_t cursor = 0; // Which menu item is currently highlighted
|
uint8_t cursor = 0; // Which menu item is currently highlighted
|
||||||
bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection)
|
bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection)
|
||||||
|
|
||||||
@@ -53,6 +50,9 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
|
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
|
||||||
|
|
||||||
Applet *borrowedTileOwner = nullptr; // Which applet we have temporarily replaced while displaying menu
|
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
|
} // namespace NicheGraphics::InkHUD
|
||||||
|
|||||||
@@ -3,20 +3,22 @@
|
|||||||
#include "./NotificationApplet.h"
|
#include "./NotificationApplet.h"
|
||||||
|
|
||||||
#include "./Notification.h"
|
#include "./Notification.h"
|
||||||
#include "graphics/niche/InkHUD/Persistence.h"
|
|
||||||
|
|
||||||
#include "meshUtils.h"
|
|
||||||
#include "modules/TextMessageModule.h"
|
|
||||||
|
|
||||||
#include "RTC.h"
|
#include "RTC.h"
|
||||||
|
|
||||||
using namespace NicheGraphics;
|
using namespace NicheGraphics;
|
||||||
|
|
||||||
InkHUD::NotificationApplet::NotificationApplet()
|
void InkHUD::NotificationApplet::onActivate()
|
||||||
{
|
{
|
||||||
textMessageObserver.observe(textMessageModule);
|
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
|
// 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()
|
// 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)
|
int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p)
|
||||||
@@ -26,7 +28,7 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket
|
|||||||
|
|
||||||
// Abort if feature disabled
|
// Abort if feature disabled
|
||||||
// This is a bit clumsy, but avoids complicated handling when the feature is enabled / 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;
|
return 0;
|
||||||
|
|
||||||
// Abort if this is an outgoing message
|
// Abort if this is an outgoing message
|
||||||
@@ -34,7 +36,7 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// Abort if message was only an "emoji reaction"
|
// Abort if message was only an "emoji reaction"
|
||||||
// Possibly some implementation of this in future?
|
// Possibly some implemetation of this in future?
|
||||||
if (p->decoded.emoji)
|
if (p->decoded.emoji)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
@@ -53,16 +55,13 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket
|
|||||||
n.sender = p->from;
|
n.sender = p->from;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close an old notification, if shown
|
|
||||||
dismiss();
|
|
||||||
|
|
||||||
// Check if we should display the notification
|
// Check if we should display the notification
|
||||||
// A foreground applet might already be displaying this info
|
// A foreground applet might already be displaying this info
|
||||||
hasNotification = true;
|
hasNotification = true;
|
||||||
currentNotification = n;
|
currentNotification = n;
|
||||||
if (isApproved()) {
|
if (isApproved()) {
|
||||||
bringToForeground();
|
bringToForeground();
|
||||||
inkhud->forceUpdate();
|
WindowManager::getInstance()->forceUpdate();
|
||||||
} else
|
} else
|
||||||
hasNotification = false; // Clear the pending notification: it was rejected
|
hasNotification = false; // Clear the pending notification: it was rejected
|
||||||
|
|
||||||
@@ -77,6 +76,8 @@ void InkHUD::NotificationApplet::onRender()
|
|||||||
// We do need to do this with the battery though, as it is an "overlay"
|
// We do need to do this with the battery though, as it is an "overlay"
|
||||||
fillRect(0, 0, width(), height(), WHITE);
|
fillRect(0, 0, width(), height(), WHITE);
|
||||||
|
|
||||||
|
setFont(fontSmall);
|
||||||
|
|
||||||
// Padding (horizontal)
|
// Padding (horizontal)
|
||||||
const uint16_t padW = 4;
|
const uint16_t padW = 4;
|
||||||
|
|
||||||
@@ -136,28 +137,6 @@ void InkHUD::NotificationApplet::onRender()
|
|||||||
printThick(textM, height() / 2, text, 2, 1);
|
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
|
// 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,
|
// Called internally when we first get a "notifiable event", and then again before render,
|
||||||
// in case autoshow swapped which applet was displayed
|
// in case autoshow swapped which applet was displayed
|
||||||
@@ -169,13 +148,7 @@ bool InkHUD::NotificationApplet::isApproved()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask all visible user applets for approval
|
return WindowManager::getInstance()->approveNotification(currentNotification);
|
||||||
for (Applet *ua : inkhud->userApplets) {
|
|
||||||
if (ua->isForeground() && !ua->approveNotification(currentNotification))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark that the notification should no-longer be rendered
|
// Mark that the notification should no-longer be rendered
|
||||||
@@ -207,8 +180,7 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila
|
|||||||
bool isBroadcast = currentNotification.type == Notification::Type::NOTIFICATION_MESSAGE_BROADCAST;
|
bool isBroadcast = currentNotification.type == Notification::Type::NOTIFICATION_MESSAGE_BROADCAST;
|
||||||
|
|
||||||
// Pick source of message
|
// Pick source of message
|
||||||
MessageStore::Message *message =
|
MessageStore::Message *message = isBroadcast ? &latestMessage.broadcast : &latestMessage.dm;
|
||||||
isBroadcast ? &inkhud->persistence->latestMessage.broadcast : &inkhud->persistence->latestMessage.dm;
|
|
||||||
|
|
||||||
// Find info about the sender
|
// Find info about the sender
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(message->sender);
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(message->sender);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
/*
|
/*
|
||||||
|
|
||||||
Pop-up notification bar, on screen top edge
|
Pop-up notification bar, on screen top edge
|
||||||
Displays information we feel is important, but which is not shown on currently focused applet(s)
|
Displays information we feel is important, but which is not shown on currently focussed applet(s)
|
||||||
E.g.: messages, while viewing map, etc
|
E.g.: messages, while viewing map, etc
|
||||||
|
|
||||||
Feature should be optional; enable disable via on-screen menu
|
Feature should be optional; enable disable via on-screen menu
|
||||||
@@ -16,21 +16,17 @@ Feature should be optional; enable disable via on-screen menu
|
|||||||
|
|
||||||
#include "concurrency/OSThread.h"
|
#include "concurrency/OSThread.h"
|
||||||
|
|
||||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
#include "graphics/niche/InkHUD/Applet.h"
|
||||||
|
|
||||||
namespace NicheGraphics::InkHUD
|
namespace NicheGraphics::InkHUD
|
||||||
{
|
{
|
||||||
|
|
||||||
class NotificationApplet : public SystemApplet
|
class NotificationApplet : public Applet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NotificationApplet();
|
|
||||||
|
|
||||||
void onRender() override;
|
void onRender() override;
|
||||||
void onForeground() override;
|
void onActivate() override;
|
||||||
void onBackground() override;
|
void onDeactivate() override;
|
||||||
void onButtonShortPress() override;
|
|
||||||
void onButtonLongPress() override;
|
|
||||||
|
|
||||||
int onReceiveTextMessage(const meshtastic_MeshPacket *p);
|
int onReceiveTextMessage(const meshtastic_MeshPacket *p);
|
||||||
|
|
||||||
@@ -44,8 +40,8 @@ class NotificationApplet : public SystemApplet
|
|||||||
|
|
||||||
std::string getNotificationText(uint16_t widthAvailable); // Get text for notification, to suit screen width
|
std::string getNotificationText(uint16_t widthAvailable); // Get text for notification, to suit screen width
|
||||||
|
|
||||||
bool hasNotification = false; // Only used for assert. Todo: remove?
|
bool hasNotification = false; // Only used for assert. Todo: remove?
|
||||||
Notification currentNotification = Notification(); // Set when something notification-worthy happens. Used by render()
|
Notification currentNotification; // Set when something notification-worthy happens. Used by render()
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace NicheGraphics::InkHUD
|
} // namespace NicheGraphics::InkHUD
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ using namespace NicheGraphics;
|
|||||||
|
|
||||||
InkHUD::PairingApplet::PairingApplet()
|
InkHUD::PairingApplet::PairingApplet()
|
||||||
{
|
{
|
||||||
bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
|
// Grab the window manager singleton, for convenience
|
||||||
|
windowManager = WindowManager::getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::PairingApplet::onRender()
|
void InkHUD::PairingApplet::onRender()
|
||||||
@@ -30,22 +31,34 @@ void InkHUD::PairingApplet::onRender()
|
|||||||
printAt(X(0.5), Y(0.75), name, CENTER, MIDDLE);
|
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()
|
void InkHUD::PairingApplet::onForeground()
|
||||||
{
|
{
|
||||||
// Prevent most other applets from requesting update, and skip their rendering entirely
|
// If another applet has locked the display, ask it to exit
|
||||||
// Another system applet with a higher precedence can potentially ignore this
|
Applet *other = windowManager->whoLocked();
|
||||||
SystemApplet::lockRendering = true;
|
if (other != nullptr)
|
||||||
SystemApplet::lockRequests = true;
|
other->sendToBackground();
|
||||||
|
|
||||||
|
windowManager->claimFullscreen(this); // Take ownership of the fullscreen tile
|
||||||
|
windowManager->lock(this); // Prevent user applets from requesting update
|
||||||
}
|
}
|
||||||
void InkHUD::PairingApplet::onBackground()
|
void InkHUD::PairingApplet::onBackground()
|
||||||
{
|
{
|
||||||
// Allow normal update behavior to resume
|
windowManager->releaseFullscreen(); // Relinquish ownership of the fullscreen tile
|
||||||
SystemApplet::lockRendering = false;
|
windowManager->unlock(this); // Allow normal user applet update requests to resume
|
||||||
SystemApplet::lockRequests = false;
|
|
||||||
|
|
||||||
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
|
// 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
|
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
windowManager->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *status)
|
int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *status)
|
||||||
@@ -62,6 +75,12 @@ int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *sta
|
|||||||
// Store the passkey for rendering
|
// Store the passkey for rendering
|
||||||
passkey = bluetoothStatus->getPasskey();
|
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
|
// Show pairing screen
|
||||||
bringToForeground();
|
bringToForeground();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,19 +10,19 @@
|
|||||||
|
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
#include "graphics/niche/InkHUD/Applet.h"
|
||||||
|
|
||||||
#include "main.h"
|
|
||||||
|
|
||||||
namespace NicheGraphics::InkHUD
|
namespace NicheGraphics::InkHUD
|
||||||
{
|
{
|
||||||
|
|
||||||
class PairingApplet : public SystemApplet
|
class PairingApplet : public Applet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PairingApplet();
|
PairingApplet();
|
||||||
|
|
||||||
void onRender() override;
|
void onRender() override;
|
||||||
|
void onActivate() override;
|
||||||
|
void onDeactivate() override;
|
||||||
void onForeground() override;
|
void onForeground() override;
|
||||||
void onBackground() override;
|
void onBackground() override;
|
||||||
|
|
||||||
@@ -34,6 +34,8 @@ class PairingApplet : public SystemApplet
|
|||||||
CallbackObserver<PairingApplet, const meshtastic::Status *>(this, &PairingApplet::onBluetoothStatusUpdate);
|
CallbackObserver<PairingApplet, const meshtastic::Status *>(this, &PairingApplet::onBluetoothStatusUpdate);
|
||||||
|
|
||||||
std::string passkey = ""; // Passkey. Six digits, possibly with leading zeros
|
std::string passkey = ""; // Passkey. Six digits, possibly with leading zeros
|
||||||
|
|
||||||
|
WindowManager *windowManager = nullptr; // For convenience. Set in constructor.
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace NicheGraphics::InkHUD
|
} // namespace NicheGraphics::InkHUD
|
||||||
|
|||||||
@@ -4,6 +4,14 @@
|
|||||||
|
|
||||||
using namespace NicheGraphics;
|
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()
|
void InkHUD::PlaceholderApplet::onRender()
|
||||||
{
|
{
|
||||||
// This placeholder applet fills its area with sparse diagonal lines
|
// This placeholder applet fills its area with sparse diagonal lines
|
||||||
|
|||||||
@@ -9,19 +9,20 @@ Fills the area with diagonal lines
|
|||||||
|
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
#include "graphics/niche/InkHUD/Applet.h"
|
||||||
|
|
||||||
namespace NicheGraphics::InkHUD
|
namespace NicheGraphics::InkHUD
|
||||||
{
|
{
|
||||||
|
|
||||||
class PlaceholderApplet : public SystemApplet
|
class PlaceholderApplet : public Applet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
PlaceholderApplet();
|
||||||
void onRender() override;
|
void onRender() override;
|
||||||
|
|
||||||
// Note: onForeground, onBackground, and wantsToRender are not meaningful for this applet.
|
// Note: onForeground, onBackground, and wantsToRender are not meaningful for this applet.
|
||||||
// The window manager decides when and where it should be rendered
|
// The window manager decides when and where it should be rendered
|
||||||
// It may be drawn to several different tiles during an Renderer::render call
|
// It may be drawn to several different tiles during on WindowManager::render call
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace NicheGraphics::InkHUD
|
} // namespace NicheGraphics::InkHUD
|
||||||
|
|||||||
@@ -2,44 +2,12 @@
|
|||||||
|
|
||||||
#include "./TipsApplet.h"
|
#include "./TipsApplet.h"
|
||||||
|
|
||||||
#include "graphics/niche/InkHUD/Persistence.h"
|
|
||||||
|
|
||||||
#include "main.h"
|
|
||||||
|
|
||||||
using namespace NicheGraphics;
|
using namespace NicheGraphics;
|
||||||
|
|
||||||
InkHUD::TipsApplet::TipsApplet()
|
InkHUD::TipsApplet::TipsApplet()
|
||||||
{
|
{
|
||||||
// Decide which tips (if any) should be shown to user after the boot screen
|
// Grab the window manager singleton, for convenience
|
||||||
|
windowManager = WindowManager::getInstance();
|
||||||
// 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()
|
void InkHUD::TipsApplet::onRender()
|
||||||
@@ -85,7 +53,7 @@ void InkHUD::TipsApplet::onRender()
|
|||||||
|
|
||||||
setFont(fontSmall);
|
setFont(fontSmall);
|
||||||
std::string shutdown;
|
std::string shutdown;
|
||||||
shutdown += "Before removing power, please shut down from InkHUD menu, or a client app. \n";
|
shutdown += "Before removing power, please shutdown from InkHUD menu, or a client app. \n";
|
||||||
shutdown += "\n";
|
shutdown += "\n";
|
||||||
shutdown += "This ensures data is saved.";
|
shutdown += "This ensures data is saved.";
|
||||||
printWrapped(0, fontLarge.lineHeight() * 1.5, width(), shutdown);
|
printWrapped(0, fontLarge.lineHeight() * 1.5, width(), shutdown);
|
||||||
@@ -185,31 +153,51 @@ void InkHUD::TipsApplet::renderWelcome()
|
|||||||
printAt(X(0.5), Y(1), "Press button to continue", CENTER, BOTTOM);
|
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()
|
void InkHUD::TipsApplet::onForeground()
|
||||||
{
|
{
|
||||||
// Prevent most other applets from requesting update, and skip their rendering entirely
|
windowManager->lock(this);
|
||||||
// Another system applet with a higher precedence can potentially ignore this
|
windowManager->claimFullscreen(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()
|
void InkHUD::TipsApplet::onBackground()
|
||||||
{
|
{
|
||||||
// Allow normal update behavior to resume
|
windowManager->releaseFullscreen();
|
||||||
SystemApplet::lockRendering = false;
|
windowManager->unlock(this);
|
||||||
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() {}
|
void InkHUD::TipsApplet::onActivate()
|
||||||
|
{
|
||||||
|
// Decide which tips (if any) should be shown to user after the boot screen
|
||||||
|
|
||||||
// While our SystemApplet::handleInput flag is true
|
// 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
|
||||||
void InkHUD::TipsApplet::onButtonShortPress()
|
void InkHUD::TipsApplet::onButtonShortPress()
|
||||||
{
|
{
|
||||||
tipQueue.pop_front();
|
tipQueue.pop_front();
|
||||||
@@ -218,15 +206,15 @@ void InkHUD::TipsApplet::onButtonShortPress()
|
|||||||
if (tipQueue.empty()) {
|
if (tipQueue.empty()) {
|
||||||
// Record that user has now seen the "tutorial" set of tips
|
// Record that user has now seen the "tutorial" set of tips
|
||||||
// Don't show them on subsequent boots
|
// Don't show them on subsequent boots
|
||||||
if (settings->tips.firstBoot) {
|
if (settings.tips.firstBoot) {
|
||||||
settings->tips.firstBoot = false;
|
settings.tips.firstBoot = false;
|
||||||
inkhud->persistence->saveSettings();
|
saveDataToFlash();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close applet, and full refresh to clean the screen
|
// 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
|
// Need to force update, because our request would be ignored otherwise, as we are now background
|
||||||
sendToBackground();
|
sendToBackground();
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
windowManager->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// More tips left
|
// More tips left
|
||||||
@@ -234,4 +222,13 @@ void InkHUD::TipsApplet::onButtonShortPress()
|
|||||||
requestUpdate();
|
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
|
#endif
|
||||||
@@ -12,12 +12,12 @@
|
|||||||
|
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
#include "graphics/niche/InkHUD/Applet.h"
|
||||||
|
|
||||||
namespace NicheGraphics::InkHUD
|
namespace NicheGraphics::InkHUD
|
||||||
{
|
{
|
||||||
|
|
||||||
class TipsApplet : public SystemApplet
|
class TipsApplet : public Applet
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
enum class Tip {
|
enum class Tip {
|
||||||
@@ -37,6 +37,7 @@ class TipsApplet : public SystemApplet
|
|||||||
void onForeground() override;
|
void onForeground() override;
|
||||||
void onBackground() override;
|
void onBackground() override;
|
||||||
void onButtonShortPress() override;
|
void onButtonShortPress() override;
|
||||||
|
void onLockAvailable() override; // Reopen if interrupted by bluetooth pairing
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void renderWelcome(); // Very first screen of tutorial
|
void renderWelcome(); // Very first screen of tutorial
|
||||||
|
|||||||
@@ -41,12 +41,14 @@ int InkHUD::AllMessageApplet::onReceiveTextMessage(const meshtastic_MeshPacket *
|
|||||||
|
|
||||||
void InkHUD::AllMessageApplet::onRender()
|
void InkHUD::AllMessageApplet::onRender()
|
||||||
{
|
{
|
||||||
|
setFont(fontSmall);
|
||||||
|
|
||||||
// Find newest message, regardless of whether DM or broadcast
|
// Find newest message, regardless of whether DM or broadcast
|
||||||
MessageStore::Message *message;
|
MessageStore::Message *message;
|
||||||
if (latestMessage->wasBroadcast)
|
if (latestMessage.wasBroadcast)
|
||||||
message = &latestMessage->broadcast;
|
message = &latestMessage.broadcast;
|
||||||
else
|
else
|
||||||
message = &latestMessage->dm;
|
message = &latestMessage.dm;
|
||||||
|
|
||||||
// Short circuit: no text message
|
// Short circuit: no text message
|
||||||
if (!message->sender) {
|
if (!message->sender) {
|
||||||
|
|||||||
@@ -44,8 +44,10 @@ int InkHUD::DMApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p)
|
|||||||
|
|
||||||
void InkHUD::DMApplet::onRender()
|
void InkHUD::DMApplet::onRender()
|
||||||
{
|
{
|
||||||
|
setFont(fontSmall);
|
||||||
|
|
||||||
// Abort if no text message
|
// Abort if no text message
|
||||||
if (!latestMessage->dm.sender) {
|
if (!latestMessage.dm.sender) {
|
||||||
printAt(X(0.5), Y(0.5), "No DMs", CENTER, MIDDLE);
|
printAt(X(0.5), Y(0.5), "No DMs", CENTER, MIDDLE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -61,7 +63,7 @@ void InkHUD::DMApplet::onRender()
|
|||||||
|
|
||||||
// RX Time
|
// RX Time
|
||||||
// - if valid
|
// - if valid
|
||||||
std::string timeString = getTimeString(latestMessage->dm.timestamp);
|
std::string timeString = getTimeString(latestMessage.dm.timestamp);
|
||||||
if (timeString.length() > 0) {
|
if (timeString.length() > 0) {
|
||||||
header += timeString;
|
header += timeString;
|
||||||
header += ": ";
|
header += ": ";
|
||||||
@@ -70,14 +72,14 @@ void InkHUD::DMApplet::onRender()
|
|||||||
// Sender's id
|
// Sender's id
|
||||||
// - shortname, if available, or
|
// - shortname, if available, or
|
||||||
// - node id
|
// - node id
|
||||||
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(latestMessage->dm.sender);
|
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(latestMessage.dm.sender);
|
||||||
if (sender && sender->has_user) {
|
if (sender && sender->has_user) {
|
||||||
header += sender->user.short_name;
|
header += sender->user.short_name;
|
||||||
header += " (";
|
header += " (";
|
||||||
header += sender->user.long_name;
|
header += sender->user.long_name;
|
||||||
header += ")";
|
header += ")";
|
||||||
} else
|
} else
|
||||||
header += hexifyNodeNum(latestMessage->dm.sender);
|
header += hexifyNodeNum(latestMessage.dm.sender);
|
||||||
|
|
||||||
// Draw a "standard" applet header
|
// Draw a "standard" applet header
|
||||||
drawHeader(header);
|
drawHeader(header);
|
||||||
@@ -101,14 +103,14 @@ void InkHUD::DMApplet::onRender()
|
|||||||
|
|
||||||
// Determine size if printed large
|
// Determine size if printed large
|
||||||
setFont(fontLarge);
|
setFont(fontLarge);
|
||||||
uint32_t textHeight = getWrappedTextHeight(0, width(), latestMessage->dm.text);
|
uint32_t textHeight = getWrappedTextHeight(0, width(), latestMessage.dm.text);
|
||||||
|
|
||||||
// If too large, swap to small font
|
// If too large, swap to small font
|
||||||
if (textHeight + textTop > (uint32_t)height()) // (compare signed and unsigned)
|
if (textHeight + textTop > (uint32_t)height()) // (compare signed and unsigned)
|
||||||
setFont(fontSmall);
|
setFont(fontSmall);
|
||||||
|
|
||||||
// Print text
|
// Print text
|
||||||
printWrapped(0, textTop, width(), latestMessage->dm.text);
|
printWrapped(0, textTop, width(), latestMessage.dm.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't show notifications for direct messages when our applet is displayed
|
// Don't show notifications for direct messages when our applet is displayed
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ class PositionsApplet : public MapApplet, public SinglePortModule
|
|||||||
protected:
|
protected:
|
||||||
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
||||||
|
|
||||||
NodeNum lastFrom = 0; // Sender of most recent (non-local) position packet
|
NodeNum lastFrom; // Sender of most recent (non-local) position packet
|
||||||
float lastLat = 0.0;
|
float lastLat;
|
||||||
float lastLng = 0.0;
|
float lastLng;
|
||||||
float lastHopsAway = 0;
|
float lastHopsAway;
|
||||||
|
|
||||||
float ourLastLat = 0.0; // Info about the most recent (non-local) position packet
|
float ourLastLat; // Info about the most recent (non-local) position packet
|
||||||
float ourLastLng = 0.0; // Info about most recent *local* position
|
float ourLastLng; // Info about most recent *local* position
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace NicheGraphics::InkHUD
|
} // namespace NicheGraphics::InkHUD
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ bool InkHUD::RecentsListApplet::isActive(uint32_t seenAtMs)
|
|||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
uint32_t secsAgo = (now - seenAtMs) / 1000UL; // millis() overflow safe
|
uint32_t secsAgo = (now - seenAtMs) / 1000UL; // millis() overflow safe
|
||||||
|
|
||||||
return (secsAgo < settings->recentlyActiveSeconds);
|
return (secsAgo < settings.recentlyActiveSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text to be shown at top of applet
|
// Text to be shown at top of applet
|
||||||
@@ -134,7 +134,7 @@ std::string InkHUD::RecentsListApplet::getHeaderText()
|
|||||||
|
|
||||||
// Print the length of our "Recents" time-window
|
// Print the length of our "Recents" time-window
|
||||||
text += "Last ";
|
text += "Last ";
|
||||||
text += to_string(settings->recentlyActiveSeconds / 60);
|
text += to_string(settings.recentlyActiveSeconds / 60);
|
||||||
text += " mins";
|
text += " mins";
|
||||||
|
|
||||||
// Print the node count
|
// Print the node count
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ InkHUD::ThreadedMessageApplet::ThreadedMessageApplet(uint8_t channelIndex) : cha
|
|||||||
|
|
||||||
void InkHUD::ThreadedMessageApplet::onRender()
|
void InkHUD::ThreadedMessageApplet::onRender()
|
||||||
{
|
{
|
||||||
|
setFont(fontSmall);
|
||||||
|
|
||||||
// =============
|
// =============
|
||||||
// Draw a header
|
// Draw a header
|
||||||
// =============
|
// =============
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class Applet;
|
|||||||
class ThreadedMessageApplet : public Applet
|
class ThreadedMessageApplet : public Applet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit ThreadedMessageApplet(uint8_t channelIndex);
|
ThreadedMessageApplet(uint8_t channelIndex);
|
||||||
ThreadedMessageApplet() = delete;
|
ThreadedMessageApplet() = delete;
|
||||||
|
|
||||||
void onRender() override;
|
void onRender() override;
|
||||||
|
|||||||
@@ -1,179 +0,0 @@
|
|||||||
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
|
||||||
|
|
||||||
#include "./Events.h"
|
|
||||||
|
|
||||||
#include "RTC.h"
|
|
||||||
#include "modules/TextMessageModule.h"
|
|
||||||
#include "sleep.h"
|
|
||||||
|
|
||||||
#include "./Applet.h"
|
|
||||||
#include "./SystemApplet.h"
|
|
||||||
|
|
||||||
using namespace NicheGraphics;
|
|
||||||
|
|
||||||
InkHUD::Events::Events()
|
|
||||||
{
|
|
||||||
// Get convenient references
|
|
||||||
inkhud = InkHUD::getInstance();
|
|
||||||
settings = &inkhud->persistence->settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::Events::begin()
|
|
||||||
{
|
|
||||||
// Register our callbacks for the various events
|
|
||||||
|
|
||||||
deepSleepObserver.observe(¬ifyDeepSleep);
|
|
||||||
rebootObserver.observe(¬ifyReboot);
|
|
||||||
textMessageObserver.observe(textMessageModule);
|
|
||||||
#ifdef ARCH_ESP32
|
|
||||||
lightSleepObserver.observe(¬ifyLightSleep);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::Events::onButtonShort()
|
|
||||||
{
|
|
||||||
// Check which system applet wants to handle the button press (if any)
|
|
||||||
SystemApplet *consumer = nullptr;
|
|
||||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
|
||||||
if (sa->handleInput) {
|
|
||||||
consumer = sa;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no system applet is handling input, default behavior instead is to cycle applets
|
|
||||||
if (consumer)
|
|
||||||
consumer->onButtonShortPress();
|
|
||||||
else
|
|
||||||
inkhud->nextApplet();
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::Events::onButtonLong()
|
|
||||||
{
|
|
||||||
// Check which system applet wants to handle the button press (if any)
|
|
||||||
SystemApplet *consumer = nullptr;
|
|
||||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
|
||||||
if (sa->handleInput) {
|
|
||||||
consumer = sa;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no system applet is handling input, default behavior instead is to open the menu
|
|
||||||
if (consumer)
|
|
||||||
consumer->onButtonLongPress();
|
|
||||||
else
|
|
||||||
inkhud->openMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback for deepSleepObserver
|
|
||||||
// Returns 0 to signal that we agree to sleep now
|
|
||||||
int InkHUD::Events::beforeDeepSleep(void *unused)
|
|
||||||
{
|
|
||||||
// Notify all applets that we're shutting down
|
|
||||||
for (Applet *ua : inkhud->userApplets) {
|
|
||||||
ua->onDeactivate();
|
|
||||||
ua->onShutdown();
|
|
||||||
}
|
|
||||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
|
||||||
// Note: no onDeactivate. System applets are always active.
|
|
||||||
sa->onShutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
// User has successful executed a safe shutdown
|
|
||||||
// We don't need to nag at boot anymore
|
|
||||||
settings->tips.safeShutdownSeen = true;
|
|
||||||
|
|
||||||
inkhud->persistence->saveSettings();
|
|
||||||
inkhud->persistence->saveLatestMessage();
|
|
||||||
|
|
||||||
// LogoApplet::onShutdown will have requested an update, to draw the shutdown screen
|
|
||||||
// Draw that now, and wait here until the update is complete
|
|
||||||
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL, false);
|
|
||||||
|
|
||||||
return 0; // We agree: deep sleep now
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback for rebootObserver
|
|
||||||
// Same as shutdown, without drawing the logoApplet
|
|
||||||
// Makes sure we don't lose message history / InkHUD config
|
|
||||||
int InkHUD::Events::beforeReboot(void *unused)
|
|
||||||
{
|
|
||||||
|
|
||||||
// Notify all applets that we're "shutting down"
|
|
||||||
// They don't need to know that it's really a reboot
|
|
||||||
for (Applet *a : inkhud->userApplets) {
|
|
||||||
a->onDeactivate();
|
|
||||||
a->onShutdown();
|
|
||||||
}
|
|
||||||
for (Applet *sa : inkhud->systemApplets) {
|
|
||||||
// Note: no onDeactivate. System applets are always active.
|
|
||||||
sa->onShutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
inkhud->persistence->saveSettings();
|
|
||||||
inkhud->persistence->saveLatestMessage();
|
|
||||||
|
|
||||||
// Note: no forceUpdate call here
|
|
||||||
// Because OSThread will not be given another chance to run before reboot, this means that no display update will occur
|
|
||||||
|
|
||||||
return 0; // No special status to report. Ignored anyway by this Observable
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback when a new text message is received
|
|
||||||
// Caches the most recently received message, for use by applets
|
|
||||||
// Rx does not trigger a save to flash, however the data *will* be saved alongside other during shutdown, etc.
|
|
||||||
// Note: this is different from devicestate.rx_text_message, which may contain an *outgoing* message
|
|
||||||
int InkHUD::Events::onReceiveTextMessage(const meshtastic_MeshPacket *packet)
|
|
||||||
{
|
|
||||||
// Short circuit: don't store outgoing messages
|
|
||||||
if (getFrom(packet) == nodeDB->getNodeNum())
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Short circuit: don't store "emoji reactions"
|
|
||||||
// Possibly some implementation of this in future?
|
|
||||||
if (packet->decoded.emoji)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Determine whether the message is broadcast or a DM
|
|
||||||
// Store this info to prevent confusion after a reboot
|
|
||||||
// Avoids need to compare timestamps, because of situation where "future" messages block newly received, if time not set
|
|
||||||
inkhud->persistence->latestMessage.wasBroadcast = isBroadcast(packet->to);
|
|
||||||
|
|
||||||
// Pick the appropriate variable to store the message in
|
|
||||||
MessageStore::Message *storedMessage = inkhud->persistence->latestMessage.wasBroadcast
|
|
||||||
? &inkhud->persistence->latestMessage.broadcast
|
|
||||||
: &inkhud->persistence->latestMessage.dm;
|
|
||||||
|
|
||||||
// Store nodenum of the sender
|
|
||||||
// Applets can use this to fetch user data from nodedb, if they want
|
|
||||||
storedMessage->sender = packet->from;
|
|
||||||
|
|
||||||
// Store the time (epoch seconds) when message received
|
|
||||||
storedMessage->timestamp = getValidTime(RTCQuality::RTCQualityDevice, true); // Current RTC time
|
|
||||||
|
|
||||||
// Store the channel
|
|
||||||
// - (potentially) used to determine whether notification shows
|
|
||||||
// - (potentially) used to determine which applet to focus
|
|
||||||
storedMessage->channelIndex = packet->channel;
|
|
||||||
|
|
||||||
// Store the text
|
|
||||||
// Need to specify manually how many bytes, because source not null-terminated
|
|
||||||
storedMessage->text =
|
|
||||||
std::string(&packet->decoded.payload.bytes[0], &packet->decoded.payload.bytes[packet->decoded.payload.size]);
|
|
||||||
|
|
||||||
return 0; // Tell caller to continue notifying other observers. (No reason to abort this event)
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
|
||||||
// Callback for lightSleepObserver
|
|
||||||
// Make sure the display is not partway through an update when we begin light sleep
|
|
||||||
// This is because some displays require active input from us to terminate the update process, and protect the panel hardware
|
|
||||||
int InkHUD::Events::beforeLightSleep(void *unused)
|
|
||||||
{
|
|
||||||
inkhud->awaitUpdate();
|
|
||||||
return 0; // No special status to report. Ignored anyway by this Observable
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Handles non-specific events for InkHUD
|
|
||||||
|
|
||||||
Individual applets are responsible for listening for their own events via the module api etc,
|
|
||||||
however this class handles general events which concern InkHUD as a whole, e.g. shutdown
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "configuration.h"
|
|
||||||
|
|
||||||
#include "Observer.h"
|
|
||||||
|
|
||||||
#include "./InkHUD.h"
|
|
||||||
#include "./Persistence.h"
|
|
||||||
|
|
||||||
namespace NicheGraphics::InkHUD
|
|
||||||
{
|
|
||||||
|
|
||||||
class Events
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Events();
|
|
||||||
void begin();
|
|
||||||
|
|
||||||
void onButtonShort(); // User button: short press
|
|
||||||
void onButtonLong(); // User button: long press
|
|
||||||
|
|
||||||
int beforeDeepSleep(void *unused); // Prepare for shutdown
|
|
||||||
int beforeReboot(void *unused); // Prepare for reboot
|
|
||||||
int onReceiveTextMessage(const meshtastic_MeshPacket *packet); // Store most recent text message
|
|
||||||
#ifdef ARCH_ESP32
|
|
||||||
int beforeLightSleep(void *unused); // Prepare for light sleep
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private:
|
|
||||||
// For convenience
|
|
||||||
InkHUD *inkhud = nullptr;
|
|
||||||
Persistence::Settings *settings = nullptr;
|
|
||||||
|
|
||||||
// Get notified when the system is shutting down
|
|
||||||
CallbackObserver<Events, void *> deepSleepObserver = CallbackObserver<Events, void *>(this, &Events::beforeDeepSleep);
|
|
||||||
|
|
||||||
// Get notified when the system is rebooting
|
|
||||||
CallbackObserver<Events, void *> rebootObserver = CallbackObserver<Events, void *>(this, &Events::beforeReboot);
|
|
||||||
|
|
||||||
// Cache *incoming* text messages, for use by applets
|
|
||||||
CallbackObserver<Events, const meshtastic_MeshPacket *> textMessageObserver =
|
|
||||||
CallbackObserver<Events, const meshtastic_MeshPacket *>(this, &Events::onReceiveTextMessage);
|
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
|
||||||
// Get notified when the system is entering light sleep
|
|
||||||
CallbackObserver<Events, void *> lightSleepObserver = CallbackObserver<Events, void *>(this, &Events::beforeLightSleep);
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace NicheGraphics::InkHUD
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
|
||||||
|
|
||||||
#include "./InkHUD.h"
|
|
||||||
|
|
||||||
#include "./Applet.h"
|
|
||||||
#include "./Events.h"
|
|
||||||
#include "./Persistence.h"
|
|
||||||
#include "./Renderer.h"
|
|
||||||
#include "./SystemApplet.h"
|
|
||||||
#include "./Tile.h"
|
|
||||||
#include "./WindowManager.h"
|
|
||||||
|
|
||||||
using namespace NicheGraphics;
|
|
||||||
|
|
||||||
// Get or create the singleton
|
|
||||||
InkHUD::InkHUD *InkHUD::InkHUD::getInstance()
|
|
||||||
{
|
|
||||||
// Create the singleton instance of our class, if not yet done
|
|
||||||
static InkHUD *instance = nullptr;
|
|
||||||
if (!instance) {
|
|
||||||
instance = new InkHUD;
|
|
||||||
|
|
||||||
instance->persistence = new Persistence;
|
|
||||||
instance->windowManager = new WindowManager;
|
|
||||||
instance->renderer = new Renderer;
|
|
||||||
instance->events = new Events;
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect the (fully set-up) E-Ink driver to InkHUD
|
|
||||||
// Should happen in your variant's nicheGraphics.h file, before InkHUD::begin is called
|
|
||||||
void InkHUD::InkHUD::setDriver(Drivers::EInk *driver)
|
|
||||||
{
|
|
||||||
renderer->setDriver(driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the target number of FAST display updates in a row, before a FULL update is used for display health
|
|
||||||
// This value applies only to updates with an UNSPECIFIED update type
|
|
||||||
// If explicitly requested FAST updates exceed this target, the stressMultiplier parameter determines how many
|
|
||||||
// subsequent FULL updates will be performed, in an attempt to restore the display's health
|
|
||||||
void InkHUD::InkHUD::setDisplayResilience(uint8_t fastPerFull, float stressMultiplier)
|
|
||||||
{
|
|
||||||
renderer->setDisplayResilience(fastPerFull, stressMultiplier);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register a user applet with InkHUD
|
|
||||||
// A variant's nicheGraphics.h file should instantiate your chosen applets, then pass them to this method
|
|
||||||
// Passing an applet to this method is all that is required to make it available to the user in your InkHUD build
|
|
||||||
void InkHUD::InkHUD::addApplet(const char *name, Applet *a, bool defaultActive, bool defaultAutoshow, uint8_t onTile)
|
|
||||||
{
|
|
||||||
windowManager->addApplet(name, a, defaultActive, defaultAutoshow, onTile);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start InkHUD!
|
|
||||||
// Call this only after you have configured InkHUD
|
|
||||||
void InkHUD::InkHUD::begin()
|
|
||||||
{
|
|
||||||
persistence->loadSettings();
|
|
||||||
persistence->loadLatestMessage();
|
|
||||||
|
|
||||||
windowManager->begin();
|
|
||||||
events->begin();
|
|
||||||
renderer->begin();
|
|
||||||
// LogoApplet shows boot screen here
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call this when your user button gets a short press
|
|
||||||
// Should be connected to an input source in nicheGraphics.h (NicheGraphics::Inputs::TwoButton?)
|
|
||||||
void InkHUD::InkHUD::shortpress()
|
|
||||||
{
|
|
||||||
events->onButtonShort();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call this when your user button gets a long press
|
|
||||||
// Should be connected to an input source in nicheGraphics.h (NicheGraphics::Inputs::TwoButton?)
|
|
||||||
void InkHUD::InkHUD::longpress()
|
|
||||||
{
|
|
||||||
events->onButtonLong();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cycle the next user applet to the foreground
|
|
||||||
// Only activated applets are cycled
|
|
||||||
// If user has a multi-applet layout, the applets will cycle on the "focused tile"
|
|
||||||
void InkHUD::InkHUD::nextApplet()
|
|
||||||
{
|
|
||||||
windowManager->nextApplet();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show the menu (on the the focused tile)
|
|
||||||
// The applet previously displayed there will be restored once the menu closes
|
|
||||||
void InkHUD::InkHUD::openMenu()
|
|
||||||
{
|
|
||||||
windowManager->openMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
// In layouts where multiple applets are shown at once, change which tile is focused
|
|
||||||
// The focused tile in the one which cycles applets on button short press, and displays menu on long press
|
|
||||||
void InkHUD::InkHUD::nextTile()
|
|
||||||
{
|
|
||||||
windowManager->nextTile();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rotate the display image by 90 degrees
|
|
||||||
void InkHUD::InkHUD::rotate()
|
|
||||||
{
|
|
||||||
windowManager->rotate();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show / hide the battery indicator in top-right
|
|
||||||
void InkHUD::InkHUD::toggleBatteryIcon()
|
|
||||||
{
|
|
||||||
windowManager->toggleBatteryIcon();
|
|
||||||
}
|
|
||||||
|
|
||||||
// An applet asking for the display to be updated
|
|
||||||
// This does not occur immediately
|
|
||||||
// Instead, rendering is scheduled ASAP, for the next Renderer::runOnce call
|
|
||||||
// This allows multiple applets to observe the same event, and then share the same opportunity to update
|
|
||||||
// Applets should requestUpdate, whether or not they are currently displayed ("foreground")
|
|
||||||
// This is because they *might* be automatically brought to foreground by WindowManager::autoshow
|
|
||||||
void InkHUD::InkHUD::requestUpdate()
|
|
||||||
{
|
|
||||||
renderer->requestUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Demand that the display be updated
|
|
||||||
// Ignores all diplomacy:
|
|
||||||
// - the display *will* update
|
|
||||||
// - the specified update type *will* be used
|
|
||||||
// If the async parameter is false, code flow is blocked while the update takes place
|
|
||||||
void InkHUD::InkHUD::forceUpdate(EInk::UpdateTypes type, bool async)
|
|
||||||
{
|
|
||||||
renderer->forceUpdate(type, async);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for any in-progress display update to complete before continuing
|
|
||||||
void InkHUD::InkHUD::awaitUpdate()
|
|
||||||
{
|
|
||||||
renderer->awaitUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask the window manager to potentially bring a different user applet to foreground
|
|
||||||
// An applet will be brought to foreground if it has just received new and relevant info
|
|
||||||
// For Example: AllMessagesApplet has just received a new text message
|
|
||||||
// Permission for this autoshow behavior is granted by the user, on an applet-by-applet basis
|
|
||||||
// If autoshow brings an applet to foreground, an InkHUD notification will not be generated for the same event
|
|
||||||
void InkHUD::InkHUD::autoshow()
|
|
||||||
{
|
|
||||||
windowManager->autoshow();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tell the window manager that the Persistence::Settings value for applet activation has changed,
|
|
||||||
// and that it should reconfigure accordingly.
|
|
||||||
// This is triggered at boot, or when the user enables / disabled applets via the on-screen menu
|
|
||||||
void InkHUD::InkHUD::updateAppletSelection()
|
|
||||||
{
|
|
||||||
windowManager->changeActivatedApplets();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tell the window manager that the Persistence::Settings value for layout or rotation has changed,
|
|
||||||
// and that it should reconfigure accordingly.
|
|
||||||
// This is triggered at boot, or by rotate / layout options in the on-screen menu
|
|
||||||
void InkHUD::InkHUD::updateLayout()
|
|
||||||
{
|
|
||||||
windowManager->changeLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Width of the display, in the context of the current rotation
|
|
||||||
uint16_t InkHUD::InkHUD::width()
|
|
||||||
{
|
|
||||||
return renderer->width();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Height of the display, in the context of the current rotation
|
|
||||||
uint16_t InkHUD::InkHUD::height()
|
|
||||||
{
|
|
||||||
return renderer->height();
|
|
||||||
}
|
|
||||||
|
|
||||||
// A collection of any user tiles which do not have a valid user applet
|
|
||||||
// This can occur in various situations, such as when a user enables fewer applets than their layout has tiles
|
|
||||||
// The tiles (and which regions the occupy) are private information of the window manager
|
|
||||||
// The renderer needs to know which regions (if any) are empty,
|
|
||||||
// in order to fill them with a "placeholder" pattern.
|
|
||||||
// -- There may be a tidier way to accomplish this --
|
|
||||||
std::vector<InkHUD::Tile *> InkHUD::InkHUD::getEmptyTiles()
|
|
||||||
{
|
|
||||||
return windowManager->getEmptyTiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a system applet by its name
|
|
||||||
// This isn't particularly elegant, but it does avoid:
|
|
||||||
// - passing around a big set of references
|
|
||||||
// - having two sets of references (systemApplet vector for iteration)
|
|
||||||
InkHUD::SystemApplet *InkHUD::InkHUD::getSystemApplet(const char *name)
|
|
||||||
{
|
|
||||||
for (SystemApplet *sa : systemApplets) {
|
|
||||||
if (strcmp(name, sa->name) == 0)
|
|
||||||
return sa;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(false); // Invalid name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Place a pixel into the image buffer
|
|
||||||
// The x and y coordinates are in the context of the current display rotation
|
|
||||||
// - Applets pass "relative" pixels to tiles
|
|
||||||
// - Tiles pass translated pixels to this method
|
|
||||||
// - this methods (Renderer) places rotated pixels into the image buffer
|
|
||||||
// This method provides the final formatting step required. The image buffer is suitable for writing to display
|
|
||||||
void InkHUD::InkHUD::drawPixel(int16_t x, int16_t y, Color c)
|
|
||||||
{
|
|
||||||
renderer->handlePixel(x, y, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
InkHUD's main class
|
|
||||||
- singleton
|
|
||||||
- mediator between the various components
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "configuration.h"
|
|
||||||
|
|
||||||
#include "graphics/niche/Drivers/EInk/EInk.h"
|
|
||||||
|
|
||||||
#include "./AppletFont.h"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace NicheGraphics::InkHUD
|
|
||||||
{
|
|
||||||
|
|
||||||
// Color, understood by display controller IC (as bit values)
|
|
||||||
// Also suitable for use as AdafruitGFX colors
|
|
||||||
enum Color : uint8_t {
|
|
||||||
BLACK = 0,
|
|
||||||
WHITE = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
class Applet;
|
|
||||||
class Events;
|
|
||||||
class Persistence;
|
|
||||||
class Renderer;
|
|
||||||
class SystemApplet;
|
|
||||||
class Tile;
|
|
||||||
class WindowManager;
|
|
||||||
|
|
||||||
class InkHUD
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static InkHUD *getInstance(); // Access to this singleton class
|
|
||||||
|
|
||||||
// Configuration
|
|
||||||
// - before InkHUD::begin, in variant nicheGraphics.h,
|
|
||||||
|
|
||||||
void setDriver(Drivers::EInk *driver);
|
|
||||||
void setDisplayResilience(uint8_t fastPerFull = 5, float stressMultiplier = 2.0);
|
|
||||||
void addApplet(const char *name, Applet *a, bool defaultActive = false, bool defaultAutoshow = false, uint8_t onTile = -1);
|
|
||||||
|
|
||||||
void begin();
|
|
||||||
|
|
||||||
// Handle user-button press
|
|
||||||
// - connected to an input source, in variant nicheGraphics.h
|
|
||||||
|
|
||||||
void shortpress();
|
|
||||||
void longpress();
|
|
||||||
|
|
||||||
// Trigger UI changes
|
|
||||||
// - called by various InkHUD components
|
|
||||||
// - suitable(?) for use by aux button, connected in variant nicheGraphics.h
|
|
||||||
|
|
||||||
void nextApplet();
|
|
||||||
void openMenu();
|
|
||||||
void nextTile();
|
|
||||||
void rotate();
|
|
||||||
void toggleBatteryIcon();
|
|
||||||
|
|
||||||
// Updating the display
|
|
||||||
// - called by various InkHUD components
|
|
||||||
|
|
||||||
void requestUpdate();
|
|
||||||
void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool async = true);
|
|
||||||
void awaitUpdate();
|
|
||||||
|
|
||||||
// (Re)configuring WindowManager
|
|
||||||
|
|
||||||
void autoshow(); // Bring an applet to foreground
|
|
||||||
void updateAppletSelection(); // Change which applets are active
|
|
||||||
void updateLayout(); // Change multiplexing (count, rotation)
|
|
||||||
|
|
||||||
// Information passed between components
|
|
||||||
|
|
||||||
uint16_t width(); // From E-Ink driver
|
|
||||||
uint16_t height(); // From E-Ink driver
|
|
||||||
std::vector<Tile *> getEmptyTiles(); // From WindowManager
|
|
||||||
|
|
||||||
// Applets
|
|
||||||
|
|
||||||
SystemApplet *getSystemApplet(const char *name);
|
|
||||||
std::vector<Applet *> userApplets;
|
|
||||||
std::vector<SystemApplet *> systemApplets;
|
|
||||||
|
|
||||||
// Pass drawing output to Renderer
|
|
||||||
void drawPixel(int16_t x, int16_t y, Color c);
|
|
||||||
|
|
||||||
// Shared data which persists between boots
|
|
||||||
Persistence *persistence = nullptr;
|
|
||||||
|
|
||||||
private:
|
|
||||||
InkHUD() {} // Constructor made private to force use of InkHUD::getInstance
|
|
||||||
|
|
||||||
Events *events = nullptr; // Handle non-specific firmware events
|
|
||||||
Renderer *renderer = nullptr; // Co-ordinate display updates
|
|
||||||
WindowManager *windowManager = nullptr; // Multiplexing of applets
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace NicheGraphics::InkHUD
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -31,7 +31,7 @@ class MessageStore
|
|||||||
};
|
};
|
||||||
|
|
||||||
MessageStore() = delete;
|
MessageStore() = delete;
|
||||||
explicit MessageStore(std::string label); // Label determines filename in flash
|
MessageStore(std::string label); // Label determines filename in flash
|
||||||
|
|
||||||
void saveToFlash();
|
void saveToFlash();
|
||||||
void loadFromFlash();
|
void loadFromFlash();
|
||||||
|
|||||||
@@ -5,21 +5,17 @@
|
|||||||
using namespace NicheGraphics;
|
using namespace NicheGraphics;
|
||||||
|
|
||||||
// Load settings and latestMessage data
|
// Load settings and latestMessage data
|
||||||
void InkHUD::Persistence::loadSettings()
|
void InkHUD::loadDataFromFlash()
|
||||||
{
|
{
|
||||||
// Load the InkHUD settings from flash, and check version number
|
// Load the InkHUD settings from flash, and check version number
|
||||||
// We should only consider the version number if the InkHUD flashdata component reports that we *did* actually load flash data
|
// We should only consider the version number if the InkHUD flashdata component reports that we *did* actually load flash data
|
||||||
Settings loadedSettings;
|
InkHUD::Settings loadedSettings;
|
||||||
bool loadSucceeded = FlashData<Settings>::load(&loadedSettings, "settings");
|
bool loadSucceeded = FlashData<Settings>::load(&loadedSettings, "settings");
|
||||||
if (loadSucceeded && loadedSettings.meta.version == SETTINGS_VERSION && loadedSettings.meta.version != 0)
|
if (loadSucceeded && loadedSettings.meta.version == SETTINGS_VERSION && loadedSettings.meta.version != 0)
|
||||||
settings = loadedSettings; // Version matched, replace the defaults with the loaded values
|
settings = loadedSettings; // Version matched, replace the defaults with the loaded values
|
||||||
else
|
else
|
||||||
LOG_WARN("Settings version changed. Using defaults");
|
LOG_WARN("Settings version changed. Using defaults");
|
||||||
}
|
|
||||||
|
|
||||||
// Load settings and latestMessage data
|
|
||||||
void InkHUD::Persistence::loadLatestMessage()
|
|
||||||
{
|
|
||||||
// Load previous "latestMessages" data from flash
|
// Load previous "latestMessages" data from flash
|
||||||
MessageStore store("latest");
|
MessageStore store("latest");
|
||||||
store.loadFromFlash();
|
store.loadFromFlash();
|
||||||
@@ -36,15 +32,12 @@ void InkHUD::Persistence::loadLatestMessage()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the InkHUD settings to flash
|
// Save settings and latestMessage data
|
||||||
void InkHUD::Persistence::saveSettings()
|
void InkHUD::saveDataToFlash()
|
||||||
{
|
{
|
||||||
|
// Save the InkHUD settings to flash
|
||||||
FlashData<Settings>::save(&settings, "settings");
|
FlashData<Settings>::save(&settings, "settings");
|
||||||
}
|
|
||||||
|
|
||||||
// Save latestMessage data to flash
|
|
||||||
void InkHUD::Persistence::saveLatestMessage()
|
|
||||||
{
|
|
||||||
// Number of strings saved determines whether last message was broadcast or dm
|
// Number of strings saved determines whether last message was broadcast or dm
|
||||||
MessageStore store("latest");
|
MessageStore store("latest");
|
||||||
store.messages.push_back(latestMessage.dm);
|
store.messages.push_back(latestMessage.dm);
|
||||||
@@ -53,31 +46,14 @@ void InkHUD::Persistence::saveLatestMessage()
|
|||||||
store.saveToFlash();
|
store.saveToFlash();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Holds InkHUD settings while running
|
||||||
void InkHUD::Persistence::printSettings(Settings *settings)
|
// Saved back to Flash at shutdown
|
||||||
{
|
// Accessed by including persistence.h
|
||||||
if (SETTINGS_VERSION != 2)
|
InkHUD::Settings InkHUD::settings;
|
||||||
LOG_WARN("Persistence::printSettings was written for SETTINGS_VERSION=2, current is %d", SETTINGS_VERSION);
|
|
||||||
|
|
||||||
LOG_DEBUG("meta.version=%d", settings->meta.version);
|
// Holds copies of the most recent broadcast and DM messages while running
|
||||||
LOG_DEBUG("userTiles.count=%d", settings->userTiles.count);
|
// Saved to Flash at shutdown
|
||||||
LOG_DEBUG("userTiles.maxCount=%d", settings->userTiles.maxCount);
|
// Accessed by including persistence.h
|
||||||
LOG_DEBUG("userTiles.focused=%d", settings->userTiles.focused);
|
InkHUD::LatestMessage InkHUD::latestMessage;
|
||||||
for (uint8_t i = 0; i < MAX_TILES_GLOBAL; i++)
|
|
||||||
LOG_DEBUG("userTiles.displayedUserApplet[%d]=%d", i, settings->userTiles.displayedUserApplet[i]);
|
|
||||||
for (uint8_t i = 0; i < MAX_USERAPPLETS_GLOBAL; i++)
|
|
||||||
LOG_DEBUG("userApplets.active[%d]=%d", i, settings->userApplets.active[i]);
|
|
||||||
for (uint8_t i = 0; i < MAX_USERAPPLETS_GLOBAL; i++)
|
|
||||||
LOG_DEBUG("userApplets.autoshow[%d]=%d", i, settings->userApplets.autoshow[i]);
|
|
||||||
LOG_DEBUG("optionalFeatures.notifications=%d", settings->optionalFeatures.notifications);
|
|
||||||
LOG_DEBUG("optionalFeatures.batteryIcon=%d", settings->optionalFeatures.batteryIcon);
|
|
||||||
LOG_DEBUG("optionalMenuItems.nextTile=%d", settings->optionalMenuItems.nextTile);
|
|
||||||
LOG_DEBUG("optionalMenuItems.backlight=%d", settings->optionalMenuItems.backlight);
|
|
||||||
LOG_DEBUG("tips.firstBoot=%d", settings->tips.firstBoot);
|
|
||||||
LOG_DEBUG("tips.safeShutdownSeen=%d", settings->tips.safeShutdownSeen);
|
|
||||||
LOG_DEBUG("rotation=%d", settings->rotation);
|
|
||||||
LOG_DEBUG("recentlyActiveSeconds=%d", settings->recentlyActiveSeconds);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -14,119 +14,110 @@ The save / load mechanism is a shared NicheGraphics feature.
|
|||||||
|
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
#include "./InkHUD.h"
|
|
||||||
#include "graphics/niche/FlashData.h"
|
#include "graphics/niche/FlashData.h"
|
||||||
#include "graphics/niche/InkHUD/MessageStore.h"
|
#include "graphics/niche/InkHUD/MessageStore.h"
|
||||||
|
|
||||||
namespace NicheGraphics::InkHUD
|
namespace NicheGraphics::InkHUD
|
||||||
{
|
{
|
||||||
|
|
||||||
class Persistence
|
constexpr uint8_t MAX_TILES_GLOBAL = 4;
|
||||||
{
|
constexpr uint8_t MAX_USERAPPLETS_GLOBAL = 16;
|
||||||
public:
|
|
||||||
static constexpr uint8_t MAX_TILES_GLOBAL = 4;
|
|
||||||
static constexpr uint8_t MAX_USERAPPLETS_GLOBAL = 16;
|
|
||||||
|
|
||||||
// Used to invalidate old settings, if needed
|
// Used to invalidate old settings, if needed
|
||||||
// Version 0 is reserved for testing, and will always load defaults
|
// Version 0 is reserved for testing, and will always load defaults
|
||||||
static constexpr uint32_t SETTINGS_VERSION = 2;
|
constexpr uint32_t SETTINGS_VERSION = 2;
|
||||||
|
|
||||||
struct Settings {
|
struct Settings {
|
||||||
struct Meta {
|
struct Meta {
|
||||||
// Used to invalidate old savefiles, if we make breaking changes
|
// Used to invalidate old savefiles, if we make breaking changes
|
||||||
uint32_t version = SETTINGS_VERSION;
|
uint32_t version = SETTINGS_VERSION;
|
||||||
} meta;
|
} meta;
|
||||||
|
|
||||||
struct UserTiles {
|
struct UserTiles {
|
||||||
// How many tiles are shown
|
// How many tiles are shown
|
||||||
uint8_t count = 1;
|
uint8_t count = 1;
|
||||||
|
|
||||||
// Maximum amount of tiles for this display
|
// Maximum amount of tiles for this display
|
||||||
uint8_t maxCount = 4;
|
uint8_t maxCount = 4;
|
||||||
|
|
||||||
// Which tile is focused (responding to user button input)
|
// Which tile is focused (responding to user button input)
|
||||||
uint8_t focused = 0;
|
uint8_t focused = 0;
|
||||||
|
|
||||||
// Which applet is displayed on which tile
|
// Which applet is displayed on which tile
|
||||||
// Index of array: which tile, as indexed in WindowManager::userTiles
|
// Index of array: which tile, as indexed in WindowManager::tiles
|
||||||
// Value of array: which applet, as indexed in InkHUD::userApplets
|
// Value of array: which applet, as indexed in WindowManager::activeApplets
|
||||||
uint8_t displayedUserApplet[MAX_TILES_GLOBAL] = {0, 1, 2, 3};
|
uint8_t displayedUserApplet[MAX_TILES_GLOBAL] = {0, 1, 2, 3};
|
||||||
} userTiles;
|
} userTiles;
|
||||||
|
|
||||||
struct UserApplets {
|
struct UserApplets {
|
||||||
// Which applets are running (either displayed, or in the background)
|
// Which applets are running (either displayed, or in the background)
|
||||||
// Index of array: which applet, as indexed in InkHUD::userApplets
|
// Index of array: which applet, as indexed in WindowManager::applets
|
||||||
// Initial value is set by the "activeByDefault" parameter of InkHUD::addApplet, in setupNicheGraphics method
|
// Initial value is set by the "activeByDefault" parameter of WindowManager::addApplet, in setupNicheGraphics()
|
||||||
bool active[MAX_USERAPPLETS_GLOBAL]{false};
|
bool active[MAX_USERAPPLETS_GLOBAL];
|
||||||
|
|
||||||
// Which user applets should be automatically shown when they have important data to show
|
// Which user applets should be automatically shown when they have important data to show
|
||||||
// If none set, foreground applets should remain foreground without manual user input
|
// If none set, foreground applets should remain foreground without manual user input
|
||||||
// If multiple applets request this at once,
|
// If multiple applets request this at once,
|
||||||
// priority is the order which they were passed to InkHUD::addApplets, in setupNicheGraphics method
|
// priority is the order which they were passed to WindowManager::addApplets, in setupNicheGraphics()
|
||||||
bool autoshow[MAX_USERAPPLETS_GLOBAL]{false};
|
bool autoshow[MAX_USERAPPLETS_GLOBAL]{false};
|
||||||
} userApplets;
|
} userApplets;
|
||||||
|
|
||||||
// Features which the user can enable / disable via the on-screen menu
|
// Features which the use can enable / disable via the on-screen menu
|
||||||
struct OptionalFeatures {
|
struct OptionalFeatures {
|
||||||
bool notifications = true;
|
bool notifications = true;
|
||||||
bool batteryIcon = false;
|
bool batteryIcon = false;
|
||||||
} optionalFeatures;
|
} optionalFeatures;
|
||||||
|
|
||||||
// Some menu items may not be required, based on device / configuration
|
// Some menu items may not be required, based on device / configuration
|
||||||
// We can enable them only when needed, to de-clutter the menu
|
// We can enable them only when needed, to de-clutter the menu
|
||||||
struct OptionalMenuItems {
|
struct OptionalMenuItems {
|
||||||
// If aux button is used to swap between tiles, we have no need for this menu item
|
// If aux button is used to swap between tiles, we have to need for this menu item
|
||||||
bool nextTile = true;
|
bool nextTile = true;
|
||||||
|
|
||||||
// Used if backlight present, and not controlled by AUX button
|
// Used if backlight present, and not controlled by AUX button
|
||||||
// If this item is added to menu: backlight is always active when menu is open
|
// If this item is added to menu: backlight is always active when menu is open
|
||||||
// The added menu items then allows the user to "Keep Backlight On", globally.
|
// The added menu items then allows the user to "Keep Backlight On", globally.
|
||||||
bool backlight = false;
|
bool backlight = false;
|
||||||
} optionalMenuItems;
|
} optionalMenuItems;
|
||||||
|
|
||||||
// Allows tips to be run once only
|
// Allows tips to be run once only
|
||||||
struct Tips {
|
struct Tips {
|
||||||
// Enables the longer "tutorial" shown only on first boot
|
// Enables the longer "tutorial" shown only on first boot
|
||||||
// Once tutorial has been completed, it is no longer shown
|
// Once tutorial has been completed, it is no longer shown
|
||||||
bool firstBoot = true;
|
bool firstBoot = true;
|
||||||
|
|
||||||
// User is advised to shut down before removing device power
|
// User is advised to shutdown before removing device power
|
||||||
// Once user executes a shutdown (either via menu or client app),
|
// Once user executes a shutdown (either via menu or client app),
|
||||||
// this tip is no longer shown
|
// this tip is no longer shown
|
||||||
bool safeShutdownSeen = false;
|
bool safeShutdownSeen = false;
|
||||||
} tips;
|
} tips;
|
||||||
|
|
||||||
// Rotation of the display
|
// Rotation of the display
|
||||||
// Multiples of 90 degrees clockwise
|
// Multiples of 90 degrees clockwise
|
||||||
// Most commonly: rotation is 0 when flex connector is oriented below display
|
// Most commonly: rotation is 0 when flex connector is oriented below display
|
||||||
uint8_t rotation = 1;
|
uint8_t rotation = 1;
|
||||||
|
|
||||||
// How long do we consider another node to be "active"?
|
// How long do we consider another node to be "active"?
|
||||||
// Used when applets want to filter for "active nodes" only
|
// Used when applets want to filter for "active nodes" only
|
||||||
uint32_t recentlyActiveSeconds = 2 * 60;
|
uint32_t recentlyActiveSeconds = 2 * 60;
|
||||||
};
|
|
||||||
|
|
||||||
// Most recently received text message
|
|
||||||
// Value is updated by InkHUD::WindowManager, as a courtesy to applets
|
|
||||||
// Note: different from devicestate.rx_text_message,
|
|
||||||
// which may contain an *outgoing message* to broadcast
|
|
||||||
struct LatestMessage {
|
|
||||||
MessageStore::Message broadcast; // Most recent message received broadcast
|
|
||||||
MessageStore::Message dm; // Most recent received DM
|
|
||||||
bool wasBroadcast; // True if most recent broadcast is newer than most recent dm
|
|
||||||
};
|
|
||||||
|
|
||||||
void loadSettings();
|
|
||||||
void saveSettings();
|
|
||||||
void loadLatestMessage();
|
|
||||||
void saveLatestMessage();
|
|
||||||
|
|
||||||
// void printSettings(Settings *settings); // Debugging use only
|
|
||||||
|
|
||||||
Settings settings;
|
|
||||||
LatestMessage latestMessage;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Most recently received text message
|
||||||
|
// Value is updated by InkHUD::WindowManager, as a courtesty to applets
|
||||||
|
// Note: different from devicestate.rx_text_message,
|
||||||
|
// which may contain an *outgoing message* to broadcast
|
||||||
|
struct LatestMessage {
|
||||||
|
MessageStore::Message broadcast; // Most recent message received broadcast
|
||||||
|
MessageStore::Message dm; // Most recent received DM
|
||||||
|
bool wasBroadcast; // True if most recent broadcast is newer than most recent dm
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Settings settings;
|
||||||
|
extern LatestMessage latestMessage;
|
||||||
|
|
||||||
|
void loadDataFromFlash();
|
||||||
|
void saveDataToFlash();
|
||||||
|
|
||||||
} // namespace NicheGraphics::InkHUD
|
} // namespace NicheGraphics::InkHUD
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
[inkhud]
|
[inkhud]
|
||||||
build_src_filter =
|
board_level = extra
|
||||||
+<graphics/niche/>; Include the nicheGraphics directory
|
build_src_filter = +<../variants/$PIOENV> ; Include nicheGraphics.h
|
||||||
+<../variants/$PIOENV>; Include nicheGraphics.h from our variant folder
|
|
||||||
build_flags =
|
build_flags =
|
||||||
-D MESHTASTIC_INCLUDE_NICHE_GRAPHICS ; Use NicheGraphics
|
-D MESHTASTIC_INCLUDE_NICHE_GRAPHICS ; Use NicheGraphics
|
||||||
-D MESHTASTIC_INCLUDE_INKHUD ; Use InkHUD (a NicheGraphics UI)
|
-D MESHTASTIC_INCLUDE_INKHUD ; Use InkHUD (a NicheGraphics UI)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user