mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-14 06:42:34 +00:00
Compare commits
200 Commits
dont-proce
...
fix-msg-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cdee41c7e | ||
|
|
eda728eb68 | ||
|
|
2191fe465c | ||
|
|
78ae8f2a51 | ||
|
|
569a911455 | ||
|
|
39ff880506 | ||
|
|
c5b95f5a4b | ||
|
|
209157c9dd | ||
|
|
15f4aebcd5 | ||
|
|
2354c52b16 | ||
|
|
fb59d68edd | ||
|
|
227d0fa7dc | ||
|
|
7b854fb5ca | ||
|
|
7c1eff54fb | ||
|
|
f8b160595f | ||
|
|
c92fa6aa8a | ||
|
|
77acbc6814 | ||
|
|
81cb1e427f | ||
|
|
f6ba9604a7 | ||
|
|
9c6544ebfa | ||
|
|
b6eeccadeb | ||
|
|
37d14f942e | ||
|
|
4594ae474e | ||
|
|
f26e657577 | ||
|
|
e7b7479589 | ||
|
|
a6b29541df | ||
|
|
175357f576 | ||
|
|
e1634076f2 | ||
|
|
d6df664102 | ||
|
|
50a5b36498 | ||
|
|
a25bfd264c | ||
|
|
4d6fe936ae | ||
|
|
ec9f3fa6ea | ||
|
|
8356ad97e4 | ||
|
|
bf51c38975 | ||
|
|
3df3c876cc | ||
|
|
f825e61b89 | ||
|
|
b32293f2cb | ||
|
|
64cd62d6af | ||
|
|
68f07c5f9d | ||
|
|
7fb96ce2ba | ||
|
|
12687a1073 | ||
|
|
89de499198 | ||
|
|
4881362340 | ||
|
|
f31fd34ce0 | ||
|
|
18000ccf21 | ||
|
|
7776ec15b6 | ||
|
|
e4c7fca716 | ||
|
|
5b63bd9331 | ||
|
|
289f90bdbe | ||
|
|
26bcc9627d | ||
|
|
09a0df3a1f | ||
|
|
fe329892de | ||
|
|
2681332678 | ||
|
|
f994eb185f | ||
|
|
cc37535b2d | ||
|
|
55c23dec13 | ||
|
|
4dfc062abd | ||
|
|
ced334d13b | ||
|
|
0be21d90c1 | ||
|
|
521fbc44b4 | ||
|
|
361771c9bb | ||
|
|
fa45660b7d | ||
|
|
2e8f4ad6af | ||
|
|
18550ea80c | ||
|
|
1c1c0cc791 | ||
|
|
a0c0388dd9 | ||
|
|
789c1ab59d | ||
|
|
e8367894f2 | ||
|
|
8aae4f1b9d | ||
|
|
5850a7cd6b | ||
|
|
6c89ea7cee | ||
|
|
8a8f60d129 | ||
|
|
b59409bec0 | ||
|
|
c66125114f | ||
|
|
edb7ec58c6 | ||
|
|
655c6b51fe | ||
|
|
0bd4cefad3 | ||
|
|
0952007805 | ||
|
|
9b1fb795d7 | ||
|
|
3040e5a7bb | ||
|
|
7612799ef6 | ||
|
|
3b82d55176 | ||
|
|
a6b8202cd4 | ||
|
|
cfc1bf10c9 | ||
|
|
c5fad6cca1 | ||
|
|
b8d7222423 | ||
|
|
7d1300ab66 | ||
|
|
16d7de5989 | ||
|
|
102c447fe3 | ||
|
|
d66665b96e | ||
|
|
088be6bf6a | ||
|
|
bd3cbfc1ad | ||
|
|
fddc4e00ca | ||
|
|
5f7eec5504 | ||
|
|
6b94c297b9 | ||
|
|
edeb25cab5 | ||
|
|
44688e8363 | ||
|
|
ca79760372 | ||
|
|
4a669032dc | ||
|
|
b53dd2ec90 | ||
|
|
5ae4ff9162 | ||
|
|
a0e14439cb | ||
|
|
ed394f5f9d | ||
|
|
11db6d4dcc | ||
|
|
10c6836263 | ||
|
|
4e03df5ea7 | ||
|
|
9b41131af8 | ||
|
|
fb34dac08d | ||
|
|
d3e3a91096 | ||
|
|
5f8503c62d | ||
|
|
b0e8321514 | ||
|
|
dd2f77ea0c | ||
|
|
6c7cff7de2 | ||
|
|
46f797c40d | ||
|
|
75b01e17bc | ||
|
|
8685436cbb | ||
|
|
834c3c5cc2 | ||
|
|
25a19b49ad | ||
|
|
a4d96bebfb | ||
|
|
d21d6d2085 | ||
|
|
26c38ffc8e | ||
|
|
237b8908f7 | ||
|
|
06bccef462 | ||
|
|
3120bb8fd7 | ||
|
|
0903ed8232 | ||
|
|
f8ba392a24 | ||
|
|
3dd384dd53 | ||
|
|
2c071a3283 | ||
|
|
596cd7e0b6 | ||
|
|
3f5c30e3b3 | ||
|
|
1a279c6053 | ||
|
|
67e3a17b28 | ||
|
|
24204feb71 | ||
|
|
4ace2638e1 | ||
|
|
5aa486d6c2 | ||
|
|
ba26d03b1b | ||
|
|
9a1c2c9b61 | ||
|
|
5b9db81819 | ||
|
|
f2ba7d7851 | ||
|
|
1eafdfcbc8 | ||
|
|
3d825c51dd | ||
|
|
915f882e1f | ||
|
|
103ea2f168 | ||
|
|
4fef890466 | ||
|
|
35f5b7ec03 | ||
|
|
5136c8ba24 | ||
|
|
1037fa5622 | ||
|
|
8b42bf7a95 | ||
|
|
1c329d9ffa | ||
|
|
093a37a2b0 | ||
|
|
1daf5aad1f | ||
|
|
fe3f14a63e | ||
|
|
7574bfb7cb | ||
|
|
ce75bf4496 | ||
|
|
5ce47045e7 | ||
|
|
57e1725419 | ||
|
|
11309662a9 | ||
|
|
890357d579 | ||
|
|
f413c49555 | ||
|
|
c19f573b49 | ||
|
|
5de61b1a3d | ||
|
|
1c1462e776 | ||
|
|
eb6ef1cbea | ||
|
|
9654f5b218 | ||
|
|
68726a1b0e | ||
|
|
5b62bbe8e6 | ||
|
|
e55084629a | ||
|
|
1691e885f2 | ||
|
|
2d7818797d | ||
|
|
f65e2c639e | ||
|
|
95200e8f6b | ||
|
|
36e8dc74f4 | ||
|
|
78c5309e9a | ||
|
|
9feb1d378e | ||
|
|
e5e8683cdb | ||
|
|
d538ad170c | ||
|
|
c64c196778 | ||
|
|
8e552a9f0c | ||
|
|
a02017a5c8 | ||
|
|
0046d957f1 | ||
|
|
4a241deb96 | ||
|
|
8d5ae1d5d2 | ||
|
|
e1e89a5e62 | ||
|
|
a7be93449e | ||
|
|
c8694f9f2d | ||
|
|
062168cd42 | ||
|
|
1877a2c531 | ||
|
|
52f0e5a3db | ||
|
|
ac8c372349 | ||
|
|
1bfa429c38 | ||
|
|
ddd149945a | ||
|
|
e3dd8164a4 | ||
|
|
9b8149f14e | ||
|
|
05f1518951 | ||
|
|
e26de85b5f | ||
|
|
a2df80e833 | ||
|
|
db238ef524 | ||
|
|
f2b935f48f | ||
|
|
e69da71d4e |
@@ -76,7 +76,7 @@ bool loopCanSleep()
|
|||||||
// Called just prior to starting Meshtastic. Allows for setting config values before startup.
|
// Called just prior to starting Meshtastic. Allows for setting config values before startup.
|
||||||
void lateInitVariant()
|
void lateInitVariant()
|
||||||
{
|
{
|
||||||
settingsMap[logoutputlevel] = level_error;
|
portduino_config.logoutputlevel = level_error;
|
||||||
channelFile.channels[0] = meshtastic_Channel{
|
channelFile.channels[0] = meshtastic_Channel{
|
||||||
.has_settings = true,
|
.has_settings = true,
|
||||||
.settings =
|
.settings =
|
||||||
@@ -132,7 +132,7 @@ int portduino_main(int argc, char **argv); // Renamed "main" function from Mesht
|
|||||||
// Start Meshtastic in a thread and wait till it has reached the ON state.
|
// Start Meshtastic in a thread and wait till it has reached the ON state.
|
||||||
int LLVMFuzzerInitialize(int *argc, char ***argv)
|
int LLVMFuzzerInitialize(int *argc, char ***argv)
|
||||||
{
|
{
|
||||||
settingsMap[maxtophone] = 5;
|
portduino_config.maxtophone = 5;
|
||||||
|
|
||||||
meshtasticThread = std::thread([program = *argv[0]]() {
|
meshtasticThread = std::thread([program = *argv[0]]() {
|
||||||
char nodeIdStr[12];
|
char nodeIdStr[12];
|
||||||
|
|||||||
4
.github/actions/setup-base/action.yml
vendored
4
.github/actions/setup-base/action.yml
vendored
@@ -5,7 +5,7 @@ runs:
|
|||||||
using: composite
|
using: composite
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
@@ -23,7 +23,7 @@ runs:
|
|||||||
sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev lsb-release
|
sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev lsb-release
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
cache: pip
|
cache: pip
|
||||||
|
|||||||
2
.github/workflows/build_debian_src.yml
vendored
2
.github/workflows/build_debian_src.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
path: meshtasticd
|
path: meshtasticd
|
||||||
|
|||||||
2
.github/workflows/build_firmware.yml
vendored
2
.github/workflows/build_firmware.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
name: build-${{ inputs.platform }}
|
name: build-${{ inputs.platform }}
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
|
|||||||
2
.github/workflows/docker_build.yml
vendored
2
.github/workflows/docker_build.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
|||||||
runs-on: ${{ inputs.runs-on }}
|
runs-on: ${{ inputs.runs-on }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
|
|||||||
2
.github/workflows/docker_manifest.yml
vendored
2
.github/workflows/docker_manifest.yml
vendored
@@ -83,7 +83,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
|
|||||||
2
.github/workflows/hook_copr.yml
vendored
2
.github/workflows/hook_copr.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
ref: ${{ github.ref }}
|
ref: ${{ github.ref }}
|
||||||
|
|||||||
40
.github/workflows/main_matrix.yml
vendored
40
.github/workflows/main_matrix.yml
vendored
@@ -42,12 +42,16 @@ jobs:
|
|||||||
- check
|
- check
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
cache: pip
|
cache: pip
|
||||||
- run: pip install -U platformio
|
- run: pip install -U platformio
|
||||||
|
- name: Uncomment build epoch
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
|
||||||
- name: Generate matrix
|
- name: Generate matrix
|
||||||
id: jsonStep
|
id: jsonStep
|
||||||
run: |
|
run: |
|
||||||
@@ -72,7 +76,7 @@ jobs:
|
|||||||
version:
|
version:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Get release version string
|
- name: Get release version string
|
||||||
run: |
|
run: |
|
||||||
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||||
@@ -93,7 +97,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Build base
|
- name: Build base
|
||||||
id: base
|
id: base
|
||||||
uses: ./.github/actions/setup-base
|
uses: ./.github/actions/setup-base
|
||||||
@@ -288,12 +292,12 @@ jobs:
|
|||||||
]
|
]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
path: ./
|
path: ./
|
||||||
pattern: firmware-${{matrix.arch}}-*
|
pattern: firmware-${{matrix.arch}}-*
|
||||||
@@ -322,7 +326,7 @@ jobs:
|
|||||||
./Meshtastic_nRF52_factory_erase*.uf2
|
./Meshtastic_nRF52_factory_erase*.uf2
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -367,10 +371,10 @@ jobs:
|
|||||||
- package-pio-deps-native-tft
|
- package-pio-deps-native-tft
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
@@ -386,14 +390,14 @@ jobs:
|
|||||||
Autogenerated by github action, developer should edit as required before publishing...
|
Autogenerated by github action, developer should edit as required before publishing...
|
||||||
|
|
||||||
- name: Download source deb
|
- name: Download source deb
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
|
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
path: ./output/debian-src
|
path: ./output/debian-src
|
||||||
|
|
||||||
- name: Download `native-tft` pio deps
|
- name: Download `native-tft` pio deps
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
|
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -436,14 +440,14 @@ jobs:
|
|||||||
needs: [release-artifacts, version]
|
needs: [release-artifacts, version]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -460,7 +464,7 @@ jobs:
|
|||||||
- name: Zip firmware
|
- name: Zip firmware
|
||||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -491,14 +495,14 @@ jobs:
|
|||||||
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
|
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|||||||
4
.github/workflows/nightly.yml
vendored
4
.github/workflows/nightly.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Trunk Check
|
- name: Trunk Check
|
||||||
uses: trunk-io/trunk-action@v1
|
uses: trunk-io/trunk-action@v1
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
pull-requests: write # For trunk to create PRs
|
pull-requests: write # For trunk to create PRs
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Trunk Upgrade
|
- name: Trunk Upgrade
|
||||||
uses: trunk-io/trunk-action/upgrade@v1
|
uses: trunk-io/trunk-action/upgrade@v1
|
||||||
|
|||||||
4
.github/workflows/package_obs.yml
vendored
4
.github/workflows/package_obs.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
|||||||
needs: build-debian-src
|
needs: build-debian-src
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
path: meshtasticd
|
path: meshtasticd
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
id: version
|
id: version
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|||||||
4
.github/workflows/package_pio_deps.yml
vendored
4
.github/workflows/package_pio_deps.yml
vendored
@@ -24,14 +24,14 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/package_ppa.yml
vendored
4
.github/workflows/package_ppa.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
needs: build-debian-src
|
needs: build-debian-src
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
path: meshtasticd
|
path: meshtasticd
|
||||||
@@ -60,7 +60,7 @@ jobs:
|
|||||||
id: version
|
id: version
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|||||||
2
.github/workflows/pr_enforce_labels.yml
vendored
2
.github/workflows/pr_enforce_labels.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Check for PR labels
|
- name: Check for PR labels
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v8
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const labels = context.payload.pull_request.labels.map(label => label.name);
|
const labels = context.payload.pull_request.labels.map(label => label.name);
|
||||||
|
|||||||
238
.github/workflows/pr_tests.yml
vendored
Normal file
238
.github/workflows/pr_tests.yml
vendored
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
name: Tests
|
||||||
|
|
||||||
|
# DISABLED: Changed from automatic PR triggers to manual only
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
reason:
|
||||||
|
description: "Reason for manual test run"
|
||||||
|
required: false
|
||||||
|
default: "Manual test execution"
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: tests-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
actions: read
|
||||||
|
checks: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
native-tests:
|
||||||
|
name: "🧪 Native Tests"
|
||||||
|
if: github.repository == 'meshtastic/firmware'
|
||||||
|
uses: ./.github/workflows/test_native.yml
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
actions: read
|
||||||
|
checks: write
|
||||||
|
|
||||||
|
test-summary:
|
||||||
|
name: "📊 Test Results"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [native-tests]
|
||||||
|
if: always()
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
actions: read
|
||||||
|
checks: write
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Get release version string
|
||||||
|
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||||
|
id: version
|
||||||
|
|
||||||
|
- name: Download test artifacts
|
||||||
|
if: needs.native-tests.result != 'skipped'
|
||||||
|
uses: actions/download-artifact@v5
|
||||||
|
with:
|
||||||
|
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Parse test results and create detailed summary
|
||||||
|
id: test-results
|
||||||
|
run: |
|
||||||
|
echo "## 🧪 Test Results Summary" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
# Check overall job status first
|
||||||
|
if [[ "${{ needs.native-tests.result }}" == "success" ]]; then
|
||||||
|
echo "✅ **Overall Status**: PASSED" >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [[ "${{ needs.native-tests.result }}" == "failure" ]]; then
|
||||||
|
echo "❌ **Overall Status**: FAILED" >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [[ "${{ needs.native-tests.result }}" == "cancelled" ]]; then
|
||||||
|
echo "⏸️ **Overall Status**: CANCELLED" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Tests were cancelled before completion." >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "⚠️ **Overall Status**: SKIPPED" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Tests were skipped." >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
# Parse detailed test results if available
|
||||||
|
if [ -f "testreport.xml" ]; then
|
||||||
|
echo "### 🔍 Individual Test Results" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
python3 << 'EOF'
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import os
|
||||||
|
|
||||||
|
try:
|
||||||
|
tree = ET.parse('testreport.xml')
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
total_tests = 0
|
||||||
|
passed_tests = 0
|
||||||
|
failed_tests = 0
|
||||||
|
skipped_tests = 0
|
||||||
|
|
||||||
|
# Parse testsuite elements
|
||||||
|
for testsuite in root.findall('.//testsuite'):
|
||||||
|
suite_name = testsuite.get('name', 'Unknown')
|
||||||
|
suite_tests = int(testsuite.get('tests', '0'))
|
||||||
|
suite_failures = int(testsuite.get('failures', '0'))
|
||||||
|
suite_errors = int(testsuite.get('errors', '0'))
|
||||||
|
suite_skipped = int(testsuite.get('skipped', '0'))
|
||||||
|
|
||||||
|
total_tests += suite_tests
|
||||||
|
failed_tests += suite_failures + suite_errors
|
||||||
|
skipped_tests += suite_skipped
|
||||||
|
passed_tests += suite_tests - suite_failures - suite_errors - suite_skipped
|
||||||
|
|
||||||
|
if suite_tests > 0:
|
||||||
|
status = "✅" if (suite_failures + suite_errors) == 0 else "❌"
|
||||||
|
print(f"**{status} Test Suite: {suite_name}**")
|
||||||
|
print(f"- Total: {suite_tests}")
|
||||||
|
print(f"- Passed: ✅ {suite_tests - suite_failures - suite_errors - suite_skipped}")
|
||||||
|
print(f"- Failed: ❌ {suite_failures + suite_errors}")
|
||||||
|
if suite_skipped > 0:
|
||||||
|
print(f"- Skipped: ⏭️ {suite_skipped}")
|
||||||
|
print("")
|
||||||
|
|
||||||
|
# Show individual test results for failed suites
|
||||||
|
if suite_failures + suite_errors > 0:
|
||||||
|
print("**Failed Tests:**")
|
||||||
|
for testcase in testsuite.findall('testcase'):
|
||||||
|
test_name = testcase.get('name', 'Unknown')
|
||||||
|
failure = testcase.find('failure')
|
||||||
|
error = testcase.find('error')
|
||||||
|
|
||||||
|
if failure is not None:
|
||||||
|
msg = failure.get('message', 'Unknown error')[:100]
|
||||||
|
print(f"- ❌ `{test_name}`: {msg}")
|
||||||
|
elif error is not None:
|
||||||
|
msg = error.get('message', 'Unknown error')[:100]
|
||||||
|
print(f"- ❌ `{test_name}`: ERROR - {msg}")
|
||||||
|
print("")
|
||||||
|
else:
|
||||||
|
# Show passed tests for successful suites
|
||||||
|
passed_count = 0
|
||||||
|
for testcase in testsuite.findall('testcase'):
|
||||||
|
if testcase.find('failure') is None and testcase.find('error') is None:
|
||||||
|
if passed_count < 5: # Limit to first 5 to avoid spam
|
||||||
|
test_name = testcase.get('name', 'Unknown')
|
||||||
|
print(f"- ✅ `{test_name}`: PASSED")
|
||||||
|
passed_count += 1
|
||||||
|
if passed_count > 5:
|
||||||
|
print(f"- ... and {passed_count - 5} more tests passed")
|
||||||
|
print("")
|
||||||
|
|
||||||
|
# Summary statistics
|
||||||
|
print("### 📊 Test Statistics")
|
||||||
|
print(f"- **Total Tests**: {total_tests}")
|
||||||
|
print(f"- **Passed**: ✅ {passed_tests}")
|
||||||
|
print(f"- **Failed**: ❌ {failed_tests}")
|
||||||
|
if skipped_tests > 0:
|
||||||
|
print(f"- **Skipped**: ⏭️ {skipped_tests}")
|
||||||
|
|
||||||
|
if failed_tests > 0:
|
||||||
|
print(f"\n❌ **{failed_tests} tests failed out of {total_tests} total**")
|
||||||
|
else:
|
||||||
|
print(f"\n✅ **All {total_tests} tests passed!**")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error parsing test results: {e}")
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
echo "⚠️ **No detailed test report available**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Test artifacts may not have been generated properly." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "---" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "View detailed logs in the [Actions tab](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
- name: Comment test results on PR
|
||||||
|
if: github.event_name == 'pull_request' && needs.native-tests.result != 'skipped'
|
||||||
|
uses: actions/github-script@v8
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Read the step summary to use as PR comment
|
||||||
|
let testSummary = "## 🧪 Test Results Summary\n\n";
|
||||||
|
|
||||||
|
if ("${{ needs.native-tests.result }}" === "success") {
|
||||||
|
testSummary += "✅ **All tests passed!**\n\n";
|
||||||
|
} else if ("${{ needs.native-tests.result }}" === "failure") {
|
||||||
|
testSummary += "❌ **Some tests failed.**\n\n";
|
||||||
|
} else {
|
||||||
|
testSummary += "⚠️ **Tests did not complete normally.**\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
testSummary += `View detailed results: [Actions Run](${context.payload.repository.html_url}/actions/runs/${context.runId})\n\n`;
|
||||||
|
testSummary += "---\n";
|
||||||
|
testSummary += "*This comment will be automatically updated when new commits are pushed.*";
|
||||||
|
|
||||||
|
// Find existing comment
|
||||||
|
const comments = await github.rest.issues.listComments({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.issue.number
|
||||||
|
});
|
||||||
|
|
||||||
|
const botComment = comments.data.find(comment =>
|
||||||
|
comment.user.type === 'Bot' &&
|
||||||
|
comment.body.includes('🧪 Test Results Summary')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (botComment) {
|
||||||
|
// Update existing comment
|
||||||
|
await github.rest.issues.updateComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
comment_id: botComment.id,
|
||||||
|
body: testSummary
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Create new comment
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
body: testSummary
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Set overall status
|
||||||
|
run: |
|
||||||
|
if [[ "${{ needs.native-tests.result }}" == "success" ]]; then
|
||||||
|
echo "All tests passed! ✅"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "Some tests failed! ❌"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
4
.github/workflows/release_channels.yml
vendored
4
.github/workflows/release_channels.yml
vendored
@@ -60,10 +60,10 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/sec_sast_semgrep_cron.yml
vendored
2
.github/workflows/sec_sast_semgrep_cron.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# step 1
|
# step 1
|
||||||
- name: clone application source code
|
- name: clone application source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
# step 2
|
# step 2
|
||||||
- name: full scan
|
- name: full scan
|
||||||
|
|||||||
2
.github/workflows/sec_sast_semgrep_pull.yml
vendored
2
.github/workflows/sec_sast_semgrep_pull.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# step 1
|
# step 1
|
||||||
- name: clone application source code
|
- name: clone application source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/stale_bot.yml
vendored
2
.github/workflows/stale_bot.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Stale PR+Issues
|
- name: Stale PR+Issues
|
||||||
uses: actions/stale@v9.1.0
|
uses: actions/stale@v10.0.0
|
||||||
with:
|
with:
|
||||||
days-before-stale: 45
|
days-before-stale: 45
|
||||||
exempt-issue-labels: pinned,3.0
|
exempt-issue-labels: pinned,3.0
|
||||||
|
|||||||
10
.github/workflows/test_native.yml
vendored
10
.github/workflows/test_native.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
name: Native Simulator Tests
|
name: Native Simulator Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
name: Native PlatformIO Tests
|
name: Native PlatformIO Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
@@ -127,7 +127,7 @@ jobs:
|
|||||||
- platformio-tests
|
- platformio-tests
|
||||||
if: always()
|
if: always()
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
@@ -137,7 +137,7 @@ jobs:
|
|||||||
id: version
|
id: version
|
||||||
|
|
||||||
- name: Download test artifacts
|
- name: Download test artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
|
name: platformio-test-report-${{ steps.version.outputs.long }}.zip
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -150,7 +150,7 @@ jobs:
|
|||||||
reporter: java-junit
|
reporter: java-junit
|
||||||
|
|
||||||
- name: Download coverage artifacts
|
- name: Download coverage artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}.zip
|
pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}.zip
|
||||||
path: code-coverage-report
|
path: code-coverage-report
|
||||||
|
|||||||
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: test-runner
|
runs-on: test-runner
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
# - uses: actions/setup-python@v5
|
# - uses: actions/setup-python@v5
|
||||||
# with:
|
# with:
|
||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
pio upgrade
|
pio upgrade
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/trunk_annotate_pr.yml
vendored
2
.github/workflows/trunk_annotate_pr.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Trunk Check
|
- name: Trunk Check
|
||||||
uses: trunk-io/trunk-action@v1
|
uses: trunk-io/trunk-action@v1
|
||||||
|
|||||||
2
.github/workflows/trunk_check.yml
vendored
2
.github/workflows/trunk_check.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Trunk Check
|
- name: Trunk Check
|
||||||
uses: trunk-io/trunk-action@v1
|
uses: trunk-io/trunk-action@v1
|
||||||
|
|||||||
4
.github/workflows/trunk_format_pr.yml
vendored
4
.github/workflows/trunk_format_pr.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
git push
|
git push
|
||||||
|
|
||||||
- name: Comment on PR
|
- name: Comment on PR
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v8
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
|
|||||||
2
.github/workflows/update_protobufs.yml
vendored
2
.github/workflows/update_protobufs.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
version: 0.1
|
version: 0.1
|
||||||
cli:
|
cli:
|
||||||
version: 1.24.0
|
version: 1.25.0
|
||||||
plugins:
|
plugins:
|
||||||
sources:
|
sources:
|
||||||
- id: trunk
|
- id: trunk
|
||||||
ref: v1.7.1
|
ref: v1.7.2
|
||||||
uri: https://github.com/trunk-io/plugins
|
uri: https://github.com/trunk-io/plugins
|
||||||
lint:
|
lint:
|
||||||
enabled:
|
enabled:
|
||||||
- checkov@3.2.451
|
- checkov@3.2.469
|
||||||
- renovate@41.40.0
|
- renovate@41.94.0
|
||||||
- prettier@3.6.2
|
- prettier@3.6.2
|
||||||
- trufflehog@3.90.1
|
- trufflehog@3.90.5
|
||||||
- yamllint@1.37.1
|
- yamllint@1.37.1
|
||||||
- bandit@1.8.6
|
- bandit@1.8.6
|
||||||
- trivy@0.64.1
|
- trivy@0.66.0
|
||||||
- taplo@0.9.3
|
- taplo@0.10.0
|
||||||
- ruff@0.12.4
|
- ruff@0.12.11
|
||||||
- isort@6.0.1
|
- isort@6.0.1
|
||||||
- markdownlint@0.45.0
|
- markdownlint@0.45.0
|
||||||
- oxipng@9.1.5
|
- oxipng@9.1.5
|
||||||
- svgo@4.0.0
|
- svgo@4.0.0
|
||||||
- actionlint@1.7.7
|
- actionlint@1.7.7
|
||||||
- flake8@7.3.0
|
- flake8@7.3.0
|
||||||
- hadolint@2.12.1-beta
|
- hadolint@2.13.1
|
||||||
- shfmt@3.6.0
|
- shfmt@3.6.0
|
||||||
- shellcheck@0.10.0
|
- shellcheck@0.11.0
|
||||||
- black@25.1.0
|
- black@25.1.0
|
||||||
- git-diff-check
|
- git-diff-check
|
||||||
- gitleaks@8.28.0
|
- gitleaks@8.28.0
|
||||||
|
|||||||
10
Dockerfile
10
Dockerfile
@@ -3,7 +3,7 @@
|
|||||||
# trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
|
# trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
|
||||||
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
|
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
|
||||||
|
|
||||||
FROM python:3.13-bookworm AS builder
|
FROM python:3.13-slim-trixie AS builder
|
||||||
ARG PIO_ENV=native
|
ARG PIO_ENV=native
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
ENV TZ=Etc/UTC
|
ENV TZ=Etc/UTC
|
||||||
@@ -36,7 +36,7 @@ RUN curl -L "https://github.com/meshtastic/web/releases/download/v$(cat /tmp/fir
|
|||||||
|
|
||||||
##### PRODUCTION BUILD #############
|
##### PRODUCTION BUILD #############
|
||||||
|
|
||||||
FROM debian:bookworm-slim
|
FROM debian:trixie-slim
|
||||||
LABEL org.opencontainers.image.title="Meshtastic" \
|
LABEL org.opencontainers.image.title="Meshtastic" \
|
||||||
org.opencontainers.image.description="Debian Meshtastic daemon and web interface" \
|
org.opencontainers.image.description="Debian Meshtastic daemon and web interface" \
|
||||||
org.opencontainers.image.url="https://meshtastic.org" \
|
org.opencontainers.image.url="https://meshtastic.org" \
|
||||||
@@ -51,8 +51,8 @@ ENV TZ=Etc/UTC
|
|||||||
USER root
|
USER root
|
||||||
|
|
||||||
RUN apt-get update && apt-get --no-install-recommends -y install \
|
RUN apt-get update && apt-get --no-install-recommends -y install \
|
||||||
libc-bin libc6 libgpiod2 libyaml-cpp0.7 libi2c0 libuv1 libusb-1.0-0-dev \
|
libc-bin libc6 libgpiod3 libyaml-cpp0.8 libi2c0 libuv1t64 libusb-1.0-0-dev \
|
||||||
liborcania2.3 libulfius2.7 libssl3 \
|
liborcania2.3 libulfius2.7t64 libssl3t64 \
|
||||||
libx11-6 libinput10 libxkbcommon-x11-0 \
|
libx11-6 libinput10 libxkbcommon-x11-0 \
|
||||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||||
&& mkdir -p /var/lib/meshtasticd \
|
&& mkdir -p /var/lib/meshtasticd \
|
||||||
@@ -61,7 +61,7 @@ RUN apt-get update && apt-get --no-install-recommends -y install \
|
|||||||
|
|
||||||
# Fetch compiled binary from the builder
|
# Fetch compiled binary from the builder
|
||||||
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/bin/
|
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/bin/
|
||||||
COPY --from=builder /tmp/web /usr/share/meshtasticd/
|
COPY --from=builder /tmp/web /usr/share/meshtasticd/web/
|
||||||
# Copy config templates
|
# Copy config templates
|
||||||
COPY ./bin/config.d /etc/meshtasticd/available.d
|
COPY ./bin/config.d /etc/meshtasticd/available.d
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ build_flags =
|
|||||||
-DMESHTASTIC_EXCLUDE_PAXCOUNTER=1
|
-DMESHTASTIC_EXCLUDE_PAXCOUNTER=1
|
||||||
|
|
||||||
build_src_filter =
|
build_src_filter =
|
||||||
${arduino_base.build_src_filter} -<platform/esp32/> -<platform/stm32wl> -<nimble/> -<mesh/wifi/> -<mesh/api/> -<mesh/http/> -<modules/esp32> -<platform/rp2xx0> -<mesh/eth/> -<mesh/raspihttp>
|
${arduino_base.build_src_filter} -<platform/esp32/> -<platform/stm32wl> -<nimble/> -<mesh/wifi/> -<mesh/api/> -<mesh/http/> -<modules/esp32> -<platform/rp2xx0> -<mesh/eth/> -<mesh/raspihttp> -<serialization/>
|
||||||
|
|
||||||
lib_deps=
|
lib_deps=
|
||||||
${arduino_base.lib_deps}
|
${arduino_base.lib_deps}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
[portduino_base]
|
[portduino_base]
|
||||||
platform =
|
platform =
|
||||||
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
|
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
|
||||||
https://github.com/meshtastic/platform-native/archive/6cb7a455b440dd0738e8ed74a18136ed5cf7ea63.zip
|
https://github.com/meshtastic/platform-native/archive/c490bcd019e0658404088a61b96e653c9da22c45.zip
|
||||||
framework = arduino
|
framework = arduino
|
||||||
|
|
||||||
build_src_filter =
|
build_src_filter =
|
||||||
@@ -17,7 +17,6 @@ build_src_filter =
|
|||||||
+<mesh/raspihttp/>
|
+<mesh/raspihttp/>
|
||||||
-<mesh/eth/>
|
-<mesh/eth/>
|
||||||
-<modules/esp32>
|
-<modules/esp32>
|
||||||
+<../variants/portduino>
|
|
||||||
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${env.lib_deps}
|
${env.lib_deps}
|
||||||
@@ -32,9 +31,12 @@ lib_deps =
|
|||||||
https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip
|
https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip
|
||||||
# renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library
|
# renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library
|
||||||
adafruit/Adafruit seesaw Library@1.7.9
|
adafruit/Adafruit seesaw Library@1.7.9
|
||||||
|
# renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main
|
||||||
|
https://github.com/RAKWireless/RAK12034-BMX160/archive/dcead07ffa267d3c906e9ca4a1330ab989e957e2.zip
|
||||||
|
|
||||||
build_flags =
|
build_flags =
|
||||||
${arduino_base.build_flags}
|
${arduino_base.build_flags}
|
||||||
|
-D ARCH_PORTDUINO
|
||||||
-fPIC
|
-fPIC
|
||||||
-Isrc/platform/portduino
|
-Isrc/platform/portduino
|
||||||
-DRADIOLIB_EEPROM_UNSUPPORTED
|
-DRADIOLIB_EEPROM_UNSUPPORTED
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ lib_deps =
|
|||||||
${radiolib_base.lib_deps}
|
${radiolib_base.lib_deps}
|
||||||
|
|
||||||
# renovate: datasource=git-refs depName=caveman99-stm32-Crypto packageName=https://github.com/caveman99/Crypto gitBranch=main
|
# renovate: datasource=git-refs depName=caveman99-stm32-Crypto packageName=https://github.com/caveman99/Crypto gitBranch=main
|
||||||
https://github.com/caveman99/Crypto/archive/eae9c768054118a9399690f8af202853d1ae8516.zip
|
https://github.com/caveman99/Crypto/archive/1aa30eb536bd52a576fde6dfa393bf7349cf102d.zip
|
||||||
|
|
||||||
lib_ignore =
|
lib_ignore =
|
||||||
OneButton
|
OneButton
|
||||||
|
|||||||
@@ -9,13 +9,4 @@ Lora:
|
|||||||
DIO3_TCXO_VOLTAGE: true
|
DIO3_TCXO_VOLTAGE: true
|
||||||
DIO2_AS_RF_SWITCH: true
|
DIO2_AS_RF_SWITCH: true
|
||||||
spidev: spidev0.0
|
spidev: spidev0.0
|
||||||
# CS: 8
|
# CS: 8
|
||||||
|
|
||||||
|
|
||||||
### RAK13300in Slot 2 pins
|
|
||||||
# IRQ: 18 #IO6
|
|
||||||
# Reset: 24 # IO4
|
|
||||||
# Busy: 19 # IO5
|
|
||||||
# # Ant_sw: 23 # IO3
|
|
||||||
# spidev: spidev0.1
|
|
||||||
# # CS: 7
|
|
||||||
8
bin/config.d/lora-RAK6421-13300-slot2.yaml
Normal file
8
bin/config.d/lora-RAK6421-13300-slot2.yaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Lora:
|
||||||
|
### RAK13300in Slot 2 pins
|
||||||
|
IRQ: 18 #IO6
|
||||||
|
Reset: 24 # IO4
|
||||||
|
Busy: 19 # IO5
|
||||||
|
# Ant_sw: 23 # IO3
|
||||||
|
spidev: spidev0.1
|
||||||
|
# CS: 7
|
||||||
@@ -7,6 +7,7 @@ SET "DEBUG=0"
|
|||||||
SET "PYTHON="
|
SET "PYTHON="
|
||||||
SET "TFT_BUILD=0"
|
SET "TFT_BUILD=0"
|
||||||
SET "BIGDB8=0"
|
SET "BIGDB8=0"
|
||||||
|
SET "MUIDB8=0"
|
||||||
SET "BIGDB16=0"
|
SET "BIGDB16=0"
|
||||||
SET "ESPTOOL_BAUD=115200"
|
SET "ESPTOOL_BAUD=115200"
|
||||||
SET "ESPTOOL_CMD="
|
SET "ESPTOOL_CMD="
|
||||||
@@ -14,11 +15,12 @@ SET "LOGCOUNTER=0"
|
|||||||
SET "BPS_RESET=0"
|
SET "BPS_RESET=0"
|
||||||
|
|
||||||
@REM FIXME: Determine mcu from PlatformIO variant, this is unmaintainable.
|
@REM FIXME: Determine mcu from PlatformIO variant, this is unmaintainable.
|
||||||
SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone"
|
SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone t-eth-elite tlora-pager mesh-tab dreamcatcher ESP32-S3-Pico seeed-sensecap-indicator heltec_capsule_sensor_v3 vision-master icarus tracksenger elecrow-adv"
|
||||||
SET "C3=esp32c3"
|
SET "C3=esp32c3"
|
||||||
@REM FIXME: Determine flash size from PlatformIO variant, this is unmaintainable.
|
@REM FIXME: Determine flash size from PlatformIO variant, this is unmaintainable.
|
||||||
SET "BIGDB_8MB=picomputer-s3 unphone seeed-sensecap-indicator crowpanel-esp32s3 heltec_capsule_sensor_v3 heltec-v3 heltec-vision-master-e213 heltec-vision-master-e290 heltec-vision-master-t190 heltec-wireless-paper heltec-wireless-tracker heltec-wsl-v3 icarus seeed-xiao-s3 tbeam-s3-core tracksenger"
|
SET "BIGDB_8MB=crowpanel-esp32s3 heltec_capsule_sensor_v3 heltec-v3 heltec-vision-master-e213 heltec-vision-master-e290 heltec-vision-master-t190 heltec-wireless-paper heltec-wireless-tracker heltec-wsl-v3 icarus seeed-xiao-s3 tbeam-s3-core tracksenger"
|
||||||
SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite t-watch-s3"
|
SET "MUIDB_8MB=picomputer-s3 unphone seeed-sensecap-indicator"
|
||||||
|
SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite tlora-pager t-watch-s3 elecrow-adv"
|
||||||
|
|
||||||
GOTO getopts
|
GOTO getopts
|
||||||
:help
|
:help
|
||||||
@@ -100,7 +102,6 @@ IF NOT "!FILENAME:update=!"=="!FILENAME!" (
|
|||||||
)
|
)
|
||||||
|
|
||||||
:skip-filename
|
:skip-filename
|
||||||
SET "ESPTOOL_BAUD=1200"
|
|
||||||
|
|
||||||
CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
|
CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
|
||||||
IF NOT "__%PYTHON%__"=="____" (
|
IF NOT "__%PYTHON%__"=="____" (
|
||||||
@@ -120,11 +121,10 @@ IF NOT "__%PYTHON%__"=="____" (
|
|||||||
|
|
||||||
CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..."
|
CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..."
|
||||||
!ESPTOOL_CMD! >nul 2>&1
|
!ESPTOOL_CMD! >nul 2>&1
|
||||||
IF %ERRORLEVEL% GEQ 2 (
|
IF %ERRORLEVEL% EQU 9009 (
|
||||||
@REM esptool exits with code 1 if help is displayed.
|
@REM 9009 = command not found on Windows
|
||||||
CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!"
|
CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!"
|
||||||
EXIT /B 1
|
EXIT /B 1
|
||||||
GOTO eof
|
|
||||||
)
|
)
|
||||||
IF %DEBUG% EQU 1 (
|
IF %DEBUG% EQU 1 (
|
||||||
CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps."
|
CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps."
|
||||||
@@ -142,7 +142,7 @@ CALL :LOG_MESSAGE INFO "Using esptool baud: !ESPTOOL_BAUD!."
|
|||||||
|
|
||||||
IF %BPS_RESET% EQU 1 (
|
IF %BPS_RESET% EQU 1 (
|
||||||
@REM Attempt to change mode via 1200bps Reset.
|
@REM Attempt to change mode via 1200bps Reset.
|
||||||
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! --after no_reset read_flash_status
|
CALL :RUN_ESPTOOL 1200 --after no_reset read_flash_status
|
||||||
GOTO eof
|
GOTO eof
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -164,6 +164,15 @@ FOR %%a IN (%BIGDB_8MB%) DO (
|
|||||||
)
|
)
|
||||||
:end_loop_bigdb_8mb
|
:end_loop_bigdb_8mb
|
||||||
|
|
||||||
|
FOR %%a IN (%MUIDB_8MB%) DO (
|
||||||
|
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
|
||||||
|
@REM We are working with any of %MUIDB_8MB%.
|
||||||
|
SET "MUIDB8=1"
|
||||||
|
GOTO end_loop_muidb_8mb
|
||||||
|
)
|
||||||
|
)
|
||||||
|
:end_loop_muidb_8mb
|
||||||
|
|
||||||
FOR %%a IN (%BIGDB_16MB%) DO (
|
FOR %%a IN (%BIGDB_16MB%) DO (
|
||||||
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
|
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
|
||||||
@REM We are working with any of %BIGDB_16MB%.
|
@REM We are working with any of %BIGDB_16MB%.
|
||||||
@@ -174,6 +183,7 @@ FOR %%a IN (%BIGDB_16MB%) DO (
|
|||||||
:end_loop_bigdb_16mb
|
:end_loop_bigdb_16mb
|
||||||
|
|
||||||
IF %BIGDB8% EQU 1 CALL :LOG_MESSAGE INFO "BigDB 8mb partition selected."
|
IF %BIGDB8% EQU 1 CALL :LOG_MESSAGE INFO "BigDB 8mb partition selected."
|
||||||
|
IF %MUIDB8% EQU 1 CALL :LOG_MESSAGE INFO "MUIDB 8mb partition selected."
|
||||||
IF %BIGDB16% EQU 1 CALL :LOG_MESSAGE INFO "BigDB 16mb partition selected."
|
IF %BIGDB16% EQU 1 CALL :LOG_MESSAGE INFO "BigDB 16mb partition selected."
|
||||||
|
|
||||||
@REM Extract BASENAME from %FILENAME% for later use.
|
@REM Extract BASENAME from %FILENAME% for later use.
|
||||||
@@ -218,6 +228,12 @@ IF %BIGDB8% EQU 1 (
|
|||||||
SET "SPIFFS_OFFSET=0x670000"
|
SET "SPIFFS_OFFSET=0x670000"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@REM Offsets for MUIDB 8mb.
|
||||||
|
IF %MUIDB8% EQU 1 (
|
||||||
|
SET "OTA_OFFSET=0x5D0000"
|
||||||
|
SET "SPIFFS_OFFSET=0x670000"
|
||||||
|
)
|
||||||
|
|
||||||
@REM Offsets for BigDB 16mb.
|
@REM Offsets for BigDB 16mb.
|
||||||
IF %BIGDB16% EQU 1 (
|
IF %BIGDB16% EQU 1 (
|
||||||
SET "OTA_OFFSET=0x650000"
|
SET "OTA_OFFSET=0x650000"
|
||||||
|
|||||||
@@ -5,38 +5,43 @@ BPS_RESET=false
|
|||||||
TFT_BUILD=false
|
TFT_BUILD=false
|
||||||
MCU=""
|
MCU=""
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
RESET_BAUD=1200
|
||||||
|
FIRMWARE_OFFSET=0x00
|
||||||
|
|
||||||
# Variant groups
|
# Variant groups
|
||||||
BIGDB_8MB=(
|
BIGDB_8MB=(
|
||||||
"picomputer-s3"
|
"crowpanel-esp32s3"
|
||||||
"unphone"
|
"heltec_capsule_sensor_v3"
|
||||||
"seeed-sensecap-indicator"
|
"heltec-v3"
|
||||||
"crowpanel-esp32s3"
|
"heltec-vision-master-e213"
|
||||||
"heltec_capsule_sensor_v3"
|
"heltec-vision-master-e290"
|
||||||
"heltec-v3"
|
"heltec-vision-master-t190"
|
||||||
"heltec-vision-master-e213"
|
"heltec-wireless-paper"
|
||||||
"heltec-vision-master-e290"
|
"heltec-wireless-tracker"
|
||||||
"heltec-vision-master-t190"
|
"heltec-wsl-v3"
|
||||||
"heltec-wireless-paper"
|
"icarus"
|
||||||
"heltec-wireless-tracker"
|
"seeed-xiao-s3"
|
||||||
"heltec-wsl-v3"
|
"tbeam-s3-core"
|
||||||
"icarus"
|
"tracksenger"
|
||||||
"seeed-xiao-s3"
|
)
|
||||||
"tbeam-s3-core"
|
MUIDB_8MB=(
|
||||||
"tracksenger"
|
"picomputer-s3"
|
||||||
|
"unphone"
|
||||||
|
"seeed-sensecap-indicator"
|
||||||
)
|
)
|
||||||
BIGDB_16MB=(
|
BIGDB_16MB=(
|
||||||
"t-deck"
|
"t-deck"
|
||||||
"mesh-tab"
|
"mesh-tab"
|
||||||
"t-energy-s3"
|
"t-energy-s3"
|
||||||
"dreamcatcher"
|
"dreamcatcher"
|
||||||
"ESP32-S3-Pico"
|
"ESP32-S3-Pico"
|
||||||
"m5stack-cores3"
|
"m5stack-cores3"
|
||||||
"station-g2"
|
"station-g2"
|
||||||
"t-eth-elite"
|
"t-eth-elite"
|
||||||
|
"tlora-pager"
|
||||||
"t-watch-s3"
|
"t-watch-s3"
|
||||||
"elecrow-adv-35-tft"
|
"elecrow-adv"
|
||||||
"elecrow-adv-24-28-tft"
|
|
||||||
"elecrow-adv1-43-50-70-tft"
|
|
||||||
)
|
)
|
||||||
S3_VARIANTS=(
|
S3_VARIANTS=(
|
||||||
"s3"
|
"s3"
|
||||||
@@ -47,6 +52,7 @@ S3_VARIANTS=(
|
|||||||
"station-g2"
|
"station-g2"
|
||||||
"unphone"
|
"unphone"
|
||||||
"t-eth-elite"
|
"t-eth-elite"
|
||||||
|
"tlora-pager"
|
||||||
"mesh-tab"
|
"mesh-tab"
|
||||||
"dreamcatcher"
|
"dreamcatcher"
|
||||||
"ESP32-S3-Pico"
|
"ESP32-S3-Pico"
|
||||||
@@ -106,8 +112,8 @@ while [ $# -gt 0 ]; do
|
|||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--1200bps-reset)
|
--1200bps-reset)
|
||||||
BPS_RESET=true
|
BPS_RESET=true
|
||||||
;;
|
;;
|
||||||
--) # Stop parsing options
|
--) # Stop parsing options
|
||||||
shift
|
shift
|
||||||
break
|
break
|
||||||
@@ -121,7 +127,7 @@ while [ $# -gt 0 ]; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
if [[ $BPS_RESET == true ]]; then
|
if [[ $BPS_RESET == true ]]; then
|
||||||
$ESPTOOL_CMD --baud 1200 --after no_reset read_flash_status
|
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -158,6 +164,13 @@ if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
for variant in "${MUIDB_8MB[@]}"; do
|
||||||
|
if [ -z "${FILENAME##*"$variant"*}" ]; then
|
||||||
|
OFFSET=0x670000
|
||||||
|
OTA_OFFSET=0x5D0000
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
# littlefs* offset for BigDB 16mb and OTA OFFSET.
|
# littlefs* offset for BigDB 16mb and OTA OFFSET.
|
||||||
for variant in "${BIGDB_16MB[@]}"; do
|
for variant in "${BIGDB_16MB[@]}"; do
|
||||||
if [ -z "${FILENAME##*"$variant"*}" ]; then
|
if [ -z "${FILENAME##*"$variant"*}" ]; then
|
||||||
@@ -201,8 +214,8 @@ if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
|
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
|
||||||
$ESPTOOL_CMD erase_flash
|
$ESPTOOL_CMD erase-flash
|
||||||
$ESPTOOL_CMD write_flash 0x00 "${FILENAME}"
|
$ESPTOOL_CMD write-flash $FIRMWARE_OFFSET "${FILENAME}"
|
||||||
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
|
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
|
||||||
$ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}"
|
$ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}"
|
||||||
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
|
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ SET "SCRIPT_NAME=%~nx0"
|
|||||||
SET "DEBUG=0"
|
SET "DEBUG=0"
|
||||||
SET "PYTHON="
|
SET "PYTHON="
|
||||||
SET "ESPTOOL_BAUD=115200"
|
SET "ESPTOOL_BAUD=115200"
|
||||||
|
SET "RESET_BAUD=1200"
|
||||||
|
SET "UPDATE_OFFSET=0x10000"
|
||||||
SET "ESPTOOL_CMD="
|
SET "ESPTOOL_CMD="
|
||||||
SET "LOGCOUNTER=0"
|
SET "LOGCOUNTER=0"
|
||||||
SET "CHANGE_MODE=0"
|
SET "CHANGE_MODE=0"
|
||||||
@@ -85,14 +87,13 @@ IF "!FILENAME:update=!"=="!FILENAME!" (
|
|||||||
)
|
)
|
||||||
|
|
||||||
:skip-filename
|
:skip-filename
|
||||||
SET "ESPTOOL_BAUD=1200"
|
|
||||||
|
|
||||||
CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
|
CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
|
||||||
IF NOT "__%PYTHON%__"=="____" (
|
IF NOT "__%PYTHON%__"=="____" (
|
||||||
SET "ESPTOOL_CMD=!PYTHON! -m esptool"
|
SET "ESPTOOL_CMD=""!PYTHON!"" -m esptool"
|
||||||
CALL :LOG_MESSAGE DEBUG "Python interpreter supplied."
|
CALL :LOG_MESSAGE DEBUG "Python interpreter supplied."
|
||||||
) ELSE (
|
) ELSE (
|
||||||
CALL :LOG_MESSAGE DEBUG "Python interpreter NOT supplied. Looking for esptool...
|
CALL :LOG_MESSAGE DEBUG "Python interpreter NOT supplied. Looking for esptool..."
|
||||||
WHERE esptool >nul 2>&1
|
WHERE esptool >nul 2>&1
|
||||||
IF %ERRORLEVEL% EQU 0 (
|
IF %ERRORLEVEL% EQU 0 (
|
||||||
@REM WHERE exits with code 0 if esptool is found.
|
@REM WHERE exits with code 0 if esptool is found.
|
||||||
@@ -105,11 +106,11 @@ IF NOT "__%PYTHON%__"=="____" (
|
|||||||
|
|
||||||
CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..."
|
CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..."
|
||||||
!ESPTOOL_CMD! >nul 2>&1
|
!ESPTOOL_CMD! >nul 2>&1
|
||||||
IF %ERRORLEVEL% GEQ 2 (
|
CALL :LOG_MESSAGE DEBUG "esptool exit code: %ERRORLEVEL%"
|
||||||
@REM esptool exits with code 1 if help is displayed.
|
IF %ERRORLEVEL% EQU 9009 (
|
||||||
|
@REM 9009 = command not found on Windows
|
||||||
CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!"
|
CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!"
|
||||||
EXIT /B 1
|
EXIT /B 1
|
||||||
GOTO eof
|
|
||||||
)
|
)
|
||||||
IF %DEBUG% EQU 1 (
|
IF %DEBUG% EQU 1 (
|
||||||
CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps."
|
CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps."
|
||||||
@@ -127,13 +128,13 @@ CALL :LOG_MESSAGE INFO "Using esptool baud: !ESPTOOL_BAUD!."
|
|||||||
|
|
||||||
IF %CHANGE_MODE% EQU 1 (
|
IF %CHANGE_MODE% EQU 1 (
|
||||||
@REM Attempt to change mode via 1200bps Reset.
|
@REM Attempt to change mode via 1200bps Reset.
|
||||||
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! --after no_reset read_flash_status
|
CALL :RUN_ESPTOOL !RESET_BAUD! --after no_reset read_flash_status
|
||||||
GOTO eof
|
GOTO eof
|
||||||
)
|
)
|
||||||
|
|
||||||
@REM Flashing operations.
|
@REM Flashing operations.
|
||||||
CALL :LOG_MESSAGE INFO "Trying to flash update "!FILENAME!" at OFFSET 0x10000..."
|
CALL :LOG_MESSAGE INFO "Trying to flash update "!FILENAME!" at OFFSET !UPDATE_OFFSET!..."
|
||||||
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash 0x10000 "!FILENAME!" || GOTO eof
|
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write-flash !UPDATE_OFFSET! "!FILENAME!" || GOTO eof
|
||||||
|
|
||||||
CALL :LOG_MESSAGE INFO "Script complete!."
|
CALL :LOG_MESSAGE INFO "Script complete!."
|
||||||
|
|
||||||
@@ -145,9 +146,9 @@ EXIT /B %ERRORLEVEL%
|
|||||||
:RUN_ESPTOOL
|
:RUN_ESPTOOL
|
||||||
@REM Subroutine used to run ESPTOOL_CMD with arguments.
|
@REM Subroutine used to run ESPTOOL_CMD with arguments.
|
||||||
@REM Also handles %ERRORLEVEL%.
|
@REM Also handles %ERRORLEVEL%.
|
||||||
@REM CALL :RUN_ESPTOOL [Baud] [erase_flash|write_flash] [OFFSET] [Filename]
|
@REM CALL :RUN_ESPTOOL [Baud] [erase-flash|write-flash] [OFFSET] [Filename]
|
||||||
@REM.
|
@REM.
|
||||||
@REM Example:: CALL :RUN_ESPTOOL 115200 write_flash 0x10000 "firmwarefile.bin"
|
@REM Example:: CALL :RUN_ESPTOOL 115200 write-flash 0x10000 "firmwarefile.bin"
|
||||||
IF %DEBUG% EQU 1 CALL :LOG_MESSAGE DEBUG "About to run command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
|
IF %DEBUG% EQU 1 CALL :LOG_MESSAGE DEBUG "About to run command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
|
||||||
CALL :RESET_ERROR
|
CALL :RESET_ERROR
|
||||||
!ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4
|
!ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4
|
||||||
|
|||||||
@@ -3,6 +3,11 @@
|
|||||||
PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
|
PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
|
||||||
CHANGE_MODE=false
|
CHANGE_MODE=false
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
FLASH_BAUD=115200
|
||||||
|
RESET_BAUD=1200
|
||||||
|
UPDATE_OFFSET=0x10000
|
||||||
|
|
||||||
# Determine the correct esptool command to use
|
# Determine the correct esptool command to use
|
||||||
if "$PYTHON" -m esptool version >/dev/null 2>&1; then
|
if "$PYTHON" -m esptool version >/dev/null 2>&1; then
|
||||||
ESPTOOL_CMD="$PYTHON -m esptool"
|
ESPTOOL_CMD="$PYTHON -m esptool"
|
||||||
@@ -64,7 +69,7 @@ done
|
|||||||
shift "$((OPTIND-1))"
|
shift "$((OPTIND-1))"
|
||||||
|
|
||||||
if [ "$CHANGE_MODE" = true ]; then
|
if [ "$CHANGE_MODE" = true ]; then
|
||||||
$ESPTOOL_CMD --baud 1200 --after no_reset read_flash_status
|
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -75,7 +80,7 @@ fi
|
|||||||
|
|
||||||
if [ -f "${FILENAME}" ] && [ -z "${FILENAME##*"update"*}" ]; then
|
if [ -f "${FILENAME}" ] && [ -z "${FILENAME##*"update"*}" ]; then
|
||||||
echo "Trying to flash update ${FILENAME}"
|
echo "Trying to flash update ${FILENAME}"
|
||||||
$ESPTOOL_CMD --baud 115200 write_flash 0x10000 "${FILENAME}"
|
$ESPTOOL_CMD --baud $FLASH_BAUD write-flash $UPDATE_OFFSET "${FILENAME}"
|
||||||
else
|
else
|
||||||
show_help
|
show_help
|
||||||
echo "Invalid file: ${FILENAME}"
|
echo "Invalid file: ${FILENAME}"
|
||||||
|
|||||||
@@ -87,6 +87,18 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="2.7.9" date="2025-09-03">
|
||||||
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.9</url>
|
||||||
|
</release>
|
||||||
|
<release version="2.7.8" date="2025-08-30">
|
||||||
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.8</url>
|
||||||
|
</release>
|
||||||
|
<release version="2.7.7" date="2025-08-28">
|
||||||
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.7</url>
|
||||||
|
</release>
|
||||||
|
<release version="2.7.6" date="2025-08-12">
|
||||||
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.6</url>
|
||||||
|
</release>
|
||||||
<release version="2.7.5" date="2025-08-09">
|
<release version="2.7.5" date="2025-08-09">
|
||||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.5</url>
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.5</url>
|
||||||
</release>
|
</release>
|
||||||
|
|||||||
54
boards/heltec_mesh_solar.json
Normal file
54
boards/heltec_mesh_solar.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"arduino": {
|
||||||
|
"ldscript": "nrf52840_s140_v6.ld"
|
||||||
|
},
|
||||||
|
"core": "nRF5",
|
||||||
|
"cpu": "cortex-m4",
|
||||||
|
"extra_flags": "-DNRF52840_XXAA",
|
||||||
|
"f_cpu": "64000000L",
|
||||||
|
"hwids": [
|
||||||
|
["0x239A", "0x4405"],
|
||||||
|
["0x239A", "0x0029"],
|
||||||
|
["0x239A", "0x002A"],
|
||||||
|
["0x239A", "0x0071"]
|
||||||
|
],
|
||||||
|
"usb_product": "HT-n5262",
|
||||||
|
"mcu": "nrf52840",
|
||||||
|
"variant": "heltec_mesh_solar",
|
||||||
|
"variants_dir": "variants",
|
||||||
|
"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",
|
||||||
|
"onboard_tools": ["jlink"],
|
||||||
|
"svd_path": "nrf52840.svd",
|
||||||
|
"openocd_target": "nrf52840-mdk-rs"
|
||||||
|
},
|
||||||
|
"frameworks": ["arduino"],
|
||||||
|
"name": "Heltec nrf (Adafruit BSP)",
|
||||||
|
"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": "https://heltec.org/project/meshsolar/",
|
||||||
|
"vendor": "Heltec"
|
||||||
|
}
|
||||||
52
boards/meshtiny.json
Normal file
52
boards/meshtiny.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": "MeshTiny",
|
||||||
|
"mcu": "nrf52840",
|
||||||
|
"variant": "meshtiny",
|
||||||
|
"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": "MeshTiny",
|
||||||
|
"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": "https://github.com/meshtastic/firmware",
|
||||||
|
"vendor": "MTools Tec"
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"arduino": {
|
"arduino": {
|
||||||
"ldscript": "esp32s3_out.ld",
|
"ldscript": "esp32s3_out.ld",
|
||||||
"partitions": "default_8MB.csv",
|
"partitions": "partition-table-8MB.csv",
|
||||||
"memory_type": "qio_opi"
|
"memory_type": "qio_opi"
|
||||||
},
|
},
|
||||||
"core": "esp32",
|
"core": "esp32",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"arduino": {
|
"arduino": {
|
||||||
"ldscript": "esp32s3_out.ld",
|
"ldscript": "esp32s3_out.ld",
|
||||||
"memory_type": "qio_opi",
|
"memory_type": "qio_opi",
|
||||||
"partitions": "default_8MB.csv"
|
"partitions": "partition-table-8MB.csv"
|
||||||
},
|
},
|
||||||
"core": "esp32",
|
"core": "esp32",
|
||||||
"extra_flags": [
|
"extra_flags": [
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
},
|
},
|
||||||
"core": "stm32",
|
"core": "stm32",
|
||||||
"cpu": "cortex-m4",
|
"cpu": "cortex-m4",
|
||||||
"extra_flags": "-DSTM32WLxx -DSTM32WLE5xx -DARDUINO_GENERIC_WLE5CCUX",
|
"extra_flags": "-DSTM32WLxx -DSTM32WLE5xx -DARDUINO_RAK3172_MODULE",
|
||||||
"f_cpu": "48000000L",
|
"f_cpu": "48000000L",
|
||||||
"mcu": "stm32wle5ccu",
|
"mcu": "stm32wle5ccu",
|
||||||
"variant": "STM32WLxx/WL54CCU_WL55CCU_WLE4C(8-B-C)U_WLE5C(8-B-C)U",
|
"variant": "STM32WLxx/WL54CCU_WL55CCU_WLE4C(8-B-C)U_WLE5C(8-B-C)U",
|
||||||
|
|||||||
14
debian/changelog
vendored
14
debian/changelog
vendored
@@ -1,4 +1,4 @@
|
|||||||
meshtasticd (2.7.5.0) UNRELEASED; urgency=medium
|
meshtasticd (2.7.9.0) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
[ Austin Lane ]
|
[ Austin Lane ]
|
||||||
* Initial packaging
|
* Initial packaging
|
||||||
@@ -37,4 +37,14 @@ meshtasticd (2.7.5.0) UNRELEASED; urgency=medium
|
|||||||
[ ]
|
[ ]
|
||||||
* GitHub Actions Automatic version bump
|
* GitHub Actions Automatic version bump
|
||||||
|
|
||||||
-- <github-actions[bot]@users.noreply.github.com> Sat, 09 Aug 2025 12:46:53 +0000
|
[ ]
|
||||||
|
* 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> Wed, 03 Sep 2025 23:39:17 +0000
|
||||||
|
|||||||
7
partition-table-8MB.csv
Normal file
7
partition-table-8MB.csv
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# This is a layout for 8MB of flash for MUI devices
|
||||||
|
# Name, Type, SubType, Offset, Size, Flags
|
||||||
|
nvs, data, nvs, 0x9000, 0x5000,
|
||||||
|
otadata, data, ota, 0xe000, 0x2000,
|
||||||
|
app0, app, ota_0, 0x10000, 0x5C0000,
|
||||||
|
flashApp, app, ota_1, 0x5D0000,0x0A0000,
|
||||||
|
spiffs, data, spiffs, 0x670000,0x180000
|
||||||
|
@@ -60,9 +60,9 @@ monitor_speed = 115200
|
|||||||
monitor_filters = direct
|
monitor_filters = direct
|
||||||
lib_deps =
|
lib_deps =
|
||||||
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
|
||||||
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/0119501e9983bd894830b02f545c377ee08d66fe.zip
|
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/9573abb64dc9c94f3051348f2bf4fc5cedf03c22.zip
|
||||||
# renovate: datasource=custom.pio depName=OneButton packageName=mathertel/library/OneButton
|
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
|
||||||
mathertel/OneButton@2.6.1
|
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
|
||||||
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
|
||||||
https://github.com/meshtastic/arduino-fsm/archive/7db3702bf0cfe97b783d6c72595e3f38e0b19159.zip
|
https://github.com/meshtastic/arduino-fsm/archive/7db3702bf0cfe97b783d6c72595e3f38e0b19159.zip
|
||||||
# renovate: datasource=git-refs depName=meshtastic-TinyGPSPlus packageName=https://github.com/meshtastic/TinyGPSPlus gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic-TinyGPSPlus packageName=https://github.com/meshtastic/TinyGPSPlus gitBranch=master
|
||||||
@@ -102,6 +102,14 @@ lib_deps =
|
|||||||
# renovate: datasource=custom.pio depName=Syslog packageName=arcao/library/Syslog
|
# renovate: datasource=custom.pio depName=Syslog packageName=arcao/library/Syslog
|
||||||
arcao/Syslog@2.0.0
|
arcao/Syslog@2.0.0
|
||||||
|
|
||||||
|
; Minimal networking libs for nrf52 (excludes Syslog to save flash)
|
||||||
|
[nrf52_networking_base]
|
||||||
|
lib_deps =
|
||||||
|
# renovate: datasource=custom.pio depName=TBPubSubClient packageName=thingsboard/library/TBPubSubClient
|
||||||
|
thingsboard/TBPubSubClient@2.12.1
|
||||||
|
# renovate: datasource=custom.pio depName=NTPClient packageName=arduino-libraries/library/NTPClient
|
||||||
|
arduino-libraries/NTPClient@3.2.1
|
||||||
|
|
||||||
[radiolib_base]
|
[radiolib_base]
|
||||||
lib_deps =
|
lib_deps =
|
||||||
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
|
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
|
||||||
@@ -110,7 +118,7 @@ lib_deps =
|
|||||||
[device-ui_base]
|
[device-ui_base]
|
||||||
lib_deps =
|
lib_deps =
|
||||||
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
||||||
https://github.com/meshtastic/device-ui/archive/0cd108ff783539e41ef38258ba2784ab3b1bdc97.zip
|
https://github.com/meshtastic/device-ui/archive/233d18ef42e9d189f90fdfe621f0cd7edff2d221.zip
|
||||||
|
|
||||||
; Common libs for environmental measurements in telemetry module
|
; Common libs for environmental measurements in telemetry module
|
||||||
[environmental_base]
|
[environmental_base]
|
||||||
@@ -149,8 +157,8 @@ lib_deps =
|
|||||||
emotibit/EmotiBit MLX90632@1.0.8
|
emotibit/EmotiBit MLX90632@1.0.8
|
||||||
# renovate: datasource=custom.pio depName=Adafruit MLX90614 packageName=adafruit/library/Adafruit MLX90614 Library
|
# renovate: datasource=custom.pio depName=Adafruit MLX90614 packageName=adafruit/library/Adafruit MLX90614 Library
|
||||||
adafruit/Adafruit MLX90614 Library@2.1.5
|
adafruit/Adafruit MLX90614 Library@2.1.5
|
||||||
# renovate: datasource=github-tags depName=INA3221 packageName=KodinLanewave/INA3221
|
# renovate: datasource=github-tags depName=INA3221 packageName=sgtwilko/INA3221
|
||||||
https://github.com/KodinLanewave/INA3221/archive/1.0.1.zip
|
https://github.com/sgtwilko/INA3221#bb03d7e9bfcc74fc798838a54f4f99738f29fc6a
|
||||||
# renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass
|
# renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass
|
||||||
mprograms/QMC5883LCompass@1.2.3
|
mprograms/QMC5883LCompass@1.2.3
|
||||||
# renovate: datasource=custom.pio depName=DFRobot_RTU packageName=dfrobot/library/DFRobot_RTU
|
# renovate: datasource=custom.pio depName=DFRobot_RTU packageName=dfrobot/library/DFRobot_RTU
|
||||||
@@ -169,6 +177,8 @@ lib_deps =
|
|||||||
adafruit/Adafruit PCT2075@1.0.5
|
adafruit/Adafruit PCT2075@1.0.5
|
||||||
# renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150
|
# renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150
|
||||||
dfrobot/DFRobot_BMM150@1.0.0
|
dfrobot/DFRobot_BMM150@1.0.0
|
||||||
|
# renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561
|
||||||
|
adafruit/Adafruit TSL2561@1.1.2
|
||||||
|
|
||||||
; (not included in native / portduino)
|
; (not included in native / portduino)
|
||||||
[environmental_extra]
|
[environmental_extra]
|
||||||
|
|||||||
Submodule protobufs updated: e2c0831aa3...a84657c220
@@ -8,6 +8,7 @@
|
|||||||
"replacements:all",
|
"replacements:all",
|
||||||
"workarounds:all"
|
"workarounds:all"
|
||||||
],
|
],
|
||||||
|
"baseBranchPatterns": ["master"],
|
||||||
"forkProcessing": "enabled",
|
"forkProcessing": "enabled",
|
||||||
"ignoreDeps": [
|
"ignoreDeps": [
|
||||||
"protobufs"
|
"protobufs"
|
||||||
|
|||||||
@@ -89,14 +89,22 @@ class BluetoothStatus : public Status
|
|||||||
case ConnectionState::CONNECTED:
|
case ConnectionState::CONNECTED:
|
||||||
LOG_DEBUG("BluetoothStatus CONNECTED");
|
LOG_DEBUG("BluetoothStatus CONNECTED");
|
||||||
#ifdef BLE_LED
|
#ifdef BLE_LED
|
||||||
|
#ifdef BLE_LED_INVERTED
|
||||||
|
digitalWrite(BLE_LED, LOW);
|
||||||
|
#else
|
||||||
digitalWrite(BLE_LED, HIGH);
|
digitalWrite(BLE_LED, HIGH);
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ConnectionState::DISCONNECTED:
|
case ConnectionState::DISCONNECTED:
|
||||||
LOG_DEBUG("BluetoothStatus DISCONNECTED");
|
LOG_DEBUG("BluetoothStatus DISCONNECTED");
|
||||||
#ifdef BLE_LED
|
#ifdef BLE_LED
|
||||||
|
#ifdef BLE_LED_INVERTED
|
||||||
|
digitalWrite(BLE_LED, HIGH);
|
||||||
|
#else
|
||||||
digitalWrite(BLE_LED, LOW);
|
digitalWrite(BLE_LED, LOW);
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *mess
|
|||||||
{
|
{
|
||||||
int result;
|
int result;
|
||||||
#ifdef ARCH_PORTDUINO
|
#ifdef ARCH_PORTDUINO
|
||||||
bool utf = !settingsMap[ascii_logs];
|
bool utf = !portduino_config.ascii_logs;
|
||||||
#else
|
#else
|
||||||
bool utf = true;
|
bool utf = true;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
#include "DisplayFormatters.h"
|
#include "DisplayFormatters.h"
|
||||||
|
|
||||||
const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName)
|
const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName,
|
||||||
|
bool usePreset)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// If use_preset is false, always return "Custom"
|
||||||
|
if (!usePreset) {
|
||||||
|
return "Custom";
|
||||||
|
}
|
||||||
|
|
||||||
switch (preset) {
|
switch (preset) {
|
||||||
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
|
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
|
||||||
return useShortName ? "ShortT" : "ShortTurbo";
|
return useShortName ? "ShortT" : "ShortTurbo";
|
||||||
@@ -31,4 +38,46 @@ const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaC
|
|||||||
return useShortName ? "Custom" : "Invalid";
|
return useShortName ? "Custom" : "Invalid";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *DisplayFormatters::getDeviceRole(meshtastic_Config_DeviceConfig_Role role)
|
||||||
|
{
|
||||||
|
switch (role) {
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_CLIENT:
|
||||||
|
return "Client";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE:
|
||||||
|
return "Client Mute";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN:
|
||||||
|
return "Client Hidden";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND:
|
||||||
|
return "Lost and Found";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_TRACKER:
|
||||||
|
return "Tracker";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_SENSOR:
|
||||||
|
return "Sensor";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_TAK:
|
||||||
|
return "TAK";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_TAK_TRACKER:
|
||||||
|
return "TAK Tracker";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_ROUTER:
|
||||||
|
return "Router";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_ROUTER_LATE:
|
||||||
|
return "Router Late";
|
||||||
|
break;
|
||||||
|
case meshtastic_Config_DeviceConfig_Role_REPEATER:
|
||||||
|
return "Repeater";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,5 +4,9 @@
|
|||||||
class DisplayFormatters
|
class DisplayFormatters
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static const char *getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName);
|
static const char *getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName,
|
||||||
|
bool usePreset);
|
||||||
|
|
||||||
|
public:
|
||||||
|
static const char *getDeviceRole(meshtastic_Config_DeviceConfig_Role role);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ class GPSStatus : public Status
|
|||||||
|
|
||||||
meshtastic_Position p = meshtastic_Position_init_default;
|
meshtastic_Position p = meshtastic_Position_init_default;
|
||||||
|
|
||||||
|
/// Time of last valid GPS fix (millis since boot)
|
||||||
|
uint32_t lastFixMillis = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GPSStatus() { statusType = STATUS_TYPE_GPS; }
|
GPSStatus() { statusType = STATUS_TYPE_GPS; }
|
||||||
|
|
||||||
@@ -83,6 +86,9 @@ class GPSStatus : public Status
|
|||||||
|
|
||||||
uint32_t getNumSatellites() const { return p.sats_in_view; }
|
uint32_t getNumSatellites() const { return p.sats_in_view; }
|
||||||
|
|
||||||
|
/// Return millis() when the last GPS fix occurred (0 = never)
|
||||||
|
uint32_t getLastFixMillis() const { return lastFixMillis; }
|
||||||
|
|
||||||
bool matches(const GPSStatus *newStatus) const
|
bool matches(const GPSStatus *newStatus) const
|
||||||
{
|
{
|
||||||
#ifdef GPS_DEBUG
|
#ifdef GPS_DEBUG
|
||||||
@@ -114,6 +120,9 @@ class GPSStatus : public Status
|
|||||||
|
|
||||||
if (isDirty) {
|
if (isDirty) {
|
||||||
if (hasLock) {
|
if (hasLock) {
|
||||||
|
// Record time of last valid GPS fix
|
||||||
|
lastFixMillis = millis();
|
||||||
|
|
||||||
// In debug logs, identify position by @timestamp:stage (stage 3 = notify)
|
// In debug logs, identify position by @timestamp:stage (stage 3 = notify)
|
||||||
LOG_DEBUG("New GPS pos@%x:3 lat=%f lon=%f alt=%d pdop=%.2f track=%.2f speed=%.2f sats=%d", p.timestamp,
|
LOG_DEBUG("New GPS pos@%x:3 lat=%f lon=%f alt=%d pdop=%.2f track=%.2f speed=%.2f sats=%d", p.timestamp,
|
||||||
p.latitude_i * 1e-7, p.longitude_i * 1e-7, p.altitude, p.PDOP * 1e-2, p.ground_track * 1e-5,
|
p.latitude_i * 1e-7, p.longitude_i * 1e-7, p.altitude, p.PDOP * 1e-2, p.ground_track * 1e-5,
|
||||||
|
|||||||
149
src/Power.cpp
149
src/Power.cpp
@@ -128,6 +128,7 @@ RAK9154Sensor rak9154Sensor;
|
|||||||
#ifdef HAS_PPM
|
#ifdef HAS_PPM
|
||||||
// note: XPOWERS_CHIP_XXX must be defined in variant.h
|
// note: XPOWERS_CHIP_XXX must be defined in variant.h
|
||||||
#include <XPowersLib.h>
|
#include <XPowersLib.h>
|
||||||
|
XPowersPPM *PPM = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAS_BQ27220
|
#ifdef HAS_BQ27220
|
||||||
@@ -681,6 +682,8 @@ bool Power::setup()
|
|||||||
found = true;
|
found = true;
|
||||||
} else if (lipoChargerInit()) {
|
} else if (lipoChargerInit()) {
|
||||||
found = true;
|
found = true;
|
||||||
|
} else if (meshSolarInit()) {
|
||||||
|
found = true;
|
||||||
} else if (analogInit()) {
|
} else if (analogInit()) {
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
@@ -743,7 +746,11 @@ void Power::shutdown()
|
|||||||
|
|
||||||
#if HAS_SCREEN
|
#if HAS_SCREEN
|
||||||
if (screen) {
|
if (screen) {
|
||||||
|
#ifdef T_DECK_PRO
|
||||||
|
screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button
|
||||||
|
#else
|
||||||
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
|
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if !defined(ARCH_STM32WL)
|
#if !defined(ARCH_STM32WL)
|
||||||
@@ -761,7 +768,7 @@ void Power::shutdown()
|
|||||||
#ifdef PIN_LED3
|
#ifdef PIN_LED3
|
||||||
ledOff(PIN_LED3);
|
ledOff(PIN_LED3);
|
||||||
#endif
|
#endif
|
||||||
doDeepSleep(DELAY_FOREVER, false, true);
|
doDeepSleep(DELAY_FOREVER, true, true);
|
||||||
#elif defined(ARCH_PORTDUINO)
|
#elif defined(ARCH_PORTDUINO)
|
||||||
exit(EXIT_SUCCESS);
|
exit(EXIT_SUCCESS);
|
||||||
#else
|
#else
|
||||||
@@ -826,16 +833,25 @@ void Power::readPowerStatus()
|
|||||||
newStatus.notifyObservers(&powerStatus2);
|
newStatus.notifyObservers(&powerStatus2);
|
||||||
#ifdef DEBUG_HEAP
|
#ifdef DEBUG_HEAP
|
||||||
if (lastheap != memGet.getFreeHeap()) {
|
if (lastheap != memGet.getFreeHeap()) {
|
||||||
std::string threadlist = "Threads running:";
|
// Use stack-allocated buffer to avoid heap allocations in monitoring code
|
||||||
|
char threadlist[256] = "Threads running:";
|
||||||
|
int threadlistLen = strlen(threadlist);
|
||||||
int running = 0;
|
int running = 0;
|
||||||
for (int i = 0; i < MAX_THREADS; i++) {
|
for (int i = 0; i < MAX_THREADS; i++) {
|
||||||
auto thread = concurrency::mainController.get(i);
|
auto thread = concurrency::mainController.get(i);
|
||||||
if ((thread != nullptr) && (thread->enabled)) {
|
if ((thread != nullptr) && (thread->enabled)) {
|
||||||
threadlist += vformat(" %s", thread->ThreadName.c_str());
|
// Use snprintf to safely append to stack buffer without heap allocation
|
||||||
|
int remaining = sizeof(threadlist) - threadlistLen - 1;
|
||||||
|
if (remaining > 0) {
|
||||||
|
int written = snprintf(threadlist + threadlistLen, remaining, " %s", thread->ThreadName.c_str());
|
||||||
|
if (written > 0 && written < remaining) {
|
||||||
|
threadlistLen += written;
|
||||||
|
}
|
||||||
|
}
|
||||||
running++;
|
running++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG_DEBUG(threadlist.c_str());
|
LOG_DEBUG(threadlist);
|
||||||
LOG_DEBUG("Heap status: %d/%d bytes free (%d), running %d/%d threads", memGet.getFreeHeap(), memGet.getHeapSize(),
|
LOG_DEBUG("Heap status: %d/%d bytes free (%d), running %d/%d threads", memGet.getFreeHeap(), memGet.getHeapSize(),
|
||||||
memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false));
|
memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false));
|
||||||
lastheap = memGet.getFreeHeap();
|
lastheap = memGet.getFreeHeap();
|
||||||
@@ -849,15 +865,19 @@ void Power::readPowerStatus()
|
|||||||
sprintf(mac, "!%02x%02x%02x%02x", dmac[2], dmac[3], dmac[4], dmac[5]);
|
sprintf(mac, "!%02x%02x%02x%02x", dmac[2], dmac[3], dmac[4], dmac[5]);
|
||||||
|
|
||||||
auto newHeap = memGet.getFreeHeap();
|
auto newHeap = memGet.getFreeHeap();
|
||||||
std::string heapTopic =
|
// Use stack-allocated buffers to avoid heap allocations in monitoring code
|
||||||
(*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh") + std::string("/2/heap/") + std::string(mac);
|
char heapTopic[128];
|
||||||
std::string heapString = std::to_string(newHeap);
|
snprintf(heapTopic, sizeof(heapTopic), "%s/2/heap/%s", (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh"), mac);
|
||||||
mqtt->pubSub.publish(heapTopic.c_str(), heapString.c_str(), false);
|
char heapString[16];
|
||||||
|
snprintf(heapString, sizeof(heapString), "%u", newHeap);
|
||||||
|
mqtt->pubSub.publish(heapTopic, heapString, false);
|
||||||
|
|
||||||
auto wifiRSSI = WiFi.RSSI();
|
auto wifiRSSI = WiFi.RSSI();
|
||||||
std::string wifiTopic =
|
char wifiTopic[128];
|
||||||
(*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh") + std::string("/2/wifi/") + std::string(mac);
|
snprintf(wifiTopic, sizeof(wifiTopic), "%s/2/wifi/%s", (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh"), mac);
|
||||||
std::string wifiString = std::to_string(wifiRSSI);
|
char wifiString[16];
|
||||||
mqtt->pubSub.publish(wifiTopic.c_str(), wifiString.c_str(), false);
|
snprintf(wifiString, sizeof(wifiString), "%d", wifiRSSI);
|
||||||
|
mqtt->pubSub.publish(wifiTopic, wifiString, false);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1318,7 +1338,6 @@ bool Power::lipoInit()
|
|||||||
class LipoCharger : public HasBatteryLevel
|
class LipoCharger : public HasBatteryLevel
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
XPowersPPM *ppm = nullptr;
|
|
||||||
BQ27220 *bq = nullptr;
|
BQ27220 *bq = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -1327,41 +1346,41 @@ class LipoCharger : public HasBatteryLevel
|
|||||||
*/
|
*/
|
||||||
bool runOnce()
|
bool runOnce()
|
||||||
{
|
{
|
||||||
if (ppm == nullptr) {
|
if (PPM == nullptr) {
|
||||||
ppm = new XPowersPPM;
|
PPM = new XPowersPPM;
|
||||||
bool result = ppm->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR);
|
bool result = PPM->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR);
|
||||||
if (result) {
|
if (result) {
|
||||||
LOG_INFO("PPM BQ25896 init succeeded");
|
LOG_INFO("PPM BQ25896 init succeeded");
|
||||||
// Set the minimum operating voltage. Below this voltage, the PPM will protect
|
// Set the minimum operating voltage. Below this voltage, the PPM will protect
|
||||||
// ppm->setSysPowerDownVoltage(3100);
|
// PPM->setSysPowerDownVoltage(3100);
|
||||||
|
|
||||||
// Set input current limit, default is 500mA
|
// Set input current limit, default is 500mA
|
||||||
// ppm->setInputCurrentLimit(800);
|
// PPM->setInputCurrentLimit(800);
|
||||||
|
|
||||||
// Disable current limit pin
|
// Disable current limit pin
|
||||||
// ppm->disableCurrentLimitPin();
|
// PPM->disableCurrentLimitPin();
|
||||||
|
|
||||||
// Set the charging target voltage, Range:3840 ~ 4608mV ,step:16 mV
|
// Set the charging target voltage, Range:3840 ~ 4608mV ,step:16 mV
|
||||||
ppm->setChargeTargetVoltage(4288);
|
PPM->setChargeTargetVoltage(4288);
|
||||||
|
|
||||||
// Set the precharge current , Range: 64mA ~ 1024mA ,step:64mA
|
// Set the precharge current , Range: 64mA ~ 1024mA ,step:64mA
|
||||||
// ppm->setPrechargeCurr(64);
|
// PPM->setPrechargeCurr(64);
|
||||||
|
|
||||||
// The premise is that limit pin is disabled, or it will
|
// The premise is that limit pin is disabled, or it will
|
||||||
// only follow the maximum charging current set by limit pin.
|
// only follow the maximum charging current set by limit pin.
|
||||||
// Set the charging current , Range:0~5056mA ,step:64mA
|
// Set the charging current , Range:0~5056mA ,step:64mA
|
||||||
ppm->setChargerConstantCurr(1024);
|
PPM->setChargerConstantCurr(1024);
|
||||||
|
|
||||||
// To obtain voltage data, the ADC must be enabled first
|
// To obtain voltage data, the ADC must be enabled first
|
||||||
ppm->enableMeasure();
|
PPM->enableMeasure();
|
||||||
|
|
||||||
// Turn on charging function
|
// Turn on charging function
|
||||||
// If there is no battery connected, do not turn on the charging function
|
// If there is no battery connected, do not turn on the charging function
|
||||||
ppm->enableCharge();
|
PPM->enableCharge();
|
||||||
} else {
|
} else {
|
||||||
LOG_WARN("PPM BQ25896 init failed");
|
LOG_WARN("PPM BQ25896 init failed");
|
||||||
delete ppm;
|
delete PPM;
|
||||||
ppm = nullptr;
|
PPM = nullptr;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1402,23 +1421,23 @@ class LipoCharger : public HasBatteryLevel
|
|||||||
/**
|
/**
|
||||||
* return true if there is a battery installed in this unit
|
* return true if there is a battery installed in this unit
|
||||||
*/
|
*/
|
||||||
virtual bool isBatteryConnect() override { return ppm->getBattVoltage() > 0; }
|
virtual bool isBatteryConnect() override { return PPM->getBattVoltage() > 0; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return true if there is an external power source detected
|
* return true if there is an external power source detected
|
||||||
*/
|
*/
|
||||||
virtual bool isVbusIn() override { return ppm->getVbusVoltage() > 0; }
|
virtual bool isVbusIn() override { return PPM->getVbusVoltage() > 0; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return true if the battery is currently charging
|
* return true if the battery is currently charging
|
||||||
*/
|
*/
|
||||||
virtual bool isCharging() override
|
virtual bool isCharging() override
|
||||||
{
|
{
|
||||||
bool isCharging = ppm->isCharging();
|
bool isCharging = PPM->isCharging();
|
||||||
if (isCharging) {
|
if (isCharging) {
|
||||||
LOG_DEBUG("BQ27220 time to full charge: %d min", bq->getTimeToFull());
|
LOG_DEBUG("BQ27220 time to full charge: %d min", bq->getTimeToFull());
|
||||||
} else {
|
} else {
|
||||||
if (!ppm->isVbusIn()) {
|
if (!PPM->isVbusIn()) {
|
||||||
LOG_DEBUG("BQ27220 time to empty: %d min (%d mAh)", bq->getTimeToEmpty(), bq->getRemainingCapacity());
|
LOG_DEBUG("BQ27220 time to empty: %d min (%d mAh)", bq->getTimeToEmpty(), bq->getRemainingCapacity());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1450,3 +1469,73 @@ bool Power::lipoChargerInit()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HELTEC_MESH_SOLAR
|
||||||
|
#include "meshSolarApp.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* meshSolar class for an SMBUS battery sensor.
|
||||||
|
*/
|
||||||
|
class meshSolarBatteryLevel : public HasBatteryLevel
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Init the I2C meshSolar battery level sensor
|
||||||
|
*/
|
||||||
|
bool runOnce()
|
||||||
|
{
|
||||||
|
meshSolarStart();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Battery state of charge, from 0 to 100 or -1 for unknown
|
||||||
|
*/
|
||||||
|
virtual int getBatteryPercent() override { return meshSolarGetBatteryPercent(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The raw voltage of the battery in millivolts, or NAN if unknown
|
||||||
|
*/
|
||||||
|
virtual uint16_t getBattVoltage() override { return meshSolarGetBattVoltage(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return true if there is a battery installed in this unit
|
||||||
|
*/
|
||||||
|
virtual bool isBatteryConnect() override { return meshSolarIsBatteryConnect(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return true if there is an external power source detected
|
||||||
|
*/
|
||||||
|
virtual bool isVbusIn() override { return meshSolarIsVbusIn(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return true if the battery is currently charging
|
||||||
|
*/
|
||||||
|
virtual bool isCharging() override { return meshSolarIsCharging(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
meshSolarBatteryLevel meshSolarLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init the meshSolar battery level sensor
|
||||||
|
*/
|
||||||
|
bool Power::meshSolarInit()
|
||||||
|
{
|
||||||
|
bool result = meshSolarLevel.runOnce();
|
||||||
|
LOG_DEBUG("Power::meshSolarInit mesh solar sensor is %s", result ? "ready" : "not ready yet");
|
||||||
|
if (!result)
|
||||||
|
return false;
|
||||||
|
batteryLevel = &meshSolarLevel;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
/**
|
||||||
|
* The meshSolar battery level sensor is unavailable - default to AnalogBatteryLevel
|
||||||
|
*/
|
||||||
|
bool Power::meshSolarInit()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -57,7 +57,7 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ARCH_PORTDUINO
|
#ifdef ARCH_PORTDUINO
|
||||||
bool color = !settingsMap[ascii_logs];
|
bool color = !portduino_config.ascii_logs;
|
||||||
#else
|
#else
|
||||||
bool color = true;
|
bool color = true;
|
||||||
#endif
|
#endif
|
||||||
@@ -99,7 +99,7 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
|
|||||||
size_t r = 0;
|
size_t r = 0;
|
||||||
|
|
||||||
#ifdef ARCH_PORTDUINO
|
#ifdef ARCH_PORTDUINO
|
||||||
bool color = !settingsMap[ascii_logs];
|
bool color = !portduino_config.ascii_logs;
|
||||||
#else
|
#else
|
||||||
bool color = true;
|
bool color = true;
|
||||||
#endif
|
#endif
|
||||||
@@ -288,7 +288,7 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...)
|
|||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
// level trace is special, two possible ways to handle it.
|
// level trace is special, two possible ways to handle it.
|
||||||
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) {
|
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) {
|
||||||
if (settingsStrings[traceFilename] != "") {
|
if (portduino_config.traceFilename != "") {
|
||||||
va_list arg;
|
va_list arg;
|
||||||
va_start(arg, format);
|
va_start(arg, format);
|
||||||
try {
|
try {
|
||||||
@@ -297,18 +297,18 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...)
|
|||||||
}
|
}
|
||||||
va_end(arg);
|
va_end(arg);
|
||||||
}
|
}
|
||||||
if (settingsMap[logoutputlevel] < level_trace && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) {
|
if (portduino_config.logoutputlevel < level_trace && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) {
|
||||||
delete[] newFormat;
|
delete[] newFormat;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (settingsMap[logoutputlevel] < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) {
|
if (portduino_config.logoutputlevel < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) {
|
||||||
delete[] newFormat;
|
delete[] newFormat;
|
||||||
return;
|
return;
|
||||||
} else if (settingsMap[logoutputlevel] < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) {
|
} else if (portduino_config.logoutputlevel < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) {
|
||||||
delete[] newFormat;
|
delete[] newFormat;
|
||||||
return;
|
return;
|
||||||
} else if (settingsMap[logoutputlevel] < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) {
|
} else if (portduino_config.logoutputlevel < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) {
|
||||||
delete[] newFormat;
|
delete[] newFormat;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,14 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
|
|||||||
|
|
||||||
int32_t SerialConsole::runOnce()
|
int32_t SerialConsole::runOnce()
|
||||||
{
|
{
|
||||||
|
#ifdef HELTEC_MESH_SOLAR
|
||||||
|
//After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module.
|
||||||
|
if(moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port
|
||||||
|
&& moduleConfig.serial.mode==meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)
|
||||||
|
{
|
||||||
|
return 250;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return runOncePart();
|
return runOncePart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,11 +28,14 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
|
|||||||
case INPUT_BROKER_USER_PRESS:
|
case INPUT_BROKER_USER_PRESS:
|
||||||
case INPUT_BROKER_ALT_PRESS:
|
case INPUT_BROKER_ALT_PRESS:
|
||||||
case INPUT_BROKER_SELECT:
|
case INPUT_BROKER_SELECT:
|
||||||
|
case INPUT_BROKER_SELECT_LONG:
|
||||||
playBeep(); // Confirmation feedback
|
playBeep(); // Confirmation feedback
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case INPUT_BROKER_UP:
|
case INPUT_BROKER_UP:
|
||||||
|
case INPUT_BROKER_UP_LONG:
|
||||||
case INPUT_BROKER_DOWN:
|
case INPUT_BROKER_DOWN:
|
||||||
|
case INPUT_BROKER_DOWN_LONG:
|
||||||
case INPUT_BROKER_LEFT:
|
case INPUT_BROKER_LEFT:
|
||||||
case INPUT_BROKER_RIGHT:
|
case INPUT_BROKER_RIGHT:
|
||||||
playChirp(); // Navigation feedback
|
playChirp(); // Navigation feedback
|
||||||
|
|||||||
@@ -140,6 +140,10 @@ bool playNextLeadUpNote()
|
|||||||
playTones(¬e, 1); // Play single note using existing playTones function
|
playTones(¬e, 1); // Play single note using existing playTones function
|
||||||
|
|
||||||
leadUpNoteIndex++;
|
leadUpNoteIndex++;
|
||||||
|
|
||||||
|
if (leadUpNoteIndex >= leadUpNotesCount) {
|
||||||
|
return false; // this was the final note
|
||||||
|
}
|
||||||
return true; // Note was played (playTones handles buzzer availability internally)
|
return true; // Note was played (playTones handles buzzer availability internally)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
#ifdef RV3028_RTC
|
#if __has_include("Melopero_RV3028.h")
|
||||||
#include "Melopero_RV3028.h"
|
#include "Melopero_RV3028.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef PCF8563_RTC
|
#if __has_include("pcf8563.h")
|
||||||
#include "pcf8563.h"
|
#include "pcf8563.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// OLED & Input
|
// OLED & Input
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
#if defined(SEEED_WIO_TRACKER_L1)
|
#if defined(SEEED_WIO_TRACKER_L1) && !defined(SEEED_WIO_TRACKER_L1_EINK)
|
||||||
#define SSD1306_ADDRESS 0x3D
|
#define SSD1306_ADDRESS 0x3D
|
||||||
#define USE_SH1106
|
#define USE_SH1106
|
||||||
#else
|
#else
|
||||||
@@ -431,6 +431,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#define MESHTASTIC_EXCLUDE_SERIAL 1
|
#define MESHTASTIC_EXCLUDE_SERIAL 1
|
||||||
#define MESHTASTIC_EXCLUDE_POWERSTRESS 1
|
#define MESHTASTIC_EXCLUDE_POWERSTRESS 1
|
||||||
#define MESHTASTIC_EXCLUDE_ADMIN 1
|
#define MESHTASTIC_EXCLUDE_ADMIN 1
|
||||||
|
#define MESHTASTIC_EXCLUDE_AMBIENTLIGHTING 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// // Turn off wifi even if HW supports wifi (webserver relies on wifi and is also disabled)
|
// // Turn off wifi even if HW supports wifi (webserver relies on wifi and is also disabled)
|
||||||
|
|||||||
@@ -79,7 +79,9 @@ class ScanI2C
|
|||||||
BQ27220,
|
BQ27220,
|
||||||
LTR553ALS,
|
LTR553ALS,
|
||||||
BHI260AP,
|
BHI260AP,
|
||||||
BMM150
|
BMM150,
|
||||||
|
TSL2561,
|
||||||
|
DRV2605
|
||||||
} DeviceType;
|
} DeviceType;
|
||||||
|
|
||||||
// typedef uint8_t DeviceAddress;
|
// typedef uint8_t DeviceAddress;
|
||||||
|
|||||||
@@ -461,7 +461,17 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||||||
SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3", (uint8_t)addr.address);
|
||||||
SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555", (uint8_t)addr.address);
|
||||||
SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700", (uint8_t)addr.address);
|
||||||
SCAN_SIMPLE_CASE(TSL25911_ADDR, TSL2591, "TSL2591", (uint8_t)addr.address);
|
case TSL25911_ADDR:
|
||||||
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x12), 1);
|
||||||
|
if (registerValue == 0x50) {
|
||||||
|
type = TSL2591;
|
||||||
|
logFoundDevice("TSL25911", (uint8_t)addr.address);
|
||||||
|
} else {
|
||||||
|
type = TSL2561;
|
||||||
|
logFoundDevice("TSL2561", (uint8_t)addr.address);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632", (uint8_t)addr.address);
|
||||||
SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802", (uint8_t)addr.address);
|
||||||
SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048", (uint8_t)addr.address);
|
SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048", (uint8_t)addr.address);
|
||||||
@@ -483,8 +493,14 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||||||
type = MLX90614;
|
type = MLX90614;
|
||||||
logFoundDevice("MLX90614", (uint8_t)addr.address);
|
logFoundDevice("MLX90614", (uint8_t)addr.address);
|
||||||
} else {
|
} else {
|
||||||
type = MPR121KB;
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); // DRV2605_REG_STATUS
|
||||||
logFoundDevice("MPR121KB", (uint8_t)addr.address);
|
if (registerValue == 0xe0) {
|
||||||
|
type = DRV2605;
|
||||||
|
logFoundDevice("DRV2605", (uint8_t)addr.address);
|
||||||
|
} else {
|
||||||
|
type = MPR121KB;
|
||||||
|
logFoundDevice("MPR121KB", (uint8_t)addr.address);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
107
src/gps/GPS.cpp
107
src/gps/GPS.cpp
@@ -808,6 +808,14 @@ bool GPS::setup()
|
|||||||
} else {
|
} else {
|
||||||
LOG_INFO("GNSS module configuration saved!");
|
LOG_INFO("GNSS module configuration saved!");
|
||||||
}
|
}
|
||||||
|
} else if (gnssModel == GNSS_MODEL_CM121) {
|
||||||
|
// only ask for RMC and GGA
|
||||||
|
// enable GGA
|
||||||
|
_serial_gps->write("$CFGMSG,0,0,1,1*1B\r\n");
|
||||||
|
delay(250);
|
||||||
|
// enable RMC
|
||||||
|
_serial_gps->write("$CFGMSG,0,4,1,1*1F\r\n");
|
||||||
|
delay(250);
|
||||||
}
|
}
|
||||||
didSerialInit = true;
|
didSerialInit = true;
|
||||||
}
|
}
|
||||||
@@ -843,9 +851,6 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
|
|||||||
setPowerPMU(true); // Power (PMU): on
|
setPowerPMU(true); // Power (PMU): on
|
||||||
writePinStandby(false); // Standby (pin): awake (not standby)
|
writePinStandby(false); // Standby (pin): awake (not standby)
|
||||||
setPowerUBLOX(true); // Standby (UBLOX): awake
|
setPowerUBLOX(true); // Standby (UBLOX): awake
|
||||||
#ifdef GNSS_AIROHA
|
|
||||||
lastFixStartMsec = 0;
|
|
||||||
#endif
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GPS_SOFTSLEEP:
|
case GPS_SOFTSLEEP:
|
||||||
@@ -863,9 +868,7 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
|
|||||||
writePinStandby(true); // Standby (pin): asleep (not awake)
|
writePinStandby(true); // Standby (pin): asleep (not awake)
|
||||||
setPowerUBLOX(false, sleepTime); // Standby (UBLOX): asleep, timed
|
setPowerUBLOX(false, sleepTime); // Standby (UBLOX): asleep, timed
|
||||||
#ifdef GNSS_AIROHA
|
#ifdef GNSS_AIROHA
|
||||||
if (config.position.gps_update_interval * 1000 >= GPS_FIX_HOLD_TIME * 2) {
|
digitalWrite(PIN_GPS_EN, LOW);
|
||||||
digitalWrite(PIN_GPS_EN, LOW);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -877,9 +880,7 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
|
|||||||
writePinStandby(true); // Standby (pin): asleep
|
writePinStandby(true); // Standby (pin): asleep
|
||||||
setPowerUBLOX(false, 0); // Standby (UBLOX): asleep, indefinitely
|
setPowerUBLOX(false, 0); // Standby (UBLOX): asleep, indefinitely
|
||||||
#ifdef GNSS_AIROHA
|
#ifdef GNSS_AIROHA
|
||||||
if (config.position.gps_update_interval * 1000 >= GPS_FIX_HOLD_TIME * 2) {
|
digitalWrite(PIN_GPS_EN, LOW);
|
||||||
digitalWrite(PIN_GPS_EN, LOW);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1062,6 +1063,8 @@ void GPS::down()
|
|||||||
}
|
}
|
||||||
// If update interval long enough (or softsleep unsupported): hardsleep instead
|
// If update interval long enough (or softsleep unsupported): hardsleep instead
|
||||||
setPowerState(GPS_HARDSLEEP, sleepTime);
|
setPowerState(GPS_HARDSLEEP, sleepTime);
|
||||||
|
// Reset the fix quality to 0, since we're off.
|
||||||
|
fixQual = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1121,11 +1124,19 @@ int32_t GPS::runOnce()
|
|||||||
shouldPublish = true;
|
shouldPublish = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t prev_fixQual = fixQual;
|
||||||
bool gotLoc = lookForLocation();
|
bool gotLoc = lookForLocation();
|
||||||
if (gotLoc && !hasValidLocation) { // declare that we have location ASAP
|
if (gotLoc && !hasValidLocation) { // declare that we have location ASAP
|
||||||
LOG_DEBUG("hasValidLocation RISING EDGE");
|
LOG_DEBUG("hasValidLocation RISING EDGE");
|
||||||
hasValidLocation = true;
|
hasValidLocation = true;
|
||||||
shouldPublish = true;
|
shouldPublish = true;
|
||||||
|
// Hold for 20secs after getting a lock to download ephemeris etc
|
||||||
|
fixHoldEnds = millis() + 20000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gotLoc && prev_fixQual == 0) { // just got a lock after turning back on.
|
||||||
|
fixHoldEnds = millis() + 20000;
|
||||||
|
shouldPublish = true; // Publish immediately, since next publish is at end of hold
|
||||||
}
|
}
|
||||||
|
|
||||||
bool tooLong = scheduling.searchedTooLong();
|
bool tooLong = scheduling.searchedTooLong();
|
||||||
@@ -1134,8 +1145,7 @@ int32_t GPS::runOnce()
|
|||||||
|
|
||||||
// Once we get a location we no longer desperately want an update
|
// Once we get a location we no longer desperately want an update
|
||||||
if ((gotLoc && gotTime) || tooLong) {
|
if ((gotLoc && gotTime) || tooLong) {
|
||||||
|
if (tooLong && !gotLoc) {
|
||||||
if (tooLong) {
|
|
||||||
// we didn't get a location during this ack window, therefore declare loss of lock
|
// we didn't get a location during this ack window, therefore declare loss of lock
|
||||||
if (hasValidLocation) {
|
if (hasValidLocation) {
|
||||||
LOG_DEBUG("hasValidLocation FALLING EDGE");
|
LOG_DEBUG("hasValidLocation FALLING EDGE");
|
||||||
@@ -1143,9 +1153,15 @@ int32_t GPS::runOnce()
|
|||||||
p = meshtastic_Position_init_default;
|
p = meshtastic_Position_init_default;
|
||||||
hasValidLocation = false;
|
hasValidLocation = false;
|
||||||
}
|
}
|
||||||
|
if (millis() > fixHoldEnds) {
|
||||||
down();
|
shouldPublish = true; // publish our update at the end of the lock hold
|
||||||
shouldPublish = true; // publish our update for this just finished acquisition window
|
publishUpdate();
|
||||||
|
down();
|
||||||
|
#ifdef GPS_DEBUG
|
||||||
|
} else {
|
||||||
|
LOG_DEBUG("Holding for GPS data download: %d ms (numSats=%d)", fixHoldEnds - millis(), p.sats_in_view);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If state has changed do a publish
|
// If state has changed do a publish
|
||||||
@@ -1232,9 +1248,15 @@ GnssModel_t GPS::probe(int serialSpeed)
|
|||||||
_serial_gps->write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n");
|
_serial_gps->write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n");
|
||||||
_serial_gps->write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n");
|
_serial_gps->write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n");
|
||||||
delay(20);
|
delay(20);
|
||||||
|
// Close NMEA sequences on CM121
|
||||||
|
_serial_gps->write("$CFGMSG,0,1,0,1*1B\r\n");
|
||||||
|
_serial_gps->write("$CFGMSG,0,2,0,1*18\r\n");
|
||||||
|
_serial_gps->write("$CFGMSG,0,3,0,1*19\r\n");
|
||||||
|
delay(20);
|
||||||
|
|
||||||
// Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A
|
// Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A,or CM121
|
||||||
std::vector<ChipInfo> unicore = {{"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}};
|
std::vector<ChipInfo> unicore = {
|
||||||
|
{"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}, {"CM121", "CM121", GNSS_MODEL_CM121}};
|
||||||
PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500);
|
PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500);
|
||||||
|
|
||||||
std::vector<ChipInfo> atgm = {
|
std::vector<ChipInfo> atgm = {
|
||||||
@@ -1414,7 +1436,7 @@ GPS *GPS::createGps()
|
|||||||
_en_gpio = PIN_GPS_EN;
|
_en_gpio = PIN_GPS_EN;
|
||||||
#endif
|
#endif
|
||||||
#ifdef ARCH_PORTDUINO
|
#ifdef ARCH_PORTDUINO
|
||||||
if (!settingsMap[has_gps])
|
if (!portduino_config.has_gps)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
#endif
|
#endif
|
||||||
if (!_rx_gpio || !_serial_gps) // Configured to have no GPS at all
|
if (!_rx_gpio || !_serial_gps) // Configured to have no GPS at all
|
||||||
@@ -1504,28 +1526,10 @@ static int32_t toDegInt(RawDegrees d)
|
|||||||
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
* Perform any processing that should be done only while the GPS is awake and looking for a fix.
|
||||||
* Override this method to check for new locations
|
* Override this method to check for new locations
|
||||||
*
|
*
|
||||||
* @return true if we've acquired a new location
|
* @return true if we've set a new time
|
||||||
*/
|
*/
|
||||||
bool GPS::lookForTime()
|
bool GPS::lookForTime()
|
||||||
{
|
{
|
||||||
|
|
||||||
#ifdef GNSS_AIROHA
|
|
||||||
uint8_t fix = reader.fixQuality();
|
|
||||||
if (fix >= 1 && fix <= 5) {
|
|
||||||
if (lastFixStartMsec > 0) {
|
|
||||||
if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
clearBuffer();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lastFixStartMsec = millis();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
auto ti = reader.time;
|
auto ti = reader.time;
|
||||||
auto d = reader.date;
|
auto d = reader.date;
|
||||||
if (ti.isValid() && d.isValid()) { // Note: we don't check for updated, because we'll only be called if needed
|
if (ti.isValid() && d.isValid()) { // Note: we don't check for updated, because we'll only be called if needed
|
||||||
@@ -1542,13 +1546,13 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
|
|||||||
t.tm_year = d.year() - 1900;
|
t.tm_year = d.year() - 1900;
|
||||||
t.tm_isdst = false;
|
t.tm_isdst = false;
|
||||||
if (t.tm_mon > -1) {
|
if (t.tm_mon > -1) {
|
||||||
LOG_DEBUG("NMEA GPS time %02d-%02d-%02d %02d:%02d:%02d age %d", d.year(), d.month(), t.tm_mday, t.tm_hour, t.tm_min,
|
if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultSuccess) {
|
||||||
t.tm_sec, ti.age());
|
LOG_DEBUG("NMEA GPS time set %02d-%02d-%02d %02d:%02d:%02d age %d", d.year(), d.month(), t.tm_mday, t.tm_hour,
|
||||||
if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultInvalidTime) {
|
t.tm_min, t.tm_sec, ti.age());
|
||||||
// Clear the GPS buffer if we got an invalid time
|
return true;
|
||||||
clearBuffer();
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
} else
|
} else
|
||||||
return false;
|
return false;
|
||||||
} else
|
} else
|
||||||
@@ -1563,25 +1567,6 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
|
|||||||
*/
|
*/
|
||||||
bool GPS::lookForLocation()
|
bool GPS::lookForLocation()
|
||||||
{
|
{
|
||||||
#ifdef GNSS_AIROHA
|
|
||||||
if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) {
|
|
||||||
uint8_t fix = reader.fixQuality();
|
|
||||||
if (fix >= 1 && fix <= 5) {
|
|
||||||
if (lastFixStartMsec > 0) {
|
|
||||||
if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
clearBuffer();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lastFixStartMsec = millis();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// By default, TinyGPS++ does not parse GPGSA lines, which give us
|
// By default, TinyGPS++ does not parse GPGSA lines, which give us
|
||||||
// the 2D/3D fixType (see NMEAGPS.h)
|
// the 2D/3D fixType (see NMEAGPS.h)
|
||||||
// At a minimum, use the fixQuality indicator in GPGGA (FIXME?)
|
// At a minimum, use the fixQuality indicator in GPGGA (FIXME?)
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ typedef enum {
|
|||||||
GNSS_MODEL_MTK_PA1616S,
|
GNSS_MODEL_MTK_PA1616S,
|
||||||
GNSS_MODEL_AG3335,
|
GNSS_MODEL_AG3335,
|
||||||
GNSS_MODEL_AG3352,
|
GNSS_MODEL_AG3352,
|
||||||
GNSS_MODEL_LS20031
|
GNSS_MODEL_LS20031,
|
||||||
|
GNSS_MODEL_CM121
|
||||||
} GnssModel_t;
|
} GnssModel_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@@ -159,7 +160,7 @@ class GPS : private concurrency::OSThread
|
|||||||
uint8_t fixType = 0; // fix type from GPGSA
|
uint8_t fixType = 0; // fix type from GPGSA
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0, lastFixStartMsec = 0;
|
uint32_t fixHoldEnds = 0;
|
||||||
uint32_t rx_gpio = 0;
|
uint32_t rx_gpio = 0;
|
||||||
uint32_t tx_gpio = 0;
|
uint32_t tx_gpio = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only upda
|
|||||||
* Reads the current date and time from the RTC module and updates the system time.
|
* Reads the current date and time from the RTC module and updates the system time.
|
||||||
* @return True if the RTC was successfully read and the system time was updated, false otherwise.
|
* @return True if the RTC was successfully read and the system time was updated, false otherwise.
|
||||||
*/
|
*/
|
||||||
void readFromRTC()
|
RTCSetResult readFromRTC()
|
||||||
{
|
{
|
||||||
struct timeval tv; /* btw settimeofday() is helpful here too*/
|
struct timeval tv; /* btw settimeofday() is helpful here too*/
|
||||||
#ifdef RV3028_RTC
|
#ifdef RV3028_RTC
|
||||||
@@ -44,15 +44,23 @@ void readFromRTC()
|
|||||||
t.tm_sec = rtc.getSecond();
|
t.tm_sec = rtc.getSecond();
|
||||||
tv.tv_sec = gm_mktime(&t);
|
tv.tv_sec = gm_mktime(&t);
|
||||||
tv.tv_usec = 0;
|
tv.tv_usec = 0;
|
||||||
|
|
||||||
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
||||||
|
|
||||||
|
#ifdef BUILD_EPOCH
|
||||||
|
if (tv.tv_sec < BUILD_EPOCH) {
|
||||||
|
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
|
||||||
|
return RTCSetResultInvalidTime;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
LOG_DEBUG("Read RTC time from RV3028 getTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1,
|
LOG_DEBUG("Read RTC time from RV3028 getTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1,
|
||||||
t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
|
t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
|
||||||
timeStartMsec = now;
|
|
||||||
zeroOffsetSecs = tv.tv_sec;
|
|
||||||
if (currentQuality == RTCQualityNone) {
|
if (currentQuality == RTCQualityNone) {
|
||||||
|
timeStartMsec = now;
|
||||||
|
zeroOffsetSecs = tv.tv_sec;
|
||||||
currentQuality = RTCQualityDevice;
|
currentQuality = RTCQualityDevice;
|
||||||
}
|
}
|
||||||
|
return RTCSetResultSuccess;
|
||||||
}
|
}
|
||||||
#elif defined(PCF8563_RTC)
|
#elif defined(PCF8563_RTC)
|
||||||
if (rtc_found.address == PCF8563_RTC) {
|
if (rtc_found.address == PCF8563_RTC) {
|
||||||
@@ -75,15 +83,23 @@ void readFromRTC()
|
|||||||
t.tm_sec = tc.second;
|
t.tm_sec = tc.second;
|
||||||
tv.tv_sec = gm_mktime(&t);
|
tv.tv_sec = gm_mktime(&t);
|
||||||
tv.tv_usec = 0;
|
tv.tv_usec = 0;
|
||||||
|
|
||||||
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
||||||
|
|
||||||
|
#ifdef BUILD_EPOCH
|
||||||
|
if (tv.tv_sec < BUILD_EPOCH) {
|
||||||
|
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
|
||||||
|
return RTCSetResultInvalidTime;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
LOG_DEBUG("Read RTC time from PCF8563 getDateTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1,
|
LOG_DEBUG("Read RTC time from PCF8563 getDateTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1,
|
||||||
t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
|
t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
|
||||||
timeStartMsec = now;
|
|
||||||
zeroOffsetSecs = tv.tv_sec;
|
|
||||||
if (currentQuality == RTCQualityNone) {
|
if (currentQuality == RTCQualityNone) {
|
||||||
|
timeStartMsec = now;
|
||||||
|
zeroOffsetSecs = tv.tv_sec;
|
||||||
currentQuality = RTCQualityDevice;
|
currentQuality = RTCQualityDevice;
|
||||||
}
|
}
|
||||||
|
return RTCSetResultSuccess;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (!gettimeofday(&tv, NULL)) {
|
if (!gettimeofday(&tv, NULL)) {
|
||||||
@@ -92,8 +108,10 @@ void readFromRTC()
|
|||||||
LOG_DEBUG("Read RTC time as %ld", printableEpoch);
|
LOG_DEBUG("Read RTC time as %ld", printableEpoch);
|
||||||
timeStartMsec = now;
|
timeStartMsec = now;
|
||||||
zeroOffsetSecs = tv.tv_sec;
|
zeroOffsetSecs = tv.tv_sec;
|
||||||
|
return RTCSetResultSuccess;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
return RTCSetResultNotSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -101,7 +119,7 @@ void readFromRTC()
|
|||||||
*
|
*
|
||||||
* @param q The quality of the provided time.
|
* @param q The quality of the provided time.
|
||||||
* @param tv A pointer to a timeval struct containing the time to potentially set the RTC to.
|
* @param tv A pointer to a timeval struct containing the time to potentially set the RTC to.
|
||||||
* @return True if the RTC was set, false otherwise.
|
* @return RTCSetResult
|
||||||
*
|
*
|
||||||
* If we haven't yet set our RTC this boot, set it from a GPS derived time
|
* If we haven't yet set our RTC this boot, set it from a GPS derived time
|
||||||
*/
|
*/
|
||||||
@@ -112,7 +130,15 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
|
|||||||
uint32_t printableEpoch = tv->tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
uint32_t printableEpoch = tv->tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
||||||
#ifdef BUILD_EPOCH
|
#ifdef BUILD_EPOCH
|
||||||
if (tv->tv_sec < BUILD_EPOCH) {
|
if (tv->tv_sec < BUILD_EPOCH) {
|
||||||
|
#ifdef GPS_DEBUG
|
||||||
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
|
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
|
||||||
|
#endif
|
||||||
|
return RTCSetResultInvalidTime;
|
||||||
|
} else if (tv->tv_sec > (BUILD_EPOCH + FORTY_YEARS)) {
|
||||||
|
#ifdef GPS_DEBUG
|
||||||
|
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH,
|
||||||
|
BUILD_EPOCH + FORTY_YEARS);
|
||||||
|
#endif
|
||||||
return RTCSetResultInvalidTime;
|
return RTCSetResultInvalidTime;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -230,7 +256,15 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
|
|||||||
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
||||||
#ifdef BUILD_EPOCH
|
#ifdef BUILD_EPOCH
|
||||||
if (tv.tv_sec < BUILD_EPOCH) {
|
if (tv.tv_sec < BUILD_EPOCH) {
|
||||||
|
#ifdef GPS_DEBUG
|
||||||
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
|
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
|
||||||
|
#endif
|
||||||
|
return RTCSetResultInvalidTime;
|
||||||
|
} else if (tv.tv_sec > (BUILD_EPOCH + FORTY_YEARS)) {
|
||||||
|
#ifdef GPS_DEBUG
|
||||||
|
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH,
|
||||||
|
BUILD_EPOCH + FORTY_YEARS);
|
||||||
|
#endif
|
||||||
return RTCSetResultInvalidTime;
|
return RTCSetResultInvalidTime;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -48,10 +48,13 @@ uint32_t getTime(bool local = false);
|
|||||||
/// Return time since 1970 in secs. If quality is RTCQualityNone return zero
|
/// Return time since 1970 in secs. If quality is RTCQualityNone return zero
|
||||||
uint32_t getValidTime(RTCQuality minQuality, bool local = false);
|
uint32_t getValidTime(RTCQuality minQuality, bool local = false);
|
||||||
|
|
||||||
void readFromRTC();
|
RTCSetResult readFromRTC();
|
||||||
|
|
||||||
time_t gm_mktime(struct tm *tm);
|
time_t gm_mktime(struct tm *tm);
|
||||||
|
|
||||||
#define SEC_PER_DAY 86400
|
#define SEC_PER_DAY 86400
|
||||||
#define SEC_PER_HOUR 3600
|
#define SEC_PER_HOUR 3600
|
||||||
#define SEC_PER_MIN 60
|
#define SEC_PER_MIN 60
|
||||||
|
#ifdef BUILD_EPOCH
|
||||||
|
#define FORTY_YEARS (40UL * 365 * SEC_PER_DAY) // probably time to update your firmware
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -67,20 +67,28 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
|
|||||||
|
|
||||||
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
|
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
|
||||||
const bool flipped = config.display.flip_screen;
|
const bool flipped = config.display.flip_screen;
|
||||||
|
// HACK for L1 EInk
|
||||||
|
#if defined(SEEED_WIO_TRACKER_L1_EINK)
|
||||||
|
// For SEEED_WIO_TRACKER_L1_EINK, setRotation(3) is correct but mirrored; flip both axes
|
||||||
|
for (uint32_t y = 0; y < displayHeight; y++) {
|
||||||
|
for (uint32_t x = 0; x < displayWidth; x++) {
|
||||||
|
auto b = buffer[x + (y / 8) * displayWidth];
|
||||||
|
auto isset = b & (1 << (y & 7));
|
||||||
|
adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
for (uint32_t y = 0; y < displayHeight; y++) {
|
for (uint32_t y = 0; y < displayHeight; y++) {
|
||||||
for (uint32_t x = 0; x < displayWidth; x++) {
|
for (uint32_t x = 0; x < displayWidth; x++) {
|
||||||
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficient
|
|
||||||
auto b = buffer[x + (y / 8) * displayWidth];
|
auto b = buffer[x + (y / 8) * displayWidth];
|
||||||
auto isset = b & (1 << (y & 7));
|
auto isset = b & (1 << (y & 7));
|
||||||
|
|
||||||
// Handle flip here, rather than with setRotation(),
|
|
||||||
// Avoids issues when display width is not a multiple of 8
|
|
||||||
if (flipped)
|
if (flipped)
|
||||||
adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE);
|
adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE);
|
||||||
else
|
else
|
||||||
adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE);
|
adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Trigger the refresh in GxEPD2
|
// Trigger the refresh in GxEPD2
|
||||||
LOG_DEBUG("Update E-Paper");
|
LOG_DEBUG("Update E-Paper");
|
||||||
@@ -140,13 +148,13 @@ bool EInkDisplay::connect()
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(TTGO_T_ECHO) || defined(ELECROW_ThinkNode_M1)
|
#if defined(TTGO_T_ECHO) || defined(ELECROW_ThinkNode_M1) || defined(T_ECHO_LITE)
|
||||||
{
|
{
|
||||||
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
|
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
|
||||||
|
|
||||||
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||||
adafruitDisplay->init();
|
adafruitDisplay->init();
|
||||||
#ifdef ELECROW_ThinkNode_M1
|
#if defined(ELECROW_ThinkNode_M1) || defined(T_ECHO_LITE)
|
||||||
adafruitDisplay->setRotation(4);
|
adafruitDisplay->setRotation(4);
|
||||||
#else
|
#else
|
||||||
adafruitDisplay->setRotation(3);
|
adafruitDisplay->setRotation(3);
|
||||||
@@ -235,7 +243,7 @@ bool EInkDisplay::connect()
|
|||||||
adafruitDisplay->setRotation(1);
|
adafruitDisplay->setRotation(1);
|
||||||
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
||||||
}
|
}
|
||||||
#elif defined(HELTEC_MESH_POCKET)
|
#elif defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK)
|
||||||
{
|
{
|
||||||
spi1 = &SPI1;
|
spi1 = &SPI1;
|
||||||
spi1->begin();
|
spi1->begin();
|
||||||
@@ -249,6 +257,7 @@ bool EInkDisplay::connect()
|
|||||||
// Init GxEPD2
|
// Init GxEPD2
|
||||||
adafruitDisplay->init();
|
adafruitDisplay->init();
|
||||||
adafruitDisplay->setRotation(3);
|
adafruitDisplay->setRotation(3);
|
||||||
|
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
||||||
}
|
}
|
||||||
#elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213)
|
#elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213)
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class EInkDisplay : public OLEDDisplay
|
|||||||
SPIClass *hspi = NULL;
|
SPIClass *hspi = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(HELTEC_MESH_POCKET)
|
#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK)
|
||||||
SPIClass *spi1 = NULL;
|
SPIClass *spi1 = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -149,6 +149,11 @@ void Screen::showSimpleBanner(const char *message, uint32_t durationMs)
|
|||||||
// Called to trigger a banner with custom message and duration
|
// Called to trigger a banner with custom message and duration
|
||||||
void Screen::showOverlayBanner(BannerOverlayOptions banner_overlay_options)
|
void Screen::showOverlayBanner(BannerOverlayOptions banner_overlay_options)
|
||||||
{
|
{
|
||||||
|
// Don't show overlay banner if virtual keyboard is active
|
||||||
|
if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
|
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
|
||||||
#endif
|
#endif
|
||||||
@@ -173,6 +178,11 @@ void Screen::showOverlayBanner(BannerOverlayOptions banner_overlay_options)
|
|||||||
// Called to trigger a banner with custom message and duration
|
// Called to trigger a banner with custom message and duration
|
||||||
void Screen::showNodePicker(const char *message, uint32_t durationMs, std::function<void(uint32_t)> bannerCallback)
|
void Screen::showNodePicker(const char *message, uint32_t durationMs, std::function<void(uint32_t)> bannerCallback)
|
||||||
{
|
{
|
||||||
|
// Don't show node picker if virtual keyboard is active
|
||||||
|
if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
|
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
|
||||||
#endif
|
#endif
|
||||||
@@ -196,6 +206,11 @@ void Screen::showNodePicker(const char *message, uint32_t durationMs, std::funct
|
|||||||
void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits,
|
void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits,
|
||||||
std::function<void(uint32_t)> bannerCallback)
|
std::function<void(uint32_t)> bannerCallback)
|
||||||
{
|
{
|
||||||
|
// Don't show number picker if virtual keyboard is active
|
||||||
|
if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
|
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
|
||||||
#endif
|
#endif
|
||||||
@@ -216,6 +231,30 @@ void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t
|
|||||||
ui->update();
|
ui->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Screen::showTextInput(const char *header, const char *initialText, uint32_t durationMs,
|
||||||
|
std::function<void(const std::string &)> textCallback)
|
||||||
|
{
|
||||||
|
LOG_INFO("showTextInput called with header='%s', durationMs=%d", header ? header : "NULL", durationMs);
|
||||||
|
|
||||||
|
// Start OnScreenKeyboardModule session (non-touch variant)
|
||||||
|
OnScreenKeyboardModule::instance().start(header, initialText, durationMs, textCallback);
|
||||||
|
NotificationRenderer::virtualKeyboard = OnScreenKeyboardModule::instance().getKeyboard();
|
||||||
|
NotificationRenderer::textInputCallback = textCallback;
|
||||||
|
|
||||||
|
// Store the message and set the expiration timestamp (use same pattern as other notifications)
|
||||||
|
strncpy(NotificationRenderer::alertBannerMessage, header ? header : "Text Input", 255);
|
||||||
|
NotificationRenderer::alertBannerMessage[255] = '\0';
|
||||||
|
NotificationRenderer::alertBannerUntil = (durationMs == 0) ? 0 : millis() + durationMs;
|
||||||
|
NotificationRenderer::pauseBanner = false;
|
||||||
|
NotificationRenderer::current_notification_type = notificationTypeEnum::text_input;
|
||||||
|
|
||||||
|
// Set the overlay using the same pattern as other notification types
|
||||||
|
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
|
||||||
|
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
|
||||||
|
ui->setTargetFPS(60);
|
||||||
|
ui->update();
|
||||||
|
}
|
||||||
|
|
||||||
static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
uint8_t module_frame;
|
uint8_t module_frame;
|
||||||
@@ -318,7 +357,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
|
|||||||
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
|
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
|
||||||
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
||||||
#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \
|
#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \
|
||||||
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS)
|
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)
|
||||||
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
|
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
|
||||||
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
||||||
#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
|
#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
|
||||||
@@ -332,7 +371,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
|
|||||||
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
||||||
#elif ARCH_PORTDUINO
|
#elif ARCH_PORTDUINO
|
||||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
||||||
if (settingsMap[displayPanel] != no_screen) {
|
if (portduino_config.displayPanel != no_screen) {
|
||||||
LOG_DEBUG("Make TFTDisplay!");
|
LOG_DEBUG("Make TFTDisplay!");
|
||||||
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
|
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
|
||||||
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
|
||||||
@@ -365,9 +404,6 @@ void Screen::doDeepSleep()
|
|||||||
{
|
{
|
||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
setOn(false, graphics::UIRenderer::drawDeepSleepFrame);
|
setOn(false, graphics::UIRenderer::drawDeepSleepFrame);
|
||||||
#ifdef PIN_EINK_EN
|
|
||||||
digitalWrite(PIN_EINK_EN, LOW); // power off backlight
|
|
||||||
#endif
|
|
||||||
#else
|
#else
|
||||||
// Without E-Ink display:
|
// Without E-Ink display:
|
||||||
setOn(false);
|
setOn(false);
|
||||||
@@ -391,8 +427,12 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
|||||||
dispdev->displayOn();
|
dispdev->displayOn();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ELECROW_ThinkNode_M5
|
#ifdef PIN_EINK_EN
|
||||||
io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
|
if (uiconfig.screen_brightness == 1)
|
||||||
|
digitalWrite(PIN_EINK_EN, HIGH);
|
||||||
|
#elif defined(PCA_PIN_EINK_EN)
|
||||||
|
if (uiconfig.screen_brightness == 1)
|
||||||
|
io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(ST7789_CS) && \
|
#if defined(ST7789_CS) && \
|
||||||
@@ -424,13 +464,10 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
|||||||
// eInkScreensaver parameter is usually NULL (default argument), default frame used instead
|
// eInkScreensaver parameter is usually NULL (default argument), default frame used instead
|
||||||
setScreensaverFrames(einkScreensaver);
|
setScreensaverFrames(einkScreensaver);
|
||||||
#endif
|
#endif
|
||||||
#ifdef ELECROW_ThinkNode_M1
|
|
||||||
if (digitalRead(PIN_EINK_EN) == HIGH) {
|
|
||||||
digitalWrite(PIN_EINK_EN, LOW);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef ELECROW_ThinkNode_M5
|
#ifdef PIN_EINK_EN
|
||||||
|
digitalWrite(PIN_EINK_EN, LOW);
|
||||||
|
#elif defined(PCA_PIN_EINK_EN)
|
||||||
io.digitalWrite(PCA_PIN_EINK_EN, LOW);
|
io.digitalWrite(PCA_PIN_EINK_EN, LOW);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -552,7 +589,7 @@ void Screen::setup()
|
|||||||
#else
|
#else
|
||||||
if (!config.display.flip_screen) {
|
if (!config.display.flip_screen) {
|
||||||
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
|
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
|
||||||
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS)
|
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)
|
||||||
static_cast<TFTDisplay *>(dispdev)->flipScreenVertically();
|
static_cast<TFTDisplay *>(dispdev)->flipScreenVertically();
|
||||||
#elif defined(USE_ST7789)
|
#elif defined(USE_ST7789)
|
||||||
static_cast<ST7789Spi *>(dispdev)->flipScreenVertically();
|
static_cast<ST7789Spi *>(dispdev)->flipScreenVertically();
|
||||||
@@ -582,7 +619,7 @@ void Screen::setup()
|
|||||||
|
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
||||||
if (settingsMap[touchscreenModule]) {
|
if (portduino_config.touchscreenModule) {
|
||||||
touchScreenImpl1 =
|
touchScreenImpl1 =
|
||||||
new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast<TFTDisplay *>(dispdev)->getTouch);
|
new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast<TFTDisplay *>(dispdev)->getTouch);
|
||||||
touchScreenImpl1->init();
|
touchScreenImpl1->init();
|
||||||
@@ -694,7 +731,7 @@ int32_t Screen::runOnce()
|
|||||||
|
|
||||||
#ifndef DISABLE_WELCOME_UNSET
|
#ifndef DISABLE_WELCOME_UNSET
|
||||||
if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
|
if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
|
||||||
menuHandler::LoraRegionPicker(0);
|
menuHandler::OnboardMessage();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) {
|
if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) {
|
||||||
@@ -715,13 +752,19 @@ int32_t Screen::runOnce()
|
|||||||
handleSetOn(false);
|
handleSetOn(false);
|
||||||
break;
|
break;
|
||||||
case Cmd::ON_PRESS:
|
case Cmd::ON_PRESS:
|
||||||
handleOnPress();
|
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
||||||
|
handleOnPress();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Cmd::SHOW_PREV_FRAME:
|
case Cmd::SHOW_PREV_FRAME:
|
||||||
handleShowPrevFrame();
|
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
||||||
|
handleShowPrevFrame();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Cmd::SHOW_NEXT_FRAME:
|
case Cmd::SHOW_NEXT_FRAME:
|
||||||
handleShowNextFrame();
|
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
||||||
|
handleShowNextFrame();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Cmd::START_ALERT_FRAME: {
|
case Cmd::START_ALERT_FRAME: {
|
||||||
showingBootScreen = false; // this should avoid the edge case where an alert triggers before the boot screen goes away
|
showingBootScreen = false; // this should avoid the edge case where an alert triggers before the boot screen goes away
|
||||||
@@ -743,7 +786,9 @@ int32_t Screen::runOnce()
|
|||||||
NotificationRenderer::pauseBanner = false;
|
NotificationRenderer::pauseBanner = false;
|
||||||
case Cmd::STOP_BOOT_SCREEN:
|
case Cmd::STOP_BOOT_SCREEN:
|
||||||
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
|
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
|
||||||
setFrames();
|
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
||||||
|
setFrames();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Cmd::NOOP:
|
case Cmd::NOOP:
|
||||||
break;
|
break;
|
||||||
@@ -779,6 +824,7 @@ int32_t Screen::runOnce()
|
|||||||
if (showingNormalScreen) {
|
if (showingNormalScreen) {
|
||||||
// standard screen loop handling here
|
// standard screen loop handling here
|
||||||
if (config.display.auto_screen_carousel_secs > 0 &&
|
if (config.display.auto_screen_carousel_secs > 0 &&
|
||||||
|
NotificationRenderer::current_notification_type != notificationTypeEnum::text_input &&
|
||||||
!Throttle::isWithinTimespanMs(lastScreenTransition, config.display.auto_screen_carousel_secs * 1000)) {
|
!Throttle::isWithinTimespanMs(lastScreenTransition, config.display.auto_screen_carousel_secs * 1000)) {
|
||||||
|
|
||||||
// If an E-Ink display struggles with fast refresh, force carousel to use full refresh instead
|
// If an E-Ink display struggles with fast refresh, force carousel to use full refresh instead
|
||||||
@@ -869,6 +915,11 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver)
|
|||||||
// Called when a frame should be added / removed, or custom frames should be cleared
|
// Called when a frame should be added / removed, or custom frames should be cleared
|
||||||
void Screen::setFrames(FrameFocus focus)
|
void Screen::setFrames(FrameFocus focus)
|
||||||
{
|
{
|
||||||
|
// Block setFrames calls when virtual keyboard is active to prevent overlay interference
|
||||||
|
if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t originalPosition = ui->getUiState()->currentFrame;
|
uint8_t originalPosition = ui->getUiState()->currentFrame;
|
||||||
uint8_t previousFrameCount = framesetInfo.frameCount;
|
uint8_t previousFrameCount = framesetInfo.frameCount;
|
||||||
FramesetInfo fsi; // Location of specific frames, for applying focus parameter
|
FramesetInfo fsi; // Location of specific frames, for applying focus parameter
|
||||||
@@ -891,71 +942,91 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if defined(DISPLAY_CLOCK_FRAME)
|
#if defined(DISPLAY_CLOCK_FRAME)
|
||||||
fsi.positions.clock = numframes;
|
if (!hiddenFrames.clock) {
|
||||||
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
|
fsi.positions.clock = numframes;
|
||||||
: graphics::ClockRenderer::drawDigitalClockFrame;
|
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
|
||||||
indicatorIcons.push_back(digital_icon_clock);
|
: graphics::ClockRenderer::drawDigitalClockFrame;
|
||||||
|
indicatorIcons.push_back(digital_icon_clock);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Declare this early so it’s available in FOCUS_PRESERVE block
|
// Declare this early so it’s available in FOCUS_PRESERVE block
|
||||||
bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message);
|
bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message);
|
||||||
|
|
||||||
fsi.positions.home = numframes;
|
if (!hiddenFrames.home) {
|
||||||
normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused;
|
fsi.positions.home = numframes;
|
||||||
indicatorIcons.push_back(icon_home);
|
normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused;
|
||||||
|
indicatorIcons.push_back(icon_home);
|
||||||
|
}
|
||||||
|
|
||||||
fsi.positions.textMessage = numframes;
|
fsi.positions.textMessage = numframes;
|
||||||
normalFrames[numframes++] = graphics::MessageRenderer::drawTextMessageFrame;
|
normalFrames[numframes++] = graphics::MessageRenderer::drawTextMessageFrame;
|
||||||
indicatorIcons.push_back(icon_mail);
|
indicatorIcons.push_back(icon_mail);
|
||||||
|
|
||||||
#ifndef USE_EINK
|
#ifndef USE_EINK
|
||||||
fsi.positions.nodelist = numframes;
|
if (!hiddenFrames.nodelist) {
|
||||||
normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen;
|
fsi.positions.nodelist = numframes;
|
||||||
indicatorIcons.push_back(icon_nodes);
|
normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen;
|
||||||
|
indicatorIcons.push_back(icon_nodes);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Show detailed node views only on E-Ink builds
|
// Show detailed node views only on E-Ink builds
|
||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
fsi.positions.nodelist_lastheard = numframes;
|
if (!hiddenFrames.nodelist_lastheard) {
|
||||||
normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen;
|
fsi.positions.nodelist_lastheard = numframes;
|
||||||
indicatorIcons.push_back(icon_nodes);
|
normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen;
|
||||||
|
indicatorIcons.push_back(icon_nodes);
|
||||||
fsi.positions.nodelist_hopsignal = numframes;
|
}
|
||||||
normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen;
|
if (!hiddenFrames.nodelist_hopsignal) {
|
||||||
indicatorIcons.push_back(icon_signal);
|
fsi.positions.nodelist_hopsignal = numframes;
|
||||||
|
normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen;
|
||||||
fsi.positions.nodelist_distance = numframes;
|
indicatorIcons.push_back(icon_signal);
|
||||||
normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen;
|
}
|
||||||
indicatorIcons.push_back(icon_distance);
|
if (!hiddenFrames.nodelist_distance) {
|
||||||
|
fsi.positions.nodelist_distance = numframes;
|
||||||
|
normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen;
|
||||||
|
indicatorIcons.push_back(icon_distance);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
#if HAS_GPS
|
#if HAS_GPS
|
||||||
fsi.positions.nodelist_bearings = numframes;
|
if (!hiddenFrames.nodelist_bearings) {
|
||||||
normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses;
|
fsi.positions.nodelist_bearings = numframes;
|
||||||
indicatorIcons.push_back(icon_list);
|
normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses;
|
||||||
|
indicatorIcons.push_back(icon_list);
|
||||||
fsi.positions.gps = numframes;
|
}
|
||||||
normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen;
|
if (!hiddenFrames.gps) {
|
||||||
indicatorIcons.push_back(icon_compass);
|
fsi.positions.gps = numframes;
|
||||||
|
normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen;
|
||||||
|
indicatorIcons.push_back(icon_compass);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
if (RadioLibInterface::instance) {
|
if (RadioLibInterface::instance && !hiddenFrames.lora) {
|
||||||
fsi.positions.lora = numframes;
|
fsi.positions.lora = numframes;
|
||||||
normalFrames[numframes++] = graphics::DebugRenderer::drawLoRaFocused;
|
normalFrames[numframes++] = graphics::DebugRenderer::drawLoRaFocused;
|
||||||
indicatorIcons.push_back(icon_radio);
|
indicatorIcons.push_back(icon_radio);
|
||||||
}
|
}
|
||||||
if (!dismissedFrames.memory) {
|
if (!hiddenFrames.system) {
|
||||||
fsi.positions.memory = numframes;
|
fsi.positions.system = numframes;
|
||||||
normalFrames[numframes++] = graphics::DebugRenderer::drawMemoryUsage;
|
normalFrames[numframes++] = graphics::DebugRenderer::drawSystemScreen;
|
||||||
indicatorIcons.push_back(icon_memory);
|
indicatorIcons.push_back(icon_system);
|
||||||
}
|
}
|
||||||
#if !defined(DISPLAY_CLOCK_FRAME)
|
#if !defined(DISPLAY_CLOCK_FRAME)
|
||||||
fsi.positions.clock = numframes;
|
if (!hiddenFrames.clock) {
|
||||||
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
|
fsi.positions.clock = numframes;
|
||||||
: graphics::ClockRenderer::drawDigitalClockFrame;
|
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
|
||||||
indicatorIcons.push_back(digital_icon_clock);
|
: graphics::ClockRenderer::drawDigitalClockFrame;
|
||||||
|
indicatorIcons.push_back(digital_icon_clock);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
if (!hiddenFrames.chirpy) {
|
||||||
|
fsi.positions.chirpy = numframes;
|
||||||
|
normalFrames[numframes++] = graphics::DebugRenderer::drawChirpy;
|
||||||
|
indicatorIcons.push_back(small_chirpy);
|
||||||
|
}
|
||||||
|
|
||||||
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
|
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
|
||||||
if (!dismissedFrames.wifi && isWifiAvailable()) {
|
if (!hiddenFrames.wifi && isWifiAvailable()) {
|
||||||
fsi.positions.wifi = numframes;
|
fsi.positions.wifi = numframes;
|
||||||
normalFrames[numframes++] = graphics::DebugRenderer::drawDebugInfoWiFiTrampoline;
|
normalFrames[numframes++] = graphics::DebugRenderer::drawDebugInfoWiFiTrampoline;
|
||||||
indicatorIcons.push_back(icon_wifi);
|
indicatorIcons.push_back(icon_wifi);
|
||||||
@@ -997,27 +1068,29 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
if (numMeshNodes > 0)
|
if (numMeshNodes > 0)
|
||||||
numMeshNodes--;
|
numMeshNodes--;
|
||||||
|
|
||||||
// Temporary array to hold favorite node frames
|
if (!hiddenFrames.show_favorites) {
|
||||||
std::vector<FrameCallback> favoriteFrames;
|
// Temporary array to hold favorite node frames
|
||||||
|
std::vector<FrameCallback> favoriteFrames;
|
||||||
|
|
||||||
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||||
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
|
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
|
||||||
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
|
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
|
||||||
favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
|
favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Insert favorite frames *after* collecting them all
|
// Insert favorite frames *after* collecting them all
|
||||||
if (!favoriteFrames.empty()) {
|
if (!favoriteFrames.empty()) {
|
||||||
fsi.positions.firstFavorite = numframes;
|
fsi.positions.firstFavorite = numframes;
|
||||||
for (const auto &f : favoriteFrames) {
|
for (const auto &f : favoriteFrames) {
|
||||||
normalFrames[numframes++] = f;
|
normalFrames[numframes++] = f;
|
||||||
indicatorIcons.push_back(icon_node);
|
indicatorIcons.push_back(icon_node);
|
||||||
|
}
|
||||||
|
fsi.positions.lastFavorite = numframes - 1;
|
||||||
|
} else {
|
||||||
|
fsi.positions.firstFavorite = 255;
|
||||||
|
fsi.positions.lastFavorite = 255;
|
||||||
}
|
}
|
||||||
fsi.positions.lastFavorite = numframes - 1;
|
|
||||||
} else {
|
|
||||||
fsi.positions.firstFavorite = 255;
|
|
||||||
fsi.positions.lastFavorite = 255;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE
|
fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE
|
||||||
@@ -1056,7 +1129,7 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
ui->switchToFrame(fsi.positions.clock);
|
ui->switchToFrame(fsi.positions.clock);
|
||||||
break;
|
break;
|
||||||
case FOCUS_SYSTEM:
|
case FOCUS_SYSTEM:
|
||||||
ui->switchToFrame(fsi.positions.memory);
|
ui->switchToFrame(fsi.positions.system);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FOCUS_PRESERVE:
|
case FOCUS_PRESERVE:
|
||||||
@@ -1084,30 +1157,101 @@ void Screen::setFrameImmediateDraw(FrameCallback *drawFrames)
|
|||||||
setFastFramerate();
|
setFastFramerate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Screen::toggleFrameVisibility(const std::string &frameName)
|
||||||
|
{
|
||||||
|
#ifndef USE_EINK
|
||||||
|
if (frameName == "nodelist") {
|
||||||
|
hiddenFrames.nodelist = !hiddenFrames.nodelist;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_EINK
|
||||||
|
if (frameName == "nodelist_lastheard") {
|
||||||
|
hiddenFrames.nodelist_lastheard = !hiddenFrames.nodelist_lastheard;
|
||||||
|
}
|
||||||
|
if (frameName == "nodelist_hopsignal") {
|
||||||
|
hiddenFrames.nodelist_hopsignal = !hiddenFrames.nodelist_hopsignal;
|
||||||
|
}
|
||||||
|
if (frameName == "nodelist_distance") {
|
||||||
|
hiddenFrames.nodelist_distance = !hiddenFrames.nodelist_distance;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if HAS_GPS
|
||||||
|
if (frameName == "nodelist_bearings") {
|
||||||
|
hiddenFrames.nodelist_bearings = !hiddenFrames.nodelist_bearings;
|
||||||
|
}
|
||||||
|
if (frameName == "gps") {
|
||||||
|
hiddenFrames.gps = !hiddenFrames.gps;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (frameName == "lora") {
|
||||||
|
hiddenFrames.lora = !hiddenFrames.lora;
|
||||||
|
}
|
||||||
|
if (frameName == "clock") {
|
||||||
|
hiddenFrames.clock = !hiddenFrames.clock;
|
||||||
|
}
|
||||||
|
if (frameName == "show_favorites") {
|
||||||
|
hiddenFrames.show_favorites = !hiddenFrames.show_favorites;
|
||||||
|
}
|
||||||
|
if (frameName == "chirpy") {
|
||||||
|
hiddenFrames.chirpy = !hiddenFrames.chirpy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Screen::isFrameHidden(const std::string &frameName) const
|
||||||
|
{
|
||||||
|
#ifndef USE_EINK
|
||||||
|
if (frameName == "nodelist")
|
||||||
|
return hiddenFrames.nodelist;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_EINK
|
||||||
|
if (frameName == "nodelist_lastheard")
|
||||||
|
return hiddenFrames.nodelist_lastheard;
|
||||||
|
if (frameName == "nodelist_hopsignal")
|
||||||
|
return hiddenFrames.nodelist_hopsignal;
|
||||||
|
if (frameName == "nodelist_distance")
|
||||||
|
return hiddenFrames.nodelist_distance;
|
||||||
|
#endif
|
||||||
|
#if HAS_GPS
|
||||||
|
if (frameName == "nodelist_bearings")
|
||||||
|
return hiddenFrames.nodelist_bearings;
|
||||||
|
if (frameName == "gps")
|
||||||
|
return hiddenFrames.gps;
|
||||||
|
#endif
|
||||||
|
if (frameName == "lora")
|
||||||
|
return hiddenFrames.lora;
|
||||||
|
if (frameName == "clock")
|
||||||
|
return hiddenFrames.clock;
|
||||||
|
if (frameName == "show_favorites")
|
||||||
|
return hiddenFrames.show_favorites;
|
||||||
|
if (frameName == "chirpy")
|
||||||
|
return hiddenFrames.chirpy;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Dismisses the currently displayed screen frame, if possible
|
// Dismisses the currently displayed screen frame, if possible
|
||||||
// Relevant for text message, waypoint, others in future?
|
// Relevant for text message, waypoint, others in future?
|
||||||
// Triggered with a CardKB keycombo
|
// Triggered with a CardKB keycombo
|
||||||
void Screen::dismissCurrentFrame()
|
void Screen::hideCurrentFrame()
|
||||||
{
|
{
|
||||||
uint8_t currentFrame = ui->getUiState()->currentFrame;
|
uint8_t currentFrame = ui->getUiState()->currentFrame;
|
||||||
bool dismissed = false;
|
bool dismissed = false;
|
||||||
|
|
||||||
if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) {
|
if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) {
|
||||||
LOG_INFO("Dismiss Text Message");
|
LOG_INFO("Hide Text Message");
|
||||||
devicestate.has_rx_text_message = false;
|
devicestate.has_rx_text_message = false;
|
||||||
memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
|
memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
|
||||||
} else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) {
|
} else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) {
|
||||||
LOG_DEBUG("Dismiss Waypoint");
|
LOG_DEBUG("Hide Waypoint");
|
||||||
devicestate.has_rx_waypoint = false;
|
devicestate.has_rx_waypoint = false;
|
||||||
dismissedFrames.waypoint = true;
|
hiddenFrames.waypoint = true;
|
||||||
dismissed = true;
|
dismissed = true;
|
||||||
} else if (currentFrame == framesetInfo.positions.wifi) {
|
} else if (currentFrame == framesetInfo.positions.wifi) {
|
||||||
LOG_DEBUG("Dismiss WiFi Screen");
|
LOG_DEBUG("Hide WiFi Screen");
|
||||||
dismissedFrames.wifi = true;
|
hiddenFrames.wifi = true;
|
||||||
dismissed = true;
|
dismissed = true;
|
||||||
} else if (currentFrame == framesetInfo.positions.memory) {
|
} else if (currentFrame == framesetInfo.positions.lora) {
|
||||||
LOG_INFO("Dismiss Memory");
|
LOG_INFO("Hide LoRa");
|
||||||
dismissedFrames.memory = true;
|
hiddenFrames.lora = true;
|
||||||
dismissed = true;
|
dismissed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1259,7 +1403,7 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
|
|||||||
// Outgoing message (likely sent from phone)
|
// Outgoing message (likely sent from phone)
|
||||||
devicestate.has_rx_text_message = false;
|
devicestate.has_rx_text_message = false;
|
||||||
memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
|
memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
|
||||||
dismissedFrames.textMessage = true;
|
hiddenFrames.textMessage = true;
|
||||||
hasUnreadMessage = false; // Clear unread state when user replies
|
hasUnreadMessage = false; // Clear unread state when user replies
|
||||||
|
|
||||||
setFrames(FOCUS_PRESERVE); // Stay on same frame, silently update frame list
|
setFrames(FOCUS_PRESERVE); // Stay on same frame, silently update frame list
|
||||||
@@ -1267,21 +1411,28 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
|
|||||||
// Incoming message
|
// Incoming message
|
||||||
devicestate.has_rx_text_message = true; // Needed to include the message frame
|
devicestate.has_rx_text_message = true; // Needed to include the message frame
|
||||||
hasUnreadMessage = true; // Enables mail icon in the header
|
hasUnreadMessage = true; // Enables mail icon in the header
|
||||||
setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view
|
|
||||||
|
|
||||||
// Only wake/force display if the configuration allows it
|
// Always update frame list to include new message, but defer UI updates if virtual keyboard is active
|
||||||
if (shouldWakeOnReceivedMessage()) {
|
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
||||||
setOn(true); // Wake up the screen first
|
setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view
|
||||||
forceDisplay(); // Forces screen redraw
|
|
||||||
|
// Only wake/force display if the configuration allows it
|
||||||
|
if (shouldWakeOnReceivedMessage()) {
|
||||||
|
setOn(true); // Wake up the screen first
|
||||||
|
forceDisplay(); // Forces screen redraw
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Virtual keyboard is active - just mark that frames need regeneration when keyboard closes
|
||||||
|
// The devicestate and hasUnreadMessage are already set above, so message will appear later
|
||||||
|
LOG_DEBUG("Virtual keyboard active - deferring frame list update for new message");
|
||||||
}
|
}
|
||||||
// === Prepare banner content ===
|
|
||||||
|
// Show message alert - either as normal banner or as keyboard popup
|
||||||
|
// === Common variables ===
|
||||||
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
|
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
|
||||||
const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
|
const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
|
||||||
|
|
||||||
const char *msgRaw = reinterpret_cast<const char *>(packet->decoded.payload.bytes);
|
const char *msgRaw = reinterpret_cast<const char *>(packet->decoded.payload.bytes);
|
||||||
|
|
||||||
char banner[256];
|
|
||||||
|
|
||||||
// Check for bell character in message to determine alert type
|
// Check for bell character in message to determine alert type
|
||||||
bool isAlert = false;
|
bool isAlert = false;
|
||||||
for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
|
for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
|
||||||
@@ -1291,21 +1442,57 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAlert) {
|
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
||||||
if (longName && longName[0]) {
|
// === Normal banner mode ===
|
||||||
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
|
char banner[256];
|
||||||
|
if (isAlert) {
|
||||||
|
if (longName && longName[0]) {
|
||||||
|
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
|
||||||
|
} else {
|
||||||
|
strcpy(banner, "Alert Received");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
strcpy(banner, "Alert Received");
|
if (longName && longName[0]) {
|
||||||
|
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
|
||||||
|
} else {
|
||||||
|
strcpy(banner, "New Message");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
screen->showSimpleBanner(banner, 3000);
|
||||||
} else {
|
} else {
|
||||||
if (longName && longName[0]) {
|
// === Virtual keyboard popup mode ===
|
||||||
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
|
char title[64];
|
||||||
|
if (isAlert) {
|
||||||
|
if (longName && longName[0]) {
|
||||||
|
snprintf(title, sizeof(title), "Alert from %s", longName);
|
||||||
|
} else {
|
||||||
|
strcpy(title, "Alert Received");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
strcpy(banner, "New Message");
|
if (longName && longName[0]) {
|
||||||
|
snprintf(title, sizeof(title), "%s", longName);
|
||||||
|
} else {
|
||||||
|
strcpy(title, "New Message");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
screen->showSimpleBanner(banner, 3000);
|
// Prepare content - clean the message content
|
||||||
|
char content[200];
|
||||||
|
size_t contentLen = 0;
|
||||||
|
for (size_t i = 0; i < packet->decoded.payload.size && i < sizeof(content) - 1; i++) {
|
||||||
|
if (msgRaw[i] != '\x07' && msgRaw[i] != '\0') { // Skip bell character and null
|
||||||
|
content[contentLen++] = msgRaw[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content[contentLen] = '\0';
|
||||||
|
|
||||||
|
// Show popup with title and content on virtual keyboard
|
||||||
|
NotificationRenderer::showKeyboardMessagePopupWithTitle(title, content, 5000);
|
||||||
|
|
||||||
|
// Force display update to show the popup immediately
|
||||||
|
setFastFramerate();
|
||||||
|
forceDisplay();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1315,6 +1502,11 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
|
|||||||
// Triggered by MeshModules
|
// Triggered by MeshModules
|
||||||
int Screen::handleUIFrameEvent(const UIFrameEvent *event)
|
int Screen::handleUIFrameEvent(const UIFrameEvent *event)
|
||||||
{
|
{
|
||||||
|
// Block UI frame events when virtual keyboard is active
|
||||||
|
if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (showingNormalScreen) {
|
if (showingNormalScreen) {
|
||||||
// Regenerate the frameset, potentially honoring a module's internal requestFocus() call
|
// Regenerate the frameset, potentially honoring a module's internal requestFocus() call
|
||||||
if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET)
|
if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET)
|
||||||
@@ -1337,6 +1529,16 @@ int Screen::handleInputEvent(const InputEvent *event)
|
|||||||
if (!screenOn)
|
if (!screenOn)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
// Handle text input notifications specially - pass input to virtual keyboard
|
||||||
|
if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
|
||||||
|
NotificationRenderer::inEvent = *event;
|
||||||
|
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
|
||||||
|
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
|
||||||
|
setFastFramerate(); // Draw ASAP
|
||||||
|
ui->update();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef USE_EINK // the screen is the last input handler, so if an event makes it here, we can assume it will prompt a screen draw.
|
#ifdef USE_EINK // the screen is the last input handler, so if an event makes it here, we can assume it will prompt a screen draw.
|
||||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
|
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
|
||||||
EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update
|
EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update
|
||||||
@@ -1374,7 +1576,7 @@ int Screen::handleInputEvent(const InputEvent *event)
|
|||||||
} else if (event->inputEvent == INPUT_BROKER_SELECT) {
|
} else if (event->inputEvent == INPUT_BROKER_SELECT) {
|
||||||
if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) {
|
if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) {
|
||||||
menuHandler::homeBaseMenu();
|
menuHandler::homeBaseMenu();
|
||||||
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.memory) {
|
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.system) {
|
||||||
menuHandler::systemBaseMenu();
|
menuHandler::systemBaseMenu();
|
||||||
#if HAS_GPS
|
#if HAS_GPS
|
||||||
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.gps && gps) {
|
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.gps && gps) {
|
||||||
@@ -1383,7 +1585,7 @@ int Screen::handleInputEvent(const InputEvent *event)
|
|||||||
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.clock) {
|
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.clock) {
|
||||||
menuHandler::clockMenu();
|
menuHandler::clockMenu();
|
||||||
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) {
|
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) {
|
||||||
menuHandler::LoraRegionPicker();
|
menuHandler::loraMenu();
|
||||||
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
|
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
|
||||||
if (devicestate.rx_text_message.from) {
|
if (devicestate.rx_text_message.from) {
|
||||||
menuHandler::messageResponseMenu();
|
menuHandler::messageResponseMenu();
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
|
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
|
||||||
namespace graphics
|
namespace graphics
|
||||||
{
|
{
|
||||||
enum notificationTypeEnum { none, text_banner, selection_picker, node_picker, number_picker };
|
enum notificationTypeEnum { none, text_banner, selection_picker, node_picker, number_picker, text_input };
|
||||||
|
|
||||||
struct BannerOverlayOptions {
|
struct BannerOverlayOptions {
|
||||||
const char *message;
|
const char *message;
|
||||||
@@ -313,6 +313,8 @@ class Screen : public concurrency::OSThread
|
|||||||
|
|
||||||
void showNodePicker(const char *message, uint32_t durationMs, std::function<void(uint32_t)> bannerCallback);
|
void showNodePicker(const char *message, uint32_t durationMs, std::function<void(uint32_t)> bannerCallback);
|
||||||
void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function<void(uint32_t)> bannerCallback);
|
void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function<void(uint32_t)> bannerCallback);
|
||||||
|
void showTextInput(const char *header, const char *initialText, uint32_t durationMs,
|
||||||
|
std::function<void(const std::string &)> textCallback);
|
||||||
|
|
||||||
void requestMenu(graphics::menuHandler::screenMenus menuToShow)
|
void requestMenu(graphics::menuHandler::screenMenus menuToShow)
|
||||||
{
|
{
|
||||||
@@ -591,7 +593,11 @@ class Screen : public concurrency::OSThread
|
|||||||
void setSSLFrames();
|
void setSSLFrames();
|
||||||
|
|
||||||
// Dismiss the currently focussed frame, if possible (e.g. text message, waypoint)
|
// Dismiss the currently focussed frame, if possible (e.g. text message, waypoint)
|
||||||
void dismissCurrentFrame();
|
void hideCurrentFrame();
|
||||||
|
|
||||||
|
// Menu-driven Show / Hide Toggle
|
||||||
|
void toggleFrameVisibility(const std::string &frameName);
|
||||||
|
bool isFrameHidden(const std::string &frameName) const;
|
||||||
|
|
||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
/// Draw an image to remain on E-Ink display after screen off
|
/// Draw an image to remain on E-Ink display after screen off
|
||||||
@@ -653,7 +659,7 @@ class Screen : public concurrency::OSThread
|
|||||||
uint8_t settings = 255;
|
uint8_t settings = 255;
|
||||||
uint8_t wifi = 255;
|
uint8_t wifi = 255;
|
||||||
uint8_t deviceFocused = 255;
|
uint8_t deviceFocused = 255;
|
||||||
uint8_t memory = 255;
|
uint8_t system = 255;
|
||||||
uint8_t gps = 255;
|
uint8_t gps = 255;
|
||||||
uint8_t home = 255;
|
uint8_t home = 255;
|
||||||
uint8_t textMessage = 255;
|
uint8_t textMessage = 255;
|
||||||
@@ -663,6 +669,7 @@ class Screen : public concurrency::OSThread
|
|||||||
uint8_t nodelist_distance = 255;
|
uint8_t nodelist_distance = 255;
|
||||||
uint8_t nodelist_bearings = 255;
|
uint8_t nodelist_bearings = 255;
|
||||||
uint8_t clock = 255;
|
uint8_t clock = 255;
|
||||||
|
uint8_t chirpy = 255;
|
||||||
uint8_t firstFavorite = 255;
|
uint8_t firstFavorite = 255;
|
||||||
uint8_t lastFavorite = 255;
|
uint8_t lastFavorite = 255;
|
||||||
uint8_t lora = 255;
|
uint8_t lora = 255;
|
||||||
@@ -671,12 +678,29 @@ class Screen : public concurrency::OSThread
|
|||||||
uint8_t frameCount = 0;
|
uint8_t frameCount = 0;
|
||||||
} framesetInfo;
|
} framesetInfo;
|
||||||
|
|
||||||
struct DismissedFrames {
|
struct hiddenFrames {
|
||||||
bool textMessage = false;
|
bool textMessage = false;
|
||||||
bool waypoint = false;
|
bool waypoint = false;
|
||||||
bool wifi = false;
|
bool wifi = false;
|
||||||
bool memory = false;
|
bool system = false;
|
||||||
} dismissedFrames;
|
bool home = false;
|
||||||
|
bool clock = false;
|
||||||
|
#ifndef USE_EINK
|
||||||
|
bool nodelist = false;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_EINK
|
||||||
|
bool nodelist_lastheard = false;
|
||||||
|
bool nodelist_hopsignal = false;
|
||||||
|
bool nodelist_distance = false;
|
||||||
|
#endif
|
||||||
|
#if HAS_GPS
|
||||||
|
bool nodelist_bearings = false;
|
||||||
|
bool gps = false;
|
||||||
|
#endif
|
||||||
|
bool lora = false;
|
||||||
|
bool show_favorites = false;
|
||||||
|
bool chirpy = true;
|
||||||
|
} hiddenFrames;
|
||||||
|
|
||||||
/// Try to start drawing ASAP
|
/// Try to start drawing ASAP
|
||||||
void setFastFramerate();
|
void setFastFramerate();
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
#include "graphics/fonts/OLEDDisplayFontsCS.h"
|
#include "graphics/fonts/OLEDDisplayFontsCS.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CROWPANEL_ESP32S3_5_EPAPER
|
#if defined(CROWPANEL_ESP32S3_5_EPAPER) && defined(USE_EINK)
|
||||||
#include "graphics/fonts/EinkDisplayFonts.h"
|
#include "graphics/fonts/EinkDisplayFonts.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -40,6 +40,9 @@
|
|||||||
#ifdef OLED_PL
|
#ifdef OLED_PL
|
||||||
#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_PL // Height: 19
|
#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_PL // Height: 19
|
||||||
#else
|
#else
|
||||||
|
#ifdef OLED_RU
|
||||||
|
#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_RU // Height: 19
|
||||||
|
#else
|
||||||
#ifdef OLED_UA
|
#ifdef OLED_UA
|
||||||
#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_UA // Height: 19
|
#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_UA // Height: 19
|
||||||
#else
|
#else
|
||||||
@@ -50,9 +53,13 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
#ifdef OLED_PL
|
#ifdef OLED_PL
|
||||||
#define FONT_LARGE_LOCAL ArialMT_Plain_24_PL // Height: 28
|
#define FONT_LARGE_LOCAL ArialMT_Plain_24_PL // Height: 28
|
||||||
#else
|
#else
|
||||||
|
#ifdef OLED_RU
|
||||||
|
#define FONT_LARGE_LOCAL ArialMT_Plain_24_RU // Height: 28
|
||||||
|
#else
|
||||||
#ifdef OLED_UA
|
#ifdef OLED_UA
|
||||||
#define FONT_LARGE_LOCAL ArialMT_Plain_24_UA // Height: 28
|
#define FONT_LARGE_LOCAL ArialMT_Plain_24_UA // Height: 28
|
||||||
#else
|
#else
|
||||||
@@ -63,9 +70,10 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
#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(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)) && \
|
||||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||||
// The screen is bigger so use bigger fonts
|
// The screen is bigger so use bigger fonts
|
||||||
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
|
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
|
||||||
@@ -77,7 +85,7 @@
|
|||||||
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
|
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(CROWPANEL_ESP32S3_5_EPAPER)
|
#if defined(CROWPANEL_ESP32S3_5_EPAPER) && defined(USE_EINK)
|
||||||
#undef FONT_SMALL
|
#undef FONT_SMALL
|
||||||
#undef FONT_MEDIUM
|
#undef FONT_MEDIUM
|
||||||
#undef FONT_LARGE
|
#undef FONT_LARGE
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "graphics/SharedUIDisplay.h"
|
#include "graphics/SharedUIDisplay.h"
|
||||||
#include "RTC.h"
|
#include "RTC.h"
|
||||||
#include "graphics/ScreenFonts.h"
|
#include "graphics/ScreenFonts.h"
|
||||||
|
#include "graphics/draw/UIRenderer.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "meshtastic/config.pb.h"
|
#include "meshtastic/config.pb.h"
|
||||||
#include "power.h"
|
#include "power.h"
|
||||||
@@ -16,6 +17,10 @@ void determineResolution(int16_t screenheight, int16_t screenwidth)
|
|||||||
isHighResolution = true;
|
isHighResolution = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (screenwidth > 128 && screenheight <= 64) {
|
||||||
|
isHighResolution = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Special case for Heltec Wireless Tracker v1.1
|
// Special case for Heltec Wireless Tracker v1.1
|
||||||
if (screenwidth == 160 && screenheight == 80) {
|
if (screenwidth == 160 && screenheight == 80) {
|
||||||
isHighResolution = false;
|
isHighResolution = false;
|
||||||
@@ -53,7 +58,7 @@ void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w,
|
|||||||
// *************************
|
// *************************
|
||||||
// * Common Header Drawing *
|
// * Common Header Drawing *
|
||||||
// *************************
|
// *************************
|
||||||
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool battery_only)
|
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool force_no_invert, bool show_date)
|
||||||
{
|
{
|
||||||
constexpr int HEADER_OFFSET_Y = 1;
|
constexpr int HEADER_OFFSET_Y = 1;
|
||||||
y += HEADER_OFFSET_Y;
|
y += HEADER_OFFSET_Y;
|
||||||
@@ -69,7 +74,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
const int screenW = display->getWidth();
|
const int screenW = display->getWidth();
|
||||||
const int screenH = display->getHeight();
|
const int screenH = display->getHeight();
|
||||||
|
|
||||||
if (!battery_only) {
|
if (!force_no_invert) {
|
||||||
// === Inverted Header Background ===
|
// === Inverted Header Background ===
|
||||||
if (isInverted) {
|
if (isInverted) {
|
||||||
display->setColor(BLACK);
|
display->setColor(BLACK);
|
||||||
@@ -187,13 +192,28 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
int timeStrWidth = display->getStringWidth("12:34"); // Default alignment
|
int timeStrWidth = display->getStringWidth("12:34"); // Default alignment
|
||||||
int timeX = screenW - xOffset - timeStrWidth + 4;
|
int timeX = screenW - xOffset - timeStrWidth + 4;
|
||||||
|
|
||||||
if (rtc_sec > 0 && !battery_only) {
|
if (rtc_sec > 0) {
|
||||||
// === Build Time String ===
|
// === Build Time String ===
|
||||||
long hms = (rtc_sec % SEC_PER_DAY + SEC_PER_DAY) % SEC_PER_DAY;
|
long hms = (rtc_sec % SEC_PER_DAY + SEC_PER_DAY) % SEC_PER_DAY;
|
||||||
int hour = hms / SEC_PER_HOUR;
|
int hour = hms / SEC_PER_HOUR;
|
||||||
int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
||||||
snprintf(timeStr, sizeof(timeStr), "%d:%02d", hour, minute);
|
snprintf(timeStr, sizeof(timeStr), "%d:%02d", hour, minute);
|
||||||
|
|
||||||
|
// === Build Date String ===
|
||||||
|
char datetimeStr[25];
|
||||||
|
UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, false);
|
||||||
|
char dateLine[40];
|
||||||
|
|
||||||
|
if (isHighResolution) {
|
||||||
|
snprintf(dateLine, sizeof(dateLine), "%s", datetimeStr);
|
||||||
|
} else {
|
||||||
|
if (hasUnreadMessage) {
|
||||||
|
snprintf(dateLine, sizeof(dateLine), "%s", &datetimeStr[5]);
|
||||||
|
} else {
|
||||||
|
snprintf(dateLine, sizeof(dateLine), "%s", &datetimeStr[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (config.display.use_12h_clock) {
|
if (config.display.use_12h_clock) {
|
||||||
bool isPM = hour >= 12;
|
bool isPM = hour >= 12;
|
||||||
hour %= 12;
|
hour %= 12;
|
||||||
@@ -202,7 +222,11 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
snprintf(timeStr, sizeof(timeStr), "%d:%02d%s", hour, minute, isPM ? "p" : "a");
|
snprintf(timeStr, sizeof(timeStr), "%d:%02d%s", hour, minute, isPM ? "p" : "a");
|
||||||
}
|
}
|
||||||
|
|
||||||
timeStrWidth = display->getStringWidth(timeStr);
|
if (show_date) {
|
||||||
|
timeStrWidth = display->getStringWidth(dateLine);
|
||||||
|
} else {
|
||||||
|
timeStrWidth = display->getStringWidth(timeStr);
|
||||||
|
}
|
||||||
timeX = screenW - xOffset - timeStrWidth + 3;
|
timeX = screenW - xOffset - timeStrWidth + 3;
|
||||||
|
|
||||||
// === Show Mail or Mute Icon to the Left of Time ===
|
// === Show Mail or Mute Icon to the Left of Time ===
|
||||||
@@ -229,7 +253,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
int iconW = 16, iconH = 12;
|
int iconW = 16, iconH = 12;
|
||||||
int iconX = iconRightEdge - iconW;
|
int iconX = iconRightEdge - iconW;
|
||||||
int iconY = textY + (FONT_HEIGHT_SMALL - iconH) / 2 - 1;
|
int iconY = textY + (FONT_HEIGHT_SMALL - iconH) / 2 - 1;
|
||||||
if (isInverted) {
|
if (isInverted && !force_no_invert) {
|
||||||
display->setColor(WHITE);
|
display->setColor(WHITE);
|
||||||
display->fillRect(iconX - 1, iconY - 1, iconW + 3, iconH + 2);
|
display->fillRect(iconX - 1, iconY - 1, iconW + 3, iconH + 2);
|
||||||
display->setColor(BLACK);
|
display->setColor(BLACK);
|
||||||
@@ -244,7 +268,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
} else {
|
} else {
|
||||||
int iconX = iconRightEdge - (mail_width - 2);
|
int iconX = iconRightEdge - (mail_width - 2);
|
||||||
int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2;
|
int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2;
|
||||||
if (isInverted) {
|
if (isInverted && !force_no_invert) {
|
||||||
display->setColor(WHITE);
|
display->setColor(WHITE);
|
||||||
display->fillRect(iconX - 1, iconY - 1, mail_width + 2, mail_height + 2);
|
display->fillRect(iconX - 1, iconY - 1, mail_width + 2, mail_height + 2);
|
||||||
display->setColor(BLACK);
|
display->setColor(BLACK);
|
||||||
@@ -287,10 +311,17 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Draw Time ===
|
if (show_date) {
|
||||||
display->drawString(timeX, textY, timeStr);
|
// === Draw Date ===
|
||||||
if (isBold)
|
display->drawString(timeX, textY, dateLine);
|
||||||
display->drawString(timeX - 1, textY, timeStr);
|
if (isBold)
|
||||||
|
display->drawString(timeX - 1, textY, dateLine);
|
||||||
|
} else {
|
||||||
|
// === Draw Time ===
|
||||||
|
display->drawString(timeX, textY, timeStr);
|
||||||
|
if (isBold)
|
||||||
|
display->drawString(timeX - 1, textY, timeStr);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// === No Time Available: Mail/Mute Icon Moves to Far Right ===
|
// === No Time Available: Mail/Mute Icon Moves to Far Right ===
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ void determineResolution(int16_t screenheight, int16_t screenwidth);
|
|||||||
void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w, int16_t h, int16_t r);
|
void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w, int16_t h, int16_t r);
|
||||||
|
|
||||||
// Shared battery/time/mail header
|
// Shared battery/time/mail header
|
||||||
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool battery_only = false);
|
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool force_no_invert = false,
|
||||||
|
bool show_date = false);
|
||||||
|
|
||||||
const int *getTextPositions(OLEDDisplay *display);
|
const int *getTextPositions(OLEDDisplay *display);
|
||||||
|
|
||||||
|
|||||||
@@ -562,6 +562,91 @@ class LGFX : public lgfx::LGFX_Device
|
|||||||
|
|
||||||
static LGFX *tft = nullptr;
|
static LGFX *tft = nullptr;
|
||||||
|
|
||||||
|
#elif defined(ST7796_CS)
|
||||||
|
#include <LovyanGFX.hpp> // Graphics and font library for ST7796 driver chip
|
||||||
|
|
||||||
|
class LGFX : public lgfx::LGFX_Device
|
||||||
|
{
|
||||||
|
lgfx::Panel_ST7796 _panel_instance;
|
||||||
|
lgfx::Bus_SPI _bus_instance;
|
||||||
|
lgfx::Light_PWM _light_instance;
|
||||||
|
|
||||||
|
public:
|
||||||
|
LGFX(void)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
auto cfg = _bus_instance.config();
|
||||||
|
|
||||||
|
// SPI
|
||||||
|
cfg.spi_host = ST7796_SPI_HOST;
|
||||||
|
cfg.spi_mode = 0;
|
||||||
|
cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing
|
||||||
|
// 80MHz by an integer)
|
||||||
|
cfg.freq_read = SPI_READ_FREQUENCY; // SPI clock when receiving
|
||||||
|
cfg.spi_3wire = false;
|
||||||
|
cfg.use_lock = true; // Set to true to use transaction locking
|
||||||
|
cfg.dma_channel = SPI_DMA_CH_AUTO; // SPI_DMA_CH_AUTO; // Set DMA channel to use (0=not use DMA / 1=1ch / 2=ch /
|
||||||
|
// SPI_DMA_CH_AUTO=auto setting)
|
||||||
|
cfg.pin_sclk = ST7796_SCK; // Set SPI SCLK pin number
|
||||||
|
cfg.pin_mosi = ST7796_SDA; // Set SPI MOSI pin number
|
||||||
|
cfg.pin_miso = ST7796_MISO; // Set SPI MISO pin number (-1 = disable)
|
||||||
|
cfg.pin_dc = ST7796_RS; // Set SPI DC pin number (-1 = disable)
|
||||||
|
|
||||||
|
_bus_instance.config(cfg); // applies the set value to the bus.
|
||||||
|
_panel_instance.setBus(&_bus_instance); // set the bus on the panel.
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Set the display panel control.
|
||||||
|
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
|
||||||
|
|
||||||
|
cfg.pin_cs = ST7796_CS; // Pin number where CS is connected (-1 = disable)
|
||||||
|
cfg.pin_rst = ST7796_RESET; // Pin number where RST is connected (-1 = disable)
|
||||||
|
cfg.pin_busy = ST7796_BUSY; // Pin number where BUSY is connected (-1 = disable)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
#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 = 8; // 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
|
||||||
|
cfg.invert = true; // Set to true if the light/darkness of the panel is reversed
|
||||||
|
cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped
|
||||||
|
cfg.dlen_16bit =
|
||||||
|
false; // Set to true for panels that transmit data length in 16-bit units with 16-bit parallel or SPI
|
||||||
|
cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.)
|
||||||
|
|
||||||
|
_panel_instance.config(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ST7796_BL
|
||||||
|
// Set the backlight control. (delete if not necessary)
|
||||||
|
{
|
||||||
|
auto cfg = _light_instance.config(); // Gets a structure for backlight settings.
|
||||||
|
|
||||||
|
cfg.pin_bl = ST7796_BL; // Pin number to which the backlight is connected
|
||||||
|
cfg.invert = false; // true to invert the brightness of the backlight
|
||||||
|
cfg.freq = 44100;
|
||||||
|
cfg.pwm_channel = 7;
|
||||||
|
|
||||||
|
_light_instance.config(cfg);
|
||||||
|
_panel_instance.setLight(&_light_instance); // Set the backlight on the panel.
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
setPanel(&_panel_instance); // Sets the panel to use.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static LGFX *tft = nullptr;
|
||||||
|
|
||||||
#elif defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER)
|
#elif defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER)
|
||||||
|
|
||||||
#include <LovyanGFX.hpp> // Graphics and font library for ILI9341/ILI9342 driver chip
|
#include <LovyanGFX.hpp> // Graphics and font library for ILI9341/ILI9342 driver chip
|
||||||
@@ -667,33 +752,42 @@ static LGFX *tft = nullptr;
|
|||||||
static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
|
static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
|
||||||
#elif ARCH_PORTDUINO
|
#elif ARCH_PORTDUINO
|
||||||
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
|
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
|
||||||
|
#if defined(LGFX_SDL)
|
||||||
|
#include <lgfx/v1/platforms/sdl/Panel_sdl.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
class LGFX : public lgfx::LGFX_Device
|
class LGFX : public lgfx::LGFX_Device
|
||||||
{
|
{
|
||||||
lgfx::Panel_Device *_panel_instance;
|
|
||||||
lgfx::Bus_SPI _bus_instance;
|
lgfx::Bus_SPI _bus_instance;
|
||||||
|
|
||||||
lgfx::ITouch *_touch_instance;
|
lgfx::ITouch *_touch_instance;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
lgfx::Panel_Device *_panel_instance;
|
||||||
|
|
||||||
LGFX(void)
|
LGFX(void)
|
||||||
{
|
{
|
||||||
if (settingsMap[displayPanel] == st7789)
|
if (portduino_config.displayPanel == st7789)
|
||||||
_panel_instance = new lgfx::Panel_ST7789;
|
_panel_instance = new lgfx::Panel_ST7789;
|
||||||
else if (settingsMap[displayPanel] == st7735)
|
else if (portduino_config.displayPanel == st7735)
|
||||||
_panel_instance = new lgfx::Panel_ST7735;
|
_panel_instance = new lgfx::Panel_ST7735;
|
||||||
else if (settingsMap[displayPanel] == st7735s)
|
else if (portduino_config.displayPanel == st7735s)
|
||||||
_panel_instance = new lgfx::Panel_ST7735S;
|
_panel_instance = new lgfx::Panel_ST7735S;
|
||||||
else if (settingsMap[displayPanel] == st7796)
|
else if (portduino_config.displayPanel == st7796)
|
||||||
_panel_instance = new lgfx::Panel_ST7796;
|
_panel_instance = new lgfx::Panel_ST7796;
|
||||||
else if (settingsMap[displayPanel] == ili9341)
|
else if (portduino_config.displayPanel == ili9341)
|
||||||
_panel_instance = new lgfx::Panel_ILI9341;
|
_panel_instance = new lgfx::Panel_ILI9341;
|
||||||
else if (settingsMap[displayPanel] == ili9342)
|
else if (portduino_config.displayPanel == ili9342)
|
||||||
_panel_instance = new lgfx::Panel_ILI9342;
|
_panel_instance = new lgfx::Panel_ILI9342;
|
||||||
else if (settingsMap[displayPanel] == ili9488)
|
else if (portduino_config.displayPanel == ili9488)
|
||||||
_panel_instance = new lgfx::Panel_ILI9488;
|
_panel_instance = new lgfx::Panel_ILI9488;
|
||||||
else if (settingsMap[displayPanel] == hx8357d)
|
else if (portduino_config.displayPanel == hx8357d)
|
||||||
_panel_instance = new lgfx::Panel_HX8357D;
|
_panel_instance = new lgfx::Panel_HX8357D;
|
||||||
|
#if defined(LGFX_SDL)
|
||||||
|
else if (portduino_config.displayPanel == x11) {
|
||||||
|
_panel_instance = new lgfx::Panel_sdl;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
else {
|
else {
|
||||||
_panel_instance = new lgfx::Panel_NULL;
|
_panel_instance = new lgfx::Panel_NULL;
|
||||||
LOG_ERROR("Unknown display panel configured!");
|
LOG_ERROR("Unknown display panel configured!");
|
||||||
@@ -701,60 +795,66 @@ class LGFX : public lgfx::LGFX_Device
|
|||||||
|
|
||||||
auto buscfg = _bus_instance.config();
|
auto buscfg = _bus_instance.config();
|
||||||
buscfg.spi_mode = 0;
|
buscfg.spi_mode = 0;
|
||||||
buscfg.spi_host = settingsMap[displayspidev];
|
buscfg.spi_host = portduino_config.display_spi_dev_int;
|
||||||
|
|
||||||
buscfg.pin_dc = settingsMap[displayDC]; // Set SPI DC pin number (-1 = disable)
|
buscfg.pin_dc = portduino_config.displayDC.pin; // Set SPI DC pin number (-1 = disable)
|
||||||
|
|
||||||
_bus_instance.config(buscfg); // applies the set value to the bus.
|
_bus_instance.config(buscfg); // applies the set value to the bus.
|
||||||
_panel_instance->setBus(&_bus_instance); // set the bus on the panel.
|
_panel_instance->setBus(&_bus_instance); // set the bus on the panel.
|
||||||
|
|
||||||
auto cfg = _panel_instance->config(); // Gets a structure for display panel settings.
|
auto cfg = _panel_instance->config(); // Gets a structure for display panel settings.
|
||||||
LOG_DEBUG("Width: %d, Height: %d", settingsMap[displayWidth], settingsMap[displayHeight]);
|
LOG_DEBUG("Width: %d, Height: %d", portduino_config.displayWidth, portduino_config.displayHeight);
|
||||||
cfg.pin_cs = settingsMap[displayCS]; // Pin number where CS is connected (-1 = disable)
|
cfg.pin_cs = portduino_config.displayCS.pin; // Pin number where CS is connected (-1 = disable)
|
||||||
cfg.pin_rst = settingsMap[displayReset];
|
cfg.pin_rst = portduino_config.displayReset.pin;
|
||||||
if (settingsMap[displayRotate]) {
|
if (portduino_config.displayRotate) {
|
||||||
cfg.panel_width = settingsMap[displayHeight]; // actual displayable width
|
cfg.panel_width = portduino_config.displayHeight; // actual displayable width
|
||||||
cfg.panel_height = settingsMap[displayWidth]; // actual displayable height
|
cfg.panel_height = portduino_config.displayWidth; // actual displayable height
|
||||||
} else {
|
} else {
|
||||||
cfg.panel_width = settingsMap[displayWidth]; // actual displayable width
|
cfg.panel_width = portduino_config.displayWidth; // actual displayable width
|
||||||
cfg.panel_height = settingsMap[displayHeight]; // actual displayable height
|
cfg.panel_height = portduino_config.displayHeight; // actual displayable height
|
||||||
}
|
}
|
||||||
cfg.offset_x = settingsMap[displayOffsetX]; // Panel offset amount in X direction
|
cfg.offset_x = portduino_config.displayOffsetX; // Panel offset amount in X direction
|
||||||
cfg.offset_y = settingsMap[displayOffsetY]; // Panel offset amount in Y direction
|
cfg.offset_y = portduino_config.displayOffsetY; // Panel offset amount in Y direction
|
||||||
cfg.offset_rotation = settingsMap[displayOffsetRotate]; // Rotation direction value offset 0~7 (4~7 is mirrored)
|
cfg.offset_rotation = portduino_config.displayOffsetRotate; // Rotation direction value offset 0~7 (4~7 is mirrored)
|
||||||
cfg.invert = settingsMap[displayInvert]; // Set to true if the light/darkness of the panel is reversed
|
cfg.invert = portduino_config.displayInvert; // Set to true if the light/darkness of the panel is reversed
|
||||||
|
|
||||||
_panel_instance->config(cfg);
|
_panel_instance->config(cfg);
|
||||||
|
|
||||||
// Configure settings for touch control.
|
// Configure settings for touch control.
|
||||||
if (settingsMap[touchscreenModule]) {
|
if (portduino_config.touchscreenModule) {
|
||||||
if (settingsMap[touchscreenModule] == xpt2046) {
|
if (portduino_config.touchscreenModule == xpt2046) {
|
||||||
_touch_instance = new lgfx::Touch_XPT2046;
|
_touch_instance = new lgfx::Touch_XPT2046;
|
||||||
} else if (settingsMap[touchscreenModule] == stmpe610) {
|
} else if (portduino_config.touchscreenModule == stmpe610) {
|
||||||
_touch_instance = new lgfx::Touch_STMPE610;
|
_touch_instance = new lgfx::Touch_STMPE610;
|
||||||
} else if (settingsMap[touchscreenModule] == ft5x06) {
|
} else if (portduino_config.touchscreenModule == ft5x06) {
|
||||||
_touch_instance = new lgfx::Touch_FT5x06;
|
_touch_instance = new lgfx::Touch_FT5x06;
|
||||||
}
|
}
|
||||||
auto touch_cfg = _touch_instance->config();
|
auto touch_cfg = _touch_instance->config();
|
||||||
|
|
||||||
touch_cfg.pin_cs = settingsMap[touchscreenCS];
|
touch_cfg.pin_cs = portduino_config.touchscreenCS.pin;
|
||||||
touch_cfg.x_min = 0;
|
touch_cfg.x_min = 0;
|
||||||
touch_cfg.x_max = settingsMap[displayHeight] - 1;
|
touch_cfg.x_max = portduino_config.displayHeight - 1;
|
||||||
touch_cfg.y_min = 0;
|
touch_cfg.y_min = 0;
|
||||||
touch_cfg.y_max = settingsMap[displayWidth] - 1;
|
touch_cfg.y_max = portduino_config.displayWidth - 1;
|
||||||
touch_cfg.pin_int = settingsMap[touchscreenIRQ];
|
touch_cfg.pin_int = portduino_config.touchscreenIRQ.pin;
|
||||||
touch_cfg.bus_shared = true;
|
touch_cfg.bus_shared = true;
|
||||||
touch_cfg.offset_rotation = settingsMap[touchscreenRotate];
|
touch_cfg.offset_rotation = portduino_config.touchscreenRotate;
|
||||||
if (settingsMap[touchscreenI2CAddr] != -1) {
|
if (portduino_config.touchscreenI2CAddr != -1) {
|
||||||
touch_cfg.i2c_addr = settingsMap[touchscreenI2CAddr];
|
touch_cfg.i2c_addr = portduino_config.touchscreenI2CAddr;
|
||||||
} else {
|
} else {
|
||||||
touch_cfg.spi_host = settingsMap[touchscreenspidev];
|
touch_cfg.spi_host = portduino_config.touchscreen_spi_dev_int;
|
||||||
}
|
}
|
||||||
|
|
||||||
_touch_instance->config(touch_cfg);
|
_touch_instance->config(touch_cfg);
|
||||||
_panel_instance->setTouch(_touch_instance);
|
_panel_instance->setTouch(_touch_instance);
|
||||||
}
|
}
|
||||||
|
#if defined(LGFX_SDL)
|
||||||
|
if (portduino_config.displayPanel == x11) {
|
||||||
|
lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)_panel_instance;
|
||||||
|
sdl_panel_->setup();
|
||||||
|
sdl_panel_->addKeyCodeMapping(SDLK_RETURN, SDL_SCANCODE_KP_ENTER);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
setPanel(_panel_instance); // Sets the panel to use.
|
setPanel(_panel_instance); // Sets the panel to use.
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -849,9 +949,29 @@ static LGFX *tft = nullptr;
|
|||||||
#include <lgfx/v1/platforms/esp32s3/Bus_RGB.hpp>
|
#include <lgfx/v1/platforms/esp32s3/Bus_RGB.hpp>
|
||||||
#include <lgfx/v1/platforms/esp32s3/Panel_RGB.hpp>
|
#include <lgfx/v1/platforms/esp32s3/Panel_RGB.hpp>
|
||||||
|
|
||||||
|
class PanelInit_ST7701 : public lgfx::Panel_ST7701
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
const uint8_t *getInitCommands(uint8_t listno) const override
|
||||||
|
{
|
||||||
|
// 180 degree hw rotation: vertical flip, horizontal flip
|
||||||
|
static constexpr const uint8_t list1[] = {0x36, 1, 0x10, // MADCTL for vertical flip
|
||||||
|
0xFF, 5, 0x77, 0x01, 0x00, 0x00, 0x10, // Command2 BK0 SEL
|
||||||
|
0xC7, 1, 0x04, // SDIR: X-direction Control (Horizontal Flip)
|
||||||
|
0xFF, 5, 0x77, 0x01, 0x00, 0x00, 0x00, // Command2 BK0 DIS
|
||||||
|
0xFF, 0xFF};
|
||||||
|
switch (listno) {
|
||||||
|
case 1:
|
||||||
|
return list1;
|
||||||
|
default:
|
||||||
|
return lgfx::Panel_ST7701::getInitCommands(listno);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class LGFX : public lgfx::LGFX_Device
|
class LGFX : public lgfx::LGFX_Device
|
||||||
{
|
{
|
||||||
lgfx::Panel_ST7701 _panel_instance;
|
PanelInit_ST7701 _panel_instance;
|
||||||
lgfx::Bus_RGB _bus_instance;
|
lgfx::Bus_RGB _bus_instance;
|
||||||
lgfx::Light_PWM _light_instance;
|
lgfx::Light_PWM _light_instance;
|
||||||
lgfx::Touch_FT5x06 _touch_instance;
|
lgfx::Touch_FT5x06 _touch_instance;
|
||||||
@@ -962,8 +1082,9 @@ static LGFX *tft = nullptr;
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
|
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ST7796_CS) || defined(ILI9341_DRIVER) || \
|
||||||
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST72xx_DE) || (ARCH_PORTDUINO && HAS_SCREEN != 0)
|
defined(ILI9342_DRIVER) || defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST72xx_DE) || \
|
||||||
|
(ARCH_PORTDUINO && HAS_SCREEN != 0)
|
||||||
#include "SPILock.h"
|
#include "SPILock.h"
|
||||||
#include "TFTDisplay.h"
|
#include "TFTDisplay.h"
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
@@ -994,10 +1115,10 @@ TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY g
|
|||||||
backlightEnable = p;
|
backlightEnable = p;
|
||||||
|
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
if (settingsMap[displayRotate]) {
|
if (portduino_config.displayRotate) {
|
||||||
setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayWidth], settingsMap[configNames::displayHeight]);
|
setGeometry(GEOMETRY_RAWMODE, portduino_config.displayWidth, portduino_config.displayWidth);
|
||||||
} else {
|
} else {
|
||||||
setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayHeight], settingsMap[configNames::displayWidth]);
|
setGeometry(GEOMETRY_RAWMODE, portduino_config.displayHeight, portduino_config.displayHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
#elif defined(SCREEN_ROTATE)
|
#elif defined(SCREEN_ROTATE)
|
||||||
@@ -1007,37 +1128,154 @@ TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY g
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TFTDisplay::~TFTDisplay()
|
||||||
|
{
|
||||||
|
// Clean up allocated line pixel buffer to prevent memory leak
|
||||||
|
if (linePixelBuffer != nullptr) {
|
||||||
|
free(linePixelBuffer);
|
||||||
|
linePixelBuffer = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Write the buffer to the display memory
|
// Write the buffer to the display memory
|
||||||
void TFTDisplay::display(bool fromBlank)
|
void TFTDisplay::display(bool fromBlank)
|
||||||
{
|
{
|
||||||
if (fromBlank)
|
if (fromBlank)
|
||||||
tft->fillScreen(TFT_BLACK);
|
tft->fillScreen(TFT_BLACK);
|
||||||
// tft->clear();
|
|
||||||
concurrency::LockGuard g(spiLock);
|
concurrency::LockGuard g(spiLock);
|
||||||
|
|
||||||
uint16_t x, y;
|
uint32_t x, y;
|
||||||
|
uint32_t y_byteIndex;
|
||||||
|
uint8_t y_byteMask;
|
||||||
|
uint32_t x_FirstPixelUpdate;
|
||||||
|
uint32_t x_LastPixelUpdate;
|
||||||
|
bool isset, dblbuf_isset;
|
||||||
|
uint16_t colorTftMesh, colorTftBlack;
|
||||||
|
bool somethingChanged = false;
|
||||||
|
|
||||||
for (y = 0; y < displayHeight; y++) {
|
// Store colors byte-reversed so that TFT_eSPI doesn't have to swap bytes in a separate step
|
||||||
for (x = 0; x < displayWidth; x++) {
|
colorTftMesh = (TFT_MESH >> 8) | ((TFT_MESH & 0xFF) << 8);
|
||||||
auto isset = buffer[x + (y / 8) * displayWidth] & (1 << (y & 7));
|
colorTftBlack = (TFT_BLACK >> 8) | ((TFT_BLACK & 0xFF) << 8);
|
||||||
|
|
||||||
|
y = 0;
|
||||||
|
while (y < displayHeight) {
|
||||||
|
y_byteIndex = (y / 8) * displayWidth;
|
||||||
|
y_byteMask = (1 << (y & 7));
|
||||||
|
|
||||||
|
// Step 1: Do a quick scan of 8 rows together. This allows fast-forwarding over unchanged screen areas.
|
||||||
|
if (y_byteMask == 1) {
|
||||||
if (!fromBlank) {
|
if (!fromBlank) {
|
||||||
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent
|
for (x = 0; x < displayWidth; x++) {
|
||||||
auto dblbuf_isset = buffer_back[x + (y / 8) * displayWidth] & (1 << (y & 7));
|
if (buffer[x + y_byteIndex] != buffer_back[x + y_byteIndex])
|
||||||
if (isset != dblbuf_isset) {
|
break;
|
||||||
tft->drawPixel(x, y, isset ? TFT_MESH : TFT_BLACK);
|
|
||||||
}
|
}
|
||||||
} else if (isset) {
|
} else {
|
||||||
tft->drawPixel(x, y, TFT_MESH);
|
for (x = 0; x < displayWidth; x++) {
|
||||||
|
if (buffer[x + y_byteIndex] != 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (x >= displayWidth) {
|
||||||
|
// No changed pixels found in these 8 rows, fast-forward to the next 8
|
||||||
|
y = y + 8;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 2: Scan each of the 8 rows individually. Find the first pixel in each row that needs updating
|
||||||
|
for (x_FirstPixelUpdate = 0; x_FirstPixelUpdate < displayWidth; x_FirstPixelUpdate++) {
|
||||||
|
isset = buffer[x_FirstPixelUpdate + y_byteIndex] & y_byteMask;
|
||||||
|
|
||||||
|
if (!fromBlank) {
|
||||||
|
// get src pixel in the page based ordering the OLED lib uses
|
||||||
|
dblbuf_isset = buffer_back[x_FirstPixelUpdate + y_byteIndex] & y_byteMask;
|
||||||
|
if (isset != dblbuf_isset) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (isset) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Did we find a pixel that needs updating on this row?
|
||||||
|
if (x_FirstPixelUpdate < displayWidth) {
|
||||||
|
|
||||||
|
// Quickly write out the first changed pixel (saves another array lookup)
|
||||||
|
linePixelBuffer[x_FirstPixelUpdate] = isset ? colorTftMesh : colorTftBlack;
|
||||||
|
x_LastPixelUpdate = x_FirstPixelUpdate;
|
||||||
|
|
||||||
|
// Step 3: copy all remaining pixels in this row into the pixel line buffer,
|
||||||
|
// while also recording the last pixel in the row that needs updating
|
||||||
|
for (x = x_FirstPixelUpdate + 1; x < displayWidth; x++) {
|
||||||
|
isset = buffer[x + y_byteIndex] & y_byteMask;
|
||||||
|
linePixelBuffer[x] = isset ? colorTftMesh : colorTftBlack;
|
||||||
|
|
||||||
|
if (!fromBlank) {
|
||||||
|
dblbuf_isset = buffer_back[x + y_byteIndex] & y_byteMask;
|
||||||
|
if (isset != dblbuf_isset) {
|
||||||
|
x_LastPixelUpdate = x;
|
||||||
|
}
|
||||||
|
} else if (isset) {
|
||||||
|
x_LastPixelUpdate = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Send the changed pixels on this line to the screen as a single block transfer.
|
||||||
|
// This function accepts pixel data MSB first so it can dump the memory straight out the SPI port.
|
||||||
|
tft->pushRect(x_FirstPixelUpdate, y, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1,
|
||||||
|
&linePixelBuffer[x_FirstPixelUpdate]);
|
||||||
|
|
||||||
|
somethingChanged = true;
|
||||||
|
}
|
||||||
|
y++;
|
||||||
}
|
}
|
||||||
// Copy the Buffer to the Back Buffer
|
// Copy the Buffer to the Back Buffer
|
||||||
for (y = 0; y < (displayHeight / 8); y++) {
|
if (somethingChanged)
|
||||||
for (x = 0; x < displayWidth; x++) {
|
memcpy(buffer_back, buffer, displayBufferSize);
|
||||||
uint16_t pos = x + y * displayWidth;
|
}
|
||||||
buffer_back[pos] = buffer[pos];
|
|
||||||
|
void TFTDisplay::sdlLoop()
|
||||||
|
{
|
||||||
|
#if defined(LGFX_SDL)
|
||||||
|
static int lastPressed = 0;
|
||||||
|
static int shuttingDown = false;
|
||||||
|
if (portduino_config.displayPanel == x11) {
|
||||||
|
lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)tft->_panel_instance;
|
||||||
|
if (sdl_panel_->loop() && !shuttingDown) {
|
||||||
|
LOG_WARN("Window Closed!");
|
||||||
|
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||||
|
inputBroker->injectInputEvent(&event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// debounce
|
||||||
|
if (lastPressed != 0 && !lgfx::v1::gpio_in(lastPressed))
|
||||||
|
return;
|
||||||
|
if (!lgfx::v1::gpio_in(37)) {
|
||||||
|
lastPressed = 37;
|
||||||
|
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_RIGHT, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||||
|
inputBroker->injectInputEvent(&event);
|
||||||
|
} else if (!lgfx::v1::gpio_in(36)) {
|
||||||
|
lastPressed = 36;
|
||||||
|
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_UP, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||||
|
inputBroker->injectInputEvent(&event);
|
||||||
|
} else if (!lgfx::v1::gpio_in(38)) {
|
||||||
|
lastPressed = 38;
|
||||||
|
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_DOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||||
|
inputBroker->injectInputEvent(&event);
|
||||||
|
} else if (!lgfx::v1::gpio_in(39)) {
|
||||||
|
lastPressed = 39;
|
||||||
|
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_LEFT, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||||
|
inputBroker->injectInputEvent(&event);
|
||||||
|
} else if (!lgfx::v1::gpio_in(SDL_SCANCODE_KP_ENTER)) {
|
||||||
|
lastPressed = SDL_SCANCODE_KP_ENTER;
|
||||||
|
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SELECT, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||||||
|
inputBroker->injectInputEvent(&event);
|
||||||
|
} else {
|
||||||
|
lastPressed = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a command to the display (low level function)
|
// Send a command to the display (low level function)
|
||||||
@@ -1050,8 +1288,8 @@ void TFTDisplay::sendCommand(uint8_t com)
|
|||||||
backlightEnable->set(true);
|
backlightEnable->set(true);
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
display(true);
|
display(true);
|
||||||
if (settingsMap[displayBacklight] > 0)
|
if (portduino_config.displayBacklight.pin > 0)
|
||||||
digitalWrite(settingsMap[displayBacklight], TFT_BACKLIGHT_ON);
|
digitalWrite(portduino_config.displayBacklight.pin, TFT_BACKLIGHT_ON);
|
||||||
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
|
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
|
||||||
tft->wakeup();
|
tft->wakeup();
|
||||||
tft->powerSaveOff();
|
tft->powerSaveOff();
|
||||||
@@ -1074,8 +1312,8 @@ void TFTDisplay::sendCommand(uint8_t com)
|
|||||||
backlightEnable->set(false);
|
backlightEnable->set(false);
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
tft->clear();
|
tft->clear();
|
||||||
if (settingsMap[displayBacklight] > 0)
|
if (portduino_config.displayBacklight.pin > 0)
|
||||||
digitalWrite(settingsMap[displayBacklight], !TFT_BACKLIGHT_ON);
|
digitalWrite(portduino_config.displayBacklight.pin, !TFT_BACKLIGHT_ON);
|
||||||
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
|
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
|
||||||
tft->sleep();
|
tft->sleep();
|
||||||
tft->powerSaveOn();
|
tft->powerSaveOn();
|
||||||
@@ -1184,15 +1422,23 @@ bool TFTDisplay::connect()
|
|||||||
attachInterrupt(digitalPinToInterrupt(SCREEN_TOUCH_INT), rak14014_tpIntHandle, FALLING);
|
attachInterrupt(digitalPinToInterrupt(SCREEN_TOUCH_INT), rak14014_tpIntHandle, FALLING);
|
||||||
#elif defined(T_DECK) || defined(PICOMPUTER_S3) || defined(CHATTER_2)
|
#elif defined(T_DECK) || defined(PICOMPUTER_S3) || defined(CHATTER_2)
|
||||||
tft->setRotation(1); // T-Deck has the TFT in landscape
|
tft->setRotation(1); // T-Deck has the TFT in landscape
|
||||||
#elif defined(T_WATCH_S3) || defined(SENSECAP_INDICATOR)
|
#elif defined(T_WATCH_S3)
|
||||||
tft->setRotation(2); // T-Watch S3 left-handed orientation
|
tft->setRotation(2); // T-Watch S3 left-handed orientation
|
||||||
#elif ARCH_PORTDUINO
|
#elif ARCH_PORTDUINO || defined(SENSECAP_INDICATOR) || defined(T_LORA_PAGER)
|
||||||
tft->setRotation(0); // use config.yaml to set rotation
|
tft->setRotation(0); // use config.yaml to set rotation
|
||||||
#else
|
#else
|
||||||
tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
|
tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
|
||||||
#endif
|
#endif
|
||||||
tft->fillScreen(TFT_BLACK);
|
tft->fillScreen(TFT_BLACK);
|
||||||
|
|
||||||
|
if (this->linePixelBuffer == NULL) {
|
||||||
|
this->linePixelBuffer = (uint16_t *)malloc(sizeof(uint16_t) * displayWidth);
|
||||||
|
|
||||||
|
if (!this->linePixelBuffer) {
|
||||||
|
LOG_ERROR("Not enough memory to create TFT line buffer\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,13 @@ class TFTDisplay : public OLEDDisplay
|
|||||||
*/
|
*/
|
||||||
TFTDisplay(uint8_t, int, int, OLEDDISPLAY_GEOMETRY, HW_I2C);
|
TFTDisplay(uint8_t, int, int, OLEDDISPLAY_GEOMETRY, HW_I2C);
|
||||||
|
|
||||||
|
// Destructor to clean up allocated memory
|
||||||
|
~TFTDisplay();
|
||||||
|
|
||||||
// Write the buffer to the display memory
|
// Write the buffer to the display memory
|
||||||
virtual void display() override { display(false); };
|
virtual void display() override { display(false); };
|
||||||
virtual void display(bool fromBlank);
|
virtual void display(bool fromBlank);
|
||||||
|
void sdlLoop();
|
||||||
|
|
||||||
// Turn the display upside down
|
// Turn the display upside down
|
||||||
virtual void flipScreenVertically();
|
virtual void flipScreenVertically();
|
||||||
@@ -57,4 +61,6 @@ class TFTDisplay : public OLEDDisplay
|
|||||||
|
|
||||||
// Connect to the display
|
// Connect to the display
|
||||||
virtual bool connect() override;
|
virtual bool connect() override;
|
||||||
|
|
||||||
|
uint16_t *linePixelBuffer = nullptr;
|
||||||
};
|
};
|
||||||
738
src/graphics/VirtualKeyboard.cpp
Normal file
738
src/graphics/VirtualKeyboard.cpp
Normal file
@@ -0,0 +1,738 @@
|
|||||||
|
#include "VirtualKeyboard.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
#include "graphics/Screen.h"
|
||||||
|
#include "graphics/ScreenFonts.h"
|
||||||
|
#include "graphics/SharedUIDisplay.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace graphics
|
||||||
|
{
|
||||||
|
|
||||||
|
VirtualKeyboard::VirtualKeyboard() : cursorRow(0), cursorCol(0), lastActivityTime(millis())
|
||||||
|
{
|
||||||
|
initializeKeyboard();
|
||||||
|
// Set cursor to H(2, 5)
|
||||||
|
cursorRow = 2;
|
||||||
|
cursorCol = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
VirtualKeyboard::~VirtualKeyboard() {}
|
||||||
|
|
||||||
|
void VirtualKeyboard::initializeKeyboard()
|
||||||
|
{
|
||||||
|
// New 4 row, 11 column keyboard layout:
|
||||||
|
static const char LAYOUT[KEYBOARD_ROWS][KEYBOARD_COLS] = {{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '\b'},
|
||||||
|
{'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '\n'},
|
||||||
|
{'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', ' '},
|
||||||
|
{'z', 'x', 'c', 'v', 'b', 'n', 'm', '.', ',', '?', '\x1b'}};
|
||||||
|
|
||||||
|
// Derive layout dimensions and assert they match the configured keyboard grid
|
||||||
|
constexpr int LAYOUT_ROWS = (int)(sizeof(LAYOUT) / sizeof(LAYOUT[0]));
|
||||||
|
constexpr int LAYOUT_COLS = (int)(sizeof(LAYOUT[0]) / sizeof(LAYOUT[0][0]));
|
||||||
|
static_assert(LAYOUT_ROWS == KEYBOARD_ROWS, "LAYOUT rows must equal KEYBOARD_ROWS");
|
||||||
|
static_assert(LAYOUT_COLS == KEYBOARD_COLS, "LAYOUT cols must equal KEYBOARD_COLS");
|
||||||
|
|
||||||
|
// Initialize all keys to empty first
|
||||||
|
for (int row = 0; row < LAYOUT_ROWS; row++) {
|
||||||
|
for (int col = 0; col < LAYOUT_COLS; col++) {
|
||||||
|
keyboard[row][col] = {0, VK_CHAR, 0, 0, 0, 0};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill keyboard from the 2D layout
|
||||||
|
for (int row = 0; row < LAYOUT_ROWS; row++) {
|
||||||
|
for (int col = 0; col < LAYOUT_COLS; col++) {
|
||||||
|
char ch = LAYOUT[row][col];
|
||||||
|
// No empty slots in the simplified layout
|
||||||
|
|
||||||
|
VirtualKeyType type = VK_CHAR;
|
||||||
|
if (ch == '\b') {
|
||||||
|
type = VK_BACKSPACE;
|
||||||
|
} else if (ch == '\n') {
|
||||||
|
type = VK_ENTER;
|
||||||
|
} else if (ch == '\x1b') { // ESC
|
||||||
|
type = VK_ESC;
|
||||||
|
} else if (ch == ' ') {
|
||||||
|
type = VK_SPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make action keys wider to fit text while keeping the last column aligned
|
||||||
|
uint8_t width = (type == VK_BACKSPACE || type == VK_ENTER || type == VK_SPACE) ? (KEY_WIDTH * 3) : KEY_WIDTH;
|
||||||
|
keyboard[row][col] = {ch, type, (uint8_t)(col * KEY_WIDTH), (uint8_t)(row * KEY_HEIGHT), width, KEY_HEIGHT};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::draw(OLEDDisplay *display, int16_t offsetX, int16_t offsetY)
|
||||||
|
{
|
||||||
|
// Repeat ticking is driven by NotificationRenderer once per frame
|
||||||
|
// Base styles
|
||||||
|
display->setColor(WHITE);
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
|
// Screen geometry
|
||||||
|
const int screenW = display->getWidth();
|
||||||
|
const int screenH = display->getHeight();
|
||||||
|
|
||||||
|
// Decide wide-screen mode: if there is comfortable width, allow taller keys and reserve fixed width for last column labels
|
||||||
|
// Heuristic: if screen width >= 200px (e.g., 240x135), treat as wide
|
||||||
|
const bool isWide = screenW >= 200;
|
||||||
|
|
||||||
|
// Determine last-column label max width
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
const int wENTER = display->getStringWidth("ENTER");
|
||||||
|
int lastColLabelW = wENTER; // ENTER is usually the widest
|
||||||
|
// Smaller padding on very small screens to avoid excessive whitespace
|
||||||
|
const int lastColPad = (screenW <= 128 ? 2 : 6);
|
||||||
|
const int reservedLastColW = lastColLabelW + lastColPad; // reserved width for last column keys
|
||||||
|
|
||||||
|
// Always reserve width for the rightmost text column to avoid overlap on small screens
|
||||||
|
int cellW = 0;
|
||||||
|
int leftoverW = 0;
|
||||||
|
{
|
||||||
|
const int leftCols = KEYBOARD_COLS - 1; // 10 input characters
|
||||||
|
int usableW = screenW - reservedLastColW;
|
||||||
|
if (usableW < leftCols) {
|
||||||
|
// Guard: ensure at least 1px per left cell if labels are extremely wide (unlikely)
|
||||||
|
usableW = leftCols;
|
||||||
|
}
|
||||||
|
cellW = usableW / leftCols;
|
||||||
|
leftoverW = usableW - cellW * leftCols; // distribute extra pixels over left columns (left to right)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic key geometry
|
||||||
|
int cellH = KEY_HEIGHT;
|
||||||
|
int keyboardStartY = 0;
|
||||||
|
if (screenH <= 64) {
|
||||||
|
const int headerHeight = headerText.empty() ? 0 : (FONT_HEIGHT_SMALL - 2);
|
||||||
|
const int gapBelowHeader = 0;
|
||||||
|
const int singleLineBoxHeight = FONT_HEIGHT_SMALL;
|
||||||
|
const int gapAboveKeyboard = 0;
|
||||||
|
keyboardStartY = offsetY + headerHeight + gapBelowHeader + singleLineBoxHeight + gapAboveKeyboard;
|
||||||
|
if (keyboardStartY < 0)
|
||||||
|
keyboardStartY = 0;
|
||||||
|
if (keyboardStartY > screenH)
|
||||||
|
keyboardStartY = screenH;
|
||||||
|
int keyboardHeight = screenH - keyboardStartY;
|
||||||
|
cellH = std::max(1, keyboardHeight / KEYBOARD_ROWS);
|
||||||
|
} else if (isWide) {
|
||||||
|
// For wide screens (e.g., T114 240x135), prefer square keys: height equals left-column key width.
|
||||||
|
cellH = std::max((int)KEY_HEIGHT, cellW);
|
||||||
|
|
||||||
|
// Guarantee at least 2 lines of input are visible by reducing cell height minimally if needed.
|
||||||
|
// Replicate the spacing used in drawInputArea(): headerGap=1, box-to-header gap=1, gap above keyboard=1
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
const int headerHeight = headerText.empty() ? 0 : (FONT_HEIGHT_SMALL + 1);
|
||||||
|
const int headerToBoxGap = 1;
|
||||||
|
const int gapAboveKb = 1;
|
||||||
|
const int minBoxHeightForTwoLines = 2 * FONT_HEIGHT_SMALL + 2; // inner 1px top/bottom
|
||||||
|
int maxKeyboardHeight = screenH - (offsetY + headerHeight + headerToBoxGap + minBoxHeightForTwoLines + gapAboveKb);
|
||||||
|
int maxCellHAllowed = maxKeyboardHeight / KEYBOARD_ROWS;
|
||||||
|
if (maxCellHAllowed < (int)KEY_HEIGHT)
|
||||||
|
maxCellHAllowed = KEY_HEIGHT;
|
||||||
|
if (maxCellHAllowed > 0 && cellH > maxCellHAllowed) {
|
||||||
|
cellH = maxCellHAllowed;
|
||||||
|
}
|
||||||
|
// Keyboard placement from bottom for wide screens
|
||||||
|
int keyboardHeight = KEYBOARD_ROWS * cellH;
|
||||||
|
keyboardStartY = screenH - keyboardHeight;
|
||||||
|
if (keyboardStartY < 0)
|
||||||
|
keyboardStartY = 0;
|
||||||
|
} else {
|
||||||
|
// Default (non-wide, non-64px) behavior: use key height heuristic and place at bottom
|
||||||
|
cellH = KEY_HEIGHT;
|
||||||
|
int keyboardHeight = KEYBOARD_ROWS * cellH;
|
||||||
|
keyboardStartY = screenH - keyboardHeight;
|
||||||
|
if (keyboardStartY < 0)
|
||||||
|
keyboardStartY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw input area above keyboard
|
||||||
|
drawInputArea(display, offsetX, offsetY, keyboardStartY);
|
||||||
|
|
||||||
|
// Precompute per-column x and width with leftover distributed over left columns for even spacing
|
||||||
|
int colX[KEYBOARD_COLS];
|
||||||
|
int colW[KEYBOARD_COLS];
|
||||||
|
int runningX = offsetX;
|
||||||
|
for (int col = 0; col < KEYBOARD_COLS - 1; ++col) {
|
||||||
|
int wcol = cellW + (col < leftoverW ? 1 : 0);
|
||||||
|
colX[col] = runningX;
|
||||||
|
colW[col] = wcol;
|
||||||
|
runningX += wcol;
|
||||||
|
}
|
||||||
|
// Last column
|
||||||
|
colX[KEYBOARD_COLS - 1] = runningX;
|
||||||
|
colW[KEYBOARD_COLS - 1] = reservedLastColW;
|
||||||
|
|
||||||
|
// Draw keyboard grid
|
||||||
|
for (int row = 0; row < KEYBOARD_ROWS; row++) {
|
||||||
|
for (int col = 0; col < KEYBOARD_COLS; col++) {
|
||||||
|
const VirtualKey &k = keyboard[row][col];
|
||||||
|
if (k.character != 0 || k.type != VK_CHAR) {
|
||||||
|
const bool isLastCol = (col == KEYBOARD_COLS - 1);
|
||||||
|
int x = colX[col];
|
||||||
|
int w = colW[col];
|
||||||
|
int y = offsetY + keyboardStartY + row * cellH;
|
||||||
|
int h = cellH;
|
||||||
|
bool selected = (row == cursorRow && col == cursorCol);
|
||||||
|
drawKey(display, k, selected, x, y, (uint8_t)w, (uint8_t)h, isLastCol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::drawInputArea(OLEDDisplay *display, int16_t offsetX, int16_t offsetY, int16_t keyboardStartY)
|
||||||
|
{
|
||||||
|
display->setColor(WHITE);
|
||||||
|
|
||||||
|
const int screenWidth = display->getWidth();
|
||||||
|
const int screenHeight = display->getHeight();
|
||||||
|
// Use the standard small font metrics for input box sizing (restore original size)
|
||||||
|
const int inputLineH = FONT_HEIGHT_SMALL;
|
||||||
|
|
||||||
|
// Header uses the standard small (which may be larger on big screens)
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
int headerHeight = 0;
|
||||||
|
if (!headerText.empty()) {
|
||||||
|
// Draw header and reserve exact font height (plus a tighter gap) to maximize input area
|
||||||
|
display->drawString(offsetX + 2, offsetY, headerText.c_str());
|
||||||
|
if (screenHeight <= 64) {
|
||||||
|
headerHeight = FONT_HEIGHT_SMALL - 2; // 11px
|
||||||
|
} else {
|
||||||
|
headerHeight = FONT_HEIGHT_SMALL; // no extra padding baked in
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int boxX = offsetX;
|
||||||
|
const int boxWidth = screenWidth;
|
||||||
|
int boxY;
|
||||||
|
int boxHeight;
|
||||||
|
if (screenHeight <= 64) {
|
||||||
|
const int gapBelowHeader = 0;
|
||||||
|
const int fixedBoxHeight = inputLineH;
|
||||||
|
const int gapAboveKeyboard = 0;
|
||||||
|
boxY = offsetY + headerHeight + gapBelowHeader;
|
||||||
|
boxHeight = fixedBoxHeight;
|
||||||
|
if (boxY + boxHeight + gapAboveKeyboard > keyboardStartY) {
|
||||||
|
int over = boxY + boxHeight + gapAboveKeyboard - keyboardStartY;
|
||||||
|
boxHeight = std::max(1, fixedBoxHeight - over);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const int gapBelowHeader = 1;
|
||||||
|
int gapAboveKeyboard = 1;
|
||||||
|
int tmpBoxY = offsetY + headerHeight + gapBelowHeader;
|
||||||
|
const int minBoxHeight = inputLineH + 2;
|
||||||
|
int availableH = keyboardStartY - tmpBoxY - gapAboveKeyboard;
|
||||||
|
if (availableH < minBoxHeight)
|
||||||
|
availableH = minBoxHeight;
|
||||||
|
boxY = tmpBoxY;
|
||||||
|
boxHeight = availableH;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw box border
|
||||||
|
display->drawRect(boxX, boxY, boxWidth, boxHeight);
|
||||||
|
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
|
// Text rendering: multi-line if space allows (>= 2 lines), else single-line with leading ellipsis
|
||||||
|
const int textX = boxX + 2;
|
||||||
|
const int maxTextWidth = boxWidth - 4;
|
||||||
|
const int maxLines = (boxHeight - 2) / inputLineH;
|
||||||
|
if (maxLines >= 2) {
|
||||||
|
// Inner bounds for caret clamping
|
||||||
|
const int innerLeft = boxX + 1;
|
||||||
|
const int innerRight = boxX + boxWidth - 2;
|
||||||
|
const int innerTop = boxY + 1;
|
||||||
|
const int innerBottom = boxY + boxHeight - 2;
|
||||||
|
|
||||||
|
// Wrap text greedily into lines that fit maxTextWidth
|
||||||
|
std::vector<std::string> lines;
|
||||||
|
{
|
||||||
|
std::string remaining = inputText;
|
||||||
|
while (!remaining.empty()) {
|
||||||
|
int bestLen = 0;
|
||||||
|
for (int len = 1; len <= (int)remaining.size(); ++len) {
|
||||||
|
int w = display->getStringWidth(remaining.substr(0, len).c_str());
|
||||||
|
if (w <= maxTextWidth)
|
||||||
|
bestLen = len;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (bestLen == 0) {
|
||||||
|
// At least show one character to make progress
|
||||||
|
bestLen = 1;
|
||||||
|
}
|
||||||
|
lines.emplace_back(remaining.substr(0, bestLen));
|
||||||
|
remaining.erase(0, bestLen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool scrolledUp = ((int)lines.size() > maxLines);
|
||||||
|
int caretX = textX;
|
||||||
|
int caretY = innerTop;
|
||||||
|
|
||||||
|
// Leave a small top gap to render '...' without replacing the first line
|
||||||
|
const int topInset = 2;
|
||||||
|
const int lineStep = std::max(1, inputLineH - 1); // slightly tighter than font height
|
||||||
|
int lineY = innerTop + topInset;
|
||||||
|
|
||||||
|
if (scrolledUp) {
|
||||||
|
// Draw three small dots centered horizontally, vertically at the midpoint of the gap
|
||||||
|
// between the inner top and the first line's top baseline. This avoids using a tall glyph.
|
||||||
|
const int firstLineTop = lineY; // baseline top for the first visible line
|
||||||
|
const int gapMidY = innerTop + (firstLineTop - innerTop) / 2 + 1; // shift down 1px as requested
|
||||||
|
const int centerX = boxX + boxWidth / 2;
|
||||||
|
const int dotSpacing = 3; // px between dots
|
||||||
|
const int dotSize = 1; // small square dot
|
||||||
|
display->fillRect(centerX - dotSpacing, gapMidY, dotSize, dotSize);
|
||||||
|
display->fillRect(centerX, gapMidY, dotSize, dotSize);
|
||||||
|
display->fillRect(centerX + dotSpacing, gapMidY, dotSize, dotSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// How many lines fit with our top inset and tighter step
|
||||||
|
const int linesCapacity = std::max(1, (innerBottom - lineY + 1) / lineStep);
|
||||||
|
const int linesToShow = std::min((int)lines.size(), linesCapacity);
|
||||||
|
const int startIndex = scrolledUp ? ((int)lines.size() - linesToShow) : 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < linesToShow; ++i) {
|
||||||
|
const std::string &chunk = lines[startIndex + i];
|
||||||
|
display->drawString(textX, lineY, chunk.c_str());
|
||||||
|
caretX = textX + display->getStringWidth(chunk.c_str());
|
||||||
|
caretY = lineY;
|
||||||
|
lineY += lineStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw caret at end of the last visible line
|
||||||
|
int caretPadY = 2;
|
||||||
|
if (boxHeight >= inputLineH + 4)
|
||||||
|
caretPadY = 3;
|
||||||
|
int cursorTop = caretY + caretPadY;
|
||||||
|
// Use lineStep so caret height matches the row spacing
|
||||||
|
int cursorH = lineStep - caretPadY * 2;
|
||||||
|
if (cursorH < 1)
|
||||||
|
cursorH = 1;
|
||||||
|
// Clamp vertical bounds to stay inside the inner rect
|
||||||
|
if (cursorTop < innerTop)
|
||||||
|
cursorTop = innerTop;
|
||||||
|
if (cursorTop + cursorH - 1 > innerBottom)
|
||||||
|
cursorH = innerBottom - cursorTop + 1;
|
||||||
|
if (cursorH < 1)
|
||||||
|
cursorH = 1;
|
||||||
|
// Only draw if cursor is inside inner bounds
|
||||||
|
if (caretX >= innerLeft && caretX <= innerRight) {
|
||||||
|
display->drawVerticalLine(caretX, cursorTop, cursorH);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::string displayText = inputText;
|
||||||
|
int textW = display->getStringWidth(displayText.c_str());
|
||||||
|
std::string scrolled = displayText;
|
||||||
|
if (textW > maxTextWidth) {
|
||||||
|
// Trim from the left until it fits
|
||||||
|
while (textW > maxTextWidth && !scrolled.empty()) {
|
||||||
|
scrolled.erase(0, 1);
|
||||||
|
textW = display->getStringWidth(scrolled.c_str());
|
||||||
|
}
|
||||||
|
// Add leading ellipsis and ensure it still fits
|
||||||
|
if (scrolled != displayText) {
|
||||||
|
scrolled = "..." + scrolled;
|
||||||
|
textW = display->getStringWidth(scrolled.c_str());
|
||||||
|
// If adding ellipsis causes overflow, trim more after the ellipsis
|
||||||
|
while (textW > maxTextWidth && scrolled.size() > 3) {
|
||||||
|
scrolled.erase(3, 1); // remove chars after the ellipsis
|
||||||
|
textW = display->getStringWidth(scrolled.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Keep textW in sync with what we draw
|
||||||
|
textW = display->getStringWidth(scrolled.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
int textY;
|
||||||
|
if (screenHeight <= 64) {
|
||||||
|
textY = boxY + (boxHeight - inputLineH) / 2;
|
||||||
|
} else {
|
||||||
|
const int innerLeft = boxX + 1;
|
||||||
|
const int innerRight = boxX + boxWidth - 2;
|
||||||
|
const int innerTop = boxY + 1;
|
||||||
|
const int innerBottom = boxY + boxHeight - 2;
|
||||||
|
|
||||||
|
// Center text vertically within inner box for single-line, then clamp so it never overlaps borders
|
||||||
|
int innerH = innerBottom - innerTop + 1;
|
||||||
|
textY = innerTop + std::max(0, (innerH - inputLineH) / 2);
|
||||||
|
// Clamp fully inside the inner rect
|
||||||
|
if (textY < innerTop)
|
||||||
|
textY = innerTop;
|
||||||
|
int maxTop = innerBottom - inputLineH + 1;
|
||||||
|
if (textY > maxTop)
|
||||||
|
textY = maxTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scrolled.empty()) {
|
||||||
|
display->drawString(textX, textY, scrolled.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
int cursorX = textX + textW;
|
||||||
|
if (screenHeight > 64) {
|
||||||
|
const int innerRight = boxX + boxWidth - 2;
|
||||||
|
if (cursorX > innerRight)
|
||||||
|
cursorX = innerRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cursorTop, cursorH;
|
||||||
|
if (screenHeight <= 64) {
|
||||||
|
cursorH = 10;
|
||||||
|
cursorTop = boxY + (boxHeight - cursorH) / 2;
|
||||||
|
} else {
|
||||||
|
const int innerLeft = boxX + 1;
|
||||||
|
const int innerRight = boxX + boxWidth - 2;
|
||||||
|
const int innerTop = boxY + 1;
|
||||||
|
const int innerBottom = boxY + boxHeight - 2;
|
||||||
|
|
||||||
|
cursorTop = boxY + 2;
|
||||||
|
cursorH = boxHeight - 4;
|
||||||
|
if (cursorH < 1)
|
||||||
|
cursorH = 1;
|
||||||
|
if (cursorTop < innerTop)
|
||||||
|
cursorTop = innerTop;
|
||||||
|
if (cursorTop + cursorH - 1 > innerBottom)
|
||||||
|
cursorH = innerBottom - cursorTop + 1;
|
||||||
|
if (cursorH < 1)
|
||||||
|
cursorH = 1;
|
||||||
|
|
||||||
|
if (cursorX < innerLeft || cursorX > innerRight)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
display->drawVerticalLine(cursorX, cursorTop, cursorH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::drawKey(OLEDDisplay *display, const VirtualKey &key, bool selected, int16_t x, int16_t y, uint8_t width,
|
||||||
|
uint8_t height, bool isLastCol)
|
||||||
|
{
|
||||||
|
// Draw key content
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
const int fontH = FONT_HEIGHT_SMALL;
|
||||||
|
// Build label and metrics first
|
||||||
|
std::string keyText;
|
||||||
|
if (key.type == VK_BACKSPACE || key.type == VK_ENTER || key.type == VK_SPACE || key.type == VK_ESC) {
|
||||||
|
// Keep literal text labels for the action keys on the rightmost column
|
||||||
|
keyText = (key.type == VK_BACKSPACE) ? "BACK"
|
||||||
|
: (key.type == VK_ENTER) ? "ENTER"
|
||||||
|
: (key.type == VK_SPACE) ? "SPACE"
|
||||||
|
: (key.type == VK_ESC) ? "ESC"
|
||||||
|
: "";
|
||||||
|
} else {
|
||||||
|
char c = getCharForKey(key, false);
|
||||||
|
if (c >= 'a' && c <= 'z') {
|
||||||
|
c = c - 'a' + 'A';
|
||||||
|
}
|
||||||
|
keyText = (key.character == ' ' || key.character == '_') ? "_" : std::string(1, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
int textWidth = display->getStringWidth(keyText.c_str());
|
||||||
|
// Label alignment
|
||||||
|
// - Rightmost action column: right-align text with a small right padding (~2px) so it hugs screen edge neatly.
|
||||||
|
// - Other keys: center horizontally; use ceil-style rounding to avoid appearing left-biased on odd widths.
|
||||||
|
int textX;
|
||||||
|
if (isLastCol) {
|
||||||
|
const int rightPad = 1;
|
||||||
|
textX = x + width - textWidth - rightPad;
|
||||||
|
if (textX < x)
|
||||||
|
textX = x; // guard
|
||||||
|
} else {
|
||||||
|
if (display->getHeight() <= 64 && (key.character >= '0' && key.character <= '9')) {
|
||||||
|
textX = x + (width - textWidth + 1) / 2;
|
||||||
|
} else {
|
||||||
|
textX = x + (width - textWidth) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int contentTop = y;
|
||||||
|
int contentH = height;
|
||||||
|
if (selected) {
|
||||||
|
display->setColor(WHITE);
|
||||||
|
bool isAction = (key.type == VK_BACKSPACE || key.type == VK_ENTER || key.type == VK_SPACE || key.type == VK_ESC);
|
||||||
|
|
||||||
|
if (display->getHeight() <= 64 && !isAction) {
|
||||||
|
display->fillRect(x, y, width, height);
|
||||||
|
} else if (isAction) {
|
||||||
|
const int padX = 1;
|
||||||
|
const int padY = 2;
|
||||||
|
int hlW = textWidth + padX * 2;
|
||||||
|
int hlX = textX - padX;
|
||||||
|
|
||||||
|
if (hlX < x) {
|
||||||
|
hlW -= (x - hlX);
|
||||||
|
hlX = x;
|
||||||
|
}
|
||||||
|
int maxW = (x + width) - hlX;
|
||||||
|
if (hlW > maxW)
|
||||||
|
hlW = maxW;
|
||||||
|
if (hlW < 1)
|
||||||
|
hlW = 1;
|
||||||
|
|
||||||
|
int hlH = std::min(fontH + padY * 2, (int)height);
|
||||||
|
int hlY = y + (height - hlH) / 2;
|
||||||
|
display->fillRect(hlX, hlY, hlW, hlH);
|
||||||
|
contentTop = hlY;
|
||||||
|
contentH = hlH;
|
||||||
|
} else {
|
||||||
|
display->fillRect(x, y, width, height);
|
||||||
|
}
|
||||||
|
display->setColor(BLACK);
|
||||||
|
} else {
|
||||||
|
display->setColor(WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
int centeredTextY;
|
||||||
|
if (display->getHeight() <= 64) {
|
||||||
|
centeredTextY = y + (height - fontH) / 2;
|
||||||
|
} else {
|
||||||
|
centeredTextY = contentTop + (contentH - fontH) / 2;
|
||||||
|
}
|
||||||
|
if (display->getHeight() > 64) {
|
||||||
|
if (centeredTextY < contentTop)
|
||||||
|
centeredTextY = contentTop;
|
||||||
|
if (centeredTextY + fontH > contentTop + contentH)
|
||||||
|
centeredTextY = std::max(contentTop, contentTop + contentH - fontH);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (display->getHeight() <= 64 && keyText.size() == 1) {
|
||||||
|
char ch = keyText[0];
|
||||||
|
if (ch == '.' || ch == ',' || ch == ';') {
|
||||||
|
centeredTextY -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
display->drawString(textX, centeredTextY, keyText.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
char VirtualKeyboard::getCharForKey(const VirtualKey &key, bool isLongPress)
|
||||||
|
{
|
||||||
|
if (key.type != VK_CHAR) {
|
||||||
|
return key.character;
|
||||||
|
}
|
||||||
|
|
||||||
|
char c = key.character;
|
||||||
|
|
||||||
|
// Long-press: only keep letter lowercase->uppercase conversion; remove other symbol mappings
|
||||||
|
if (isLongPress && c >= 'a' && c <= 'z') {
|
||||||
|
c = (char)(c - 'a' + 'A');
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::moveCursorDelta(int dRow, int dCol)
|
||||||
|
{
|
||||||
|
resetTimeout();
|
||||||
|
// wrap around rows and cols in the 4x11 grid
|
||||||
|
int r = (int)cursorRow + dRow;
|
||||||
|
int c = (int)cursorCol + dCol;
|
||||||
|
if (r < 0)
|
||||||
|
r = KEYBOARD_ROWS - 1;
|
||||||
|
else if (r >= KEYBOARD_ROWS)
|
||||||
|
r = 0;
|
||||||
|
if (c < 0)
|
||||||
|
c = KEYBOARD_COLS - 1;
|
||||||
|
else if (c >= KEYBOARD_COLS)
|
||||||
|
c = 0;
|
||||||
|
cursorRow = (uint8_t)r;
|
||||||
|
cursorCol = (uint8_t)c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::moveCursorUp()
|
||||||
|
{
|
||||||
|
moveCursorDelta(-1, 0);
|
||||||
|
}
|
||||||
|
void VirtualKeyboard::moveCursorDown()
|
||||||
|
{
|
||||||
|
moveCursorDelta(1, 0);
|
||||||
|
}
|
||||||
|
void VirtualKeyboard::moveCursorLeft()
|
||||||
|
{
|
||||||
|
resetTimeout();
|
||||||
|
|
||||||
|
if (cursorCol > 0) {
|
||||||
|
cursorCol--;
|
||||||
|
} else {
|
||||||
|
if (cursorRow > 0) {
|
||||||
|
cursorRow--;
|
||||||
|
cursorCol = KEYBOARD_COLS - 1;
|
||||||
|
} else {
|
||||||
|
cursorRow = KEYBOARD_ROWS - 1;
|
||||||
|
cursorCol = KEYBOARD_COLS - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void VirtualKeyboard::moveCursorRight()
|
||||||
|
{
|
||||||
|
resetTimeout();
|
||||||
|
|
||||||
|
if (cursorCol < KEYBOARD_COLS - 1) {
|
||||||
|
cursorCol++;
|
||||||
|
} else {
|
||||||
|
if (cursorRow < KEYBOARD_ROWS - 1) {
|
||||||
|
cursorRow++;
|
||||||
|
cursorCol = 0;
|
||||||
|
} else {
|
||||||
|
cursorRow = 0;
|
||||||
|
cursorCol = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::handlePress()
|
||||||
|
{
|
||||||
|
resetTimeout(); // Reset timeout on any input activity
|
||||||
|
|
||||||
|
const VirtualKey &key = keyboard[cursorRow][cursorCol];
|
||||||
|
|
||||||
|
// Don't handle press if the key is empty (but allow special keys)
|
||||||
|
if (key.character == 0 && key.type == VK_CHAR) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For character keys, insert lowercase character
|
||||||
|
if (key.type == VK_CHAR) {
|
||||||
|
insertCharacter(getCharForKey(key, false)); // false = lowercase/normal char
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle non-character keys immediately
|
||||||
|
switch (key.type) {
|
||||||
|
case VK_BACKSPACE:
|
||||||
|
deleteCharacter();
|
||||||
|
break;
|
||||||
|
case VK_ENTER:
|
||||||
|
submitText();
|
||||||
|
break;
|
||||||
|
case VK_SPACE:
|
||||||
|
insertCharacter(' ');
|
||||||
|
break;
|
||||||
|
case VK_ESC:
|
||||||
|
if (onTextEntered) {
|
||||||
|
std::function<void(const std::string &)> callback = onTextEntered;
|
||||||
|
onTextEntered = nullptr;
|
||||||
|
inputText = "";
|
||||||
|
callback("");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::handleLongPress()
|
||||||
|
{
|
||||||
|
resetTimeout(); // Reset timeout on any input activity
|
||||||
|
|
||||||
|
const VirtualKey &key = keyboard[cursorRow][cursorCol];
|
||||||
|
|
||||||
|
// Don't handle press if the key is empty (but allow special keys)
|
||||||
|
if (key.character == 0 && key.type == VK_CHAR) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For character keys, insert uppercase/alternate character
|
||||||
|
if (key.type == VK_CHAR) {
|
||||||
|
insertCharacter(getCharForKey(key, true)); // true = uppercase/alternate char
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (key.type) {
|
||||||
|
case VK_BACKSPACE:
|
||||||
|
// One-shot: delete up to 5 characters on long press
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
if (inputText.empty())
|
||||||
|
break;
|
||||||
|
deleteCharacter();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case VK_ENTER:
|
||||||
|
submitText();
|
||||||
|
break;
|
||||||
|
case VK_SPACE:
|
||||||
|
insertCharacter(' ');
|
||||||
|
break;
|
||||||
|
case VK_ESC:
|
||||||
|
if (onTextEntered) {
|
||||||
|
onTextEntered("");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::insertCharacter(char c)
|
||||||
|
{
|
||||||
|
if (inputText.length() < 160) { // Reasonable text length limit
|
||||||
|
inputText += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::deleteCharacter()
|
||||||
|
{
|
||||||
|
if (!inputText.empty()) {
|
||||||
|
inputText.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::submitText()
|
||||||
|
{
|
||||||
|
LOG_INFO("Virtual keyboard: submitting text '%s'", inputText.c_str());
|
||||||
|
|
||||||
|
// Only submit if text is not empty
|
||||||
|
if (!inputText.empty() && onTextEntered) {
|
||||||
|
// Store callback and text to submit before clearing callback
|
||||||
|
std::function<void(const std::string &)> callback = onTextEntered;
|
||||||
|
std::string textToSubmit = inputText;
|
||||||
|
onTextEntered = nullptr;
|
||||||
|
// Don't clear inputText here - let the calling module handle cleanup
|
||||||
|
// inputText = ""; // Removed: keep text visible until module cleans up
|
||||||
|
callback(textToSubmit);
|
||||||
|
} else if (inputText.empty()) {
|
||||||
|
// For empty text, just ignore the submission - don't clear callback
|
||||||
|
// This keeps the virtual keyboard responsive for further input
|
||||||
|
LOG_INFO("Virtual keyboard: empty text submitted, ignoring - keyboard remains active");
|
||||||
|
} else {
|
||||||
|
// No callback available
|
||||||
|
if (screen) {
|
||||||
|
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::setInputText(const std::string &text)
|
||||||
|
{
|
||||||
|
inputText = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string VirtualKeyboard::getInputText() const
|
||||||
|
{
|
||||||
|
return inputText;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::setHeader(const std::string &header)
|
||||||
|
{
|
||||||
|
headerText = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::setCallback(std::function<void(const std::string &)> callback)
|
||||||
|
{
|
||||||
|
onTextEntered = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualKeyboard::resetTimeout()
|
||||||
|
{
|
||||||
|
lastActivityTime = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VirtualKeyboard::isTimedOut() const
|
||||||
|
{
|
||||||
|
return (millis() - lastActivityTime) > TIMEOUT_MS;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace graphics
|
||||||
80
src/graphics/VirtualKeyboard.h
Normal file
80
src/graphics/VirtualKeyboard.h
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
#include <OLEDDisplay.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace graphics
|
||||||
|
{
|
||||||
|
|
||||||
|
enum VirtualKeyType { VK_CHAR, VK_BACKSPACE, VK_ENTER, VK_SHIFT, VK_ESC, VK_SPACE };
|
||||||
|
|
||||||
|
struct VirtualKey {
|
||||||
|
char character;
|
||||||
|
VirtualKeyType type;
|
||||||
|
uint8_t x;
|
||||||
|
uint8_t y;
|
||||||
|
uint8_t width;
|
||||||
|
uint8_t height;
|
||||||
|
};
|
||||||
|
|
||||||
|
class VirtualKeyboard
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
VirtualKeyboard();
|
||||||
|
~VirtualKeyboard();
|
||||||
|
|
||||||
|
void draw(OLEDDisplay *display, int16_t offsetX, int16_t offsetY);
|
||||||
|
void setInputText(const std::string &text);
|
||||||
|
std::string getInputText() const;
|
||||||
|
void setHeader(const std::string &header);
|
||||||
|
void setCallback(std::function<void(const std::string &)> callback);
|
||||||
|
|
||||||
|
// Navigation methods for encoder input
|
||||||
|
void moveCursorUp();
|
||||||
|
void moveCursorDown();
|
||||||
|
void moveCursorLeft();
|
||||||
|
void moveCursorRight();
|
||||||
|
void handlePress();
|
||||||
|
void handleLongPress();
|
||||||
|
|
||||||
|
// Timeout management
|
||||||
|
void resetTimeout();
|
||||||
|
bool isTimedOut() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const uint8_t KEYBOARD_ROWS = 4;
|
||||||
|
static const uint8_t KEYBOARD_COLS = 11;
|
||||||
|
static const uint8_t KEY_WIDTH = 9;
|
||||||
|
static const uint8_t KEY_HEIGHT = 9; // Compressed to fit 4 rows on 64px displays
|
||||||
|
static const uint8_t KEYBOARD_START_Y = 26; // Start just below input box bottom
|
||||||
|
|
||||||
|
VirtualKey keyboard[KEYBOARD_ROWS][KEYBOARD_COLS];
|
||||||
|
|
||||||
|
std::string inputText;
|
||||||
|
std::string headerText;
|
||||||
|
std::function<void(const std::string &)> onTextEntered;
|
||||||
|
|
||||||
|
uint8_t cursorRow;
|
||||||
|
uint8_t cursorCol;
|
||||||
|
|
||||||
|
// Timeout management for auto-exit
|
||||||
|
uint32_t lastActivityTime;
|
||||||
|
static const uint32_t TIMEOUT_MS = 60000; // 1 minute timeout
|
||||||
|
|
||||||
|
void initializeKeyboard();
|
||||||
|
void drawKey(OLEDDisplay *display, const VirtualKey &key, bool selected, int16_t x, int16_t y, uint8_t w, uint8_t h,
|
||||||
|
bool isLastCol);
|
||||||
|
void drawInputArea(OLEDDisplay *display, int16_t offsetX, int16_t offsetY, int16_t keyboardStartY);
|
||||||
|
|
||||||
|
// Unified cursor movement helper
|
||||||
|
void moveCursorDelta(int dRow, int dCol);
|
||||||
|
|
||||||
|
char getCharForKey(const VirtualKey &key, bool isLongPress = false);
|
||||||
|
void insertCharacter(char c);
|
||||||
|
void deleteCharacter();
|
||||||
|
void submitText();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace graphics
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
#if HAS_SCREEN
|
#if HAS_SCREEN
|
||||||
#include "ClockRenderer.h"
|
#include "ClockRenderer.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "UIRenderer.h"
|
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "gps/GeoCoord.h"
|
#include "gps/GeoCoord.h"
|
||||||
#include "gps/RTC.h"
|
#include "gps/RTC.h"
|
||||||
@@ -190,7 +189,7 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
|||||||
// === Set Title, Blank for Clock
|
// === Set Title, Blank for Clock
|
||||||
const char *titleStr = "";
|
const char *titleStr = "";
|
||||||
// === Header ===
|
// === Header ===
|
||||||
graphics::drawCommonHeader(display, x, y, titleStr, true);
|
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
|
||||||
|
|
||||||
#ifdef T_WATCH_S3
|
#ifdef T_WATCH_S3
|
||||||
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
|
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
|
||||||
@@ -294,6 +293,7 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
|||||||
display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2,
|
display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2,
|
||||||
isPM ? "pm" : "am");
|
isPM ? "pm" : "am");
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef USE_EINK
|
#ifndef USE_EINK
|
||||||
xOffset = (isHighResolution) ? 18 : 10;
|
xOffset = (isHighResolution) ? 18 : 10;
|
||||||
display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - yOffset,
|
display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - yOffset,
|
||||||
@@ -313,7 +313,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
// === Set Title, Blank for Clock
|
// === Set Title, Blank for Clock
|
||||||
const char *titleStr = "";
|
const char *titleStr = "";
|
||||||
// === Header ===
|
// === Header ===
|
||||||
graphics::drawCommonHeader(display, x, y, titleStr, true);
|
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
|
||||||
|
|
||||||
#ifdef T_WATCH_S3
|
#ifdef T_WATCH_S3
|
||||||
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
|
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
|
||||||
|
|||||||
@@ -94,7 +94,8 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
|
|||||||
if (!Throttle::isWithinTimespanMs(storeForwardModule->lastHeartbeat,
|
if (!Throttle::isWithinTimespanMs(storeForwardModule->lastHeartbeat,
|
||||||
(storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit
|
(storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit
|
||||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
#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(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \
|
||||||
|
ARCH_PORTDUINO) && \
|
||||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12,
|
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12,
|
||||||
8, imgQuestionL1);
|
8, imgQuestionL1);
|
||||||
@@ -106,7 +107,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
|
|||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
#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(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS)) && \
|
||||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||||
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16,
|
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16,
|
||||||
8, imgSFL1);
|
8, imgSFL1);
|
||||||
@@ -121,7 +122,8 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
|
|||||||
} else {
|
} else {
|
||||||
// TODO: Raspberry Pi supports more than just the one screen size
|
// 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) || \
|
#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(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \
|
||||||
|
ARCH_PORTDUINO) && \
|
||||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
|
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
|
||||||
imgInfoL1);
|
imgInfoL1);
|
||||||
@@ -261,12 +263,6 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
display->drawString(x + 1, y, "USB");
|
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 currentMillis = millis();
|
||||||
uint32_t seconds = currentMillis / 1000;
|
uint32_t seconds = currentMillis / 1000;
|
||||||
uint32_t minutes = seconds / 60;
|
uint32_t minutes = seconds / 60;
|
||||||
@@ -395,18 +391,27 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
int nameX = (SCREEN_WIDTH - textWidth);
|
int nameX = (SCREEN_WIDTH - textWidth);
|
||||||
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
|
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
|
||||||
|
|
||||||
// === Second Row: Radio Preset ===
|
// === Second Row: Role ===
|
||||||
auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false);
|
auto role = DisplayFormatters::getDeviceRole(config.device.role);
|
||||||
|
char device_role[25];
|
||||||
|
snprintf(device_role, sizeof(device_role), "Role: %s", role);
|
||||||
|
textWidth = display->getStringWidth(device_role);
|
||||||
|
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||||
|
display->drawString(nameX, getTextPositions(display)[line++], device_role);
|
||||||
|
|
||||||
|
// === Third Row: Radio Preset ===
|
||||||
|
auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset);
|
||||||
|
|
||||||
char regionradiopreset[25];
|
char regionradiopreset[25];
|
||||||
const char *region = myRegion ? myRegion->name : NULL;
|
const char *region = myRegion ? myRegion->name : NULL;
|
||||||
if (region != nullptr) {
|
if (region != nullptr) {
|
||||||
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, mode);
|
snprintf(regionradiopreset, sizeof(regionradiopreset), "Reg: %s/%s", region, mode);
|
||||||
}
|
}
|
||||||
textWidth = display->getStringWidth(regionradiopreset);
|
textWidth = display->getStringWidth(regionradiopreset);
|
||||||
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||||
display->drawString(nameX, getTextPositions(display)[line++], regionradiopreset);
|
display->drawString(nameX, getTextPositions(display)[line++], regionradiopreset);
|
||||||
|
|
||||||
// === Third Row: Frequency / ChanNum ===
|
// === Fourth Row: Frequency / ChanNum ===
|
||||||
char frequencyslot[35];
|
char frequencyslot[35];
|
||||||
char freqStr[16];
|
char freqStr[16];
|
||||||
float freq = RadioLibInterface::instance->getFreq();
|
float freq = RadioLibInterface::instance->getFreq();
|
||||||
@@ -424,7 +429,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||||
display->drawString(nameX, getTextPositions(display)[line++], frequencyslot);
|
display->drawString(nameX, getTextPositions(display)[line++], frequencyslot);
|
||||||
|
|
||||||
// === Fourth Row: Channel Utilization ===
|
// === Fifth Row: Channel Utilization ===
|
||||||
const char *chUtil = "ChUtil:";
|
const char *chUtil = "ChUtil:";
|
||||||
char chUtilPercentage[10];
|
char chUtilPercentage[10];
|
||||||
snprintf(chUtilPercentage, sizeof(chUtilPercentage), "%2.0f%%", airTime->channelUtilizationPercent());
|
snprintf(chUtilPercentage, sizeof(chUtilPercentage), "%2.0f%%", airTime->channelUtilizationPercent());
|
||||||
@@ -441,7 +446,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
int total_line_content_width = (chUtil_x + chutil_bar_width + display->getStringWidth(chUtilPercentage) + extraoffset) / 2;
|
int total_line_content_width = (chUtil_x + chutil_bar_width + display->getStringWidth(chUtilPercentage) + extraoffset) / 2;
|
||||||
int starting_position = centerofscreen - total_line_content_width;
|
int starting_position = centerofscreen - total_line_content_width;
|
||||||
|
|
||||||
display->drawString(starting_position, getTextPositions(display)[line++], chUtil);
|
display->drawString(starting_position, getTextPositions(display)[line], chUtil);
|
||||||
|
|
||||||
// Force 56% or higher to show a full 100% bar, text would still show related percent.
|
// Force 56% or higher to show a full 100% bar, text would still show related percent.
|
||||||
if (chutil_percent >= 61) {
|
if (chutil_percent >= 61) {
|
||||||
@@ -478,14 +483,14 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
display->fillRect(starting_position + chUtil_x, chUtil_y, fillRight, chutil_bar_height);
|
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],
|
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[line++],
|
||||||
chUtilPercentage);
|
chUtilPercentage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ****************************
|
// ****************************
|
||||||
// * System Screen *
|
// * System Screen *
|
||||||
// ****************************
|
// ****************************
|
||||||
void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
display->clear();
|
display->clear();
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
@@ -629,6 +634,33 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
display->drawString(nameX, getTextPositions(display)[line], uptimeStr);
|
display->drawString(nameX, getTextPositions(display)[line], uptimeStr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ****************************
|
||||||
|
// * Chirpy Screen *
|
||||||
|
// ****************************
|
||||||
|
void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
|
{
|
||||||
|
display->clear();
|
||||||
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
int line = 1;
|
||||||
|
int iconX = SCREEN_WIDTH - chirpy_width - (chirpy_width / 3);
|
||||||
|
int iconY = (SCREEN_HEIGHT - chirpy_height) / 2;
|
||||||
|
int textX_offset = 10;
|
||||||
|
if (isHighResolution) {
|
||||||
|
iconX = SCREEN_WIDTH - chirpy_width_hirez - (chirpy_width_hirez / 3);
|
||||||
|
iconY = (SCREEN_HEIGHT - chirpy_height_hirez) / 2;
|
||||||
|
textX_offset = textX_offset * 4;
|
||||||
|
display->drawXbm(iconX, iconY, chirpy_width_hirez, chirpy_height_hirez, chirpy_hirez);
|
||||||
|
} else {
|
||||||
|
display->drawXbm(iconX, iconY, chirpy_width, chirpy_height, chirpy);
|
||||||
|
}
|
||||||
|
|
||||||
|
int textX = (display->getWidth() / 2) - textX_offset - (display->getStringWidth("Hello") / 2);
|
||||||
|
display->drawString(textX, getTextPositions(display)[line++], "Hello");
|
||||||
|
textX = (display->getWidth() / 2) - textX_offset - (display->getStringWidth("World!") / 2);
|
||||||
|
display->drawString(textX, getTextPositions(display)[line++], "World!");
|
||||||
|
}
|
||||||
} // namespace DebugRenderer
|
} // namespace DebugRenderer
|
||||||
} // namespace graphics
|
} // namespace graphics
|
||||||
#endif
|
#endif
|
||||||
@@ -31,8 +31,11 @@ void drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state
|
|||||||
// LoRa information display
|
// LoRa information display
|
||||||
void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
|
||||||
// Memory screen display
|
// System screen display
|
||||||
void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
|
||||||
|
// Chirpy screen display
|
||||||
|
void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
} // namespace DebugRenderer
|
} // namespace DebugRenderer
|
||||||
|
|
||||||
} // namespace graphics
|
} // namespace graphics
|
||||||
|
|||||||
@@ -10,7 +10,10 @@
|
|||||||
#include "graphics/Screen.h"
|
#include "graphics/Screen.h"
|
||||||
#include "graphics/SharedUIDisplay.h"
|
#include "graphics/SharedUIDisplay.h"
|
||||||
#include "graphics/draw/UIRenderer.h"
|
#include "graphics/draw/UIRenderer.h"
|
||||||
|
#include "input/RotaryEncoderInterruptImpl1.h"
|
||||||
|
#include "input/UpDownInterruptImpl1.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
#include "mesh/MeshTypes.h"
|
||||||
#include "modules/AdminModule.h"
|
#include "modules/AdminModule.h"
|
||||||
#include "modules/CannedMessageModule.h"
|
#include "modules/CannedMessageModule.h"
|
||||||
#include "modules/KeyVerificationModule.h"
|
#include "modules/KeyVerificationModule.h"
|
||||||
@@ -26,6 +29,47 @@ menuHandler::screenMenus menuHandler::menuQueue = menu_none;
|
|||||||
bool test_enabled = false;
|
bool test_enabled = false;
|
||||||
uint8_t test_count = 0;
|
uint8_t test_count = 0;
|
||||||
|
|
||||||
|
void menuHandler::loraMenu()
|
||||||
|
{
|
||||||
|
static const char *optionsArray[] = {"Back", "Region Picker", "Device Role"};
|
||||||
|
enum optionsNumbers { Back = 0, lora_picker = 1, device_role_picker = 2 };
|
||||||
|
BannerOverlayOptions bannerOptions;
|
||||||
|
bannerOptions.message = "LoRa Actions";
|
||||||
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
|
bannerOptions.optionsCount = 3;
|
||||||
|
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||||
|
if (selected == Back) {
|
||||||
|
// No action
|
||||||
|
} else if (selected == lora_picker) {
|
||||||
|
menuHandler::menuQueue = menuHandler::lora_picker;
|
||||||
|
} else if (selected == device_role_picker) {
|
||||||
|
menuHandler::menuQueue = menuHandler::device_role_picker;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
screen->showOverlayBanner(bannerOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
void menuHandler::OnboardMessage()
|
||||||
|
{
|
||||||
|
static const char *optionsArray[] = {"OK", "Got it!"};
|
||||||
|
enum optionsNumbers { OK, got };
|
||||||
|
BannerOverlayOptions bannerOptions;
|
||||||
|
#if HAS_TFT
|
||||||
|
bannerOptions.message = "Welcome to Meshtastic!\nSwipe to navigate and\nlong press to select\nor open a menu.";
|
||||||
|
#elif defined(BUTTON_PIN)
|
||||||
|
bannerOptions.message = "Welcome to Meshtastic!\nClick to navigate and\nlong press to select\nor open a menu.";
|
||||||
|
#else
|
||||||
|
bannerOptions.message = "Welcome to Meshtastic!\nUse the Select button\nto open menus\nand make selections.";
|
||||||
|
#endif
|
||||||
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
|
bannerOptions.optionsCount = 2;
|
||||||
|
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||||
|
menuHandler::menuQueue = menuHandler::no_timeout_lora_picker;
|
||||||
|
screen->runNow();
|
||||||
|
};
|
||||||
|
screen->showOverlayBanner(bannerOptions);
|
||||||
|
}
|
||||||
|
|
||||||
void menuHandler::LoraRegionPicker(uint32_t duration)
|
void menuHandler::LoraRegionPicker(uint32_t duration)
|
||||||
{
|
{
|
||||||
static const char *optionsArray[] = {"Back",
|
static const char *optionsArray[] = {"Back",
|
||||||
@@ -98,6 +142,40 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
|
|||||||
screen->showOverlayBanner(bannerOptions);
|
screen->showOverlayBanner(bannerOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void menuHandler::DeviceRolePicker()
|
||||||
|
{
|
||||||
|
static const char *optionsArray[] = {"Back", "Client", "Client Mute", "Lost and Found", "Tracker"};
|
||||||
|
enum optionsNumbers {
|
||||||
|
Back = 0,
|
||||||
|
devicerole_client = 1,
|
||||||
|
devicerole_clientmute = 2,
|
||||||
|
devicerole_lostandfound = 3,
|
||||||
|
devicerole_tracker = 4
|
||||||
|
};
|
||||||
|
BannerOverlayOptions bannerOptions;
|
||||||
|
bannerOptions.message = "Device Role";
|
||||||
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
|
bannerOptions.optionsCount = 5;
|
||||||
|
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||||
|
if (selected == Back) {
|
||||||
|
menuHandler::menuQueue = menuHandler::lora_Menu;
|
||||||
|
screen->runNow();
|
||||||
|
return;
|
||||||
|
} else if (selected == devicerole_client) {
|
||||||
|
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
|
||||||
|
} else if (selected == devicerole_clientmute) {
|
||||||
|
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE;
|
||||||
|
} else if (selected == devicerole_lostandfound) {
|
||||||
|
config.device.role = meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND;
|
||||||
|
} else if (selected == devicerole_tracker) {
|
||||||
|
config.device.role = meshtastic_Config_DeviceConfig_Role_TRACKER;
|
||||||
|
}
|
||||||
|
service->reloadConfig(SEGMENT_CONFIG);
|
||||||
|
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
|
||||||
|
};
|
||||||
|
screen->showOverlayBanner(bannerOptions);
|
||||||
|
}
|
||||||
|
|
||||||
void menuHandler::TwelveHourPicker()
|
void menuHandler::TwelveHourPicker()
|
||||||
{
|
{
|
||||||
static const char *optionsArray[] = {"Back", "12-hour", "24-hour"};
|
static const char *optionsArray[] = {"Back", "12-hour", "24-hour"};
|
||||||
@@ -284,7 +362,7 @@ void menuHandler::messageResponseMenu()
|
|||||||
bannerOptions.optionsCount = options;
|
bannerOptions.optionsCount = options;
|
||||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||||
if (selected == Dismiss) {
|
if (selected == Dismiss) {
|
||||||
screen->dismissCurrentFrame();
|
screen->hideCurrentFrame();
|
||||||
} else if (selected == Preset) {
|
} else if (selected == Preset) {
|
||||||
if (devicestate.rx_text_message.to == NODENUM_BROADCAST) {
|
if (devicestate.rx_text_message.to == NODENUM_BROADCAST) {
|
||||||
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST, devicestate.rx_text_message.channel);
|
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST, devicestate.rx_text_message.channel);
|
||||||
@@ -318,15 +396,18 @@ void menuHandler::homeBaseMenu()
|
|||||||
static int optionsEnumArray[enumEnd] = {Back};
|
static int optionsEnumArray[enumEnd] = {Back};
|
||||||
int options = 1;
|
int options = 1;
|
||||||
|
|
||||||
#ifdef PIN_EINK_EN
|
#if defined(PIN_EINK_EN) || defined(PCA_PIN_EINK_EN)
|
||||||
optionsArray[options] = "Toggle Backlight";
|
optionsArray[options] = "Toggle Backlight";
|
||||||
optionsEnumArray[options++] = Backlight;
|
optionsEnumArray[options++] = Backlight;
|
||||||
#else
|
#else
|
||||||
optionsArray[options] = "Sleep Screen";
|
optionsArray[options] = "Sleep Screen";
|
||||||
optionsEnumArray[options++] = Sleep;
|
optionsEnumArray[options++] = Sleep;
|
||||||
#endif
|
#endif
|
||||||
|
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
||||||
optionsArray[options] = "Send Position";
|
optionsArray[options] = "Send Position";
|
||||||
|
} else {
|
||||||
|
optionsArray[options] = "Send Node Info";
|
||||||
|
}
|
||||||
optionsEnumArray[options++] = Position;
|
optionsEnumArray[options++] = Position;
|
||||||
optionsArray[options] = "New Preset Msg";
|
optionsArray[options] = "New Preset Msg";
|
||||||
optionsEnumArray[options++] = Preset;
|
optionsEnumArray[options++] = Preset;
|
||||||
@@ -342,12 +423,24 @@ void menuHandler::homeBaseMenu()
|
|||||||
bannerOptions.optionsCount = options;
|
bannerOptions.optionsCount = options;
|
||||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||||
if (selected == Backlight) {
|
if (selected == Backlight) {
|
||||||
#ifdef PIN_EINK_EN
|
#if defined(PIN_EINK_EN)
|
||||||
if (digitalRead(PIN_EINK_EN) == HIGH) {
|
if (uiconfig.screen_brightness == 1) {
|
||||||
|
uiconfig.screen_brightness = 0;
|
||||||
digitalWrite(PIN_EINK_EN, LOW);
|
digitalWrite(PIN_EINK_EN, LOW);
|
||||||
} else {
|
} else {
|
||||||
|
uiconfig.screen_brightness = 1;
|
||||||
digitalWrite(PIN_EINK_EN, HIGH);
|
digitalWrite(PIN_EINK_EN, HIGH);
|
||||||
}
|
}
|
||||||
|
saveUIConfig();
|
||||||
|
#elif defined(PCA_PIN_EINK_EN)
|
||||||
|
if (uiconfig.screen_brightness == 1) {
|
||||||
|
uiconfig.screen_brightness = 0;
|
||||||
|
io.digitalWrite(PCA_PIN_EINK_EN, LOW);
|
||||||
|
} else {
|
||||||
|
uiconfig.screen_brightness = 1;
|
||||||
|
io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
|
||||||
|
}
|
||||||
|
saveUIConfig();
|
||||||
#endif
|
#endif
|
||||||
} else if (selected == Sleep) {
|
} else if (selected == Sleep) {
|
||||||
screen->setOn(false);
|
screen->setOn(false);
|
||||||
@@ -394,19 +487,22 @@ void menuHandler::textMessageBaseMenu()
|
|||||||
|
|
||||||
void menuHandler::systemBaseMenu()
|
void menuHandler::systemBaseMenu()
|
||||||
{
|
{
|
||||||
enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, Test, enumEnd };
|
enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, FrameToggles, Test, enumEnd };
|
||||||
static const char *optionsArray[enumEnd] = {"Back"};
|
static const char *optionsArray[enumEnd] = {"Back"};
|
||||||
static int optionsEnumArray[enumEnd] = {Back};
|
static int optionsEnumArray[enumEnd] = {Back};
|
||||||
int options = 1;
|
int options = 1;
|
||||||
|
|
||||||
optionsArray[options] = "Notifications";
|
optionsArray[options] = "Notifications";
|
||||||
optionsEnumArray[options++] = Notifications;
|
optionsEnumArray[options++] = Notifications;
|
||||||
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || \
|
#if defined(ST7789_CS) || defined(ST7796_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || \
|
||||||
defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
|
defined(USE_SH1107) || defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
|
||||||
optionsArray[options] = "Screen Options";
|
optionsArray[options] = "Screen Options";
|
||||||
optionsEnumArray[options++] = ScreenOptions;
|
optionsEnumArray[options++] = ScreenOptions;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
optionsArray[options] = "Frame Visiblity Toggle";
|
||||||
|
optionsEnumArray[options++] = FrameToggles;
|
||||||
|
|
||||||
optionsArray[options] = "Bluetooth Toggle";
|
optionsArray[options] = "Bluetooth Toggle";
|
||||||
optionsEnumArray[options++] = Bluetooth;
|
optionsEnumArray[options++] = Bluetooth;
|
||||||
|
|
||||||
@@ -433,6 +529,9 @@ void menuHandler::systemBaseMenu()
|
|||||||
} else if (selected == PowerMenu) {
|
} else if (selected == PowerMenu) {
|
||||||
menuHandler::menuQueue = menuHandler::power_menu;
|
menuHandler::menuQueue = menuHandler::power_menu;
|
||||||
screen->runNow();
|
screen->runNow();
|
||||||
|
} else if (selected == FrameToggles) {
|
||||||
|
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||||
|
screen->runNow();
|
||||||
} else if (selected == Test) {
|
} else if (selected == Test) {
|
||||||
menuHandler::menuQueue = menuHandler::test_menu;
|
menuHandler::menuQueue = menuHandler::test_menu;
|
||||||
screen->runNow();
|
screen->runNow();
|
||||||
@@ -499,6 +598,7 @@ void menuHandler::positionBaseMenu()
|
|||||||
optionsArray[options] = "Compass Calibrate";
|
optionsArray[options] = "Compass Calibrate";
|
||||||
optionsEnumArray[options++] = CompassCalibrate;
|
optionsEnumArray[options++] = CompassCalibrate;
|
||||||
}
|
}
|
||||||
|
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
bannerOptions.message = "Position Action";
|
bannerOptions.message = "Position Action";
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
@@ -692,7 +792,7 @@ void menuHandler::BrightnessPickerMenu()
|
|||||||
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190)
|
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190)
|
||||||
// For HELTEC devices, use analogWrite to control backlight
|
// For HELTEC devices, use analogWrite to control backlight
|
||||||
analogWrite(VTFT_LEDA, uiconfig.screen_brightness);
|
analogWrite(VTFT_LEDA, uiconfig.screen_brightness);
|
||||||
#elif defined(ST7789_CS)
|
#elif defined(ST7789_CS) || defined(ST7796_CS)
|
||||||
static_cast<TFTDisplay *>(screen->getDisplayDevice())->setDisplayBrightness(uiconfig.screen_brightness);
|
static_cast<TFTDisplay *>(screen->getDisplayDevice())->setDisplayBrightness(uiconfig.screen_brightness);
|
||||||
#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
|
#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
|
||||||
screen->getDisplayDevice()->setBrightness(uiconfig.screen_brightness);
|
screen->getDisplayDevice()->setBrightness(uiconfig.screen_brightness);
|
||||||
@@ -735,7 +835,7 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
|
|||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
bannerOptions.optionsCount = 10;
|
bannerOptions.optionsCount = 10;
|
||||||
bannerOptions.bannerCallback = [display](int selected) -> void {
|
bannerOptions.bannerCallback = [display](int selected) -> void {
|
||||||
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT
|
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT
|
||||||
uint8_t TFT_MESH_r = 0;
|
uint8_t TFT_MESH_r = 0;
|
||||||
uint8_t TFT_MESH_g = 0;
|
uint8_t TFT_MESH_g = 0;
|
||||||
uint8_t TFT_MESH_b = 0;
|
uint8_t TFT_MESH_b = 0;
|
||||||
@@ -904,16 +1004,33 @@ void menuHandler::traceRouteMenu()
|
|||||||
void menuHandler::testMenu()
|
void menuHandler::testMenu()
|
||||||
{
|
{
|
||||||
|
|
||||||
static const char *optionsArray[] = {"Back", "Number Picker"};
|
enum optionsNumbers { Back, NumberPicker, ShowChirpy };
|
||||||
|
static const char *optionsArray[4] = {"Back"};
|
||||||
|
static int optionsEnumArray[4] = {Back};
|
||||||
|
int options = 1;
|
||||||
|
|
||||||
|
optionsArray[options] = "Number Picker";
|
||||||
|
optionsEnumArray[options++] = NumberPicker;
|
||||||
|
|
||||||
|
optionsArray[options] = screen->isFrameHidden("chirpy") ? "Show Chirpy" : "Hide Chirpy";
|
||||||
|
optionsEnumArray[options++] = ShowChirpy;
|
||||||
|
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
std::string message = "Test to Run?\n";
|
bannerOptions.message = "Hidden Test Menu";
|
||||||
bannerOptions.message = message.c_str();
|
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
bannerOptions.optionsCount = 2;
|
bannerOptions.optionsCount = options;
|
||||||
|
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
||||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||||
if (selected == 1) {
|
if (selected == NumberPicker) {
|
||||||
menuQueue = number_test;
|
menuQueue = number_test;
|
||||||
screen->runNow();
|
screen->runNow();
|
||||||
|
} else if (selected == ShowChirpy) {
|
||||||
|
screen->toggleFrameVisibility("chirpy");
|
||||||
|
screen->setFrames(Screen::FOCUS_SYSTEM);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
menuQueue = system_base_menu;
|
||||||
|
screen->runNow();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
screen->showOverlayBanner(bannerOptions);
|
screen->showOverlayBanner(bannerOptions);
|
||||||
@@ -1012,7 +1129,7 @@ void menuHandler::screenOptionsMenu()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only show screen color for TFT displays
|
// Only show screen color for TFT displays
|
||||||
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT
|
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT
|
||||||
optionsArray[options] = "Screen Color";
|
optionsArray[options] = "Screen Color";
|
||||||
optionsEnumArray[options++] = ScreenColor;
|
optionsEnumArray[options++] = ScreenColor;
|
||||||
#endif
|
#endif
|
||||||
@@ -1110,6 +1227,116 @@ void menuHandler::keyVerificationFinalPrompt()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void menuHandler::FrameToggles_menu()
|
||||||
|
{
|
||||||
|
enum optionsNumbers {
|
||||||
|
Finish,
|
||||||
|
nodelist,
|
||||||
|
nodelist_lastheard,
|
||||||
|
nodelist_hopsignal,
|
||||||
|
nodelist_distance,
|
||||||
|
nodelist_bearings,
|
||||||
|
gps,
|
||||||
|
lora,
|
||||||
|
clock,
|
||||||
|
show_favorites,
|
||||||
|
enumEnd
|
||||||
|
};
|
||||||
|
static const char *optionsArray[enumEnd] = {"Finish"};
|
||||||
|
static int optionsEnumArray[enumEnd] = {Finish};
|
||||||
|
int options = 1;
|
||||||
|
|
||||||
|
// Track last selected index (not enum value!)
|
||||||
|
static int lastSelectedIndex = 0;
|
||||||
|
|
||||||
|
#ifndef USE_EINK
|
||||||
|
optionsArray[options] = screen->isFrameHidden("nodelist") ? "Show Node List" : "Hide Node List";
|
||||||
|
optionsEnumArray[options++] = nodelist;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_EINK
|
||||||
|
optionsArray[options] = screen->isFrameHidden("nodelist_lastheard") ? "Show NL - Last Heard" : "Hide NL - Last Heard";
|
||||||
|
optionsEnumArray[options++] = nodelist_lastheard;
|
||||||
|
optionsArray[options] = screen->isFrameHidden("nodelist_hopsignal") ? "Show NL - Hops/Signal" : "Hide NL - Hops/Signal";
|
||||||
|
optionsEnumArray[options++] = nodelist_hopsignal;
|
||||||
|
optionsArray[options] = screen->isFrameHidden("nodelist_distance") ? "Show NL - Distance" : "Hide NL - Distance";
|
||||||
|
optionsEnumArray[options++] = nodelist_distance;
|
||||||
|
#endif
|
||||||
|
#if HAS_GPS
|
||||||
|
optionsArray[options] = screen->isFrameHidden("nodelist_bearings") ? "Show Bearings" : "Hide Bearings";
|
||||||
|
optionsEnumArray[options++] = nodelist_bearings;
|
||||||
|
|
||||||
|
optionsArray[options] = screen->isFrameHidden("gps") ? "Show Position" : "Hide Position";
|
||||||
|
optionsEnumArray[options++] = gps;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
optionsArray[options] = screen->isFrameHidden("lora") ? "Show LoRa" : "Hide LoRa";
|
||||||
|
optionsEnumArray[options++] = lora;
|
||||||
|
|
||||||
|
optionsArray[options] = screen->isFrameHidden("clock") ? "Show Clock" : "Hide Clock";
|
||||||
|
optionsEnumArray[options++] = clock;
|
||||||
|
|
||||||
|
optionsArray[options] = screen->isFrameHidden("show_favorites") ? "Show Favorites" : "Hide Favorites";
|
||||||
|
optionsEnumArray[options++] = show_favorites;
|
||||||
|
|
||||||
|
BannerOverlayOptions bannerOptions;
|
||||||
|
bannerOptions.message = "Show/Hide Frames";
|
||||||
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
|
bannerOptions.optionsCount = options;
|
||||||
|
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
||||||
|
bannerOptions.InitialSelected = lastSelectedIndex; // Use index, not enum value
|
||||||
|
|
||||||
|
bannerOptions.bannerCallback = [options](int selected) mutable -> void {
|
||||||
|
// Find the index of selected in optionsEnumArray
|
||||||
|
int idx = 0;
|
||||||
|
for (; idx < options; ++idx) {
|
||||||
|
if (optionsEnumArray[idx] == selected)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lastSelectedIndex = idx;
|
||||||
|
|
||||||
|
if (selected == Finish) {
|
||||||
|
screen->setFrames(Screen::FOCUS_DEFAULT);
|
||||||
|
} else if (selected == nodelist) {
|
||||||
|
screen->toggleFrameVisibility("nodelist");
|
||||||
|
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||||
|
screen->runNow();
|
||||||
|
} else if (selected == nodelist_lastheard) {
|
||||||
|
screen->toggleFrameVisibility("nodelist_lastheard");
|
||||||
|
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||||
|
screen->runNow();
|
||||||
|
} else if (selected == nodelist_hopsignal) {
|
||||||
|
screen->toggleFrameVisibility("nodelist_hopsignal");
|
||||||
|
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||||
|
screen->runNow();
|
||||||
|
} else if (selected == nodelist_distance) {
|
||||||
|
screen->toggleFrameVisibility("nodelist_distance");
|
||||||
|
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||||
|
screen->runNow();
|
||||||
|
} else if (selected == nodelist_bearings) {
|
||||||
|
screen->toggleFrameVisibility("nodelist_bearings");
|
||||||
|
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||||
|
screen->runNow();
|
||||||
|
} else if (selected == gps) {
|
||||||
|
screen->toggleFrameVisibility("gps");
|
||||||
|
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||||
|
screen->runNow();
|
||||||
|
} else if (selected == lora) {
|
||||||
|
screen->toggleFrameVisibility("lora");
|
||||||
|
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||||
|
screen->runNow();
|
||||||
|
} else if (selected == clock) {
|
||||||
|
screen->toggleFrameVisibility("clock");
|
||||||
|
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||||
|
screen->runNow();
|
||||||
|
} else if (selected == show_favorites) {
|
||||||
|
screen->toggleFrameVisibility("show_favorites");
|
||||||
|
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||||
|
screen->runNow();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
screen->showOverlayBanner(bannerOptions);
|
||||||
|
}
|
||||||
|
|
||||||
void menuHandler::handleMenuSwitch(OLEDDisplay *display)
|
void menuHandler::handleMenuSwitch(OLEDDisplay *display)
|
||||||
{
|
{
|
||||||
if (menuQueue != menu_none)
|
if (menuQueue != menu_none)
|
||||||
@@ -1117,9 +1344,18 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
|
|||||||
switch (menuQueue) {
|
switch (menuQueue) {
|
||||||
case menu_none:
|
case menu_none:
|
||||||
break;
|
break;
|
||||||
|
case lora_Menu:
|
||||||
|
loraMenu();
|
||||||
|
break;
|
||||||
case lora_picker:
|
case lora_picker:
|
||||||
LoraRegionPicker();
|
LoraRegionPicker();
|
||||||
break;
|
break;
|
||||||
|
case device_role_picker:
|
||||||
|
DeviceRolePicker();
|
||||||
|
break;
|
||||||
|
case no_timeout_lora_picker:
|
||||||
|
LoraRegionPicker(0);
|
||||||
|
break;
|
||||||
case TZ_picker:
|
case TZ_picker:
|
||||||
TZPicker();
|
TZPicker();
|
||||||
break;
|
break;
|
||||||
@@ -1203,6 +1439,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
|
|||||||
case power_menu:
|
case power_menu:
|
||||||
powerMenu();
|
powerMenu();
|
||||||
break;
|
break;
|
||||||
|
case FrameToggles:
|
||||||
|
FrameToggles_menu();
|
||||||
|
break;
|
||||||
case throttle_message:
|
case throttle_message:
|
||||||
screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000);
|
screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ class menuHandler
|
|||||||
public:
|
public:
|
||||||
enum screenMenus {
|
enum screenMenus {
|
||||||
menu_none,
|
menu_none,
|
||||||
|
lora_Menu,
|
||||||
lora_picker,
|
lora_picker,
|
||||||
|
device_role_picker,
|
||||||
|
no_timeout_lora_picker,
|
||||||
TZ_picker,
|
TZ_picker,
|
||||||
twelve_hour_picker,
|
twelve_hour_picker,
|
||||||
clock_face_picker,
|
clock_face_picker,
|
||||||
@@ -38,10 +41,14 @@ class menuHandler
|
|||||||
key_verification_final_prompt,
|
key_verification_final_prompt,
|
||||||
trace_route_menu,
|
trace_route_menu,
|
||||||
throttle_message,
|
throttle_message,
|
||||||
|
FrameToggles
|
||||||
};
|
};
|
||||||
static screenMenus menuQueue;
|
static screenMenus menuQueue;
|
||||||
|
|
||||||
|
static void OnboardMessage();
|
||||||
static void LoraRegionPicker(uint32_t duration = 30000);
|
static void LoraRegionPicker(uint32_t duration = 30000);
|
||||||
|
static void loraMenu();
|
||||||
|
static void DeviceRolePicker();
|
||||||
static void handleMenuSwitch(OLEDDisplay *display);
|
static void handleMenuSwitch(OLEDDisplay *display);
|
||||||
static void showConfirmationBanner(const char *message, std::function<void()> onConfirm);
|
static void showConfirmationBanner(const char *message, std::function<void()> onConfirm);
|
||||||
static void clockMenu();
|
static void clockMenu();
|
||||||
@@ -74,6 +81,7 @@ class menuHandler
|
|||||||
static void notificationsMenu();
|
static void notificationsMenu();
|
||||||
static void screenOptionsMenu();
|
static void screenOptionsMenu();
|
||||||
static void powerMenu();
|
static void powerMenu();
|
||||||
|
static void FrameToggles_menu();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void saveUIConfig();
|
static void saveUIConfig();
|
||||||
|
|||||||
@@ -137,7 +137,11 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string
|
|||||||
display->drawString(cursorX + 1, fontY, textChunk.c_str());
|
display->drawString(cursorX + 1, fontY, textChunk.c_str());
|
||||||
}
|
}
|
||||||
display->drawString(cursorX, fontY, textChunk.c_str());
|
display->drawString(cursorX, fontY, textChunk.c_str());
|
||||||
|
#if defined(OLED_UA) || defined(OLED_RU)
|
||||||
|
cursorX += display->getStringWidth(textChunk.c_str(), textChunk.length(), true);
|
||||||
|
#else
|
||||||
cursorX += display->getStringWidth(textChunk.c_str());
|
cursorX += display->getStringWidth(textChunk.c_str());
|
||||||
|
#endif
|
||||||
i = nextControl;
|
i = nextControl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -155,7 +159,12 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string
|
|||||||
display->drawString(cursorX + 1, fontY, remaining.c_str());
|
display->drawString(cursorX + 1, fontY, remaining.c_str());
|
||||||
}
|
}
|
||||||
display->drawString(cursorX, fontY, remaining.c_str());
|
display->drawString(cursorX, fontY, remaining.c_str());
|
||||||
|
#if defined(OLED_UA) || defined(OLED_RU)
|
||||||
|
cursorX += display->getStringWidth(remaining.c_str(), remaining.length(), true);
|
||||||
|
#else
|
||||||
cursorX += display->getStringWidth(remaining.c_str());
|
cursorX += display->getStringWidth(remaining.c_str());
|
||||||
|
#endif
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -374,10 +383,16 @@ std::vector<std::string> generateLines(OLEDDisplay *display, const char *headerS
|
|||||||
} else {
|
} else {
|
||||||
word += ch;
|
word += ch;
|
||||||
std::string test = line + word;
|
std::string test = line + word;
|
||||||
// Keep these lines for diagnostics
|
// Keep these lines for diagnostics
|
||||||
// LOG_INFO("Char: '%c' (0x%02X)", ch, (unsigned char)ch);
|
// LOG_INFO("Char: '%c' (0x%02X)", ch, (unsigned char)ch);
|
||||||
// LOG_INFO("Current String: %s", test.c_str());
|
// LOG_INFO("Current String: %s", test.c_str());
|
||||||
if (display->getStringWidth(test.c_str()) > textWidth) {
|
// Note: there are boolean comparison uint16 (getStringWidth) with int (textWidth), hope textWidth is always positive :)
|
||||||
|
#if defined(OLED_UA) || defined(OLED_RU)
|
||||||
|
uint16_t strWidth = display->getStringWidth(test.c_str(), test.length(), true);
|
||||||
|
#else
|
||||||
|
uint16_t strWidth = display->getStringWidth(test.c_str());
|
||||||
|
#endif
|
||||||
|
if (strWidth > textWidth) {
|
||||||
if (!line.empty())
|
if (!line.empty())
|
||||||
lines.push_back(line);
|
lines.push_back(line);
|
||||||
line = word;
|
line = word;
|
||||||
|
|||||||
@@ -7,10 +7,18 @@
|
|||||||
#include "graphics/ScreenFonts.h"
|
#include "graphics/ScreenFonts.h"
|
||||||
#include "graphics/SharedUIDisplay.h"
|
#include "graphics/SharedUIDisplay.h"
|
||||||
#include "graphics/images.h"
|
#include "graphics/images.h"
|
||||||
|
#include "input/RotaryEncoderInterruptImpl1.h"
|
||||||
|
#include "input/UpDownInterruptImpl1.h"
|
||||||
|
#if HAS_BUTTON
|
||||||
|
#include "input/ButtonThread.h"
|
||||||
|
#endif
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#if HAS_TRACKBALL
|
||||||
|
#include "input/TrackballInterruptImpl1.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
#include "esp_task_wdt.h"
|
#include "esp_task_wdt.h"
|
||||||
@@ -18,6 +26,11 @@
|
|||||||
|
|
||||||
using namespace meshtastic;
|
using namespace meshtastic;
|
||||||
|
|
||||||
|
#if HAS_BUTTON
|
||||||
|
// Global button thread pointer defined in main.cpp
|
||||||
|
extern ::ButtonThread *UserButtonThread;
|
||||||
|
#endif
|
||||||
|
|
||||||
// External references to global variables from Screen.cpp
|
// External references to global variables from Screen.cpp
|
||||||
extern std::vector<std::string> functionSymbol;
|
extern std::vector<std::string> functionSymbol;
|
||||||
extern std::string functionSymbolString;
|
extern std::string functionSymbolString;
|
||||||
@@ -36,8 +49,11 @@ const int *NotificationRenderer::optionsEnumPtr = nullptr;
|
|||||||
std::function<void(int)> NotificationRenderer::alertBannerCallback = NULL;
|
std::function<void(int)> NotificationRenderer::alertBannerCallback = NULL;
|
||||||
bool NotificationRenderer::pauseBanner = false;
|
bool NotificationRenderer::pauseBanner = false;
|
||||||
notificationTypeEnum NotificationRenderer::current_notification_type = notificationTypeEnum::none;
|
notificationTypeEnum NotificationRenderer::current_notification_type = notificationTypeEnum::none;
|
||||||
|
|
||||||
uint32_t NotificationRenderer::numDigits = 0;
|
uint32_t NotificationRenderer::numDigits = 0;
|
||||||
uint32_t NotificationRenderer::currentNumber = 0;
|
uint32_t NotificationRenderer::currentNumber = 0;
|
||||||
|
VirtualKeyboard *NotificationRenderer::virtualKeyboard = nullptr;
|
||||||
|
std::function<void(const std::string &)> NotificationRenderer::textInputCallback = nullptr;
|
||||||
|
|
||||||
uint32_t pow_of_10(uint32_t n)
|
uint32_t pow_of_10(uint32_t n)
|
||||||
{
|
{
|
||||||
@@ -70,9 +86,13 @@ void NotificationRenderer::drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiStat
|
|||||||
|
|
||||||
void NotificationRenderer::resetBanner()
|
void NotificationRenderer::resetBanner()
|
||||||
{
|
{
|
||||||
|
notificationTypeEnum previousType = current_notification_type;
|
||||||
|
|
||||||
alertBannerMessage[0] = '\0';
|
alertBannerMessage[0] = '\0';
|
||||||
current_notification_type = notificationTypeEnum::none;
|
current_notification_type = notificationTypeEnum::none;
|
||||||
|
|
||||||
|
OnScreenKeyboardModule::instance().clearPopup();
|
||||||
|
|
||||||
inEvent.inputEvent = INPUT_BROKER_NONE;
|
inEvent.inputEvent = INPUT_BROKER_NONE;
|
||||||
inEvent.kbchar = 0;
|
inEvent.kbchar = 0;
|
||||||
curSelected = 0;
|
curSelected = 0;
|
||||||
@@ -85,18 +105,44 @@ void NotificationRenderer::resetBanner()
|
|||||||
currentNumber = 0;
|
currentNumber = 0;
|
||||||
|
|
||||||
nodeDB->pause_sort(false);
|
nodeDB->pause_sort(false);
|
||||||
|
|
||||||
|
// If we're exiting from text_input (virtual keyboard), stop module and trigger frame update
|
||||||
|
// to ensure any messages received during keyboard use are now displayed
|
||||||
|
if (previousType == notificationTypeEnum::text_input && screen) {
|
||||||
|
OnScreenKeyboardModule::instance().stop(false);
|
||||||
|
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state)
|
void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state)
|
||||||
{
|
{
|
||||||
if (!isOverlayBannerShowing() && alertBannerMessage[0] != '\0')
|
// Handle text_input notifications first - they have their own timeout/banner logic
|
||||||
resetBanner();
|
if (current_notification_type == notificationTypeEnum::text_input) {
|
||||||
if (!isOverlayBannerShowing() || pauseBanner)
|
// Check for timeout and reset if needed for text input
|
||||||
|
if (millis() > alertBannerUntil && alertBannerUntil > 0) {
|
||||||
|
resetBanner();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
drawTextInput(display, state);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (millis() > alertBannerUntil && alertBannerUntil > 0) {
|
||||||
|
resetBanner();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit if no banner is showing or banner is paused
|
||||||
|
if (!isOverlayBannerShowing() || pauseBanner) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (current_notification_type) {
|
switch (current_notification_type) {
|
||||||
case notificationTypeEnum::none:
|
case notificationTypeEnum::none:
|
||||||
// Do nothing - no notification to display
|
// Do nothing - no notification to display
|
||||||
break;
|
break;
|
||||||
|
case notificationTypeEnum::text_input:
|
||||||
|
// Already handled above with dedicated logic (early return). Keep a case here to satisfy -Wswitch.
|
||||||
|
break;
|
||||||
case notificationTypeEnum::text_banner:
|
case notificationTypeEnum::text_banner:
|
||||||
case notificationTypeEnum::selection_picker:
|
case notificationTypeEnum::selection_picker:
|
||||||
drawAlertBannerOverlay(display, state);
|
drawAlertBannerOverlay(display, state);
|
||||||
@@ -267,12 +313,9 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
|
|||||||
if (nodeDB->getMeshNodeByIndex(i + 1)->has_user) {
|
if (nodeDB->getMeshNodeByIndex(i + 1)->has_user) {
|
||||||
std::string sanitized = sanitizeString(nodeDB->getMeshNodeByIndex(i + 1)->user.long_name);
|
std::string sanitized = sanitizeString(nodeDB->getMeshNodeByIndex(i + 1)->user.long_name);
|
||||||
strncpy(temp_name, sanitized.c_str(), sizeof(temp_name) - 1);
|
strncpy(temp_name, sanitized.c_str(), sizeof(temp_name) - 1);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
snprintf(temp_name, sizeof(temp_name), "(%04X)", (uint16_t)(nodeDB->getMeshNodeByIndex(i + 1)->num & 0xFFFF));
|
snprintf(temp_name, sizeof(temp_name), "(%04X)", (uint16_t)(nodeDB->getMeshNodeByIndex(i + 1)->num & 0xFFFF));
|
||||||
}
|
}
|
||||||
// make temp buffer for name
|
|
||||||
// fi
|
|
||||||
if (i == curSelected) {
|
if (i == curSelected) {
|
||||||
selectedNodenum = nodeDB->getMeshNodeByIndex(i + 1)->num;
|
selectedNodenum = nodeDB->getMeshNodeByIndex(i + 1)->num;
|
||||||
if (isHighResolution) {
|
if (isHighResolution) {
|
||||||
@@ -286,7 +329,8 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
|
|||||||
}
|
}
|
||||||
scratchLineBuffer[scratchLineNum][39] = '\0';
|
scratchLineBuffer[scratchLineNum][39] = '\0';
|
||||||
} else {
|
} else {
|
||||||
strncpy(scratchLineBuffer[scratchLineNum], temp_name, 36);
|
strncpy(scratchLineBuffer[scratchLineNum], temp_name, 39);
|
||||||
|
scratchLineBuffer[scratchLineNum][39] = '\0';
|
||||||
}
|
}
|
||||||
linePointers[linesShown] = scratchLineBuffer[scratchLineNum++];
|
linePointers[linesShown] = scratchLineBuffer[scratchLineNum++];
|
||||||
}
|
}
|
||||||
@@ -383,7 +427,9 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
|
|||||||
|
|
||||||
uint8_t firstOptionToShow = 0;
|
uint8_t firstOptionToShow = 0;
|
||||||
if (alertBannerOptions > 0) {
|
if (alertBannerOptions > 0) {
|
||||||
if (curSelected > 1 && alertBannerOptions > visibleTotalLines - lineCount) {
|
if (visibleTotalLines - lineCount == 1) {
|
||||||
|
firstOptionToShow = curSelected;
|
||||||
|
} else if (curSelected > 1 && alertBannerOptions > visibleTotalLines - lineCount) {
|
||||||
if (curSelected > alertBannerOptions - visibleTotalLines + lineCount)
|
if (curSelected > alertBannerOptions - visibleTotalLines + lineCount)
|
||||||
firstOptionToShow = alertBannerOptions - visibleTotalLines + lineCount;
|
firstOptionToShow = alertBannerOptions - visibleTotalLines + lineCount;
|
||||||
else
|
else
|
||||||
@@ -392,6 +438,9 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
|
|||||||
firstOptionToShow = 0;
|
firstOptionToShow = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Useful log line for troubleshooting:
|
||||||
|
/* LOG_WARN("alertBannerOptions: %u, curSelected: %u, visibleTotalLines: %u, lineCount: %u, firstOptionToShow: %u",
|
||||||
|
alertBannerOptions, curSelected, visibleTotalLines, lineCount, firstOptionToShow); */
|
||||||
|
|
||||||
for (int i = firstOptionToShow; i < alertBannerOptions && linesShown < visibleTotalLines; i++, linesShown++) {
|
for (int i = firstOptionToShow; i < alertBannerOptions && linesShown < visibleTotalLines; i++, linesShown++) {
|
||||||
if (i == curSelected) {
|
if (i == curSelected) {
|
||||||
@@ -570,10 +619,182 @@ void NotificationRenderer::drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUi
|
|||||||
"Please be patient and do not power off.");
|
"Please be patient and do not power off.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NotificationRenderer::drawTextInput(OLEDDisplay *display, OLEDDisplayUiState *state)
|
||||||
|
{
|
||||||
|
// Delegate session to OnScreenKeyboardModule
|
||||||
|
auto &osk = OnScreenKeyboardModule::instance();
|
||||||
|
|
||||||
|
if (!osk.isActive()) {
|
||||||
|
LOG_INFO("Virtual keyboard is not active - resetting banner");
|
||||||
|
resetBanner();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inEvent.inputEvent != INPUT_BROKER_NONE) {
|
||||||
|
osk.handleInput(inEvent);
|
||||||
|
inEvent.inputEvent = INPUT_BROKER_NONE; // consume
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw. If draw returns false, session ended (timeout or cancel)
|
||||||
|
if (!osk.draw(display)) {
|
||||||
|
// Session ended, ensure banner reset and restore frames
|
||||||
|
resetBanner();
|
||||||
|
if (screen)
|
||||||
|
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool NotificationRenderer::isOverlayBannerShowing()
|
bool NotificationRenderer::isOverlayBannerShowing()
|
||||||
{
|
{
|
||||||
return strlen(alertBannerMessage) > 0 && (alertBannerUntil == 0 || millis() <= alertBannerUntil);
|
return strlen(alertBannerMessage) > 0 && (alertBannerUntil == 0 || millis() <= alertBannerUntil);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NotificationRenderer::showKeyboardMessagePopupWithTitle(const char *title, const char *content, uint32_t durationMs)
|
||||||
|
{
|
||||||
|
if (!title || !content || current_notification_type != notificationTypeEnum::text_input)
|
||||||
|
return;
|
||||||
|
OnScreenKeyboardModule::instance().showPopup(title, content, durationMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// drawKeyboardMessagePopup removed; OnScreenKeyboardModule handles popup drawing within draw()
|
||||||
|
|
||||||
|
// Custom inverted color version for keyboard popup - black background with white text
|
||||||
|
void NotificationRenderer::drawInvertedNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[],
|
||||||
|
uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth)
|
||||||
|
{
|
||||||
|
bool is_picker = false;
|
||||||
|
uint16_t lineCount = 0;
|
||||||
|
// === Layout Configuration ===
|
||||||
|
constexpr uint16_t hPadding = 5;
|
||||||
|
constexpr uint16_t vPadding = 2;
|
||||||
|
bool needs_bell = false;
|
||||||
|
uint16_t lineWidths[totalLines] = {0};
|
||||||
|
uint16_t lineLengths[totalLines] = {0};
|
||||||
|
|
||||||
|
if (maxWidth != 0)
|
||||||
|
is_picker = true;
|
||||||
|
|
||||||
|
// Setup font and alignment
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
|
|
||||||
|
while (lines[lineCount] != nullptr) {
|
||||||
|
auto newlinePointer = strchr(lines[lineCount], '\n');
|
||||||
|
if (newlinePointer)
|
||||||
|
lineLengths[lineCount] = (newlinePointer - lines[lineCount]);
|
||||||
|
else
|
||||||
|
lineLengths[lineCount] = strlen(lines[lineCount]);
|
||||||
|
lineWidths[lineCount] = display->getStringWidth(lines[lineCount], lineLengths[lineCount], true);
|
||||||
|
if (!is_picker) {
|
||||||
|
if (lineWidths[lineCount] > maxWidth)
|
||||||
|
maxWidth = lineWidths[lineCount];
|
||||||
|
}
|
||||||
|
lineCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t boxWidth = hPadding * 2 + maxWidth;
|
||||||
|
if (needs_bell) {
|
||||||
|
if (isHighResolution && boxWidth <= 150)
|
||||||
|
boxWidth += 26;
|
||||||
|
if (!isHighResolution && boxWidth <= 100)
|
||||||
|
boxWidth += 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t screenHeight = display->height();
|
||||||
|
uint8_t effectiveLineHeight = FONT_HEIGHT_SMALL - 3;
|
||||||
|
uint8_t visibleTotalLines = std::min<uint8_t>(lineCount, (screenHeight - vPadding * 2) / effectiveLineHeight);
|
||||||
|
uint16_t contentHeight = visibleTotalLines * effectiveLineHeight;
|
||||||
|
uint16_t boxHeight = contentHeight + vPadding * 2;
|
||||||
|
if (visibleTotalLines == 1) {
|
||||||
|
boxHeight += (isHighResolution) ? 4 : 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t boxLeft = (display->width() / 2) - (boxWidth / 2);
|
||||||
|
if (totalLines > visibleTotalLines) {
|
||||||
|
boxWidth += (isHighResolution) ? 4 : 2;
|
||||||
|
}
|
||||||
|
int16_t boxTop = (display->height() / 2) - (boxHeight / 2);
|
||||||
|
|
||||||
|
// === Draw Box with INVERTED COLORS ===
|
||||||
|
// Add outer separation pixels (1-pixel white background around the box)
|
||||||
|
display->setColor(WHITE);
|
||||||
|
display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2);
|
||||||
|
|
||||||
|
// Make outer corners round by filling back with背景色 (BLACK for separation)
|
||||||
|
display->setColor(BLACK);
|
||||||
|
// Top-left outer corner
|
||||||
|
display->fillRect(boxLeft - 1, boxTop - 1, 1, 1);
|
||||||
|
// Top-right outer corner
|
||||||
|
display->fillRect(boxLeft + boxWidth, boxTop - 1, 1, 1);
|
||||||
|
// Bottom-left outer corner
|
||||||
|
display->fillRect(boxLeft - 1, boxTop + boxHeight, 1, 1);
|
||||||
|
// Bottom-right outer corner
|
||||||
|
display->fillRect(boxLeft + boxWidth, boxTop + boxHeight, 1, 1);
|
||||||
|
|
||||||
|
// Draw single pixel black border
|
||||||
|
display->setColor(BLACK);
|
||||||
|
display->drawRect(boxLeft, boxTop, boxWidth, boxHeight);
|
||||||
|
|
||||||
|
// Make inner corners round by filling white pixels at corners
|
||||||
|
display->setColor(WHITE);
|
||||||
|
display->fillRect(boxLeft, boxTop, 1, 1);
|
||||||
|
display->fillRect(boxLeft + boxWidth - 1, boxTop, 1, 1);
|
||||||
|
display->fillRect(boxLeft, boxTop + boxHeight - 1, 1, 1);
|
||||||
|
display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1);
|
||||||
|
|
||||||
|
// Fill interior with BLACK (inverted)
|
||||||
|
display->setColor(BLACK);
|
||||||
|
display->fillRect(boxLeft + 1, boxTop + 1, boxWidth - 2, boxHeight - 2);
|
||||||
|
|
||||||
|
// === Draw Content with WHITE text on BLACK background ===
|
||||||
|
display->setColor(WHITE);
|
||||||
|
int16_t lineY = boxTop + vPadding;
|
||||||
|
for (int i = 0; i < lineCount; i++) {
|
||||||
|
int16_t textX = boxLeft + (boxWidth - lineWidths[i]) / 2;
|
||||||
|
|
||||||
|
char lineBuffer[lineLengths[i] + 1];
|
||||||
|
strncpy(lineBuffer, lines[i], lineLengths[i]);
|
||||||
|
lineBuffer[lineLengths[i]] = '\0';
|
||||||
|
|
||||||
|
// For keyboard popup, treat first line as title if it's different from others
|
||||||
|
if (i == 0 && lineCount > 1) {
|
||||||
|
// Title line - use inverted colors (white background, black text)
|
||||||
|
display->setColor(WHITE);
|
||||||
|
int background_yOffset = 1;
|
||||||
|
// Check for low hanging characters
|
||||||
|
if (strchr(lineBuffer, 'p') || strchr(lineBuffer, 'g') || strchr(lineBuffer, 'y') || strchr(lineBuffer, 'j')) {
|
||||||
|
background_yOffset = -1;
|
||||||
|
}
|
||||||
|
display->fillRect(boxLeft + 1, boxTop + 1, boxWidth - 2, effectiveLineHeight - background_yOffset);
|
||||||
|
display->setColor(BLACK);
|
||||||
|
int yOffset = 3;
|
||||||
|
display->drawString(textX, lineY - yOffset, lineBuffer);
|
||||||
|
display->setColor(WHITE); // Reset to white for next lines
|
||||||
|
lineY += (effectiveLineHeight - 2 - background_yOffset);
|
||||||
|
} else {
|
||||||
|
// Content lines - white text on black background
|
||||||
|
display->drawString(textX, lineY, lineBuffer);
|
||||||
|
lineY += effectiveLineHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Scroll Bar (if needed) ===
|
||||||
|
if (totalLines > visibleTotalLines) {
|
||||||
|
const uint8_t scrollBarWidth = 5;
|
||||||
|
int16_t scrollBarX = boxLeft + boxWidth - scrollBarWidth - 2;
|
||||||
|
int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight;
|
||||||
|
uint16_t scrollBarHeight = boxHeight - vPadding * 2 - effectiveLineHeight;
|
||||||
|
|
||||||
|
float ratio = (float)visibleTotalLines / totalLines;
|
||||||
|
uint16_t indicatorHeight = std::max((int)(scrollBarHeight * ratio), 4);
|
||||||
|
float scrollRatio = (float)(firstOptionToShow + lineCount - visibleTotalLines) / (totalLines - visibleTotalLines);
|
||||||
|
uint16_t indicatorY = scrollBarY + scrollRatio * (scrollBarHeight - indicatorHeight);
|
||||||
|
|
||||||
|
display->setColor(WHITE);
|
||||||
|
display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight);
|
||||||
|
display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace graphics
|
} // namespace graphics
|
||||||
#endif
|
#endif
|
||||||
@@ -3,6 +3,10 @@
|
|||||||
#include "OLEDDisplay.h"
|
#include "OLEDDisplay.h"
|
||||||
#include "OLEDDisplayUi.h"
|
#include "OLEDDisplayUi.h"
|
||||||
#include "graphics/Screen.h"
|
#include "graphics/Screen.h"
|
||||||
|
#include "graphics/VirtualKeyboard.h"
|
||||||
|
#include "modules/OnScreenKeyboardModule.h"
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
#define MAX_LINES 5
|
#define MAX_LINES 5
|
||||||
|
|
||||||
namespace graphics
|
namespace graphics
|
||||||
@@ -22,16 +26,22 @@ class NotificationRenderer
|
|||||||
static std::function<void(int)> alertBannerCallback;
|
static std::function<void(int)> alertBannerCallback;
|
||||||
static uint32_t numDigits;
|
static uint32_t numDigits;
|
||||||
static uint32_t currentNumber;
|
static uint32_t currentNumber;
|
||||||
|
static VirtualKeyboard *virtualKeyboard;
|
||||||
|
static std::function<void(const std::string &)> textInputCallback;
|
||||||
|
|
||||||
static bool pauseBanner;
|
static bool pauseBanner;
|
||||||
|
|
||||||
static void resetBanner();
|
static void resetBanner();
|
||||||
|
static void showKeyboardMessagePopupWithTitle(const char *title, const char *content, uint32_t durationMs);
|
||||||
static void drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state);
|
static void drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state);
|
||||||
static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state);
|
static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state);
|
||||||
static void drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiState *state);
|
static void drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiState *state);
|
||||||
static void drawNodePicker(OLEDDisplay *display, OLEDDisplayUiState *state);
|
static void drawNodePicker(OLEDDisplay *display, OLEDDisplayUiState *state);
|
||||||
|
static void drawTextInput(OLEDDisplay *display, OLEDDisplayUiState *state);
|
||||||
static void drawNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[MAX_LINES + 1],
|
static void drawNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[MAX_LINES + 1],
|
||||||
uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth = 0);
|
uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth = 0);
|
||||||
|
static void drawInvertedNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[],
|
||||||
|
uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth = 0);
|
||||||
|
|
||||||
static void drawCriticalFaultFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
static void drawCriticalFaultFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
static void drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
static void drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
#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(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS)) && \
|
||||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||||
|
|
||||||
if (isHighResolution) {
|
if (isHighResolution) {
|
||||||
@@ -879,7 +879,26 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
|||||||
config.display.heading_bold = false;
|
config.display.heading_bold = false;
|
||||||
|
|
||||||
const char *displayLine = ""; // Initialize to empty string by default
|
const char *displayLine = ""; // Initialize to empty string by default
|
||||||
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
|
|
||||||
|
bool usePhoneGPS = (ourNode && nodeDB->hasValidPosition(ourNode) &&
|
||||||
|
config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED);
|
||||||
|
|
||||||
|
if (usePhoneGPS) {
|
||||||
|
// Phone-provided GPS is active
|
||||||
|
displayLine = "Phone GPS";
|
||||||
|
int yOffset = (isHighResolution) ? 3 : 1;
|
||||||
|
if (isHighResolution) {
|
||||||
|
NodeListRenderer::drawScaledXBitmap16x16(x, getTextPositions(display)[line] + yOffset - 5, imgSatellite_width,
|
||||||
|
imgSatellite_height, imgSatellite, display);
|
||||||
|
} else {
|
||||||
|
display->drawXbm(x + 1, getTextPositions(display)[line] + yOffset, imgSatellite_width, imgSatellite_height,
|
||||||
|
imgSatellite);
|
||||||
|
}
|
||||||
|
int xOffset = (isHighResolution) ? 6 : 0;
|
||||||
|
display->drawString(x + 11 + xOffset, getTextPositions(display)[line++], displayLine);
|
||||||
|
} else if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
||||||
|
// GPS disabled / not present
|
||||||
if (config.position.fixed_position) {
|
if (config.position.fixed_position) {
|
||||||
displayLine = "Fixed GPS";
|
displayLine = "Fixed GPS";
|
||||||
} else {
|
} else {
|
||||||
@@ -896,6 +915,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
|||||||
int xOffset = (isHighResolution) ? 6 : 0;
|
int xOffset = (isHighResolution) ? 6 : 0;
|
||||||
display->drawString(x + 11 + xOffset, getTextPositions(display)[line++], displayLine);
|
display->drawString(x + 11 + xOffset, getTextPositions(display)[line++], displayLine);
|
||||||
} else {
|
} else {
|
||||||
|
// Onboard GPS
|
||||||
UIRenderer::drawGps(display, 0, getTextPositions(display)[line++], gpsStatus);
|
UIRenderer::drawGps(display, 0, getTextPositions(display)[line++], gpsStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -922,32 +942,61 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
|||||||
|
|
||||||
// If GPS is off, no need to display these parts
|
// If GPS is off, no need to display these parts
|
||||||
if (strcmp(displayLine, "GPS off") != 0 && strcmp(displayLine, "No GPS") != 0) {
|
if (strcmp(displayLine, "GPS off") != 0 && strcmp(displayLine, "No GPS") != 0) {
|
||||||
|
// === Second Row: Last GPS Fix ===
|
||||||
|
if (gpsStatus->getLastFixMillis() > 0) {
|
||||||
|
uint32_t delta = (millis() - gpsStatus->getLastFixMillis()) / 1000; // seconds since last fix
|
||||||
|
uint32_t days = delta / 86400;
|
||||||
|
uint32_t hours = (delta % 86400) / 3600;
|
||||||
|
uint32_t mins = (delta % 3600) / 60;
|
||||||
|
uint32_t secs = delta % 60;
|
||||||
|
|
||||||
// === Second Row: Date ===
|
char buf[32];
|
||||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true);
|
#if defined(USE_EINK)
|
||||||
char datetimeStr[25];
|
// E-Ink: skip seconds, show only days/hours/mins
|
||||||
bool showTime = false; // set to true for full datetime
|
if (days > 0) {
|
||||||
UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, showTime);
|
snprintf(buf, sizeof(buf), " Last: %ud %uh", days, hours);
|
||||||
char fullLine[40];
|
} else if (hours > 0) {
|
||||||
snprintf(fullLine, sizeof(fullLine), " Date: %s", datetimeStr);
|
snprintf(buf, sizeof(buf), " Last: %uh %um", hours, mins);
|
||||||
display->drawString(0, getTextPositions(display)[line++], fullLine);
|
} else {
|
||||||
|
snprintf(buf, sizeof(buf), " Last: %um", mins);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// Non E-Ink: include seconds where useful
|
||||||
|
if (days > 0) {
|
||||||
|
snprintf(buf, sizeof(buf), "Last: %ud %uh", days, hours);
|
||||||
|
} else if (hours > 0) {
|
||||||
|
snprintf(buf, sizeof(buf), "Last: %uh %um", hours, mins);
|
||||||
|
} else if (mins > 0) {
|
||||||
|
snprintf(buf, sizeof(buf), "Last: %um %us", mins, secs);
|
||||||
|
} else {
|
||||||
|
snprintf(buf, sizeof(buf), "Last: %us", secs);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
display->drawString(0, getTextPositions(display)[line++], buf);
|
||||||
|
} else {
|
||||||
|
display->drawString(0, getTextPositions(display)[line++], "Last: ?");
|
||||||
|
}
|
||||||
|
|
||||||
// === Third Row: Latitude ===
|
// === Third Row: Latitude ===
|
||||||
char latStr[32];
|
char latStr[32];
|
||||||
snprintf(latStr, sizeof(latStr), " Lat: %.5f", geoCoord.getLatitude() * 1e-7);
|
snprintf(latStr, sizeof(latStr), "Lat: %.5f", geoCoord.getLatitude() * 1e-7);
|
||||||
display->drawString(x, getTextPositions(display)[line++], latStr);
|
display->drawString(x, getTextPositions(display)[line++], latStr);
|
||||||
|
|
||||||
// === Fourth Row: Longitude ===
|
// === Fourth Row: Longitude ===
|
||||||
char lonStr[32];
|
char lonStr[32];
|
||||||
snprintf(lonStr, sizeof(lonStr), " Lon: %.5f", geoCoord.getLongitude() * 1e-7);
|
snprintf(lonStr, sizeof(lonStr), "Lon: %.5f", geoCoord.getLongitude() * 1e-7);
|
||||||
display->drawString(x, getTextPositions(display)[line++], lonStr);
|
display->drawString(x, getTextPositions(display)[line++], lonStr);
|
||||||
|
|
||||||
// === Fifth Row: Altitude ===
|
// === Fifth Row: Altitude ===
|
||||||
char DisplayLineTwo[32] = {0};
|
char DisplayLineTwo[32] = {0};
|
||||||
|
int32_t alt = (strcmp(displayLine, "Phone GPS") == 0 && ourNode && nodeDB->hasValidPosition(ourNode))
|
||||||
|
? ourNode->position.altitude
|
||||||
|
: geoCoord.getAltitude();
|
||||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||||
snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), " Alt: %.0fft", geoCoord.getAltitude() * METERS_TO_FEET);
|
snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), "Alt: %.0fft", geoCoord.getAltitude() * METERS_TO_FEET);
|
||||||
} else {
|
} else {
|
||||||
snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), " Alt: %.0im", geoCoord.getAltitude());
|
snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), "Alt: %.0im", geoCoord.getAltitude());
|
||||||
}
|
}
|
||||||
display->drawString(x, getTextPositions(display)[line++], DisplayLineTwo);
|
display->drawString(x, getTextPositions(display)[line++], DisplayLineTwo);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "NodeDB.h"
|
||||||
#include "graphics/Screen.h"
|
#include "graphics/Screen.h"
|
||||||
#include "graphics/emotes.h"
|
#include "graphics/emotes.h"
|
||||||
#include <OLEDDisplay.h>
|
#include <OLEDDisplay.h>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#ifdef USE_EINK
|
||||||
|
|
||||||
#include "EinkDisplayFonts.h"
|
#include "EinkDisplayFonts.h"
|
||||||
|
|
||||||
// Created by https://oleddisplay.squix.ch/ Consider a donation
|
// Created by https://oleddisplay.squix.ch/ Consider a donation
|
||||||
@@ -1182,3 +1184,5 @@ const uint8_t Monospaced_plain_30[] PROGMEM = {
|
|||||||
0xF0, 0xFF, 0x01, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0xE0, 0x00, 0xFE, 0x03, 0x00, 0xE0, 0x80, 0x7F,
|
0xF0, 0xFF, 0x01, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0xE0, 0x00, 0xFE, 0x03, 0x00, 0xE0, 0x80, 0x7F,
|
||||||
0x00, 0x00, 0xE0, 0xE0, 0x1F, 0x00, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x10 // 255
|
0x00, 0x00, 0xE0, 0xE0, 0x1F, 0x00, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x10 // 255
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif // USE_EINK
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#ifndef EINKDISPLAYFONTS_h
|
#ifndef EINKDISPLAYFONTS_h
|
||||||
#define EINKDISPLAYFONTS_h
|
#define EINKDISPLAYFONTS_h
|
||||||
|
|
||||||
|
#ifdef USE_EINK
|
||||||
|
|
||||||
#ifdef ARDUINO
|
#ifdef ARDUINO
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#elif __MBED__
|
#elif __MBED__
|
||||||
@@ -11,4 +13,7 @@
|
|||||||
* Monospaced Plain 30
|
* Monospaced Plain 30
|
||||||
*/
|
*/
|
||||||
extern const uint8_t Monospaced_plain_30[] PROGMEM;
|
extern const uint8_t Monospaced_plain_30[] PROGMEM;
|
||||||
|
|
||||||
|
#endif // USE_EINK
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#ifdef OLED_CS
|
||||||
|
|
||||||
#include "OLEDDisplayFontsCS.h"
|
#include "OLEDDisplayFontsCS.h"
|
||||||
|
|
||||||
// Font generated or edited with the glyphEditor
|
// Font generated or edited with the glyphEditor
|
||||||
@@ -1860,4 +1862,6 @@ const uint8_t ArialMT_Plain_24_CS[] PROGMEM = {
|
|||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0xC0, 0xF0, 0x01, 0x06, 0xC0, 0x80, 0x0F, 0x07, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0xC0, 0xF0, 0x01, 0x06, 0xC0, 0x80, 0x0F, 0x07, 0x00,
|
||||||
0x00, 0xFE, 0x03, 0x00, 0x00, 0xFC, 0x00, 0xC0, 0xC0, 0x1F, 0x00, 0xC0, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00,
|
0x00, 0xFE, 0x03, 0x00, 0x00, 0xFC, 0x00, 0xC0, 0xC0, 0x1F, 0x00, 0xC0, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00,
|
||||||
0x06, // 255
|
0x06, // 255
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif // OLED_CS
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
// trunk-ignore-all(clang-format): Preserve long lines
|
// trunk-ignore-all(clang-format): Preserve long lines
|
||||||
|
#ifdef OLED_PL
|
||||||
#include "OLEDDisplayFontsPL.h"
|
#include "OLEDDisplayFontsPL.h"
|
||||||
|
|
||||||
const uint8_t ArialMT_Plain_10_PL[] PROGMEM = {
|
const uint8_t ArialMT_Plain_10_PL[] PROGMEM = {
|
||||||
@@ -1310,4 +1311,6 @@ const uint8_t ArialMT_Plain_24_PL[] PROGMEM = {
|
|||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0x00, 0xF0, 0x01, 0x06, 0x00, 0x80, 0x0F, 0x07, 0x80, 0x00, 0xFE, 0x03, 0xE0, 0x00, 0xFC, 0x00, 0x60, 0xC0, 0x1F, 0x00, 0x20, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x06, // 253
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0x00, 0xF0, 0x01, 0x06, 0x00, 0x80, 0x0F, 0x07, 0x80, 0x00, 0xFE, 0x03, 0xE0, 0x00, 0xFC, 0x00, 0x60, 0xC0, 0x1F, 0x00, 0x20, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x06, // 253
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x07, 0xE0, 0xFF, 0xFF, 0x07, 0x00, 0x1C, 0x18, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xF0, 0x03, // 254
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x07, 0xE0, 0xFF, 0xFF, 0x07, 0x00, 0x1C, 0x18, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xF0, 0x03, // 254
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0xC0, 0xF0, 0x01, 0x06, 0xC0, 0x80, 0x0F, 0x07, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x00, 0xFC, 0x00, 0xC0, 0xC0, 0x1F, 0x00, 0xC0, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x06, // 255
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0xC0, 0xF0, 0x01, 0x06, 0xC0, 0x80, 0x0F, 0x07, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x00, 0xFC, 0x00, 0xC0, 0xC0, 0x1F, 0x00, 0xC0, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x06, // 255
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif // OLED_PL
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -8,4 +8,6 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern const uint8_t ArialMT_Plain_10_RU[] PROGMEM;
|
extern const uint8_t ArialMT_Plain_10_RU[] PROGMEM;
|
||||||
|
extern const uint8_t ArialMT_Plain_16_RU[] PROGMEM;
|
||||||
|
extern const uint8_t ArialMT_Plain_24_RU[] PROGMEM;
|
||||||
#endif
|
#endif
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#ifdef OLED_UA
|
||||||
|
|
||||||
#include "OLEDDisplayFontsUA.h"
|
#include "OLEDDisplayFontsUA.h"
|
||||||
|
|
||||||
// Font generated or edited with the glyphEditor
|
// Font generated or edited with the glyphEditor
|
||||||
@@ -1920,4 +1922,6 @@ const uint8_t ArialMT_Plain_24_UA[] PROGMEM = {
|
|||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xC1, 0x00, 0x00, 0xF0, 0xE3, 0x00, 0x00, 0x38, 0x7B, 0x00, 0x00, 0x18, 0x1A, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xC1, 0x00, 0x00, 0xF0, 0xE3, 0x00, 0x00, 0x38, 0x7B, 0x00, 0x00, 0x18, 0x1A, 0x00, 0x00,
|
||||||
0x18, 0x0E, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x00, 0xF8,
|
0x18, 0x0E, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x00, 0xF8,
|
||||||
0xFF, // 1103
|
0xFF, // 1103
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif // OLED_UA
|
||||||
@@ -27,7 +27,8 @@ const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03
|
|||||||
0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f};
|
0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f};
|
||||||
|
|
||||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
#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) || ARCH_PORTDUINO) && \
|
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \
|
||||||
|
ARCH_PORTDUINO) && \
|
||||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||||
const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff};
|
const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff};
|
||||||
const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f};
|
const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f};
|
||||||
@@ -117,8 +118,8 @@ const uint8_t icon_radio[] PROGMEM = {
|
|||||||
0xA9 // Row 7: #..#.#.#
|
0xA9 // Row 7: #..#.#.#
|
||||||
};
|
};
|
||||||
|
|
||||||
// 🪙 Memory Icon
|
// 🪙 System Icon
|
||||||
const uint8_t icon_memory[] PROGMEM = {
|
const uint8_t icon_system[] PROGMEM = {
|
||||||
0x24, // Row 0: ..#..#..
|
0x24, // Row 0: ..#..#..
|
||||||
0x3C, // Row 1: ..####..
|
0x3C, // Row 1: ..####..
|
||||||
0xC3, // Row 2: ##....##
|
0xC3, // Row 2: ##....##
|
||||||
@@ -287,5 +288,77 @@ const uint8_t digital_icon_clock[] PROGMEM = {0b00111100, 0b01000010, 0b10000101
|
|||||||
const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000,
|
const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000,
|
||||||
0b00100100, 0b01000010, 0b01000010, 0b11111111};
|
0b00100100, 0b01000010, 0b01000010, 0b11111111};
|
||||||
|
|
||||||
|
#define chirpy_width 38
|
||||||
|
#define chirpy_height 50
|
||||||
|
static unsigned char chirpy[] = {
|
||||||
|
0xfe, 0xff, 0xff, 0xff, 0xdf, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x80, 0xe3, 0x01,
|
||||||
|
0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0x80, 0xe3, 0x01, 0x00,
|
||||||
|
0x00, 0x00, 0xe0, 0x81, 0xff, 0xff, 0x7f, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xcf, 0x7f,
|
||||||
|
0xfe, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc,
|
||||||
|
0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0,
|
||||||
|
0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1,
|
||||||
|
0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0xcf, 0x7f, 0xfe, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0x81, 0xff,
|
||||||
|
0xff, 0x7f, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0xc3, 0x00, 0xe0, 0x01, 0x00, 0xc3,
|
||||||
|
0x00, 0xe0, 0x01, 0x80, 0xe1, 0x01, 0xe0, 0x01, 0x80, 0xe1, 0x01, 0xe0, 0x01, 0xc0, 0x30, 0x03, 0xe0, 0x01, 0xc0, 0x30, 0x03,
|
||||||
|
0xe0, 0x01, 0x60, 0x18, 0x06, 0xe0, 0x01, 0x60, 0x18, 0x06, 0xe0, 0x01, 0x30, 0x0c, 0x0c, 0xe0, 0x01, 0x30, 0x0c, 0x0c, 0xe0,
|
||||||
|
0x01, 0x18, 0x06, 0x18, 0xe0, 0x01, 0x18, 0x06, 0x18, 0xe0, 0x01, 0x0c, 0x03, 0x30, 0xe0, 0x01, 0x0c, 0x03, 0x30, 0xe0, 0x01,
|
||||||
|
0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0xfe, 0xff, 0xff, 0xff, 0xdf};
|
||||||
|
|
||||||
|
#define chirpy_width_hirez 76
|
||||||
|
#define chirpy_height_hirez 100
|
||||||
|
static unsigned char chirpy_hirez[] = {
|
||||||
|
0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x03,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00,
|
||||||
|
0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc,
|
||||||
|
0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03,
|
||||||
|
0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xf0, 0xff, 0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0,
|
||||||
|
0xff, 0xf0, 0xff, 0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f,
|
||||||
|
0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0,
|
||||||
|
0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff,
|
||||||
|
0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f,
|
||||||
|
0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0,
|
||||||
|
0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff,
|
||||||
|
0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00,
|
||||||
|
0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc,
|
||||||
|
0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03,
|
||||||
|
0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0,
|
||||||
|
0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f,
|
||||||
|
0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0,
|
||||||
|
0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xf0, 0xff,
|
||||||
|
0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xf0, 0xff, 0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f,
|
||||||
|
0x00, 0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc,
|
||||||
|
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03,
|
||||||
|
0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00,
|
||||||
|
0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00,
|
||||||
|
0xc0, 0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0,
|
||||||
|
0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00,
|
||||||
|
0x0f, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x0f,
|
||||||
|
0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c,
|
||||||
|
0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c, 0x00,
|
||||||
|
0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00,
|
||||||
|
0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xfc,
|
||||||
|
0x03, 0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03,
|
||||||
|
0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00,
|
||||||
|
0xf0, 0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0,
|
||||||
|
0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xf3, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3};
|
||||||
|
|
||||||
|
#define chirpy_small_image_width 8
|
||||||
|
#define chirpy_small_image_height 8
|
||||||
|
static unsigned char small_chirpy[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
|
||||||
|
|
||||||
#include "img/icon.xbm"
|
#include "img/icon.xbm"
|
||||||
static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning");
|
static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning");
|
||||||
68
src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.cpp
Normal file
68
src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.cpp
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#include "./ZJY122250_0213BAAMFGN.h"
|
||||||
|
|
||||||
|
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||||
|
|
||||||
|
using namespace NicheGraphics::Drivers;
|
||||||
|
|
||||||
|
// Map the display controller IC's output to the connected panel
|
||||||
|
void ZJY122250_0213BAAMFGN::configScanning()
|
||||||
|
{
|
||||||
|
// "Driver output control"
|
||||||
|
// Scan gates from 0 to 249 (vertical resolution 250px)
|
||||||
|
sendCommand(0x01);
|
||||||
|
sendData(0xF9);
|
||||||
|
sendData(0x00);
|
||||||
|
sendData(0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify which information is used to control the sequence of voltages applied to move the pixels
|
||||||
|
// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
|
||||||
|
// the controller IC's OTP memory, when the update procedure begins.
|
||||||
|
void ZJY122250_0213BAAMFGN::configWaveform()
|
||||||
|
{
|
||||||
|
switch (updateType) {
|
||||||
|
case FAST:
|
||||||
|
sendCommand(0x3C); // Border waveform:
|
||||||
|
sendData(0x80); // VCOM
|
||||||
|
break;
|
||||||
|
case FULL:
|
||||||
|
default:
|
||||||
|
sendCommand(0x3C); // Border waveform:
|
||||||
|
sendData(0x01); // Follow LUT 1 (blink same as white pixels)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendCommand(0x18); // Temperature sensor:
|
||||||
|
sendData(0x80); // Use internal temperature sensor to select an appropriate refresh waveform
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZJY122250_0213BAAMFGN::configUpdateSequence()
|
||||||
|
{
|
||||||
|
switch (updateType) {
|
||||||
|
case FAST:
|
||||||
|
sendCommand(0x22); // Set "update sequence"
|
||||||
|
sendData(0xFF); // Will load LUT from OTP memory, Display mode 2 "differential refresh"
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FULL:
|
||||||
|
default:
|
||||||
|
sendCommand(0x22); // Set "update sequence"
|
||||||
|
sendData(0xF7); // Will load LUT from OTP memory
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once the refresh operation has been started,
|
||||||
|
// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
|
||||||
|
// Only used when refresh is "async"
|
||||||
|
void ZJY122250_0213BAAMFGN::detachFromUpdate()
|
||||||
|
{
|
||||||
|
switch (updateType) {
|
||||||
|
case FAST:
|
||||||
|
return beginPolling(50, 500); // At least 500ms for fast refresh
|
||||||
|
case FULL:
|
||||||
|
default:
|
||||||
|
return beginPolling(100, 2000); // At least 2 seconds for full refresh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||||
42
src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.h
Normal file
42
src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
E-Ink display driver
|
||||||
|
- ZJY122250_0213BAAMFGN
|
||||||
|
- Manufacturer: Zhongjingyuan
|
||||||
|
- Size: 2.13 inch
|
||||||
|
- Resolution: 250px x 122px
|
||||||
|
- Flex connector marking (not a unique identifier): FPC-A002
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
#include "./SSD16XX.h"
|
||||||
|
|
||||||
|
namespace NicheGraphics::Drivers
|
||||||
|
{
|
||||||
|
class ZJY122250_0213BAAMFGN : public SSD16XX
|
||||||
|
{
|
||||||
|
// Display properties
|
||||||
|
private:
|
||||||
|
static constexpr uint32_t width = 122;
|
||||||
|
static constexpr uint32_t height = 250;
|
||||||
|
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
|
||||||
|
|
||||||
|
public:
|
||||||
|
ZJY122250_0213BAAMFGN() : SSD16XX(width, height, supported) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void configScanning() override;
|
||||||
|
virtual void configWaveform() override;
|
||||||
|
virtual void configUpdateSequence() override;
|
||||||
|
void detachFromUpdate() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace NicheGraphics::Drivers
|
||||||
|
|
||||||
|
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||||
@@ -7,12 +7,7 @@ using namespace NicheGraphics;
|
|||||||
|
|
||||||
// Timing for "maintenance"
|
// Timing for "maintenance"
|
||||||
// Paying off full-refresh debt with unprovoked updates, if the display is not very active
|
// Paying off full-refresh debt with unprovoked updates, if the display is not very active
|
||||||
|
|
||||||
#ifdef SEEED_WIO_TRACKER_L1
|
|
||||||
static constexpr uint32_t MAINTENANCE_MS_INITIAL = 5 * 1000UL;
|
|
||||||
#else
|
|
||||||
static constexpr uint32_t MAINTENANCE_MS_INITIAL = 60 * 1000UL;
|
static constexpr uint32_t MAINTENANCE_MS_INITIAL = 60 * 1000UL;
|
||||||
#endif
|
|
||||||
static constexpr uint32_t MAINTENANCE_MS = 60 * 60 * 1000UL;
|
static constexpr uint32_t MAINTENANCE_MS = 60 * 60 * 1000UL;
|
||||||
|
|
||||||
InkHUD::DisplayHealth::DisplayHealth() : concurrency::OSThread("Mediator")
|
InkHUD::DisplayHealth::DisplayHealth() : concurrency::OSThread("Mediator")
|
||||||
|
|||||||
@@ -41,78 +41,78 @@ void tftSetup(void)
|
|||||||
PacketAPI::create(PacketServer::init());
|
PacketAPI::create(PacketServer::init());
|
||||||
deviceScreen->init(new PacketClient);
|
deviceScreen->init(new PacketClient);
|
||||||
#else
|
#else
|
||||||
if (settingsMap[displayPanel] != no_screen) {
|
if (portduino_config.displayPanel != no_screen) {
|
||||||
DisplayDriverConfig displayConfig;
|
DisplayDriverConfig displayConfig;
|
||||||
static char *panels[] = {"NOSCREEN", "X11", "FB", "ST7789", "ST7735", "ST7735S",
|
static char *panels[] = {"NOSCREEN", "X11", "FB", "ST7789", "ST7735", "ST7735S",
|
||||||
"ST7796", "ILI9341", "ILI9342", "ILI9486", "ILI9488", "HX8357D"};
|
"ST7796", "ILI9341", "ILI9342", "ILI9486", "ILI9488", "HX8357D"};
|
||||||
static char *touch[] = {"NOTOUCH", "XPT2046", "STMPE610", "GT911", "FT5x06"};
|
static char *touch[] = {"NOTOUCH", "XPT2046", "STMPE610", "GT911", "FT5x06"};
|
||||||
#if defined(USE_X11)
|
#if defined(USE_X11)
|
||||||
if (settingsMap[displayPanel] == x11) {
|
if (portduino_config.displayPanel == x11) {
|
||||||
if (settingsMap[displayWidth] && settingsMap[displayHeight])
|
if (portduino_config.displayWidth && portduino_config.displayHeight)
|
||||||
displayConfig = DisplayDriverConfig(DisplayDriverConfig::device_t::X11, (uint16_t)settingsMap[displayWidth],
|
displayConfig = DisplayDriverConfig(DisplayDriverConfig::device_t::X11, (uint16_t)portduino_config.displayWidth,
|
||||||
(uint16_t)settingsMap[displayHeight]);
|
(uint16_t)portduino_config.displayHeight);
|
||||||
else
|
else
|
||||||
displayConfig.device(DisplayDriverConfig::device_t::X11);
|
displayConfig.device(DisplayDriverConfig::device_t::X11);
|
||||||
} else
|
} else
|
||||||
#elif defined(USE_FRAMEBUFFER)
|
#elif defined(USE_FRAMEBUFFER)
|
||||||
if (settingsMap[displayPanel] == fb) {
|
if (portduino_config.displayPanel == fb) {
|
||||||
if (settingsMap[displayWidth] && settingsMap[displayHeight])
|
if (portduino_config.displayWidth && portduino_config.displayHeight)
|
||||||
displayConfig = DisplayDriverConfig(DisplayDriverConfig::device_t::FB, (uint16_t)settingsMap[displayWidth],
|
displayConfig = DisplayDriverConfig(DisplayDriverConfig::device_t::FB, (uint16_t)portduino_config.displayWidth,
|
||||||
(uint16_t)settingsMap[displayHeight]);
|
(uint16_t)portduino_config.displayHeight);
|
||||||
else
|
else
|
||||||
displayConfig.device(DisplayDriverConfig::device_t::FB);
|
displayConfig.device(DisplayDriverConfig::device_t::FB);
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
displayConfig.device(DisplayDriverConfig::device_t::CUSTOM_TFT)
|
displayConfig.device(DisplayDriverConfig::device_t::CUSTOM_TFT)
|
||||||
.panel(DisplayDriverConfig::panel_config_t{.type = panels[settingsMap[displayPanel]],
|
.panel(DisplayDriverConfig::panel_config_t{.type = panels[portduino_config.displayPanel],
|
||||||
.panel_width = (uint16_t)settingsMap[displayWidth],
|
.panel_width = (uint16_t)portduino_config.displayWidth,
|
||||||
.panel_height = (uint16_t)settingsMap[displayHeight],
|
.panel_height = (uint16_t)portduino_config.displayHeight,
|
||||||
.rotation = (bool)settingsMap[displayRotate],
|
.rotation = (bool)portduino_config.displayRotate,
|
||||||
.pin_cs = (int16_t)settingsMap[displayCS],
|
.pin_cs = (int16_t)portduino_config.displayCS.pin,
|
||||||
.pin_rst = (int16_t)settingsMap[displayReset],
|
.pin_rst = (int16_t)portduino_config.displayReset.pin,
|
||||||
.offset_x = (uint16_t)settingsMap[displayOffsetX],
|
.offset_x = (uint16_t)portduino_config.displayOffsetX,
|
||||||
.offset_y = (uint16_t)settingsMap[displayOffsetY],
|
.offset_y = (uint16_t)portduino_config.displayOffsetY,
|
||||||
.offset_rotation = (uint8_t)settingsMap[displayOffsetRotate],
|
.offset_rotation = (uint8_t)portduino_config.displayOffsetRotate,
|
||||||
.invert = settingsMap[displayInvert] ? true : false,
|
.invert = portduino_config.displayInvert ? true : false,
|
||||||
.rgb_order = (bool)settingsMap[displayRGBOrder],
|
.rgb_order = (bool)portduino_config.displayRGBOrder,
|
||||||
.dlen_16bit = settingsMap[displayPanel] == ili9486 ||
|
.dlen_16bit = portduino_config.displayPanel == ili9486 ||
|
||||||
settingsMap[displayPanel] == ili9488})
|
portduino_config.displayPanel == ili9488})
|
||||||
.bus(DisplayDriverConfig::bus_config_t{.freq_write = (uint32_t)settingsMap[displayBusFrequency],
|
.bus(DisplayDriverConfig::bus_config_t{.freq_write = (uint32_t)portduino_config.displayBusFrequency,
|
||||||
.freq_read = 16000000,
|
.freq_read = 16000000,
|
||||||
.spi{.pin_dc = (int8_t)settingsMap[displayDC],
|
.spi{.pin_dc = (int8_t)portduino_config.displayDC.pin,
|
||||||
.use_lock = true,
|
.use_lock = true,
|
||||||
.spi_host = (uint16_t)settingsMap[displayspidev]}})
|
.spi_host = (uint16_t)portduino_config.display_spi_dev_int}})
|
||||||
.input(DisplayDriverConfig::input_config_t{.keyboardDevice = settingsStrings[keyboardDevice],
|
.input(DisplayDriverConfig::input_config_t{.keyboardDevice = portduino_config.keyboardDevice,
|
||||||
.pointerDevice = settingsStrings[pointerDevice]})
|
.pointerDevice = portduino_config.pointerDevice})
|
||||||
.light(DisplayDriverConfig::light_config_t{.pin_bl = (int16_t)settingsMap[displayBacklight],
|
.light(DisplayDriverConfig::light_config_t{.pin_bl = (int16_t)portduino_config.displayBacklight.pin,
|
||||||
.pwm_channel = (int8_t)settingsMap[displayBacklightPWMChannel],
|
.pwm_channel = (int8_t)portduino_config.displayBacklightPWMChannel.pin,
|
||||||
.invert = (bool)settingsMap[displayBacklightInvert]});
|
.invert = (bool)portduino_config.displayBacklightInvert});
|
||||||
if (settingsMap[touchscreenI2CAddr] == -1) {
|
if (portduino_config.touchscreenI2CAddr == -1) {
|
||||||
displayConfig.touch(
|
displayConfig.touch(
|
||||||
DisplayDriverConfig::touch_config_t{.type = touch[settingsMap[touchscreenModule]],
|
DisplayDriverConfig::touch_config_t{.type = touch[portduino_config.touchscreenModule],
|
||||||
.freq = (uint32_t)settingsMap[touchscreenBusFrequency],
|
.freq = (uint32_t)portduino_config.touchscreenBusFrequency,
|
||||||
.pin_int = (int16_t)settingsMap[touchscreenIRQ],
|
.pin_int = (int16_t)portduino_config.touchscreenIRQ.pin,
|
||||||
.offset_rotation = (uint8_t)settingsMap[touchscreenRotate],
|
.offset_rotation = (uint8_t)portduino_config.touchscreenRotate,
|
||||||
.spi{
|
.spi{
|
||||||
.spi_host = (int8_t)settingsMap[touchscreenspidev],
|
.spi_host = (int8_t)portduino_config.touchscreen_spi_dev_int,
|
||||||
},
|
},
|
||||||
.pin_cs = (int16_t)settingsMap[touchscreenCS]});
|
.pin_cs = (int16_t)portduino_config.touchscreenCS.pin});
|
||||||
} else {
|
} else {
|
||||||
displayConfig.touch(DisplayDriverConfig::touch_config_t{
|
displayConfig.touch(DisplayDriverConfig::touch_config_t{
|
||||||
.type = touch[settingsMap[touchscreenModule]],
|
.type = touch[portduino_config.touchscreenModule],
|
||||||
.freq = (uint32_t)settingsMap[touchscreenBusFrequency],
|
.freq = (uint32_t)portduino_config.touchscreenBusFrequency,
|
||||||
.x_min = 0,
|
.x_min = 0,
|
||||||
.x_max =
|
.x_max = (int16_t)((portduino_config.touchscreenRotate & 1 ? portduino_config.displayWidth
|
||||||
(int16_t)((settingsMap[touchscreenRotate] & 1 ? settingsMap[displayWidth] : settingsMap[displayHeight]) -
|
: portduino_config.displayHeight) -
|
||||||
1),
|
1),
|
||||||
.y_min = 0,
|
.y_min = 0,
|
||||||
.y_max =
|
.y_max = (int16_t)((portduino_config.touchscreenRotate & 1 ? portduino_config.displayHeight
|
||||||
(int16_t)((settingsMap[touchscreenRotate] & 1 ? settingsMap[displayHeight] : settingsMap[displayWidth]) -
|
: portduino_config.displayWidth) -
|
||||||
1),
|
1),
|
||||||
.pin_int = (int16_t)settingsMap[touchscreenIRQ],
|
.pin_int = (int16_t)portduino_config.touchscreenIRQ.pin,
|
||||||
.offset_rotation = (uint8_t)settingsMap[touchscreenRotate],
|
.offset_rotation = (uint8_t)portduino_config.touchscreenRotate,
|
||||||
.i2c{.i2c_addr = (uint8_t)settingsMap[touchscreenI2CAddr]}});
|
.i2c{.i2c_addr = (uint8_t)portduino_config.touchscreenI2CAddr}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deviceScreen = &DeviceScreen::create(&displayConfig);
|
deviceScreen = &DeviceScreen::create(&displayConfig);
|
||||||
|
|||||||
@@ -92,8 +92,11 @@ bool ButtonThread::initButton(const ButtonConfig &config)
|
|||||||
if (config.shortLong != INPUT_BROKER_NONE) {
|
if (config.shortLong != INPUT_BROKER_NONE) {
|
||||||
_shortLong = config.shortLong;
|
_shortLong = config.shortLong;
|
||||||
}
|
}
|
||||||
|
#ifdef USE_EINK
|
||||||
|
userButton.setDebounceMs(0);
|
||||||
|
#else
|
||||||
userButton.setDebounceMs(1);
|
userButton.setDebounceMs(1);
|
||||||
|
#endif
|
||||||
userButton.setPressMs(_longPressTime);
|
userButton.setPressMs(_longPressTime);
|
||||||
|
|
||||||
if (screen) {
|
if (screen) {
|
||||||
@@ -137,8 +140,7 @@ int32_t ButtonThread::runOnce()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Progressive lead-up sound system
|
// Progressive lead-up sound system
|
||||||
if (buttonCurrentlyPressed && (millis() - buttonPressStartTime) >= BUTTON_LEADUP_MS &&
|
if (buttonCurrentlyPressed && (millis() - buttonPressStartTime) >= BUTTON_LEADUP_MS) {
|
||||||
(millis() - buttonPressStartTime) < _longLongPressTime) {
|
|
||||||
|
|
||||||
// Start the progressive sequence if not already active
|
// Start the progressive sequence if not already active
|
||||||
if (!leadUpSequenceActive) {
|
if (!leadUpSequenceActive) {
|
||||||
@@ -150,13 +152,14 @@ int32_t ButtonThread::runOnce()
|
|||||||
else if ((millis() - lastLeadUpNoteTime) >= 400) { // 400ms interval between notes
|
else if ((millis() - lastLeadUpNoteTime) >= 400) { // 400ms interval between notes
|
||||||
if (playNextLeadUpNote()) {
|
if (playNextLeadUpNote()) {
|
||||||
lastLeadUpNoteTime = millis();
|
lastLeadUpNoteTime = millis();
|
||||||
|
} else {
|
||||||
|
leadUpPlayed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset when button is released
|
// Reset when button is released
|
||||||
if (!buttonCurrentlyPressed && buttonWasPressed) {
|
if (!buttonCurrentlyPressed && buttonWasPressed) {
|
||||||
leadUpPlayed = false;
|
|
||||||
leadUpSequenceActive = false;
|
leadUpSequenceActive = false;
|
||||||
resetLeadUpSequence();
|
resetLeadUpSequence();
|
||||||
}
|
}
|
||||||
@@ -253,12 +256,13 @@ int32_t ButtonThread::runOnce()
|
|||||||
|
|
||||||
LOG_INFO("LONG PRESS RELEASE AFTER %u MILLIS", millis() - buttonPressStartTime);
|
LOG_INFO("LONG PRESS RELEASE AFTER %u MILLIS", millis() - buttonPressStartTime);
|
||||||
if (millis() > 30000 && _longLongPress != INPUT_BROKER_NONE &&
|
if (millis() > 30000 && _longLongPress != INPUT_BROKER_NONE &&
|
||||||
(millis() - buttonPressStartTime) >= _longLongPressTime) {
|
(millis() - buttonPressStartTime) >= _longLongPressTime && leadUpPlayed) {
|
||||||
evt.inputEvent = _longLongPress;
|
evt.inputEvent = _longLongPress;
|
||||||
this->notifyObservers(&evt);
|
this->notifyObservers(&evt);
|
||||||
}
|
}
|
||||||
// Reset combination tracking
|
// Reset combination tracking
|
||||||
waitingForLongPress = false;
|
waitingForLongPress = false;
|
||||||
|
leadUpPlayed = false;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ class ButtonThread : public Observable<const InputEvent *>, public concurrency::
|
|||||||
return digitalRead(buttonPin); // Most buttons are active low by default
|
return digitalRead(buttonPin); // Most buttons are active low by default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true while this thread's button is physically held down
|
||||||
|
bool isHeld() { return isButtonPressed(_pinNum); }
|
||||||
|
|
||||||
// Disconnect and reconnect interrupts for light sleep
|
// Disconnect and reconnect interrupts for light sleep
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
int beforeLightSleep(void *unused);
|
int beforeLightSleep(void *unused);
|
||||||
@@ -92,7 +95,7 @@ class ButtonThread : public Observable<const InputEvent *>, public concurrency::
|
|||||||
|
|
||||||
voidFuncPtr _intRoutine = nullptr;
|
voidFuncPtr _intRoutine = nullptr;
|
||||||
uint16_t _longPressTime = 500;
|
uint16_t _longPressTime = 500;
|
||||||
uint16_t _longLongPressTime = 5000;
|
uint16_t _longLongPressTime = 3900;
|
||||||
int _pinNum = 0;
|
int _pinNum = 0;
|
||||||
bool _activeLow = true;
|
bool _activeLow = true;
|
||||||
bool _touchQuirk = false;
|
bool _touchQuirk = false;
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
enum input_broker_event {
|
enum input_broker_event {
|
||||||
INPUT_BROKER_NONE = 0,
|
INPUT_BROKER_NONE = 0,
|
||||||
INPUT_BROKER_SELECT = 10,
|
INPUT_BROKER_SELECT = 10,
|
||||||
|
INPUT_BROKER_SELECT_LONG = 11,
|
||||||
|
INPUT_BROKER_UP_LONG = 12,
|
||||||
|
INPUT_BROKER_DOWN_LONG = 13,
|
||||||
INPUT_BROKER_UP = 17,
|
INPUT_BROKER_UP = 17,
|
||||||
INPUT_BROKER_DOWN = 18,
|
INPUT_BROKER_DOWN = 18,
|
||||||
INPUT_BROKER_LEFT = 19,
|
INPUT_BROKER_LEFT = 19,
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ int32_t LinuxInput::runOnce()
|
|||||||
{
|
{
|
||||||
|
|
||||||
if (firstTime) {
|
if (firstTime) {
|
||||||
if (settingsStrings[keyboardDevice] == "")
|
if (portduino_config.keyboardDevice == "")
|
||||||
return disable();
|
return disable();
|
||||||
fd = open(settingsStrings[keyboardDevice].c_str(), O_RDWR);
|
fd = open(portduino_config.keyboardDevice.c_str(), O_RDWR);
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
return disable();
|
return disable();
|
||||||
ret = ioctl(fd, EVIOCGRAB, (void *)1);
|
ret = ioctl(fd, EVIOCGRAB, (void *)1);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user