Compare commits

..

14 Commits

Author SHA1 Message Date
Thomas Göttgens
45ecd139e5 Merge branch 'master' into indicator-comms 2025-07-06 14:38:50 +02:00
Thomas Göttgens
79db7a5208 WIP: GPS works 2025-07-04 00:24:33 +02:00
Thomas Göttgens
7289b2a972 Update src/main.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-03 00:15:52 +02:00
Thomas Göttgens
9356be3f8f Update src/mesh/comms/FakeI2C.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-03 00:15:32 +02:00
Thomas Göttgens
cce1b050c8 Update src/mesh/comms/FakeUART.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-03 00:15:24 +02:00
Thomas Göttgens
fd5ff679f3 WIP 2025-07-02 23:41:51 +02:00
Thomas Göttgens
7a7ef5f0c9 Merge branch 'master' into indicator-comms 2025-07-02 17:21:23 +02:00
Thomas Göttgens
f084a8a11d Merge branch 'master' into indicator-comms 2025-06-05 14:35:04 +02:00
Thomas Göttgens
37857941bf Merge branch 'master' into indicator-comms 2025-05-23 15:53:16 +02:00
Thomas Göttgens
d544b41ab7 Merge branch 'master' into indicator-comms 2025-03-31 11:09:44 +02:00
Thomas Göttgens
8c53ce82f2 don't build FakeUART on other platforms 2025-03-04 11:11:25 +01:00
Thomas Göttgens
7ee95f2a0c Merge branch 'indicator-comms' of github.com:meshtastic/firmware into indicator-comms 2025-03-04 10:39:35 +01:00
Thomas Göttgens
65b50babee get sensor and NMEA data from indicator. Will test tomorrow, don't merge yet. 2025-03-04 01:56:56 +01:00
Thomas Göttgens
3c30821337 get sensor and NMEA data from indicator. Will test tomorrow, don't merge yet. 2025-03-04 01:53:18 +01:00
107 changed files with 1180 additions and 2295 deletions

View File

@@ -11,30 +11,27 @@ permissions: read-all
jobs:
build-esp32:
runs-on: ubuntu-24.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Build ESP32
id: build
uses: meshtastic/gh-action-firmware@main
uses: ./.github/actions/build-variant
with:
pio_platform: esp32
pio_env: ${{ inputs.board }}
pio_target: build
ota_firmware_source: firmware.bin
ota_firmware_target: release/bleota.bin
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-esp32-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
overwrite: true
path: |
github_token: ${{ secrets.GITHUB_TOKEN }}
board: ${{ inputs.board }}
remove-debug-flags: >-
./arch/esp32/esp32.ini
./arch/esp32/esp32s2.ini
./arch/esp32/esp32s3.ini
./arch/esp32/esp32c3.ini
./arch/esp32/esp32c6.ini
build-script-path: bin/build-esp32.sh
ota-firmware-source: firmware.bin
ota-firmware-target: release/bleota.bin
artifact-paths: |
release/*.bin
release/*.elf
#include-web-ui: true
arch: esp32

View File

@@ -11,30 +11,27 @@ permissions: read-all
jobs:
build-esp32-c3:
runs-on: ubuntu-24.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Build ESP32-C3
id: build
uses: meshtastic/gh-action-firmware@main
uses: ./.github/actions/build-variant
with:
pio_platform: esp32
pio_env: ${{ inputs.board }}
pio_target: build
ota_firmware_source: firmware-c3.bin
ota_firmware_target: release/bleota-c3.bin
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-esp32c3-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
overwrite: true
path: |
github_token: ${{ secrets.GITHUB_TOKEN }}
board: ${{ inputs.board }}
remove-debug-flags: >-
./arch/esp32/esp32.ini
./arch/esp32/esp32s2.ini
./arch/esp32/esp32s3.ini
./arch/esp32/esp32c3.ini
./arch/esp32/esp32c6.ini
build-script-path: bin/build-esp32.sh
ota-firmware-source: firmware-c3.bin
ota-firmware-target: release/bleota-c3.bin
artifact-paths: |
release/*.bin
release/*.elf
#include-web-ui: true
arch: esp32c3

View File

@@ -11,30 +11,27 @@ permissions: read-all
jobs:
build-esp32-c6:
runs-on: ubuntu-24.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Build ESP32-C6
id: build
uses: meshtastic/gh-action-firmware@main
uses: ./.github/actions/build-variant
with:
pio_platform: esp32
pio_env: ${{ inputs.board }}
pio_target: build
ota_firmware_source: firmware-c3.bin
ota_firmware_target: release/bleota-c3.bin
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-esp32c6-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
overwrite: true
path: |
github_token: ${{ secrets.GITHUB_TOKEN }}
board: ${{ inputs.board }}
remove-debug-flags: >-
./arch/esp32/esp32.ini
./arch/esp32/esp32s2.ini
./arch/esp32/esp32s3.ini
./arch/esp32/esp32c3.ini
./arch/esp32/esp32c6.ini
build-script-path: bin/build-esp32.sh
ota-firmware-source: firmware-c3.bin
ota-firmware-target: release/bleota-c3.bin
artifact-paths: |
release/*.bin
release/*.elf
#include-web-ui: true
arch: esp32c6

View File

@@ -11,30 +11,27 @@ permissions: read-all
jobs:
build-esp32-s3:
runs-on: ubuntu-24.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Build ESP32-S3
id: build
uses: meshtastic/gh-action-firmware@main
uses: ./.github/actions/build-variant
with:
pio_platform: esp32
pio_env: ${{ inputs.board }}
pio_target: build
ota_firmware_source: firmware-s3.bin
ota_firmware_target: release/bleota-s3.bin
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-esp32s3-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
overwrite: true
path: |
github_token: ${{ secrets.GITHUB_TOKEN }}
board: ${{ inputs.board }}
remove-debug-flags: >-
./arch/esp32/esp32.ini
./arch/esp32/esp32s2.ini
./arch/esp32/esp32s3.ini
./arch/esp32/esp32c3.ini
./arch/esp32/esp32c6.ini
build-script-path: bin/build-esp32.sh
ota-firmware-source: firmware-s3.bin
ota-firmware-target: release/bleota-s3.bin
artifact-paths: |
release/*.bin
release/*.elf
#include-web-ui: true
arch: esp32s3

View File

@@ -11,30 +11,20 @@ permissions: read-all
jobs:
build-nrf52:
runs-on: ubuntu-24.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Build NRF52
id: build
uses: meshtastic/gh-action-firmware@main
uses: ./.github/actions/build-variant
with:
pio_platform: nrf52
pio_env: ${{ inputs.board }}
pio_target: build
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-nrf52840-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
overwrite: true
path: |
github_token: ${{ secrets.GITHUB_TOKEN }}
board: ${{ inputs.board }}
build-script-path: bin/build-nrf52.sh
artifact-paths: |
release/*.hex
release/*.uf2
release/*.elf
release/*.hex
release/*-ota.zip
release/*.zip
arch: nrf52840

View File

@@ -11,28 +11,18 @@ permissions: read-all
jobs:
build-rpi2040:
runs-on: ubuntu-24.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Build Raspberry Pi 2040
id: build
uses: meshtastic/gh-action-firmware@main
uses: ./.github/actions/build-variant
with:
pio_platform: rp2xx0
pio_env: ${{ inputs.board }}
pio_target: build
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-rp2040-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
overwrite: true
path: |
github_token: ${{ secrets.GITHUB_TOKEN }}
board: ${{ inputs.board }}
build-script-path: bin/build-rpi2040.sh
artifact-paths: |
release/*.uf2
release/*.elf
arch: rp2040

View File

@@ -11,29 +11,19 @@ permissions: read-all
jobs:
build-stm32:
runs-on: ubuntu-24.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get release version string
shell: bash
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Build STM32WL
id: build
uses: meshtastic/gh-action-firmware@main
uses: ./.github/actions/build-variant
with:
pio_platform: stm32wl
pio_env: ${{ inputs.board }}
pio_target: build
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-stm32-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
overwrite: true
path: |
github_token: ${{ secrets.GITHUB_TOKEN }}
board: ${{ inputs.board }}
build-script-path: bin/build-stm32.sh
artifact-paths: |
release/*.hex
release/*.bin
release/*.elf
arch: stm32

View File

@@ -30,7 +30,7 @@ jobs:
strategy:
fail-fast: false
matrix:
series: [plucky, noble, jammy]
series: [plucky, oracular, noble, jammy]
uses: ./.github/workflows/package_ppa.yml
with:
ppa_repo: ppa:meshtastic/daily

View File

@@ -135,7 +135,6 @@ jobs:
board: ${{ matrix.board }}
build-debian-src:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/build_debian_src.yml
with:
series: UNRELEASED
@@ -426,7 +425,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-firmware:
runs-on: ubuntu-24.04
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
needs: [release-firmware]
env:

View File

@@ -8,7 +8,6 @@ permissions: read-all
jobs:
trunk_check:
if: github.repository == 'meshtastic/firmware'
name: Trunk Check and Upload
runs-on: ubuntu-24.04
@@ -22,7 +21,6 @@ jobs:
trunk-token: ${{ secrets.TRUNK_TOKEN }}
trunk_upgrade:
if: github.repository == 'meshtastic/firmware'
# See: https://github.com/trunk-io/trunk-action/blob/v1/readme.md#automatic-upgrades
name: Trunk Upgrade (PR)
runs-on: ubuntu-24.04

View File

@@ -20,7 +20,7 @@ jobs:
strategy:
fail-fast: false
matrix:
series: [plucky, noble, jammy]
series: [plucky, oracular, noble, jammy]
uses: ./.github/workflows/package_ppa.yml
with:
ppa_repo: |-

View File

@@ -13,7 +13,6 @@ permissions:
jobs:
semgrep-full:
if: github.repository == 'meshtastic/firmware'
runs-on: ubuntu-24.04
container:
image: semgrep/semgrep

View File

@@ -11,7 +11,6 @@ permissions:
jobs:
stale_issues:
if: github.repository == 'meshtastic/firmware'
name: Close Stale Issues
runs-on: ubuntu-latest

View File

@@ -143,7 +143,7 @@ jobs:
merge-multiple: true
- name: Test Report
uses: dorny/test-reporter@v2.1.1
uses: dorny/test-reporter@v2.1.0
with:
name: PlatformIO Tests
path: testreport.xml

View File

@@ -12,11 +12,9 @@ permissions:
jobs:
native-tests:
if: github.repository == 'meshtastic/firmware'
uses: ./.github/workflows/test_native.yml
hardware-tests:
if: github.repository == 'meshtastic/firmware'
runs-on: test-runner
steps:
- name: Checkout code

View File

@@ -8,15 +8,15 @@ plugins:
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.450
- renovate@41.29.1
- checkov@3.2.447
- renovate@41.17.2
- prettier@3.6.2
- trufflehog@3.89.2
- yamllint@1.37.1
- bandit@1.8.6
- trivy@0.64.1
- bandit@1.8.5
- trivy@0.64.0
- taplo@0.9.3
- ruff@0.12.2
- ruff@0.12.1
- isort@6.0.1
- markdownlint@0.45.0
- oxipng@9.1.5

View File

@@ -17,6 +17,3 @@ lib_ignore =
${esp32_base.lib_ignore}
NimBLE-Arduino
libpax
lib_deps = ${esp32_base.lib_deps}
tanakamasayuki/EspUsbHost@^1.0.2

View File

@@ -3,6 +3,3 @@ extends = esp32_base
custom_esp32_kind = esp32s3
monitor_speed = 115200
lib_deps = ${esp32_base.lib_deps}
tanakamasayuki/EspUsbHost@^1.0.2

View File

@@ -2,7 +2,7 @@
[portduino_base]
platform =
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
https://github.com/meshtastic/platform-native/archive/6cb7a455b440dd0738e8ed74a18136ed5cf7ea63.zip
https://github.com/meshtastic/platform-native/archive/681ee029207e9fd040afa223df6e54074cbbe084.zip
framework = arduino
build_src_filter =
@@ -30,8 +30,6 @@ lib_deps =
lovyan03/LovyanGFX@^1.2.0
# renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main
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
adafruit/Adafruit seesaw Library@1.7.9
build_flags =
${arduino_base.build_flags}
@@ -50,7 +48,4 @@ build_flags =
-std=gnu17
-std=c++17
lib_ignore =
Adafruit NeoPixel
Adafruit ST7735 and ST7789 Library
SD
lib_ignore = Adafruit NeoPixel

View File

@@ -47,4 +47,4 @@ lib_deps =
https://github.com/caveman99/Crypto/archive/eae9c768054118a9399690f8af202853d1ae8516.zip
lib_ignore =
OneButton
mathertel/OneButton@2.6.1

View File

@@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg install -e $1
platformio pkg update -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.*

View File

@@ -25,7 +25,7 @@ mkdir -p $OUTDIR/
rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
pio pkg install --environment "$PIO_ENV" || platformioFailed
pio pkg update --environment "$PIO_ENV" || platformioFailed
pio run --environment "$PIO_ENV" || platformioFailed
cp ".pio/build/$PIO_ENV/program" "$OUTDIR/meshtasticd_linux_$(uname -m)"
cp bin/native-install.* $OUTDIR

View File

@@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg install -e $1
platformio pkg update -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.*

View File

@@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg install -e $1
platformio pkg update -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.*

View File

@@ -11,7 +11,7 @@ rm -f $OUTDIR/firmware*
rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg install -e $1
platformio pkg update -e $1
echo "Building for $1 with $PLATFORMIO_BUILD_FLAGS"
rm -f .pio/build/$1/firmware.*

View File

@@ -199,10 +199,6 @@ HostMetrics:
# UserStringCommand: cat /sys/firmware/devicetree/base/serial-number # Command to execute, to send the results as the userString
Config:
# DisplayMode: TWOCOLOR # uncomment to force BaseUI
# DisplayMode: COLOR # uncomment to force MUI
General:
MaxNodes: 200
MaxMessageQueue: 100

View File

@@ -1,52 +0,0 @@
# https://www.waveshare.com/pico-lora-sx1262-868m.htm
# http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/details/Orange-Pi-Zero-3.html
#
# See Orange Pi Zero3 manual, chapter 3.16, page 124 for 26-pin header pinout
#
# Pin Connection
# Waveshare Orange Pi Zero3
# 36 3.3V 17
# 15 MOSI 19
# 16 MISO 21
# 14 CLK 23
# 38 GND 25
# 4 BUSY 18
# 20 RESET 22
# 5 CS 24
# 26 DIO1/IRQ 26
Lora:
Module: sx1262 # Waveshare Raspberry Pico Lora module
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
# Specify either the spidev1_1 or the CS below, not both!
# On DietPi Linux, when using the user overlay dietpi-spi1_1.dtbo, CS will be configured with spidev1.1
spidev: spidev1.1 # See Orange Pi Zero3 manual, chapter 3.18.3, page 130
# CS: # CS PIN_24 -> chip 1, line 233
# pin: 24
# gpiochip: 1
# line: 233
SCK: # SCK PIN_23 -> chip 1, line 230
pin: 23
gpiochip: 1
line: 230
Busy: # BUSY PIN_18 -> chip 1, line 78
pin: 18
gpiochip: 1
line: 78
MOSI: # MOSI PIN_19 -> chip 1, line 231
pin: 19
gpiochip: 1
line: 231
MISO: # MISO PIN_21 -> chip 1, line 232
pin: 21
gpiochip: 1
line: 232
Reset: # NRST PIN_22 -> chip 1, line 71
pin: 22
gpiochip: 1
line: 71
IRQ: # DIO1 PIN_26 -> chip 1, line 74
pin: 26
gpiochip: 1
line: 74

View File

@@ -7,7 +7,12 @@ MCU=""
# Variant groups
BIGDB_8MB=(
"picomputer-s3"
# Check if FILENAME contains "-tft-" and set target partitionScheme accordingly.
if [[ $FILENAME == *"-tft-"* ]]; then
TFT_BUILD=true
fi
# Extract BASENAME from %FILENAME% for later use.r-s3"
"unphone"
"seeed-sensecap-indicator"
"crowpanel-esp32s3"

View File

@@ -31,16 +31,17 @@ EOF
}
# Check for --change-mode and remove it from arguments
NEW_ARGS=()
NEW_ARGS=""
for arg in "$@"; do
if [ "$arg" = "--change-mode" ]; then
CHANGE_MODE=true
else
NEW_ARGS+=("$arg")
NEW_ARGS="$NEW_ARGS \"\$arg\""
fi
done
set -- "${NEW_ARGS[@]}"
# Reset positional parameters to filtered list
eval set -- $NEW_ARGS
while getopts ":hp:P:f:" opt; do
case "${opt}" in

View File

@@ -87,9 +87,6 @@
</screenshots>
<releases>
<release version="2.7.3" date="2025-07-10">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.3</url>
</release>
<release version="2.7.2" date="2025-07-04">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.2</url>
</release>

View File

@@ -10,8 +10,7 @@
"hwids": [
["0x239A", "0x4405"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x2886", "0x1667"]
["0x239A", "0x002A"]
],
"usb_product": "HT-n5262",
"mcu": "nrf52840",

7
debian/changelog vendored
View File

@@ -1,4 +1,4 @@
meshtasticd (2.7.3.0) UNRELEASED; urgency=medium
meshtasticd (2.7.2.0) UNRELEASED; urgency=medium
[ Austin Lane ]
* Initial packaging
@@ -28,7 +28,4 @@ meshtasticd (2.7.3.0) UNRELEASED; urgency=medium
[ ]
* GitHub Actions Automatic version bump
[ Ubuntu ]
* GitHub Actions Automatic version bump
-- Ubuntu <github-actions[bot]@users.noreply.github.com> Thu, 10 Jul 2025 16:29:27 +0000
-- <github-actions[bot]@users.noreply.github.com> Fri, 04 Jul 2025 11:58:01 +0000

View File

@@ -104,18 +104,18 @@ lib_deps =
[radiolib_base]
lib_deps =
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
jgromes/RadioLib@7.2.1
jgromes/RadioLib@7.2.0
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/86a09a7360f92d10053fbbf8d74f67f85b0ceb09.zip
https://github.com/meshtastic/device-ui/archive/8c7092c73425adfda1aac8c6960df06cd85f6d92.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]
lib_deps =
# renovate: datasource=custom.pio depName=Adafruit BusIO packageName=adafruit/library/Adafruit BusIO
adafruit/Adafruit BusIO@1.17.2
adafruit/Adafruit BusIO@1.17.1
# renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor
adafruit/Adafruit Unified Sensor@1.1.15
# renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library
@@ -129,7 +129,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=Adafruit MCP9808 packageName=adafruit/library/Adafruit MCP9808 Library
adafruit/Adafruit MCP9808 Library@2.0.2
# renovate: datasource=custom.pio depName=Adafruit INA260 packageName=adafruit/library/Adafruit INA260 Library
adafruit/Adafruit INA260 Library@1.5.3
adafruit/Adafruit INA260 Library@1.5.2
# renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219
adafruit/Adafruit INA219@1.2.3
# renovate: datasource=custom.pio depName=Adafruit PM25 AQI Sensor packageName=adafruit/library/Adafruit PM25 AQI Sensor

View File

@@ -40,7 +40,9 @@ template <typename T, std::size_t N> std::size_t array_count(const T (&)[N])
}
#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
#if defined(RAK2560)
#if defined(SENSECAP_INDICATOR)
FakeUART *GPS::_serial_gps = FakeSerial;
#elif defined(RAK2560)
HardwareSerial *GPS::_serial_gps = &Serial2;
#else
HardwareSerial *GPS::_serial_gps = &Serial1;
@@ -1076,6 +1078,7 @@ void GPS::publishUpdate()
int32_t GPS::runOnce()
{
#if !defined(SENSECAP_INDICATOR)
if (!GPSInitFinished) {
if (!_serial_gps || config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
LOG_INFO("GPS set to not-present. Skip probe");
@@ -1091,6 +1094,7 @@ int32_t GPS::runOnce()
GPSInitFinished = true;
publishUpdate();
}
#endif
// Repeaters have no need for GPS
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
@@ -1409,8 +1413,10 @@ GPS *GPS::createGps()
if (!settingsMap[has_gps])
return nullptr;
#endif
#if !defined(SENSECAP_INDICATOR)
if (!_rx_gpio || !_serial_gps) // Configured to have no GPS at all
return nullptr;
#endif
GPS *new_gps = new GPS;
new_gps->rx_gpio = _rx_gpio;
@@ -1536,10 +1542,7 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
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,
t.tm_sec, ti.age());
if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultInvalidTime) {
// Clear the GPS buffer if we got an invalid time
clearBuffer();
}
perhapsSetRTC(RTCQualityGPS, t);
return true;
} else
return false;

View File

@@ -11,6 +11,10 @@
#include "input/UpDownInterruptImpl1.h"
#include "modules/PositionModule.h"
#ifdef SENSECAP_INDICATOR
#include "mesh/comms/FakeUART.h"
#endif
// Allow defining the polarity of the ENABLE output. default is active high
#ifndef GPS_EN_ACTIVE
#define GPS_EN_ACTIVE 1
@@ -188,7 +192,9 @@ class GPS : private concurrency::OSThread
CallbackObserver<GPS, void *> notifyDeepSleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareDeepSleep);
/** If !NULL we will use this serial port to construct our GPS */
#if defined(ARCH_RP2040)
#if defined(SENSECAP_INDICATOR)
static FakeUART *_serial_gps;
#elif defined(ARCH_RP2040)
static SerialUART *_serial_gps;
#else
static HardwareSerial *_serial_gps;

