mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-14 06:42:34 +00:00
Compare commits
280 Commits
ble-locati
...
indicator-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45ecd139e5 | ||
|
|
708978911b | ||
|
|
2a26209889 | ||
|
|
1378657206 | ||
|
|
98d010761e | ||
|
|
798b1f4d86 | ||
|
|
29893e0c28 | ||
|
|
1994bb3cd1 | ||
|
|
f35ca812a3 | ||
|
|
abbeb4874d | ||
|
|
0f96bd7a26 | ||
|
|
dfb07e8bd2 | ||
|
|
ff4eed08bc | ||
|
|
f13dc5b903 | ||
|
|
c1431f4f9a | ||
|
|
79db7a5208 | ||
|
|
93132fad28 | ||
|
|
2254d551f4 | ||
|
|
f2d3f54824 | ||
|
|
6fa597bc5d | ||
|
|
b02e58521d | ||
|
|
81828c6244 | ||
|
|
549250b91a | ||
|
|
409dfe22ae | ||
|
|
a6be2e46ed | ||
|
|
3fdefe8289 | ||
|
|
1f85e2a02a | ||
|
|
7289b2a972 | ||
|
|
9356be3f8f | ||
|
|
cce1b050c8 | ||
|
|
fd5ff679f3 | ||
|
|
f99ac2104c | ||
|
|
7a7ef5f0c9 | ||
|
|
553fc0cb1b | ||
|
|
f39c7ad47e | ||
|
|
e505ec847e | ||
|
|
90e99b2bac | ||
|
|
d25240b33b | ||
|
|
d494c23a88 | ||
|
|
17f8303e01 | ||
|
|
aadea89202 | ||
|
|
53013e9a7e | ||
|
|
30eec01f55 | ||
|
|
cc961d7762 | ||
|
|
a7528d777a | ||
|
|
13013a272f | ||
|
|
3ea96bb6e1 | ||
|
|
baf0e9c7e6 | ||
|
|
598eebfb10 | ||
|
|
5841c889ba | ||
|
|
4bd416413a | ||
|
|
be06a7d881 | ||
|
|
26df4f8142 | ||
|
|
b6a13f1114 | ||
|
|
2bcf608654 | ||
|
|
705515ace2 | ||
|
|
a97df4bb52 | ||
|
|
f6743798e2 | ||
|
|
2ea70927c8 | ||
|
|
de5b55921e | ||
|
|
2b97576b18 | ||
|
|
29e7a71c97 | ||
|
|
18fbc2149d | ||
|
|
50424d1035 | ||
|
|
e87c991975 | ||
|
|
2ab717cebb | ||
|
|
ad23c065f6 | ||
|
|
eeb52a1221 | ||
|
|
8ae05f6b33 | ||
|
|
f6630cd31d | ||
|
|
c144bd03dc | ||
|
|
7512673b09 | ||
|
|
3870d81bf6 | ||
|
|
a7dcf580ad | ||
|
|
ecfaf3a095 | ||
|
|
91bcf072a0 | ||
|
|
4802cef3ca | ||
|
|
38896198f2 | ||
|
|
012f88e56f | ||
|
|
0808f5215f | ||
|
|
247e05bb10 | ||
|
|
4308bbc156 | ||
|
|
ce1480df98 | ||
|
|
0108ad7992 | ||
|
|
e1df4e19e5 | ||
|
|
8ba98ae873 | ||
|
|
7a38368494 | ||
|
|
195b7cc30a | ||
|
|
4feaec651f | ||
|
|
82b7cb5dd0 | ||
|
|
30bbb449db | ||
|
|
14421c3609 | ||
|
|
2cf7e51061 | ||
|
|
7fd12782a1 | ||
|
|
c914a62d93 | ||
|
|
12680ad9cd | ||
|
|
0561f2ca4b | ||
|
|
58743021c8 | ||
|
|
2fb46ce5d5 | ||
|
|
8be76a56c7 | ||
|
|
2c206febab | ||
|
|
db1eac12af | ||
|
|
56e67cb434 | ||
|
|
e9d5e36738 | ||
|
|
f71fdef3fd | ||
|
|
5e92145324 | ||
|
|
89a4589b68 | ||
|
|
20991d8b53 | ||
|
|
3ab9005b2f | ||
|
|
aabc5b7cf2 | ||
|
|
afcd97c154 | ||
|
|
cbdd7eae70 | ||
|
|
6374ffea35 | ||
|
|
1a6bb97f16 | ||
|
|
4f0b95e910 | ||
|
|
a81b41cbfb | ||
|
|
465fe18a89 | ||
|
|
bd0e25f3f5 | ||
|
|
9861e82f0a | ||
|
|
fcefd592e2 | ||
|
|
8a8a7cdefc | ||
|
|
8f9e569825 | ||
|
|
b0c5327585 | ||
|
|
f1dd623ce9 | ||
|
|
ac52edd11a | ||
|
|
66d5dde956 | ||
|
|
7dfbcc8f1d | ||
|
|
28244148a2 | ||
|
|
e623c70bd0 | ||
|
|
425f384b1f | ||
|
|
1557219bad | ||
|
|
691917b956 | ||
|
|
cc0fbfbd21 | ||
|
|
5d0bf03b01 | ||
|
|
8ff99437cb | ||
|
|
ba93097bb7 | ||
|
|
de098cca4c | ||
|
|
8faa04afdb | ||
|
|
fede1b8597 | ||
|
|
8557bd031d | ||
|
|
4e6418b635 | ||
|
|
a1a5503fe9 | ||
|
|
3b94981e56 | ||
|
|
f299447216 | ||
|
|
5f0c8863fd | ||
|
|
f9d17cdee0 | ||
|
|
68a28a177f | ||
|
|
60ec05e536 | ||
|
|
730cd388d6 | ||
|
|
6549b0477c | ||
|
|
8304cae010 | ||
|
|
0ad9758cfd | ||
|
|
e5f6804421 | ||
|
|
720add72b2 | ||
|
|
693b11db1d | ||
|
|
4bf2dd04ae | ||
|
|
c6c2a4d4dd | ||
|
|
79b8e7b1cf | ||
|
|
cf4f088337 | ||
|
|
22cb20d294 | ||
|
|
1eacdd0629 | ||
|
|
67e3d57412 | ||
|
|
7924ef87b5 | ||
|
|
3dec521f75 | ||
|
|
57a33790ed | ||
|
|
484af8eb9f | ||
|
|
b8970d66a1 | ||
|
|
e78033bb85 | ||
|
|
8bd7adca47 | ||
|
|
f67aec40e8 | ||
|
|
46c7d74760 | ||
|
|
15d2ae17f8 | ||
|
|
91579c4650 | ||
|
|
79b710a108 | ||
|
|
ba296db701 | ||
|
|
c0e1616382 | ||
|
|
f084a8a11d | ||
|
|
070deb290f | ||
|
|
76f7207463 | ||
|
|
55b2bbf937 | ||
|
|
a5716cf25c | ||
|
|
4d81280ac2 | ||
|
|
9ce44556ce | ||
|
|
be0c7d73a3 | ||
|
|
d833a9ea61 | ||
|
|
5cd74f4b53 | ||
|
|
284b8bcff2 | ||
|
|
9799f10e63 | ||
|
|
f9d4fdbb52 | ||
|
|
b6cb0b148c | ||
|
|
7a7166d575 | ||
|
|
ba53543354 | ||
|
|
cb9429e83e | ||
|
|
f972b62d89 | ||
|
|
7849a3d291 | ||
|
|
c0c2ec195f | ||
|
|
e31cd0bc77 | ||
|
|
5195815df0 | ||
|
|
42a80d8aed | ||
|
|
da69d88790 | ||
|
|
96c18d9908 | ||
|
|
8908805894 | ||
|
|
158c88ddef | ||
|
|
138dc89442 | ||
|
|
baefda213a | ||
|
|
106dd08710 | ||
|
|
f223b8a55d | ||
|
|
75a49d3486 | ||
|
|
7d95b487ef | ||
|
|
2e72850d99 | ||
|
|
5fbdf4b6dc | ||
|
|
c47bdd11f9 | ||
|
|
d3b16c1e47 | ||
|
|
e29588d2e2 | ||
|
|
9b69c2a9af | ||
|
|
30e83d36b7 | ||
|
|
2c9e169451 | ||
|
|
067d01b832 | ||
|
|
37857941bf | ||
|
|
3aed7b4190 | ||
|
|
c01db98819 | ||
|
|
beba1b4882 | ||
|
|
ba1ef45024 | ||
|
|
b12e9d43be | ||
|
|
7cd50d7044 | ||
|
|
41c1b29d70 | ||
|
|
6041357cbb | ||
|
|
cf3f35d566 | ||
|
|
e0f878872f | ||
|
|
c70fa0ef13 | ||
|
|
16994c8725 | ||
|
|
3dd6dc0296 | ||
|
|
b2d81b740f | ||
|
|
61e4eb12e6 | ||
|
|
5b312ab917 | ||
|
|
a50a94150a | ||
|
|
3398a52a34 | ||
|
|
066609a718 | ||
|
|
1ef4caea05 | ||
|
|
7d8f9c7f6d | ||
|
|
c2d5862161 | ||
|
|
6bba17d463 | ||
|
|
ef9d0d7805 | ||
|
|
60d2cb35e0 | ||
|
|
ed6de5095e | ||
|
|
b63b73ab84 | ||
|
|
3901ae8956 | ||
|
|
7cffd9ba70 | ||
|
|
fc64bea698 | ||
|
|
a51a6b8c47 | ||
|
|
1af4a0bdc9 | ||
|
|
bc313da064 | ||
|
|
f16402dec1 | ||
|
|
feafd2bc0c | ||
|
|
b1955c34aa | ||
|
|
94af3bd1ab | ||
|
|
d9ad2322e8 | ||
|
|
0a8bd1e4be | ||
|
|
a7415791a5 | ||
|
|
cc66f7c79b | ||
|
|
e1417cff2e | ||
|
|
b9fcd9da23 | ||
|
|
2a06b058fd | ||
|
|
62e1974d09 | ||
|
|
b208e1924f | ||
|
|
b17bb49a63 | ||
|
|
7c9296b0f4 | ||
|
|
23fe093a65 | ||
|
|
c2a38357f1 | ||
|
|
3aee4bfc6b | ||
|
|
981ecfdb61 | ||
|
|
ca9bf6b31a | ||
|
|
ff2a12d579 | ||
|
|
3a6fc668d8 | ||
|
|
b657ba1abb | ||
|
|
d544b41ab7 | ||
|
|
8c53ce82f2 | ||
|
|
7ee95f2a0c | ||
|
|
65b50babee | ||
|
|
3c30821337 |
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
open_collective: meshtastic
|
||||
50
.github/actions/build-variant/action.yml
vendored
50
.github/actions/build-variant/action.yml
vendored
@@ -27,10 +27,10 @@ inputs:
|
||||
description: A newline separated list of paths to store as artifacts
|
||||
required: false
|
||||
default: ""
|
||||
include-web-ui:
|
||||
description: Include the web UI in the build
|
||||
required: false
|
||||
default: "false"
|
||||
# include-web-ui:
|
||||
# description: Include the web UI in the build
|
||||
# required: false
|
||||
# default: "false"
|
||||
arch:
|
||||
description: Processor arch name
|
||||
required: true
|
||||
@@ -43,29 +43,29 @@ runs:
|
||||
id: base
|
||||
uses: ./.github/actions/setup-base
|
||||
|
||||
- name: Get web ui version
|
||||
if: inputs.include-web-ui == 'true'
|
||||
id: webver
|
||||
shell: bash
|
||||
run: |
|
||||
echo "ver=$(cat bin/web.version)" >> $GITHUB_OUTPUT
|
||||
# - name: Get web ui version
|
||||
# if: inputs.include-web-ui == 'true'
|
||||
# id: webver
|
||||
# shell: bash
|
||||
# run: |
|
||||
# echo "ver=$(cat bin/web.version)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Pull web ui
|
||||
if: inputs.include-web-ui == 'true'
|
||||
uses: dsaltares/fetch-gh-release-asset@master
|
||||
with:
|
||||
repo: meshtastic/web
|
||||
file: build.tar
|
||||
target: build.tar
|
||||
token: ${{ inputs.github_token }}
|
||||
version: tags/v${{ steps.webver.outputs.ver }}
|
||||
# - name: Pull web ui
|
||||
# if: inputs.include-web-ui == 'true'
|
||||
# uses: dsaltares/fetch-gh-release-asset@master
|
||||
# with:
|
||||
# repo: meshtastic/web
|
||||
# file: build.tar
|
||||
# target: build.tar
|
||||
# token: ${{ inputs.github_token }}
|
||||
# version: tags/v${{ steps.webver.outputs.ver }}
|
||||
|
||||
- name: Unpack web ui
|
||||
if: inputs.include-web-ui == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
tar -xf build.tar -C data/static
|
||||
rm build.tar
|
||||
# - name: Unpack web ui
|
||||
# if: inputs.include-web-ui == 'true'
|
||||
# shell: bash
|
||||
# run: |
|
||||
# tar -xf build.tar -C data/static
|
||||
# rm build.tar
|
||||
|
||||
- name: Remove debug flags for release
|
||||
shell: bash
|
||||
|
||||
5
.github/pull_request_template.md
vendored
5
.github/pull_request_template.md
vendored
@@ -1,6 +1,7 @@
|
||||
## 🙏 Thank you for sending in a pull request, here's some tips to get started!
|
||||
|
||||
### ❌ (Please delete all these tips and replace them with your text) ❌
|
||||
|
||||
- Before starting on some new big chunk of code, it it is optional but highly recommended to open an issue first
|
||||
to say "Hey, I think this idea X should be implemented and I'm starting work on it. My general plan is Y, any feedback
|
||||
is appreciated." This will allow other devs to potentially save you time by not accidentially duplicating work etc...
|
||||
@@ -15,12 +16,12 @@
|
||||
- If you do not have the affected hardware to test your code changes adequately against regressions, please indicate this, so that contributors and commnunity members can help test your changes.
|
||||
- If your PR gets accepted you can request a "Contributor" role in the Meshtastic Discord
|
||||
|
||||
|
||||
## 🤝 Attestations
|
||||
|
||||
- [ ] I have tested that my proposed changes behave as described.
|
||||
- [ ] I have tested that my proposed changes do not cause any obvious regressions on the following devices:
|
||||
- [ ] Heltec (Lora32) V3
|
||||
- [ ] LilyGo T-Deck
|
||||
- [ ] LilyGo T-Deck
|
||||
- [ ] LilyGo T-Beam
|
||||
- [ ] RAK WisBlock 4631
|
||||
- [ ] Seeed Studio T-1000E tracker card
|
||||
|
||||
2
.github/workflows/build_esp32.yml
vendored
2
.github/workflows/build_esp32.yml
vendored
@@ -33,5 +33,5 @@ jobs:
|
||||
artifact-paths: |
|
||||
release/*.bin
|
||||
release/*.elf
|
||||
include-web-ui: true
|
||||
#include-web-ui: true
|
||||
arch: esp32
|
||||
|
||||
2
.github/workflows/build_esp32_c3.yml
vendored
2
.github/workflows/build_esp32_c3.yml
vendored
@@ -33,5 +33,5 @@ jobs:
|
||||
artifact-paths: |
|
||||
release/*.bin
|
||||
release/*.elf
|
||||
include-web-ui: true
|
||||
#include-web-ui: true
|
||||
arch: esp32c3
|
||||
|
||||
2
.github/workflows/build_esp32_c6.yml
vendored
2
.github/workflows/build_esp32_c6.yml
vendored
@@ -33,5 +33,5 @@ jobs:
|
||||
artifact-paths: |
|
||||
release/*.bin
|
||||
release/*.elf
|
||||
include-web-ui: true
|
||||
#include-web-ui: true
|
||||
arch: esp32c6
|
||||
|
||||
2
.github/workflows/build_esp32_s3.yml
vendored
2
.github/workflows/build_esp32_s3.yml
vendored
@@ -33,5 +33,5 @@ jobs:
|
||||
artifact-paths: |
|
||||
release/*.bin
|
||||
release/*.elf
|
||||
include-web-ui: true
|
||||
#include-web-ui: true
|
||||
arch: esp32s3
|
||||
|
||||
2
.github/workflows/daily_packaging.yml
vendored
2
.github/workflows/daily_packaging.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Daily Packaging
|
||||
on:
|
||||
schedule:
|
||||
- cron: 0 9 * * *
|
||||
- cron: 0 2 * * *
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
|
||||
1
.github/workflows/main_matrix.yml
vendored
1
.github/workflows/main_matrix.yml
vendored
@@ -257,7 +257,6 @@ jobs:
|
||||
./device-*.sh
|
||||
./device-*.bat
|
||||
./littlefs-*.bin
|
||||
./littlefswebui-*.bin
|
||||
./bleota*bin
|
||||
./Meshtastic_nRF52_factory_erase*.uf2
|
||||
retention-days: 30
|
||||
|
||||
1
.github/workflows/release_channels.yml
vendored
1
.github/workflows/release_channels.yml
vendored
@@ -98,6 +98,7 @@ jobs:
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
base: ${{ github.event.repository.default_branch }}
|
||||
branch: create-pull-request/bump-version
|
||||
title: Bump release version
|
||||
commit-message: automated bumps
|
||||
add-paths: |
|
||||
|
||||
2
.github/workflows/test_native.yml
vendored
2
.github/workflows/test_native.yml
vendored
@@ -143,7 +143,7 @@ jobs:
|
||||
merge-multiple: true
|
||||
|
||||
- name: Test Report
|
||||
uses: dorny/test-reporter@v2.0.0
|
||||
uses: dorny/test-reporter@v2.1.0
|
||||
with:
|
||||
name: PlatformIO Tests
|
||||
path: testreport.xml
|
||||
|
||||
7
.github/workflows/tests.yml
vendored
7
.github/workflows/tests.yml
vendored
@@ -5,7 +5,10 @@ on:
|
||||
- cron: 0 0 * * * # Run every day at midnight
|
||||
workflow_dispatch: {}
|
||||
|
||||
permissions: read-all
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
checks: write
|
||||
|
||||
jobs:
|
||||
native-tests:
|
||||
@@ -44,7 +47,7 @@ jobs:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 22
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
1
.github/workflows/update_protobufs.yml
vendored
1
.github/workflows/update_protobufs.yml
vendored
@@ -33,6 +33,7 @@ jobs:
|
||||
- name: Create pull request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
branch: create-pull-request/update-protobufs
|
||||
title: Update protobufs and classes
|
||||
add-paths: |
|
||||
protobufs
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -37,4 +37,7 @@ release/
|
||||
.vscode/extensions.json
|
||||
/compile_commands.json
|
||||
src/mesh/raspihttp/certificate.pem
|
||||
src/mesh/raspihttp/private_key.pem
|
||||
src/mesh/raspihttp/private_key.pem
|
||||
|
||||
# Ignore logo (set at build time with platformio-custom.py)
|
||||
data/boot/logo.*
|
||||
|
||||
1
.trunk/configs/.prettierignore
Normal file
1
.trunk/configs/.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
renovate.json
|
||||
@@ -1,34 +1,34 @@
|
||||
version: 0.1
|
||||
cli:
|
||||
version: 1.22.15
|
||||
version: 1.24.0
|
||||
plugins:
|
||||
sources:
|
||||
- id: trunk
|
||||
ref: v1.6.8
|
||||
ref: v1.7.1
|
||||
uri: https://github.com/trunk-io/plugins
|
||||
lint:
|
||||
enabled:
|
||||
- renovate@40.0.6
|
||||
- prettier@3.5.3
|
||||
- trufflehog@3.88.27
|
||||
- checkov@3.2.447
|
||||
- renovate@41.17.2
|
||||
- prettier@3.6.2
|
||||
- trufflehog@3.89.2
|
||||
- yamllint@1.37.1
|
||||
- bandit@1.8.3
|
||||
- terrascan@1.19.9
|
||||
- trivy@0.61.1
|
||||
- bandit@1.8.5
|
||||
- trivy@0.64.0
|
||||
- taplo@0.9.3
|
||||
- ruff@0.11.8
|
||||
- ruff@0.12.1
|
||||
- isort@6.0.1
|
||||
- markdownlint@0.44.0
|
||||
- markdownlint@0.45.0
|
||||
- oxipng@9.1.5
|
||||
- svgo@3.3.2
|
||||
- svgo@4.0.0
|
||||
- actionlint@1.7.7
|
||||
- flake8@7.2.0
|
||||
- flake8@7.3.0
|
||||
- hadolint@2.12.1-beta
|
||||
- shfmt@3.6.0
|
||||
- shellcheck@0.10.0
|
||||
- black@25.1.0
|
||||
- git-diff-check
|
||||
- gitleaks@8.25.1
|
||||
- gitleaks@8.27.2
|
||||
- clang-format@16.0.3
|
||||
ignore:
|
||||
- linters: [ALL]
|
||||
@@ -38,7 +38,7 @@ runtimes:
|
||||
enabled:
|
||||
- python@3.10.8
|
||||
- go@1.21.0
|
||||
- node@18.20.5
|
||||
- node@22.16.0
|
||||
actions:
|
||||
disabled:
|
||||
- trunk-announce
|
||||
|
||||
14
Dockerfile
14
Dockerfile
@@ -1,4 +1,3 @@
|
||||
# trunk-ignore-all(terrascan/AC_DOCKER_0002): Known terrascan issue
|
||||
# trunk-ignore-all(trivy/DS002): We must run as root for this container
|
||||
# trunk-ignore-all(hadolint/DL3002): We must run as root for this container
|
||||
# trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
|
||||
@@ -38,6 +37,13 @@ RUN curl -L "https://github.com/meshtastic/web/releases/download/v$(cat /tmp/fir
|
||||
##### PRODUCTION BUILD #############
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
LABEL org.opencontainers.image.title="Meshtastic" \
|
||||
org.opencontainers.image.description="Debian Meshtastic daemon and web interface" \
|
||||
org.opencontainers.image.url="https://meshtastic.org" \
|
||||
org.opencontainers.image.documentation="https://meshtastic.org/docs/" \
|
||||
org.opencontainers.image.authors="Meshtastic" \
|
||||
org.opencontainers.image.licenses="GPL-3.0-or-later" \
|
||||
org.opencontainers.image.source="https://github.com/meshtastic/firmware/"
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV TZ=Etc/UTC
|
||||
|
||||
@@ -54,7 +60,7 @@ RUN apt-get update && apt-get --no-install-recommends -y install \
|
||||
&& mkdir -p /etc/meshtasticd/ssl
|
||||
|
||||
# Fetch compiled binary from the builder
|
||||
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/sbin/
|
||||
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/bin/
|
||||
COPY --from=builder /tmp/web /usr/share/meshtasticd/
|
||||
# Copy config templates
|
||||
COPY ./bin/config.d /etc/meshtasticd/available.d
|
||||
@@ -65,8 +71,8 @@ VOLUME /var/lib/meshtasticd
|
||||
# Expose Meshtastic TCP API port from the host
|
||||
EXPOSE 4403
|
||||
# Expose Meshtastic Web UI port from the host
|
||||
EXPOSE 443
|
||||
EXPOSE 9443
|
||||
|
||||
CMD [ "sh", "-cx", "meshtasticd -d /var/lib/meshtasticd" ]
|
||||
CMD [ "sh", "-cx", "meshtasticd --fsdir=/var/lib/meshtasticd" ]
|
||||
|
||||
HEALTHCHECK NONE
|
||||
|
||||
@@ -37,3 +37,4 @@ Join our community and help improve Meshtastic! 🚀
|
||||
## Stats
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# trunk-ignore-all(hadolint/DL3018): Do not pin apk package versions
|
||||
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
|
||||
|
||||
FROM python:3.13-alpine3.21 AS builder
|
||||
FROM python:3.13-alpine3.22 AS builder
|
||||
ARG PIO_ENV=native
|
||||
ENV PIP_ROOT_USER_ACTION=ignore
|
||||
|
||||
@@ -27,13 +27,20 @@ RUN bash ./bin/build-native.sh "$PIO_ENV" && \
|
||||
|
||||
# ##### PRODUCTION BUILD #############
|
||||
|
||||
FROM alpine:3.21
|
||||
FROM alpine:3.22
|
||||
LABEL org.opencontainers.image.title="Meshtastic" \
|
||||
org.opencontainers.image.description="Alpine Meshtastic daemon" \
|
||||
org.opencontainers.image.url="https://meshtastic.org" \
|
||||
org.opencontainers.image.documentation="https://meshtastic.org/docs/" \
|
||||
org.opencontainers.image.authors="Meshtastic" \
|
||||
org.opencontainers.image.licenses="GPL-3.0-or-later" \
|
||||
org.opencontainers.image.source="https://github.com/meshtastic/firmware/"
|
||||
|
||||
# nosemgrep: dockerfile.security.last-user-is-root.last-user-is-root
|
||||
USER root
|
||||
|
||||
RUN apk --no-cache add \
|
||||
libstdc++ libgpiod yaml-cpp libusb i2c-tools libuv \
|
||||
shadow libstdc++ libgpiod yaml-cpp libusb i2c-tools libuv \
|
||||
libx11 libinput libxkbcommon \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& mkdir -p /var/lib/meshtasticd \
|
||||
@@ -41,7 +48,7 @@ RUN apk --no-cache add \
|
||||
&& mkdir -p /etc/meshtasticd/ssl
|
||||
|
||||
# Fetch compiled binary from the builder
|
||||
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/sbin/
|
||||
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/bin/
|
||||
# Copy config templates
|
||||
COPY ./bin/config.d /etc/meshtasticd/available.d
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ extends = arduino_base
|
||||
custom_esp32_kind = esp32
|
||||
platform =
|
||||
# renovate: datasource=custom.pio depName=platformio/espressif32 packageName=platformio/platform/espressif32
|
||||
platformio/espressif32@6.10.0
|
||||
platformio/espressif32@6.11.0
|
||||
|
||||
build_src_filter =
|
||||
${arduino_base.build_src_filter} -<platform/nrf52/> -<platform/stm32wl> -<platform/rp2xx0> -<mesh/eth/> -<mesh/raspihttp>
|
||||
@@ -49,13 +49,13 @@ lib_deps =
|
||||
${environmental_extra.lib_deps}
|
||||
${radiolib_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-esp32_https_server packageName=https://github.com/meshtastic/esp32_https_server gitBranch=master
|
||||
https://github.com/meshtastic/esp32_https_server/archive/896f1771ceb5979987a0b41028bf1b4e7aad419b.zip
|
||||
https://github.com/meshtastic/esp32_https_server/archive/3223704846752e6d545139204837bdb2a55459ca.zip
|
||||
# renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino
|
||||
h2zero/NimBLE-Arduino@^1.4.3
|
||||
# renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master
|
||||
https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip
|
||||
# renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
|
||||
lewisxhe/XPowersLib@^0.2.7
|
||||
lewisxhe/XPowersLib@0.3.0
|
||||
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
|
||||
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
|
||||
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
|
||||
|
||||
@@ -28,7 +28,7 @@ lib_deps =
|
||||
${environmental_extra.lib_deps}
|
||||
${radiolib_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
|
||||
lewisxhe/XPowersLib@^0.2.7
|
||||
lewisxhe/XPowersLib@0.3.0
|
||||
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
|
||||
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
|
||||
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
[portduino_base]
|
||||
platform =
|
||||
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
|
||||
https://github.com/meshtastic/platform-native/archive/622341c6de8a239704318b10c3dbb00c21a3eab3.zip
|
||||
https://github.com/meshtastic/platform-native/archive/681ee029207e9fd040afa223df6e54074cbbe084.zip
|
||||
framework = arduino
|
||||
|
||||
build_src_filter =
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
extends = arduino_base
|
||||
platform =
|
||||
# renovate: datasource=custom.pio depName=platformio/ststm32 packageName=platformio/platform/ststm32
|
||||
platformio/ststm32@19.1.0
|
||||
platformio/ststm32@19.2.0
|
||||
platform_packages =
|
||||
# TODO renovate
|
||||
platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32/archive/2.10.1.zip
|
||||
@@ -16,14 +16,17 @@ build_flags =
|
||||
${arduino_base.build_flags}
|
||||
-flto
|
||||
-Isrc/platform/stm32wl -g
|
||||
-DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
-DMESHTASTIC_EXCLUDE_INPUTBROKER
|
||||
-DMESHTASTIC_EXCLUDE_I2C
|
||||
-DMESHTASTIC_EXCLUDE_POWERMON
|
||||
-DMESHTASTIC_EXCLUDE_SCREEN
|
||||
-DMESHTASTIC_EXCLUDE_MQTT
|
||||
-DMESHTASTIC_EXCLUDE_BLUETOOTH
|
||||
-DMESHTASTIC_EXCLUDE_GPS
|
||||
-DMESHTASTIC_EXCLUDE_AUDIO=1
|
||||
-DMESHTASTIC_EXCLUDE_ATAK=1 ; ATAK is quite big, disable it for big flash savings.
|
||||
-DMESHTASTIC_EXCLUDE_INPUTBROKER=1
|
||||
-DMESHTASTIC_EXCLUDE_POWERMON=1
|
||||
-DMESHTASTIC_EXCLUDE_SCREEN=1
|
||||
-DMESHTASTIC_EXCLUDE_MQTT=1
|
||||
-DMESHTASTIC_EXCLUDE_BLUETOOTH=1
|
||||
-DMESHTASTIC_EXCLUDE_GPS=1
|
||||
-DMESHTASTIC_EXCLUDE_WIFI=1
|
||||
-DMESHTASTIC_EXCLUDE_TZ=1 ; Exclude TZ to save some flash space.
|
||||
-DPIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF ; This is REQUIRED for at least traceroute debug prints - without it the length ends up uninitialized.
|
||||
;-DDEBUG_MUTE
|
||||
-fmerge-all-constants
|
||||
-ffunction-sections
|
||||
@@ -39,9 +42,9 @@ debug_tool = stlink
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
${radiolib_base.lib_deps}
|
||||
|
||||
# renovate: datasource=git-refs depName=caveman99-stm32-Crypto packageName=https://github.com/caveman99/Crypto gitBranch=main
|
||||
https://github.com/caveman99/Crypto/archive/eae9c768054118a9399690f8af202853d1ae8516.zip
|
||||
|
||||
lib_ignore =
|
||||
mathertel/OneButton@2.6.1
|
||||
Wire
|
||||
|
||||
7
bin/99-meshtasticd-udev.rules
Normal file
7
bin/99-meshtasticd-udev.rules
Normal file
@@ -0,0 +1,7 @@
|
||||
# Set spidev ownership to 'spi' group
|
||||
SUBSYSTEM=="spidev", KERNEL=="spidev*", GROUP="spi", MODE="0660"
|
||||
# Allow access to USB CH341 devices
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="5512", MODE="0666"
|
||||
# Set gpio ownership to 'gpio' group
|
||||
SUBSYSTEM=="*gpiomem*", GROUP="gpio", MODE="0660"
|
||||
SUBSYSTEM=="gpio", GROUP="gpio", MODE="0660"
|
||||
@@ -34,11 +34,12 @@ SRCBIN=.pio/build/$1/firmware.bin
|
||||
cp $SRCBIN $OUTDIR/$basename-update.bin
|
||||
|
||||
echo "Building Filesystem for ESP32 targets"
|
||||
pio run --environment $1 -t buildfs
|
||||
cp .pio/build/$1/littlefs.bin $OUTDIR/littlefswebui-$1-$VERSION.bin
|
||||
# Remove webserver files from the filesystem and rebuild
|
||||
ls -l data/static # Diagnostic list of files
|
||||
rm -rf data/static
|
||||
# If you want to build the webui, uncomment the following lines
|
||||
# pio run --environment $1 -t buildfs
|
||||
# cp .pio/build/$1/littlefs.bin $OUTDIR/littlefswebui-$1-$VERSION.bin
|
||||
# # Remove webserver files from the filesystem and rebuild
|
||||
# ls -l data/static # Diagnostic list of files
|
||||
# rm -rf data/static
|
||||
pio run --environment $1 -t buildfs
|
||||
cp .pio/build/$1/littlefs.bin $OUTDIR/littlefs-$1-$VERSION.bin
|
||||
cp bin/device-install.* $OUTDIR
|
||||
|
||||
@@ -23,4 +23,4 @@ for BOARD in $BOARDS; do
|
||||
CHECK="${CHECK} -e ${BOARD}"
|
||||
done
|
||||
|
||||
pio check --flags "-DAPP_VERSION=${APP_VERSION} --suppressions-list=suppressions.txt" $CHECK --skip-packages --pattern="src/" --fail-on-defect=medium --fail-on-defect=high
|
||||
pio check --flags "-DAPP_VERSION=${APP_VERSION} --suppressions-list=suppressions.txt --inline-suppr" $CHECK --skip-packages --pattern="src/" --fail-on-defect=medium --fail-on-defect=high
|
||||
|
||||
@@ -96,9 +96,9 @@ Lora:
|
||||
### Some devices, like the pinedio, may require spidev0.1 as a workaround.
|
||||
# spidev: spidev0.0
|
||||
|
||||
### Define GPIO buttons here:
|
||||
### Deprecated location for User Button:
|
||||
|
||||
GPIO:
|
||||
#GPIO:
|
||||
# User: 6
|
||||
|
||||
### Define GPS
|
||||
@@ -115,17 +115,6 @@ I2C:
|
||||
|
||||
Display:
|
||||
|
||||
### Waveshare 1.44inch LCD HAT
|
||||
# Panel: ST7735S
|
||||
# CS: 8 #Chip Select
|
||||
# DC: 25 # Data/Command pin
|
||||
# Backlight: 24
|
||||
# Width: 128
|
||||
# Height: 128
|
||||
# Reset: 27
|
||||
# OffsetX: 0
|
||||
# OffsetY: 0
|
||||
|
||||
### Adafruit PiTFT 2.8 TFT+Touchscreen
|
||||
# Panel: ILI9341
|
||||
# CS: 8
|
||||
@@ -180,6 +169,16 @@ Input:
|
||||
|
||||
# KeyboardDevice: /dev/input/by-id/usb-_Raspberry_Pi_Internal_Keyboard-event-kbd
|
||||
|
||||
### Standard User Button Config
|
||||
# UserButton: 6
|
||||
|
||||
### Trackball/Joystick input
|
||||
# TrackballUp: 6
|
||||
# TrackballDown: 19
|
||||
# TrackballLeft: 5
|
||||
# TrackballRight: 26
|
||||
# TrackballPress: 13
|
||||
|
||||
###
|
||||
|
||||
Logging:
|
||||
@@ -188,11 +187,18 @@ Logging:
|
||||
# AsciiLogs: true # default if not specified is !isatty() on stdout
|
||||
|
||||
Webserver:
|
||||
# Port: 443 # Port for Webserver & Webservices
|
||||
# Port: 9443 # Port for Webserver & Webservices
|
||||
# RootPath: /usr/share/meshtasticd/web # Root Dir of WebServer
|
||||
# SSLKey: /etc/meshtasticd/ssl/private_key.pem # Path to SSL Key, generated if not present
|
||||
# SSLCert: /etc/meshtasticd/ssl/certificate.pem # Path to SSL Certificate, generated if not present
|
||||
|
||||
|
||||
HostMetrics:
|
||||
# ReportInterval: 30 # Interval in minutes between HostMetrics report packets, or 0 for disabled
|
||||
# Channel: 0 # channel to send Host Metrics over. Defaults to the primary channel.
|
||||
# UserStringCommand: cat /sys/firmware/devicetree/base/serial-number # Command to execute, to send the results as the userString
|
||||
|
||||
|
||||
General:
|
||||
MaxNodes: 200
|
||||
MaxMessageQueue: 100
|
||||
|
||||
26
bin/config.d/display-waveshare-1-44.yaml
Normal file
26
bin/config.d/display-waveshare-1-44.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
### Waveshare 1.44inch LCD HAT
|
||||
Display:
|
||||
Panel: ST7735S
|
||||
spidev: spidev0.0 # Specify either the spidev here, or the CS below
|
||||
# CS: 8 #Chip Select # Optional, as this is the default pin for spidev0.0
|
||||
DC: 25 # Data/Command pin
|
||||
Backlight: 24
|
||||
Width: 128
|
||||
Height: 128
|
||||
Reset: 27
|
||||
OffsetX: 2
|
||||
OffsetY: 1
|
||||
|
||||
|
||||
# OffsetY: 31 # These two options are used to properly flip the screen 180 degrees
|
||||
# OffsetRotate: 3
|
||||
|
||||
|
||||
Input:
|
||||
TrackballUp: 6
|
||||
TrackballDown: 19
|
||||
TrackballLeft: 5
|
||||
TrackballRight: 26
|
||||
TrackballPress: 13
|
||||
TrackballDirection: FALLING
|
||||
# User: 21
|
||||
@@ -6,6 +6,6 @@ Lora:
|
||||
IRQ: 16
|
||||
Busy: 20
|
||||
Reset: 24
|
||||
TXen: 13
|
||||
RXen: 12
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
|
||||
21
bin/config.d/lora-RAK6421.yaml
Normal file
21
bin/config.d/lora-RAK6421.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
Lora:
|
||||
|
||||
### RAK13300in Slot 1
|
||||
Module: sx1262
|
||||
IRQ: 22 #IO6
|
||||
Reset: 16 # IO4
|
||||
Busy: 24 # IO5
|
||||
# Ant_sw: 13 # IO3
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
spidev: spidev0.0
|
||||
# CS: 8
|
||||
|
||||
|
||||
### RAK13300in Slot 2 pins
|
||||
# IRQ: 18 #IO6
|
||||
# Reset: 24 # IO4
|
||||
# Busy: 19 # IO5
|
||||
# # Ant_sw: 23 # IO3
|
||||
# spidev: spidev0.1
|
||||
# # CS: 7
|
||||
18
bin/config.d/lora-lyra-picocalc-wio-sx1262.yaml
Normal file
18
bin/config.d/lora-lyra-picocalc-wio-sx1262.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
Lora:
|
||||
Module: sx1262
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
gpiochip: 0
|
||||
MOSI: 12
|
||||
MISO: 13
|
||||
IRQ: 1
|
||||
Busy: 23
|
||||
Reset: 22
|
||||
RXen: 0
|
||||
gpiochip: 1
|
||||
CS: 9
|
||||
SCK: 11
|
||||
# TXen: bridge to DIO2 on E22 module
|
||||
SX126X_MAX_POWER: 22
|
||||
spidev: spidev1.0
|
||||
spiSpeed: 2000000
|
||||
@@ -5,13 +5,13 @@ TITLE Meshtastic device-install
|
||||
SET "SCRIPT_NAME=%~nx0"
|
||||
SET "DEBUG=0"
|
||||
SET "PYTHON="
|
||||
SET "WEB_APP=0"
|
||||
SET "TFT_BUILD=0"
|
||||
SET "BIGDB8=0"
|
||||
SET "BIGDB16=0"
|
||||
SET "ESPTOOL_BAUD=115200"
|
||||
SET "ESPTOOL_CMD="
|
||||
SET "LOGCOUNTER=0"
|
||||
SET "BPS_RESET=0"
|
||||
|
||||
@REM FIXME: Determine mcu from PlatformIO variant, this is unmaintainable.
|
||||
SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone"
|
||||
@@ -24,7 +24,7 @@ GOTO getopts
|
||||
:help
|
||||
ECHO Flash image file to device, but first erasing and writing system information.
|
||||
ECHO.
|
||||
ECHO Usage: %SCRIPT_NAME% -f filename [-p PORT] [-P python] (--web)
|
||||
ECHO Usage: %SCRIPT_NAME% -f filename [-p PORT] [-P python] [--1200bps-reset]
|
||||
ECHO.
|
||||
ECHO Options:
|
||||
ECHO -f filename The firmware .bin file to flash. Custom to your device type and region. (required)
|
||||
@@ -34,14 +34,16 @@ 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 --1200bps-reset Attempt to place the device in correct mode. (1200bps Reset)
|
||||
ECHO Some hardware requires this twice.
|
||||
ECHO.
|
||||
ECHO Example: %SCRIPT_NAME% -p COM17 --1200bps-reset
|
||||
ECHO Example: %SCRIPT_NAME% -f firmware-t-deck-tft-2.6.0.0b106d4.bin -p COM11
|
||||
ECHO Example: %SCRIPT_NAME% -f firmware-unphone-2.6.0.0b106d4.bin -p COM11 --web
|
||||
ECHO Example: %SCRIPT_NAME% -f firmware-unphone-2.6.0.0b106d4.bin -p COM11
|
||||
GOTO eof
|
||||
|
||||
:version
|
||||
ECHO %SCRIPT_NAME% [Version 2.6.1]
|
||||
ECHO %SCRIPT_NAME% [Version 2.6.2]
|
||||
ECHO Meshtastic
|
||||
GOTO eof
|
||||
|
||||
@@ -57,11 +59,13 @@ 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"
|
||||
IF /I "%~1"=="--1200bps-reset" SET "BPS_RESET=1"
|
||||
SHIFT
|
||||
GOTO getopts
|
||||
:endopts
|
||||
|
||||
IF %BPS_RESET% EQU 1 GOTO skip-filename
|
||||
|
||||
CALL :LOG_MESSAGE DEBUG "Checking FILENAME parameter..."
|
||||
IF "__!FILENAME!__"=="____" (
|
||||
CALL :LOG_MESSAGE DEBUG "Missing -f filename input."
|
||||
@@ -95,6 +99,9 @@ IF NOT "!FILENAME:update=!"=="!FILENAME!" (
|
||||
CALL :LOG_MESSAGE DEBUG "We are NOT working with a *update* file. !FILENAME!"
|
||||
)
|
||||
|
||||
:skip-filename
|
||||
SET "ESPTOOL_BAUD=1200"
|
||||
|
||||
CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
|
||||
IF NOT "__%PYTHON%__"=="____" (
|
||||
SET "ESPTOOL_CMD=!PYTHON! -m esptool"
|
||||
@@ -133,13 +140,16 @@ IF "__!ESPTOOL_PORT!__" == "____" (
|
||||
)
|
||||
CALL :LOG_MESSAGE INFO "Using esptool baud: !ESPTOOL_BAUD!."
|
||||
|
||||
IF %BPS_RESET% EQU 1 (
|
||||
@REM Attempt to change mode via 1200bps Reset.
|
||||
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! --after no_reset read_flash_status
|
||||
GOTO eof
|
||||
)
|
||||
|
||||
@REM Check if FILENAME contains "-tft-" and set target partitionScheme accordingly.
|
||||
@REM https://github.com/meshtastic/web-flasher/blob/main/types/resources.ts#L3
|
||||
IF NOT "!FILENAME:-tft-=!"=="!FILENAME!" (
|
||||
CALL :LOG_MESSAGE DEBUG "We are working with a *-tft-* file. !FILENAME!"
|
||||
IF %WEB_APP% EQU 1 (
|
||||
CALL :LOG_MESSAGE ERROR "Cannot enable WebUI (--web) and MUI." & GOTO eof
|
||||
)
|
||||
SET "TFT_BUILD=1"
|
||||
) ELSE (
|
||||
CALL :LOG_MESSAGE DEBUG "We are NOT working with a *-tft-* file. !FILENAME!"
|
||||
@@ -193,13 +203,8 @@ SET "OTA_FILENAME=bleota.bin"
|
||||
: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%"
|
||||
)
|
||||
@REM Set SPIFFS filename with "littlefs-" prefix.
|
||||
SET "SPIFFS_FILENAME=littlefs-%BASENAME%"
|
||||
CALL :LOG_MESSAGE DEBUG "Set SPIFFS_FILENAME to: !SPIFFS_FILENAME!"
|
||||
|
||||
@REM Default offsets.
|
||||
@@ -254,6 +259,7 @@ EXIT /B %ERRORLEVEL%
|
||||
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 %BPS_RESET% EQU 1 GOTO :eof
|
||||
IF %ERRORLEVEL% NEQ 0 (
|
||||
CALL :LOG_MESSAGE ERROR "Error running command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
|
||||
EXIT /B %ERRORLEVEL%
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
PYTHON=${PYTHON:-$(which python3 python | head -n 1)}
|
||||
WEB_APP=false
|
||||
BPS_RESET=false
|
||||
TFT_BUILD=false
|
||||
MCU=""
|
||||
|
||||
# Variant groups
|
||||
BIGDB_8MB=(
|
||||
"picomputer-s3"
|
||||
# Check if FILENAME contains "-tft-" and set target partitionScheme accordingly.
|
||||
if [[ $FILENAME == *"-tft-"* ]]; then
|
||||
TFT_BUILD=true
|
||||
fi
|
||||
|
||||
# Extract BASENAME from %FILENAME% for later use.r-s3"
|
||||
"unphone"
|
||||
"seeed-sensecap-indicator"
|
||||
"crowpanel-esp32s3"
|
||||
@@ -32,175 +37,185 @@ BIGDB_16MB=(
|
||||
"ESP32-S3-Pico"
|
||||
"m5stack-cores3"
|
||||
"station-g2"
|
||||
"t-eth-elite"
|
||||
"t-watch-s3"
|
||||
"t-eth-elite"
|
||||
"t-watch-s3"
|
||||
"elecrow-adv-35-tft"
|
||||
"elecrow-adv-24-28-tft"
|
||||
"elecrow-adv1-43-50-70-tft"
|
||||
)
|
||||
S3_VARIANTS=(
|
||||
"s3"
|
||||
"-v3"
|
||||
"t-deck"
|
||||
"wireless-paper"
|
||||
"wireless-tracker"
|
||||
"station-g2"
|
||||
"unphone"
|
||||
"s3"
|
||||
"-v3"
|
||||
"t-deck"
|
||||
"wireless-paper"
|
||||
"wireless-tracker"
|
||||
"station-g2"
|
||||
"unphone"
|
||||
"t-eth-elite"
|
||||
"mesh-tab"
|
||||
"dreamcatcher"
|
||||
"ESP32-S3-Pico"
|
||||
"seeed-sensecap-indicator"
|
||||
"heltec_capsule_sensor_v3"
|
||||
"vision-master"
|
||||
"icarus"
|
||||
"tracksenger"
|
||||
"elecrow-adv"
|
||||
)
|
||||
|
||||
# Determine the correct esptool command to use
|
||||
if "$PYTHON" -m esptool version >/dev/null 2>&1; then
|
||||
ESPTOOL_CMD="$PYTHON -m esptool"
|
||||
ESPTOOL_CMD="$PYTHON -m esptool"
|
||||
elif command -v esptool >/dev/null 2>&1; then
|
||||
ESPTOOL_CMD="esptool"
|
||||
ESPTOOL_CMD="esptool"
|
||||
elif command -v esptool.py >/dev/null 2>&1; then
|
||||
ESPTOOL_CMD="esptool.py"
|
||||
ESPTOOL_CMD="esptool.py"
|
||||
else
|
||||
echo "Error: esptool not found"
|
||||
exit 1
|
||||
echo "Error: esptool not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -e
|
||||
|
||||
# Usage info
|
||||
show_help() {
|
||||
cat <<EOF
|
||||
Usage: $(basename $0) [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME] [--web]
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME] [--1200bps-reset]
|
||||
Flash image file to device, but first erasing and writing system information.
|
||||
|
||||
-h Display this help and exit.
|
||||
-p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerous).
|
||||
-P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: "$PYTHON")
|
||||
-f FILENAME The firmware .bin file to flash. Custom to your device type and region.
|
||||
--web Enable WebUI. (Default: false)
|
||||
--1200bps-reset Attempt to place the device in correct mode. Some hardware requires this twice. (1200bps Reset)
|
||||
|
||||
EOF
|
||||
}
|
||||
# Parse arguments using a single while loop
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-h | --help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
-p)
|
||||
ESPTOOL_CMD="$ESPTOOL_CMD --port $2"
|
||||
shift
|
||||
;;
|
||||
-P)
|
||||
PYTHON="$2"
|
||||
shift
|
||||
;;
|
||||
-f)
|
||||
FILENAME="$2"
|
||||
shift
|
||||
;;
|
||||
--web)
|
||||
WEB_APP=true
|
||||
;;
|
||||
--) # Stop parsing options
|
||||
shift
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift # Move to the next argument
|
||||
case "$1" in
|
||||
-h | --help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
-p)
|
||||
ESPTOOL_CMD="$ESPTOOL_CMD --port $2"
|
||||
shift
|
||||
;;
|
||||
-P)
|
||||
PYTHON="$2"
|
||||
shift
|
||||
;;
|
||||
-f)
|
||||
FILENAME="$2"
|
||||
shift
|
||||
;;
|
||||
--1200bps-reset)
|
||||
BPS_RESET=true
|
||||
;;
|
||||
--) # Stop parsing options
|
||||
shift
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift # Move to the next argument
|
||||
done
|
||||
|
||||
[ -z "$FILENAME" -a -n "$1" ] && {
|
||||
FILENAME=$1
|
||||
shift
|
||||
if [[ $BPS_RESET == true ]]; then
|
||||
$ESPTOOL_CMD --baud 1200 --after no_reset read_flash_status
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[ -z "$FILENAME" ] && [ -n "$1" ] && {
|
||||
FILENAME="$1"
|
||||
shift
|
||||
}
|
||||
|
||||
if [[ $FILENAME != firmware-* ]]; then
|
||||
if [[ "$FILENAME" != firmware-* ]]; then
|
||||
echo "Filename must be a firmware-* file."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if FILENAME contains "-tft-" and prevent web/mui comingling.
|
||||
if [[ ${FILENAME//-tft-/} != "$FILENAME" ]]; then
|
||||
TFT_BUILD=true
|
||||
if [[ $WEB_APP == true ]] && [[ $TFT_BUILD == true ]]; then
|
||||
echo "Cannot enable WebUI (--web) and MUI."
|
||||
exit 1
|
||||
fi
|
||||
# Check if FILENAME contains "-tft-" and set target partitionScheme accordingly.
|
||||
if [[ "${FILENAME//-tft-/}" != "$FILENAME" ]]; then
|
||||
TFT_BUILD=true
|
||||
fi
|
||||
|
||||
# Extract BASENAME from %FILENAME% for later use.
|
||||
BASENAME="${FILENAME/firmware-/}"
|
||||
|
||||
if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
|
||||
# Default littlefs* offset (--web).
|
||||
OFFSET=0x300000
|
||||
# Default littlefs* offset.
|
||||
OFFSET=0x300000
|
||||
|
||||
# Default OTA Offset
|
||||
OTA_OFFSET=0x260000
|
||||
# Default OTA Offset
|
||||
OTA_OFFSET=0x260000
|
||||
|
||||
# littlefs* offset for BigDB 8mb and OTA OFFSET.
|
||||
for variant in "${BIGDB_8MB[@]}"; do
|
||||
if [ -z "${FILENAME##*"$variant"*}" ]; then
|
||||
OFFSET=0x670000
|
||||
OTA_OFFSET=0x340000
|
||||
fi
|
||||
done
|
||||
# littlefs* offset for BigDB 8mb and OTA OFFSET.
|
||||
for variant in "${BIGDB_8MB[@]}"; do
|
||||
if [ -z "${FILENAME##*"$variant"*}" ]; then
|
||||
OFFSET=0x670000
|
||||
OTA_OFFSET=0x340000
|
||||
fi
|
||||
done
|
||||
|
||||
# littlefs* offset for BigDB 16mb and OTA OFFSET.
|
||||
for variant in "${BIGDB_16MB[@]}"; do
|
||||
if [ -z "${FILENAME##*"$variant"*}" ]; then
|
||||
OFFSET=0xc90000
|
||||
OTA_OFFSET=0x650000
|
||||
fi
|
||||
done
|
||||
# littlefs* offset for BigDB 16mb and OTA OFFSET.
|
||||
for variant in "${BIGDB_16MB[@]}"; do
|
||||
if [ -z "${FILENAME##*"$variant"*}" ]; then
|
||||
OFFSET=0xc90000
|
||||
OTA_OFFSET=0x650000
|
||||
fi
|
||||
done
|
||||
|
||||
# Account for S3 board's different OTA partition
|
||||
# FIXME: Use PlatformIO info to determine MCU type, this is unmaintainable
|
||||
for variant in "${S3_VARIANTS[@]}"; do
|
||||
if [ -z "${FILENAME##*"$variant"*}" ]; then
|
||||
MCU="esp32s3"
|
||||
fi
|
||||
done
|
||||
# Account for S3 board's different OTA partition
|
||||
# FIXME: Use PlatformIO info to determine MCU type, this is unmaintainable
|
||||
for variant in "${S3_VARIANTS[@]}"; do
|
||||
if [ -z "${FILENAME##*"$variant"*}" ]; then
|
||||
MCU="esp32s3"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$MCU" != "esp32s3" ]; then
|
||||
if [ -n "${FILENAME##*"esp32c3"*}" ]; then
|
||||
OTAFILE=bleota.bin
|
||||
else
|
||||
OTAFILE=bleota-c3.bin
|
||||
fi
|
||||
else
|
||||
OTAFILE=bleota-s3.bin
|
||||
fi
|
||||
if [ "$MCU" != "esp32s3" ]; then
|
||||
if [ -n "${FILENAME##*"esp32c3"*}" ]; then
|
||||
OTAFILE=bleota.bin
|
||||
else
|
||||
OTAFILE=bleota-c3.bin
|
||||
fi
|
||||
else
|
||||
OTAFILE=bleota-s3.bin
|
||||
fi
|
||||
|
||||
# Check if WEB_APP (--web) is enabled and add "littlefswebui-" to BASENAME else "littlefs-".
|
||||
if [ "$WEB_APP" = true ]; then
|
||||
SPIFFSFILE=littlefswebui-${BASENAME}
|
||||
else
|
||||
SPIFFSFILE=littlefs-${BASENAME}
|
||||
fi
|
||||
# Set SPIFFS filename with "littlefs-" prefix.
|
||||
SPIFFSFILE=littlefs-${BASENAME}
|
||||
|
||||
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
|
||||
if [[ ! -f "$FILENAME" ]]; then
|
||||
echo "Error: file ${FILENAME} wasn't found. Terminating."
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "$OTAFILE" ]]; then
|
||||
echo "Error: file ${OTAFILE} wasn't found. Terminating."
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "$SPIFFSFILE" ]]; then
|
||||
echo "Error: file ${SPIFFSFILE} wasn't found. Terminating."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
|
||||
$ESPTOOL_CMD erase_flash
|
||||
$ESPTOOL_CMD write_flash 0x00 "${FILENAME}"
|
||||
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
|
||||
$ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}"
|
||||
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
|
||||
$ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}"
|
||||
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
|
||||
$ESPTOOL_CMD erase_flash
|
||||
$ESPTOOL_CMD write_flash 0x00 "${FILENAME}"
|
||||
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
|
||||
$ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}"
|
||||
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
|
||||
$ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}"
|
||||
|
||||
else
|
||||
show_help
|
||||
echo "Invalid file: ${FILENAME}"
|
||||
show_help
|
||||
echo "Invalid file: ${FILENAME}"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -8,12 +8,13 @@ SET "PYTHON="
|
||||
SET "ESPTOOL_BAUD=115200"
|
||||
SET "ESPTOOL_CMD="
|
||||
SET "LOGCOUNTER=0"
|
||||
SET "CHANGE_MODE=0"
|
||||
|
||||
GOTO getopts
|
||||
:help
|
||||
ECHO Flash image file to device, but leave existing system intact.
|
||||
ECHO.
|
||||
ECHO Usage: %SCRIPT_NAME% -f filename [-p PORT] [-P python]
|
||||
ECHO Usage: %SCRIPT_NAME% -f filename [-p PORT] [-P python] [--change-mode]
|
||||
ECHO.
|
||||
ECHO Options:
|
||||
ECHO -f filename The update .bin file to flash. Custom to your device type and region. (required)
|
||||
@@ -23,12 +24,15 @@ 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 --change-mode Attempt to place the device in correct mode. (1200bps Reset)
|
||||
ECHO Some hardware requires this twice.
|
||||
ECHO.
|
||||
ECHO Example: %SCRIPT_NAME% -p COM17 --change-mode
|
||||
ECHO Example: %SCRIPT_NAME% -f firmware-t-deck-tft-2.6.0.0b106d4-update.bin -p COM11
|
||||
GOTO eof
|
||||
|
||||
:version
|
||||
ECHO %SCRIPT_NAME% [Version 2.6.1]
|
||||
ECHO %SCRIPT_NAME% [Version 2.6.2]
|
||||
ECHO Meshtastic
|
||||
GOTO eof
|
||||
|
||||
@@ -44,10 +48,13 @@ 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"=="--change-mode" SET "CHANGE_MODE=1"
|
||||
SHIFT
|
||||
GOTO getopts
|
||||
:endopts
|
||||
|
||||
IF %CHANGE_MODE% EQU 1 GOTO skip-filename
|
||||
|
||||
CALL :LOG_MESSAGE DEBUG "Checking FILENAME parameter..."
|
||||
IF "__!FILENAME!__"=="____" (
|
||||
CALL :LOG_MESSAGE DEBUG "Missing -f filename input."
|
||||
@@ -77,6 +84,9 @@ IF "!FILENAME:update=!"=="!FILENAME!" (
|
||||
CALL :LOG_MESSAGE DEBUG "We are working with a *update* file. !FILENAME!"
|
||||
)
|
||||
|
||||
:skip-filename
|
||||
SET "ESPTOOL_BAUD=1200"
|
||||
|
||||
CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
|
||||
IF NOT "__%PYTHON%__"=="____" (
|
||||
SET "ESPTOOL_CMD=!PYTHON! -m esptool"
|
||||
@@ -115,6 +125,12 @@ IF "__!ESPTOOL_PORT!__" == "____" (
|
||||
)
|
||||
CALL :LOG_MESSAGE INFO "Using esptool baud: !ESPTOOL_BAUD!."
|
||||
|
||||
IF %CHANGE_MODE% EQU 1 (
|
||||
@REM Attempt to change mode via 1200bps Reset.
|
||||
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! --after no_reset read_flash_status
|
||||
GOTO eof
|
||||
)
|
||||
|
||||
@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
|
||||
@@ -135,6 +151,7 @@ EXIT /B %ERRORLEVEL%
|
||||
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 %CHANGE_MODE% EQU 1 GOTO :eof
|
||||
IF %ERRORLEVEL% NEQ 0 (
|
||||
CALL :LOG_MESSAGE ERROR "Error running command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
|
||||
EXIT /B %ERRORLEVEL%
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
|
||||
CHANGE_MODE=false
|
||||
|
||||
# Determine the correct esptool command to use
|
||||
if "$PYTHON" -m esptool version >/dev/null 2>&1; then
|
||||
@@ -17,17 +18,30 @@ fi
|
||||
# Usage info
|
||||
show_help() {
|
||||
cat << EOF
|
||||
Usage: $(basename $0) [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME|FILENAME]
|
||||
Usage: $(basename "$0") [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME|FILENAME] [--change-mode]
|
||||
Flash image file to device, leave existing system intact."
|
||||
|
||||
-h Display this help and exit
|
||||
-p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerous).
|
||||
-P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: "$PYTHON")
|
||||
-f FILENAME The *update.bin file to flash. Custom to your device type.
|
||||
|
||||
--change-mode Attempt to place the device in correct mode. Some hardware requires this twice. (1200bps Reset)
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Check for --change-mode and remove it from arguments
|
||||
NEW_ARGS=""
|
||||
for arg in "$@"; do
|
||||
if [ "$arg" = "--change-mode" ]; then
|
||||
CHANGE_MODE=true
|
||||
else
|
||||
NEW_ARGS="$NEW_ARGS \"\$arg\""
|
||||
fi
|
||||
done
|
||||
|
||||
# Reset positional parameters to filtered list
|
||||
eval set -- $NEW_ARGS
|
||||
|
||||
while getopts ":hp:P:f:" opt; do
|
||||
case "${opt}" in
|
||||
@@ -36,13 +50,13 @@ while getopts ":hp:P:f:" opt; do
|
||||
exit 0
|
||||
;;
|
||||
p) ESPTOOL_CMD="$ESPTOOL_CMD --port ${OPTARG}"
|
||||
;;
|
||||
;;
|
||||
P) PYTHON=${OPTARG}
|
||||
;;
|
||||
f) FILENAME=${OPTARG}
|
||||
;;
|
||||
*)
|
||||
echo "Invalid flag."
|
||||
echo "Invalid flag."
|
||||
show_help >&2
|
||||
exit 1
|
||||
;;
|
||||
@@ -50,17 +64,22 @@ while getopts ":hp:P:f:" opt; do
|
||||
done
|
||||
shift "$((OPTIND-1))"
|
||||
|
||||
[ -z "$FILENAME" -a -n "$1" ] && {
|
||||
FILENAME=$1
|
||||
if [ "$CHANGE_MODE" = true ]; then
|
||||
$ESPTOOL_CMD --baud 1200 --after no_reset read_flash_status
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[ -z "$FILENAME" ] && [ -n "$1" ] && {
|
||||
FILENAME="$1"
|
||||
shift
|
||||
}
|
||||
|
||||
if [ -f "${FILENAME}" ] && [ -z "${FILENAME##*"update"*}" ]; then
|
||||
printf "Trying to flash update ${FILENAME}"
|
||||
$ESPTOOL_CMD --baud 115200 write_flash 0x10000 ${FILENAME}
|
||||
echo "Trying to flash update ${FILENAME}"
|
||||
$ESPTOOL_CMD --baud 115200 write_flash 0x10000 "${FILENAME}"
|
||||
else
|
||||
show_help
|
||||
echo "Invalid file: ${FILENAME}"
|
||||
show_help
|
||||
echo "Invalid file: ${FILENAME}"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -27,7 +27,7 @@ for subdir, dirs, files in os.walk(rootdir):
|
||||
if c.startswith("env:"):
|
||||
section = config[c].name[4:]
|
||||
if "extends" in config[config[c].name]:
|
||||
if config[config[c].name]["extends"] == options[0] + "_base":
|
||||
if options[0] + "_base" in config[config[c].name]["extends"]:
|
||||
if "board_level" in config[config[c].name]:
|
||||
if (
|
||||
config[config[c].name]["board_level"] == "extra"
|
||||
|
||||
@@ -5,10 +5,11 @@ StartLimitInterval=200
|
||||
StartLimitBurst=5
|
||||
|
||||
[Service]
|
||||
User=root
|
||||
Group=root
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
User=meshtasticd
|
||||
Group=meshtasticd
|
||||
Type=simple
|
||||
ExecStart=/usr/sbin/meshtasticd
|
||||
ExecStart=/usr/bin/meshtasticd
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cp "release/meshtasticd_linux_$(uname -m)" /usr/sbin/meshtasticd
|
||||
cp "release/meshtasticd_linux_$(uname -m)" /usr/bin/meshtasticd
|
||||
mkdir -p /etc/meshtasticd
|
||||
if [[ -f "/etc/meshtasticd/config.yaml" ]]; then
|
||||
cp bin/config-dist.yaml /etc/meshtasticd/config-upgrade.yaml
|
||||
|
||||
@@ -87,6 +87,30 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="2.7.2" date="2025-07-04">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.2</url>
|
||||
</release>
|
||||
<release version="2.7.1" date="2025-06-27">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.1</url>
|
||||
</release>
|
||||
<release version="2.7.0" date="2025-06-20">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.0</url>
|
||||
</release>
|
||||
<release version="2.6.13" date="2025-06-16">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.6.13</url>
|
||||
</release>
|
||||
<release version="2.6.12" date="2025-06-15">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.6.12</url>
|
||||
</release>
|
||||
<release version="2.6.11" date="2025-06-02">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.6.11</url>
|
||||
</release>
|
||||
<release version="2.6.10" date="2025-05-25">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.6.10</url>
|
||||
</release>
|
||||
<release version="2.6.9" date="2025-05-15">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.6.9</url>
|
||||
</release>
|
||||
<release version="2.6.8" date="2025-05-05">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.6.8</url>
|
||||
</release>
|
||||
|
||||
@@ -131,3 +131,33 @@ for lb in env.GetLibBuilders():
|
||||
if lb.name == "meshtastic-device-ui":
|
||||
lb.env.Append(CPPDEFINES=[("APP_VERSION", verObj["long"])])
|
||||
break
|
||||
|
||||
# Get the display resolution from macros
|
||||
def get_display_resolution(build_flags):
|
||||
# Check "DISPLAY_SIZE" to determine the screen resolution
|
||||
for flag in build_flags:
|
||||
if isinstance(flag, tuple) and flag[0] == "DISPLAY_SIZE":
|
||||
screen_width, screen_height = map(int, flag[1].split("x"))
|
||||
return screen_width, screen_height
|
||||
print("No screen resolution defined in build_flags. Please define DISPLAY_SIZE.")
|
||||
exit(1)
|
||||
|
||||
def load_boot_logo(source, target, env):
|
||||
build_flags = env.get("CPPDEFINES", [])
|
||||
logo_w, logo_h = get_display_resolution(build_flags)
|
||||
print(f"TFT build with {logo_w}x{logo_h} resolution detected")
|
||||
|
||||
# Load the boot logo from `branding/logo_<width>x<height>.png` if it exists
|
||||
source_path = join(env["PROJECT_DIR"], "branding", f"logo_{logo_w}x{logo_h}.png")
|
||||
dest_dir = join(env["PROJECT_DIR"], "data", "boot")
|
||||
dest_path = join(dest_dir, "logo.png")
|
||||
if env.File(source_path).exists():
|
||||
print(f"Loading boot logo from {source_path}")
|
||||
# Prepare the destination
|
||||
env.Execute(f"mkdir -p {dest_dir} && rm -f {dest_path}")
|
||||
# Copy the logo to the `data/boot` directory
|
||||
env.Execute(f"cp {source_path} {dest_path}")
|
||||
|
||||
# Load the boot logo on TFT builds
|
||||
if ("HAS_TFT", 1) in env.get("CPPDEFINES", []):
|
||||
env.AddPreAction('$BUILD_DIR/littlefs.bin', load_boot_logo)
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.5.3
|
||||
2.6.4
|
||||
@@ -48,6 +48,6 @@
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "FIXME",
|
||||
"url": "https://www.elecrow.com/thinknode-m1-meshtastic-lora-signal-transceiver-powered-by-nrf52840-with-154-screen-support-gps.html",
|
||||
"vendor": "ELECROW"
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=0",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=0"
|
||||
],
|
||||
|
||||
52
boards/gat562_mesh_trial_tracker.json
Normal file
52
boards/gat562_mesh_trial_tracker.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x239A", "0x8029"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"],
|
||||
["0x239A", "0x802A"]
|
||||
],
|
||||
"usb_product": "GAT562 Mesh Trial Tracker",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "gat562_mesh_trial_tracker",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino", "freertos"],
|
||||
"name": "GAT562 Mesh Trial Tracker",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "http://www.gat-iot.com/",
|
||||
"vendor": "GAT-IOT"
|
||||
}
|
||||
@@ -48,6 +48,6 @@
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "FIXME",
|
||||
"url": "https://heltec.org/project/mesh-node-t114/",
|
||||
"vendor": "Heltec"
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"hwids": [["0x2886", "0x0059"]],
|
||||
"usb_product": "XIAO-BOOT",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "Seeed_Solar_Node",
|
||||
"variant": "seeed_solar_node",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
@@ -31,7 +31,7 @@
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Seeed_Solar_Node",
|
||||
"name": "seeed_solar_node",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
57
boards/seeed_wio_tracker_L1.json
Normal file
57
boards/seeed_wio_tracker_L1.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v7.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_MDBT50Q_RX -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x2886", "0x1668"],
|
||||
["0x2886", "0x1667"]
|
||||
],
|
||||
"usb_product": "TRACKER L1",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "seeed_wio_tracker_L1",
|
||||
"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_wio_tracker_L1",
|
||||
"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/Wio-Tracker-L1-p-6477.html",
|
||||
"vendor": "Seeed Studio"
|
||||
}
|
||||
@@ -7,9 +7,7 @@
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_MDBT50Q_RX -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x2886", "0x0166"]
|
||||
],
|
||||
"hwids": [["0x2886", "0x0166"]],
|
||||
"usb_product": "XIAO-BOOT",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "seeed_xiao_nrf52840_kit",
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
["0x239A", "0x8029"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"],
|
||||
["0x239A", "0x802A"]
|
||||
["0x239A", "0x802A"],
|
||||
["0x2886", "0x0057"]
|
||||
],
|
||||
"usb_product": "T1000-E-BOOT",
|
||||
"mcu": "nrf52840",
|
||||
|
||||
41
boards/wiscore_rak3312.json
Normal file
41
boards/wiscore_rak3312.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"memory_type": "qio_opi",
|
||||
"partitions": "default_16MB.csv"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DRAK3312",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1",
|
||||
"-DBOARD_HAS_PSRAM"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "dio",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "rak3312"
|
||||
},
|
||||
"connectivity": ["wifi", "bluetooth"],
|
||||
"debug": {
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "WisCore RAK3312 Board",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 16777216,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://www.rakwireless.com/en-us",
|
||||
"vendor": "rakwireless"
|
||||
}
|
||||
17
branding/README.md
Normal file
17
branding/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Meshtastic Branding / Whitelabeling
|
||||
|
||||
This directory is consumed during the creation of **event** firmware.
|
||||
|
||||
`bin/platformio-custom.py` determines the display resolution, and locates the corresponding `logo_<width>x<height>.png`.
|
||||
|
||||
Ex:
|
||||
|
||||
- `logo_800x480.png`
|
||||
- `logo_480x480.png`
|
||||
- `logo_480x320.png`
|
||||
- `logo_320x480.png`
|
||||
- `logo_320x240.png`
|
||||
|
||||
This file is copied to `data/boot/logo.png` before filesytem image compilation.
|
||||
|
||||
For additional examples see the [`event/defcon33` branch](https://github.com/meshtastic/firmware/tree/event/defcon33).
|
||||
22
debian/changelog
vendored
22
debian/changelog
vendored
@@ -1,4 +1,4 @@
|
||||
meshtasticd (2.6.8.0) UNRELEASED; urgency=medium
|
||||
meshtasticd (2.7.2.0) UNRELEASED; urgency=medium
|
||||
|
||||
[ Austin Lane ]
|
||||
* Initial packaging
|
||||
@@ -10,4 +10,22 @@ meshtasticd (2.6.8.0) UNRELEASED; urgency=medium
|
||||
[ ]
|
||||
* GitHub Actions Automatic version bump
|
||||
|
||||
-- <github-actions[bot]@users.noreply.github.com> Tue, 06 May 2025 01:32:49 +0000
|
||||
[ ]
|
||||
* GitHub Actions Automatic version bump
|
||||
|
||||
[ ]
|
||||
* GitHub Actions Automatic version bump
|
||||
|
||||
[ ]
|
||||
* GitHub Actions Automatic version bump
|
||||
|
||||
[ ]
|
||||
* GitHub Actions Automatic version bump
|
||||
|
||||
[ ]
|
||||
* GitHub Actions Automatic version bump
|
||||
|
||||
[ ]
|
||||
* GitHub Actions Automatic version bump
|
||||
|
||||
-- <github-actions[bot]@users.noreply.github.com> Fri, 04 Jul 2025 11:58:01 +0000
|
||||
|
||||
6
debian/control
vendored
6
debian/control
vendored
@@ -31,7 +31,9 @@ Rules-Requires-Root: no
|
||||
|
||||
Package: meshtasticd
|
||||
Architecture: any
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}
|
||||
Depends: adduser,
|
||||
${misc:Depends},
|
||||
${shlibs:Depends}
|
||||
Description: Meshtastic daemon for communicating with Meshtastic devices
|
||||
Meshtastic is an off-grid text communication platform that uses inexpensive
|
||||
LoRa radios.
|
||||
LoRa radios.
|
||||
|
||||
3
debian/meshtasticd.dirs
vendored
3
debian/meshtasticd.dirs
vendored
@@ -1,5 +1,6 @@
|
||||
var/lib/meshtasticd
|
||||
etc/meshtasticd
|
||||
etc/meshtasticd/config.d
|
||||
etc/meshtasticd/available.d
|
||||
usr/share/meshtasticd/web
|
||||
etc/meshtasticd/ssl
|
||||
etc/meshtasticd/ssl
|
||||
|
||||
4
debian/meshtasticd.install
vendored
4
debian/meshtasticd.install
vendored
@@ -1,8 +1,8 @@
|
||||
.pio/build/native-tft/meshtasticd usr/sbin
|
||||
.pio/build/native-tft/meshtasticd usr/bin
|
||||
|
||||
bin/config.yaml etc/meshtasticd
|
||||
bin/config.d/* etc/meshtasticd/available.d
|
||||
|
||||
bin/meshtasticd.service lib/systemd/system
|
||||
|
||||
web/* usr/share/meshtasticd/web
|
||||
web/* usr/share/meshtasticd/web
|
||||
|
||||
80
debian/meshtasticd.postinst
vendored
Executable file
80
debian/meshtasticd.postinst
vendored
Executable file
@@ -0,0 +1,80 @@
|
||||
#!/bin/sh
|
||||
# postinst script for meshtasticd
|
||||
#
|
||||
# see: dh_installdeb(1)
|
||||
|
||||
set -e
|
||||
|
||||
# summary of how this script can be called:
|
||||
# * <postinst> `configure' <most-recently-configured-version>
|
||||
# * <old-postinst> `abort-upgrade' <new version>
|
||||
# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
|
||||
# <new-version>
|
||||
# * <postinst> `abort-remove'
|
||||
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
|
||||
# <failed-install-package> <version> `removing'
|
||||
# <conflicting-package> <version>
|
||||
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||
# the debian-policy package
|
||||
|
||||
|
||||
case "$1" in
|
||||
configure|reconfigure)
|
||||
# create spi, gpio groups (for udev rules)
|
||||
# these groups already exist on Raspberry Pi OS
|
||||
getent group spi >/dev/null 2>/dev/null || addgroup --system spi
|
||||
getent group gpio >/dev/null 2>/dev/null || addgroup --system gpio
|
||||
# create a meshtasticd group and user
|
||||
getent passwd meshtasticd >/dev/null 2>/dev/null || adduser --system --home /var/lib/meshtasticd --no-create-home meshtasticd
|
||||
getent group meshtasticd >/dev/null 2>/dev/null || addgroup --system meshtasticd
|
||||
adduser meshtasticd meshtasticd >/dev/null 2>/dev/null
|
||||
adduser meshtasticd spi >/dev/null 2>/dev/null
|
||||
adduser meshtasticd gpio >/dev/null 2>/dev/null
|
||||
# add meshtasticd user to appropriate groups (if they exist)
|
||||
getent group plugdev >/dev/null 2>/dev/null && adduser meshtasticd plugdev >/dev/null 2>/dev/null
|
||||
getent group dialout >/dev/null 2>/dev/null && adduser meshtasticd dialout >/dev/null 2>/dev/null
|
||||
getent group i2c >/dev/null 2>/dev/null && adduser meshtasticd i2c >/dev/null 2>/dev/null
|
||||
getent group video >/dev/null 2>/dev/null && adduser meshtasticd video >/dev/null 2>/dev/null
|
||||
getent group audio >/dev/null 2>/dev/null && adduser meshtasticd audio >/dev/null 2>/dev/null
|
||||
getent group input >/dev/null 2>/dev/null && adduser meshtasticd input >/dev/null 2>/dev/null
|
||||
|
||||
|
||||
# migrate /root/.portduino to /var/lib/meshtasticd/.portduino
|
||||
# should only run once, upon upgrade from < 2.6.9
|
||||
if [ -n "$2" ] && dpkg --compare-versions "$2" lt 2.6.9; then
|
||||
if [ -d /root/.portduino ] && [ ! -e /var/lib/meshtasticd/.portduino ]; then
|
||||
cp -r /root/.portduino /var/lib/meshtasticd/.portduino
|
||||
echo "Migrated meshtasticd VFS from /root/.portduino to /var/lib/meshtasticd/.portduino"
|
||||
echo "meshtasticd now runs as the 'meshtasticd' user, not 'root'."
|
||||
echo "See https://github.com/meshtastic/firmware/pull/6718 for details"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -d /var/lib/meshtasticd ]; then
|
||||
chown -R meshtasticd:meshtasticd /var/lib/meshtasticd
|
||||
fi
|
||||
|
||||
if [ -d /etc/meshtasticd ]; then
|
||||
chown -R meshtasticd:meshtasticd /etc/meshtasticd
|
||||
fi
|
||||
|
||||
if [ -d /usr/share/meshtasticd ]; then
|
||||
chown -R meshtasticd:meshtasticd /usr/share/meshtasticd
|
||||
fi
|
||||
;;
|
||||
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "postinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# dh_installdeb will replace this with shell code automatically
|
||||
# generated by other debhelper scripts.
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
||||
41
debian/meshtasticd.postrm
vendored
Executable file
41
debian/meshtasticd.postrm
vendored
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/bin/sh
|
||||
# postrm script for meshtasticd
|
||||
#
|
||||
# see: dh_installdeb(1)
|
||||
|
||||
set -e
|
||||
|
||||
# summary of how this script can be called:
|
||||
# * <postrm> `remove'
|
||||
# * <postrm> `purge'
|
||||
# * <old-postrm> `upgrade' <new-version>
|
||||
# * <new-postrm> `failed-upgrade' <old-version>
|
||||
# * <new-postrm> `abort-install'
|
||||
# * <new-postrm> `abort-install' <old-version>
|
||||
# * <new-postrm> `abort-upgrade' <old-version>
|
||||
# * <disappearer's-postrm> `disappear' <overwriter>
|
||||
# <overwriter-version>
|
||||
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||
# the debian-policy package
|
||||
|
||||
|
||||
case "$1" in
|
||||
purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
|
||||
# Only remove /var/lib/meshtasticd on purge
|
||||
if [ "${1}" = "purge" ] ; then
|
||||
rm -rf /var/lib/meshtasticd
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "postrm called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# dh_installdeb will replace this with shell code automatically
|
||||
# generated by other debhelper scripts.
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
||||
7
debian/meshtasticd.udev
vendored
Normal file
7
debian/meshtasticd.udev
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Set spidev ownership to 'spi' group
|
||||
SUBSYSTEM=="spidev", KERNEL=="spidev*", GROUP="spi", MODE="0660"
|
||||
# Allow access to USB CH341 devices
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="5512", MODE="0666"
|
||||
# Set gpio ownership to 'gpio' group
|
||||
SUBSYSTEM=="*gpiomem*", GROUP="gpio", MODE="0660"
|
||||
SUBSYSTEM=="gpio", GROUP="gpio", MODE="0660"
|
||||
@@ -10,6 +10,8 @@
|
||||
# - https://docs.pagure.org/rpkg-util/v3/index.html
|
||||
# - https://docs.fedoraproject.org/en-US/packaging-guidelines/Versioning/
|
||||
|
||||
%global meshtasticd_user meshtasticd
|
||||
|
||||
Name: meshtasticd
|
||||
# Version Ex: 2.5.19
|
||||
Version: {{{ meshtastic_version }}}
|
||||
@@ -47,6 +49,8 @@ BuildRequires: pkgconfig(x11)
|
||||
BuildRequires: pkgconfig(libinput)
|
||||
BuildRequires: pkgconfig(xkbcommon-x11)
|
||||
|
||||
Requires: systemd-udev
|
||||
|
||||
%description
|
||||
Meshtastic daemon for controlling Meshtastic devices. Meshtastic is an off-grid
|
||||
text communication platform that uses inexpensive LoRa radios.
|
||||
@@ -63,15 +67,25 @@ gzip -dr web
|
||||
platformio run -e native-tft
|
||||
|
||||
%install
|
||||
mkdir -p %{buildroot}%{_sbindir}
|
||||
install -m 0755 .pio/build/native-tft/program %{buildroot}%{_sbindir}/meshtasticd
|
||||
# Install meshtasticd binary
|
||||
mkdir -p %{buildroot}%{_bindir}
|
||||
install -m 0755 .pio/build/native-tft/program %{buildroot}%{_bindir}/meshtasticd
|
||||
|
||||
# Install portduino VFS dir
|
||||
install -p -d -m 0770 %{buildroot}%{_localstatedir}/lib/meshtasticd
|
||||
|
||||
# Install udev rules
|
||||
mkdir -p %{buildroot}%{_udevrulesdir}
|
||||
install -m 0644 bin/99-meshtasticd-udev.rules %{buildroot}%{_udevrulesdir}/99-meshtasticd-udev.rules
|
||||
|
||||
# Install config dirs
|
||||
mkdir -p %{buildroot}%{_sysconfdir}/meshtasticd
|
||||
install -m 0644 bin/config-dist.yaml %{buildroot}%{_sysconfdir}/meshtasticd/config.yaml
|
||||
mkdir -p %{buildroot}%{_sysconfdir}/meshtasticd/config.d
|
||||
mkdir -p %{buildroot}%{_sysconfdir}/meshtasticd/available.d
|
||||
cp -r bin/config.d/* %{buildroot}%{_sysconfdir}/meshtasticd/available.d
|
||||
|
||||
# Install systemd service
|
||||
install -D -m 0644 bin/meshtasticd.service %{buildroot}%{_unitdir}/meshtasticd.service
|
||||
|
||||
# Install the web files under /usr/share/meshtasticd/web
|
||||
@@ -80,10 +94,54 @@ cp -r web/* %{buildroot}%{_datadir}/meshtasticd/web
|
||||
# Install default SSL storage directory (for web)
|
||||
mkdir -p %{buildroot}%{_sysconfdir}/meshtasticd/ssl
|
||||
|
||||
%pre
|
||||
# create spi group (for udev rules)
|
||||
getent group spi > /dev/null || groupadd -r spi
|
||||
# create a meshtasticd group and user
|
||||
getent group %{meshtasticd_user} > /dev/null || groupadd -r %{meshtasticd_user}
|
||||
getent passwd %{meshtasticd_user} > /dev/null || \
|
||||
useradd -r -d %{_localstatedir}/lib/meshtasticd -g %{meshtasticd_user} -G spi \
|
||||
-s /sbin/nologin -c "Meshtastic Daemon" %{meshtasticd_user}
|
||||
# add meshtasticd user to appropriate groups (if they exist)
|
||||
getent group gpio > /dev/null && usermod -a -G gpio %{meshtasticd_user} > /dev/null
|
||||
getent group plugdev > /dev/null && usermod -a -G plugdev %{meshtasticd_user} > /dev/null
|
||||
getent group dialout > /dev/null && usermod -a -G dialout %{meshtasticd_user} > /dev/null
|
||||
getent group i2c > /dev/null && usermod -a -G i2c %{meshtasticd_user} > /dev/null
|
||||
getent group video > /dev/null && usermod -a -G video %{meshtasticd_user} > /dev/null
|
||||
getent group audio > /dev/null && usermod -a -G audio %{meshtasticd_user} > /dev/null
|
||||
getent group input > /dev/null && usermod -a -G input %{meshtasticd_user} > /dev/null
|
||||
exit 0
|
||||
|
||||
%triggerin -- meshtasticd < 2.6.9
|
||||
# migrate .portduino (if it exists and hasn’t already been copied)
|
||||
if [ -d /root/.portduino ] && [ ! -e /var/lib/meshtasticd/.portduino ]; then
|
||||
mkdir -p /var/lib/meshtasticd
|
||||
cp -r /root/.portduino /var/lib/meshtasticd/.portduino
|
||||
chown -R %{meshtasticd_user}:%{meshtasticd_user} \
|
||||
%{_localstatedir}/lib/meshtasticd || :
|
||||
# Fix SELinux labels if present (no-op on non-SELinux systems)
|
||||
restorecon -R /var/lib/meshtasticd/.portduino 2>/dev/null || :
|
||||
echo "Migrated meshtasticd VFS from /root/.portduino to /var/lib/meshtasticd/.portduino"
|
||||
echo "meshtasticd now runs as the 'meshtasticd' user, not 'root'."
|
||||
echo "See https://github.com/meshtastic/firmware/pull/6718 for details"
|
||||
fi
|
||||
|
||||
%post
|
||||
%systemd_post meshtasticd.service
|
||||
|
||||
%preun
|
||||
%systemd_preun meshtasticd.service
|
||||
|
||||
%postun
|
||||
%systemd_postun_with_restart meshtasticd.service
|
||||
|
||||
%files
|
||||
%defattr(-,%{meshtasticd_user},%{meshtasticd_user})
|
||||
%license LICENSE
|
||||
%doc README.md
|
||||
%{_sbindir}/meshtasticd
|
||||
%{_bindir}/meshtasticd
|
||||
%dir %{_localstatedir}/lib/meshtasticd
|
||||
%{_udevrulesdir}/99-meshtasticd-udev.rules
|
||||
%dir %{_sysconfdir}/meshtasticd
|
||||
%dir %{_sysconfdir}/meshtasticd/config.d
|
||||
%dir %{_sysconfdir}/meshtasticd/available.d
|
||||
@@ -96,4 +154,4 @@ mkdir -p %{buildroot}%{_sysconfdir}/meshtasticd/ssl
|
||||
%dir %{_sysconfdir}/meshtasticd/ssl
|
||||
|
||||
%changelog
|
||||
%autochangelog
|
||||
%autochangelog
|
||||
|
||||
@@ -49,10 +49,11 @@ build_flags = -Wno-missing-field-initializers
|
||||
-DMESHTASTIC_EXCLUDE_DROPZONE=1
|
||||
-DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1
|
||||
-DMESHTASTIC_EXCLUDE_HEALTH_TELEMETRY=1
|
||||
-DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
|
||||
-DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
|
||||
-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
|
||||
#-DBUILD_EPOCH=$UNIX_TIME
|
||||
#-D OLED_PL=1
|
||||
-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
|
||||
#-DBUILD_EPOCH=$UNIX_TIME
|
||||
#-D OLED_PL=1
|
||||
|
||||
monitor_speed = 115200
|
||||
monitor_filters = direct
|
||||
@@ -103,12 +104,12 @@ lib_deps =
|
||||
[radiolib_base]
|
||||
lib_deps =
|
||||
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
|
||||
jgromes/RadioLib@7.1.2
|
||||
jgromes/RadioLib@7.2.0
|
||||
|
||||
[device-ui_base]
|
||||
lib_deps =
|
||||
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
||||
https://github.com/meshtastic/device-ui/archive/35576e131e250f259878ea81819a90df837d1307.zip
|
||||
https://github.com/meshtastic/device-ui/archive/8c7092c73425adfda1aac8c6960df06cd85f6d92.zip
|
||||
|
||||
; Common libs for environmental measurements in telemetry module
|
||||
[environmental_base]
|
||||
@@ -122,7 +123,7 @@ lib_deps =
|
||||
# renovate: datasource=custom.pio depName=Adafruit BMP085 packageName=adafruit/library/Adafruit BMP085 Library
|
||||
adafruit/Adafruit BMP085 Library@1.2.4
|
||||
# renovate: datasource=custom.pio depName=Adafruit BME280 packageName=adafruit/library/Adafruit BME280 Library
|
||||
adafruit/Adafruit BME280 Library@2.2.4
|
||||
adafruit/Adafruit BME280 Library@2.3.0
|
||||
# renovate: datasource=custom.pio depName=Adafruit DPS310 packageName=adafruit/library/Adafruit DPS310
|
||||
adafruit/Adafruit DPS310@1.1.5
|
||||
# renovate: datasource=custom.pio depName=Adafruit MCP9808 packageName=adafruit/library/Adafruit MCP9808 Library
|
||||
@@ -132,7 +133,7 @@ lib_deps =
|
||||
# renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219
|
||||
adafruit/Adafruit INA219@1.2.3
|
||||
# renovate: datasource=custom.pio depName=Adafruit PM25 AQI Sensor packageName=adafruit/library/Adafruit PM25 AQI Sensor
|
||||
adafruit/Adafruit PM25 AQI Sensor@1.2.0
|
||||
adafruit/Adafruit PM25 AQI Sensor@2.0.0
|
||||
# renovate: datasource=custom.pio depName=Adafruit MPU6050 packageName=adafruit/library/Adafruit MPU6050
|
||||
adafruit/Adafruit MPU6050@2.2.6
|
||||
# renovate: datasource=custom.pio depName=Adafruit LIS3DH packageName=adafruit/library/Adafruit LIS3DH
|
||||
@@ -147,8 +148,6 @@ lib_deps =
|
||||
emotibit/EmotiBit MLX90632@1.0.8
|
||||
# renovate: datasource=custom.pio depName=Adafruit MLX90614 packageName=adafruit/library/Adafruit MLX90614 Library
|
||||
adafruit/Adafruit MLX90614 Library@2.1.5
|
||||
# renovate: datasource=custom.pio depName=Bosch BME68x packageName=boschsensortec/library/BME68x Sensor Library
|
||||
boschsensortec/BME68x Sensor Library@1.2.40408
|
||||
# renovate: datasource=github-tags depName=INA3221 packageName=KodinLanewave/INA3221
|
||||
https://github.com/KodinLanewave/INA3221/archive/1.0.1.zip
|
||||
# renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass
|
||||
@@ -161,6 +160,14 @@ lib_deps =
|
||||
robtillaart/INA226@0.6.4
|
||||
# renovate: datasource=custom.pio depName=SparkFun MAX3010x packageName=sparkfun/library/SparkFun MAX3010x Pulse and Proximity Sensor Library
|
||||
sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@1.1.2
|
||||
# renovate: datasource=custom.pio depName=SparkFun 9DoF IMU Breakout ICM 20948 packageName=sparkfun/library/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library
|
||||
sparkfun/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library@1.3.2
|
||||
# renovate: datasource=custom.pio depName=Adafruit LTR390 Library packageName=adafruit/library/Adafruit LTR390 Library
|
||||
adafruit/Adafruit LTR390 Library@1.1.2
|
||||
# renovate: datasource=custom.pio depName=Adafruit PCT2075 packageName=adafruit/library/Adafruit PCT2075
|
||||
adafruit/Adafruit PCT2075@1.0.5
|
||||
# renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150
|
||||
dfrobot/DFRobot_BMM150@1.0.0
|
||||
|
||||
; (not included in native / portduino)
|
||||
[environmental_extra]
|
||||
@@ -181,11 +188,15 @@ lib_deps =
|
||||
adafruit/Adafruit SHT4x Library@1.0.5
|
||||
# renovate: datasource=custom.pio depName=SparkFun Qwiic Scale NAU7802 packageName=sparkfun/library/SparkFun Qwiic Scale NAU7802 Arduino Library
|
||||
sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@1.0.6
|
||||
# renovate: datasource=custom.pio depName=SparkFun 9DoF IMU Breakout ICM 20948 packageName=sparkfun/library/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library
|
||||
sparkfun/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library@1.3.0
|
||||
# renovate: datasource=custom.pio depName=ClosedCube OPT3001 packageName=closedcube/library/ClosedCube OPT3001
|
||||
ClosedCube OPT3001@1.1.2
|
||||
# renovate: datasource=git-refs depName=Bosch BSEC2 packageName=https://github.com/meshtastic/Bosch-BSEC2-Library gitBranch=extra_script
|
||||
https://github.com/meshtastic/Bosch-BSEC2-Library/archive/e16952dfe5addd4287e1eb8c4f6ecac0fa3dd3de.zip
|
||||
# renovate: datasource=custom.pio depName=Bosch BSEC2 packageName=boschsensortec/library/bsec2
|
||||
boschsensortec/bsec2@1.10.2610
|
||||
# renovate: datasource=custom.pio depName=Bosch BME68x packageName=boschsensortec/library/BME68x Sensor Library
|
||||
boschsensortec/BME68x Sensor Library@1.3.40408
|
||||
# renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master
|
||||
https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip
|
||||
# renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core
|
||||
sensirion/Sensirion Core@0.7.1
|
||||
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
|
||||
sensirion/Sensirion I2C SCD4x@1.1.0
|
||||
|
||||
Submodule protobufs updated: 078ac8dfbe...584f0a3a35
@@ -9,15 +9,21 @@
|
||||
"workarounds:all"
|
||||
],
|
||||
"forkProcessing": "enabled",
|
||||
"ignoreDeps": ["protobufs"],
|
||||
"ignoreDeps": [
|
||||
"protobufs"
|
||||
],
|
||||
"git-submodules": {
|
||||
"enabled": true
|
||||
},
|
||||
"pip_requirements": {
|
||||
"fileMatch": ["bin/bump_metainfo/requirements.txt"]
|
||||
"managerFilePatterns": [
|
||||
"/bin/bump_metainfo/requirements.txt/"
|
||||
]
|
||||
},
|
||||
"commitMessageTopic": "{{depName}}",
|
||||
"labels": ["dependencies"],
|
||||
"labels": [
|
||||
"dependencies"
|
||||
],
|
||||
"customDatasources": {
|
||||
"pio": {
|
||||
"description": "PlatformIO Registry",
|
||||
@@ -32,8 +38,12 @@
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Match meshtastic/web version",
|
||||
"fileMatch": ["bin/web.version"],
|
||||
"matchStrings": ["(?<currentValue>.+)$"],
|
||||
"managerFilePatterns": [
|
||||
"/bin/web.version/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"(?<currentValue>.+)$"
|
||||
],
|
||||
"datasourceTemplate": "github-releases",
|
||||
"depNameTemplate": "meshtastic/web",
|
||||
"versioningTemplate": "semver-coerced"
|
||||
@@ -41,7 +51,9 @@
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Match normal PIO dependencies",
|
||||
"fileMatch": [".*\\.ini$"],
|
||||
"managerFilePatterns": [
|
||||
"/.*\\.ini$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"# renovate: datasource=(?<datasource>.*?)(?: depName=(?<depName>.+?))? packageName=(?<packageName>.+?)(?: versioning=(?<versioning>[a-z-]+?))?\\s+?.+?@(?<currentValue>.+?)\\s"
|
||||
],
|
||||
@@ -50,9 +62,11 @@
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Match PIO zipped dependencies with github tag ref",
|
||||
"fileMatch": [".*\\.ini$"],
|
||||
"managerFilePatterns": [
|
||||
"/.*\\.ini$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"# renovate: datasource=github-tags(?: depName=(?<depName>.+?))? packageName=(?<packageName>.+?)(?: versioning=(?<versioning>[a-z-]+?))?\\s+?https:\/\/.+?archive\/(?<currentValue>.+?).zip\\s"
|
||||
"# renovate: datasource=github-tags(?: depName=(?<depName>.+?))? packageName=(?<packageName>.+?)(?: versioning=(?<versioning>[a-z-]+?))?\\s+?https://.+?archive/(?<currentValue>.+?).zip\\s"
|
||||
],
|
||||
"datasourceTemplate": "github-tags",
|
||||
"versioningTemplate": "{{#if versioning}}{{{versioning}}}{{else}}semver-coerced{{/if}}"
|
||||
@@ -60,9 +74,11 @@
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Match PIO zipped dependencies with git commit ref",
|
||||
"fileMatch": [".*\\.ini$"],
|
||||
"managerFilePatterns": [
|
||||
"/.*\\.ini$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"# renovate: datasource=git-refs(?: depName=(?<depName>.+?))? packageName=(?<packageName>.+?)(?: versioning=(?<versioning>[a-z-]+?))?\\sgitBranch=(?<gitBranch>.+?)\\s+?https:\/\/.+?archive\/(?<currentDigest>.+?).zip\\s"
|
||||
"# renovate: datasource=git-refs(?: depName=(?<depName>.+?))? packageName=(?<packageName>.+?)(?: versioning=(?<versioning>[a-z-]+?))?\\sgitBranch=(?<gitBranch>.+?)\\s+?https://.+?archive/(?<currentDigest>.+?).zip\\s"
|
||||
],
|
||||
"datasourceTemplate": "git-refs",
|
||||
"currentValueTemplate": "{{{gitBranch}}}",
|
||||
@@ -71,8 +87,12 @@
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchDepNames": ["meshtastic/device-ui"],
|
||||
"reviewers": ["mverch67"],
|
||||
"matchDepNames": [
|
||||
"meshtastic/device-ui"
|
||||
],
|
||||
"reviewers": [
|
||||
"mverch67"
|
||||
],
|
||||
"changelogUrl": "https://github.com/meshtastic/device-ui/compare/{{currentDigest}}...{{newDigest}}"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -59,82 +59,82 @@ class AmbientLightingThread : public concurrency::OSThread
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG("AmbientLighting init");
|
||||
#if defined(HAS_NCP5623) || defined(HAS_LP5562)
|
||||
#ifdef HAS_NCP5623
|
||||
if (_type == ScanI2C::NCP5623) {
|
||||
rgb.begin();
|
||||
#endif
|
||||
#ifdef HAS_LP5562
|
||||
} else if (_type == ScanI2C::LP5562) {
|
||||
rgbw.begin();
|
||||
if (_type == ScanI2C::LP5562) {
|
||||
rgbw.begin();
|
||||
#endif
|
||||
#ifdef RGBLED_RED
|
||||
pinMode(RGBLED_RED, OUTPUT);
|
||||
pinMode(RGBLED_GREEN, OUTPUT);
|
||||
pinMode(RGBLED_BLUE, OUTPUT);
|
||||
pinMode(RGBLED_RED, OUTPUT);
|
||||
pinMode(RGBLED_GREEN, OUTPUT);
|
||||
pinMode(RGBLED_BLUE, OUTPUT);
|
||||
#endif
|
||||
#ifdef HAS_NEOPIXEL
|
||||
pixels.begin(); // Initialise the pixel(s)
|
||||
pixels.clear(); // Set all pixel colors to 'off'
|
||||
pixels.setBrightness(moduleConfig.ambient_lighting.current);
|
||||
pixels.begin(); // Initialise the pixel(s)
|
||||
pixels.clear(); // Set all pixel colors to 'off'
|
||||
pixels.setBrightness(moduleConfig.ambient_lighting.current);
|
||||
#endif
|
||||
setLighting();
|
||||
setLighting();
|
||||
#endif
|
||||
#if defined(HAS_NCP5623) || defined(HAS_LP5562)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
int32_t runOnce() override
|
||||
{
|
||||
protected:
|
||||
int32_t runOnce() override
|
||||
{
|
||||
#ifdef HAS_RGB_LED
|
||||
#if defined(HAS_NCP5623) || defined(HAS_LP5562)
|
||||
if ((_type == ScanI2C::NCP5623 || _type == ScanI2C::LP5562) && moduleConfig.ambient_lighting.led_state) {
|
||||
if ((_type == ScanI2C::NCP5623 || _type == ScanI2C::LP5562) && moduleConfig.ambient_lighting.led_state) {
|
||||
#endif
|
||||
setLighting();
|
||||
return 30000; // 30 seconds to reset from any animations that may have been running from Ext. Notification
|
||||
setLighting();
|
||||
return 30000; // 30 seconds to reset from any animations that may have been running from Ext. Notification
|
||||
#if defined(HAS_NCP5623) || defined(HAS_LP5562)
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
return disable();
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
return disable();
|
||||
}
|
||||
|
||||
// When shutdown() is issued, setLightingOff will be called.
|
||||
CallbackObserver<AmbientLightingThread, void *> notifyDeepSleepObserver =
|
||||
CallbackObserver<AmbientLightingThread, void *>(this, &AmbientLightingThread::setLightingOff);
|
||||
// When shutdown() is issued, setLightingOff will be called.
|
||||
CallbackObserver<AmbientLightingThread, void *> notifyDeepSleepObserver =
|
||||
CallbackObserver<AmbientLightingThread, void *>(this, &AmbientLightingThread::setLightingOff);
|
||||
|
||||
private:
|
||||
ScanI2C::DeviceType _type = ScanI2C::DeviceType::NONE;
|
||||
private:
|
||||
ScanI2C::DeviceType _type = ScanI2C::DeviceType::NONE;
|
||||
|
||||
// Turn RGB lighting off, is used in junction to shutdown()
|
||||
int setLightingOff(void *unused)
|
||||
{
|
||||
// Turn RGB lighting off, is used in junction to shutdown()
|
||||
int setLightingOff(void *unused)
|
||||
{
|
||||
#ifdef HAS_NCP5623
|
||||
rgb.setCurrent(0);
|
||||
rgb.setRed(0);
|
||||
rgb.setGreen(0);
|
||||
rgb.setBlue(0);
|
||||
LOG_INFO("OFF: NCP5623 Ambient lighting");
|
||||
rgb.setCurrent(0);
|
||||
rgb.setRed(0);
|
||||
rgb.setGreen(0);
|
||||
rgb.setBlue(0);
|
||||
LOG_INFO("OFF: NCP5623 Ambient lighting");
|
||||
#endif
|
||||
#ifdef HAS_LP5562
|
||||
rgbw.setCurrent(0);
|
||||
rgbw.setRed(0);
|
||||
rgbw.setGreen(0);
|
||||
rgbw.setBlue(0);
|
||||
rgbw.setWhite(0);
|
||||
LOG_INFO("OFF: LP5562 Ambient lighting");
|
||||
rgbw.setCurrent(0);
|
||||
rgbw.setRed(0);
|
||||
rgbw.setGreen(0);
|
||||
rgbw.setBlue(0);
|
||||
rgbw.setWhite(0);
|
||||
LOG_INFO("OFF: LP5562 Ambient lighting");
|
||||
#endif
|
||||
#ifdef HAS_NEOPIXEL
|
||||
pixels.clear();
|
||||
pixels.show();
|
||||
LOG_INFO("OFF: NeoPixel Ambient lighting");
|
||||
pixels.clear();
|
||||
pixels.show();
|
||||
LOG_INFO("OFF: NeoPixel Ambient lighting");
|
||||
#endif
|
||||
#ifdef RGBLED_CA
|
||||
analogWrite(RGBLED_RED, 255 - 0);
|
||||
analogWrite(RGBLED_GREEN, 255 - 0);
|
||||
analogWrite(RGBLED_BLUE, 255 - 0);
|
||||
LOG_INFO("OFF: Ambient light RGB Common Anode");
|
||||
analogWrite(RGBLED_RED, 255 - 0);
|
||||
analogWrite(RGBLED_GREEN, 255 - 0);
|
||||
analogWrite(RGBLED_BLUE, 255 - 0);
|
||||
LOG_INFO("OFF: Ambient light RGB Common Anode");
|
||||
#elif defined(RGBLED_RED)
|
||||
analogWrite(RGBLED_RED, 0);
|
||||
analogWrite(RGBLED_GREEN, 0);
|
||||
@@ -142,56 +142,57 @@ class AmbientLightingThread : public concurrency::OSThread
|
||||
LOG_INFO("OFF: Ambient light RGB Common Cathode");
|
||||
#endif
|
||||
#ifdef UNPHONE
|
||||
unphone.rgb(0, 0, 0);
|
||||
LOG_INFO("OFF: unPhone Ambient lighting");
|
||||
unphone.rgb(0, 0, 0);
|
||||
LOG_INFO("OFF: unPhone Ambient lighting");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void setLighting()
|
||||
{
|
||||
void setLighting()
|
||||
{
|
||||
#ifdef HAS_NCP5623
|
||||
rgb.setCurrent(moduleConfig.ambient_lighting.current);
|
||||
rgb.setRed(moduleConfig.ambient_lighting.red);
|
||||
rgb.setGreen(moduleConfig.ambient_lighting.green);
|
||||
rgb.setBlue(moduleConfig.ambient_lighting.blue);
|
||||
LOG_DEBUG("Init NCP5623 Ambient light w/ current=%d, red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.current,
|
||||
moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
rgb.setCurrent(moduleConfig.ambient_lighting.current);
|
||||
rgb.setRed(moduleConfig.ambient_lighting.red);
|
||||
rgb.setGreen(moduleConfig.ambient_lighting.green);
|
||||
rgb.setBlue(moduleConfig.ambient_lighting.blue);
|
||||
LOG_DEBUG("Init NCP5623 Ambient light w/ current=%d, red=%d, green=%d, blue=%d",
|
||||
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
|
||||
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
#endif
|
||||
#ifdef HAS_LP5562
|
||||
rgbw.setCurrent(moduleConfig.ambient_lighting.current);
|
||||
rgbw.setRed(moduleConfig.ambient_lighting.red);
|
||||
rgbw.setGreen(moduleConfig.ambient_lighting.green);
|
||||
rgbw.setBlue(moduleConfig.ambient_lighting.blue);
|
||||
LOG_DEBUG("Init LP5562 Ambient light w/ current=%d, red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.current,
|
||||
moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
rgbw.setCurrent(moduleConfig.ambient_lighting.current);
|
||||
rgbw.setRed(moduleConfig.ambient_lighting.red);
|
||||
rgbw.setGreen(moduleConfig.ambient_lighting.green);
|
||||
rgbw.setBlue(moduleConfig.ambient_lighting.blue);
|
||||
LOG_DEBUG("Init LP5562 Ambient light w/ current=%d, red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.current,
|
||||
moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
#endif
|
||||
#ifdef HAS_NEOPIXEL
|
||||
pixels.fill(pixels.Color(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
|
||||
moduleConfig.ambient_lighting.blue),
|
||||
0, NEOPIXEL_COUNT);
|
||||
pixels.fill(pixels.Color(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
|
||||
moduleConfig.ambient_lighting.blue),
|
||||
0, NEOPIXEL_COUNT);
|
||||
|
||||
// RadioMaster Bandit has addressable LED at the two buttons
|
||||
// this allow us to set different lighting for them in variant.h file.
|
||||
#ifdef RADIOMASTER_900_BANDIT
|
||||
#if defined(BUTTON1_COLOR) && defined(BUTTON1_COLOR_INDEX)
|
||||
pixels.fill(BUTTON1_COLOR, BUTTON1_COLOR_INDEX, 1);
|
||||
pixels.fill(BUTTON1_COLOR, BUTTON1_COLOR_INDEX, 1);
|
||||
#endif
|
||||
#if defined(BUTTON2_COLOR) && defined(BUTTON2_COLOR_INDEX)
|
||||
pixels.fill(BUTTON2_COLOR, BUTTON2_COLOR_INDEX, 1);
|
||||
pixels.fill(BUTTON2_COLOR, BUTTON2_COLOR_INDEX, 1);
|
||||
#endif
|
||||
#endif
|
||||
pixels.show();
|
||||
LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d",
|
||||
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
|
||||
moduleConfig.ambient_lighting.blue);
|
||||
pixels.show();
|
||||
LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d",
|
||||
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
|
||||
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
#endif
|
||||
#ifdef RGBLED_CA
|
||||
analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red);
|
||||
analogWrite(RGBLED_GREEN, 255 - moduleConfig.ambient_lighting.green);
|
||||
analogWrite(RGBLED_BLUE, 255 - moduleConfig.ambient_lighting.blue);
|
||||
LOG_DEBUG("Init Ambient light RGB Common Anode w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red,
|
||||
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red);
|
||||
analogWrite(RGBLED_GREEN, 255 - moduleConfig.ambient_lighting.green);
|
||||
analogWrite(RGBLED_BLUE, 255 - moduleConfig.ambient_lighting.blue);
|
||||
LOG_DEBUG("Init Ambient light RGB Common Anode w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red,
|
||||
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
#elif defined(RGBLED_RED)
|
||||
analogWrite(RGBLED_RED, moduleConfig.ambient_lighting.red);
|
||||
analogWrite(RGBLED_GREEN, moduleConfig.ambient_lighting.green);
|
||||
@@ -200,11 +201,12 @@ class AmbientLightingThread : public concurrency::OSThread
|
||||
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
#endif
|
||||
#ifdef UNPHONE
|
||||
unphone.rgb(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
LOG_DEBUG("Init unPhone Ambient light w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red,
|
||||
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
unphone.rgb(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
|
||||
moduleConfig.ambient_lighting.blue);
|
||||
LOG_DEBUG("Init unPhone Ambient light w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red,
|
||||
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
@@ -47,6 +47,20 @@ class AudioThread : public concurrency::OSThread
|
||||
setCPUFast(false);
|
||||
}
|
||||
|
||||
void readAloud(const char *text)
|
||||
{
|
||||
if (i2sRtttl != nullptr) {
|
||||
i2sRtttl->stop();
|
||||
delete i2sRtttl;
|
||||
i2sRtttl = nullptr;
|
||||
}
|
||||
|
||||
ESP8266SAM *sam = new ESP8266SAM;
|
||||
sam->Say(audioOut, text);
|
||||
delete sam;
|
||||
setCPUFast(false);
|
||||
}
|
||||
|
||||
protected:
|
||||
int32_t runOnce() override
|
||||
{
|
||||
|
||||
@@ -88,10 +88,16 @@ class BluetoothStatus : public Status
|
||||
break;
|
||||
case ConnectionState::CONNECTED:
|
||||
LOG_DEBUG("BluetoothStatus CONNECTED");
|
||||
#ifdef BLE_LED
|
||||
digitalWrite(BLE_LED, HIGH);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case ConnectionState::DISCONNECTED:
|
||||
LOG_DEBUG("BluetoothStatus DISCONNECTED");
|
||||
#ifdef BLE_LED
|
||||
digitalWrite(BLE_LED, LOW);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -102,4 +108,4 @@ class BluetoothStatus : public Status
|
||||
|
||||
} // namespace meshtastic
|
||||
|
||||
extern meshtastic::BluetoothStatus *bluetoothStatus;
|
||||
extern meshtastic::BluetoothStatus *bluetoothStatus;
|
||||
|
||||
@@ -1,458 +0,0 @@
|
||||
#include "ButtonThread.h"
|
||||
|
||||
#include "configuration.h"
|
||||
#if !MESHTASTIC_EXCLUDE_GPS
|
||||
#include "GPS.h"
|
||||
#endif
|
||||
#include "MeshService.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "RadioLibInterface.h"
|
||||
#include "buzz.h"
|
||||
#include "main.h"
|
||||
#include "modules/ExternalNotificationModule.h"
|
||||
#include "power.h"
|
||||
#include "sleep.h"
|
||||
#ifdef ARCH_PORTDUINO
|
||||
#include "platform/portduino/PortduinoGlue.h"
|
||||
#endif
|
||||
|
||||
#define DEBUG_BUTTONS 0
|
||||
#if DEBUG_BUTTONS
|
||||
#define LOG_BUTTON(...) LOG_DEBUG(__VA_ARGS__)
|
||||
#else
|
||||
#define LOG_BUTTON(...)
|
||||
#endif
|
||||
|
||||
using namespace concurrency;
|
||||
|
||||
ButtonThread *buttonThread; // Declared extern in header
|
||||
volatile ButtonThread::ButtonEventType ButtonThread::btnEvent = ButtonThread::BUTTON_EVENT_NONE;
|
||||
|
||||
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) || defined(USERPREFS_BUTTON_PIN)
|
||||
OneButton ButtonThread::userButton; // Get reference to static member
|
||||
#endif
|
||||
ButtonThread::ButtonThread() : OSThread("Button")
|
||||
{
|
||||
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) || defined(USERPREFS_BUTTON_PIN)
|
||||
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) {
|
||||
this->userButton = OneButton(settingsMap[user], true, true);
|
||||
LOG_DEBUG("Use GPIO%02d for button", settingsMap[user]);
|
||||
}
|
||||
#elif defined(BUTTON_PIN)
|
||||
#if !defined(USERPREFS_BUTTON_PIN)
|
||||
int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; // Resolved button pin
|
||||
#endif
|
||||
#ifdef USERPREFS_BUTTON_PIN
|
||||
int pin = config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN; // Resolved button pin
|
||||
#endif
|
||||
#if defined(HELTEC_CAPSULE_SENSOR_V3) || defined(HELTEC_SENSOR_HUB)
|
||||
this->userButton = OneButton(pin, false, false);
|
||||
#elif defined(BUTTON_ACTIVE_LOW)
|
||||
this->userButton = OneButton(pin, BUTTON_ACTIVE_LOW, BUTTON_ACTIVE_PULLUP);
|
||||
#else
|
||||
this->userButton = OneButton(pin, true, true);
|
||||
#endif
|
||||
LOG_DEBUG("Use GPIO%02d for button", pin);
|
||||
#endif
|
||||
|
||||
#ifdef INPUT_PULLUP_SENSE
|
||||
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
|
||||
#ifdef BUTTON_SENSE_TYPE
|
||||
pinMode(pin, BUTTON_SENSE_TYPE);
|
||||
#else
|
||||
pinMode(pin, INPUT_PULLUP_SENSE);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) || defined(USERPREFS_BUTTON_PIN)
|
||||
userButton.attachClick(userButtonPressed);
|
||||
userButton.setClickMs(BUTTON_CLICK_MS);
|
||||
userButton.setPressMs(BUTTON_LONGPRESS_MS);
|
||||
userButton.setDebounceMs(1);
|
||||
userButton.attachDoubleClick(userButtonDoublePressed);
|
||||
userButton.attachMultiClick(userButtonMultiPressed, this); // Reference to instance: get click count from non-static OneButton
|
||||
#if !defined(T_DECK) && \
|
||||
!defined( \
|
||||
ELECROW_ThinkNode_M2) // T-Deck immediately wakes up after shutdown, Thinknode M2 has this on the smaller ALT button
|
||||
userButton.attachLongPressStart(userButtonPressedLongStart);
|
||||
userButton.attachLongPressStop(userButtonPressedLongStop);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef BUTTON_PIN_ALT
|
||||
#if defined(ELECROW_ThinkNode_M2)
|
||||
this->userButtonAlt = OneButton(BUTTON_PIN_ALT, false, false);
|
||||
#else
|
||||
this->userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true);
|
||||
#endif
|
||||
#ifdef INPUT_PULLUP_SENSE
|
||||
// Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
|
||||
pinMode(BUTTON_PIN_ALT, INPUT_PULLUP_SENSE);
|
||||
#endif
|
||||
userButtonAlt.attachClick(userButtonPressedScreen);
|
||||
userButtonAlt.setClickMs(BUTTON_CLICK_MS);
|
||||
userButtonAlt.setPressMs(BUTTON_LONGPRESS_MS);
|
||||
userButtonAlt.setDebounceMs(1);
|
||||
userButtonAlt.attachLongPressStart(userButtonPressedLongStart);
|
||||
userButtonAlt.attachLongPressStop(userButtonPressedLongStop);
|
||||
#endif
|
||||
|
||||
#ifdef BUTTON_PIN_TOUCH
|
||||
userButtonTouch = OneButton(BUTTON_PIN_TOUCH, true, true);
|
||||
userButtonTouch.setPressMs(BUTTON_TOUCH_MS);
|
||||
userButtonTouch.attachLongPressStart(touchPressedLongStart); // Better handling with longpress than click?
|
||||
#endif
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// Register callbacks for before and after lightsleep
|
||||
// Used to detach and reattach interrupts
|
||||
lsObserver.observe(¬ifyLightSleep);
|
||||
lsEndObserver.observe(¬ifyLightSleepEnd);
|
||||
#endif
|
||||
|
||||
attachButtonInterrupts();
|
||||
#endif
|
||||
}
|
||||
|
||||
void ButtonThread::switchPage()
|
||||
{
|
||||
#ifdef BUTTON_PIN
|
||||
#if !defined(USERPREFS_BUTTON_PIN)
|
||||
if (((config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN) !=
|
||||
moduleConfig.canned_message.inputbroker_pin_press) ||
|
||||
!(moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.rotary1_enabled) ||
|
||||
!moduleConfig.canned_message.enabled) {
|
||||
powerFSM.trigger(EVENT_PRESS);
|
||||
}
|
||||
#endif
|
||||
#if defined(USERPREFS_BUTTON_PIN)
|
||||
if (((config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN) !=
|
||||
moduleConfig.canned_message.inputbroker_pin_press) ||
|
||||
!(moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.rotary1_enabled) ||
|
||||
!moduleConfig.canned_message.enabled) {
|
||||
powerFSM.trigger(EVENT_PRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
if ((settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) &&
|
||||
(settingsMap[user] != moduleConfig.canned_message.inputbroker_pin_press) ||
|
||||
!moduleConfig.canned_message.enabled) {
|
||||
powerFSM.trigger(EVENT_PRESS);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ButtonThread::sendAdHocPosition()
|
||||
{
|
||||
service->refreshLocalMeshNode();
|
||||
auto sentPosition = service->trySendPosition(NODENUM_BROADCAST, true);
|
||||
if (screen) {
|
||||
if (sentPosition)
|
||||
screen->print("Sent ad-hoc position\n");
|
||||
else
|
||||
screen->print("Sent ad-hoc nodeinfo\n");
|
||||
screen->forceDisplay(true); // Force a new UI frame, then force an EInk update
|
||||
}
|
||||
}
|
||||
|
||||
int32_t ButtonThread::runOnce()
|
||||
{
|
||||
// If the button is pressed we suppress CPU sleep until release
|
||||
canSleep = true; // Assume we should not keep the board awake
|
||||
|
||||
#if defined(BUTTON_PIN) || defined(USERPREFS_BUTTON_PIN)
|
||||
userButton.tick();
|
||||
canSleep &= userButton.isIdle();
|
||||
#elif defined(ARCH_PORTDUINO)
|
||||
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) {
|
||||
userButton.tick();
|
||||
canSleep &= userButton.isIdle();
|
||||
}
|
||||
#endif
|
||||
#ifdef BUTTON_PIN_ALT
|
||||
userButtonAlt.tick();
|
||||
canSleep &= userButtonAlt.isIdle();
|
||||
#endif
|
||||
#ifdef BUTTON_PIN_TOUCH
|
||||
userButtonTouch.tick();
|
||||
canSleep &= userButtonTouch.isIdle();
|
||||
#endif
|
||||
|
||||
if (btnEvent != BUTTON_EVENT_NONE) {
|
||||
switch (btnEvent) {
|
||||
case BUTTON_EVENT_PRESSED: {
|
||||
LOG_BUTTON("press!");
|
||||
// If a nag notification is running, stop it and prevent other actions
|
||||
if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) {
|
||||
externalNotificationModule->stopNow();
|
||||
break;
|
||||
}
|
||||
#ifdef ELECROW_ThinkNode_M1
|
||||
sendAdHocPosition();
|
||||
break;
|
||||
#endif
|
||||
switchPage();
|
||||
break;
|
||||
}
|
||||
|
||||
case BUTTON_EVENT_PRESSED_SCREEN: {
|
||||
LOG_BUTTON("AltPress!");
|
||||
#ifdef ELECROW_ThinkNode_M1
|
||||
// If a nag notification is running, stop it and prevent other actions
|
||||
if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) {
|
||||
externalNotificationModule->stopNow();
|
||||
break;
|
||||
}
|
||||
switchPage();
|
||||
break;
|
||||
#endif
|
||||
// turn screen on or off
|
||||
screen_flag = !screen_flag;
|
||||
if (screen)
|
||||
screen->setOn(screen_flag);
|
||||
break;
|
||||
}
|
||||
|
||||
case BUTTON_EVENT_DOUBLE_PRESSED: {
|
||||
LOG_BUTTON("Double press!");
|
||||
#ifdef ELECROW_ThinkNode_M1
|
||||
digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
|
||||
break;
|
||||
#endif
|
||||
sendAdHocPosition();
|
||||
break;
|
||||
}
|
||||
|
||||
case BUTTON_EVENT_MULTI_PRESSED: {
|
||||
LOG_BUTTON("Mulitipress! %hux", multipressClickCount);
|
||||
switch (multipressClickCount) {
|
||||
#if HAS_GPS && !defined(ELECROW_ThinkNode_M1)
|
||||
// 3 clicks: toggle GPS
|
||||
case 3:
|
||||
if (!config.device.disable_triple_click && (gps != nullptr)) {
|
||||
gps->toggleGpsMode();
|
||||
if (screen)
|
||||
screen->forceDisplay(true); // Force a new UI frame, then force an EInk update
|
||||
}
|
||||
break;
|
||||
#elif defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2)
|
||||
case 3:
|
||||
LOG_INFO("3 clicks: toggle buzzer");
|
||||
buzzer_flag = !buzzer_flag;
|
||||
if (!buzzer_flag)
|
||||
noTone(PIN_BUZZER);
|
||||
break;
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(USE_EINK) && defined(PIN_EINK_EN) && !defined(ELECROW_ThinkNode_M1) // i.e. T-Echo
|
||||
// 4 clicks: toggle backlight
|
||||
case 4:
|
||||
digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
|
||||
break;
|
||||
#endif
|
||||
#if defined(RAK_4631)
|
||||
// 5 clicks: start accelerometer/magenetometer calibration for 30 seconds
|
||||
case 5:
|
||||
if (accelerometerThread) {
|
||||
accelerometerThread->calibrate(30);
|
||||
}
|
||||
break;
|
||||
// 6 clicks: start accelerometer/magenetometer calibration for 60 seconds
|
||||
case 6:
|
||||
if (accelerometerThread) {
|
||||
accelerometerThread->calibrate(60);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
// No valid multipress action
|
||||
default:
|
||||
break;
|
||||
} // end switch: click count
|
||||
|
||||
break;
|
||||
} // end multipress event
|
||||
|
||||
case BUTTON_EVENT_LONG_PRESSED: {
|
||||
LOG_BUTTON("Long press!");
|
||||
powerFSM.trigger(EVENT_PRESS);
|
||||
if (screen) {
|
||||
screen->startAlert("Shutting down...");
|
||||
}
|
||||
playBeep();
|
||||
break;
|
||||
}
|
||||
|
||||
// Do actual shutdown when button released, otherwise the button release
|
||||
// may wake the board immediatedly.
|
||||
case BUTTON_EVENT_LONG_RELEASED: {
|
||||
LOG_INFO("Shutdown from long press");
|
||||
playShutdownMelody();
|
||||
delay(3000);
|
||||
power->shutdown();
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef BUTTON_PIN_TOUCH
|
||||
case BUTTON_EVENT_TOUCH_LONG_PRESSED: {
|
||||
LOG_BUTTON("Touch press!");
|
||||
if (screen) {
|
||||
// Wake if asleep
|
||||
if (powerFSM.getState() == &stateDARK)
|
||||
powerFSM.trigger(EVENT_PRESS);
|
||||
|
||||
// Update display (legacy behaviour)
|
||||
screen->forceDisplay();
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif // BUTTON_PIN_TOUCH
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
btnEvent = BUTTON_EVENT_NONE;
|
||||
}
|
||||
|
||||
return 50;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attach (or re-attach) hardware interrupts for buttons
|
||||
* Public method. Used outside class when waking from MCU sleep
|
||||
*/
|
||||
void ButtonThread::attachButtonInterrupts()
|
||||
{
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC)
|
||||
wakeOnIrq(settingsMap[user], FALLING);
|
||||
#elif defined(BUTTON_PIN)
|
||||
// Interrupt for user button, during normal use. Improves responsiveness.
|
||||
attachInterrupt(
|
||||
#if !defined(USERPREFS_BUTTON_PIN)
|
||||
config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN,
|
||||
#endif
|
||||
#if defined(USERPREFS_BUTTON_PIN)
|
||||
config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN,
|
||||
#endif
|
||||
[]() {
|
||||
ButtonThread::userButton.tick();
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
mainDelay.interruptFromISR(&higherWake);
|
||||
},
|
||||
CHANGE);
|
||||
#endif
|
||||
|
||||
#ifdef BUTTON_PIN_ALT
|
||||
#ifdef ELECROW_ThinkNode_M2
|
||||
wakeOnIrq(BUTTON_PIN_ALT, RISING);
|
||||
#else
|
||||
wakeOnIrq(BUTTON_PIN_ALT, FALLING);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef BUTTON_PIN_TOUCH
|
||||
wakeOnIrq(BUTTON_PIN_TOUCH, FALLING);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Detach the "normal" button interrupts.
|
||||
* Public method. Used before attaching a "wake-on-button" interrupt for MCU sleep
|
||||
*/
|
||||
void ButtonThread::detachButtonInterrupts()
|
||||
{
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC)
|
||||
detachInterrupt(settingsMap[user]);
|
||||
#elif defined(BUTTON_PIN)
|
||||
#if !defined(USERPREFS_BUTTON_PIN)
|
||||
detachInterrupt(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN);
|
||||
#endif
|
||||
#if defined(USERPREFS_BUTTON_PIN)
|
||||
detachInterrupt(config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef BUTTON_PIN_ALT
|
||||
detachInterrupt(BUTTON_PIN_ALT);
|
||||
#endif
|
||||
|
||||
#ifdef BUTTON_PIN_TOUCH
|
||||
detachInterrupt(BUTTON_PIN_TOUCH);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
|
||||
// Detach our class' interrupts before lightsleep
|
||||
// Allows sleep.cpp to configure its own interrupts, which wake the device on user-button press
|
||||
int ButtonThread::beforeLightSleep(void *unused)
|
||||
{
|
||||
detachButtonInterrupts();
|
||||
return 0; // Indicates success
|
||||
}
|
||||
|
||||
// Reconfigure our interrupts
|
||||
// Our class' interrupts were disconnected during sleep, to allow the user button to wake the device from sleep
|
||||
int ButtonThread::afterLightSleep(esp_sleep_wakeup_cause_t cause)
|
||||
{
|
||||
attachButtonInterrupts();
|
||||
return 0; // Indicates success
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Watch a GPIO and if we get an IRQ, wake the main thread.
|
||||
* Use to add wake on button press
|
||||
*/
|
||||
void ButtonThread::wakeOnIrq(int irq, int mode)
|
||||
{
|
||||
attachInterrupt(
|
||||
irq,
|
||||
[] {
|
||||
BaseType_t higherWake = 0;
|
||||
mainDelay.interruptFromISR(&higherWake);
|
||||
runASAP = true;
|
||||
},
|
||||
FALLING);
|
||||
}
|
||||
|
||||
// Static callback
|
||||
void ButtonThread::userButtonMultiPressed(void *callerThread)
|
||||
{
|
||||
// Grab click count from non-static button, while the info is still valid
|
||||
ButtonThread *thread = (ButtonThread *)callerThread;
|
||||
thread->storeClickCount();
|
||||
|
||||
// Then handle later, in the usual way
|
||||
btnEvent = BUTTON_EVENT_MULTI_PRESSED;
|
||||
}
|
||||
|
||||
// Non-static method, runs during callback. Grabs info while still valid
|
||||
void ButtonThread::storeClickCount()
|
||||
{
|
||||
#if defined(BUTTON_PIN) || defined(USERPREFS_BUTTON_PIN)
|
||||
multipressClickCount = userButton.getNumberClicks();
|
||||
#endif
|
||||
}
|
||||
|
||||
void ButtonThread::userButtonPressedLongStart()
|
||||
{
|
||||
if (millis() > c_holdOffTime) {
|
||||
btnEvent = BUTTON_EVENT_LONG_PRESSED;
|
||||
}
|
||||
}
|
||||
|
||||
void ButtonThread::userButtonPressedLongStop()
|
||||
{
|
||||
if (millis() > c_holdOffTime) {
|
||||
btnEvent = BUTTON_EVENT_LONG_RELEASED;
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "OneButton.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "configuration.h"
|
||||
|
||||
#ifndef BUTTON_CLICK_MS
|
||||
#define BUTTON_CLICK_MS 250
|
||||
#endif
|
||||
|
||||
#ifndef BUTTON_LONGPRESS_MS
|
||||
#define BUTTON_LONGPRESS_MS 5000
|
||||
#endif
|
||||
|
||||
#ifndef BUTTON_TOUCH_MS
|
||||
#define BUTTON_TOUCH_MS 400
|
||||
#endif
|
||||
|
||||
class ButtonThread : public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
static const uint32_t c_holdOffTime = 30000; // hold off 30s after boot
|
||||
|
||||
enum ButtonEventType {
|
||||
BUTTON_EVENT_NONE,
|
||||
BUTTON_EVENT_PRESSED,
|
||||
BUTTON_EVENT_PRESSED_SCREEN,
|
||||
BUTTON_EVENT_DOUBLE_PRESSED,
|
||||
BUTTON_EVENT_MULTI_PRESSED,
|
||||
BUTTON_EVENT_LONG_PRESSED,
|
||||
BUTTON_EVENT_LONG_RELEASED,
|
||||
BUTTON_EVENT_TOUCH_LONG_PRESSED,
|
||||
};
|
||||
|
||||
ButtonThread();
|
||||
int32_t runOnce() override;
|
||||
void attachButtonInterrupts();
|
||||
void detachButtonInterrupts();
|
||||
void storeClickCount();
|
||||
bool isBuzzing() { return buzzer_flag; }
|
||||
void setScreenFlag(bool flag) { screen_flag = flag; }
|
||||
bool getScreenFlag() { return screen_flag; }
|
||||
|
||||
// Disconnect and reconnect interrupts for light sleep
|
||||
#ifdef ARCH_ESP32
|
||||
int beforeLightSleep(void *unused);
|
||||
int afterLightSleep(esp_sleep_wakeup_cause_t cause);
|
||||
#endif
|
||||
private:
|
||||
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) || defined(USERPREFS_BUTTON_PIN)
|
||||
static OneButton userButton; // Static - accessed from an interrupt
|
||||
#endif
|
||||
#ifdef BUTTON_PIN_ALT
|
||||
OneButton userButtonAlt;
|
||||
#endif
|
||||
#ifdef BUTTON_PIN_TOUCH
|
||||
OneButton userButtonTouch;
|
||||
#endif
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// Get notified when lightsleep begins and ends
|
||||
CallbackObserver<ButtonThread, void *> lsObserver =
|
||||
CallbackObserver<ButtonThread, void *>(this, &ButtonThread::beforeLightSleep);
|
||||
CallbackObserver<ButtonThread, esp_sleep_wakeup_cause_t> lsEndObserver =
|
||||
CallbackObserver<ButtonThread, esp_sleep_wakeup_cause_t>(this, &ButtonThread::afterLightSleep);
|
||||
#endif
|
||||
|
||||
// set during IRQ
|
||||
static volatile ButtonEventType btnEvent;
|
||||
bool buzzer_flag = false;
|
||||
bool screen_flag = true;
|
||||
|
||||
// Store click count during callback, for later use
|
||||
volatile int multipressClickCount = 0;
|
||||
|
||||
static void wakeOnIrq(int irq, int mode);
|
||||
|
||||
static void sendAdHocPosition();
|
||||
static void switchPage();
|
||||
|
||||
// IRQ callbacks
|
||||
static void userButtonPressed() { btnEvent = BUTTON_EVENT_PRESSED; }
|
||||
static void userButtonPressedScreen() { btnEvent = BUTTON_EVENT_PRESSED_SCREEN; }
|
||||
static void userButtonDoublePressed() { btnEvent = BUTTON_EVENT_DOUBLE_PRESSED; }
|
||||
static void userButtonMultiPressed(void *callerThread); // Retrieve click count from non-static Onebutton while still valid
|
||||
static void userButtonPressedLongStart();
|
||||
static void userButtonPressedLongStop();
|
||||
static void touchPressedLongStart() { btnEvent = BUTTON_EVENT_TOUCH_LONG_PRESSED; }
|
||||
};
|
||||
|
||||
extern ButtonThread *buttonThread;
|
||||
@@ -103,7 +103,7 @@ NullSensor ina3221Sensor;
|
||||
|
||||
#endif
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_STM32WL)
|
||||
#if !MESHTASTIC_EXCLUDE_I2C
|
||||
#include "modules/Telemetry/Sensor/MAX17048Sensor.h"
|
||||
#include <utility>
|
||||
extern std::pair<uint8_t, TwoWire *> nodeTelemetrySensorsMap[_meshtastic_TelemetrySensorType_MAX + 1];
|
||||
@@ -278,7 +278,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAS_TELEMETRY && !defined(ARCH_STM32WL) && !defined(HAS_PMU) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
#if HAS_TELEMETRY && !defined(HAS_PMU) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
if (hasINA()) {
|
||||
return getINAVoltage();
|
||||
}
|
||||
@@ -456,8 +456,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
#ifdef EXT_CHRG_DETECT
|
||||
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
|
||||
#else
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_STM32WL) && \
|
||||
!defined(DISABLE_INA_CHARGING_DETECTION)
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION)
|
||||
if (hasINA()) {
|
||||
// get current flow from INA sensor - negative value means power flowing into the battery
|
||||
// default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT RESISTOR <--> INA_VIN- <--> LOAD
|
||||
@@ -503,7 +502,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_STM32WL)
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
uint16_t getINAVoltage()
|
||||
{
|
||||
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA219].first == config.power.device_battery_ina_address) {
|
||||
@@ -661,12 +660,14 @@ bool Power::analogInit()
|
||||
*/
|
||||
bool Power::setup()
|
||||
{
|
||||
// initialise one power sensor (only)
|
||||
bool found = axpChipInit();
|
||||
if (!found)
|
||||
found = lipoInit();
|
||||
if (!found)
|
||||
found = analogInit();
|
||||
bool found = false;
|
||||
if (axpChipInit()) {
|
||||
found = true;
|
||||
} else if (lipoInit()) {
|
||||
found = true;
|
||||
} else if (analogInit()) {
|
||||
found = true;
|
||||
}
|
||||
|
||||
#ifdef NRF_APM
|
||||
found = true;
|
||||
@@ -680,7 +681,7 @@ bool Power::setup()
|
||||
|
||||
void Power::shutdown()
|
||||
{
|
||||
LOG_INFO("Shutting down");
|
||||
LOG_INFO("Shutting Down");
|
||||
|
||||
#if defined(ARCH_NRF52) || defined(ARCH_ESP32) || defined(ARCH_RP2040)
|
||||
#ifdef PIN_LED1
|
||||
@@ -853,7 +854,8 @@ int32_t Power::runOnce()
|
||||
#ifndef T_WATCH_S3 // FIXME - why is this triggering on the T-Watch S3?
|
||||
if (PMU->isPekeyLongPressIrq()) {
|
||||
LOG_DEBUG("PEK long button press");
|
||||
screen->setOn(false);
|
||||
if (screen)
|
||||
screen->setOn(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1158,7 +1160,7 @@ bool Power::axpChipInit()
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
|
||||
#if !MESHTASTIC_EXCLUDE_I2C && __has_include(<Adafruit_MAX1704X.h>)
|
||||
|
||||
/**
|
||||
* Wrapper class for an I2C MAX17048 Lipo battery sensor.
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#ifndef SLEEP_TIME
|
||||
#define SLEEP_TIME 30
|
||||
#endif
|
||||
#if EXCLUDE_POWER_FSM
|
||||
#if MESHTASTIC_EXCLUDE_POWER_FSM
|
||||
FakeFsm powerFSM;
|
||||
void PowerFSM_setup(){};
|
||||
#else
|
||||
@@ -82,7 +82,8 @@ static uint32_t secsSlept;
|
||||
static void lsEnter()
|
||||
{
|
||||
LOG_INFO("lsEnter begin, ls_secs=%u", config.power.ls_secs);
|
||||
screen->setOn(false);
|
||||
if (screen)
|
||||
screen->setOn(false);
|
||||
secsSlept = 0; // How long have we been sleeping this time
|
||||
|
||||
// LOG_INFO("lsEnter end");
|
||||
@@ -160,7 +161,8 @@ static void lsExit()
|
||||
static void nbEnter()
|
||||
{
|
||||
LOG_DEBUG("State: NB");
|
||||
screen->setOn(false);
|
||||
if (screen)
|
||||
screen->setOn(false);
|
||||
#ifdef ARCH_ESP32
|
||||
// Only ESP32 should turn off bluetooth
|
||||
setBluetoothEnable(false);
|
||||
@@ -172,22 +174,23 @@ static void nbEnter()
|
||||
static void darkEnter()
|
||||
{
|
||||
setBluetoothEnable(true);
|
||||
screen->setOn(false);
|
||||
if (screen)
|
||||
screen->setOn(false);
|
||||
}
|
||||
|
||||
static void serialEnter()
|
||||
{
|
||||
LOG_DEBUG("State: SERIAL");
|
||||
setBluetoothEnable(false);
|
||||
screen->setOn(true);
|
||||
screen->print("Serial connected\n");
|
||||
if (screen) {
|
||||
screen->setOn(true);
|
||||
}
|
||||
}
|
||||
|
||||
static void serialExit()
|
||||
{
|
||||
// Turn bluetooth back on when we leave serial stream API
|
||||
setBluetoothEnable(true);
|
||||
screen->print("Serial disconnected\n");
|
||||
}
|
||||
|
||||
static void powerEnter()
|
||||
@@ -198,15 +201,10 @@ static void powerEnter()
|
||||
LOG_INFO("Loss of power in Powered");
|
||||
powerFSM.trigger(EVENT_POWER_DISCONNECTED);
|
||||
} else {
|
||||
screen->setOn(true);
|
||||
if (screen)
|
||||
screen->setOn(true);
|
||||
setBluetoothEnable(true);
|
||||
// within enter() the function getState() returns the state we came from
|
||||
|
||||
// Mothballed: print change of power-state to device screen
|
||||
/* if (strcmp(powerFSM.getState()->name, "BOOT") != 0 && strcmp(powerFSM.getState()->name, "POWER") != 0 &&
|
||||
strcmp(powerFSM.getState()->name, "DARK") != 0) {
|
||||
screen->print("Powered...\n");
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,18 +219,16 @@ static void powerIdle()
|
||||
|
||||
static void powerExit()
|
||||
{
|
||||
screen->setOn(true);
|
||||
if (screen)
|
||||
screen->setOn(true);
|
||||
setBluetoothEnable(true);
|
||||
|
||||
// Mothballed: print change of power-state to device screen
|
||||
/*if (!isPowered())
|
||||
screen->print("Unpowered...\n");*/
|
||||
}
|
||||
|
||||
static void onEnter()
|
||||
{
|
||||
LOG_DEBUG("State: ON");
|
||||
screen->setOn(true);
|
||||
if (screen)
|
||||
screen->setOn(true);
|
||||
setBluetoothEnable(true);
|
||||
}
|
||||
|
||||
@@ -244,11 +240,6 @@ static void onIdle()
|
||||
}
|
||||
}
|
||||
|
||||
static void screenPress()
|
||||
{
|
||||
screen->onPress();
|
||||
}
|
||||
|
||||
static void bootEnter()
|
||||
{
|
||||
LOG_DEBUG("State: BOOT");
|
||||
@@ -292,9 +283,9 @@ void PowerFSM_setup()
|
||||
powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, NULL, "Press");
|
||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_PRESS, NULL, "Press");
|
||||
powerFSM.add_transition(&stateDARK, isPowered() ? &statePOWER : &stateON, EVENT_PRESS, NULL, "Press");
|
||||
powerFSM.add_transition(&statePOWER, &statePOWER, EVENT_PRESS, screenPress, "Press");
|
||||
powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, screenPress, "Press"); // reenter On to restart our timers
|
||||
powerFSM.add_transition(&stateSERIAL, &stateSERIAL, EVENT_PRESS, screenPress,
|
||||
powerFSM.add_transition(&statePOWER, &statePOWER, EVENT_PRESS, NULL, "Press");
|
||||
powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, NULL, "Press"); // reenter On to restart our timers
|
||||
powerFSM.add_transition(&stateSERIAL, &stateSERIAL, EVENT_PRESS, NULL,
|
||||
"Press"); // Allow button to work while in serial API
|
||||
|
||||
// Handle critically low power battery by forcing deep sleep
|
||||
@@ -328,10 +319,10 @@ void PowerFSM_setup()
|
||||
// if any packet destined for phone arrives, turn on bluetooth at least
|
||||
powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone");
|
||||
|
||||
// show the latest node when we get a new node db update
|
||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||
powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||
// Removed 2.7: we don't show the nodes individually for every node on the screen anymore
|
||||
// powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||
// powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||
// powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||
|
||||
// Show the received text message
|
||||
powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text");
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#define EVENT_RECEIVED_MSG 5
|
||||
// #define EVENT_BOOT 6 // now done with a timed transition
|
||||
#define EVENT_BLUETOOTH_PAIR 7
|
||||
#define EVENT_NODEDB_UPDATED 8 // NodeDB has a big enough change that we think you should turn on the screen
|
||||
// #define EVENT_NODEDB_UPDATED 8 // Now defunct: NodeDB has a big enough change that we think you should turn on the screen
|
||||
#define EVENT_CONTACT_FROM_PHONE 9 // the phone just talked to us over bluetooth
|
||||
#define EVENT_LOW_BATTERY 10 // Battery is critically low, go to sleep
|
||||
#define EVENT_SERIAL_CONNECTED 11
|
||||
@@ -22,7 +22,7 @@
|
||||
#define EVENT_SHUTDOWN 16 // force a full shutdown now (not just sleep)
|
||||
#define EVENT_INPUT 17 // input broker wants something, we need to wake up and enable screen
|
||||
|
||||
#if EXCLUDE_POWER_FSM
|
||||
#if MESHTASTIC_EXCLUDE_POWER_FSM
|
||||
class FakeFsm
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -18,7 +18,7 @@ class PowerFSMThread : public OSThread
|
||||
protected:
|
||||
int32_t runOnce() override
|
||||
{
|
||||
#if !EXCLUDE_POWER_FSM
|
||||
#if !MESHTASTIC_EXCLUDE_POWER_FSM
|
||||
powerFSM.run_machine();
|
||||
|
||||
/// If we are in power state we force the CPU to wake every 10ms to check for serial characters (we don't yet wake
|
||||
|
||||
@@ -352,8 +352,8 @@ void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16
|
||||
for (uint16_t i = 0; i < len; i += 16) {
|
||||
if (i % 128 == 0)
|
||||
log(logLevel, " +------------------------------------------------+ +----------------+");
|
||||
char s[] = "| | | |\n";
|
||||
uint8_t ix = 1, iy = 52;
|
||||
char s[] = " | | | |\n";
|
||||
uint8_t ix = 5, iy = 56;
|
||||
for (uint8_t j = 0; j < 16; j++) {
|
||||
if (i + j < len) {
|
||||
uint8_t c = buf[i + j];
|
||||
@@ -367,10 +367,8 @@ void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16
|
||||
}
|
||||
}
|
||||
uint8_t index = i / 16;
|
||||
if (i < 256)
|
||||
log(logLevel, " ");
|
||||
log(logLevel, "%02x", index);
|
||||
log(logLevel, ".");
|
||||
sprintf(s, "%03x", index);
|
||||
s[3] = '.';
|
||||
log(logLevel, s);
|
||||
}
|
||||
log(logLevel, " +------------------------------------------------+ +----------------+");
|
||||
@@ -393,4 +391,4 @@ std::string RedirectablePrint::mt_sprintf(const std::string fmt_str, ...)
|
||||
break;
|
||||
}
|
||||
return std::string(formatted.get());
|
||||
}
|
||||
}
|
||||
|
||||
79
src/buzz/BuzzerFeedbackThread.cpp
Normal file
79
src/buzz/BuzzerFeedbackThread.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#include "BuzzerFeedbackThread.h"
|
||||
#include "NodeDB.h"
|
||||
#include "buzz.h"
|
||||
#include "configuration.h"
|
||||
|
||||
BuzzerFeedbackThread *buzzerFeedbackThread;
|
||||
|
||||
BuzzerFeedbackThread::BuzzerFeedbackThread() : OSThread("BuzzerFeedback")
|
||||
{
|
||||
if (inputBroker)
|
||||
inputObserver.observe(inputBroker);
|
||||
}
|
||||
|
||||
int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
|
||||
{
|
||||
// Only provide feedback if buzzer is enabled for notifications
|
||||
if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DISABLED ||
|
||||
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_NOTIFICATIONS_ONLY) {
|
||||
return 0; // Let other handlers process the event
|
||||
}
|
||||
|
||||
// Track last event time for potential future use
|
||||
lastEventTime = millis();
|
||||
needsUpdate = true;
|
||||
|
||||
// Handle different input events with appropriate buzzer feedback
|
||||
switch (event->inputEvent) {
|
||||
case INPUT_BROKER_USER_PRESS:
|
||||
case INPUT_BROKER_ALT_PRESS:
|
||||
case INPUT_BROKER_SELECT:
|
||||
playBeep(); // Confirmation feedback
|
||||
break;
|
||||
|
||||
case INPUT_BROKER_UP:
|
||||
case INPUT_BROKER_DOWN:
|
||||
case INPUT_BROKER_LEFT:
|
||||
case INPUT_BROKER_RIGHT:
|
||||
playChirp(); // Navigation feedback
|
||||
break;
|
||||
|
||||
case INPUT_BROKER_CANCEL:
|
||||
case INPUT_BROKER_BACK:
|
||||
playBoop(); // Cancel/back feedback
|
||||
break;
|
||||
|
||||
case INPUT_BROKER_SEND_PING:
|
||||
playComboTune(); // Ping sent feedback
|
||||
break;
|
||||
|
||||
case INPUT_BROKER_SHUTDOWN:
|
||||
playShutdownMelody(); // Shutdown feedback
|
||||
break;
|
||||
|
||||
default:
|
||||
// For other events, check if it's a printable character
|
||||
if (event->kbchar >= 32 && event->kbchar <= 126) {
|
||||
// Typing feedback - very short boop
|
||||
// Removing this for now, too chatty
|
||||
// playChirp();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 0; // Allow other handlers to process the event
|
||||
}
|
||||
|
||||
int32_t BuzzerFeedbackThread::runOnce()
|
||||
{
|
||||
// This thread is primarily event-driven, but we can use runOnce
|
||||
// for any periodic tasks if needed in the future
|
||||
|
||||
if (needsUpdate) {
|
||||
needsUpdate = false;
|
||||
// Could add any periodic processing here
|
||||
}
|
||||
|
||||
// Run every 100ms when active, less frequently when idle
|
||||
return needsUpdate ? 100 : 1000;
|
||||
}
|
||||
24
src/buzz/BuzzerFeedbackThread.h
Normal file
24
src/buzz/BuzzerFeedbackThread.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "Observer.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "input/InputBroker.h"
|
||||
|
||||
class BuzzerFeedbackThread : public concurrency::OSThread
|
||||
{
|
||||
CallbackObserver<BuzzerFeedbackThread, const InputEvent *> inputObserver =
|
||||
CallbackObserver<BuzzerFeedbackThread, const InputEvent *>(this, &BuzzerFeedbackThread::handleInputEvent);
|
||||
|
||||
public:
|
||||
BuzzerFeedbackThread();
|
||||
int handleInputEvent(const InputEvent *event);
|
||||
|
||||
protected:
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
private:
|
||||
uint32_t lastEventTime = 0;
|
||||
bool needsUpdate = false;
|
||||
};
|
||||
|
||||
extern BuzzerFeedbackThread *buzzerFeedbackThread;
|
||||
@@ -38,6 +38,11 @@ const int DURATION_1_1 = 1000; // 1/1 note
|
||||
|
||||
void playTones(const ToneDuration *tone_durations, int size)
|
||||
{
|
||||
if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DISABLED ||
|
||||
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_NOTIFICATIONS_ONLY) {
|
||||
// Buzzer is disabled or not set to system tones
|
||||
return;
|
||||
}
|
||||
#ifdef PIN_BUZZER
|
||||
if (!config.device.buzzer_gpio)
|
||||
config.device.buzzer_gpio = PIN_BUZZER;
|
||||
@@ -54,7 +59,7 @@ void playTones(const ToneDuration *tone_durations, int size)
|
||||
|
||||
void playBeep()
|
||||
{
|
||||
ToneDuration melody[] = {{NOTE_B3, DURATION_1_4}};
|
||||
ToneDuration melody[] = {{NOTE_B3, DURATION_1_8}};
|
||||
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
|
||||
}
|
||||
|
||||
@@ -87,3 +92,72 @@ void playShutdownMelody()
|
||||
ToneDuration melody[] = {{NOTE_CS4, DURATION_1_8}, {NOTE_AS3, DURATION_1_8}, {NOTE_FS3, DURATION_1_4}};
|
||||
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
|
||||
}
|
||||
|
||||
void playChirp()
|
||||
{
|
||||
// A short, friendly "chirp" sound for key presses
|
||||
ToneDuration melody[] = {{NOTE_AS3, 20}}; // Very short AS3 note
|
||||
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
|
||||
}
|
||||
|
||||
void playBoop()
|
||||
{
|
||||
// A short, friendly "boop" sound for button presses
|
||||
ToneDuration melody[] = {{NOTE_A3, 50}}; // Very short A3 note
|
||||
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
|
||||
}
|
||||
|
||||
void playLongPressLeadUp()
|
||||
{
|
||||
// An ascending lead-up sequence for long press - builds anticipation
|
||||
ToneDuration melody[] = {
|
||||
{NOTE_C3, 100}, // Start low
|
||||
{NOTE_E3, 100}, // Step up
|
||||
{NOTE_G3, 100}, // Keep climbing
|
||||
{NOTE_B3, 150} // Peak with longer note for emphasis
|
||||
};
|
||||
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
|
||||
}
|
||||
|
||||
// Static state for progressive lead-up notes
|
||||
static int leadUpNoteIndex = 0;
|
||||
static const ToneDuration leadUpNotes[] = {
|
||||
{NOTE_C3, 100}, // Start low
|
||||
{NOTE_E3, 100}, // Step up
|
||||
{NOTE_G3, 100}, // Keep climbing
|
||||
{NOTE_B3, 150} // Peak with longer note for emphasis
|
||||
};
|
||||
static const int leadUpNotesCount = sizeof(leadUpNotes) / sizeof(ToneDuration);
|
||||
|
||||
bool playNextLeadUpNote()
|
||||
{
|
||||
if (leadUpNoteIndex >= leadUpNotesCount) {
|
||||
return false; // All notes have been played
|
||||
}
|
||||
|
||||
// Use playTones to handle buzzer logic consistently
|
||||
const auto ¬e = leadUpNotes[leadUpNoteIndex];
|
||||
playTones(¬e, 1); // Play single note using existing playTones function
|
||||
|
||||
leadUpNoteIndex++;
|
||||
return true; // Note was played (playTones handles buzzer availability internally)
|
||||
}
|
||||
|
||||
void resetLeadUpSequence()
|
||||
{
|
||||
leadUpNoteIndex = 0;
|
||||
}
|
||||
|
||||
void playComboTune()
|
||||
{
|
||||
// Quick high-pitched notes with trills
|
||||
ToneDuration melody[] = {
|
||||
{NOTE_G3, 80}, // Quick chirp
|
||||
{NOTE_B3, 60}, // Higher chirp
|
||||
{NOTE_CS4, 80}, // Even higher
|
||||
{NOTE_G3, 60}, // Quick trill down
|
||||
{NOTE_CS4, 60}, // Quick trill up
|
||||
{NOTE_B3, 120} // Ending chirp
|
||||
};
|
||||
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
|
||||
}
|
||||
|
||||
@@ -5,4 +5,10 @@ void playLongBeep();
|
||||
void playStartMelody();
|
||||
void playShutdownMelody();
|
||||
void playGPSEnableBeep();
|
||||
void playGPSDisableBeep();
|
||||
void playGPSDisableBeep();
|
||||
void playComboTune();
|
||||
void playBoop();
|
||||
void playChirp();
|
||||
void playLongPressLeadUp();
|
||||
bool playNextLeadUpNote(); // Play the next note in the lead-up sequence
|
||||
void resetLeadUpSequence(); // Reset the lead-up sequence to start from beginning
|
||||
@@ -12,7 +12,7 @@ enum class Cmd {
|
||||
STOP_ALERT_FRAME,
|
||||
START_FIRMWARE_UPDATE_SCREEN,
|
||||
STOP_BOOT_SCREEN,
|
||||
PRINT,
|
||||
SHOW_PREV_FRAME,
|
||||
SHOW_NEXT_FRAME
|
||||
SHOW_NEXT_FRAME,
|
||||
NOOP
|
||||
};
|
||||
@@ -9,17 +9,23 @@ namespace concurrency
|
||||
Lock::Lock() : handle(xSemaphoreCreateBinary())
|
||||
{
|
||||
assert(handle);
|
||||
assert(xSemaphoreGive(handle));
|
||||
if (xSemaphoreGive(handle) == false) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void Lock::lock()
|
||||
{
|
||||
assert(xSemaphoreTake(handle, portMAX_DELAY));
|
||||
if (xSemaphoreTake(handle, portMAX_DELAY) == false) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void Lock::unlock()
|
||||
{
|
||||
assert(xSemaphoreGive(handle));
|
||||
if (xSemaphoreGive(handle) == false) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
#else
|
||||
Lock::Lock() {}
|
||||
|
||||
@@ -80,10 +80,46 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// Override user saved region, for producing region-locked builds
|
||||
// #define REGULATORY_LORA_REGIONCODE meshtastic_Config_LoRaConfig_RegionCode_SG_923
|
||||
|
||||
// Total system gain in dBm to subtract from Tx power to remain within regulatory ERP limit for non-licensed operators
|
||||
// This value should be set in variant.h and is PA gain + antenna gain (if system ships with an antenna)
|
||||
#ifndef REGULATORY_GAIN_LORA
|
||||
#define REGULATORY_GAIN_LORA 0
|
||||
// Total system gain in dBm to subtract from Tx power to remain within regulatory and Tx PA limits
|
||||
// The value consists of PA gain + antenna gain (if variant has a non-removable antenna)
|
||||
// TX_GAIN_LORA should be set with definitions below for common modules, or in variant.h.
|
||||
|
||||
// Gain for common modules with transmit PAs
|
||||
#ifdef EBYTE_E22_900M30S
|
||||
// 10dB PA gain and 30dB rated output; based on measurements from
|
||||
// https://github.com/S5NC/EBYTE_ESP32-S3/blob/main/E22-900M30S%20power%20output%20testing.txt
|
||||
#define TX_GAIN_LORA 7
|
||||
#define SX126X_MAX_POWER 22
|
||||
#endif
|
||||
|
||||
#ifdef EBYTE_E22_900M33S
|
||||
// 25dB PA gain and 33dB rated output; based on TX Power Curve from E22-900M33S_UserManual_EN_v1.0.pdf
|
||||
#define TX_GAIN_LORA 25
|
||||
#define SX126X_MAX_POWER 8
|
||||
#endif
|
||||
|
||||
#ifdef NICERF_MINIF27
|
||||
// Note that datasheet power level of 9 corresponds with SX1262 at 22dBm
|
||||
// Maximum output power of 29dBm with VCC_PA = 5V
|
||||
#define TX_GAIN_LORA 7
|
||||
#define SX126X_MAX_POWER 22
|
||||
#endif
|
||||
|
||||
#ifdef NICERF_F30_HF
|
||||
// Maximum output power of 29.6dBm with VCC = 5V and SX1262 at 22dBm
|
||||
#define TX_GAIN_LORA 8
|
||||
#define SX126X_MAX_POWER 22
|
||||
#endif
|
||||
|
||||
#ifdef NICERF_F30_LF
|
||||
// Maximum output power of 32.0dBm with VCC = 5V and SX1262 at 22dBm
|
||||
#define TX_GAIN_LORA 10
|
||||
#define SX126X_MAX_POWER 22
|
||||
#endif
|
||||
|
||||
// Default system gain to 0 if not defined
|
||||
#ifndef TX_GAIN_LORA
|
||||
#define TX_GAIN_LORA 0
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -99,8 +135,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// -----------------------------------------------------------------------------
|
||||
// OLED & Input
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if defined(SEEED_WIO_TRACKER_L1)
|
||||
#define SSD1306_ADDRESS 0x3D
|
||||
#define USE_SH1106
|
||||
#else
|
||||
#define SSD1306_ADDRESS 0x3C
|
||||
#endif
|
||||
#define ST7567_ADDRESS 0x3F
|
||||
|
||||
// The SH1106 controller is almost, but not quite, the same as SSD1306
|
||||
@@ -149,10 +189,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define DFROBOT_RAIN_ADDR 0x1d
|
||||
#define NAU7802_ADDR 0x2A
|
||||
#define MAX30102_ADDR 0x57
|
||||
#define SCD4X_ADDR 0x62
|
||||
#define MLX90614_ADDR_DEF 0x5A
|
||||
#define CGRADSENS_ADDR 0x66
|
||||
#define LTR390UV_ADDR 0x53
|
||||
#define XPOWERS_AXP192_AXP2101_ADDRESS 0x34 // same adress as TCA8418
|
||||
#define PCT2075_ADDR 0x37
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// ACCELEROMETER
|
||||
@@ -166,6 +208,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define BMX160_ADDR 0x69
|
||||
#define ICM20948_ADDR 0x69
|
||||
#define ICM20948_ADDR_ALT 0x68
|
||||
#define BMM150_ADDR 0x13
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// LED
|
||||
@@ -188,6 +231,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// -----------------------------------------------------------------------------
|
||||
#define FT6336U_ADDR 0x48
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected)
|
||||
// - the default i2c address for this sensor is 0x20, and users are instructed to
|
||||
// set 0x21 and 0x22 for the second and third sensor if present.
|
||||
// -----------------------------------------------------------------------------
|
||||
#define RAK120351_ADDR 0x20
|
||||
#define RAK120352_ADDR 0x21
|
||||
#define RAK120353_ADDR 0x22
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// BIAS-T Generator
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -297,11 +349,41 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#error HW_VENDOR must be defined
|
||||
#endif
|
||||
|
||||
#ifndef TB_DOWN
|
||||
#define TB_DOWN 255
|
||||
#endif
|
||||
#ifndef TB_UP
|
||||
#define TB_UP 255
|
||||
#endif
|
||||
#ifndef TB_LEFT
|
||||
#define TB_LEFT 255
|
||||
#endif
|
||||
#ifndef TB_RIGHT
|
||||
#define TB_RIGHT 255
|
||||
#endif
|
||||
#ifndef TB_PRESS
|
||||
#define TB_PRESS 255
|
||||
#endif
|
||||
|
||||
// Support multiple RGB LED configuration
|
||||
#if defined(HAS_NCP5623) || defined(HAS_LP5562) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE)
|
||||
#define HAS_RGB_LED
|
||||
#endif
|
||||
|
||||
// default mapping of pins
|
||||
#if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN)
|
||||
#define ALT_BUTTON_PIN PIN_BUTTON2
|
||||
#endif
|
||||
#if defined ALT_BUTTON_PIN
|
||||
|
||||
#ifndef ALT_BUTTON_ACTIVE_LOW
|
||||
#define ALT_BUTTON_ACTIVE_LOW true
|
||||
#endif
|
||||
#ifndef ALT_BUTTON_ACTIVE_PULLUP
|
||||
#define ALT_BUTTON_ACTIVE_PULLUP true
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Global switches to turn off features for a minimized build
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -37,8 +37,14 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
|
||||
{
|
||||
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948, QMA6100P};
|
||||
return firstOfOrNONE(8, types);
|
||||
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948, QMA6100P, BMM150};
|
||||
return firstOfOrNONE(9, types);
|
||||
}
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstAQI() const
|
||||
{
|
||||
ScanI2C::DeviceType types[] = {PMSA0031, SCD4X};
|
||||
return firstOfOrNONE(2, types);
|
||||
}
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstRGBLED() const
|
||||
@@ -80,4 +86,4 @@ bool ScanI2C::DeviceAddress::operator<(const ScanI2C::DeviceAddress &other) cons
|
||||
|| (port != NO_I2C && other.port != NO_I2C && (address < other.address));
|
||||
}
|
||||
|
||||
ScanI2C::FoundDevice::FoundDevice(ScanI2C::DeviceType type, ScanI2C::DeviceAddress address) : type(type), address(address) {}
|
||||
ScanI2C::FoundDevice::FoundDevice(ScanI2C::DeviceType type, ScanI2C::DeviceAddress address) : type(type), address(address) {}
|
||||
|
||||
@@ -61,6 +61,7 @@ class ScanI2C
|
||||
FT6336U,
|
||||
STK8BAXX,
|
||||
ICM20948,
|
||||
SCD4X,
|
||||
MAX30102,
|
||||
TPS65233,
|
||||
MPR121KB,
|
||||
@@ -70,7 +71,10 @@ class ScanI2C
|
||||
DFROBOT_RAIN,
|
||||
DPS310,
|
||||
LTR390UV,
|
||||
RAK12035,
|
||||
TCA8418KB,
|
||||
PCT2075,
|
||||
BMM150,
|
||||
} DeviceType;
|
||||
|
||||
// typedef uint8_t DeviceAddress;
|
||||
@@ -123,6 +127,8 @@ class ScanI2C
|
||||
|
||||
FoundDevice firstAccelerometer() const;
|
||||
|
||||
FoundDevice firstAQI() const;
|
||||
|
||||
FoundDevice firstRGBLED() const;
|
||||
|
||||
virtual FoundDevice find(DeviceType) const;
|
||||
|
||||
@@ -358,7 +358,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT
|
||||
case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
|
||||
if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c) {
|
||||
if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c || registerValue == 0xc8d) {
|
||||
type = SHT4X;
|
||||
logFoundDevice("SHT4X", (uint8_t)addr.address);
|
||||
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
|
||||
@@ -423,9 +423,21 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
logFoundDevice("BMA423", (uint8_t)addr.address);
|
||||
}
|
||||
break;
|
||||
case TCA9535_ADDR:
|
||||
case RAK120352_ADDR:
|
||||
case RAK120353_ADDR:
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x02), 1);
|
||||
if (registerValue == addr.address) { // RAK12035 returns its I2C address at 0x02 (eg 0x20)
|
||||
type = RAK12035;
|
||||
logFoundDevice("RAK12035", (uint8_t)addr.address);
|
||||
} else {
|
||||
type = TCA9535;
|
||||
logFoundDevice("TCA9535", (uint8_t)addr.address);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(TCA9535_ADDR, TCA9535, "TCA9535", (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(TSL25911_ADDR, TSL2591, "TSL2591", (uint8_t)addr.address);
|
||||
@@ -434,6 +446,9 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(BMM150_ADDR, BMM150, "BMM150", (uint8_t)addr.address);
|
||||
#ifdef HAS_TPS65233
|
||||
SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233", (uint8_t)addr.address);
|
||||
#endif
|
||||
@@ -542,4 +557,4 @@ void ScanI2CTwoWire::logFoundDevice(const char *device, uint8_t address)
|
||||
{
|
||||
LOG_INFO("%s found at address 0x%x", device, address);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -40,7 +40,9 @@ template <typename T, std::size_t N> std::size_t array_count(const T (&)[N])
|
||||
}
|
||||
|
||||
#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
|
||||
#if defined(RAK2560)
|
||||
#if defined(SENSECAP_INDICATOR)
|
||||
FakeUART *GPS::_serial_gps = FakeSerial;
|
||||
#elif defined(RAK2560)
|
||||
HardwareSerial *GPS::_serial_gps = &Serial2;
|
||||
#else
|
||||
HardwareSerial *GPS::_serial_gps = &Serial1;
|
||||
@@ -1076,6 +1078,7 @@ void GPS::publishUpdate()
|
||||
|
||||
int32_t GPS::runOnce()
|
||||
{
|
||||
#if !defined(SENSECAP_INDICATOR)
|
||||
if (!GPSInitFinished) {
|
||||
if (!_serial_gps || config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
|
||||
LOG_INFO("GPS set to not-present. Skip probe");
|
||||
@@ -1091,6 +1094,7 @@ int32_t GPS::runOnce()
|
||||
GPSInitFinished = true;
|
||||
publishUpdate();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Repeaters have no need for GPS
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
|
||||
@@ -1250,11 +1254,10 @@ GnssModel_t GPS::probe(int serialSpeed)
|
||||
// Close all NMEA sentences, valid for MTK3333 and MTK3339 platforms
|
||||
_serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n");
|
||||
delay(20);
|
||||
std::vector<ChipInfo> mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B},
|
||||
{"PA1010D", "1010D", GNSS_MODEL_MTK_PA1010D},
|
||||
{"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S},
|
||||
{"LS20031", "MC-1513", GNSS_MODEL_MTK_L76B},
|
||||
{"L96", "Quectel-L96", GNSS_MODEL_MTK_L76B}};
|
||||
std::vector<ChipInfo> mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B}, {"PA1010D", "1010D", GNSS_MODEL_MTK_PA1010D},
|
||||
{"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S}, {"LS20031", "MC-1513", GNSS_MODEL_MTK_L76B},
|
||||
{"L96", "Quectel-L96", GNSS_MODEL_MTK_L76B}, {"L80-R", "_3337_", GNSS_MODEL_MTK_L76B},
|
||||
{"L80", "_3339_", GNSS_MODEL_MTK_L76B}};
|
||||
|
||||
PROBE_FAMILY("MTK Family", "$PMTK605*31", mtk, 500);
|
||||
|
||||
@@ -1410,8 +1413,10 @@ GPS *GPS::createGps()
|
||||
if (!settingsMap[has_gps])
|
||||
return nullptr;
|
||||
#endif
|
||||
#if !defined(SENSECAP_INDICATOR)
|
||||
if (!_rx_gpio || !_serial_gps) // Configured to have no GPS at all
|
||||
return nullptr;
|
||||
#endif
|
||||
|
||||
GPS *new_gps = new GPS;
|
||||
new_gps->rx_gpio = _rx_gpio;
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
#include "input/UpDownInterruptImpl1.h"
|
||||
#include "modules/PositionModule.h"
|
||||
|
||||
#ifdef SENSECAP_INDICATOR
|
||||
#include "mesh/comms/FakeUART.h"
|
||||
#endif
|
||||
|
||||
// Allow defining the polarity of the ENABLE output. default is active high
|
||||
#ifndef GPS_EN_ACTIVE
|
||||
#define GPS_EN_ACTIVE 1
|
||||
@@ -188,7 +192,9 @@ class GPS : private concurrency::OSThread
|
||||
CallbackObserver<GPS, void *> notifyDeepSleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareDeepSleep);
|
||||
|
||||
/** If !NULL we will use this serial port to construct our GPS */
|
||||
#if defined(ARCH_RP2040)
|
||||
#if defined(SENSECAP_INDICATOR)
|
||||
static FakeUART *_serial_gps;
|
||||
#elif defined(ARCH_RP2040)
|
||||
static SerialUART *_serial_gps;
|
||||
#else
|
||||
static HardwareSerial *_serial_gps;
|
||||
|
||||
@@ -244,11 +244,15 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t)
|
||||
*/
|
||||
int32_t getTZOffset()
|
||||
{
|
||||
#if MESHTASTIC_EXCLUDE_TZ
|
||||
return 0;
|
||||
#else
|
||||
time_t now = getTime(false);
|
||||
struct tm *gmt;
|
||||
gmt = gmtime(&now);
|
||||
gmt->tm_isdst = -1;
|
||||
return (int32_t)difftime(now, mktime(gmt));
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,26 @@
|
||||
#include "detect/ScanI2C.h"
|
||||
#include "mesh/generated/meshtastic/config.pb.h"
|
||||
#include <OLEDDisplay.h>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
|
||||
namespace graphics
|
||||
{
|
||||
enum notificationTypeEnum { none, text_banner, selection_picker, node_picker, number_picker };
|
||||
|
||||
struct BannerOverlayOptions {
|
||||
const char *message;
|
||||
uint32_t durationMs = 30000;
|
||||
const char **optionsArrayPtr = nullptr;
|
||||
const int *optionsEnumPtr = nullptr;
|
||||
uint8_t optionsCount = 0;
|
||||
std::function<void(int)> bannerCallback = nullptr;
|
||||
int8_t InitialSelected = 0;
|
||||
notificationTypeEnum notificationType = notificationTypeEnum::text_banner;
|
||||
};
|
||||
} // namespace graphics
|
||||
|
||||
#if !HAS_SCREEN
|
||||
#include "power.h"
|
||||
@@ -14,11 +34,20 @@ namespace graphics
|
||||
class Screen
|
||||
{
|
||||
public:
|
||||
enum FrameFocus : uint8_t {
|
||||
FOCUS_DEFAULT, // No specific frame
|
||||
FOCUS_PRESERVE, // Return to the previous frame
|
||||
FOCUS_FAULT,
|
||||
FOCUS_TEXTMESSAGE,
|
||||
FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus
|
||||
FOCUS_CLOCK,
|
||||
FOCUS_SYSTEM,
|
||||
};
|
||||
|
||||
explicit Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY);
|
||||
void onPress() {}
|
||||
void setup() {}
|
||||
void setOn(bool) {}
|
||||
void print(const char *) {}
|
||||
void doDeepSleep() {}
|
||||
void forceDisplay(bool forceUiUpdate = false) {}
|
||||
void startFirmwareUpdateScreen() {}
|
||||
@@ -27,6 +56,9 @@ class Screen
|
||||
void setFunctionSymbol(std::string) {}
|
||||
void removeFunctionSymbol(std::string) {}
|
||||
void startAlert(const char *) {}
|
||||
void showSimpleBanner(const char *message, uint32_t durationMs = 0) {}
|
||||
void showOverlayBanner(BannerOverlayOptions) {}
|
||||
void setFrames(FrameFocus focus) {}
|
||||
void endAlert() {}
|
||||
};
|
||||
} // namespace graphics
|
||||
@@ -62,8 +94,10 @@ class Screen
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "input/InputBroker.h"
|
||||
#include "mesh/MeshModule.h"
|
||||
#include "modules/AdminModule.h"
|
||||
#include "power.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// 0 to 255, though particular variants might define different defaults
|
||||
#ifndef BRIGHTNESS_DEFAULT
|
||||
@@ -90,7 +124,7 @@ class Screen
|
||||
|
||||
/// Convert an integer GPS coords to a floating point
|
||||
#define DegD(i) (i * 1e-7)
|
||||
|
||||
extern bool hasUnreadMessage;
|
||||
namespace
|
||||
{
|
||||
/// A basic 2D point class for drawing
|
||||
@@ -176,14 +210,31 @@ class Screen : public concurrency::OSThread
|
||||
CallbackObserver<Screen, const UIFrameEvent *>(this, &Screen::handleUIFrameEvent); // Sent by Mesh Modules
|
||||
CallbackObserver<Screen, const InputEvent *> inputObserver =
|
||||
CallbackObserver<Screen, const InputEvent *>(this, &Screen::handleInputEvent);
|
||||
CallbackObserver<Screen, const meshtastic_AdminMessage *> adminMessageObserver =
|
||||
CallbackObserver<Screen, const meshtastic_AdminMessage *>(this, &Screen::handleAdminMessage);
|
||||
CallbackObserver<Screen, AdminModule_ObserverData *> adminMessageObserver =
|
||||
CallbackObserver<Screen, AdminModule_ObserverData *>(this, &Screen::handleAdminMessage);
|
||||
|
||||
public:
|
||||
OLEDDisplay *getDisplayDevice() { return dispdev; }
|
||||
explicit Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY);
|
||||
|
||||
size_t frameCount = 0; // Total number of active frames
|
||||
~Screen();
|
||||
|
||||
// Which frame we want to be displayed, after we regen the frameset by calling setFrames
|
||||
enum FrameFocus : uint8_t {
|
||||
FOCUS_DEFAULT, // No specific frame
|
||||
FOCUS_PRESERVE, // Return to the previous frame
|
||||
FOCUS_FAULT,
|
||||
FOCUS_TEXTMESSAGE,
|
||||
FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus
|
||||
FOCUS_CLOCK,
|
||||
FOCUS_SYSTEM,
|
||||
};
|
||||
|
||||
// Regenerate the normal set of frames, focusing a specific frame if requested
|
||||
// Call when a frame should be added / removed, or custom frames should be cleared
|
||||
void setFrames(FrameFocus focus = FOCUS_DEFAULT);
|
||||
|
||||
std::vector<const uint8_t *> indicatorIcons; // Per-frame custom icon pointers
|
||||
Screen(const Screen &) = delete;
|
||||
Screen &operator=(const Screen &) = delete;
|
||||
|
||||
@@ -191,6 +242,12 @@ class Screen : public concurrency::OSThread
|
||||
meshtastic_Config_DisplayConfig_OledType model;
|
||||
OLEDDISPLAY_GEOMETRY geometry;
|
||||
|
||||
bool isOverlayBannerShowing();
|
||||
|
||||
// Stores the last 4 of our hardware ID, to make finding the device for pairing easier
|
||||
// FIXME: Needs refactoring and getMacAddr needs to be moved to a utility class
|
||||
char ourId[5];
|
||||
|
||||
/// Initializes the UI, turns on the display, starts showing boot screen.
|
||||
//
|
||||
// Not thread safe - must be called before any other methods are called.
|
||||
@@ -214,21 +271,9 @@ class Screen : public concurrency::OSThread
|
||||
|
||||
void blink();
|
||||
|
||||
void drawFrameText(OLEDDisplay *, OLEDDisplayUiState *, int16_t, int16_t, const char *);
|
||||
|
||||
void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength);
|
||||
|
||||
// Draw north
|
||||
void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading);
|
||||
|
||||
static uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight);
|
||||
|
||||
float estimatedHeading(double lat, double lon);
|
||||
|
||||
void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian);
|
||||
|
||||
void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields);
|
||||
|
||||
/// Handle button press, trackball or swipe action)
|
||||
void onPress() { enqueueCmd(ScreenCmd{.cmd = Cmd::ON_PRESS}); }
|
||||
void showPrevFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_PREV_FRAME}); }
|
||||
@@ -260,6 +305,12 @@ class Screen : public concurrency::OSThread
|
||||
enqueueCmd(cmd);
|
||||
}
|
||||
|
||||
void showSimpleBanner(const char *message, uint32_t durationMs = 0);
|
||||
void showOverlayBanner(BannerOverlayOptions);
|
||||
|
||||
void showNodePicker(const char *message, uint32_t durationMs, std::function<void(int)> bannerCallback);
|
||||
void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function<void(uint32_t)> bannerCallback);
|
||||
|
||||
void startFirmwareUpdateScreen()
|
||||
{
|
||||
ScreenCmd cmd;
|
||||
@@ -272,7 +323,7 @@ class Screen : public concurrency::OSThread
|
||||
void setHeading(long _heading)
|
||||
{
|
||||
hasCompass = true;
|
||||
compassHeading = _heading;
|
||||
compassHeading = fmod(_heading, 360);
|
||||
}
|
||||
|
||||
bool hasHeading() { return hasCompass; }
|
||||
@@ -292,23 +343,12 @@ class Screen : public concurrency::OSThread
|
||||
/// Stops showing the boot screen.
|
||||
void stopBootScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BOOT_SCREEN}); }
|
||||
|
||||
/// Writes a string to the screen.
|
||||
void print(const char *text)
|
||||
void runNow()
|
||||
{
|
||||
ScreenCmd cmd;
|
||||
cmd.cmd = Cmd::PRINT;
|
||||
// TODO(girts): strdup() here is scary, but we can't use std::string as
|
||||
// FreeRTOS queue is just dumbly copying memory contents. It would be
|
||||
// nice if we had a queue that could copy objects by value.
|
||||
cmd.print_text = strdup(text);
|
||||
if (!enqueueCmd(cmd)) {
|
||||
free(cmd.print_text);
|
||||
}
|
||||
setFastFramerate();
|
||||
enqueueCmd(ScreenCmd{.cmd = Cmd::NOOP});
|
||||
}
|
||||
|
||||
/// generates a very brief time delta display
|
||||
std::string drawTimeDelta(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t seconds);
|
||||
|
||||
/// Overrides the default utf8 character conversion, to replace empty space with question marks
|
||||
static char customFontTableLookup(const uint8_t ch)
|
||||
{
|
||||
@@ -533,7 +573,7 @@ class Screen : public concurrency::OSThread
|
||||
int handleTextMessage(const meshtastic_MeshPacket *arg);
|
||||
int handleUIFrameEvent(const UIFrameEvent *arg);
|
||||
int handleInputEvent(const InputEvent *arg);
|
||||
int handleAdminMessage(const meshtastic_AdminMessage *arg);
|
||||
int handleAdminMessage(AdminModule_ObserverData *arg);
|
||||
|
||||
/// Used to force (super slow) eink displays to draw critical frames
|
||||
void forceDisplay(bool forceUiUpdate = false);
|
||||
@@ -541,8 +581,6 @@ class Screen : public concurrency::OSThread
|
||||
/// Draws our SSL cert screen during boot (called from WebServer)
|
||||
void setSSLFrames();
|
||||
|
||||
void setWelcomeFrames();
|
||||
|
||||
// Dismiss the currently focussed frame, if possible (e.g. text message, waypoint)
|
||||
void dismissCurrentFrame();
|
||||
|
||||
@@ -591,7 +629,6 @@ class Screen : public concurrency::OSThread
|
||||
void handleOnPress();
|
||||
void handleShowNextFrame();
|
||||
void handleShowPrevFrame();
|
||||
void handlePrint(const char *text);
|
||||
void handleStartFirmwareUpdateScreen();
|
||||
|
||||
// Info collected by setFrames method.
|
||||
@@ -600,30 +637,37 @@ class Screen : public concurrency::OSThread
|
||||
// - Used to dismiss the currently shown frame (txt; waypoint) by CardKB combo
|
||||
struct FramesetInfo {
|
||||
struct FramePositions {
|
||||
uint8_t fault = 0;
|
||||
uint8_t textMessage = 0;
|
||||
uint8_t waypoint = 0;
|
||||
uint8_t focusedModule = 0;
|
||||
uint8_t log = 0;
|
||||
uint8_t settings = 0;
|
||||
uint8_t wifi = 0;
|
||||
uint8_t fault = 255;
|
||||
uint8_t waypoint = 255;
|
||||
uint8_t focusedModule = 255;
|
||||
uint8_t log = 255;
|
||||
uint8_t settings = 255;
|
||||
uint8_t wifi = 255;
|
||||
uint8_t deviceFocused = 255;
|
||||
uint8_t memory = 255;
|
||||
uint8_t gps = 255;
|
||||
uint8_t home = 255;
|
||||
uint8_t textMessage = 255;
|
||||
uint8_t nodelist = 255;
|
||||
uint8_t nodelist_lastheard = 255;
|
||||
uint8_t nodelist_hopsignal = 255;
|
||||
uint8_t nodelist_distance = 255;
|
||||
uint8_t nodelist_bearings = 255;
|
||||
uint8_t clock = 255;
|
||||
uint8_t firstFavorite = 255;
|
||||
uint8_t lastFavorite = 255;
|
||||
uint8_t lora = 255;
|
||||
} positions;
|
||||
|
||||
uint8_t frameCount = 0;
|
||||
} framesetInfo;
|
||||
|
||||
// Which frame we want to be displayed, after we regen the frameset by calling setFrames
|
||||
enum FrameFocus : uint8_t {
|
||||
FOCUS_DEFAULT, // No specific frame
|
||||
FOCUS_PRESERVE, // Return to the previous frame
|
||||
FOCUS_FAULT,
|
||||
FOCUS_TEXTMESSAGE,
|
||||
FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus
|
||||
};
|
||||
|
||||
// Regenerate the normal set of frames, focusing a specific frame if requested
|
||||
// Call when a frame should be added / removed, or custom frames should be cleared
|
||||
void setFrames(FrameFocus focus = FOCUS_DEFAULT);
|
||||
struct DismissedFrames {
|
||||
bool textMessage = false;
|
||||
bool waypoint = false;
|
||||
bool wifi = false;
|
||||
bool memory = false;
|
||||
} dismissedFrames;
|
||||
|
||||
/// Try to start drawing ASAP
|
||||
void setFastFramerate();
|
||||
@@ -631,34 +675,6 @@ class Screen : public concurrency::OSThread
|
||||
// Sets frame up for immediate drawing
|
||||
void setFrameImmediateDraw(FrameCallback *drawFrames);
|
||||
|
||||
/// Called when debug screen is to be drawn, calls through to debugInfo.drawFrame.
|
||||
static void drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
|
||||
static void drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
|
||||
static void drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
|
||||
#if defined(DISPLAY_CLOCK_FRAME)
|
||||
static void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
|
||||
static void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
|
||||
static void drawSegmentedDisplayCharacter(OLEDDisplay *display, int x, int y, uint8_t number, float scale = 1);
|
||||
|
||||
static void drawHorizontalSegment(OLEDDisplay *display, int x, int y, int width, int height);
|
||||
|
||||
static void drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int height);
|
||||
|
||||
static void drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale = 1);
|
||||
|
||||
static void drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode = true, float scale = 1);
|
||||
|
||||
static void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y);
|
||||
|
||||
// Whether we are showing the digital watch face or the analog one
|
||||
bool digitalWatchFace = true;
|
||||
#endif
|
||||
|
||||
/// callback for current alert frame
|
||||
FrameCallback alertFrame;
|
||||
|
||||
@@ -691,4 +707,9 @@ class Screen : public concurrency::OSThread
|
||||
|
||||
} // namespace graphics
|
||||
|
||||
// Extern declarations for function symbols used in UIRenderer
|
||||
extern std::vector<std::string> functionSymbol;
|
||||
extern std::string functionSymbolString;
|
||||
extern graphics::Screen *screen;
|
||||
|
||||
#endif
|
||||
@@ -65,8 +65,8 @@
|
||||
#endif
|
||||
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS)) || \
|
||||
defined(ILI9488_CS) && !defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS)) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
// The screen is bigger so use bigger fonts
|
||||
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
|
||||
#define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 28
|
||||
|
||||
6
src/graphics/ScreenGlobals.cpp
Normal file
6
src/graphics/ScreenGlobals.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Global variables for screen function overlay
|
||||
std::vector<std::string> functionSymbol;
|
||||
std::string functionSymbolString;
|
||||
394
src/graphics/SharedUIDisplay.cpp
Normal file
394
src/graphics/SharedUIDisplay.cpp
Normal file
@@ -0,0 +1,394 @@
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
#include "RTC.h"
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include "main.h"
|
||||
#include "meshtastic/config.pb.h"
|
||||
#include "power.h"
|
||||
#include <OLEDDisplay.h>
|
||||
#include <graphics/images.h>
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
|
||||
void determineResolution(int16_t screenheight, int16_t screenwidth)
|
||||
{
|
||||
if (screenwidth > 128) {
|
||||
isHighResolution = true;
|
||||
}
|
||||
|
||||
// Special case for Heltec Wireless Tracker v1.1
|
||||
if (screenwidth == 160 && screenheight == 80) {
|
||||
isHighResolution = false;
|
||||
}
|
||||
}
|
||||
|
||||
// === Shared External State ===
|
||||
bool hasUnreadMessage = false;
|
||||
bool isMuted = false;
|
||||
bool isHighResolution = false;
|
||||
|
||||
// === Internal State ===
|
||||
bool isBoltVisibleShared = true;
|
||||
uint32_t lastBlinkShared = 0;
|
||||
bool isMailIconVisible = true;
|
||||
uint32_t lastMailBlink = 0;
|
||||
|
||||
// *********************************
|
||||
// * Rounded Header when inverted *
|
||||
// *********************************
|
||||
void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w, int16_t h, int16_t r)
|
||||
{
|
||||
// Draw the center and side rectangles
|
||||
display->fillRect(x + r, y, w - 2 * r, h); // center bar
|
||||
display->fillRect(x, y + r, r, h - 2 * r); // left edge
|
||||
display->fillRect(x + w - r, y + r, r, h - 2 * r); // right edge
|
||||
|
||||
// Draw the rounded corners using filled circles
|
||||
display->fillCircle(x + r + 1, y + r, r); // top-left
|
||||
display->fillCircle(x + w - r - 1, y + r, r); // top-right
|
||||
display->fillCircle(x + r + 1, y + h - r - 1, r); // bottom-left
|
||||
display->fillCircle(x + w - r - 1, y + h - r - 1, r); // bottom-right
|
||||
}
|
||||
|
||||
// *************************
|
||||
// * Common Header Drawing *
|
||||
// *************************
|
||||
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool battery_only)
|
||||
{
|
||||
constexpr int HEADER_OFFSET_Y = 1;
|
||||
y += HEADER_OFFSET_Y;
|
||||
|
||||
display->setFont(FONT_SMALL);
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
const int xOffset = 4;
|
||||
const int highlightHeight = FONT_HEIGHT_SMALL - 1;
|
||||
const bool isInverted = (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED);
|
||||
const bool isBold = config.display.heading_bold;
|
||||
|
||||
const int screenW = display->getWidth();
|
||||
const int screenH = display->getHeight();
|
||||
|
||||
if (!battery_only) {
|
||||
// === Inverted Header Background ===
|
||||
if (isInverted) {
|
||||
display->setColor(BLACK);
|
||||
display->fillRect(0, 0, screenW, highlightHeight + 2);
|
||||
display->setColor(WHITE);
|
||||
drawRoundedHighlight(display, x, y, screenW, highlightHeight, 2);
|
||||
display->setColor(BLACK);
|
||||
} else {
|
||||
display->setColor(BLACK);
|
||||
display->fillRect(0, 0, screenW, highlightHeight + 2);
|
||||
display->setColor(WHITE);
|
||||
if (isHighResolution) {
|
||||
display->drawLine(0, 20, screenW, 20);
|
||||
} else {
|
||||
display->drawLine(0, 14, screenW, 14);
|
||||
}
|
||||
}
|
||||
|
||||
// === Screen Title ===
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->drawString(SCREEN_WIDTH / 2, y, titleStr);
|
||||
if (config.display.heading_bold) {
|
||||
display->drawString((SCREEN_WIDTH / 2) + 1, y, titleStr);
|
||||
}
|
||||
}
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
// === Battery State ===
|
||||
int chargePercent = powerStatus->getBatteryChargePercent();
|
||||
bool isCharging = powerStatus->getIsCharging();
|
||||
bool usbPowered = powerStatus->getHasUSB();
|
||||
|
||||
if (chargePercent >= 100) {
|
||||
isCharging = false;
|
||||
}
|
||||
if (chargePercent == 101) {
|
||||
usbPowered = true; // Forcing this flag on for the express purpose that some devices have no concept of having a USB cable
|
||||
// plugged in
|
||||
}
|
||||
|
||||
uint32_t now = millis();
|
||||
|
||||
#ifndef USE_EINK
|
||||
if (isCharging && now - lastBlinkShared > 500) {
|
||||
isBoltVisibleShared = !isBoltVisibleShared;
|
||||
lastBlinkShared = now;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool useHorizontalBattery = (isHighResolution && screenW >= screenH);
|
||||
const int textY = y + (highlightHeight - FONT_HEIGHT_SMALL) / 2;
|
||||
|
||||
int batteryX = 1;
|
||||
int batteryY = HEADER_OFFSET_Y + 1;
|
||||
|
||||
// === Battery Icons ===
|
||||
if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging
|
||||
batteryX += 1;
|
||||
batteryY += 2;
|
||||
if (isHighResolution) {
|
||||
display->drawXbm(batteryX, batteryY, 19, 12, imgUSB_HighResolution);
|
||||
batteryX += 20; // Icon + 1 pixel
|
||||
} else {
|
||||
display->drawXbm(batteryX, batteryY, 10, 8, imgUSB);
|
||||
batteryX += 11; // Icon + 1 pixel
|
||||
}
|
||||
} else {
|
||||
if (useHorizontalBattery) {
|
||||
batteryX += 1;
|
||||
batteryY += 2;
|
||||
display->drawXbm(batteryX, batteryY, 9, 13, batteryBitmap_h_bottom);
|
||||
display->drawXbm(batteryX + 9, batteryY, 9, 13, batteryBitmap_h_top);
|
||||
if (isCharging && isBoltVisibleShared)
|
||||
display->drawXbm(batteryX + 4, batteryY, 9, 13, lightning_bolt_h);
|
||||
else {
|
||||
display->drawLine(batteryX + 5, batteryY, batteryX + 10, batteryY);
|
||||
display->drawLine(batteryX + 5, batteryY + 12, batteryX + 10, batteryY + 12);
|
||||
int fillWidth = 14 * chargePercent / 100;
|
||||
display->fillRect(batteryX + 1, batteryY + 1, fillWidth, 11);
|
||||
}
|
||||
batteryX += 18; // Icon + 2 pixels
|
||||
} else {
|
||||
#ifdef USE_EINK
|
||||
batteryY += 2;
|
||||
#endif
|
||||
display->drawXbm(batteryX, batteryY, 7, 11, batteryBitmap_v);
|
||||
if (isCharging && isBoltVisibleShared)
|
||||
display->drawXbm(batteryX + 1, batteryY + 3, 5, 5, lightning_bolt_v);
|
||||
else {
|
||||
display->drawXbm(batteryX - 1, batteryY + 4, 8, 3, batteryBitmap_sidegaps_v);
|
||||
int fillHeight = 8 * chargePercent / 100;
|
||||
int fillY = batteryY - fillHeight;
|
||||
display->fillRect(batteryX + 1, fillY + 10, 5, fillHeight);
|
||||
}
|
||||
batteryX += 9; // Icon + 2 pixels
|
||||
}
|
||||
}
|
||||
|
||||
if (chargePercent != 101) {
|
||||
// === Battery % Display ===
|
||||
char chargeStr[4];
|
||||
snprintf(chargeStr, sizeof(chargeStr), "%d", chargePercent);
|
||||
int chargeNumWidth = display->getStringWidth(chargeStr);
|
||||
display->drawString(batteryX, textY, chargeStr);
|
||||
display->drawString(batteryX + chargeNumWidth - 1, textY, "%");
|
||||
if (isBold) {
|
||||
display->drawString(batteryX + 1, textY, chargeStr);
|
||||
display->drawString(batteryX + chargeNumWidth, textY, "%");
|
||||
}
|
||||
}
|
||||
|
||||
// === Time and Right-aligned Icons ===
|
||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true);
|
||||
char timeStr[10] = "--:--"; // Fallback display
|
||||
int timeStrWidth = display->getStringWidth("12:34"); // Default alignment
|
||||
int timeX = screenW - xOffset - timeStrWidth + 4;
|
||||
|
||||
if (rtc_sec > 0 && !battery_only) {
|
||||
// === Build Time String ===
|
||||
long hms = (rtc_sec % SEC_PER_DAY + SEC_PER_DAY) % SEC_PER_DAY;
|
||||
int hour = hms / SEC_PER_HOUR;
|
||||
int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
||||
snprintf(timeStr, sizeof(timeStr), "%d:%02d", hour, minute);
|
||||
|
||||
if (config.display.use_12h_clock) {
|
||||
bool isPM = hour >= 12;
|
||||
hour %= 12;
|
||||
if (hour == 0)
|
||||
hour = 12;
|
||||
snprintf(timeStr, sizeof(timeStr), "%d:%02d%s", hour, minute, isPM ? "p" : "a");
|
||||
}
|
||||
|
||||
timeStrWidth = display->getStringWidth(timeStr);
|
||||
timeX = screenW - xOffset - timeStrWidth + 3;
|
||||
|
||||
// === Show Mail or Mute Icon to the Left of Time ===
|
||||
int iconRightEdge = timeX - 1;
|
||||
|
||||
bool showMail = false;
|
||||
|
||||
#ifndef USE_EINK
|
||||
if (hasUnreadMessage) {
|
||||
if (now - lastMailBlink > 500) {
|
||||
isMailIconVisible = !isMailIconVisible;
|
||||
lastMailBlink = now;
|
||||
}
|
||||
showMail = isMailIconVisible;
|
||||
}
|
||||
#else
|
||||
if (hasUnreadMessage) {
|
||||
showMail = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (showMail) {
|
||||
if (useHorizontalBattery) {
|
||||
int iconW = 16, iconH = 12;
|
||||
int iconX = iconRightEdge - iconW;
|
||||
int iconY = textY + (FONT_HEIGHT_SMALL - iconH) / 2 - 1;
|
||||
if (isInverted) {
|
||||
display->setColor(WHITE);
|
||||
display->fillRect(iconX - 1, iconY - 1, iconW + 3, iconH + 2);
|
||||
display->setColor(BLACK);
|
||||
} else {
|
||||
display->setColor(BLACK);
|
||||
display->fillRect(iconX - 1, iconY - 1, iconW + 3, iconH + 2);
|
||||
display->setColor(WHITE);
|
||||
}
|
||||
display->drawRect(iconX, iconY, iconW + 1, iconH);
|
||||
display->drawLine(iconX, iconY, iconX + iconW / 2, iconY + iconH - 4);
|
||||
display->drawLine(iconX + iconW, iconY, iconX + iconW / 2, iconY + iconH - 4);
|
||||
} else {
|
||||
int iconX = iconRightEdge - (mail_width - 2);
|
||||
int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2;
|
||||
if (isInverted) {
|
||||
display->setColor(WHITE);
|
||||
display->fillRect(iconX - 1, iconY - 1, mail_width + 2, mail_height + 2);
|
||||
display->setColor(BLACK);
|
||||
} else {
|
||||
display->setColor(BLACK);
|
||||
display->fillRect(iconX - 1, iconY - 1, mail_width + 2, mail_height + 2);
|
||||
display->setColor(WHITE);
|
||||
}
|
||||
display->drawXbm(iconX, iconY, mail_width, mail_height, mail);
|
||||
}
|
||||
} else if (isMuted) {
|
||||
if (isHighResolution) {
|
||||
int iconX = iconRightEdge - mute_symbol_big_width;
|
||||
int iconY = textY + (FONT_HEIGHT_SMALL - mute_symbol_big_height) / 2;
|
||||
|
||||
if (isInverted) {
|
||||
display->setColor(WHITE);
|
||||
display->fillRect(iconX - 1, iconY - 1, mute_symbol_big_width + 2, mute_symbol_big_height + 2);
|
||||
display->setColor(BLACK);
|
||||
} else {
|
||||
display->setColor(BLACK);
|
||||
display->fillRect(iconX - 1, iconY - 1, mute_symbol_big_width + 2, mute_symbol_big_height + 2);
|
||||
display->setColor(WHITE);
|
||||
}
|
||||
display->drawXbm(iconX, iconY, mute_symbol_big_width, mute_symbol_big_height, mute_symbol_big);
|
||||
} else {
|
||||
int iconX = iconRightEdge - mute_symbol_width;
|
||||
int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2;
|
||||
|
||||
if (isInverted) {
|
||||
display->setColor(WHITE);
|
||||
display->fillRect(iconX - 1, iconY - 1, mute_symbol_width + 2, mute_symbol_height + 2);
|
||||
display->setColor(BLACK);
|
||||
} else {
|
||||
display->setColor(BLACK);
|
||||
display->fillRect(iconX - 1, iconY - 1, mute_symbol_width + 2, mute_symbol_height + 2);
|
||||
display->setColor(WHITE);
|
||||
}
|
||||
display->drawXbm(iconX, iconY, mute_symbol_width, mute_symbol_height, mute_symbol);
|
||||
}
|
||||
}
|
||||
|
||||
// === Draw Time ===
|
||||
display->drawString(timeX, textY, timeStr);
|
||||
if (isBold)
|
||||
display->drawString(timeX - 1, textY, timeStr);
|
||||
|
||||
} else {
|
||||
// === No Time Available: Mail/Mute Icon Moves to Far Right ===
|
||||
int iconRightEdge = screenW - xOffset;
|
||||
|
||||
bool showMail = false;
|
||||
|
||||
#ifndef USE_EINK
|
||||
if (hasUnreadMessage) {
|
||||
if (now - lastMailBlink > 500) {
|
||||
isMailIconVisible = !isMailIconVisible;
|
||||
lastMailBlink = now;
|
||||
}
|
||||
showMail = isMailIconVisible;
|
||||
}
|
||||
#else
|
||||
if (hasUnreadMessage) {
|
||||
showMail = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (showMail) {
|
||||
if (useHorizontalBattery) {
|
||||
int iconW = 16, iconH = 12;
|
||||
int iconX = iconRightEdge - iconW;
|
||||
int iconY = textY + (FONT_HEIGHT_SMALL - iconH) / 2 - 1;
|
||||
display->drawRect(iconX, iconY, iconW + 1, iconH);
|
||||
display->drawLine(iconX, iconY, iconX + iconW / 2, iconY + iconH - 4);
|
||||
display->drawLine(iconX + iconW, iconY, iconX + iconW / 2, iconY + iconH - 4);
|
||||
} else {
|
||||
int iconX = iconRightEdge - mail_width;
|
||||
int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2;
|
||||
display->drawXbm(iconX, iconY, mail_width, mail_height, mail);
|
||||
}
|
||||
} else if (isMuted) {
|
||||
if (isHighResolution) {
|
||||
int iconX = iconRightEdge - mute_symbol_big_width;
|
||||
int iconY = textY + (FONT_HEIGHT_SMALL - mute_symbol_big_height) / 2;
|
||||
display->drawXbm(iconX, iconY, mute_symbol_big_width, mute_symbol_big_height, mute_symbol_big);
|
||||
} else {
|
||||
int iconX = iconRightEdge - mute_symbol_width;
|
||||
int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2;
|
||||
display->drawXbm(iconX, iconY, mute_symbol_width, mute_symbol_height, mute_symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
display->setColor(WHITE); // Reset for other UI
|
||||
}
|
||||
|
||||
const int *getTextPositions(OLEDDisplay *display)
|
||||
{
|
||||
static int textPositions[7]; // Static array that persists beyond function scope
|
||||
|
||||
if (isHighResolution) {
|
||||
textPositions[0] = textZeroLine;
|
||||
textPositions[1] = textFirstLine_medium;
|
||||
textPositions[2] = textSecondLine_medium;
|
||||
textPositions[3] = textThirdLine_medium;
|
||||
textPositions[4] = textFourthLine_medium;
|
||||
textPositions[5] = textFifthLine_medium;
|
||||
textPositions[6] = textSixthLine_medium;
|
||||
} else {
|
||||
textPositions[0] = textZeroLine;
|
||||
textPositions[1] = textFirstLine;
|
||||
textPositions[2] = textSecondLine;
|
||||
textPositions[3] = textThirdLine;
|
||||
textPositions[4] = textFourthLine;
|
||||
textPositions[5] = textFifthLine;
|
||||
textPositions[6] = textSixthLine;
|
||||
}
|
||||
return textPositions;
|
||||
}
|
||||
|
||||
bool isAllowedPunctuation(char c)
|
||||
{
|
||||
const std::string allowed = ".,!?;:-_()[]{}'\"@#$/\\&+=%~^ ";
|
||||
return allowed.find(c) != std::string::npos;
|
||||
}
|
||||
|
||||
std::string sanitizeString(const std::string &input)
|
||||
{
|
||||
std::string output;
|
||||
bool inReplacement = false;
|
||||
|
||||
for (char c : input) {
|
||||
if (std::isalnum(static_cast<unsigned char>(c)) || isAllowedPunctuation(c)) {
|
||||
output += c;
|
||||
inReplacement = false;
|
||||
} else {
|
||||
if (!inReplacement) {
|
||||
output += 0xbf; // ISO-8859-1 for inverted question mark
|
||||
inReplacement = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
} // namespace graphics
|
||||
60
src/graphics/SharedUIDisplay.h
Normal file
60
src/graphics/SharedUIDisplay.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <OLEDDisplay.h>
|
||||
#include <string>
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
|
||||
// =======================
|
||||
// Shared UI Helpers
|
||||
// =======================
|
||||
|
||||
#define textZeroLine 0
|
||||
// Consistent Line Spacing - this is standard for all display and the fall-back spacing
|
||||
#define textFirstLine (FONT_HEIGHT_SMALL - 1)
|
||||
#define textSecondLine (textFirstLine + (FONT_HEIGHT_SMALL - 5))
|
||||
#define textThirdLine (textSecondLine + (FONT_HEIGHT_SMALL - 5))
|
||||
#define textFourthLine (textThirdLine + (FONT_HEIGHT_SMALL - 5))
|
||||
#define textFifthLine (textFourthLine + (FONT_HEIGHT_SMALL - 5))
|
||||
#define textSixthLine (textFifthLine + (FONT_HEIGHT_SMALL - 5))
|
||||
|
||||
// Consistent Line Spacing for devices like T114 and TEcho/ThinkNode M1 of devices
|
||||
#define textFirstLine_medium (FONT_HEIGHT_SMALL + 1)
|
||||
#define textSecondLine_medium (textFirstLine_medium + FONT_HEIGHT_SMALL)
|
||||
#define textThirdLine_medium (textSecondLine_medium + FONT_HEIGHT_SMALL)
|
||||
#define textFourthLine_medium (textThirdLine_medium + FONT_HEIGHT_SMALL)
|
||||
#define textFifthLine_medium (textFourthLine_medium + FONT_HEIGHT_SMALL)
|
||||
#define textSixthLine_medium (textFifthLine_medium + FONT_HEIGHT_SMALL)
|
||||
|
||||
// Consistent Line Spacing for devices like VisionMaster T190
|
||||
#define textFirstLine_large (FONT_HEIGHT_SMALL + 1)
|
||||
#define textSecondLine_large (textFirstLine_large + (FONT_HEIGHT_SMALL + 5))
|
||||
#define textThirdLine_large (textSecondLine_large + (FONT_HEIGHT_SMALL + 5))
|
||||
#define textFourthLine_large (textThirdLine_large + (FONT_HEIGHT_SMALL + 5))
|
||||
#define textFifthLine_large (textFourthLine_large + (FONT_HEIGHT_SMALL + 5))
|
||||
#define textSixthLine_large (textFifthLine_large + (FONT_HEIGHT_SMALL + 5))
|
||||
|
||||
// Quick screen access
|
||||
#define SCREEN_WIDTH display->getWidth()
|
||||
#define SCREEN_HEIGHT display->getHeight()
|
||||
|
||||
// Shared state (declare inside namespace)
|
||||
extern bool hasUnreadMessage;
|
||||
extern bool isMuted;
|
||||
extern bool isHighResolution;
|
||||
void determineResolution(int16_t screenheight, int16_t screenwidth);
|
||||
|
||||
// Rounded highlight (used for inverted headers)
|
||||
void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w, int16_t h, int16_t r);
|
||||
|
||||
// Shared battery/time/mail header
|
||||
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool battery_only = false);
|
||||
|
||||
const int *getTextPositions(OLEDDisplay *display);
|
||||
|
||||
bool isAllowedPunctuation(char c);
|
||||
|
||||
std::string sanitizeString(const std::string &input);
|
||||
|
||||
} // namespace graphics
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
#include "platform/portduino/PortduinoGlue.h"
|
||||
#endif
|
||||
@@ -14,8 +15,10 @@
|
||||
extern SX1509 gpioExtender;
|
||||
#endif
|
||||
|
||||
#ifndef TFT_MESH
|
||||
#define TFT_MESH COLOR565(0x67, 0xEA, 0x94)
|
||||
#ifdef TFT_MESH_OVERRIDE
|
||||
uint16_t TFT_MESH = TFT_MESH_OVERRIDE;
|
||||
#else
|
||||
uint16_t TFT_MESH = COLOR565(0x67, 0xEA, 0x94);
|
||||
#endif
|
||||
|
||||
#if defined(ST7735S)
|
||||
@@ -467,18 +470,27 @@ class LGFX : public lgfx::LGFX_Device
|
||||
|
||||
// The following setting values are general initial values for each panel, so please comment out any
|
||||
// unknown items and try them.
|
||||
|
||||
cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC
|
||||
cfg.memory_height = TFT_HEIGHT; // Maximum height supported by the driver IC
|
||||
cfg.panel_width = TFT_WIDTH; // actual displayable width
|
||||
cfg.panel_height = TFT_HEIGHT; // actual displayable height
|
||||
cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction
|
||||
cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction
|
||||
cfg.offset_rotation = TFT_OFFSET_ROTATION; // Rotation direction value offset 0~7 (4~7 is mirrored)
|
||||
#if defined(T_WATCH_S3)
|
||||
cfg.panel_width = 240;
|
||||
cfg.panel_height = 240;
|
||||
cfg.memory_width = 240;
|
||||
cfg.memory_height = 320;
|
||||
cfg.offset_x = 0;
|
||||
cfg.offset_y = 0; // No vertical shift needed — panel is top-aligned
|
||||
cfg.offset_rotation = 2; // Rotate 180° to correct upside-down layout
|
||||
#else
|
||||
cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC
|
||||
cfg.memory_height = TFT_HEIGHT; // Maximum height supported by the driver IC
|
||||
cfg.panel_width = TFT_WIDTH; // actual displayable width
|
||||
cfg.panel_height = TFT_HEIGHT; // actual displayable height
|
||||
cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction
|
||||
cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction
|
||||
cfg.offset_rotation = TFT_OFFSET_ROTATION; // Rotation direction value offset 0~7 (4~7 is mirrored)
|
||||
#endif
|
||||
#ifdef TFT_DUMMY_READ_PIXELS
|
||||
cfg.dummy_read_pixel = TFT_DUMMY_READ_PIXELS; // Number of bits for dummy read before pixel readout
|
||||
#else
|
||||
cfg.dummy_read_pixel = 9; // Number of bits for dummy read before pixel readout
|
||||
cfg.dummy_read_pixel = 9; // Number of bits for dummy read before pixel readout
|
||||
#endif
|
||||
cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read
|
||||
cfg.readable = true; // Set to true if data can be read
|
||||
@@ -653,7 +665,7 @@ static LGFX *tft = nullptr;
|
||||
#include <TFT_eSPI.h> // Graphics and font library for ILI9342 driver chip
|
||||
|
||||
static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
|
||||
#elif ARCH_PORTDUINO && HAS_SCREEN != 0 && !HAS_TFT
|
||||
#elif ARCH_PORTDUINO
|
||||
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
|
||||
|
||||
class LGFX : public lgfx::LGFX_Device
|
||||
@@ -697,11 +709,16 @@ class LGFX : public lgfx::LGFX_Device
|
||||
_panel_instance->setBus(&_bus_instance); // set the bus on the panel.
|
||||
|
||||
auto cfg = _panel_instance->config(); // Gets a structure for display panel settings.
|
||||
LOG_DEBUG("Height: %d, Width: %d ", settingsMap[displayHeight], settingsMap[displayWidth]);
|
||||
LOG_DEBUG("Width: %d, Height: %d", settingsMap[displayWidth], settingsMap[displayHeight]);
|
||||
cfg.pin_cs = settingsMap[displayCS]; // Pin number where CS is connected (-1 = disable)
|
||||
cfg.pin_rst = settingsMap[displayReset];
|
||||
cfg.panel_width = settingsMap[displayWidth]; // actual displayable width
|
||||
cfg.panel_height = settingsMap[displayHeight]; // actual displayable height
|
||||
if (settingsMap[displayRotate]) {
|
||||
cfg.panel_width = settingsMap[displayHeight]; // actual displayable width
|
||||
cfg.panel_height = settingsMap[displayWidth]; // actual displayable height
|
||||
} else {
|
||||
cfg.panel_width = settingsMap[displayWidth]; // actual displayable width
|
||||
cfg.panel_height = settingsMap[displayHeight]; // actual displayable height
|
||||
}
|
||||
cfg.offset_x = settingsMap[displayOffsetX]; // Panel offset amount in X direction
|
||||
cfg.offset_y = settingsMap[displayOffsetY]; // Panel offset amount in Y direction
|
||||
cfg.offset_rotation = settingsMap[displayOffsetRotate]; // Rotation direction value offset 0~7 (4~7 is mirrored)
|
||||
@@ -978,9 +995,9 @@ TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY g
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
if (settingsMap[displayRotate]) {
|
||||
setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayHeight], settingsMap[configNames::displayWidth]);
|
||||
} else {
|
||||
setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayWidth], settingsMap[configNames::displayHeight]);
|
||||
} else {
|
||||
setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayHeight], settingsMap[configNames::displayWidth]);
|
||||
}
|
||||
|
||||
#elif defined(SCREEN_ROTATE)
|
||||
@@ -1169,6 +1186,8 @@ bool TFTDisplay::connect()
|
||||
tft->setRotation(1); // T-Deck has the TFT in landscape
|
||||
#elif defined(T_WATCH_S3) || defined(SENSECAP_INDICATOR)
|
||||
tft->setRotation(2); // T-Watch S3 left-handed orientation
|
||||
#elif ARCH_PORTDUINO
|
||||
tft->setRotation(0); // use config.yaml to set rotation
|
||||
#else
|
||||
tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
|
||||
#endif
|
||||
|
||||
103
src/graphics/TimeFormatters.cpp
Normal file
103
src/graphics/TimeFormatters.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#include "TimeFormatters.h"
|
||||
#include "configuration.h"
|
||||
#include "gps/RTC.h"
|
||||
#include "mesh/NodeDB.h"
|
||||
#include <cstring>
|
||||
|
||||
bool deltaToTimestamp(uint32_t secondsAgo, uint8_t *hours, uint8_t *minutes, int32_t *daysAgo)
|
||||
{
|
||||
// Cache the result - avoid frequent recalculation
|
||||
static uint8_t hoursCached = 0, minutesCached = 0;
|
||||
static uint32_t daysAgoCached = 0;
|
||||
static uint32_t secondsAgoCached = 0;
|
||||
static bool validCached = false;
|
||||
|
||||
// Abort: if timezone not set
|
||||
if (strlen(config.device.tzdef) == 0) {
|
||||
validCached = false;
|
||||
return validCached;
|
||||
}
|
||||
|
||||
// Abort: if invalid pointers passed
|
||||
if (hours == nullptr || minutes == nullptr || daysAgo == nullptr) {
|
||||
validCached = false;
|
||||
return validCached;
|
||||
}
|
||||
|
||||
// Abort: if time seems invalid.. (> 6 months ago, probably seen before RTC set)
|
||||
if (secondsAgo > SEC_PER_DAY * 30UL * 6) {
|
||||
validCached = false;
|
||||
return validCached;
|
||||
}
|
||||
|
||||
// If repeated request, don't bother recalculating
|
||||
if (secondsAgo - secondsAgoCached < 60 && secondsAgoCached != 0) {
|
||||
if (validCached) {
|
||||
*hours = hoursCached;
|
||||
*minutes = minutesCached;
|
||||
*daysAgo = daysAgoCached;
|
||||
}
|
||||
return validCached;
|
||||
}
|
||||
|
||||
// Get local time
|
||||
uint32_t secondsRTC = getValidTime(RTCQuality::RTCQualityDevice, true); // Get local time
|
||||
|
||||
// Abort: if RTC not set
|
||||
if (!secondsRTC) {
|
||||
validCached = false;
|
||||
return validCached;
|
||||
}
|
||||
|
||||
// Get absolute time when last seen
|
||||
uint32_t secondsSeenAt = secondsRTC - secondsAgo;
|
||||
|
||||
// Calculate daysAgo
|
||||
*daysAgo = (secondsRTC / SEC_PER_DAY) - (secondsSeenAt / SEC_PER_DAY); // How many "midnights" have passed
|
||||
|
||||
// Get seconds since midnight
|
||||
uint32_t hms = (secondsRTC - secondsAgo) % SEC_PER_DAY;
|
||||
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
|
||||
|
||||
// Tear apart hms into hours and minutes
|
||||
*hours = hms / SEC_PER_HOUR;
|
||||
*minutes = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
||||
|
||||
// Cache the result
|
||||
daysAgoCached = *daysAgo;
|
||||
hoursCached = *hours;
|
||||
minutesCached = *minutes;
|
||||
secondsAgoCached = secondsAgo;
|
||||
|
||||
validCached = true;
|
||||
return validCached;
|
||||
}
|
||||
|
||||
void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength)
|
||||
{
|
||||
// Use an absolute timestamp in some cases.
|
||||
// Particularly useful with E-Ink displays. Static UI, fewer refreshes.
|
||||
uint8_t timestampHours, timestampMinutes;
|
||||
int32_t daysAgo;
|
||||
bool useTimestamp = deltaToTimestamp(agoSecs, ×tampHours, ×tampMinutes, &daysAgo);
|
||||
|
||||
if (agoSecs < 120) // last 2 mins?
|
||||
snprintf(timeStr, maxLength, "%u seconds ago", agoSecs);
|
||||
// -- if suitable for timestamp --
|
||||
else if (useTimestamp && agoSecs < 15 * SECONDS_IN_MINUTE) // Last 15 minutes
|
||||
snprintf(timeStr, maxLength, "%u minutes ago", agoSecs / SECONDS_IN_MINUTE);
|
||||
else if (useTimestamp && daysAgo == 0) // Today
|
||||
snprintf(timeStr, maxLength, "Last seen: %02u:%02u", (unsigned int)timestampHours, (unsigned int)timestampMinutes);
|
||||
else if (useTimestamp && daysAgo == 1) // Yesterday
|
||||
snprintf(timeStr, maxLength, "Seen yesterday");
|
||||
else if (useTimestamp && daysAgo > 1) // Last six months (capped by deltaToTimestamp method)
|
||||
snprintf(timeStr, maxLength, "%li days ago", (long)daysAgo);
|
||||
// -- if using time delta instead --
|
||||
else if (agoSecs < 120 * 60) // last 2 hrs
|
||||
snprintf(timeStr, maxLength, "%u minutes ago", agoSecs / 60);
|
||||
// Only show hours ago if it's been less than 6 months. Otherwise, we may have bad data.
|
||||
else if ((agoSecs / 60 / 60) < (730 * 6))
|
||||
snprintf(timeStr, maxLength, "%u hours ago", agoSecs / 60 / 60);
|
||||
else
|
||||
snprintf(timeStr, maxLength, "unknown age");
|
||||
}
|
||||
26
src/graphics/TimeFormatters.h
Normal file
26
src/graphics/TimeFormatters.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "configuration.h"
|
||||
#include "gps/RTC.h"
|
||||
#include <airtime.h>
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* Convert a delta in seconds ago to timestamp information (hours, minutes, days ago).
|
||||
*
|
||||
* @param secondsAgo Number of seconds ago to convert
|
||||
* @param hours Pointer to store the hours (0-23)
|
||||
* @param minutes Pointer to store the minutes (0-59)
|
||||
* @param daysAgo Pointer to store the number of days ago
|
||||
* @return true if conversion was successful, false if invalid input or time not available
|
||||
*/
|
||||
bool deltaToTimestamp(uint32_t secondsAgo, uint8_t *hours, uint8_t *minutes, int32_t *daysAgo);
|
||||
|
||||
/**
|
||||
* Get a human-readable string representing the time ago in a format like "2 days, 3 hours, 15 minutes".
|
||||
*
|
||||
* @param agoSecs Number of seconds ago to convert
|
||||
* @param timeStr Pointer to store the resulting string
|
||||
* @param maxLength Maximum length of the resulting string buffer
|
||||
*/
|
||||
void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength);
|
||||
516
src/graphics/draw/ClockRenderer.cpp
Normal file
516
src/graphics/draw/ClockRenderer.cpp
Normal file
@@ -0,0 +1,516 @@
|
||||
#include "configuration.h"
|
||||
#if HAS_SCREEN
|
||||
#include "ClockRenderer.h"
|
||||
#include "NodeDB.h"
|
||||
#include "UIRenderer.h"
|
||||
#include "configuration.h"
|
||||
#include "gps/GeoCoord.h"
|
||||
#include "gps/RTC.h"
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
#include "graphics/emotes.h"
|
||||
#include "graphics/images.h"
|
||||
#include "main.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_BLUETOOTH
|
||||
#include "nimble/NimbleBluetooth.h"
|
||||
#endif
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
|
||||
namespace ClockRenderer
|
||||
{
|
||||
|
||||
void drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale)
|
||||
{
|
||||
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
|
||||
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
|
||||
|
||||
uint16_t cellHeight = (segmentWidth * 2) + (segmentHeight * 3) + 8;
|
||||
|
||||
uint16_t topAndBottomX = x + (4 * scale);
|
||||
|
||||
uint16_t quarterCellHeight = cellHeight / 4;
|
||||
|
||||
uint16_t topY = y + quarterCellHeight;
|
||||
uint16_t bottomY = y + (quarterCellHeight * 3);
|
||||
|
||||
display->fillRect(topAndBottomX, topY, segmentHeight, segmentHeight);
|
||||
display->fillRect(topAndBottomX, bottomY, segmentHeight, segmentHeight);
|
||||
}
|
||||
|
||||
void drawSegmentedDisplayCharacter(OLEDDisplay *display, int x, int y, uint8_t number, float scale)
|
||||
{
|
||||
// the numbers 0-9, each expressed as an array of seven boolean (0|1) values encoding the on/off state of
|
||||
// segment {innerIndex + 1}
|
||||
// e.g., to display the numeral '0', segments 1-6 are on, and segment 7 is off.
|
||||
uint8_t numbers[10][7] = {
|
||||
{1, 1, 1, 1, 1, 1, 0}, // 0 Display segment key
|
||||
{0, 1, 1, 0, 0, 0, 0}, // 1 1
|
||||
{1, 1, 0, 1, 1, 0, 1}, // 2 ___
|
||||
{1, 1, 1, 1, 0, 0, 1}, // 3 6 | | 2
|
||||
{0, 1, 1, 0, 0, 1, 1}, // 4 |_7̲_|
|
||||
{1, 0, 1, 1, 0, 1, 1}, // 5 5 | | 3
|
||||
{1, 0, 1, 1, 1, 1, 1}, // 6 |___|
|
||||
{1, 1, 1, 0, 0, 1, 0}, // 7
|
||||
{1, 1, 1, 1, 1, 1, 1}, // 8 4
|
||||
{1, 1, 1, 1, 0, 1, 1}, // 9
|
||||
};
|
||||
|
||||
// the width and height of each segment's central rectangle:
|
||||
// _____________________
|
||||
// ⋰| (only this part, |⋱
|
||||
// ⋰ | not including | ⋱
|
||||
// ⋱ | the triangles | ⋰
|
||||
// ⋱| on the ends) |⋰
|
||||
// ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
||||
|
||||
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
|
||||
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
|
||||
|
||||
// segment x and y coordinates
|
||||
uint16_t segmentOneX = x + segmentHeight + 2;
|
||||
uint16_t segmentOneY = y;
|
||||
|
||||
uint16_t segmentTwoX = segmentOneX + segmentWidth + 2;
|
||||
uint16_t segmentTwoY = segmentOneY + segmentHeight + 2;
|
||||
|
||||
uint16_t segmentThreeX = segmentTwoX;
|
||||
uint16_t segmentThreeY = segmentTwoY + segmentWidth + 2 + segmentHeight + 2;
|
||||
|
||||
uint16_t segmentFourX = segmentOneX;
|
||||
uint16_t segmentFourY = segmentThreeY + segmentWidth + 2;
|
||||
|
||||
uint16_t segmentFiveX = x;
|
||||
uint16_t segmentFiveY = segmentThreeY;
|
||||
|
||||
uint16_t segmentSixX = x;
|
||||
uint16_t segmentSixY = segmentTwoY;
|
||||
|
||||
uint16_t segmentSevenX = segmentOneX;
|
||||
uint16_t segmentSevenY = segmentTwoY + segmentWidth + 2;
|
||||
|
||||
if (numbers[number][0]) {
|
||||
graphics::ClockRenderer::drawHorizontalSegment(display, segmentOneX, segmentOneY, segmentWidth, segmentHeight);
|
||||
}
|
||||
|
||||
if (numbers[number][1]) {
|
||||
graphics::ClockRenderer::drawVerticalSegment(display, segmentTwoX, segmentTwoY, segmentWidth, segmentHeight);
|
||||
}
|
||||
|
||||
if (numbers[number][2]) {
|
||||
graphics::ClockRenderer::drawVerticalSegment(display, segmentThreeX, segmentThreeY, segmentWidth, segmentHeight);
|
||||
}
|
||||
|
||||
if (numbers[number][3]) {
|
||||
graphics::ClockRenderer::drawHorizontalSegment(display, segmentFourX, segmentFourY, segmentWidth, segmentHeight);
|
||||
}
|
||||
|
||||
if (numbers[number][4]) {
|
||||
graphics::ClockRenderer::drawVerticalSegment(display, segmentFiveX, segmentFiveY, segmentWidth, segmentHeight);
|
||||
}
|
||||
|
||||
if (numbers[number][5]) {
|
||||
graphics::ClockRenderer::drawVerticalSegment(display, segmentSixX, segmentSixY, segmentWidth, segmentHeight);
|
||||
}
|
||||
|
||||
if (numbers[number][6]) {
|
||||
graphics::ClockRenderer::drawHorizontalSegment(display, segmentSevenX, segmentSevenY, segmentWidth, segmentHeight);
|
||||
}
|
||||
}
|
||||
|
||||
void drawHorizontalSegment(OLEDDisplay *display, int x, int y, int width, int height)
|
||||
{
|
||||
int halfHeight = height / 2;
|
||||
|
||||
// draw central rectangle
|
||||
display->fillRect(x, y, width, height);
|
||||
|
||||
// draw end triangles
|
||||
display->fillTriangle(x, y, x, y + height - 1, x - halfHeight, y + halfHeight);
|
||||
|
||||
display->fillTriangle(x + width, y, x + width + halfHeight, y + halfHeight, x + width, y + height - 1);
|
||||
}
|
||||
|
||||
void drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int height)
|
||||
{
|
||||
int halfHeight = height / 2;
|
||||
|
||||
// draw central rectangle
|
||||
display->fillRect(x, y, height, width);
|
||||
|
||||
// draw end triangles
|
||||
display->fillTriangle(x + halfHeight, y - halfHeight, x + height - 1, y, x, y);
|
||||
|
||||
display->fillTriangle(x, y + width, x + height - 1, y + width, x + halfHeight, y + width + halfHeight);
|
||||
}
|
||||
|
||||
/*
|
||||
void drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode, float scale)
|
||||
{
|
||||
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
|
||||
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
|
||||
|
||||
if (digitalMode) {
|
||||
uint16_t radius = (segmentWidth + (segmentHeight * 2) + 4) / 2;
|
||||
uint16_t centerX = (x + segmentHeight + 2) + (radius / 2);
|
||||
uint16_t centerY = (y + segmentHeight + 2) + (radius / 2);
|
||||
|
||||
display->drawCircle(centerX, centerY, radius);
|
||||
display->drawCircle(centerX, centerY, radius + 1);
|
||||
display->drawLine(centerX, centerY, centerX, centerY - radius + 3);
|
||||
display->drawLine(centerX, centerY, centerX + radius - 3, centerY);
|
||||
} else {
|
||||
uint16_t segmentOneX = x + segmentHeight + 2;
|
||||
uint16_t segmentOneY = y;
|
||||
|
||||
uint16_t segmentTwoX = segmentOneX + segmentWidth + 2;
|
||||
uint16_t segmentTwoY = segmentOneY + segmentHeight + 2;
|
||||
|
||||
uint16_t segmentThreeX = segmentOneX;
|
||||
uint16_t segmentThreeY = segmentTwoY + segmentWidth + 2;
|
||||
|
||||
uint16_t segmentFourX = x;
|
||||
uint16_t segmentFourY = y + segmentHeight + 2;
|
||||
|
||||
drawHorizontalSegment(display, segmentOneX, segmentOneY, segmentWidth, segmentHeight);
|
||||
drawVerticalSegment(display, segmentTwoX, segmentTwoY, segmentWidth, segmentHeight);
|
||||
drawHorizontalSegment(display, segmentThreeX, segmentThreeY, segmentWidth, segmentHeight);
|
||||
drawVerticalSegment(display, segmentFourX, segmentFourY, segmentWidth, segmentHeight);
|
||||
}
|
||||
}
|
||||
*/
|
||||
// Draw a digital clock
|
||||
void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
display->clear();
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
int line = 1;
|
||||
// === Set Title, Blank for Clock
|
||||
const char *titleStr = "";
|
||||
// === Header ===
|
||||
graphics::drawCommonHeader(display, x, y, titleStr, true);
|
||||
|
||||
#ifdef T_WATCH_S3
|
||||
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
|
||||
graphics::ClockRenderer::drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14);
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
|
||||
char timeString[16];
|
||||
int hour = 0;
|
||||
int minute = 0;
|
||||
int second = 0;
|
||||
if (rtc_sec > 0) {
|
||||
long hms = rtc_sec % SEC_PER_DAY;
|
||||
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
|
||||
|
||||
hour = hms / SEC_PER_HOUR;
|
||||
minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
||||
second = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
|
||||
}
|
||||
|
||||
bool isPM = hour >= 12;
|
||||
// hour = hour > 12 ? hour - 12 : hour;
|
||||
if (config.display.use_12h_clock) {
|
||||
hour %= 12;
|
||||
if (hour == 0)
|
||||
hour = 12;
|
||||
bool isPM = hour >= 12;
|
||||
snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute);
|
||||
} else {
|
||||
snprintf(timeString, sizeof(timeString), "%02d:%02d", hour, minute);
|
||||
}
|
||||
|
||||
// Format seconds string
|
||||
char secondString[8];
|
||||
snprintf(secondString, sizeof(secondString), "%02d", second);
|
||||
|
||||
#ifdef T_WATCH_S3
|
||||
float scale = 1.5;
|
||||
#else
|
||||
float scale = 0.75;
|
||||
if (isHighResolution) {
|
||||
scale = 1.5;
|
||||
}
|
||||
#endif
|
||||
|
||||
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
|
||||
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
|
||||
|
||||
// calculate hours:minutes string width
|
||||
uint16_t timeStringWidth = strlen(timeString) * 5;
|
||||
|
||||
for (uint8_t i = 0; i < strlen(timeString); i++) {
|
||||
char character = timeString[i];
|
||||
|
||||
if (character == ':') {
|
||||
timeStringWidth += segmentHeight;
|
||||
} else {
|
||||
timeStringWidth += segmentWidth + (segmentHeight * 2) + 4;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t hourMinuteTextX = (display->getWidth() / 2) - (timeStringWidth / 2);
|
||||
|
||||
uint16_t startingHourMinuteTextX = hourMinuteTextX;
|
||||
|
||||
uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2);
|
||||
|
||||
// iterate over characters in hours:minutes string and draw segmented characters
|
||||
for (uint8_t i = 0; i < strlen(timeString); i++) {
|
||||
char character = timeString[i];
|
||||
|
||||
if (character == ':') {
|
||||
drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale);
|
||||
|
||||
hourMinuteTextX += segmentHeight + 6;
|
||||
} else {
|
||||
drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character - '0', scale);
|
||||
|
||||
hourMinuteTextX += segmentWidth + (segmentHeight * 2) + 4;
|
||||
}
|
||||
|
||||
hourMinuteTextX += 5;
|
||||
}
|
||||
|
||||
// draw seconds string
|
||||
display->setFont(FONT_SMALL);
|
||||
int xOffset = (isHighResolution) ? 0 : -1;
|
||||
if (hour >= 10) {
|
||||
xOffset += (isHighResolution) ? 32 : 18;
|
||||
}
|
||||
int yOffset = (isHighResolution) ? 3 : 1;
|
||||
#ifdef SENSECAP_INDICATOR
|
||||
yOffset -= 3;
|
||||
#endif
|
||||
if (config.display.use_12h_clock) {
|
||||
display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2,
|
||||
isPM ? "pm" : "am");
|
||||
}
|
||||
#ifndef USE_EINK
|
||||
xOffset = (isHighResolution) ? 18 : 10;
|
||||
display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - yOffset,
|
||||
secondString);
|
||||
#endif
|
||||
}
|
||||
|
||||
void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y)
|
||||
{
|
||||
display->drawFastImage(x, y, 18, 14, bluetoothConnectedIcon);
|
||||
}
|
||||
|
||||
// Draw an analog clock
|
||||
void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
// === Set Title, Blank for Clock
|
||||
const char *titleStr = "";
|
||||
// === Header ===
|
||||
graphics::drawCommonHeader(display, x, y, titleStr, true);
|
||||
|
||||
#ifdef T_WATCH_S3
|
||||
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
|
||||
drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14);
|
||||
}
|
||||
#endif
|
||||
// clock face center coordinates
|
||||
int16_t centerX = display->getWidth() / 2;
|
||||
int16_t centerY = display->getHeight() / 2;
|
||||
|
||||
// clock face radius
|
||||
int16_t radius = 0;
|
||||
if (display->getHeight() < display->getWidth()) {
|
||||
radius = (display->getHeight() / 2) * 0.9;
|
||||
} else {
|
||||
radius = (display->getWidth() / 2) * 0.9;
|
||||
}
|
||||
#ifdef T_WATCH_S3
|
||||
radius = (display->getWidth() / 2) * 0.8;
|
||||
#endif
|
||||
|
||||
// noon (0 deg) coordinates (outermost circle)
|
||||
int16_t noonX = centerX;
|
||||
int16_t noonY = centerY - radius;
|
||||
|
||||
// second hand radius and y coordinate (outermost circle)
|
||||
int16_t secondHandNoonY = noonY + 1;
|
||||
|
||||
// tick mark outer y coordinate; (first nested circle)
|
||||
int16_t tickMarkOuterNoonY = secondHandNoonY;
|
||||
|
||||
// seconds tick mark inner y coordinate; (second nested circle)
|
||||
double secondsTickMarkInnerNoonY = (double)noonY + 4;
|
||||
if (isHighResolution) {
|
||||
secondsTickMarkInnerNoonY = (double)noonY + 8;
|
||||
}
|
||||
|
||||
// hours tick mark inner y coordinate; (third nested circle)
|
||||
double hoursTickMarkInnerNoonY = (double)noonY + 6;
|
||||
if (isHighResolution) {
|
||||
hoursTickMarkInnerNoonY = (double)noonY + 16;
|
||||
}
|
||||
|
||||
// minute hand y coordinate
|
||||
int16_t minuteHandNoonY = secondsTickMarkInnerNoonY + 4;
|
||||
|
||||
// hour string y coordinate
|
||||
int16_t hourStringNoonY = minuteHandNoonY + 18;
|
||||
|
||||
// hour hand radius and y coordinate
|
||||
int16_t hourHandRadius = radius * 0.35;
|
||||
if (isHighResolution) {
|
||||
int16_t hourHandRadius = radius * 0.55;
|
||||
}
|
||||
int16_t hourHandNoonY = centerY - hourHandRadius;
|
||||
|
||||
display->setColor(OLEDDISPLAY_COLOR::WHITE);
|
||||
display->drawCircle(centerX, centerY, radius);
|
||||
|
||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
|
||||
if (rtc_sec > 0) {
|
||||
long hms = rtc_sec % SEC_PER_DAY;
|
||||
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
|
||||
|
||||
// Tear apart hms into h:m:s
|
||||
int hour = hms / SEC_PER_HOUR;
|
||||
int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
||||
int second = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
|
||||
|
||||
bool isPM = hour >= 12;
|
||||
if (config.display.use_12h_clock) {
|
||||
bool isPM = hour >= 12;
|
||||
display->setFont(FONT_SMALL);
|
||||
int yOffset = isHighResolution ? 1 : 0;
|
||||
#ifdef USE_EINK
|
||||
yOffset += 3;
|
||||
#endif
|
||||
display->drawString(centerX - (display->getStringWidth(isPM ? "pm" : "am") / 2), centerY + yOffset,
|
||||
isPM ? "pm" : "am");
|
||||
}
|
||||
hour %= 12;
|
||||
if (hour == 0)
|
||||
hour = 12;
|
||||
|
||||
int16_t degreesPerHour = 30;
|
||||
int16_t degreesPerMinuteOrSecond = 6;
|
||||
|
||||
double hourBaseAngle = hour * degreesPerHour;
|
||||
double hourAngleOffset = ((double)minute / 60) * degreesPerHour;
|
||||
double hourAngle = radians(hourBaseAngle + hourAngleOffset);
|
||||
|
||||
double minuteBaseAngle = minute * degreesPerMinuteOrSecond;
|
||||
double minuteAngleOffset = ((double)second / 60) * degreesPerMinuteOrSecond;
|
||||
double minuteAngle = radians(minuteBaseAngle + minuteAngleOffset);
|
||||
|
||||
double secondAngle = radians(second * degreesPerMinuteOrSecond);
|
||||
|
||||
double hourX = sin(-hourAngle) * (hourHandNoonY - centerY) + noonX;
|
||||
double hourY = cos(-hourAngle) * (hourHandNoonY - centerY) + centerY;
|
||||
|
||||
double minuteX = sin(-minuteAngle) * (minuteHandNoonY - centerY) + noonX;
|
||||
double minuteY = cos(-minuteAngle) * (minuteHandNoonY - centerY) + centerY;
|
||||
|
||||
double secondX = sin(-secondAngle) * (secondHandNoonY - centerY) + noonX;
|
||||
double secondY = cos(-secondAngle) * (secondHandNoonY - centerY) + centerY;
|
||||
|
||||
display->setFont(FONT_MEDIUM);
|
||||
|
||||
// draw minute and hour tick marks and hour numbers
|
||||
for (uint16_t angle = 0; angle < 360; angle += 6) {
|
||||
double angleInRadians = radians(angle);
|
||||
|
||||
double sineAngleInRadians = sin(-angleInRadians);
|
||||
double cosineAngleInRadians = cos(-angleInRadians);
|
||||
|
||||
double endX = sineAngleInRadians * (tickMarkOuterNoonY - centerY) + noonX;
|
||||
double endY = cosineAngleInRadians * (tickMarkOuterNoonY - centerY) + centerY;
|
||||
|
||||
if (angle % degreesPerHour == 0) {
|
||||
double startX = sineAngleInRadians * (hoursTickMarkInnerNoonY - centerY) + noonX;
|
||||
double startY = cosineAngleInRadians * (hoursTickMarkInnerNoonY - centerY) + centerY;
|
||||
|
||||
// draw hour tick mark
|
||||
display->drawLine(startX, startY, endX, endY);
|
||||
|
||||
static char buffer[2];
|
||||
|
||||
uint8_t hourInt = (angle / 30);
|
||||
|
||||
if (hourInt == 0) {
|
||||
hourInt = 12;
|
||||
}
|
||||
|
||||
// hour number x offset needs to be adjusted for some cases
|
||||
int8_t hourStringXOffset;
|
||||
int8_t hourStringYOffset = 13;
|
||||
|
||||
switch (hourInt) {
|
||||
case 3:
|
||||
hourStringXOffset = 5;
|
||||
break;
|
||||
case 9:
|
||||
hourStringXOffset = 7;
|
||||
break;
|
||||
case 10:
|
||||
case 11:
|
||||
hourStringXOffset = 8;
|
||||
break;
|
||||
case 12:
|
||||
hourStringXOffset = 13;
|
||||
break;
|
||||
default:
|
||||
hourStringXOffset = 6;
|
||||
break;
|
||||
}
|
||||
|
||||
double hourStringX = (sineAngleInRadians * (hourStringNoonY - centerY) + noonX) - hourStringXOffset;
|
||||
double hourStringY = (cosineAngleInRadians * (hourStringNoonY - centerY) + centerY) - hourStringYOffset;
|
||||
|
||||
#ifdef T_WATCH_S3
|
||||
// draw hour number
|
||||
display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt);
|
||||
#else
|
||||
#ifdef USE_EINK
|
||||
if (isHighResolution) {
|
||||
// draw hour number
|
||||
display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt);
|
||||
}
|
||||
#else
|
||||
if (isHighResolution && (hourInt == 3 || hourInt == 6 || hourInt == 9 || hourInt == 12)) {
|
||||
// draw hour number
|
||||
display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
if (angle % degreesPerMinuteOrSecond == 0) {
|
||||
double startX = sineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + noonX;
|
||||
double startY = cosineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + centerY;
|
||||
|
||||
if (isHighResolution) {
|
||||
// draw minute tick mark
|
||||
display->drawLine(startX, startY, endX, endY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// draw hour hand
|
||||
display->drawLine(centerX, centerY, hourX, hourY);
|
||||
|
||||
// draw minute hand
|
||||
display->drawLine(centerX, centerY, minuteX, minuteY);
|
||||
#ifndef USE_EINK
|
||||
// draw second hand
|
||||
display->drawLine(centerX, centerY, secondX, secondY);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ClockRenderer
|
||||
|
||||
} // namespace graphics
|
||||
#endif
|
||||
31
src/graphics/draw/ClockRenderer.h
Normal file
31
src/graphics/draw/ClockRenderer.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <OLEDDisplay.h>
|
||||
#include <OLEDDisplayUi.h>
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
|
||||
/// Forward declarations
|
||||
class Screen;
|
||||
|
||||
namespace ClockRenderer
|
||||
{
|
||||
|
||||
// Clock frame functions
|
||||
void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
|
||||
// Segmented display functions
|
||||
void drawSegmentedDisplayCharacter(OLEDDisplay *display, int x, int y, uint8_t number, float scale = 1);
|
||||
void drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale = 1);
|
||||
void drawHorizontalSegment(OLEDDisplay *display, int x, int y, int width, int height);
|
||||
void drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int height);
|
||||
|
||||
// UI elements for clock displays
|
||||
// void drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode = true, float scale = 1);
|
||||
void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y);
|
||||
|
||||
} // namespace ClockRenderer
|
||||
|
||||
} // namespace graphics
|
||||
137
src/graphics/draw/CompassRenderer.cpp
Normal file
137
src/graphics/draw/CompassRenderer.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#include "CompassRenderer.h"
|
||||
#include "NodeDB.h"
|
||||
#include "UIRenderer.h"
|
||||
#include "configuration.h"
|
||||
#include "gps/GeoCoord.h"
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
#include <cmath>
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
namespace CompassRenderer
|
||||
{
|
||||
|
||||
// Point helper class for compass calculations
|
||||
struct Point {
|
||||
float x, y;
|
||||
Point(float x, float y) : x(x), y(y) {}
|
||||
|
||||
void rotate(float angle)
|
||||
{
|
||||
float cos_a = cos(angle);
|
||||
float sin_a = sin(angle);
|
||||
float new_x = x * cos_a - y * sin_a;
|
||||
float new_y = x * sin_a + y * cos_a;
|
||||
x = new_x;
|
||||
y = new_y;
|
||||
}
|
||||
|
||||
void scale(float factor)
|
||||
{
|
||||
x *= factor;
|
||||
y *= factor;
|
||||
}
|
||||
|
||||
void translate(float dx, float dy)
|
||||
{
|
||||
x += dx;
|
||||
y += dy;
|
||||
}
|
||||
};
|
||||
|
||||
void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading, int16_t radius)
|
||||
{
|
||||
// Show the compass heading (not implemented in original)
|
||||
// This could draw a "N" indicator or north arrow
|
||||
// For now, we'll draw a simple north indicator
|
||||
// const float radius = 17.0f;
|
||||
if (isHighResolution) {
|
||||
radius += 4;
|
||||
}
|
||||
Point north(0, -radius);
|
||||
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING)
|
||||
north.rotate(-myHeading);
|
||||
north.translate(compassX, compassY);
|
||||
|
||||
display->setFont(FONT_SMALL);
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->setColor(BLACK);
|
||||
if (isHighResolution) {
|
||||
display->fillRect(north.x - 8, north.y - 1, display->getStringWidth("N") + 3, FONT_HEIGHT_SMALL - 6);
|
||||
} else {
|
||||
display->fillRect(north.x - 4, north.y - 1, display->getStringWidth("N") + 2, FONT_HEIGHT_SMALL - 6);
|
||||
}
|
||||
display->setColor(WHITE);
|
||||
display->drawString(north.x, north.y - 3, "N");
|
||||
}
|
||||
|
||||
void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian)
|
||||
{
|
||||
Point tip(0.0f, -0.5f), tail(0.0f, 0.35f); // pointing up initially
|
||||
float arrowOffsetX = 0.14f, arrowOffsetY = 0.9f;
|
||||
Point leftArrow(tip.x - arrowOffsetX, tip.y + arrowOffsetY), rightArrow(tip.x + arrowOffsetX, tip.y + arrowOffsetY);
|
||||
|
||||
Point *arrowPoints[] = {&tip, &tail, &leftArrow, &rightArrow};
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
arrowPoints[i]->rotate(headingRadian);
|
||||
arrowPoints[i]->scale(compassDiam * 0.6);
|
||||
arrowPoints[i]->translate(compassX, compassY);
|
||||
}
|
||||
|
||||
#ifdef USE_EINK
|
||||
display->drawTriangle(tip.x, tip.y, rightArrow.x, rightArrow.y, tail.x, tail.y);
|
||||
#else
|
||||
display->fillTriangle(tip.x, tip.y, rightArrow.x, rightArrow.y, tail.x, tail.y);
|
||||
#endif
|
||||
display->drawTriangle(tip.x, tip.y, leftArrow.x, leftArrow.y, tail.x, tail.y);
|
||||
}
|
||||
|
||||
void drawArrowToNode(OLEDDisplay *display, int16_t x, int16_t y, int16_t size, float bearing)
|
||||
{
|
||||
float radians = bearing * DEG_TO_RAD;
|
||||
|
||||
Point tip(0, -size / 2);
|
||||
Point left(-size / 6, size / 4);
|
||||
Point right(size / 6, size / 4);
|
||||
Point tail(0, size / 4.5);
|
||||
|
||||
tip.rotate(radians);
|
||||
left.rotate(radians);
|
||||
right.rotate(radians);
|
||||
tail.rotate(radians);
|
||||
|
||||
tip.translate(x, y);
|
||||
left.translate(x, y);
|
||||
right.translate(x, y);
|
||||
tail.translate(x, y);
|
||||
|
||||
display->fillTriangle(tip.x, tip.y, left.x, left.y, tail.x, tail.y);
|
||||
display->fillTriangle(tip.x, tip.y, right.x, right.y, tail.x, tail.y);
|
||||
}
|
||||
|
||||
float estimatedHeading(double lat, double lon)
|
||||
{
|
||||
// Simple magnetic declination estimation
|
||||
// This is a very basic implementation - the original might be more sophisticated
|
||||
return 0.0f; // Return 0 for now, indicating no heading available
|
||||
}
|
||||
|
||||
uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight)
|
||||
{
|
||||
// Calculate appropriate compass diameter based on display size
|
||||
uint16_t minDimension = (displayWidth < displayHeight) ? displayWidth : displayHeight;
|
||||
uint16_t maxDiam = minDimension / 3; // Use 1/3 of the smaller dimension
|
||||
|
||||
// Ensure minimum and maximum bounds
|
||||
if (maxDiam < 16)
|
||||
maxDiam = 16;
|
||||
if (maxDiam > 64)
|
||||
maxDiam = 64;
|
||||
|
||||
return maxDiam;
|
||||
}
|
||||
|
||||
} // namespace CompassRenderer
|
||||
} // namespace graphics
|
||||
33
src/graphics/draw/CompassRenderer.h
Normal file
33
src/graphics/draw/CompassRenderer.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "graphics/Screen.h"
|
||||
#include "mesh/generated/meshtastic/mesh.pb.h"
|
||||
#include <OLEDDisplay.h>
|
||||
#include <OLEDDisplayUi.h>
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
|
||||
/// Forward declarations
|
||||
class Screen;
|
||||
|
||||
/**
|
||||
* @brief Compass and navigation drawing functions
|
||||
*
|
||||
* Contains all functions related to drawing compass elements, headings,
|
||||
* navigation arrows, and location-based UI components.
|
||||
*/
|
||||
namespace CompassRenderer
|
||||
{
|
||||
// Compass drawing functions
|
||||
void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading, int16_t radius);
|
||||
void drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian);
|
||||
void drawArrowToNode(OLEDDisplay *display, int16_t x, int16_t y, int16_t size, float bearing);
|
||||
|
||||
// Navigation and location functions
|
||||
float estimatedHeading(double lat, double lon);
|
||||
uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight);
|
||||
|
||||
} // namespace CompassRenderer
|
||||
|
||||
} // namespace graphics
|
||||
622
src/graphics/draw/DebugRenderer.cpp
Normal file
622
src/graphics/draw/DebugRenderer.cpp
Normal file
@@ -0,0 +1,622 @@
|
||||
#include "configuration.h"
|
||||
#if HAS_SCREEN
|
||||
#include "../Screen.h"
|
||||
#include "DebugRenderer.h"
|
||||
#include "FSCommon.h"
|
||||
#include "NodeDB.h"
|
||||
#include "Throttle.h"
|
||||
#include "UIRenderer.h"
|
||||
#include "airtime.h"
|
||||
#include "gps/RTC.h"
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
#include "graphics/images.h"
|
||||
#include "main.h"
|
||||
#include "mesh/Channels.h"
|
||||
#include "mesh/generated/meshtastic/deviceonly.pb.h"
|
||||
#include "sleep.h"
|
||||
|
||||
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
|
||||
#include "mesh/wifi/WiFiAPClient.h"
|
||||
#include <WiFi.h>
|
||||
#ifdef ARCH_ESP32
|
||||
#include "mesh/wifi/WiFiAPClient.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
#include "modules/StoreForwardModule.h"
|
||||
#endif
|
||||
#include <DisplayFormatters.h>
|
||||
#include <RadioLibInterface.h>
|
||||
#include <target_specific.h>
|
||||
|
||||
using namespace meshtastic;
|
||||
|
||||
// External variables
|
||||
extern graphics::Screen *screen;
|
||||
extern PowerStatus *powerStatus;
|
||||
extern NodeStatus *nodeStatus;
|
||||
extern GPSStatus *gpsStatus;
|
||||
extern Channels channels;
|
||||
extern AirTime *airTime;
|
||||
|
||||
// External functions from Screen.cpp
|
||||
extern bool heartbeat;
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
extern StoreForwardModule *storeForwardModule;
|
||||
#endif
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
namespace DebugRenderer
|
||||
{
|
||||
|
||||
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
// The coordinates define the left starting point of the text
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
|
||||
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
|
||||
display->setColor(BLACK);
|
||||
}
|
||||
|
||||
char channelStr[20];
|
||||
snprintf(channelStr, sizeof(channelStr), "#%s", channels.getName(channels.getPrimaryIndex()));
|
||||
// Display nodes status
|
||||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
|
||||
UIRenderer::drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodeStatus);
|
||||
} else {
|
||||
UIRenderer::drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 3, nodeStatus);
|
||||
}
|
||||
#if HAS_GPS
|
||||
// Display GPS status
|
||||
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
||||
UIRenderer::drawGpsPowerStatus(display, x, y + 2, gpsStatus);
|
||||
} else {
|
||||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
|
||||
UIRenderer::drawGps(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus);
|
||||
} else {
|
||||
UIRenderer::drawGps(display, x + (SCREEN_WIDTH * 0.63), y + 3, gpsStatus);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
display->setColor(WHITE);
|
||||
// Draw the channel name
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr);
|
||||
// Draw our hardware ID to assist with bluetooth pairing. Either prefix with Info or S&F Logo
|
||||
if (moduleConfig.store_forward.enabled) {
|
||||
#ifdef ARCH_ESP32
|
||||
if (!Throttle::isWithinTimespanMs(storeForwardModule->lastHeartbeat,
|
||||
(storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || ARCH_PORTDUINO) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12,
|
||||
8, imgQuestionL1);
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 11 + FONT_HEIGHT_SMALL, 12,
|
||||
8, imgQuestionL2);
|
||||
#else
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(screen->ourId), y + 2 + FONT_HEIGHT_SMALL, 8,
|
||||
8, imgQuestion);
|
||||
#endif
|
||||
} else {
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS)) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16,
|
||||
8, imgSFL1);
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 11 + FONT_HEIGHT_SMALL, 16,
|
||||
8, imgSFL2);
|
||||
#else
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 13 - display->getStringWidth(screen->ourId), y + 2 + FONT_HEIGHT_SMALL, 11,
|
||||
8, imgSF);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
// TODO: Raspberry Pi supports more than just the one screen size
|
||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||||
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || ARCH_PORTDUINO) && \
|
||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
|
||||
imgInfoL1);
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8,
|
||||
imgInfoL2);
|
||||
#else
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(screen->ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8,
|
||||
imgInfo);
|
||||
#endif
|
||||
}
|
||||
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(screen->ourId), y + FONT_HEIGHT_SMALL, screen->ourId);
|
||||
|
||||
// Draw any log messages
|
||||
display->drawLogBuffer(x, y + (FONT_HEIGHT_SMALL * 2));
|
||||
|
||||
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||
#ifdef SHOW_REDRAWS
|
||||
if (heartbeat)
|
||||
display->setPixel(0, 0);
|
||||
heartbeat = !heartbeat;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ****************************
|
||||
// * WiFi Screen *
|
||||
// ****************************
|
||||
void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
|
||||
display->clear();
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
int line = 1;
|
||||
|
||||
// === Set Title
|
||||
const char *titleStr = "WiFi";
|
||||
|
||||
// === Header ===
|
||||
graphics::drawCommonHeader(display, x, y, titleStr);
|
||||
|
||||
const char *wifiName = config.network.wifi_ssid;
|
||||
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
display->drawString(x, getTextPositions(display)[line++], "WiFi: Not Connected");
|
||||
} else {
|
||||
display->drawString(x, getTextPositions(display)[line++], "WiFi: Connected");
|
||||
|
||||
char rssiStr[32];
|
||||
snprintf(rssiStr, sizeof(rssiStr), "RSSI: %d", WiFi.RSSI());
|
||||
display->drawString(x, getTextPositions(display)[line++], rssiStr);
|
||||
}
|
||||
|
||||
/*
|
||||
- WL_CONNECTED: assigned when connected to a WiFi network;
|
||||
- WL_NO_SSID_AVAIL: assigned when no SSID are available;
|
||||
- WL_CONNECT_FAILED: assigned when the connection fails for all the attempts;
|
||||
- WL_CONNECTION_LOST: assigned when the connection is lost;
|
||||
- WL_DISCONNECTED: assigned when disconnected from a network;
|
||||
- WL_IDLE_STATUS: it is a temporary status assigned when WiFi.begin() is called and remains active until the number of
|
||||
attempts expires (resulting in WL_CONNECT_FAILED) or a connection is established (resulting in WL_CONNECTED);
|
||||
- WL_SCAN_COMPLETED: assigned when the scan networks is completed;
|
||||
- WL_NO_SHIELD: assigned when no WiFi shield is present;
|
||||
|
||||
*/
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
char ipStr[64];
|
||||
snprintf(ipStr, sizeof(ipStr), "IP: %s", WiFi.localIP().toString().c_str());
|
||||
display->drawString(x, getTextPositions(display)[line++], ipStr);
|
||||
} else if (WiFi.status() == WL_NO_SSID_AVAIL) {
|
||||
display->drawString(x, getTextPositions(display)[line++], "SSID Not Found");
|
||||
} else if (WiFi.status() == WL_CONNECTION_LOST) {
|
||||
display->drawString(x, getTextPositions(display)[line++], "Connection Lost");
|
||||
} else if (WiFi.status() == WL_IDLE_STATUS) {
|
||||
display->drawString(x, getTextPositions(display)[line++], "Idle ... Reconnecting");
|
||||
} else if (WiFi.status() == WL_CONNECT_FAILED) {
|
||||
display->drawString(x, getTextPositions(display)[line++], "Connection Failed");
|
||||
}
|
||||
#ifdef ARCH_ESP32
|
||||
else {
|
||||
// Codes:
|
||||
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-reason-code
|
||||
display->drawString(x, getTextPositions(display)[line++],
|
||||
WiFi.disconnectReasonName(static_cast<wifi_err_reason_t>(getWifiDisconnectReason())));
|
||||
}
|
||||
#else
|
||||
else {
|
||||
char statusStr[32];
|
||||
snprintf(statusStr, sizeof(statusStr), "Unknown status: %d", WiFi.status());
|
||||
display->drawString(x, getTextPositions(display)[line++], statusStr);
|
||||
}
|
||||
#endif
|
||||
|
||||
char ssidStr[64];
|
||||
snprintf(ssidStr, sizeof(ssidStr), "SSID: %s", wifiName);
|
||||
display->drawString(x, getTextPositions(display)[line++], ssidStr);
|
||||
|
||||
display->drawString(x, getTextPositions(display)[line++], "URL: http://meshtastic.local");
|
||||
|
||||
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||
#ifdef SHOW_REDRAWS
|
||||
if (heartbeat)
|
||||
display->setPixel(0, 0);
|
||||
heartbeat = !heartbeat;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
// The coordinates define the left starting point of the text
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
|
||||
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
|
||||
display->setColor(BLACK);
|
||||
}
|
||||
|
||||
char batStr[20];
|
||||
if (powerStatus->getHasBattery()) {
|
||||
int batV = powerStatus->getBatteryVoltageMv() / 1000;
|
||||
int batCv = (powerStatus->getBatteryVoltageMv() % 1000) / 10;
|
||||
|
||||
snprintf(batStr, sizeof(batStr), "B %01d.%02dV %3d%% %c%c", batV, batCv, powerStatus->getBatteryChargePercent(),
|
||||
powerStatus->getIsCharging() ? '+' : ' ', powerStatus->getHasUSB() ? 'U' : ' ');
|
||||
|
||||
// Line 1
|
||||
display->drawString(x, y, batStr);
|
||||
if (config.display.heading_bold)
|
||||
display->drawString(x + 1, y, batStr);
|
||||
} else {
|
||||
// Line 1
|
||||
display->drawString(x, y, "USB");
|
||||
if (config.display.heading_bold)
|
||||
display->drawString(x + 1, y, "USB");
|
||||
}
|
||||
|
||||
// auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, true);
|
||||
|
||||
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode);
|
||||
// if (config.display.heading_bold)
|
||||
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode);
|
||||
|
||||
uint32_t currentMillis = millis();
|
||||
uint32_t seconds = currentMillis / 1000;
|
||||
uint32_t minutes = seconds / 60;
|
||||
uint32_t hours = minutes / 60;
|
||||
uint32_t days = hours / 24;
|
||||
// currentMillis %= 1000;
|
||||
// seconds %= 60;
|
||||
// minutes %= 60;
|
||||
// hours %= 24;
|
||||
|
||||
// Show uptime as days, hours, minutes OR seconds
|
||||
std::string uptime = UIRenderer::drawTimeDelta(days, hours, minutes, seconds);
|
||||
|
||||
// Line 1 (Still)
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
|
||||
if (config.display.heading_bold)
|
||||
display->drawString(x - 1 + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
|
||||
|
||||
display->setColor(WHITE);
|
||||
|
||||
// Setup string to assemble analogClock string
|
||||
std::string analogClock = "";
|
||||
|
||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
|
||||
if (rtc_sec > 0) {
|
||||
long hms = rtc_sec % SEC_PER_DAY;
|
||||
// hms += tz.tz_dsttime * SEC_PER_HOUR;
|
||||
// hms -= tz.tz_minuteswest * SEC_PER_MIN;
|
||||
// mod `hms` to ensure in positive range of [0...SEC_PER_DAY)
|
||||
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
|
||||
|
||||
// Tear apart hms into h:m:s
|
||||
int hour = hms / SEC_PER_HOUR;
|
||||
int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
||||
int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
|
||||
|
||||
char timebuf[12];
|
||||
|
||||
if (config.display.use_12h_clock) {
|
||||
std::string meridiem = "am";
|
||||
if (hour >= 12) {
|
||||
if (hour > 12)
|
||||
hour -= 12;
|
||||
meridiem = "pm";
|
||||
}
|
||||
if (hour == 00) {
|
||||
hour = 12;
|
||||
}
|
||||
snprintf(timebuf, sizeof(timebuf), "%d:%02d:%02d%s", hour, min, sec, meridiem.c_str());
|
||||
} else {
|
||||
snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", hour, min, sec);
|
||||
}
|
||||
analogClock += timebuf;
|
||||
}
|
||||
|
||||
// Line 2
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, analogClock.c_str());
|
||||
|
||||
// Display Channel Utilization
|
||||
char chUtil[13];
|
||||
snprintf(chUtil, sizeof(chUtil), "ChUtil %2.0f%%", airTime->channelUtilizationPercent());
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(chUtil), y + FONT_HEIGHT_SMALL * 1, chUtil);
|
||||
|
||||
#if HAS_GPS
|
||||
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
||||
// Line 3
|
||||
if (config.display.gps_format !=
|
||||
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude
|
||||
UIRenderer::drawGpsAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
|
||||
|
||||
// Line 4
|
||||
UIRenderer::drawGpsCoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus);
|
||||
} else {
|
||||
UIRenderer::drawGpsPowerStatus(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
|
||||
}
|
||||
#endif
|
||||
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||
#ifdef SHOW_REDRAWS
|
||||
if (heartbeat)
|
||||
display->setPixel(0, 0);
|
||||
heartbeat = !heartbeat;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Trampoline functions for DebugInfo class access
|
||||
void drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
drawFrame(display, state, x, y);
|
||||
}
|
||||
|
||||
void drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
drawFrameSettings(display, state, x, y);
|
||||
}
|
||||
|
||||
void drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
drawFrameWiFi(display, state, x, y);
|
||||
}
|
||||
|
||||
// ****************************
|
||||
// * LoRa Focused Screen *
|
||||
// ****************************
|
||||
void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
display->clear();
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
int line = 1;
|
||||
|
||||
// === Set Title
|
||||
const char *titleStr = (isHighResolution) ? "LoRa Info" : "LoRa";
|
||||
|
||||
// === Header ===
|
||||
graphics::drawCommonHeader(display, x, y, titleStr);
|
||||
|
||||
// === First Row: Region / BLE Name ===
|
||||
graphics::UIRenderer::drawNodes(display, x, getTextPositions(display)[line] + 2, nodeStatus, 0, true, "");
|
||||
|
||||
uint8_t dmac[6];
|
||||
char shortnameble[35];
|
||||
getMacAddr(dmac);
|
||||
snprintf(screen->ourId, sizeof(screen->ourId), "%02x%02x", dmac[4], dmac[5]);
|
||||
snprintf(shortnameble, sizeof(shortnameble), "BLE: %s", screen->ourId);
|
||||
int textWidth = display->getStringWidth(shortnameble);
|
||||
int nameX = (SCREEN_WIDTH - textWidth);
|
||||
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
|
||||
|
||||
// === Second Row: Radio Preset ===
|
||||
auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false);
|
||||
char regionradiopreset[25];
|
||||
const char *region = myRegion ? myRegion->name : NULL;
|
||||
if (region != nullptr) {
|
||||
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, mode);
|
||||
}
|
||||
textWidth = display->getStringWidth(regionradiopreset);
|
||||
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||
display->drawString(nameX, getTextPositions(display)[line++], regionradiopreset);
|
||||
|
||||
// === Third Row: Frequency / ChanNum ===
|
||||
char frequencyslot[35];
|
||||
char freqStr[16];
|
||||
float freq = RadioLibInterface::instance->getFreq();
|
||||
snprintf(freqStr, sizeof(freqStr), "%.3f", freq);
|
||||
if (config.lora.channel_num == 0) {
|
||||
snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %smhz", freqStr);
|
||||
} else {
|
||||
snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %smhz (%d)", freqStr, config.lora.channel_num);
|
||||
}
|
||||
size_t len = strlen(frequencyslot);
|
||||
if (len >= 4 && strcmp(frequencyslot + len - 4, " (0)") == 0) {
|
||||
frequencyslot[len - 4] = '\0'; // Remove the last three characters
|
||||
}
|
||||
textWidth = display->getStringWidth(frequencyslot);
|
||||
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||
display->drawString(nameX, getTextPositions(display)[line++], frequencyslot);
|
||||
|
||||
// === Fourth Row: Channel Utilization ===
|
||||
const char *chUtil = "ChUtil:";
|
||||
char chUtilPercentage[10];
|
||||
snprintf(chUtilPercentage, sizeof(chUtilPercentage), "%2.0f%%", airTime->channelUtilizationPercent());
|
||||
|
||||
int chUtil_x = (isHighResolution) ? display->getStringWidth(chUtil) + 10 : display->getStringWidth(chUtil) + 5;
|
||||
int chUtil_y = getTextPositions(display)[line] + 3;
|
||||
|
||||
int chutil_bar_width = (isHighResolution) ? 100 : 50;
|
||||
int chutil_bar_height = (isHighResolution) ? 12 : 7;
|
||||
int extraoffset = (isHighResolution) ? 6 : 3;
|
||||
int chutil_percent = airTime->channelUtilizationPercent();
|
||||
|
||||
int centerofscreen = SCREEN_WIDTH / 2;
|
||||
int total_line_content_width = (chUtil_x + chutil_bar_width + display->getStringWidth(chUtilPercentage) + extraoffset) / 2;
|
||||
int starting_position = centerofscreen - total_line_content_width;
|
||||
|
||||
display->drawString(starting_position, getTextPositions(display)[line++], chUtil);
|
||||
|
||||
// Force 56% or higher to show a full 100% bar, text would still show related percent.
|
||||
if (chutil_percent >= 61) {
|
||||
chutil_percent = 100;
|
||||
}
|
||||
|
||||
// Weighting for nonlinear segments
|
||||
float milestone1 = 25;
|
||||
float milestone2 = 40;
|
||||
float weight1 = 0.45; // Weight for 0–25%
|
||||
float weight2 = 0.35; // Weight for 25–40%
|
||||
float weight3 = 0.20; // Weight for 40–100%
|
||||
float totalWeight = weight1 + weight2 + weight3;
|
||||
|
||||
int seg1 = chutil_bar_width * (weight1 / totalWeight);
|
||||
int seg2 = chutil_bar_width * (weight2 / totalWeight);
|
||||
int seg3 = chutil_bar_width * (weight3 / totalWeight);
|
||||
|
||||
int fillRight = 0;
|
||||
|
||||
if (chutil_percent <= milestone1) {
|
||||
fillRight = (seg1 * (chutil_percent / milestone1));
|
||||
} else if (chutil_percent <= milestone2) {
|
||||
fillRight = seg1 + (seg2 * ((chutil_percent - milestone1) / (milestone2 - milestone1)));
|
||||
} else {
|
||||
fillRight = seg1 + seg2 + (seg3 * ((chutil_percent - milestone2) / (100 - milestone2)));
|
||||
}
|
||||
|
||||
// Draw outline
|
||||
display->drawRect(starting_position + chUtil_x, chUtil_y, chutil_bar_width, chutil_bar_height);
|
||||
|
||||
// Fill progress
|
||||
if (fillRight > 0) {
|
||||
display->fillRect(starting_position + chUtil_x, chUtil_y, fillRight, chutil_bar_height);
|
||||
}
|
||||
|
||||
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[4],
|
||||
chUtilPercentage);
|
||||
}
|
||||
|
||||
// ****************************
|
||||
// * Memory Screen *
|
||||
// ****************************
|
||||
void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
display->clear();
|
||||
display->setFont(FONT_SMALL);
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
// === Set Title
|
||||
const char *titleStr = "System";
|
||||
|
||||
// === Header ===
|
||||
graphics::drawCommonHeader(display, x, y, titleStr);
|
||||
|
||||
// === Layout ===
|
||||
int line = 1;
|
||||
const int barHeight = 6;
|
||||
const int labelX = x;
|
||||
int barsOffset = (isHighResolution) ? 24 : 0;
|
||||
#ifdef USE_EINK
|
||||
barsOffset -= 12;
|
||||
#endif
|
||||
const int barX = x + 40 + barsOffset;
|
||||
|
||||
auto drawUsageRow = [&](const char *label, uint32_t used, uint32_t total, bool isHeap = false) {
|
||||
if (total == 0)
|
||||
return;
|
||||
|
||||
int percent = (used * 100) / total;
|
||||
|
||||
char combinedStr[24];
|
||||
if (isHighResolution) {
|
||||
snprintf(combinedStr, sizeof(combinedStr), "%s%3d%% %u/%uKB", (percent > 80) ? "! " : "", percent, used / 1024,
|
||||
total / 1024);
|
||||
} else {
|
||||
snprintf(combinedStr, sizeof(combinedStr), "%s%3d%%", (percent > 80) ? "! " : "", percent);
|
||||
}
|
||||
|
||||
int textWidth = display->getStringWidth(combinedStr);
|
||||
int adjustedBarWidth = SCREEN_WIDTH - barX - textWidth - 6;
|
||||
if (adjustedBarWidth < 10)
|
||||
adjustedBarWidth = 10;
|
||||
|
||||
int fillWidth = (used * adjustedBarWidth) / total;
|
||||
|
||||
// Label
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->drawString(labelX, getTextPositions(display)[line], label);
|
||||
|
||||
// Bar
|
||||
int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2;
|
||||
display->setColor(WHITE);
|
||||
display->drawRect(barX, barY, adjustedBarWidth, barHeight);
|
||||
|
||||
display->fillRect(barX, barY, fillWidth, barHeight);
|
||||
display->setColor(WHITE);
|
||||
|
||||
// Value string
|
||||
display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
||||
display->drawString(SCREEN_WIDTH - 2, getTextPositions(display)[line], combinedStr);
|
||||
};
|
||||
|
||||
// === Memory values ===
|
||||
uint32_t heapUsed = memGet.getHeapSize() - memGet.getFreeHeap();
|
||||
uint32_t heapTotal = memGet.getHeapSize();
|
||||
|
||||
uint32_t psramUsed = memGet.getPsramSize() - memGet.getFreePsram();
|
||||
uint32_t psramTotal = memGet.getPsramSize();
|
||||
|
||||
uint32_t flashUsed = 0, flashTotal = 0;
|
||||
#ifdef ESP32
|
||||
flashUsed = FSCom.usedBytes();
|
||||
flashTotal = FSCom.totalBytes();
|
||||
#endif
|
||||
|
||||
uint32_t sdUsed = 0, sdTotal = 0;
|
||||
bool hasSD = false;
|
||||
/*
|
||||
#ifdef HAS_SDCARD
|
||||
hasSD = SD.cardType() != CARD_NONE;
|
||||
if (hasSD) {
|
||||
sdUsed = SD.usedBytes();
|
||||
sdTotal = SD.totalBytes();
|
||||
}
|
||||
#endif
|
||||
*/
|
||||
// === Draw memory rows
|
||||
drawUsageRow("Heap:", heapUsed, heapTotal, true);
|
||||
#ifdef ESP32
|
||||
if (psramUsed > 0) {
|
||||
line += 1;
|
||||
drawUsageRow("PSRAM:", psramUsed, psramTotal);
|
||||
}
|
||||
if (flashTotal > 0) {
|
||||
line += 1;
|
||||
drawUsageRow("Flash:", flashUsed, flashTotal);
|
||||
}
|
||||
#endif
|
||||
if (hasSD && sdTotal > 0) {
|
||||
line += 1;
|
||||
drawUsageRow("SD:", sdUsed, sdTotal);
|
||||
}
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
// System Uptime
|
||||
if (line < 2) {
|
||||
line += 1;
|
||||
}
|
||||
line += 1;
|
||||
char appversionstr[35];
|
||||
snprintf(appversionstr, sizeof(appversionstr), "Ver.: %s", optstr(APP_VERSION));
|
||||
int textWidth = display->getStringWidth(appversionstr);
|
||||
int nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||
display->drawString(nameX, getTextPositions(display)[line], appversionstr);
|
||||
|
||||
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line < 4)) { // Only show uptime if the screen can show it
|
||||
line += 1;
|
||||
char uptimeStr[32] = "";
|
||||
uint32_t uptime = millis() / 1000;
|
||||
uint32_t days = uptime / 86400;
|
||||
uint32_t hours = (uptime % 86400) / 3600;
|
||||
uint32_t mins = (uptime % 3600) / 60;
|
||||
// Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
|
||||
if (days)
|
||||
snprintf(uptimeStr, sizeof(uptimeStr), " Up: %ud %uh", days, hours);
|
||||
else if (hours)
|
||||
snprintf(uptimeStr, sizeof(uptimeStr), " Up: %uh %um", hours, mins);
|
||||
else
|
||||
snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %um", mins);
|
||||
textWidth = display->getStringWidth(uptimeStr);
|
||||
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||
display->drawString(nameX, getTextPositions(display)[line], uptimeStr);
|
||||
}
|
||||
}
|
||||
} // namespace DebugRenderer
|
||||
} // namespace graphics
|
||||
#endif
|
||||
38
src/graphics/draw/DebugRenderer.h
Normal file
38
src/graphics/draw/DebugRenderer.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <OLEDDisplay.h>
|
||||
#include <OLEDDisplayUi.h>
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
|
||||
/// Forward declarations
|
||||
class Screen;
|
||||
class DebugInfo;
|
||||
|
||||
/**
|
||||
* @brief Debug and diagnostic drawing functions
|
||||
*
|
||||
* Contains all functions related to drawing debug information,
|
||||
* WiFi status, settings screens, and diagnostic data.
|
||||
*/
|
||||
namespace DebugRenderer
|
||||
{
|
||||
// Debug frame functions
|
||||
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
|
||||
// Trampoline functions for framework callback compatibility
|
||||
void drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
void drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
void drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
|
||||
// LoRa information display
|
||||
void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
|
||||
// Memory screen display
|
||||
void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
} // namespace DebugRenderer
|
||||
|
||||
} // namespace graphics
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user