View File

@@ -105,7 +105,7 @@ void readFromRTC()
*
* If we haven't yet set our RTC this boot, set it from a GPS derived time
*/
RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
{
static uint32_t lastSetMsec = 0;
uint32_t now = millis();
@@ -113,7 +113,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
#ifdef BUILD_EPOCH
if (tv->tv_sec < BUILD_EPOCH) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
return RTCSetResultInvalidTime;
return false;
}
#endif
@@ -184,9 +184,9 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
readFromRTC();
#endif
return RTCSetResultSuccess;
return true;
} else {
return RTCSetResultNotSet; // RTC was already set with a higher quality time
return false;
}
}
@@ -215,7 +215,7 @@ const char *RtcName(RTCQuality quality)
* @param t The time to potentially set the RTC to.
* @return True if the RTC was set to the provided time, false otherwise.
*/
RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
bool perhapsSetRTC(RTCQuality q, struct tm &t)
{
/* Convert to unix time
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
@@ -231,7 +231,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
// LOG_DEBUG("Got time from GPS month=%d, year=%d, unixtime=%ld", t.tm_mon, t.tm_year, tv.tv_sec);
if (t.tm_year < 0 || t.tm_year >= 300) {
// LOG_DEBUG("Ignore invalid GPS month=%d, year=%d, unixtime=%ld", t.tm_mon, t.tm_year, tv.tv_sec);
return RTCSetResultInvalidTime;
return false;
} else {
return perhapsSetRTC(q, &tv);
}

View File

@@ -22,22 +22,13 @@ enum RTCQuality {
RTCQualityGPS = 4
};
/// The RTC set result codes
/// Used to indicate the result of an attempt to set the RTC.
enum RTCSetResult {
RTCSetResultNotSet = 0, ///< RTC was set successfully
RTCSetResultSuccess = 1, ///< RTC was set successfully
RTCSetResultInvalidTime = 3, ///< The provided time was invalid (e.g., before the build epoch)
RTCSetResultError = 4 ///< An error occurred while setting the RTC
};
RTCQuality getRTCQuality();
extern uint32_t lastSetFromPhoneNtpOrGps;
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false);
RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t);
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false);
bool perhapsSetRTC(RTCQuality q, struct tm &t);
/// Return a string name for the quality
const char *RtcName(RTCQuality quality);

View File

@@ -6,10 +6,6 @@
#include "main.h"
#include <SPI.h>
#ifdef GXEPD2_DRIVER_0
#include "einkDetect.h"
#endif
/*
The macros EINK_DISPLAY_MODEL, EINK_WIDTH, and EINK_HEIGHT are defined as build_flags in a variant's platformio.ini
Previously, these macros were defined at the top of this file.
@@ -178,8 +174,9 @@ bool EInkDisplay::connect()
}
}
#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || \
defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER)
#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) || \
defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) || \
defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER)
{
// Start HSPI
hspi = new SPIClass(HSPI);
@@ -235,23 +232,6 @@ bool EInkDisplay::connect()
adafruitDisplay->init();
adafruitDisplay->setRotation(3);
}
#elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213)
// Detect display model, before starting SPI
EInkDetectionResult displayModel = detectEInk();
// Start HSPI
hspi = new SPIClass(HSPI);
hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
// Create GxEPD2 object
adafruitDisplay = new GxEPD2_Multi<GXEPD2_DRIVER_0, GXEPD2_DRIVER_1>((uint8_t)displayModel, PIN_EINK_CS, PIN_EINK_DC,
PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
// Init GxEPD2
adafruitDisplay->init();
adafruitDisplay->setRotation(3);
#endif
return true;

View File

@@ -5,10 +5,6 @@
#include "GxEPD2_BW.h"
#include <OLEDDisplay.h>
#ifdef GXEPD2_DRIVER_0 // If variant has multiple possible display models
#include "GxEPD2Multi.h"
#endif
/**
* An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation.
*
@@ -67,15 +63,8 @@ class EInkDisplay : public OLEDDisplay
// Connect to the display
virtual bool connect() override;
#ifdef GXEPD2_DRIVER_0
// AdafruitGFX display object - wrapper for multiple drivers
// Allows runtime detection of multiple displays
// Avoid this situation if possible!
GxEPD2_Multi<GXEPD2_DRIVER_0, GXEPD2_DRIVER_1> *adafruitDisplay = NULL;
#else
// AdafruitGFX display object (for single display model) - instantiated in connect(), variant specific
// AdafruitGFX display object - instantiated in connect(), variant specific
GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT> *adafruitDisplay = NULL;
#endif
// If display uses HSPI
#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \

View File

@@ -1,135 +0,0 @@
// Wrapper class for GxEPD2_BW
// Generic signature at build-time, so that we can detect display model at run-time
// Workaround for issue of GxEPD2_BW objects not having a shared base class
// Only exposes methods which we are actually using
template <typename Driver0, typename Driver1> class GxEPD2_Multi
{
public:
void drawPixel(int16_t x, int16_t y, uint16_t color)
{
if (which == 0)
driver0->drawPixel(x, y, color);
else
driver1->drawPixel(x, y, color);
}
bool nextPage()
{
if (which == 0)
return driver0->nextPage();
else
return driver1->nextPage();
}
void hibernate()
{
if (which == 0)
driver0->hibernate();
else
driver1->hibernate();
}
void init(uint32_t serial_diag_bitrate = 0)
{
if (which == 0)
driver0->init(serial_diag_bitrate);
else
driver1->init(serial_diag_bitrate);
}
void init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration = 20, bool pulldown_rst_mode = false)
{
if (which == 0)
driver0->init(serial_diag_bitrate, initial, reset_duration, pulldown_rst_mode);
else
driver1->init(serial_diag_bitrate, initial, reset_duration, pulldown_rst_mode);
}
void setRotation(uint8_t x)
{
if (which == 0)
driver0->setRotation(x);
else
driver1->setRotation(x);
}
void setPartialWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
if (which == 0)
driver0->setPartialWindow(x, y, w, h);
else
driver1->setPartialWindow(x, y, w, h);
}
void setFullWindow()
{
if (which == 0)
driver0->setFullWindow();
else
driver1->setFullWindow();
}
int16_t width()
{
if (which == 0)
return driver0->width();
else
return driver1->width();
}
int16_t height()
{
if (which == 0)
return driver0->height();
else
return driver1->height();
}
void clearScreen(uint8_t value = 0xFF)
{
if (which == 0)
driver0->clearScreen();
else
driver1->clearScreen();
}
void endAsyncFull()
{
if (which == 0)
driver0->endAsyncFull();
else
driver1->endAsyncFull();
}
// Exposes methods of the GxEPD2_EPD object which is usually available as GxEPD2_BW::epd
class Epd2Wrapper
{
public:
bool isBusy() { return m_epd2->isBusy(); }
GxEPD2_EPD *m_epd2;
} epd2;
// Constructor
// Select driver by passing whichDriver as 0 or 1
GxEPD2_Multi(uint8_t whichDriver, int16_t cs, int16_t dc, int16_t rst, int16_t busy, SPIClass &spi)
{
assert(whichDriver == 0 || whichDriver == 1);
which = whichDriver;
LOG_DEBUG("GxEPD2_Multi driver: %d", which);
if (which == 0) {
driver0 = new GxEPD2_BW<Driver0, Driver0::HEIGHT>(Driver0(cs, dc, rst, busy, spi));
epd2.m_epd2 = &(driver0->epd2);
} else if (which == 1) {
driver1 = new GxEPD2_BW<Driver1, Driver1::HEIGHT>(Driver1(cs, dc, rst, busy, spi));
epd2.m_epd2 = &(driver1->epd2);
}
}
private:
uint8_t which;
GxEPD2_BW<Driver0, Driver0::HEIGHT> *driver0;
GxEPD2_BW<Driver1, Driver1::HEIGHT> *driver1;
};

View File

@@ -21,7 +21,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Screen.h"
#include "NodeDB.h"
#include "PowerMon.h"
#include "Throttle.h"
#include "configuration.h"
@@ -45,6 +44,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#endif
#include "FSCommon.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "RadioLibInterface.h"
#include "error.h"
#include "gps/GeoCoord.h"
@@ -171,7 +171,7 @@ void Screen::showOverlayBanner(BannerOverlayOptions banner_overlay_options)
}
// 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(int)> bannerCallback)
{
#ifdef USE_EINK
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
@@ -196,6 +196,7 @@ void Screen::showNodePicker(const char *message, uint32_t durationMs, std::funct
void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits,
std::function<void(uint32_t)> bannerCallback)
{
LOG_WARN("Show Number Picker");
#ifdef USE_EINK
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
#endif
@@ -293,13 +294,13 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
LOG_INFO("Protobuf Value uiconfig.screen_rgb_color: %d", uiconfig.screen_rgb_color);
int32_t rawRGB = uiconfig.screen_rgb_color;
if (rawRGB > 0 && rawRGB <= 255255255) {
uint8_t TFT_MESH_r = (rawRGB >> 16) & 0xFF;
uint8_t TFT_MESH_g = (rawRGB >> 8) & 0xFF;
uint8_t TFT_MESH_b = rawRGB & 0xFF;
LOG_INFO("Values of r,g,b: %d, %d, %d", TFT_MESH_r, TFT_MESH_g, TFT_MESH_b);
uint8_t r = (rawRGB >> 16) & 0xFF;
uint8_t g = (rawRGB >> 8) & 0xFF;
uint8_t b = rawRGB & 0xFF;
LOG_INFO("Values of r,g,b: %d, %d, %d", r, g, b);
if (TFT_MESH_r <= 255 && TFT_MESH_g <= 255 && TFT_MESH_b <= 255) {
TFT_MESH = COLOR565(TFT_MESH_r, TFT_MESH_g, TFT_MESH_b);
if (r <= 255 && g <= 255 && b <= 255) {
TFT_MESH = COLOR565(r, g, b);
}
}
@@ -312,8 +313,8 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
ST7789_MISO, ST7789_SCK);
#else
dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT);
#endif
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
#endif
#elif defined(USE_SSD1306)
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
@@ -640,11 +641,6 @@ void Screen::forceDisplay(bool forceUiUpdate)
// Tell EInk class to update the display
static_cast<EInkDisplay *>(dispdev)->forceDisplay();
#else
// No delay between UI frame rendering
if (forceUiUpdate) {
setFastFramerate();
}
#endif
}
@@ -948,6 +944,22 @@ void Screen::setFrames(FrameFocus focus)
indicatorIcons.push_back(digital_icon_clock);
#endif
// We don't show the node info of our node (if we have it yet - we should)
size_t numMeshNodes = nodeDB->getNumMeshNodes();
if (numMeshNodes > 0)
numMeshNodes--;
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
if (fsi.positions.firstFavorite == 255)
fsi.positions.firstFavorite = numframes;
fsi.positions.lastFavorite = numframes;
normalFrames[numframes++] = graphics::UIRenderer::drawNodeInfo;
indicatorIcons.push_back(icon_node);
}
}
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
if (!dismissedFrames.wifi && isWifiAvailable()) {
fsi.positions.wifi = numframes;
@@ -957,7 +969,7 @@ void Screen::setFrames(FrameFocus focus)
#endif
// Beware of what changes you make in this code!
// We pass numframes into GetMeshModulesWithUIFrames() which is highly important!
// We pass numfames into GetMeshModulesWithUIFrames() which is highly important!
// Inside of that callback, goes over to MeshModule.cpp and we run
// modulesWithUIFrames.resize(startIndex, nullptr), to insert nullptr
// entries until we're ready to start building the matching entries.
@@ -986,34 +998,6 @@ void Screen::setFrames(FrameFocus focus)
LOG_DEBUG("Added modules. numframes: %d", numframes);
// We don't show the node info of our node (if we have it yet - we should)
size_t numMeshNodes = nodeDB->getNumMeshNodes();
if (numMeshNodes > 0)
numMeshNodes--;
// Temporary array to hold favorite node frames
std::vector<FrameCallback> favoriteFrames;
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
}
}
// Insert favorite frames *after* collecting them all
if (!favoriteFrames.empty()) {
fsi.positions.firstFavorite = numframes;
for (auto &f : favoriteFrames) {
normalFrames[numframes++] = f;
indicatorIcons.push_back(icon_node);
}
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
this->frameCount = numframes; // ✅ Save frame count for use in custom overlay
LOG_DEBUG("Finished build frames. numframes: %d", numframes);
@@ -1025,7 +1009,8 @@ void Screen::setFrames(FrameFocus focus)
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list just changed)
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list
// just changed)
// Focus on a specific frame, in the frame set we just created
switch (focus) {
@@ -1262,45 +1247,40 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
devicestate.has_rx_text_message = true; // Needed to include the message frame
hasUnreadMessage = true; // Enables mail icon in the header
setFrames(FOCUS_PRESERVE); // Refresh frame list without switching view
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
// === Prepare banner content ===
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
// === Prepare banner content ===
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
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];
char banner[256];
// Check for bell character in message to determine alert type
bool isAlert = false;
for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
if (msgRaw[i] == '\x07') {
isAlert = true;
break;
}
// Check for bell character in message to determine alert type
bool isAlert = false;
for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
if (msgRaw[i] == '\x07') {
isAlert = true;
break;
}
if (isAlert) {
if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
} else {
strcpy(banner, "Alert Received");
}
} else {
if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
} else {
strcpy(banner, "New Message");
}
}
screen->showSimpleBanner(banner, 3000);
}
if (isAlert) {
if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
} else {
strcpy(banner, "Alert Received");
}
} else {
if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
} else {
strcpy(banner, "New Message");
}
}
screen->showSimpleBanner(banner, 3000);
}
}
@@ -1339,7 +1319,7 @@ int Screen::handleInputEvent(const InputEvent *event)
setFastFramerate(); // Draw ASAP
#endif
if (NotificationRenderer::isOverlayBannerShowing()) {
NotificationRenderer::inEvent = *event;
NotificationRenderer::inEvent = event->inputEvent;
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
setFastFramerate(); // Draw ASAP
@@ -1433,23 +1413,3 @@ bool Screen::isOverlayBannerShowing()
#else
graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {}
#endif // HAS_SCREEN
bool shouldWakeOnReceivedMessage()
{
/*
The goal here is to determine when we do NOT wake up the screen on message received:
- Any ext. notifications are turned on
- If role is not client / client_mute
- If the battery level is very low
*/
if (moduleConfig.external_notification.enabled) {
return false;
}
if (!meshtastic_Config_DeviceConfig_Role_CLIENT && !meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE) {
return false;
}
if (powerStatus && powerStatus->getBatteryChargePercent() < 10) {
return false;
}
return true;
}

View File

@@ -26,8 +26,6 @@ struct BannerOverlayOptions {
};
} // namespace graphics
bool shouldWakeOnReceivedMessage();
#if !HAS_SCREEN
#include "power.h"
namespace graphics
@@ -94,7 +92,6 @@ class Screen
#include "commands.h"
#include "concurrency/LockGuard.h"
#include "concurrency/OSThread.h"
#include "graphics/draw/MenuHandler.h"
#include "input/InputBroker.h"
#include "mesh/MeshModule.h"
#include "modules/AdminModule.h"
@@ -311,15 +308,9 @@ class Screen : public concurrency::OSThread
void showSimpleBanner(const char *message, uint32_t durationMs = 0);
void showOverlayBanner(BannerOverlayOptions);
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(int)> bannerCallback);
void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function<void(uint32_t)> bannerCallback);
void requestMenu(graphics::menuHandler::screenMenus menuToShow)
{
graphics::menuHandler::menuQueue = menuToShow;
runNow();
}
void startFirmwareUpdateScreen()
{
ScreenCmd cmd;

View File

@@ -13,7 +13,6 @@
#include "main.h"
#include "modules/AdminModule.h"
#include "modules/CannedMessageModule.h"
#include "modules/KeyVerificationModule.h"
extern uint16_t TFT_MESH;
@@ -129,15 +128,14 @@ void menuHandler::ClockFacePicker()
screen->runNow();
} else if (selected == Digital) {
uiconfig.is_clockface_analog = false;
saveUIConfig();
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
screen->setFrames(Screen::FOCUS_CLOCK);
} else {
uiconfig.is_clockface_analog = true;
saveUIConfig();
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
screen->setFrames(Screen::FOCUS_CLOCK);
}
};
bannerOptions.InitialSelected = uiconfig.is_clockface_analog ? 2 : 1;
screen->showOverlayBanner(bannerOptions);
}
@@ -238,25 +236,27 @@ void menuHandler::clockMenu()
void menuHandler::messageResponseMenu()
{
enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3, Aloud = 4, enumEnd = 5 };
static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply via Preset"};
static int optionsEnumArray[enumEnd] = {Back, Dismiss, Preset};
int options = 3;
static const char **optionsArrayPtr;
int options;
enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3 };
if (kb_found) {
optionsArray[options] = "Reply via Freetext";
optionsEnumArray[options++] = Freetext;
static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset", "Reply via Freetext"};
optionsArrayPtr = optionsArray;
options = 4;
} else {
static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset"};
optionsArrayPtr = optionsArray;
options = 3;
}
#ifdef HAS_I2S
optionsArray[options] = "Read Aloud";
optionsEnumArray[options++] = Aloud;
static const char *optionsArray[] = {"Back", "Dismiss", "Reply via Preset", "Reply via Freetext", "Read Aloud"};
optionsArrayPtr = optionsArray;
options = 5;
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Message Action";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsArrayPtr = optionsArrayPtr;
bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Dismiss) {
@@ -275,7 +275,7 @@ void menuHandler::messageResponseMenu()
}
}
#ifdef HAS_I2S
else if (selected == Aloud) {
else if (selected == 4) {
const meshtastic_MeshPacket &mp = devicestate.rx_text_message;
const char *msg = reinterpret_cast<const char *>(mp.decoded.payload.bytes);
@@ -288,10 +288,10 @@ void menuHandler::messageResponseMenu()
void menuHandler::homeBaseMenu()
{
enum optionsNumbers { Back, Backlight, Position, Preset, Freetext, Bluetooth, Sleep, enumEnd };
enum optionsNumbers { Back, Backlight, Position, Preset, Freetext, Bluetooth, Sleep };
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
static const char *optionsArray[6] = {"Back"};
static int optionsEnumArray[6] = {Back};
int options = 1;
#ifdef PIN_EINK_EN
@@ -337,8 +337,8 @@ void menuHandler::homeBaseMenu()
} else if (selected == Freetext) {
cannedMessageModule->LaunchFreetextWithDestination(NODENUM_BROADCAST);
} else if (selected == Bluetooth) {
menuQueue = bluetooth_toggle_menu;
screen->runNow();
InputEvent event = {.inputEvent = (input_broker_event)170, .kbchar = 170, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
}
};
screen->showOverlayBanner(bannerOptions);
@@ -346,28 +346,37 @@ void menuHandler::homeBaseMenu()
void menuHandler::systemBaseMenu()
{
// Check if brightness is supported
bool hasSupportBrightness = false;
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || HAS_TFT
hasSupportBrightness = true;
#endif
enum optionsNumbers { Back, Notifications, ScreenOptions, PowerMenu, Test, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
enum optionsNumbers { Back, Beeps, Brightness, Reboot, Color, MUI, Test };
static const char *optionsArray[7] = {"Back"};
static int optionsEnumArray[7] = {Back};
int options = 1;
optionsArray[options] = "Notifications";
optionsEnumArray[options++] = Notifications;
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || \
defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
optionsArray[options] = "Screen Options";
optionsEnumArray[options++] = ScreenOptions;
optionsArray[options] = "Beeps Action";
optionsEnumArray[options++] = Beeps;
if (hasSupportBrightness) {
optionsArray[options] = "Brightness";
optionsEnumArray[options++] = Brightness;
}
optionsArray[options] = "Reboot";
optionsEnumArray[options++] = Reboot;
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
optionsArray[options] = "Screen Color";
optionsEnumArray[options++] = Color;
#endif
#if HAS_TFT
optionsArray[options] = "Switch to MUI";
optionsEnumArray[options++] = MUI;
#endif
optionsArray[options] = "Reboot/Shutdown";
optionsEnumArray[options++] = PowerMenu;
if (test_enabled) {
optionsArray[options] = "Test Menu";
optionsEnumArray[options++] = Test;
@@ -379,14 +388,20 @@ void menuHandler::systemBaseMenu()
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Notifications) {
menuHandler::menuQueue = menuHandler::notifications_menu;
if (selected == Beeps) {
menuHandler::menuQueue = menuHandler::buzzermodemenupicker;
screen->runNow();
} else if (selected == ScreenOptions) {
menuHandler::menuQueue = menuHandler::screen_options_menu;
} else if (selected == Brightness) {
menuHandler::menuQueue = menuHandler::brightness_picker;
screen->runNow();
} else if (selected == PowerMenu) {
menuHandler::menuQueue = menuHandler::power_menu;
} else if (selected == Reboot) {
menuHandler::menuQueue = menuHandler::reboot_menu;
screen->runNow();
} else if (selected == MUI) {
menuHandler::menuQueue = menuHandler::mui_picker;
screen->runNow();
} else if (selected == Color) {
menuHandler::menuQueue = menuHandler::tftcolormenupicker;
screen->runNow();
} else if (selected == Test) {
menuHandler::menuQueue = menuHandler::test_menu;
@@ -403,22 +418,21 @@ void menuHandler::systemBaseMenu()
void menuHandler::favoriteBaseMenu()
{
enum optionsNumbers { Back, Preset, Freetext, Remove, enumEnd };
static const char *optionsArray[enumEnd] = {"Back", "New Preset Msg"};
static int optionsEnumArray[enumEnd] = {Back, Preset};
int options = 2;
int options;
static const char **optionsArrayPtr;
if (kb_found) {
optionsArray[options] = "New Freetext Msg";
optionsEnumArray[options++] = Freetext;
static const char *optionsArray[] = {"Back", "New Preset Msg", "New Freetext Msg", "Remove Favorite"};
optionsArrayPtr = optionsArray;
options = 4;
} else {
static const char *optionsArray[] = {"Back", "New Preset Msg", "Remove Favorite"};
optionsArrayPtr = optionsArray;
options = 3;
}
optionsArray[options] = "Remove Favorite";
optionsEnumArray[options++] = Remove;
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Favorites Action";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsArrayPtr = optionsArrayPtr;
bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
@@ -435,29 +449,34 @@ void menuHandler::favoriteBaseMenu()
void menuHandler::positionBaseMenu()
{
enum optionsNumbers { Back, GPSToggle, CompassMenu, CompassCalibrate, enumEnd };
static const char *optionsArray[enumEnd] = {"Back", "GPS Toggle", "Compass"};
static int optionsEnumArray[enumEnd] = {Back, GPSToggle, CompassMenu};
int options = 3;
int options;
static const char **optionsArrayPtr;
static const char *optionsArray[] = {"Back", "GPS Toggle", "Compass"};
static const char *optionsArrayCalibrate[] = {"Back", "GPS Toggle", "Compass", "Compass Calibrate"};
if (accelerometerThread) {
optionsArray[options] = "Compass Calibrate";
optionsEnumArray[options++] = CompassCalibrate;
optionsArrayPtr = optionsArrayCalibrate;
options = 4;
} else {
optionsArrayPtr = optionsArray;
options = 3;
}
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Position Action";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsArrayPtr = optionsArrayPtr;
bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == GPSToggle) {
if (selected == 1) {
#if MESHTASTIC_EXCLUDE_GPS
menuQueue = menu_none;
#else
menuQueue = gps_toggle_menu;
screen->runNow();
} else if (selected == CompassMenu) {
#endif
} else if (selected == 2) {
menuQueue = compass_point_north_menu;
screen->runNow();
} else if (selected == CompassCalibrate) {
} else if (selected == 3) {
accelerometerThread->calibrate(30);
}
};
@@ -466,20 +485,16 @@ void menuHandler::positionBaseMenu()
void menuHandler::nodeListMenu()
{
enum optionsNumbers { Back, Favorite, Verify, Reset };
static const char *optionsArray[] = {"Back", "Add Favorite", "Key Verification", "Reset NodeDB"};
static const char *optionsArray[] = {"Back", "Add Favorite", "Reset NodeDB"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Node Action";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 4;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Favorite) {
if (selected == 1) {
menuQueue = add_favorite;
screen->runNow();
} else if (selected == Verify) {
menuQueue = key_verification_init;
screen->runNow();
} else if (selected == Reset) {
} else if (selected == 2) {
menuQueue = reset_node_db_menu;
screen->runNow();
}
@@ -507,7 +522,6 @@ void menuHandler::resetNodeDBMenu()
void menuHandler::compassNorthMenu()
{
enum optionsNumbers { Back, Dynamic, Fixed, Freeze };
static const char *optionsArray[] = {"Back", "Dynamic", "Fixed Ring", "Freeze Heading"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "North Directions?";
@@ -515,25 +529,28 @@ void menuHandler::compassNorthMenu()
bannerOptions.optionsCount = 4;
bannerOptions.InitialSelected = uiconfig.compass_mode + 1;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Dynamic) {
if (selected == 1) {
if (uiconfig.compass_mode != meshtastic_CompassMode_DYNAMIC) {
uiconfig.compass_mode = meshtastic_CompassMode_DYNAMIC;
saveUIConfig();
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg,
&uiconfig);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
} else if (selected == Fixed) {
} else if (selected == 2) {
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) {
uiconfig.compass_mode = meshtastic_CompassMode_FIXED_RING;
saveUIConfig();
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg,
&uiconfig);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
} else if (selected == Freeze) {
} else if (selected == 3) {
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) {
uiconfig.compass_mode = meshtastic_CompassMode_FREEZE_HEADING;
saveUIConfig();
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg,
&uiconfig);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
} else if (selected == Back) {
} else if (selected == 0) {
menuQueue = position_base_menu;
screen->runNow();
}
@@ -544,7 +561,6 @@ void menuHandler::compassNorthMenu()
#if !MESHTASTIC_EXCLUDE_GPS
void menuHandler::GPSToggleMenu()
{
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Toggle GPS";
@@ -571,28 +587,11 @@ void menuHandler::GPSToggleMenu()
}
#endif
void menuHandler::BluetoothToggleMenu()
{
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Toggle Bluetooth";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1 || selected == 2) {
InputEvent event = {.inputEvent = (input_broker_event)170, .kbchar = 170, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
}
};
bannerOptions.InitialSelected = config.bluetooth.enabled ? 1 : 2;
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::BuzzerModeMenu()
{
static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Buzzer Mode";
bannerOptions.message = "Beep Action";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 4;
bannerOptions.bannerCallback = [](int selected) -> void {
@@ -642,7 +641,7 @@ void menuHandler::BrightnessPickerMenu()
#endif
// Save to device
saveUIConfig();
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
LOG_INFO("Screen brightness set to %d", uiconfig.screen_brightness);
}
@@ -653,13 +652,13 @@ void menuHandler::BrightnessPickerMenu()
void menuHandler::switchToMUIMenu()
{
static const char *optionsArray[] = {"No", "Yes"};
static const char *optionsArray[] = {"Yes", "No"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Switch to MUI?";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
if (selected == 0) {
config.display.displaymode = meshtastic_Config_DisplayConfig_DisplayMode_COLOR;
config.bluetooth.enabled = false;
service->reloadConfig(SEGMENT_CONFIG);
@@ -678,55 +677,52 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 10;
bannerOptions.bannerCallback = [display](int selected) -> void {
uint8_t TFT_MESH_r = 0;
uint8_t TFT_MESH_g = 0;
uint8_t TFT_MESH_b = 0;
uint8_t r = 0;
uint8_t g = 0;
uint8_t b = 0;
if (selected == 1) {
LOG_INFO("Setting color to system default or defined variant");
// Given just before we set all these to zero, we will allow this to go through
} else if (selected == 2) {
LOG_INFO("Setting color to Meshtastic Green");
TFT_MESH_r = 103;
TFT_MESH_g = 234;
TFT_MESH_b = 148;
r = 103;
g = 234;
b = 148;
} else if (selected == 3) {
LOG_INFO("Setting color to Yellow");
TFT_MESH_r = 255;
TFT_MESH_g = 255;
TFT_MESH_b = 128;
r = 255;
g = 255;
b = 128;
} else if (selected == 4) {
LOG_INFO("Setting color to Red");
TFT_MESH_r = 255;
TFT_MESH_g = 64;
TFT_MESH_b = 64;
r = 255;
g = 64;
b = 64;
} else if (selected == 5) {
LOG_INFO("Setting color to Orange");
TFT_MESH_r = 255;
TFT_MESH_g = 160;
TFT_MESH_b = 20;
r = 255;
g = 160;
b = 20;
} else if (selected == 6) {
LOG_INFO("Setting color to Purple");
TFT_MESH_r = 204;
TFT_MESH_g = 153;
TFT_MESH_b = 255;
r = 204;
g = 153;
b = 255;
} else if (selected == 7) {
LOG_INFO("Setting color to Teal");
TFT_MESH_r = 64;
TFT_MESH_g = 224;
TFT_MESH_b = 208;
r = 64;
g = 224;
b = 208;
} else if (selected == 8) {
LOG_INFO("Setting color to Pink");
TFT_MESH_r = 255;
TFT_MESH_g = 105;
TFT_MESH_b = 180;
r = 255;
g = 105;
b = 180;
} else if (selected == 9) {
LOG_INFO("Setting color to White");
TFT_MESH_r = 255;
TFT_MESH_g = 255;
TFT_MESH_b = 255;
} else {
menuQueue = system_base_menu;
screen->runNow();
r = 255;
g = 255;
b = 255;
}
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
@@ -735,14 +731,14 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
display->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
display->setColor(WHITE);
if (TFT_MESH_r == 0 && TFT_MESH_g == 0 && TFT_MESH_b == 0) {
if (r == 0 && g == 0 && b == 0) {
#ifdef TFT_MESH_OVERRIDE
TFT_MESH = TFT_MESH_OVERRIDE;
#else
TFT_MESH = COLOR565(0x67, 0xEA, 0x94);
#endif
} else {
TFT_MESH = COLOR565(TFT_MESH_r, TFT_MESH_g, TFT_MESH_b);
TFT_MESH = COLOR565(r, g, b);
}
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190)
@@ -750,13 +746,13 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
#endif
screen->setFrames(graphics::Screen::FOCUS_SYSTEM);
if (TFT_MESH_r == 0 && TFT_MESH_g == 0 && TFT_MESH_b == 0) {
if (r == 0 && g == 0 && b == 0) {
uiconfig.screen_rgb_color = 0;
} else {
uiconfig.screen_rgb_color = (TFT_MESH_r << 16) | (TFT_MESH_g << 8) | TFT_MESH_b;
uiconfig.screen_rgb_color = (r << 16) | (g << 8) | b;
}
LOG_INFO("Storing Value of %d to uiconfig.screen_rgb_color", uiconfig.screen_rgb_color);
saveUIConfig();
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
}
#endif
};
@@ -775,29 +771,6 @@ void menuHandler::rebootMenu()
IF_SCREEN(screen->showSimpleBanner("Rebooting...", 0));
nodeDB->saveToDisk();
rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000;
} else {
menuQueue = power_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::shutdownMenu()
{
static const char *optionsArray[] = {"Back", "Confirm"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Shutdown Device?";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
IF_SCREEN(screen->showSimpleBanner("Shutting Down...", 0));
nodeDB->saveToDisk();
power->shutdown();
} else {
menuQueue = power_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
@@ -805,7 +778,7 @@ void menuHandler::shutdownMenu()
void menuHandler::addFavoriteMenu()
{
screen->showNodePicker("Node To Favorite", 30000, [](uint32_t nodenum) -> void {
screen->showNodePicker("Node To Favorite", 30000, [](int nodenum) -> void {
LOG_WARN("Nodenum: %u", nodenum);
nodeDB->set_favorite(true, nodenum);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
@@ -896,148 +869,6 @@ void menuHandler::wifiToggleMenu()
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::notificationsMenu()
{
enum optionsNumbers { Back, BuzzerActions };
static const char *optionsArray[] = {"Back", "Buzzer Actions"};
static int optionsEnumArray[] = {Back, BuzzerActions};
int options = 2;
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Notifications";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == BuzzerActions) {
menuHandler::menuQueue = menuHandler::buzzermodemenupicker;
screen->runNow();
} else {
menuQueue = system_base_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::screenOptionsMenu()
{
// Check if brightness is supported
bool hasSupportBrightness = false;
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || HAS_TFT
hasSupportBrightness = true;
#endif
enum optionsNumbers { Back, Brightness, ScreenColor };
static const char *optionsArray[4] = {"Back"};
static int optionsEnumArray[4] = {Back};
int options = 1;
// Only show brightness for B&W displays
if (hasSupportBrightness && !HAS_TFT) {
optionsArray[options] = "Brightness";
optionsEnumArray[options++] = Brightness;
}
// Only show screen color for TFT displays
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
optionsArray[options] = "Screen Color";
optionsEnumArray[options++] = ScreenColor;
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Screen Options";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Brightness) {
menuHandler::menuQueue = menuHandler::brightness_picker;
screen->runNow();
} else if (selected == ScreenColor) {
menuHandler::menuQueue = menuHandler::tftcolormenupicker;
screen->runNow();
} else {
menuQueue = system_base_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::powerMenu()
{
enum optionsNumbers { Back, Reboot, Shutdown, MUI };
static const char *optionsArray[4] = {"Back"};
static int optionsEnumArray[4] = {Back};
int options = 1;
optionsArray[options] = "Reboot";
optionsEnumArray[options++] = Reboot;
optionsArray[options] = "Shutdown";
optionsEnumArray[options++] = Shutdown;
#if HAS_TFT
optionsArray[options] = "Switch to MUI";
optionsEnumArray[options++] = MUI;
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Reboot / Shutdown";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Reboot) {
menuHandler::menuQueue = menuHandler::reboot_menu;
screen->runNow();
} else if (selected == Shutdown) {
menuHandler::menuQueue = menuHandler::shutdown_menu;
screen->runNow();
} else if (selected == MUI) {
menuHandler::menuQueue = menuHandler::mui_picker;
screen->runNow();
} else {
menuQueue = system_base_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::keyVerificationInitMenu()
{
screen->showNodePicker("Node to Verify", 30000,
[](uint32_t selected) -> void { keyVerificationModule->sendInitialRequest(selected); });
}
void menuHandler::keyVerificationFinalPrompt()
{
char message[40] = {0};
memset(message, 0, sizeof(message));
sprintf(message, "Verification: \n");
keyVerificationModule->generateVerificationCode(message + 15); // send the toPhone packet
if (screen) {
static const char *optionsArray[] = {"Reject", "Accept"};
graphics::BannerOverlayOptions options;
options.message = message;
options.durationMs = 30000;
options.optionsArrayPtr = optionsArray;
options.optionsCount = 2;
options.notificationType = graphics::notificationTypeEnum::selection_picker;
options.bannerCallback = [=](int selected) {
if (selected == 1) {
auto remoteNodePtr = nodeDB->getMeshNode(keyVerificationModule->getCurrentRemoteNode());
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
}
};
screen->showOverlayBanner(options);
}
}
void menuHandler::handleMenuSwitch(OLEDDisplay *display)
{
if (menuQueue != menu_none)
@@ -1060,9 +891,6 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case clock_menu:
clockMenu();
break;
case system_base_menu:
systemBaseMenu();
break;
case position_base_menu:
positionBaseMenu();
break;
@@ -1092,9 +920,6 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case reboot_menu:
rebootMenu();
break;
case shutdown_menu:
shutdownMenu();
break;
case add_favorite:
addFavoriteMenu();
break;
@@ -1110,36 +935,10 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case wifi_toggle_menu:
wifiToggleMenu();
break;
case key_verification_init:
keyVerificationInitMenu();
break;
case key_verification_final_prompt:
keyVerificationFinalPrompt();
break;
case bluetooth_toggle_menu:
BluetoothToggleMenu();
break;
case notifications_menu:
notificationsMenu();
break;
case screen_options_menu:
screenOptionsMenu();
break;
case power_menu:
powerMenu();
break;
case throttle_message:
screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000);
break;
}
menuQueue = menu_none;
}
void menuHandler::saveUIConfig()
{
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
}
} // namespace graphics
#endif

View File

@@ -1,5 +1,3 @@
#pragma once
#if HAS_SCREEN
#include "configuration.h"
namespace graphics
{
@@ -23,20 +21,11 @@ class menuHandler
tftcolormenupicker,
brightness_picker,
reboot_menu,
shutdown_menu,
add_favorite,
remove_favorite,
test_menu,
number_test,
wifi_toggle_menu,
bluetooth_toggle_menu,
notifications_menu,
screen_options_menu,
power_menu,
system_base_menu,
key_verification_init,
key_verification_final_prompt,
throttle_message
wifi_toggle_menu
};
static screenMenus menuQueue;
@@ -60,23 +49,12 @@ class menuHandler
static void resetNodeDBMenu();
static void BrightnessPickerMenu();
static void rebootMenu();
static void shutdownMenu();
static void addFavoriteMenu();
static void removeFavoriteMenu();
static void testMenu();
static void numberTest();
static void wifiBaseMenu();
static void wifiToggleMenu();
static void notificationsMenu();
static void screenOptionsMenu();
static void powerMenu();
private:
static void saveUIConfig();
static void keyVerificationInitMenu();
static void keyVerificationFinalPrompt();
static void BluetoothToggleMenu();
};
} // namespace graphics
#endif
} // namespace graphics

View File

@@ -26,7 +26,7 @@ extern bool hasUnreadMessage;
namespace graphics
{
InputEvent NotificationRenderer::inEvent;
char NotificationRenderer::inEvent = INPUT_BROKER_NONE;
int8_t NotificationRenderer::curSelected = 0;
char NotificationRenderer::alertBannerMessage[256] = {0};
uint32_t NotificationRenderer::alertBannerUntil = 0; // 0 is a special case meaning forever
@@ -42,7 +42,7 @@ uint32_t NotificationRenderer::currentNumber = 0;
uint32_t pow_of_10(uint32_t n)
{
uint32_t ret = 1;
for (uint32_t i = 0; i < n; i++) {
for (int i = 0; i < n; i++) {
ret *= 10;
}
return ret;
@@ -72,31 +72,14 @@ void NotificationRenderer::resetBanner()
{
alertBannerMessage[0] = '\0';
current_notification_type = notificationTypeEnum::none;
inEvent.inputEvent = INPUT_BROKER_NONE;
inEvent.kbchar = 0;
curSelected = 0;
alertBannerOptions = 0; // last x lines are seelctable options
optionsArrayPtr = nullptr;
optionsEnumPtr = nullptr;
alertBannerCallback = NULL;
pauseBanner = false;
numDigits = 0;
currentNumber = 0;
nodeDB->pause_sort(false);
}
void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state)
{
if (!isOverlayBannerShowing() && alertBannerMessage[0] != '\0')
resetBanner();
if (!isOverlayBannerShowing() || pauseBanner)
return;
switch (current_notification_type) {
case notificationTypeEnum::none:
// Do nothing - no notification to display
break;
case notificationTypeEnum::text_banner:
case notificationTypeEnum::selection_picker:
drawAlertBannerOverlay(display, state);
@@ -129,40 +112,31 @@ void NotificationRenderer::drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiS
// modulo to extract
uint8_t this_digit = (currentNumber % (pow_of_10(numDigits - curSelected))) / (pow_of_10(numDigits - curSelected - 1));
// Handle input
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
if (inEvent == INPUT_BROKER_UP || inEvent == INPUT_BROKER_ALT_PRESS) {
if (this_digit == 9) {
currentNumber -= 9 * (pow_of_10(numDigits - curSelected - 1));
} else {
currentNumber += (pow_of_10(numDigits - curSelected - 1));
}
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
} else if (inEvent == INPUT_BROKER_DOWN || inEvent == INPUT_BROKER_USER_PRESS) {
if (this_digit == 0) {
currentNumber += 9 * (pow_of_10(numDigits - curSelected - 1));
} else {
currentNumber -= (pow_of_10(numDigits - curSelected - 1));
}
} else if (inEvent.inputEvent == INPUT_BROKER_ANYKEY) {
if (inEvent.kbchar > 47 && inEvent.kbchar < 58) { // have a digit
currentNumber -= this_digit * (pow_of_10(numDigits - curSelected - 1));
currentNumber += (inEvent.kbchar - 48) * (pow_of_10(numDigits - curSelected - 1));
curSelected++;
}
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT || inEvent.inputEvent == INPUT_BROKER_RIGHT) {
} else if (inEvent == INPUT_BROKER_SELECT || inEvent == INPUT_BROKER_RIGHT) {
curSelected++;
} else if (inEvent.inputEvent == INPUT_BROKER_LEFT) {
} else if (inEvent == INPUT_BROKER_LEFT) {
curSelected--;
} else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) &&
alertBannerUntil != 0) {
} else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) {
resetBanner();
return;
}
if (curSelected == numDigits) {
alertBannerCallback(currentNumber);
resetBanner();
return;
alertBannerCallback(currentNumber);
}
inEvent.inputEvent = INPUT_BROKER_NONE;
inEvent = INPUT_BROKER_NONE;
if (alertBannerMessage[0] == '\0')
return;
@@ -170,12 +144,12 @@ void NotificationRenderer::drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiS
const char *linePointers[totalLines + 1] = {0}; // this is sort of a dynamic allocation
// copy the linestarts to display to the linePointers holder
for (uint16_t i = 0; i < lineCount; i++) {
for (int i = 0; i < lineCount; i++) {
linePointers[i] = lineStarts[i];
}
std::string digits = " ";
std::string arrowPointer = " ";
for (uint16_t i = 0; i < numDigits; i++) {
for (int i = 0; i < numDigits; i++) {
// Modulo minus modulo to return just the current number
digits += std::to_string((currentNumber % (pow_of_10(numDigits - i))) / (pow_of_10(numDigits - i - 1))) + " ";
if (curSelected == i) {
@@ -216,18 +190,16 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
}
// Handle input
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
if (inEvent == INPUT_BROKER_UP || inEvent == INPUT_BROKER_ALT_PRESS) {
curSelected--;
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
} else if (inEvent == INPUT_BROKER_DOWN || inEvent == INPUT_BROKER_USER_PRESS) {
curSelected++;
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
} else if (inEvent == INPUT_BROKER_SELECT) {
resetBanner();
alertBannerCallback(selectedNodenum);
} else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) {
resetBanner();
return;
} else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) &&
alertBannerUntil != 0) {
resetBanner();
return;
}
if (curSelected == -1)
@@ -235,7 +207,7 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
if (curSelected == alertBannerOptions)
curSelected = 0;
inEvent.inputEvent = INPUT_BROKER_NONE;
inEvent = INPUT_BROKER_NONE;
if (alertBannerMessage[0] == '\0')
return;
@@ -333,11 +305,11 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
// Handle input
if (alertBannerOptions > 0) {
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
if (inEvent == INPUT_BROKER_UP || inEvent == INPUT_BROKER_ALT_PRESS) {
curSelected--;
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
} else if (inEvent == INPUT_BROKER_DOWN || inEvent == INPUT_BROKER_USER_PRESS) {
curSelected++;
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
} else if (inEvent == INPUT_BROKER_SELECT) {
if (optionsEnumPtr != nullptr) {
alertBannerCallback(optionsEnumPtr[curSelected]);
optionsEnumPtr = nullptr;
@@ -345,11 +317,8 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
alertBannerCallback(curSelected);
}
resetBanner();
return;
} else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) &&
alertBannerUntil != 0) {
} else if ((inEvent == INPUT_BROKER_CANCEL || inEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) {
resetBanner();
return;
}
if (curSelected == -1)
@@ -357,14 +326,12 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
if (curSelected == alertBannerOptions)
curSelected = 0;
} else {
if (inEvent.inputEvent == INPUT_BROKER_SELECT || inEvent.inputEvent == INPUT_BROKER_ALT_LONG ||
inEvent.inputEvent == INPUT_BROKER_CANCEL) {
if (inEvent == INPUT_BROKER_SELECT || inEvent == INPUT_BROKER_ALT_LONG || inEvent == INPUT_BROKER_CANCEL) {
resetBanner();
return;
}
}
inEvent.inputEvent = INPUT_BROKER_NONE;
inEvent = INPUT_BROKER_NONE;
if (alertBannerMessage[0] == '\0')
return;

View File

@@ -11,8 +11,7 @@ namespace graphics
class NotificationRenderer
{
public:
static InputEvent inEvent;
static char inKeypress;
static char inEvent;
static int8_t curSelected;
static char alertBannerMessage[256];
static uint32_t alertBannerUntil; // 0 is a special case meaning forever

View File

@@ -1,84 +0,0 @@
#include "./E0213A367.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
// Map the display controller IC's output to the connected panel
void E0213A367::configScanning()
{
// "Driver output control"
// Scan gates from 0 to 249 (vertical resolution 250px)
sendCommand(0x01);
sendData(0xF9);
sendData(0x00);
}
// Specify which information is used to control the sequence of voltages applied to move the pixels
void E0213A367::configWaveform()
{
// This command (0x37) is poorly documented
// As of July 2025, the datasheet for this display's controller IC is unavailable
// The values are supplied by Heltec, who presumably have privileged access to information from the display manufacturer
// Datasheet for the similar SSD1680 IC hints at the function of this command:
// "Spare VCOM OTP selection":
// Unclear why 0x40 is set. Sane values for related SSD1680 seem to be 0x80 or 0x00.
// Maybe value is redundant? No noticeable impact when set to 0x00.
// We'll leave it set to 0x40, following Heltec's lead, just in case.
// "Display Mode"
// Seems to specify whether a waveform stored in OTP should use display mode 1 or 2 (full refresh or differential refresh)
// Unusual that waveforms are programmed to OTP, but this meta information is not ..?
sendCommand(0x37); // "Write Register for Display Option" ?
sendData(0x40); // "Spare VCOM OTP selection" ?
sendData(0x80); // "Display Mode for WS[7:0]" ?
sendData(0x03); // "Display Mode for WS[15:8]" ?
sendData(0x0E); // "Display Mode [23:16]" ?
switch (updateType) {
case FAST:
sendCommand(0x3C); // Border waveform:
sendData(0x81); // As specified by Heltec. Actually VCOM (0x80)?. Bit 0 seems redundant here.
break;
case FULL:
default:
sendCommand(0x3C); // Border waveform:
sendData(0x01); // Follow LUT 1 (blink same as white pixels)
break;
}
}
// Tell controller IC which operations to run
void E0213A367::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, Display mode 1 "full refresh"
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 E0213A367::detachFromUpdate()
{
switch (updateType) {
case FAST:
return beginPolling(50, 500); // At least 500ms for fast refresh
case FULL:
default:
return beginPolling(100, 1500); // At least 1.5 seconds for full refresh
}
}
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS

View File

@@ -1,41 +0,0 @@
/*
E-Ink display driver
- SSD1682
- Manufacturer: SEEKINK
- Size: 2.13 inch
- Resolution: 122px x 255px
- Flex connector marking: HINK-E0213A162-A1 (hidden, printed on reverse)
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD1682.h"
namespace NicheGraphics::Drivers
{
class E0213A367 : public SSD1682
{
// Display properties
private:
static constexpr uint32_t width = 122;
static constexpr uint32_t height = 250;
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
public:
E0213A367() : SSD1682(width, height, supported, 0) {}
protected:
void configScanning() override;
void configWaveform() override;
void configUpdateSequence() override;
void detachFromUpdate() override;
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS

View File

@@ -1,41 +0,0 @@
#include "./SSD1682.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
SSD1682::SSD1682(uint16_t width, uint16_t height, EInk::UpdateTypes supported, uint8_t bufferOffsetX)
: SSD16XX(width, height, supported, bufferOffsetX)
{
}
// SSD1682 only accepts single-byte x and y values
// This causes an incompatibility with the default SSD16XX::configFullscreen
void SSD1682::configFullscreen()
{
// Define the boundaries of the "fullscreen" region, for the controller IC
static const uint8_t sx = bufferOffsetX; // Notice the offset
static const uint8_t sy = 0;
static const uint8_t ex = bufferRowSize + bufferOffsetX - 1; // End is "max index", not "count". Minus 1 handles this
static const uint8_t ey = height;
// Data entry mode - Left to Right, Top to Bottom
sendCommand(0x11);
sendData(0x03);
// Select controller IC memory region to display a fullscreen image
sendCommand(0x44); // Memory X start - end
sendData(sx);
sendData(ex);
sendCommand(0x45); // Memory Y start - end
sendData(sy);
sendData(ey);
// Place the cursor at the start of this memory region, ready to send image data x=0 y=0
sendCommand(0x4E); // Memory cursor X
sendData(sx);
sendCommand(0x4F); // Memory cursor y
sendData(sy);
}
#endif

View File

@@ -1,31 +0,0 @@
/*
E-Ink base class for displays based on SSD1682
SSD1682 has a few quirks. We're implementing them here in a new base class,
to avoid re-implementing them every time we need to add a new SSD1682-based display.
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD16XX.h"
namespace NicheGraphics::Drivers
{
class SSD1682 : public SSD16XX
{
public:
SSD1682(uint16_t width, uint16_t height, EInk::UpdateTypes supported, uint8_t bufferOffsetX = 0);
virtual void configFullscreen(); // Select memory region on controller IC
virtual void deepSleep() {} // Not usable (image memory not retained)
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS

View File

@@ -39,8 +39,8 @@ void InkHUD::Events::begin()
void InkHUD::Events::onButtonShort()
{
// Audio feedback (via buzzer)
// Short tone
playChirp();
// Short low tone
playBoop();
// Cancel any beeping, buzzing, blinking
// Some button handling suppressed if we are dismissing an external notification (see below)
bool dismissedExt = dismissExternalNotification();
@@ -64,8 +64,8 @@ void InkHUD::Events::onButtonShort()
void InkHUD::Events::onButtonLong()
{
// Audio feedback (via buzzer)
// Slightly longer than playChirp
playBoop();
// Low tone, longer than playBoop
playBeep();
// Check which system applet wants to handle the button press (if any)
SystemApplet *consumer = nullptr;

View File

@@ -1,83 +0,0 @@
#ifdef ARCH_PORTDUINO
#include "SeesawRotary.h"
#include "input/InputBroker.h"
using namespace concurrency;
SeesawRotary *seesawRotary;
SeesawRotary::SeesawRotary(const char *name) : OSThread(name)
{
_originName = name;
}
bool SeesawRotary::init()
{
if (inputBroker)
inputBroker->registerSource(this);
if (!ss.begin(SEESAW_ADDR)) {
return false;
}
// attachButtonInterrupts();
uint32_t version = ((ss.getVersion() >> 16) & 0xFFFF);
if (version != 4991) {
LOG_WARN("Wrong firmware loaded? %u", version);
} else {
LOG_INFO("Found Product 4991");
}
/*
#ifdef ARCH_ESP32
// Register callbacks for before and after lightsleep
// Used to detach and reattach interrupts
lsObserver.observe(&notifyLightSleep);
lsEndObserver.observe(&notifyLightSleepEnd);
#endif
*/
ss.pinMode(SS_SWITCH, INPUT_PULLUP);
// get starting position
encoder_position = ss.getEncoderPosition();
ss.setGPIOInterrupts((uint32_t)1 << SS_SWITCH, 1);
ss.enableEncoderInterrupt();
canSleep = true; // Assume we should not keep the board awake
return true;
}
int32_t SeesawRotary::runOnce()
{
InputEvent e;
e.inputEvent = INPUT_BROKER_NONE;
bool currentlyPressed = !ss.digitalRead(SS_SWITCH);
if (currentlyPressed && !wasPressed) {
e.inputEvent = INPUT_BROKER_SELECT;
}
wasPressed = currentlyPressed;
int32_t new_position = ss.getEncoderPosition();
// did we move arounde?
if (encoder_position != new_position) {
if (encoder_position == 0 && new_position != 1) {
e.inputEvent = INPUT_BROKER_ALT_PRESS;
} else if (new_position == 0 && encoder_position != 1) {
e.inputEvent = INPUT_BROKER_USER_PRESS;
} else if (new_position > encoder_position) {
e.inputEvent = INPUT_BROKER_USER_PRESS;
} else {
e.inputEvent = INPUT_BROKER_ALT_PRESS;
}
encoder_position = new_position;
}
if (e.inputEvent != INPUT_BROKER_NONE) {
e.source = this->_originName;
e.kbchar = 0x00;
this->notifyObservers(&e);
}
return 50;
}
#endif

View File

@@ -1,29 +0,0 @@
#pragma once
#ifdef ARCH_PORTDUINO
#include "Adafruit_seesaw.h"
#include "InputBroker.h"
#include "concurrency/OSThread.h"
#include "configuration.h"
#define SS_SWITCH 24
#define SS_NEOPIX 6
#define SEESAW_ADDR 0x36
class SeesawRotary : public Observable<const InputEvent *>, public concurrency::OSThread
{
public:
const char *_originName;
bool init();
SeesawRotary(const char *name);
int32_t runOnce() override;
private:
Adafruit_seesaw ss;
int32_t encoder_position;
bool wasPressed = false;
};
extern SeesawRotary *seesawRotary;
#endif

View File

@@ -1,72 +0,0 @@
#include "kbUsbBase.h"
#include "configuration.h"
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32S2
KbUsbBase::KbUsbBase(const char *name) : concurrency::OSThread(name)
{
this->_originName = name;
}
int32_t KbUsbBase::runOnce()
{
if (firstTime) {
// This is the first time the OSThread library has called this function, so init the USB HID routines
begin();
firstTime = 0;
} else {
task();
}
return 100;
}
void KbUsbBase::onKeyboardKey(uint8_t ascii, uint8_t keycode, uint8_t modifier)
{
if (ascii != 0) {
LOG_DEBUG("Key 0x%x Code 0x%x Mod 0x%x pressed\n", ascii, keycode, modifier);
// reset shift now that we have a keypress
InputEvent e;
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE;
e.source = this->_originName;
switch (ascii) {
case 0x1b: // ESC
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL;
break;
case 0x08: // Back
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK;
e.kbchar = ascii;
break;
case 0xb5: // Up
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP;
break;
case 0xb6: // Down
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN;
break;
case 0xb4: // Left
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT;
e.kbchar = ascii;
break;
case 0xb7: // Right
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT;
e.kbchar = ascii;
break;
case 0x0d: // Enter
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
break;
case 0x00: // nopress
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE;
break;
default: // all other keys
e.inputEvent = ANYKEY;
e.kbchar = ascii;
break;
}
if (e.inputEvent != meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE) {
this->notifyObservers(&e);
}
}
}
#endif

View File

@@ -1,21 +0,0 @@
#pragma once
#include "InputBroker.h"
#include "concurrency/OSThread.h"
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32S2
#include "EspUsbHost.h"
class KbUsbBase : public Observable<const InputEvent *>, public concurrency::OSThread, public EspUsbHost
{
public:
explicit KbUsbBase(const char *name);
protected:
virtual int32_t runOnce() override;
private:
void onKeyboardKey(uint8_t ascii, uint8_t keycode, uint8_t modifier);
const char *_originName;
bool firstTime = 1;
};
#endif

View File

@@ -1,15 +0,0 @@
#include "kbUsbImpl.h"
#include "InputBroker.h"
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32S2
KbUsbImpl *kbUsbImpl;
KbUsbImpl::KbUsbImpl() : KbUsbBase("usbKB") {}
void KbUsbImpl::init()
{
inputBroker->registerSource(this);
}
#endif // INPUTBROKER_MATRIX_TYPE

View File

@@ -1,23 +0,0 @@
#pragma once
#include "kbUsbBase.h"
#include "main.h"
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32S2
/**
* @brief The idea behind this class to have static methods for the event handlers.
* Check attachInterrupt() at RotaryEncoderInteruptBase.cpp
* Technically you can have as many rotary encoders hardver attached
* to your device as you wish, but you always need to have separate event
* handlers, thus you need to have a RotaryEncoderInterrupt implementation.
*/
class KbUsbImpl : public KbUsbBase
{
public:
KbUsbImpl();
void init();
};
extern KbUsbImpl *kbUsbImpl;
#endif

View File

@@ -22,6 +22,11 @@
#include "error.h"
#include "power.h"
#ifdef SENSECAP_INDICATOR // on the indicator run the additional serial port for the RP2040
#include "IndicatorSerial.h"
#include "mesh/comms/FakeI2C.h"
#endif
#if !MESHTASTIC_EXCLUDE_I2C
#include "detect/ScanI2CTwoWire.h"
#include <Wire.h>
@@ -457,7 +462,11 @@ void setup()
fsInit();
#if !MESHTASTIC_EXCLUDE_I2C
#if defined(I2C_SDA1) && defined(ARCH_RP2040)
// The Sensecap Indicator hast I2C on the secondary MCU. Tunnel this as wire1
#if defined(SENSECAP_INDICATOR)
FakeI2C& Wire1 = *FakeWire;
Wire1.begin();
#elif defined(I2C_SDA1) && defined(ARCH_RP2040)
Wire1.setSDA(I2C_SDA1);
Wire1.setSCL(I2C_SCL1);
Wire1.begin();
@@ -515,7 +524,9 @@ void setup()
LOG_INFO("Scan for i2c devices");
#endif
#if defined(I2C_SDA1) && defined(ARCH_RP2040)
#if defined(SENSECAP_INDICATOR)
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1);
#elif defined(I2C_SDA1) && defined(ARCH_RP2040)
Wire1.setSDA(I2C_SDA1);
Wire1.setSCL(I2C_SCL1);
Wire1.begin();
@@ -748,6 +759,15 @@ void setup()
} else
router = new ReliableRouter();
// If we have an indicator, start process to service secondary port
#ifdef SENSECAP_INDICATOR
#if SENSOR_PORT_NUM == 2
sensecapIndicator = new SensecapIndicator(Serial2);
#elif SENSOR_PORT_NUM == 1
sensecapIndicator = new SensecapIndicator(Serial1);
#endif
#endif
// only play start melody when role is not tracker or sensor
if (config.power.is_power_saving == true &&
IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TRACKER,

View File

@@ -24,11 +24,6 @@
#define min_node_info_broadcast_secs 60 * 60 // No regular broadcasts of more than once an hour
#define min_neighbor_info_broadcast_secs 4 * 60 * 60
#define default_map_publish_interval_secs 60 * 60
#ifdef USERPREFS_RINGTONE_NAG_SECS
#define default_ringtone_nag_secs USERPREFS_RINGTONE_NAG_SECS
#else
#define default_ringtone_nag_secs 60
#endif
#define default_mqtt_address "mqtt.meshtastic.org"
#define default_mqtt_username "meshdev"

View File

@@ -0,0 +1,154 @@
#ifdef SENSECAP_INDICATOR
#include "IndicatorSerial.h"
#include "mesh/comms/FakeI2C.h"
#include "mesh/comms/FakeUART.h"
#include <HardwareSerial.h>
#include <pb_decode.h>
#include <pb_encode.h>
SensecapIndicator *sensecapIndicator;
SensecapIndicator::SensecapIndicator(HardwareSerial &serial) : OSThread("SensecapIndicator")
{
if (!running) {
_serial = &serial;
_serial->setRxBufferSize(PB_BUFSIZE);
_serial->setPins(SENSOR_RP2040_RXD, SENSOR_RP2040_TXD);
_serial->begin(SENSOR_BAUD_RATE);
running = true;
LOG_DEBUG("Start indicator communication thread");
}
}
int32_t SensecapIndicator::runOnce()
{
if (running) {
size_t bytes_read = 0;
// See if there are any more bytes to add to our buffer.
size_t space_left = PB_BUFSIZE - pb_rx_size;
bytes_read = serial_check((char *)pb_rx_buf + pb_rx_size, space_left);
pb_rx_size += bytes_read;
check_packet();
return (10);
} else {
LOG_DEBUG("Not running");
return (1000);
}
}
bool SensecapIndicator::send_uplink(meshtastic_InterdeviceMessage message)
{
pb_tx_buf[0] = MT_MAGIC_0;
pb_tx_buf[1] = MT_MAGIC_1;
pb_ostream_t stream = pb_ostream_from_buffer(pb_tx_buf + MT_HEADER_SIZE, PB_BUFSIZE);
if (!pb_encode(&stream, meshtastic_InterdeviceMessage_fields, &message)) {
LOG_DEBUG("pb_encode failed");
return false;
}
// Store the payload length in the header
pb_tx_buf[2] = stream.bytes_written / 256;
pb_tx_buf[3] = stream.bytes_written % 256;
bool rv = send((const char *)pb_tx_buf, MT_HEADER_SIZE + stream.bytes_written);
return rv;
}
size_t SensecapIndicator::serial_check(char *buf, size_t space_left)
{
size_t bytes_read = 0;
while (_serial->available()) {
char c = _serial->read();
*buf++ = c;
if (++bytes_read >= space_left) {
LOG_DEBUG("Serial overflow: %d > %d", bytes_read, space_left);
break;
}
}
return bytes_read;
}
void SensecapIndicator::check_packet()
{
if (pb_rx_size < MT_HEADER_SIZE) {
// We don't even have a header yet
delay(NO_NEWS_PAUSE);
return;
}
if (pb_rx_buf[0] != MT_MAGIC_0 || pb_rx_buf[1] != MT_MAGIC_1) {
LOG_DEBUG("Got bad magic");
memset(pb_rx_buf, 0, PB_BUFSIZE);
pb_rx_size = 0;
return;
}
uint16_t payload_len = pb_rx_buf[2] << 8 | pb_rx_buf[3];
if (payload_len > PB_BUFSIZE) {
LOG_DEBUG("Got packet claiming to be ridiculous length");
return;
}
if ((size_t)(payload_len + 4) > pb_rx_size) {
delay(NO_NEWS_PAUSE);
return;
}
// We have a complete packet, handle it
handle_packet(payload_len);
}
bool SensecapIndicator::handle_packet(size_t payload_len)
{
meshtastic_InterdeviceMessage message = meshtastic_InterdeviceMessage_init_zero;
// Decode the protobuf and shift forward any remaining bytes in the buffer
// (which, if present, belong to the packet that we're going to process on the
// next loop)
pb_istream_t stream = pb_istream_from_buffer(pb_rx_buf + MT_HEADER_SIZE, payload_len);
bool status = pb_decode(&stream, meshtastic_InterdeviceMessage_fields, &message);
memmove(pb_rx_buf, pb_rx_buf + MT_HEADER_SIZE + payload_len, PB_BUFSIZE - MT_HEADER_SIZE - payload_len);
pb_rx_size -= MT_HEADER_SIZE + payload_len;
if (!status) {
LOG_DEBUG("Decoding failed");
return false;
}
switch (message.which_data) {
case meshtastic_InterdeviceMessage_i2c_response_tag:
LOG_DEBUG("Got I2C response");
if (message.data.i2c_response.status != meshtastic_I2CResponse_Status_OK) {
LOG_DEBUG("I2C response error: %d", message.data.i2c_response.status);
return false;
}
// send I2C response to FakeI2C
FakeWire->ingest(message.data.i2c_response);
return true;
break;
case meshtastic_InterdeviceMessage_nmea_tag:
// send String to NMEA processing
FakeSerial->stuff_buffer(message.data.nmea, strlen(message.data.nmea));
return true;
break;
default:
// the other messages really only flow downstream
LOG_DEBUG("Got a message of unexpected type");
return false;
}
}
bool SensecapIndicator::send(const char *buf, size_t len)
{
size_t wrote = _serial->write(buf, len);
if (wrote == len)
return true;
return false;
}
#endif // SENSECAP_INDICATOR

View File

@@ -0,0 +1,47 @@
#pragma once
#ifndef INDICATORSERIAL_H
#define INDICATORSERIAL_H
#ifdef SENSECAP_INDICATOR
#include "concurrency/OSThread.h"
#include "configuration.h"
#include "mesh/generated/meshtastic/interdevice.pb.h"
// Magic number at the start of all MT packets
#define MT_MAGIC_0 0x94
#define MT_MAGIC_1 0xc3
// The header is the magic number plus a 16-bit payload-length field
#define MT_HEADER_SIZE 4
// Wait this many msec if there's nothing new on the channel
#define NO_NEWS_PAUSE 25
#define PB_BUFSIZE meshtastic_InterdeviceMessage_size + MT_HEADER_SIZE
class SensecapIndicator : public concurrency::OSThread
{
public:
SensecapIndicator(HardwareSerial &serial);
int32_t runOnce() override;
bool send_uplink(meshtastic_InterdeviceMessage message);
private:
pb_byte_t pb_tx_buf[PB_BUFSIZE];
pb_byte_t pb_rx_buf[PB_BUFSIZE];
size_t pb_rx_size = 0; // Number of bytes currently in the buffer
HardwareSerial *_serial = &Serial2;
bool running = false;
size_t serial_check(char *buf, size_t space_left);
void check_packet();
bool handle_packet(size_t payload_len);
bool send(const char *buf, size_t len);
};
extern SensecapIndicator *sensecapIndicator;
#endif // SENSECAP_INDICATOR
#endif // INDICATORSERIAL_H

View File

@@ -344,22 +344,6 @@ NodeDB::NodeDB()
config.device.node_info_broadcast_secs = MAX_INTERVAL;
if (config.position.position_broadcast_secs > MAX_INTERVAL)
config.position.position_broadcast_secs = MAX_INTERVAL;
if (config.position.gps_update_interval > MAX_INTERVAL)
config.position.gps_update_interval = MAX_INTERVAL;
if (config.position.gps_attempt_time > MAX_INTERVAL)
config.position.gps_attempt_time = MAX_INTERVAL;
if (config.position.position_flags > MAX_INTERVAL)
config.position.position_flags = MAX_INTERVAL;
if (config.position.rx_gpio > MAX_INTERVAL)
config.position.rx_gpio = MAX_INTERVAL;
if (config.position.tx_gpio > MAX_INTERVAL)
config.position.tx_gpio = MAX_INTERVAL;
if (config.position.broadcast_smart_minimum_distance > MAX_INTERVAL)
config.position.broadcast_smart_minimum_distance = MAX_INTERVAL;
if (config.position.broadcast_smart_minimum_interval_secs > MAX_INTERVAL)
config.position.broadcast_smart_minimum_interval_secs = MAX_INTERVAL;
if (config.position.gps_en_gpio > MAX_INTERVAL)
config.position.gps_en_gpio = MAX_INTERVAL;
if (moduleConfig.neighbor_info.update_interval > MAX_INTERVAL)
moduleConfig.neighbor_info.update_interval = MAX_INTERVAL;
if (moduleConfig.telemetry.device_update_interval > MAX_INTERVAL)
@@ -385,14 +369,6 @@ NodeDB::NodeDB()
config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_LOCAL_ONLY;
}
#if !HAS_TFT
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
// On a device without MUI, this display mode makes no sense, and will break logic.
config.display.displaymode = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT;
config.bluetooth.enabled = true;
}
#endif
if (devicestateCRC != crc32Buffer(&devicestate, sizeof(devicestate)))
saveWhat |= SEGMENT_DEVICESTATE;
if (nodeDatabaseCRC != crc32Buffer(&nodeDatabase, sizeof(nodeDatabase)))
@@ -794,7 +770,7 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.external_notification.output_buzzer = PIN_BUZZER;
moduleConfig.external_notification.use_pwm = true;
moduleConfig.external_notification.alert_message_buzzer = true;
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
moduleConfig.external_notification.nag_timeout = 60;
#endif
#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312)
// Default to RAK led pin 2 (blue)
@@ -803,7 +779,7 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.external_notification.active = true;
moduleConfig.external_notification.alert_message = true;
moduleConfig.external_notification.output_ms = 1000;
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
moduleConfig.external_notification.nag_timeout = 60;
#endif
#ifdef HAS_I2S
@@ -812,10 +788,10 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.external_notification.use_i2s_as_buzzer = true;
moduleConfig.external_notification.alert_message_buzzer = true;
#if HAS_TFT
if (moduleConfig.external_notification.nag_timeout == default_ringtone_nag_secs)
if (moduleConfig.external_notification.nag_timeout == 60)
moduleConfig.external_notification.nag_timeout = 0;
#else
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
moduleConfig.external_notification.nag_timeout = 60;
#endif
#endif
#ifdef NANO_G2_ULTRA
@@ -1325,13 +1301,6 @@ void NodeDB::loadFromDisk()
saveToDisk(SEGMENT_MODULECONFIG);
}
#if ARCH_PORTDUINO
// set any config overrides
if (settingsMap[has_configDisplayMode]) {
config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)settingsMap[configDisplayMode];
}
#endif
}
/** Save a protobuf from a file, return true for success */

View File

@@ -246,10 +246,8 @@ void PacketHistory::insert(PacketRecord &r)
#if RECENT_WARN_AGE > 0
if (tu->rxTimeMsec && (OldtrxTimeMsec < RECENT_WARN_AGE)) {
if (!(tu->id == r.id && tu->sender == r.sender)) {
#if VERBOSE_PACKET_HISTORY
LOG_WARN("Packet History - insert: Reusing slot aged %ds < %ds RECENT_WARN_AGE", OldtrxTimeMsec / 1000,
RECENT_WARN_AGE / 1000);
#endif
} else {
// debug only
#if VERBOSE_PACKET_HISTORY
@@ -277,9 +275,7 @@ void PacketHistory::insert(PacketRecord &r)
#endif
if (r.rxTimeMsec == 0) {
#if VERBOSE_PACKET_HISTORY
LOG_WARN("Packet History - insert: I will not store packet with rxTimeMsec = 0.");
#endif
return; // Return early if we can't update the history
}

View File

@@ -645,6 +645,10 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
if (power > loraMaxPower) // Clamp power to maximum defined level
power = loraMaxPower;
if (TX_GAIN_LORA == 0) { // Setting power in config with defined TX_GAIN_LORA will cause decreasing power on each reboot
config.lora.tx_power = power; // Set limited power in config
}
LOG_INFO("Final Tx power: %d dBm", power);
}

103
src/mesh/comms/FakeI2C.cpp Normal file
View File

@@ -0,0 +1,103 @@
// File: /master/FakeI2C.cpp
#include "FakeI2C.h"
#ifdef SENSECAP_INDICATOR
FakeI2C *FakeWire = new FakeI2C();
void FakeI2C::begin() {}
void FakeI2C::beginTransmission(uint8_t address) {
_currentAddress = address;
// create a default interdevice message for I2C start
meshtastic_InterdeviceMessage cmd = meshtastic_InterdeviceMessage_init_default;
cmd.which_data = meshtastic_InterdeviceMessage_i2c_command_tag;
cmd.data.i2c_command.op = meshtastic_I2CCommand_Operation_START;
cmd.data.i2c_command.addr = _currentAddress;
sensecapIndicator->send_uplink(cmd);
}
void FakeI2C::endTransmission() {
meshtastic_InterdeviceMessage cmd = meshtastic_InterdeviceMessage_init_default;
cmd.which_data = meshtastic_InterdeviceMessage_i2c_command_tag;
cmd.data.i2c_command.op = meshtastic_I2CCommand_Operation_STOP;
sensecapIndicator->send_uplink(cmd);
}
void FakeI2C::write(uint8_t val) {
meshtastic_InterdeviceMessage cmd = meshtastic_InterdeviceMessage_init_default;
cmd.which_data = meshtastic_InterdeviceMessage_i2c_command_tag;
cmd.data.i2c_command.op = meshtastic_I2CCommand_Operation_WRITE;
cmd.data.i2c_command.data = val;
sensecapIndicator->send_uplink(cmd);
}
uint8_t FakeI2C::requestFrom(uint8_t address, uint8_t quantity) {
if (quantity != 1) return 0xFF;
meshtastic_InterdeviceMessage cmd = meshtastic_InterdeviceMessage_init_default;
cmd.which_data = meshtastic_InterdeviceMessage_i2c_command_tag;
cmd.data.i2c_command.op = meshtastic_I2CCommand_Operation_READ;
cmd.data.i2c_command.addr = address;
cmd.data.i2c_command.ack = false;
sensecapIndicator->send_uplink(cmd);
// Wait for the response coming in asynchronously till there is a timeout
unsigned long start = millis();
while (millis() - start < 100) {
if (_pending) {
_pending = false; // Clear the pending flag
return 1; // Indicate that we have read one byte
}
delay(10); // Avoid busy waiting
}
return 0;
}
int FakeI2C::read() {
return _lastByte;
}
uint8_t FakeI2C::readRegister(uint8_t reg) {
beginTransmission(_currentAddress);
write(reg);
endTransmission();
requestFrom(_currentAddress, 1);
return read();
}
void FakeI2C::writeRegister(uint8_t reg, uint8_t val) {
beginTransmission(_currentAddress);
write(reg);
write(val);
endTransmission();
}
uint16_t FakeI2C::readRegister16(uint8_t reg) {
beginTransmission(_currentAddress);
write(reg);
endTransmission();
uint16_t result = 0;
for (int i = 0; i < 2; i++) {
requestFrom(_currentAddress, 1);
result |= ((uint16_t)read()) << (8 * (1 - i));
}
return result;
}
void FakeI2C::writeRegister16(uint8_t reg, uint16_t val) {
beginTransmission(_currentAddress);
write(reg);
write((uint8_t)(val >> 8));
write((uint8_t)(val & 0xFF));
endTransmission();
}
void FakeI2C::ingest(meshtastic_I2CResponse data) {
// Simulate receiving data as if it were from an I2C device
_lastByte = data.data; // Store the last byte read
_pending = true; // Indicate that we have pending data
}
#endif // SENSECAP_INDICATOR

38
src/mesh/comms/FakeI2C.h Normal file
View File

@@ -0,0 +1,38 @@
// File: /master/FakeI2C.h
#ifndef FAKEI2C_H
#define FAKEI2C_H
#ifdef SENSECAP_INDICATOR
#include <Arduino.h>
#include "../IndicatorSerial.h"
#include "../generated/meshtastic/interdevice.pb.h"
class FakeI2C {
public:
void begin();
void beginTransmission(uint8_t address);
void endTransmission();
void write(uint8_t val);
uint8_t requestFrom(uint8_t address, uint8_t quantity);
int read();
uint8_t readRegister(uint8_t reg);
void writeRegister(uint8_t reg, uint8_t val);
uint16_t readRegister16(uint8_t reg);
void writeRegister16(uint8_t reg, uint16_t val);
void ingest(meshtastic_I2CResponse data);
private:
uint8_t _currentAddress = 0;
uint8_t _lastByte = 0;
bool _pending = false; // Indicates if there is pending data to be read
};
extern FakeI2C *FakeWire;
#endif // SENSECAP_INDICATOR
#endif // FAKEI2C_H

View File

@@ -0,0 +1,97 @@
#include "FakeUART.h"
#ifdef SENSECAP_INDICATOR
FakeUART *FakeSerial = new FakeUART();
FakeUART::FakeUART() {}
void FakeUART::begin(unsigned long baud, uint32_t config, int8_t rxPin, int8_t txPin, bool invert, unsigned long timeout_ms,
uint8_t rxfifo_full_thrhd)
{
baudrate = baud;
FakeBuf.clear();
LOG_DEBUG("FakeUART::begin(%lu)", baud);
}
void FakeUART::end()
{
FakeBuf.clear();
}
int FakeUART::available()
{
return FakeBuf.size();
}
int FakeUART::peek()
{
unsigned char ret;
if (FakeBuf.peek(ret))
return ret;
return -1;
}
int FakeUART::read()
{
unsigned char ret;
if (FakeBuf.pop(ret))
return ret;
return -1;
}
void FakeUART::flush(bool wait)
{
FakeBuf.clear();
}
uint32_t FakeUART::baudRate()
{
return baudrate;
}
void FakeUART::updateBaudRate(unsigned long speed)
{
baudrate = speed;
}
size_t FakeUART::setRxBufferSize(size_t size)
{
return size;
}
size_t FakeUART::write(const char *buffer)
{
return write((char *)buffer, strlen(buffer));
}
size_t FakeUART::write(uint8_t *buffer, size_t size)
{
return write((char *)buffer, size);
}
size_t FakeUART::write(char *buffer, size_t size)
{
meshtastic_InterdeviceMessage message = meshtastic_InterdeviceMessage_init_zero;
if (size > sizeof(message.data.nmea)) {
size = sizeof(message.data.nmea); // Truncate if buffer is too large
}
memcpy(message.data.nmea, buffer, size);
message.which_data = meshtastic_InterdeviceMessage_nmea_tag;
LOG_DEBUG("FakeUART::write(%s)", message.data.nmea);
sensecapIndicator->send_uplink(message);
return size;
}
size_t FakeUART::stuff_buffer(const char *buffer, size_t size)
{
// push buffer in a loop to FakeBuf
for (size_t i = 0; i < size; i++) {
if (!FakeBuf.push(buffer[i])) {
return i;
}
}
return size;
}
#endif // SENSECAP_INDICATOR

44
src/mesh/comms/FakeUART.h Normal file
View File

@@ -0,0 +1,44 @@
#pragma once
#ifndef FAKEUART_H
#define FAKEUART_H
#ifdef SENSECAP_INDICATOR
#include "../IndicatorSerial.h"
#include <RingBuf.h>
#include <Stream.h>
#include <inttypes.h>
class FakeUART : public Stream
{
public:
FakeUART();
void begin(unsigned long baud, uint32_t config = 0x800001c, int8_t rxPin = -1, int8_t txPin = -1, bool invert = false,
unsigned long timeout_ms = 20000UL, uint8_t rxfifo_full_thrhd = 112);
void end();
int available();
int peek();
int read();
void flush(bool wait = true);
uint32_t baudRate();
void updateBaudRate(unsigned long speed);
size_t setRxBufferSize(size_t size);
size_t write(const char *buffer);
size_t write(char *buffer, size_t size);
size_t write(uint8_t *buffer, size_t size);
size_t stuff_buffer(const char *buffer, size_t size);
virtual size_t write(uint8_t c) { return write(&c, 1); }
private:
unsigned long baudrate = 115200;
RingBuf<unsigned char, 2048> FakeBuf;
};
extern FakeUART *FakeSerial;
#endif // SENSECAP_INDICATOR
#endif // FAKEUART_H

View File

@@ -13,9 +13,8 @@ template <class T> constexpr const T &clamp(const T &v, const T &lo, const T &hi
#if HAS_SCREEN
#define IF_SCREEN(X) \
if (screen) { \
X; \
}
if (screen) \
X;
#else
#define IF_SCREEN(...)
#endif

View File

@@ -1327,6 +1327,12 @@ void AdminModule::handleSendInputEvent(const meshtastic_AdminMessage_InputEvent
LOG_DEBUG("Processing input event: event_code=%u, kb_char=%u, touch_x=%u, touch_y=%u", inputEvent.event_code,
inputEvent.kb_char, inputEvent.touch_x, inputEvent.touch_y);
// Validate input parameters
if (inputEvent.event_code > INPUT_BROKER_ANYKEY) {
LOG_WARN("Invalid input event code: %u", inputEvent.event_code);
return;
}
// Create InputEvent for injection
InputEvent event = {.inputEvent = (input_broker_event)inputEvent.event_code,
.kbchar = (unsigned char)inputEvent.kb_char,

View File

@@ -454,7 +454,7 @@ int CannedMessageModule::handleDestinationSelectionInput(const InputEvent *event
else if ((destIndex / columns) >= (scrollIndex + visibleRows))
scrollIndex = (destIndex / columns) - visibleRows + 1;
screen->forceDisplay(true);
screen->forceDisplay();
return 1;
}
@@ -469,7 +469,7 @@ int CannedMessageModule::handleDestinationSelectionInput(const InputEvent *event
if ((destIndex / columns) >= (scrollIndex + visibleRows))
scrollIndex = (destIndex / columns) - visibleRows + 1;
screen->forceDisplay(true);
screen->forceDisplay();
return 1;
}
@@ -491,7 +491,7 @@ int CannedMessageModule::handleDestinationSelectionInput(const InputEvent *event
runState = returnToCannedList ? CANNED_MESSAGE_RUN_STATE_ACTIVE : CANNED_MESSAGE_RUN_STATE_FREETEXT;
returnToCannedList = false;
screen->forceDisplay(true);
screen->forceDisplay();
return 1;
}
@@ -504,7 +504,7 @@ int CannedMessageModule::handleDestinationSelectionInput(const InputEvent *event
// UIFrameEvent e;
// e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
// notifyObservers(&e);
screen->forceDisplay(true);
screen->forceDisplay();
return 1;
}
@@ -716,7 +716,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
}
// Backspace
if (event->inputEvent == INPUT_BROKER_BACK && this->freetext.length() > 0) {
if (event->inputEvent == INPUT_BROKER_BACK) {
payload = 0x08;
lastTouchMillis = millis();
runOnce();
@@ -739,8 +739,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
}
// Cancel (dismiss freetext screen)
if (event->inputEvent == INPUT_BROKER_CANCEL || event->inputEvent == INPUT_BROKER_ALT_LONG ||
(event->inputEvent == INPUT_BROKER_BACK && this->freetext.length() == 0)) {
if (event->inputEvent == INPUT_BROKER_CANCEL || event->inputEvent == INPUT_BROKER_ALT_LONG) {
runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
freetext = "";
cursor = 0;
@@ -990,7 +989,6 @@ int32_t CannedMessageModule::runOnce()
}
this->cursor--;
}
} else {
}
break;
case INPUT_BROKER_MSG_TAB: // Tab key: handled by input handler
@@ -2077,4 +2075,4 @@ String CannedMessageModule::drawWithCursor(String text, int cursor)
return result;
}
#endif
#endif

View File

@@ -362,8 +362,9 @@ ExternalNotificationModule::ExternalNotificationModule()
if (nodeDB->loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig),
&meshtastic_RTTTLConfig_msg, &rtttlConfig) != LoadFileResult::LOAD_SUCCESS) {
memset(rtttlConfig.ringtone, 0, sizeof(rtttlConfig.ringtone));
// The default ringtone is always loaded from userPrefs.jsonc
strncpy(rtttlConfig.ringtone, USERPREFS_RINGTONE_RTTTL, sizeof(rtttlConfig.ringtone));
strncpy(rtttlConfig.ringtone,
"24:d=32,o=5,b=565:f6,p,f6,4p,p,f6,p,f6,2p,p,b6,p,b6,p,b6,p,b6,p,b,p,b,p,b,p,b,p,b,p,b,p,b,p,b,1p.,2p.,p",
sizeof(rtttlConfig.ringtone));
}
LOG_INFO("Init External Notification Module");

View File

@@ -2,9 +2,7 @@
#include "KeyVerificationModule.h"
#include "MeshService.h"
#include "RTC.h"
#include "graphics/draw/MenuHandler.h"
#include "main.h"
#include "meshUtils.h"
#include "modules/AdminModule.h"
#include <SHA256.h>
@@ -50,22 +48,18 @@ AdminMessageHandleResult KeyVerificationModule::handleAdminMessageForModule(cons
bool KeyVerificationModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_KeyVerification *r)
{
updateState();
if (mp.pki_encrypted == false) {
if (mp.pki_encrypted == false)
return false;
}
if (mp.from != currentRemoteNode) { // because the inital connection request is handled in allocReply()
if (mp.from != currentRemoteNode) // because the inital connection request is handled in allocReply()
return false;
}
if (currentState == KEY_VERIFICATION_IDLE) {
return false; // if we're idle, the only acceptable message is an init, which should be handled by allocReply()
}
if (currentState == KEY_VERIFICATION_SENDER_HAS_INITIATED && r->nonce == currentNonce && r->hash2.size == 32 &&
r->hash1.size == 0) {
} else if (currentState == KEY_VERIFICATION_SENDER_HAS_INITIATED && r->nonce == currentNonce && r->hash2.size == 32 &&
r->hash1.size == 0) {
memcpy(hash2, r->hash2.bytes, 32);
IF_SCREEN(screen->showNumberPicker("Enter Security Number", 60000, 6, [](int number_picked) -> void {
keyVerificationModule->processSecurityNumber(number_picked);
});)
if (screen)
screen->showSimpleBanner("Enter Security Number", 30000);
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING;
@@ -85,20 +79,23 @@ bool KeyVerificationModule::handleReceivedProtobuf(const meshtastic_MeshPacket &
memset(message, 0, sizeof(message));
sprintf(message, "Verification: \n");
generateVerificationCode(message + 15);
static const char *optionsArray[] = {"ACCEPT", "REJECT"};
LOG_INFO("Hash1 matches!");
static const char *optionsArray[] = {"Reject", "Accept"};
// Don't try to put the array definition in the macro. Does not work with curly braces.
IF_SCREEN(graphics::BannerOverlayOptions options; options.message = message; options.durationMs = 30000;
options.optionsArrayPtr = optionsArray; options.optionsCount = 2;
options.notificationType = graphics::notificationTypeEnum::selection_picker;
options.bannerCallback =
[=](int selected) {
if (selected == 1) {
auto remoteNodePtr = nodeDB->getMeshNode(currentRemoteNode);
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
}
};
screen->showOverlayBanner(options);)
if (screen) {
graphics::BannerOverlayOptions options;
options.message = message;
options.durationMs = 30000;
options.optionsArrayPtr = optionsArray;
options.optionsCount = 2;
options.notificationType = graphics::notificationTypeEnum::selection_picker;
options.bannerCallback = [=](int selected) {
if (selected == 0) {
auto remoteNodePtr = nodeDB->getMeshNode(currentRemoteNode);
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
}
};
screen->showOverlayBanner(options);
}
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING;
sprintf(cn->message, "Final confirmation for incoming manual key verification %s", message);
@@ -123,7 +120,6 @@ bool KeyVerificationModule::sendInitialRequest(NodeNum remoteNode)
// generate nonce
updateState();
if (currentState != KEY_VERIFICATION_IDLE) {
IF_SCREEN(graphics::menuHandler::menuQueue = graphics::menuHandler::throttle_message;)
return false;
}
currentNonce = random();
@@ -194,8 +190,11 @@ meshtastic_MeshPacket *KeyVerificationModule::allocReply()
responsePacket = allocDataProtobuf(response);
responsePacket->pki_encrypted = true;
IF_SCREEN(snprintf(message, 25, "Security Number \n%03u %03u", currentSecurityNumber / 1000, currentSecurityNumber % 1000);
screen->showSimpleBanner(message, 30000); LOG_WARN("%s", message);)
if (screen) {
snprintf(message, 25, "Security Number \n%03u %03u", currentSecurityNumber / 1000, currentSecurityNumber % 1000);
screen->showSimpleBanner(message, 30000);
LOG_WARN("%s", message);
}
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING;
sprintf(cn->message, "Incoming Key Verification.\nSecurity Number\n%03u %03u", currentSecurityNumber / 1000,
@@ -259,7 +258,12 @@ void KeyVerificationModule::processSecurityNumber(uint32_t incomingNumber)
p->priority = meshtastic_MeshPacket_Priority_HIGH;
service->sendToMesh(p, RX_SRC_LOCAL, true);
currentState = KEY_VERIFICATION_SENDER_AWAITING_USER;
IF_SCREEN(screen->requestMenu(graphics::menuHandler::key_verification_final_prompt);)
memset(message, 0, sizeof(message));
sprintf(message, "Verification: \n");
generateVerificationCode(message + 15); // send the toPhone packet
if (screen) {
screen->showSimpleBanner(message, 30000);
}
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING;
sprintf(cn->message, "Final confirmation for outgoing manual key verification %s", message);
@@ -278,7 +282,7 @@ void KeyVerificationModule::processSecurityNumber(uint32_t incomingNumber)
void KeyVerificationModule::updateState()
{
if (currentState != KEY_VERIFICATION_IDLE) {
// check for the 60 second timeout
// check for the 30 second timeout
if (currentNonceTimestamp < getTime() - 60) {
resetToIdle();
} else {

View File

@@ -27,8 +27,6 @@ class KeyVerificationModule : public ProtobufModule<meshtastic_KeyVerification>
}*/
virtual bool wantUIFrame() { return false; };
bool sendInitialRequest(NodeNum remoteNode);
void generateVerificationCode(char *); // fills char with the user readable verification code
uint32_t getCurrentRemoteNode() { return currentRemoteNode; }
protected:
/* Called to handle a particular incoming message
@@ -58,8 +56,9 @@ class KeyVerificationModule : public ProtobufModule<meshtastic_KeyVerification>
char message[40] = {0};
void processSecurityNumber(uint32_t);
void updateState(); // check the timeouts and maybe reset the state to idle
void resetToIdle(); // Zero out module state
void updateState(); // check the timeouts and maybe reset the state to idle
void resetToIdle(); // Zero out module state
void generateVerificationCode(char *); // fills char with the user readable verification code
};
extern KeyVerificationModule *keyVerificationModule;

View File

@@ -12,7 +12,6 @@
#include "input/cardKbI2cImpl.h"
#endif
#include "input/kbMatrixImpl.h"
#include "input/kbUsbImpl.h"
#endif
#if !MESHTASTIC_EXCLUDE_PKI
#include "KeyVerificationModule.h"
@@ -54,7 +53,6 @@
#endif
#if ARCH_PORTDUINO
#include "input/LinuxInputImpl.h"
#include "input/SeesawRotary.h"
#include "modules/Telemetry/HostMetrics.h"
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
#include "modules/StoreForwardModule.h"
@@ -165,6 +163,7 @@ void setupModules()
// Example: Put your module here
// new ReplyModule();
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
if (!rotaryEncoderInterruptImpl1->init()) {
@@ -182,23 +181,14 @@ void setupModules()
kbMatrixImpl = new KbMatrixImpl();
kbMatrixImpl->init();
#endif // INPUTBROKER_MATRIX_TYPE
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32S2
kbUsbImpl = new KbUsbImpl();
kbUsbImpl->init();
#endif
#ifdef INPUTBROKER_SERIAL_TYPE
aSerialKeyboardImpl = new SerialKeyboardImpl();
aSerialKeyboardImpl->init();
#endif // INPUTBROKER_SERIAL_TYPE
#endif // INPUTBROKER_MATRIX_TYPE
}
#endif // HAS_BUTTON
#if ARCH_PORTDUINO
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
seesawRotary = new SeesawRotary("SeesawRotary");
if (!seesawRotary->init()) {
delete seesawRotary;
seesawRotary = nullptr;
}
aLinuxInputImpl = new LinuxInputImpl();
aLinuxInputImpl->init();
}

View File

@@ -193,10 +193,6 @@ CGRadSensSensor cgRadSens;
#include "Sensor/T1000xSensor.h"
T1000xSensor t1000xSensor;
#endif
#ifdef SENSECAP_INDICATOR
#include "Sensor/IndicatorSensor.h"
IndicatorSensor indicatorSensor;
#endif
#define FAILED_STATE_SENSOR_READ_MULTIPLIER 10
#define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true
@@ -236,9 +232,6 @@ int32_t EnvironmentTelemetryModule::runOnce()
if (moduleConfig.telemetry.environment_measurement_enabled || ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE) {
LOG_INFO("Environment Telemetry: init");
#ifdef SENSECAP_INDICATOR
result = indicatorSensor.runOnce();
#endif
#ifdef T1000X_SENSOR_EN
result = t1000xSensor.runOnce();
#elif !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL
@@ -540,10 +533,6 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m
m->which_variant = meshtastic_Telemetry_environment_metrics_tag;
m->variant.environment_metrics = meshtastic_EnvironmentMetrics_init_zero;
#ifdef SENSECAP_INDICATOR
valid = valid && indicatorSensor.getMetrics(m);
hasSensor = true;
#endif
#ifdef T1000X_SENSOR_EN // add by WayenWeng
valid = valid && t1000xSensor.getMetrics(m);
hasSensor = true;

View File

@@ -1,166 +0,0 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && defined(SENSECAP_INDICATOR)
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "IndicatorSensor.h"
#include "TelemetrySensor.h"
#include "serialization/cobs.h"
#include <Adafruit_Sensor.h>
#include <driver/uart.h>
IndicatorSensor::IndicatorSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SENSOR_UNSET, "Indicator") {}
#define SENSOR_BUF_SIZE (512)
uint8_t buf[SENSOR_BUF_SIZE]; // recv
uint8_t data[SENSOR_BUF_SIZE]; // decode
#define ACK_PKT_PARA "ACK"
enum sensor_pkt_type {
PKT_TYPE_ACK = 0x00, // uin32_t
PKT_TYPE_CMD_COLLECT_INTERVAL = 0xA0, // uin32_t
PKT_TYPE_CMD_BEEP_ON = 0xA1, // uin32_t ms: on time
PKT_TYPE_CMD_BEEP_OFF = 0xA2,
PKT_TYPE_CMD_SHUTDOWN = 0xA3, // uin32_t
PKT_TYPE_CMD_POWER_ON = 0xA4,
PKT_TYPE_SENSOR_SCD41_TEMP = 0xB0, // float
PKT_TYPE_SENSOR_SCD41_HUMIDITY = 0xB1, // float
PKT_TYPE_SENSOR_SCD41_CO2 = 0xB2, // float
PKT_TYPE_SENSOR_AHT20_TEMP = 0xB3, // float
PKT_TYPE_SENSOR_AHT20_HUMIDITY = 0xB4, // float
PKT_TYPE_SENSOR_TVOC_INDEX = 0xB5, // float
};
static int cmd_send(uint8_t cmd, const char *p_data, uint8_t len)
{
uint8_t send_buf[32] = {0};
uint8_t send_data[32] = {0};
if (len > 31) {
return -1;
}
uint8_t index = 1;
send_data[0] = cmd;
if (len > 0 && p_data != NULL) {
memcpy(&send_data[1], p_data, len);
index += len;
}
cobs_encode_result ret = cobs_encode(send_buf, sizeof(send_buf), send_data, index);
// LOG_DEBUG("cobs TX status:%d, len:%d, type 0x%x", ret.status, ret.out_len, cmd);
if (ret.status == COBS_ENCODE_OK) {
return uart_write_bytes(SENSOR_PORT_NUM, send_buf, ret.out_len + 1);
}
return -1;
}
int32_t IndicatorSensor::runOnce()
{
LOG_INFO("%s: init", sensorName);
setup();
return 2 * DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; // give it some time to start up
}
void IndicatorSensor::setup()
{
uart_config_t uart_config = {
.baud_rate = SENSOR_BAUD_RATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_APB,
};
int intr_alloc_flags = 0;
char buffer[11];
uart_driver_install(SENSOR_PORT_NUM, SENSOR_BUF_SIZE * 2, 0, 0, NULL, intr_alloc_flags);
uart_param_config(SENSOR_PORT_NUM, &uart_config);
uart_set_pin(SENSOR_PORT_NUM, SENSOR_RP2040_TXD, SENSOR_RP2040_RXD, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
cmd_send(PKT_TYPE_CMD_POWER_ON, NULL, 0);
// measure and send only once every minute, for the phone API
const char *interval = ultoa(60000, buffer, 10);
cmd_send(PKT_TYPE_CMD_COLLECT_INTERVAL, interval, strlen(interval) + 1);
}
bool IndicatorSensor::getMetrics(meshtastic_Telemetry *measurement)
{
cobs_decode_result ret;
int len = uart_read_bytes(SENSOR_PORT_NUM, buf, (SENSOR_BUF_SIZE - 1), 100 / portTICK_PERIOD_MS);
float value = 0.0;
uint8_t *p_buf_start = buf;
uint8_t *p_buf_end = buf;
if (len > 0) {
while (p_buf_start < (buf + len)) {
p_buf_end = p_buf_start;
while (p_buf_end < (buf + len)) {
if (*p_buf_end == 0x00) {
break;
}
p_buf_end++;
}
// decode buf
memset(data, 0, sizeof(data));
ret = cobs_decode(data, sizeof(data), p_buf_start, p_buf_end - p_buf_start);
// LOG_DEBUG("cobs RX status:%d, len:%d, type:0x%x ", ret.status, ret.out_len, data[0]);
if (ret.out_len > 1 && ret.status == COBS_DECODE_OK) {
value = 0.0;
uint8_t pkt_type = data[0];
switch (pkt_type) {
case PKT_TYPE_SENSOR_SCD41_CO2: {
memcpy(&value, &data[1], sizeof(value));
// LOG_DEBUG("CO2: %.1f", value);
cmd_send(PKT_TYPE_ACK, ACK_PKT_PARA, 4);
break;
}
case PKT_TYPE_SENSOR_AHT20_TEMP: {
memcpy(&value, &data[1], sizeof(value));
// LOG_DEBUG("Temp: %.1f", value);
cmd_send(PKT_TYPE_ACK, ACK_PKT_PARA, 4);
measurement->variant.environment_metrics.has_temperature = true;
measurement->variant.environment_metrics.temperature = value;
break;
}
case PKT_TYPE_SENSOR_AHT20_HUMIDITY: {
memcpy(&value, &data[1], sizeof(value));
// LOG_DEBUG("Humidity: %.1f", value);
cmd_send(PKT_TYPE_ACK, ACK_PKT_PARA, 4);
measurement->variant.environment_metrics.has_relative_humidity = true;
measurement->variant.environment_metrics.relative_humidity = value;
break;
}
case PKT_TYPE_SENSOR_TVOC_INDEX: {
memcpy(&value, &data[1], sizeof(value));
// LOG_DEBUG("Tvoc: %.1f", value);
cmd_send(PKT_TYPE_ACK, ACK_PKT_PARA, 4);
measurement->variant.environment_metrics.has_iaq = true;
measurement->variant.environment_metrics.iaq = value;
break;
}
default:
break;
}
}
p_buf_start = p_buf_end + 1; // next message
}
return true;
}
return false;
}
#endif

View File

@@ -1,19 +0,0 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
class IndicatorSensor : public TelemetrySensor
{
protected:
virtual void setup() override;
public:
IndicatorSensor();
virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
};
#endif

View File

@@ -4,7 +4,6 @@
#include "PowerFSM.h"
#include "buzz.h"
#include "configuration.h"
#include "graphics/Screen.h"
TextMessageModule *textMessageModule;
ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp)
@@ -18,10 +17,7 @@ ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp
devicestate.rx_text_message = mp;
devicestate.has_rx_text_message = true;
// Only trigger screen wake if configuration allows it
if (shouldWakeOnReceivedMessage()) {
powerFSM.trigger(EVENT_RECEIVED_MSG);
}
powerFSM.trigger(EVENT_RECEIVED_MSG);
notifyObservers(&mp);
return ProcessMessage::CONTINUE; // Let others look at this message also if they want

View File

@@ -35,8 +35,6 @@ char *configPath = nullptr;
char *optionMac = nullptr;
bool forceSimulated = false;
const char *argp_program_version = optstr(APP_VERSION);
// FIXME - move setBluetoothEnable into a HALPlatform class
void setBluetoothEnable(bool enable)
{
@@ -667,21 +665,6 @@ bool loadConfig(const char *configPath)
settingsStrings[hostMetrics_user_command] = (yamlConfig["HostMetrics"]["UserStringCommand"]).as<std::string>("");
}
if (yamlConfig["Config"]) {
if (yamlConfig["Config"]["DisplayMode"]) {
settingsMap[has_configDisplayMode] = true;
if ((yamlConfig["Config"]["DisplayMode"]).as<std::string>("") == "TWOCOLOR") {
settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR;
} else if ((yamlConfig["Config"]["DisplayMode"]).as<std::string>("") == "INVERTED") {
settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_INVERTED;
} else if ((yamlConfig["Config"]["DisplayMode"]).as<std::string>("") == "COLOR") {
settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_COLOR;
} else {
settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT;
}
}
}
if (yamlConfig["General"]) {
settingsMap[maxnodes] = (yamlConfig["General"]["MaxNodes"]).as<int>(200);
settingsMap[maxtophone] = (yamlConfig["General"]["MaxMessageQueue"]).as<int>(100);

View File

@@ -109,9 +109,7 @@ enum configNames {
mac_address,
hostMetrics_interval,
hostMetrics_channel,
hostMetrics_user_command,
configDisplayMode,
has_configDisplayMode
hostMetrics_user_command
};
enum { no_screen, x11, fb, st7789, st7735, st7735s, st7796, ili9341, ili9342, ili9486, ili9488, hx8357d };
enum { no_touchscreen, xpt2046, stmpe610, gt911, ft5x06 };

View File

@@ -1,129 +0,0 @@
#include "cobs.h"
#include <stdlib.h>
#ifdef SENSECAP_INDICATOR
cobs_encode_result cobs_encode(uint8_t *dst_buf_ptr, size_t dst_buf_len, const uint8_t *src_ptr, size_t src_len)
{
cobs_encode_result result = {0, COBS_ENCODE_OK};
if (!dst_buf_ptr || !src_ptr) {
result.status = COBS_ENCODE_NULL_POINTER;
return result;
}
const uint8_t *src_read_ptr = src_ptr;
const uint8_t *src_end_ptr = src_read_ptr + src_len;
uint8_t *dst_buf_start_ptr = dst_buf_ptr;
uint8_t *dst_buf_end_ptr = dst_buf_start_ptr + dst_buf_len;
uint8_t *dst_code_write_ptr = dst_buf_ptr;
uint8_t *dst_write_ptr = dst_code_write_ptr + 1;
uint8_t search_len = 1;
if (src_len != 0) {
for (;;) {
if (dst_write_ptr >= dst_buf_end_ptr) {
result.status = (cobs_encode_status)(result.status | (cobs_encode_status)COBS_ENCODE_OUT_BUFFER_OVERFLOW);
break;
}
uint8_t src_byte = *src_read_ptr++;
if (src_byte == 0) {
*dst_code_write_ptr = search_len;
dst_code_write_ptr = dst_write_ptr++;
search_len = 1;
if (src_read_ptr >= src_end_ptr) {
break;
}
} else {
*dst_write_ptr++ = src_byte;
search_len++;
if (src_read_ptr >= src_end_ptr) {
break;
}
if (search_len == 0xFF) {
*dst_code_write_ptr = search_len;
dst_code_write_ptr = dst_write_ptr++;
search_len = 1;
}
}
}
}
if (dst_code_write_ptr >= dst_buf_end_ptr) {
result.status = (cobs_encode_status)(result.status | (cobs_encode_status)COBS_ENCODE_OUT_BUFFER_OVERFLOW);
dst_write_ptr = dst_buf_end_ptr;
} else {
*dst_code_write_ptr = search_len;
}
result.out_len = dst_write_ptr - dst_buf_start_ptr;
return result;
}
cobs_decode_result cobs_decode(uint8_t *dst_buf_ptr, size_t dst_buf_len, const uint8_t *src_ptr, size_t src_len)
{
cobs_decode_result result = {0, COBS_DECODE_OK};
if (!dst_buf_ptr || !src_ptr) {
result.status = COBS_DECODE_NULL_POINTER;
return result;
}
const uint8_t *src_read_ptr = src_ptr;
const uint8_t *src_end_ptr = src_read_ptr + src_len;
uint8_t *dst_buf_start_ptr = dst_buf_ptr;
const uint8_t *dst_buf_end_ptr = dst_buf_start_ptr + dst_buf_len;
uint8_t *dst_write_ptr = dst_buf_ptr;
if (src_len != 0) {
for (;;) {
uint8_t len_code = *src_read_ptr++;
if (len_code == 0) {
result.status = (cobs_decode_status)(result.status | (cobs_decode_status)COBS_DECODE_ZERO_BYTE_IN_INPUT);
break;
}
len_code--;
size_t remaining_bytes = src_end_ptr - src_read_ptr;
if (len_code > remaining_bytes) {
result.status = (cobs_decode_status)(result.status | (cobs_decode_status)COBS_DECODE_INPUT_TOO_SHORT);
len_code = remaining_bytes;
}
remaining_bytes = dst_buf_end_ptr - dst_write_ptr;
if (len_code > remaining_bytes) {
result.status = (cobs_decode_status)(result.status | (cobs_decode_status)COBS_DECODE_OUT_BUFFER_OVERFLOW);
len_code = remaining_bytes;
}
for (uint8_t i = len_code; i != 0; i--) {
uint8_t src_byte = *src_read_ptr++;
if (src_byte == 0) {
result.status = (cobs_decode_status)(result.status | (cobs_decode_status)COBS_DECODE_ZERO_BYTE_IN_INPUT);
}
*dst_write_ptr++ = src_byte;
}
if (src_read_ptr >= src_end_ptr) {
break;
}
if (len_code != 0xFE) {
if (dst_write_ptr >= dst_buf_end_ptr) {
result.status = (cobs_decode_status)(result.status | (cobs_decode_status)COBS_DECODE_OUT_BUFFER_OVERFLOW);
break;
}
*dst_write_ptr++ = 0;
}
}
}
result.out_len = dst_write_ptr - dst_buf_start_ptr;
return result;
}
#endif

View File

@@ -1,75 +0,0 @@
#ifndef COBS_H_
#define COBS_H_
#include "configuration.h"
#ifdef SENSECAP_INDICATOR
#include <stdint.h>
#include <stdlib.h>
#define COBS_ENCODE_DST_BUF_LEN_MAX(SRC_LEN) ((SRC_LEN) + (((SRC_LEN) + 253u) / 254u))
#define COBS_DECODE_DST_BUF_LEN_MAX(SRC_LEN) (((SRC_LEN) == 0) ? 0u : ((SRC_LEN)-1u))
#define COBS_ENCODE_SRC_OFFSET(SRC_LEN) (((SRC_LEN) + 253u) / 254u)
typedef enum {
COBS_ENCODE_OK = 0x00,
COBS_ENCODE_NULL_POINTER = 0x01,
COBS_ENCODE_OUT_BUFFER_OVERFLOW = 0x02
} cobs_encode_status;
typedef struct {
size_t out_len;
cobs_encode_status status;
} cobs_encode_result;
typedef enum {
COBS_DECODE_OK = 0x00,
COBS_DECODE_NULL_POINTER = 0x01,
COBS_DECODE_OUT_BUFFER_OVERFLOW = 0x02,
COBS_DECODE_ZERO_BYTE_IN_INPUT = 0x04,
COBS_DECODE_INPUT_TOO_SHORT = 0x08
} cobs_decode_status;
typedef struct {
size_t out_len;
cobs_decode_status status;
} cobs_decode_result;
#ifdef __cplusplus
extern "C" {
#endif
/* COBS-encode a string of input bytes.
*
* dst_buf_ptr: The buffer into which the result will be written
* dst_buf_len: Length of the buffer into which the result will be written
* src_ptr: The byte string to be encoded
* src_len Length of the byte string to be encoded
*
* returns: A struct containing the success status of the encoding
* operation and the length of the result (that was written to
* dst_buf_ptr)
*/
cobs_encode_result cobs_encode(uint8_t *dst_buf_ptr, size_t dst_buf_len, const uint8_t *src_ptr, size_t src_len);
/* Decode a COBS byte string.
*
* dst_buf_ptr: The buffer into which the result will be written
* dst_buf_len: Length of the buffer into which the result will be written
* src_ptr: The byte string to be decoded
* src_len Length of the byte string to be decoded
*
* returns: A struct containing the success status of the decoding
* operation and the length of the result (that was written to
* dst_buf_ptr)
*/
cobs_decode_result cobs_decode(uint8_t *dst_buf_ptr, size_t dst_buf_len, const uint8_t *src_ptr, size_t src_len);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* SENSECAP_INDICATOR */
#endif /* COBS_H_ */

View File

@@ -53,7 +53,5 @@
// "USERPREFS_MQTT_ENCRYPTION_ENABLED": "true",
// "USERPREFS_MQTT_TLS_ENABLED": "false",
// "USERPREFS_MQTT_ROOT_TOPIC": "event/REPLACEME",
// "USERPREFS_RINGTONE_NAG_SECS": "60",
"USERPREFS_RINGTONE_RTTTL": "24:d=32,o=5,b=565:f6,p,f6,4p,p,f6,p,f6,2p,p,b6,p,b6,p,b6,p,b6,p,b,p,b,p,b,p,b,p,b,p,b,p,b,p,b,1p.,2p.,p",
"USERPREFS_TZ_STRING": "tzplaceholder "
}

View File

@@ -104,11 +104,11 @@ void setupNicheGraphics()
buttons->setHandlerDown(1, [backlight]() { backlight->peek(); });
buttons->setHandlerLongPress(1, [backlight]() {
backlight->latch();
playBoop();
playBeep();
});
buttons->setHandlerShortPress(1, [backlight]() {
backlight->off();
playChirp();
playBoop();
});
// Begin handling button events

View File

@@ -8,7 +8,7 @@ board_level = extra
upload_protocol = esptool
;upload_port = /dev/ttyACM2
lib_deps =
${esp32s3_base.lib_deps}
${esp32_base.lib_deps}
caveman99/ESP32 Codec2@^1.0.1
build_flags =
${esp32s3_base.build_flags} -D PRIVATE_HW -I variants/bpi_picow_esp32_s3
${esp32_base.build_flags} -D PRIVATE_HW -I variants/bpi_picow_esp32_s3

View File

@@ -1,24 +0,0 @@
#ifndef Pins_Arduino_h
#define Pins_Arduino_h
#include <stdint.h>
static const uint8_t TX = 21;
static const uint8_t RX = 20;
static const uint8_t SDA = 1;
static const uint8_t SCL = 0;
static const uint8_t SS = 8;
static const uint8_t MOSI = 7;
static const uint8_t MISO = 6;
static const uint8_t SCK = 10;
static const uint8_t A0 = 0;
static const uint8_t A1 = 1;
static const uint8_t A2 = 2;
static const uint8_t A3 = 3;
static const uint8_t A4 = 4;
static const uint8_t A5 = 5;
#endif /* Pins_Arduino_h */

View File

@@ -1,61 +0,0 @@
#ifndef _VARIANT_ESP32C3_SUPER_MINI_
#define _VARIANT_ESP32C3_SUPER_MINI_
/*----------------------------------------------------------------------------
* Headers
*----------------------------------------------------------------------------*/
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// I2C (Wire) & OLED
#define WIRE_INTERFACES_COUNT (1)
#define I2C_SDA (1)
#define I2C_SCL (0)
#define USE_SSD1306
// GPS
#undef GPS_RX_PIN
#undef GPS_TX_PIN
#define GPS_RX_PIN (20)
#define GPS_TX_PIN (21)
// Button
#define BUTTON_PIN (9) // BOOT button
// LoRa
#define USE_LLCC68
#define USE_SX1262
// #define USE_RF95
#define USE_SX1268
#define LORA_DIO0 RADIOLIB_NC
#define LORA_RESET (5)
#define LORA_DIO1 (3)
#define LORA_RXEN (2)
#define LORA_BUSY (4)
#define LORA_SCK (10)
#define LORA_MISO (6)
#define LORA_MOSI (7)
#define LORA_CS (8)
#define SX126X_CS LORA_CS
#define SX126X_DIO1 LORA_DIO1
#define SX126X_BUSY LORA_BUSY
#define SX126X_RESET LORA_RESET
#define SX126X_RXEN LORA_RXEN
#define SX126X_DIO3_TCXO_VOLTAGE (1.8)
#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL
#ifdef __cplusplus
}
#endif
/*----------------------------------------------------------------------------
* Arduino objects - C++ only
*----------------------------------------------------------------------------*/
#endif

View File

@@ -96,20 +96,6 @@ board_level = extra
build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} -D PRIVATE_HW -DXIAO_BLE_LEGACY_PINOUT -DEBYTE_E22 -DEBYTE_E22_900M30S
build_unflags = -DGPS_L76K
; Seeed XIAO nRF52840 + EBYTE E22-900M30S - Pinout matching Wio-SX1262 (SKU 113010003)
[env:seeed_xiao_nrf52840_e22_900m30s]
extends = env:seeed_xiao_nrf52840_kit
board_level = extra
build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} -D PRIVATE_HW -DEBYTE_E22 -DEBYTE_E22_900M30S
build_unflags = -DGPS_L76K
; Seeed XIAO nRF52840 + EBYTE E22-900M33S - Pinout matching Wio-SX1262 (SKU 113010003)
[env:seeed_xiao_nrf52840_e22_900m33s]
extends = env:seeed_xiao_nrf52840_kit
board_level = extra
build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} -D PRIVATE_HW -DEBYTE_E22 -DEBYTE_E22_900M33S
build_unflags = -DGPS_L76K
; Seeed XIAO nRF52840 + XIAO Wio SX1262 DIY
[env:seeed-xiao-nrf52840-wio-sx1262]
board = xiao_ble_sense
@@ -141,16 +127,3 @@ build_flags =
-D ARDUINO_USB_MODE=0
-D ARDUINO_USB_CDC_ON_BOOT=1
-I variants/diy/t-energy-s3_e22
; ESP32 C3 Super Mini Development Board
; https://www.espboards.dev/esp32/esp32-c3-super-mini/
[env:esp32c3_super_mini]
extends = esp32c3_base
board = esp32-c3-devkitm-1
build_flags =
${esp32_base.build_flags}
-D PRIVATE_HW
-I variants/diy/esp32c3_super_mini
-D ARDUINO_USB_MODE=1
-D ARDUINO_USB_CDC_ON_BOOT=1
board_level = extra

View File

@@ -1,168 +1,264 @@
# XIAO nrf52840/nrf52840 Sense + Ebyte E22-900M30S
#
_A step-by-step guide for macOS and Linux._
<p align="center" style="font-size: 28px;">
Xiao BLE/BLE Sense + Ebyte E22-900M30S
</p>
<p align="center" style="font-size: 20px;">
A step-by-step guide for macOS and Linux
</p>
## Introduction
This guide will walk you through everything needed to get the XIAO nrf52840 (or XIAO nrf52840 Sense) running Meshtastic using an Ebyte E22-900M30S LoRa module. The combination of the E22 with an nRF52840 MCU is desirable because it allows for both very low idle (Rx) power draw _and_ high transmit power.
This guide will walk you through everything needed to get the Xiao BLE (or BLE Sense) running Meshtastic using an Ebyte E22-900M30S LoRa module. The combination of the E22 with an nRF52840 MCU is desirable because it allows for both very low idle (Rx) power draw <i>and</i> high transmit power. The Xiao BLE is a small but surprisingly well-appointed nRF52840 board, with enough GPIO for most Meshtastic applications and a built-in LiPo charger. The E22, on the other hand, is a famously inscrutable and mysterious beast. It is one of the more readily available LoRa modules capable of transmitting at 30 dBm, and includes an LNA to boost its Rx sensitivity a few dB beyond that of the SX1262. However, its documentation is relatively sparse overall, and seems to merely hint at (or completely omit) several key details regarding its functionality. Thus, much of what follows is a synthesis of my observations and inferences over the course of many hours of trial and error.
The XIAO nrf52840 is a small but surprisingly well-appointed nRF52840 board, with enough GPIO for most Meshtastic applications and a built-in LiPo charger.
The E22, on the other hand, is a famously inscrutable and mysterious beast. It is one of the more readily available LoRa modules capable of transmitting at 30 dBm, and includes an LNA to boost its Rx sensitivity a few dB beyond that of the SX1262.
However, its documentation is relatively sparse overall, and seems to merely hint at (or completely omit) several key details regarding its functionality. Thus, much of what follows is a synthesis of my observations and inferences over the course of many hours of trial and error.
### Acknowledgement and Friendly Disclaimer
<h3>Acknowledgement and friendly disclaimer</h3>
Huge thanks to those in the community who have forged the way with the E22, without whose hard work none of this would have been possible! (thebentern, riddick, rainer_vie, beegee-tokyo, geeksville, caveman99, Der_Bear, PlumRugOfDoom, BigCorvus, and many others.)
<br>
Please take the conclusions here as a tentative work in progress, representing my current (and fairly limited) understanding of the E22 when paired with this particular MCU. It is my hope that this guide will be helpful to others who are interested in trying a DIY Meshtastic build, and also be subject to revision by folks with more experience and better test equipment.
### Obligatory Liability Disclaimer
### Obligatory liability disclaimer
This guide and all associated content is for informational purposes only. The information presented is intended for consumption only by persons having appropriate technical skill and judgement, to be used entirely at their own discretion and risk. The authors of this guide in no way provide any warranty, express or implied, toward the content herein, nor its correctness, safety, or suitability to any particular purpose. By following the instructions in this guide in part or in full, you assume all responsibility for all potential risks, including but not limited to fire, property damage, bodily injury, and death.
## 1. Wire the board
### Note
Connecting the E22 to the XIAO nrf52840 is straightforward, but there are a few gotchas to be mindful of.
These instructions assume you are running macOS or Linux, but it should be relatively easy to translate each command for Windows. (In this case, in step 2 below, each line of `xiao_ble.sh` would also need to be converted to the equivalent Windows CLI command and run individually.)
### On the XIAO nrf52840
## 1. Update Bootloader
- Pins D4 and D5 are currently mapped to `PIN_WIRE_SDA` and `PIN_WIRE_SCL`, respectively. If you are not using I²C and would like to free up pins D4 and D5 for use as GPIO, `PIN_WIRE_SDA` and `PIN_WIRE_SCL` can be reassigned to any two other unused pin numbers.
- Pins D6 and D7 were originally mapped to the TX and RX pins for serial interface 1 (`PIN_SERIAL1_RX` and `PIN_SERIAL1_TX`) but are currently set to -1 in `variant.h`. If you need to expose a serial interface, you can restore these pins and move e.g. `SX126X_RXEN` to pin 4 or 5 (the opposite should work too).
The first thing you will need to do is update the Xiao BLE's bootloader. The stock bootloader is functionally very similar to the Adafruit nRF52 UF2 bootloader, but apparently not quite enough so to work with Meshtastic out of the box.
### On the E22
1. Connect the Xiao BLE to your computer via USB-C.
- There are two options for the E22's `TXEN` pin:
1. It can be connected to the MCU on the pin defined as `SX126X_TXEN` in `variant.h`. In this configuration, the MCU will control Tx/Rx switching "manually". As long as `SX126X_TXEN` and `SX126X_RXEN` are both defined in `variant.h` (and neither is set to `RADIOLIB_NC`), `SX126xInterface.cpp` will initialize the E22 correctly for this mode.
2. Alternately, it can be connected to the E22's `DIO2` pin only, with neither `TXEN` nor `DIO2` being connected to the MCU. In this configuration, the E22 will control Tx/Rx switching automatically. In `variant.h`, as long as `SX126X_TXEN` is defined as `RADIOLIB_NC`, and `SX126X_RXEN` is defined and connected to the E22's `RXEN` pin, and `E22_TXEN_CONNECTED_TO_DIO2` is defined, `SX126xInterface.cpp` will initialize the E22 correctly for this mode. This configuration frees up a GPIO, and presents no drawbacks that I have found.
- Note that any combination other than the two described above will likely result in unexpected behavior. In my testing, some of these other configurations appeared to "work" at first glance, but every one I tried had at least one of the following flaws: weak Tx power, extremely poor Rx sensitivity, or the E22 overheating because TXEN was never pulled low, causing its PA to stay on indefinitely.
- Along the same lines, it is a good idea to check the E22's temperature frequently by lightly touching the shield. If you feel the shield getting hot (i.e. approaching uncomfortable to touch) near pins 1, 2, and 3, something is probably misconfigured; disconnect both the XIAO nrf52840 and E22 from power and double check wiring and pin mapping.
- Whether you opt to let the E22 control Rx and Tx or handle this manually, **the E22's `RXEN` pin must always be connected to the MCU** on the pin defined as `SX126X_RXEN` in `variant.h`.
2. Install `adafruit-nrfutil` by following the instructions <a href="https://github.com/adafruit/Adafruit_nRF52_nrfutil">here</a>.
#### Note
3. Open a terminal window and navigate to `firmware/variants/xiao_ble` (where `firmware` is the directory into which you have cloned the <a href="https://github.com/meshtastic/firmware">Meshtastic firmware repo</a>).
The default pin mapping in `variant.h` uses "Automatic Tx/Rx switching" mode.
4. Run the following command, replacing `/dev/cu.usbmodem2101` with the serial port your Xiao BLE is connected to:
If you wire your board for Manual Tx/Rx Switching Mode, `SX126X_TXEN` must be defined (`#define #define SX126X_TXEN D6`) in `variants/seeed_xiao_nrf52840_kit/variant.h` in the code block following:
```bash
adafruit-nrfutil --verbose dfu serial --package xiao_nrf52840_ble_bootloader-0.7.0-22-g277a0c8_s140_7.3.0.zip --port /dev/cu.usbmodem2101 -b 115200 --singlebank --touch 1200
```
```c
#ifdef XIAO_BLE_LEGACY_PINOUT
// Legacy xiao_ble variant pinout for third-party SX126x modules e.g. EBYTE E22
```
5. If all goes well, the Xiao BLE's red LED should start to pulse slowly, and you should see a new USB storage device called `XIAO-BOOT` appear under `Locations` in Finder.
### Example Wiring for Automatic Tx/Rx Switching Mode
&nbsp;
#### MCU -> E22 Connections
## 2. PlatformIO Environment Preparation
| XIAO nrf52840 pin | variant.h definition | E22 pin | Notes |
| :---------------- | :------------------- | :-------- | :------------------------------------------------------------------------------------------------------------------- |
| D0 | SX126X_CS | 19 (NSS) | |
| D1 | SX126X_DIO1 | 13 (DIO1) | |
| D2 | SX126X_BUSY | 14 (BUSY) | |
| D3 | SX126X_RESET | 15 (NRST) | |
| D7 | SX126X_RXEN | 6 (RXEN) | These pins must still be connected, and `SX126X_RXEN` defined in `variant.h`, otherwise Rx sensitivity will be poor. |
| D8 | PIN_SPI_SCK | 18 (SCK) | |
| D9 | PIN_SPI_MISO | 16 (MISO) | |
| D10 | PIN_SPI_MOSI | 17 (MOSI) | |
Before building Meshtastic for the Xiao BLE + E22, it is necessary to pull in SoftDevice 7.3.0 and its associated linker script (nrf52840_s140_v7.ld) from Seeed Studio's Arduino core. The `xiao_ble.sh` script does this.
#### E22 -> E22 Connections
1. In your terminal window, run the following command:
```bash
sudo ./xiao_ble.sh
```
&nbsp;
## 3. Build Meshtastic
At this point, you should be able to build the firmware successfully.
1. In VS Code, press `Command Shift P` to bring up the command palette.
2. Search for and run the `Developer: Reload Window` command.
3. Bring up the command palette again with `Command Shift P`. Search for and run the `PlatformIO: Pick Project Environment` command.
4. In the list of environments, select `env:xiao_ble`. PlatformIO may update itself for a minute or two, and should let you know once done.
5. Return to the command palette once again (`Command Shift P`). Search for and run the `PlatformIO: Build` command.
6. PlatformIO will build the project. After a few minutes you should see a green `SUCCESS` message.
&nbsp;
## 4. Wire the board
Connecting the E22 to the Xiao BLE is straightforward, but there are a few gotchas to be mindful of.
- <strong>On the Xiao BLE:</strong>
- Pins D4 and D5 are currently mapped to `PIN_WIRE_SDA` and `PIN_WIRE_SCL`, respectively. If you are not using I²C and would like to free up pins D4 and D5 for use as GPIO, `PIN_WIRE_SDA` and `PIN_WIRE_SCL` can be reassigned to any two other unused pin numbers.
- Pins D6 and D7 were originally mapped to the TX and RX pins for serial interface 1 (`PIN_SERIAL1_RX` and `PIN_SERIAL1_TX`) but are currently set to -1 in `variant.h`. If you need to expose a serial interface, you can restore these pins and move e.g. `SX126X_RXEN` to pin 4 or 5 (the opposite should work too).
- <strong>On the E22:</strong>
- There are two options for the E22's `TXEN` pin:
1. It can be connected to the MCU on the pin defined as `SX126X_TXEN` in `variant.h`. In this configuration, the MCU will control Tx/Rx switching "manually". As long as `SX126X_TXEN` and `SX126X_RXEN` are both defined in `variant.h` (and neither is set to `RADIOLIB_NC`), `SX126xInterface.cpp` will initialize the E22 correctly for this mode.
2. Alternately, it can be connected to the E22's `DIO2` pin only, with neither `TXEN` nor `DIO2` being connected to the MCU. In this configuration, the E22 will control Tx/Rx switching automatically. In `variant.h`, as long as `SX126X_TXEN` is defined as `RADIOLIB_NC`, and `SX126X_RXEN` is defined and connected to the E22's `RXEN` pin, and `E22_TXEN_CONNECTED_TO_DIO2` is defined, `SX126xInterface.cpp` will initialize the E22 correctly for this mode. This configuration frees up a GPIO, and presents no drawbacks that I have found.
- Note that any combination other than the two described above will likely result in unexpected behavior. In my testing, some of these other configurations appeared to "work" at first glance, but every one I tried had at least one of the following flaws: weak Tx power, extremely poor Rx sensitivity, or the E22 overheating because TXEN was never pulled low, causing its PA to stay on indefinitely.
- Along the same lines, it is a good idea to check the E22's temperature frequently by lightly touching the shield. If you feel the shield getting hot (i.e. approaching uncomfortable to touch) near pins 1, 2, and 3, something is probably misconfigured; disconnect both the Xiao BLE and E22 from power and double check wiring and pin mapping.
- Whether you opt to let the E22 control Rx and Tx or handle this manually, <strong>the E22's `RXEN` pin must always be connected to the MCU</strong> on the pin defined as `SX126X_RXEN` in `variant.h`.
<h3>Note</h3>
The default pin mapping in `variant.h` uses 'automatic Tx/Rx switching' mode. If you wire your board for manual Rx/Tx switching, make sure to update `variant.h` accordingly by commenting/uncommenting the necessary lines in the 'E22 Tx/Rx control options' section.
&nbsp;
---
&nbsp;
<h3>Example wiring for "E22 automatic Tx/Rx switching" mode:</h3>
&nbsp;
<strong>MCU -> E22 connections</strong>
| Xiao BLE pin | variant.h definition | E22 pin | Notes |
| :----------- | :------------------- | :-------- | :------------------------------------------------------------------------------------------------------------------- |
| D0 | SX126X_CS | 19 (NSS) | |
| D1 | SX126X_DIO1 | 13 (DIO1) | |
| D2 | SX126X_BUSY | 14 (BUSY) | |
| D3 | SX126X_RESET | 15 (NRST) | |
| D7 | SX126X_RXEN | 6 (RXEN) | These pins must still be connected, and `SX126X_RXEN` defined in `variant.h`, otherwise Rx sensitivity will be poor. |
| D8 | PIN_SPI_SCK | 18 (SCK) | |
| D9 | PIN_SPI_MISO | 16 (MISO) | |
| D10 | PIN_SPI_MOSI | 17 (MOSI) | |
&nbsp;
&nbsp;
<strong>E22 -> E22 connections:</strong>
| E22 pin | E22 pin | Notes |
| :------ | :------ | :------------------------------------------------------------------------ |
| TXEN | DIO2 | These must be physically connected for automatic Tx/Rx switching to work. |
#### Note
<h3>Note</h3>
The schematic (`xiao-ble-e22-schematic.png`) in the `eagle-project` directory uses this wiring.
### Example Wiring for Manual Tx/Rx Switching Mode
&nbsp;
#### MCU -> E22 Connections
---
| XIAO nrf52840 pin | variant.h definition | E22 pin | Notes |
| :---------------- | :------------------- | :-------- | :---- |
| D0 | SX126X_CS | 19 (NSS) | |
| D1 | SX126X_DIO1 | 13 (DIO1) | |
| D2 | SX126X_BUSY | 14 (BUSY) | |
| D3 | SX126X_RESET | 15 (NRST) | |
| D6 | SX126X_TXEN | 7 (TXEN) | |
| D7 | SX126X_RXEN | 6 (RXEN) | |
| D8 | PIN_SPI_SCK | 18 (SCK) | |
| D9 | PIN_SPI_MISO | 16 (MISO) | |
| D10 | PIN_SPI_MOSI | 17 (MOSI) | |
&nbsp;
#### E22 -> E22 connections
<h3>Example wiring for "Manual Tx/Rx switching" mode:</h3>
_(none)_
<strong>MCU -> E22 connections</strong>
## 2. Build Meshtastic
| Xiao BLE pin | variant.h definition | E22 pin | Notes |
| :----------- | :------------------- | :-------- | :---- |
| D0 | SX126X_CS | 19 (NSS) | |
| D1 | SX126X_DIO1 | 13 (DIO1) | |
| D2 | SX126X_BUSY | 14 (BUSY) | |
| D3 | SX126X_RESET | 15 (NRST) | |
| D6 | SX126X_TXEN | 7 (TXEN) | |
| D7 | SX126X_RXEN | 6 (RXEN) | |
| D8 | PIN_SPI_SCK | 18 (SCK) | |
| D9 | PIN_SPI_MISO | 16 (MISO) | |
| D10 | PIN_SPI_MOSI | 17 (MOSI) | |
1. Follow the [Building Meshtastic Firmware](https://meshtastic.org/docs/development/firmware/build/) documentation, stop after **Build****Step 2**
2. For **Build****Step 3**, select `xiao_ble` as your target
3. Adjust source code if you:
- Wired your board for Manual Tx/Rx Switching Mode: see [Wire the Board](#1-wire-the-board)
- Used an E22-900M33S module
(this step is important to avoid **damaging the power amplifier** in the M33S module and **transmitting power above legal limits**!):
1. Open `variants/diy/platformio.ini`
2. Search for `[env:xiao_ble]`
3. In the line starting with `build_flags` within this section, change `-DEBYTE_E22_900M30S` to `-DEBYTE_E22_900M33S`
4. Follow **Build****Step 4** to build the firmware
5. Stop here, because the **PlatformIO: Upload** step does not work for factory-fresh XIAO nrf52840 (the automatic reset to bootloader only works if Meshtastic firmware is already running)
6. The built `firmware.uf2` binary can be found in the folder `.pio/build/xiao_ble/firmware.uf2` (relative to where you cloned the Git repository to), we will need it for [flashing the firmware](#3-flash-the-firmware-to-the-xiao-nrf52840) (manually)
<strong>E22 -> E22 connections:</strong> (none)
## 3. Flash the Firmware to the XIAO nrf52840
&nbsp;
1. Double press the XIAO nrf52840's `reset` button to put it in bootloader mode, and a USB volume named `XIAO SENSE` will appear
2. Copy the `firmware.uf2` file to the `XIAO SENSE` volume (refer to the last step of [Build Meshtastic](#2-build-meshtastic))
3. The XIAO nrf52840's red LED will flash for several seconds as the firmware is copied
4. Once Meshtastic firmware succesfully boots, the:
1. Green LED will turn on
2. Red LED will flash several times to indicate flash memory writes during initial settings file creation
3. Green LED will blink every second once the firmware is running normally
5. If you do not see the above LED patters, proceed to [Troubleshooting](#4-troubleshooting)
## 5. Flash the firmware to the Xiao BLE
## 4. Troubleshooting
1. Double press the Xiao's `reset` button to put it in bootloader mode.
2. In a terminal window, navigate to the Meshtastic firmware repo's root directory, and from there to `.pio/build/xiao_ble`.
3. Convert the generated `.hex` file into a `.uf2` file:
```bash
../../../bin/uf2conv.py firmware.hex -c -o firmware.uf2 -f 0xADA52840
```
4. Copy the new `.uf2` file to the Xiao's mass storage volume:
```bash
cp firmware.uf2 /Volumes/XIAO-BOOT
```
5. The Xiao's red LED will flash for several seconds as the firmware is copied.
6. Once the firmware is copied, to verify it is running, run the following command:
```bash
meshtastic --noproto
```
7. Then, press the Xiao's `reset` button again. You should see a lot of debug output logged in the terminal window.
&nbsp;
## 6. Troubleshooting
- If after flashing Meshtastic, the Xiao is bootlooped, look at the serial output (you can see this by running `meshtastic --noproto` with the device connected to your computer via USB).
- If after flashing Meshtastic, the XIAO is bootlooped, look at the serial output (you can see this by running `meshtastic --noproto` with the device connected to your computer via USB).
- If you see that the SX1262 init result was -2, this likely indicates a wiring problem; double check your wiring and pin mapping in `variant.h`.
- If you see an error mentioning tinyFS, this may mean you need to reformat the XIAO's storage:
1. Open the [Meshtastic web flasher](https://flasher.meshtastic.org/)
2. Select the **_Seeed XIAO NRF52840 Kit_**
3. Click the **_trash can icon_** to the right of **_Flash_**
4. Follow the instructions on the screen
**Do not flash the Seeed XIAO NRF52840 Kit firmware** if you have wired the LoRa module according to this variant, as the Seeed XIAO NRF52840 Kit uses different wiring for the SX1262 LoRa chip
- If you see an error mentioning tinyFS, this may mean you need to reformat the Xiao's storage:
1. Double press the `reset` button to put the Xiao in bootloader mode.
2. In a terminal window, navigate to the Meshtastic firmware repo's root directory, and from there to `variants/xiao_ble`.
3. Run the following command: &nbsp;`cp xiao-ble-internal-format.uf2 /Volumes/XIAO-BOOT`
4. The Xiao's red LED will flash briefly as the filesystem format firmware is copied.
5. Run the following command: &nbsp;`meshtastic --noproto`
6. In the output of the above command, you should see a message saying "Formatting...done".
7. To flash Meshtastic again, repeat the steps in section 5 above.
- If you don't see any specific error message, but the boot process is stuck or not proceeding as expected, this might also mean there is a conflict in `variant.h`. If you have made any changes to the pin mapping, ensure they do not result in a conflict. If all else fails, try reverting your changes and using the known-good configuration included here.
- The above might also mean something is wired incorrectly. Try reverting to one of the known-good example wirings in section 4.
- If the E22 gets hot to the touch:
- The power amplifier is likely running continually. Disconnect it and the XIAO from power immediately, and double check wiring and pin mapping. In my experimentation this occurred in cases where TXEN was inadvertenly high (usually due to a pin mapping conflict).
- The power amplifier is likely running continually. Disconnect it and the Xiao from power immediately, and double check wiring and pin mapping. In my experimentation this occurred in cases where TXEN was inadvertenly high (usually due to a pin mapping conflict).
## 5. Notes
&nbsp;
- **Transmit Power**
- There is a power amplifier after the SX1262's Tx, so the actual Tx power is just over 7 dB greater than the SX1262's set Tx power (the E22-900M30S actually tops out just over 29dB at 5V according to the datasheet)
- Meshtastic firmware is aware of the gain of the E22-900M30S module, so the Meshtastic clients' Tx power setting reflects the actual output power, i.e. setting 30 dBm in the Meshtastic app programs the E22 module to correctly output 30 dBm, setting 24 dBm will output 24 dBm, etc.
- **Adequate 5V Power Supply to the E22 Module**
- Have a bypass capacitor from its 5V supply to ground; 100 µF works well
- Voltage must be between 5V5.5V, lower supply voltage results in less output power; for example, with a fully charged LiPo at 4.2V, Tx power appears to max out around 26-27 dBm
## 7. Notes
### Additional Reading
- There are several anecdotal recommendations regarding the Tx power the E22's internal SX1262 should be set to in order to achieve the advertised output of 30 dBm, ranging from 4 (per <a href="https://github.com/jgromes/RadioLib/wiki/High-power-Radio-Modules-Guide">this article</a> in the RadioLib github repo) to 22 (per <a href="https://discord.com/channels/867578229534359593/871539930852130866/976472577545490482">this conversation</a> from the Meshtastic Discord). When paired with the Xiao BLE in the configurations described above, I observed that the output is at its maximum when Tx power is set to 22.
- [S5NC/CDEBYTE_Modules](https://github.com/S5NC/CDEBYTE_Modules) has additional information about EBYTE E22 modules' internal workings, including photographs
- [RadioLib High power Radio Modules Guide](https://github.com/jgromes/RadioLib/wiki/High-power-Radio-Modules-Guide)
- To achieve its full output, the E22 should have a bypass capacitor from its 5V supply to ground. 100 µF works well.
## 6. Testing Methodology
- The E22 will happily run on voltages lower than 5V, but the full output power will not be realized. For example, with a fully charged LiPo at 4.2V, Tx power appears to max out around 26-27 dBm.
&nbsp;
## 8. Testing Methodology
During what became a fairly long trial-and-error process, I did a lot of careful testing of Tx power and Rx sensitivity. My methodology in these tests was as follows:
- All tests were conducted between two nodes:
1. The XIAO nrf52840 + E22 coupled with an [Abracon ARRKP4065-S915A](https://www.digikey.com/en/products/detail/abracon-llc/ARRKP4065-S915A/8593263") ceramic patch antenna
2. A RAK 5005/4631 coupled with a [Laird MA9-5N](https://www.streakwave.com/laird-technologies-ma9-5n-55dbi-900mhz-mobile-omni-select-mount) antenna via a 4" U.FL to Type N pigtail.
1. The Xiao BLE + E22 coupled with an <a href="https://www.digikey.com/en/products/detail/abracon-llc/ARRKP4065-S915A/8593263">Abracon ARRKP4065-S915A</a> ceramic patch antenna
2. A RAK 5005/4631 coupled with a <a href="https://www.streakwave.com/laird-technologies-ma9-5n-55dbi-900mhz-mobile-omni-select-mount">Laird MA9-5N</a> antenna via a 4" U.FL to Type N pigtail.
- No other nodes were powered up onsite or nearby.
<br>
- Each node and its antenna was kept in exactly the same position and orientation throughout testing.
- Other environmental factors (e.g. the location and resting position of my body in the room while testing) were controlled as carefully as possible.
- Each test comprised at least five (and often ten) runs, after which the results were averaged.
- All testing was done by sending single-character messages between nodes and observing the received RSSI reported in the message acknowledgement. Messages were sent one by one, waiting for each to be acknowledged or time out before sending the next.
- The E22's Tx power was observed by sending messages from the RAK to the XIAO nrf52840 + E22 and recording the received RSSI.
- The opposite was done to observe the E22's Rx sensitivity: messages were sent from the XIAO nrf52840 + E22 to the RAK, and the received RSSI was recorded.
While this cannot match the level of accuracy achievable with actual test equipment in a lab setting, it was nonetheless sufficient to demonstrate the (sometimes very large) differences in Tx power and Rx sensitivity between various configurations.
- The E22's Tx power was observed by sending messages from the RAK to the Xiao BLE + E22 and recording the received RSSI.
- The opposite was done to observe the E22's Rx sensitivity: messages were sent from the Xiao BLE + E22 to the RAK, and the received RSSI was recorded.
While this cannot match the level of accuracy achievable with actual test equipment in a lab setting, it was nonetheless sufficient to demonstrate the (sometimes very large) differences in Tx power and Rx sensitivity between various configurations.

View File

@@ -2,7 +2,7 @@
extends = esp32c3_base
board = esp32-c3-devkitm-1
build_flags =
${esp32c3_base.build_flags}
${esp32_base.build_flags}
-D HELTEC_HT62
-I variants/heltec_esp32c3
monitor_speed = 115200

View File

@@ -1,35 +0,0 @@
#pragma once
#include "configuration.h"
enum class EInkDetectionResult : uint8_t {
LCMEN213EFC1 = 0, // Initial version
E0213A367 = 1, // E213 PCB marked V1.1 (Mid 2025)
};
EInkDetectionResult detectEInk()
{
// Test 1: Logic of BUSY pin
// Determines controller IC manufacturer
// Fitipower: busy when LOW
// Solomon Systech: busy when HIGH
// Force display BUSY by holding reset pin active
pinMode(PIN_EINK_RES, OUTPUT);
digitalWrite(PIN_EINK_RES, LOW);
delay(10);
// Read whether pin is HIGH or LOW while busy
pinMode(PIN_EINK_BUSY, INPUT);
bool busyLogic = digitalRead(PIN_EINK_BUSY);
// Test complete. Release pin
pinMode(PIN_EINK_RES, INPUT);
if (busyLogic == LOW)
return EInkDetectionResult::LCMEN213EFC1;
else // busy HIGH
return EInkDetectionResult::E0213A367;
}

View File

@@ -18,22 +18,16 @@
// Shared NicheGraphics components
// --------------------------------
#include "graphics/niche/Drivers/EInk/E0213A367.h"
#include "graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h"
#include "graphics/niche/Inputs/TwoButton.h"
#include "buzz.h" // Button feedback
#include "einkDetect.h" // Detect display model at runtime
// Button feedback
#include "buzz.h"
void setupNicheGraphics()
{
using namespace NicheGraphics;
// Detect E-Ink Model
// -------------------
EInkDetectionResult displayModel = detectEInk();
// SPI
// -----------------------------
@@ -44,13 +38,7 @@ void setupNicheGraphics()
// E-Ink Driver
// -----------------------------
Drivers::EInk *driver;
if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1 (unmarked)
driver = new Drivers::LCMEN213EFC1;
else if (displayModel == EInkDetectionResult::E0213A367) // V1.1
driver = new Drivers::E0213A367;
Drivers::EInk *driver = new Drivers::LCMEN213EFC1;
driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
// InkHUD
@@ -63,11 +51,7 @@ void setupNicheGraphics()
// Set how many FAST updates per FULL update
// Set how unhealthy additional FAST updates beyond this number are
if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1 (unmarked)
inkhud->setDisplayResilience(10, 1.5);
else if (displayModel == EInkDetectionResult::E0213A367) // V1.1
inkhud->setDisplayResilience(15, 3);
inkhud->setDisplayResilience(10, 1.5);
// Select fonts
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
@@ -107,7 +91,7 @@ void setupNicheGraphics()
buttons->setWiring(1, PIN_BUTTON2);
buttons->setHandlerShortPress(1, [inkhud]() {
inkhud->nextTile();
playChirp();
playBoop();
});
// Begin handling button events

View File

@@ -7,8 +7,7 @@ build_flags =
-Ivariants/heltec_vision_master_e213
-DHELTEC_VISION_MASTER_E213
-DUSE_EINK
-DGXEPD2_DRIVER_0=GxEPD2_213_FC1
-DGXEPD2_DRIVER_1=GxEPD2_213_E0213A367
-DEINK_DISPLAY_MODEL=GxEPD2_213_FC1
-DEINK_WIDTH=250
-DEINK_HEIGHT=122
-DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
@@ -17,7 +16,7 @@ build_flags =
-DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
lib_deps =
${esp32s3_base.lib_deps}
https://github.com/meshtastic/GxEPD2/archive/1655054ba298e0e29fc2044741940f927f9c2a43.zip
https://github.com/meshtastic/GxEPD2/archive/b202ebfec6a4821e098cf7a625ba0f6f2400292d.zip
lewisxhe/PCF8563_Library@^1.0.1
upload_speed = 115200

View File

@@ -104,7 +104,7 @@ void setupNicheGraphics()
buttons->setWiring(1, PIN_BUTTON2);
buttons->setHandlerShortPress(1, [inkhud]() {
inkhud->nextTile();
playChirp();
playBoop();
});
// Begin handling button events

View File

@@ -1,35 +0,0 @@
#pragma once
#include "configuration.h"
enum class EInkDetectionResult : uint8_t {
LCMEN213EFC1 = 0, // V1.1
E0213A367 = 1, // V1.1.1, V1.2
};
EInkDetectionResult detectEInk()
{
// Test 1: Logic of BUSY pin
// Determines controller IC manufacturer
// Fitipower: busy when LOW
// Solomon Systech: busy when HIGH
// Force display BUSY by holding reset pin active
pinMode(PIN_EINK_RES, OUTPUT);
digitalWrite(PIN_EINK_RES, LOW);
delay(10);
// Read whether pin is HIGH or LOW while busy
pinMode(PIN_EINK_BUSY, INPUT);
bool busyLogic = digitalRead(PIN_EINK_BUSY);
// Test complete. Release pin
pinMode(PIN_EINK_RES, INPUT);
if (busyLogic == LOW)
return EInkDetectionResult::LCMEN213EFC1;
else // busy HIGH
return EInkDetectionResult::E0213A367;
}

View File

@@ -18,21 +18,13 @@
// Shared NicheGraphics components
// --------------------------------
#include "graphics/niche/Drivers/EInk/E0213A367.h"
#include "graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h"
#include "graphics/niche/Inputs/TwoButton.h"
#include "einkDetect.h" // Detect display model at runtime
void setupNicheGraphics()
{
using namespace NicheGraphics;
// Detect E-Ink Model
// -------------------
EInkDetectionResult displayModel = detectEInk();
// SPI
// -----------------------------
@@ -43,13 +35,7 @@ void setupNicheGraphics()
// E-Ink Driver
// -----------------------------
Drivers::EInk *driver;
if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1.1
driver = new Drivers::LCMEN213EFC1;
else if (displayModel == EInkDetectionResult::E0213A367) // V1.1.1, V1.2
driver = new Drivers::E0213A367;
Drivers::EInk *driver = new Drivers::LCMEN213EFC1;
driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
// InkHUD
@@ -62,11 +48,7 @@ void setupNicheGraphics()
// Set how many FAST updates per FULL update
// Set how unhealthy additional FAST updates beyond this number are
if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1.1 (unmarked)
inkhud->setDisplayResilience(10, 1.5);
else if (displayModel == EInkDetectionResult::E0213A367) // V1.1.1, V1.2
inkhud->setDisplayResilience(15, 3);
inkhud->setDisplayResilience(10, 1.5);
// Select fonts
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;

View File

@@ -7,8 +7,7 @@ build_flags =
${esp32s3_base.build_flags}
-I variants/heltec_wireless_paper
-D HELTEC_WIRELESS_PAPER
-D GXEPD2_DRIVER_0=GxEPD2_213_FC1
-D GXEPD2_DRIVER_1=GxEPD2_213_E0213A367
-D EINK_DISPLAY_MODEL=GxEPD2_213_FC1
-D EINK_WIDTH=250
-D EINK_HEIGHT=122
-D USE_EINK
@@ -18,7 +17,7 @@ build_flags =
-D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
lib_deps =
${esp32s3_base.lib_deps}
https://github.com/meshtastic/GxEPD2/archive/1655054ba298e0e29fc2044741940f927f9c2a43.zip
https://github.com/meshtastic/GxEPD2/archive/b202ebfec6a4821e098cf7a625ba0f6f2400292d.zip
lewisxhe/PCF8563_Library@^1.0.1
upload_speed = 115200

View File

@@ -3,7 +3,7 @@ extends = esp32c3_base
board = esp32-c3-devkitm-1
board_level = extra
build_flags =
${esp32c3_base.build_flags}
${esp32_base.build_flags}
-D PRIVATE_HW
-I variants/m5stack-stamp-c3
monitor_speed = 115200

View File

@@ -11,15 +11,15 @@ upload_speed = 921600
platform_packages =
platformio/tool-esptoolpy@^1.40801.0
lib_deps =
${esp32s3_base.lib_deps}
${esp32_base.lib_deps}
zinggjm/GxEPD2@^1.6.2
adafruit/Adafruit NeoPixel @ ^1.12.0
build_unflags =
${esp32s3_base.build_unflags}
-DARDUINO_USB_MODE=1
build_flags =
;${esp32s3_base.build_flags} -D MY_ESP32S3_DIY -I variants/my_esp32s3_diy_eink
${esp32s3_base.build_flags} -D PRIVATE_HW -I variants/my_esp32s3_diy_eink
;${esp32_base.build_flags} -D MY_ESP32S3_DIY -I variants/my_esp32s3_diy_eink
${esp32_base.build_flags} -D PRIVATE_HW -I variants/my_esp32s3_diy_eink
-Dmy
-DEINK_DISPLAY_MODEL=GxEPD2_290_T5D
-DEINK_WIDTH=296

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