mirror of
https://github.com/meshtastic/firmware.git
synced 2026-01-12 21:07:19 +00:00
Compare commits
22 Commits
indicator-
...
event/open
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1324faa68c | ||
|
|
856ab293dc | ||
|
|
6165449cee | ||
|
|
3896086469 | ||
|
|
67ff575279 | ||
|
|
f6d378255c | ||
|
|
19d831d20d | ||
|
|
00495140bd | ||
|
|
354f149338 | ||
|
|
999e1207a5 | ||
|
|
916587c2a6 | ||
|
|
db4e4e6e53 | ||
|
|
9c08220d24 | ||
|
|
88b299dd41 | ||
|
|
19af2d9e3b | ||
|
|
fa23be4424 | ||
|
|
e1f40c2db9 | ||
|
|
415dc4aa47 | ||
|
|
f2fb473ecf | ||
|
|
f95c77b8bd | ||
|
|
09d4ee1ea7 | ||
|
|
40c586ca97 |
35
.github/workflows/build_esp32.yml
vendored
35
.github/workflows/build_esp32.yml
vendored
@@ -11,27 +11,30 @@ permissions: read-all
|
||||
|
||||
jobs:
|
||||
build-esp32:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
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: ./.github/actions/build-variant
|
||||
uses: meshtastic/gh-action-firmware@main
|
||||
with:
|
||||
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: |
|
||||
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: |
|
||||
release/*.bin
|
||||
release/*.elf
|
||||
#include-web-ui: true
|
||||
arch: esp32
|
||||
|
||||
35
.github/workflows/build_esp32_c3.yml
vendored
35
.github/workflows/build_esp32_c3.yml
vendored
@@ -11,27 +11,30 @@ permissions: read-all
|
||||
|
||||
jobs:
|
||||
build-esp32-c3:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
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: ./.github/actions/build-variant
|
||||
uses: meshtastic/gh-action-firmware@main
|
||||
with:
|
||||
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: |
|
||||
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: |
|
||||
release/*.bin
|
||||
release/*.elf
|
||||
#include-web-ui: true
|
||||
arch: esp32c3
|
||||
|
||||
35
.github/workflows/build_esp32_c6.yml
vendored
35
.github/workflows/build_esp32_c6.yml
vendored
@@ -11,27 +11,30 @@ permissions: read-all
|
||||
|
||||
jobs:
|
||||
build-esp32-c6:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
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: ./.github/actions/build-variant
|
||||
uses: meshtastic/gh-action-firmware@main
|
||||
with:
|
||||
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: |
|
||||
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: |
|
||||
release/*.bin
|
||||
release/*.elf
|
||||
#include-web-ui: true
|
||||
arch: esp32c6
|
||||
|
||||
35
.github/workflows/build_esp32_s3.yml
vendored
35
.github/workflows/build_esp32_s3.yml
vendored
@@ -11,27 +11,30 @@ permissions: read-all
|
||||
|
||||
jobs:
|
||||
build-esp32-s3:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
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: ./.github/actions/build-variant
|
||||
uses: meshtastic/gh-action-firmware@main
|
||||
with:
|
||||
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: |
|
||||
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: |
|
||||
release/*.bin
|
||||
release/*.elf
|
||||
#include-web-ui: true
|
||||
arch: esp32s3
|
||||
|
||||
28
.github/workflows/build_nrf52.yml
vendored
28
.github/workflows/build_nrf52.yml
vendored
@@ -11,20 +11,30 @@ permissions: read-all
|
||||
|
||||
jobs:
|
||||
build-nrf52:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
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: ./.github/actions/build-variant
|
||||
uses: meshtastic/gh-action-firmware@main
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
board: ${{ inputs.board }}
|
||||
build-script-path: bin/build-nrf52.sh
|
||||
artifact-paths: |
|
||||
release/*.hex
|
||||
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: |
|
||||
release/*.uf2
|
||||
release/*.elf
|
||||
release/*.zip
|
||||
arch: nrf52840
|
||||
release/*.hex
|
||||
release/*-ota.zip
|
||||
|
||||
24
.github/workflows/build_rpi2040.yml
vendored
24
.github/workflows/build_rpi2040.yml
vendored
@@ -11,18 +11,28 @@ permissions: read-all
|
||||
|
||||
jobs:
|
||||
build-rpi2040:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
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: ./.github/actions/build-variant
|
||||
uses: meshtastic/gh-action-firmware@main
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
board: ${{ inputs.board }}
|
||||
build-script-path: bin/build-rpi2040.sh
|
||||
artifact-paths: |
|
||||
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: |
|
||||
release/*.uf2
|
||||
release/*.elf
|
||||
arch: rp2040
|
||||
|
||||
24
.github/workflows/build_stm32.yml
vendored
24
.github/workflows/build_stm32.yml
vendored
@@ -11,19 +11,29 @@ permissions: read-all
|
||||
|
||||
jobs:
|
||||
build-stm32:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
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: ./.github/actions/build-variant
|
||||
uses: meshtastic/gh-action-firmware@main
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
board: ${{ inputs.board }}
|
||||
build-script-path: bin/build-stm32.sh
|
||||
artifact-paths: |
|
||||
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: |
|
||||
release/*.hex
|
||||
release/*.bin
|
||||
release/*.elf
|
||||
arch: stm32
|
||||
|
||||
3
.github/workflows/main_matrix.yml
vendored
3
.github/workflows/main_matrix.yml
vendored
@@ -135,6 +135,7 @@ jobs:
|
||||
board: ${{ matrix.board }}
|
||||
|
||||
build-debian-src:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
uses: ./.github/workflows/build_debian_src.yml
|
||||
with:
|
||||
series: UNRELEASED
|
||||
@@ -425,7 +426,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
publish-firmware:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
needs: [release-firmware]
|
||||
env:
|
||||
|
||||
2
.github/workflows/nightly.yml
vendored
2
.github/workflows/nightly.yml
vendored
@@ -8,6 +8,7 @@ permissions: read-all
|
||||
|
||||
jobs:
|
||||
trunk_check:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
name: Trunk Check and Upload
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
@@ -21,6 +22,7 @@ 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
|
||||
|
||||
1
.github/workflows/sec_sast_semgrep_cron.yml
vendored
1
.github/workflows/sec_sast_semgrep_cron.yml
vendored
@@ -13,6 +13,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
semgrep-full:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
runs-on: ubuntu-24.04
|
||||
container:
|
||||
image: semgrep/semgrep
|
||||
|
||||
1
.github/workflows/stale_bot.yml
vendored
1
.github/workflows/stale_bot.yml
vendored
@@ -11,6 +11,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
stale_issues:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
name: Close Stale Issues
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -12,9 +12,11 @@ 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
|
||||
|
||||
@@ -9,14 +9,14 @@ plugins:
|
||||
lint:
|
||||
enabled:
|
||||
- checkov@3.2.447
|
||||
- renovate@41.17.2
|
||||
- renovate@41.23.4
|
||||
- prettier@3.6.2
|
||||
- trufflehog@3.89.2
|
||||
- yamllint@1.37.1
|
||||
- bandit@1.8.5
|
||||
- trivy@0.64.0
|
||||
- bandit@1.8.6
|
||||
- trivy@0.64.1
|
||||
- taplo@0.9.3
|
||||
- ruff@0.12.1
|
||||
- ruff@0.12.2
|
||||
- isort@6.0.1
|
||||
- markdownlint@0.45.0
|
||||
- oxipng@9.1.5
|
||||
|
||||
@@ -7,12 +7,7 @@ MCU=""
|
||||
|
||||
# Variant groups
|
||||
BIGDB_8MB=(
|
||||
# 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"
|
||||
"picomputer-s3"
|
||||
"unphone"
|
||||
"seeed-sensecap-indicator"
|
||||
"crowpanel-esp32s3"
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"hwids": [
|
||||
["0x239A", "0x4405"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"]
|
||||
["0x239A", "0x002A"],
|
||||
["0x2886", "0x1667"]
|
||||
],
|
||||
"usb_product": "HT-n5262",
|
||||
"mcu": "nrf52840",
|
||||
|
||||
BIN
branding/logo_320x240.png
Normal file
BIN
branding/logo_320x240.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
branding/logo_320x480.png
Normal file
BIN
branding/logo_320x480.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
branding/logo_480x320.png
Normal file
BIN
branding/logo_480x320.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
branding/logo_480x480.png
Normal file
BIN
branding/logo_480x480.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
branding/logo_800x480.png
Normal file
BIN
branding/logo_800x480.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
@@ -40,9 +40,7 @@ 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(SENSECAP_INDICATOR)
|
||||
FakeUART *GPS::_serial_gps = FakeSerial;
|
||||
#elif defined(RAK2560)
|
||||
#if defined(RAK2560)
|
||||
HardwareSerial *GPS::_serial_gps = &Serial2;
|
||||
#else
|
||||
HardwareSerial *GPS::_serial_gps = &Serial1;
|
||||
@@ -1078,7 +1076,6 @@ 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");
|
||||
@@ -1094,7 +1091,6 @@ int32_t GPS::runOnce()
|
||||
GPSInitFinished = true;
|
||||
publishUpdate();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Repeaters have no need for GPS
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
|
||||
@@ -1413,10 +1409,8 @@ 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;
|
||||
@@ -1542,7 +1536,10 @@ 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());
|
||||
perhapsSetRTC(RTCQualityGPS, t);
|
||||
if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultInvalidTime) {
|
||||
// Clear the GPS buffer if we got an invalid time
|
||||
clearBuffer();
|
||||
}
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
|
||||
@@ -11,10 +11,6 @@
|
||||
#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
|
||||
@@ -192,9 +188,7 @@ 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(SENSECAP_INDICATOR)
|
||||
static FakeUART *_serial_gps;
|
||||
#elif defined(ARCH_RP2040)
|
||||
#if defined(ARCH_RP2040)
|
||||
static SerialUART *_serial_gps;
|
||||
#else
|
||||
static HardwareSerial *_serial_gps;
|
||||
|
||||
@@ -105,7 +105,7 @@ void readFromRTC()
|
||||
*
|
||||
* If we haven't yet set our RTC this boot, set it from a GPS derived time
|
||||
*/
|
||||
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
|
||||
RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
|
||||
{
|
||||
static uint32_t lastSetMsec = 0;
|
||||
uint32_t now = millis();
|
||||
@@ -113,7 +113,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
|
||||
#ifdef BUILD_EPOCH
|
||||
if (tv->tv_sec < BUILD_EPOCH) {
|
||||
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
|
||||
return false;
|
||||
return RTCSetResultInvalidTime;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -184,9 +184,9 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
|
||||
readFromRTC();
|
||||
#endif
|
||||
|
||||
return true;
|
||||
return RTCSetResultSuccess;
|
||||
} else {
|
||||
return false;
|
||||
return RTCSetResultNotSet; // RTC was already set with a higher quality time
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
bool perhapsSetRTC(RTCQuality q, struct tm &t)
|
||||
RTCSetResult 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 @@ bool 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 false;
|
||||
return RTCSetResultInvalidTime;
|
||||
} else {
|
||||
return perhapsSetRTC(q, &tv);
|
||||
}
|
||||
|
||||
@@ -22,13 +22,22 @@ 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
|
||||
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false);
|
||||
bool perhapsSetRTC(RTCQuality q, struct tm &t);
|
||||
RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false);
|
||||
RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t);
|
||||
|
||||
/// Return a string name for the quality
|
||||
const char *RtcName(RTCQuality quality);
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
#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.
|
||||
@@ -174,9 +178,8 @@ bool EInkDisplay::connect()
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) || \
|
||||
defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) || \
|
||||
defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER)
|
||||
#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)
|
||||
{
|
||||
// Start HSPI
|
||||
hspi = new SPIClass(HSPI);
|
||||
@@ -232,6 +235,23 @@ 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;
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
#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.
|
||||
*
|
||||
@@ -63,8 +67,15 @@ class EInkDisplay : public OLEDDisplay
|
||||
// Connect to the display
|
||||
virtual bool connect() override;
|
||||
|
||||
// AdafruitGFX display object - instantiated in connect(), variant specific
|
||||
#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
|
||||
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) || \
|
||||
|
||||
135
src/graphics/GxEPD2Multi.h
Normal file
135
src/graphics/GxEPD2Multi.h
Normal file
@@ -0,0 +1,135 @@
|
||||
// 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;
|
||||
};
|
||||
@@ -294,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 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);
|
||||
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);
|
||||
|
||||
if (r <= 255 && g <= 255 && b <= 255) {
|
||||
TFT_MESH = COLOR565(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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,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);
|
||||
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
|
||||
#endif
|
||||
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
|
||||
#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);
|
||||
@@ -944,22 +944,6 @@ 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;
|
||||
@@ -969,7 +953,7 @@ void Screen::setFrames(FrameFocus focus)
|
||||
#endif
|
||||
|
||||
// Beware of what changes you make in this code!
|
||||
// We pass numfames into GetMeshModulesWithUIFrames() which is highly important!
|
||||
// We pass numframes 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.
|
||||
@@ -998,6 +982,34 @@ 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);
|
||||
@@ -1009,8 +1021,7 @@ 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) {
|
||||
|
||||
@@ -136,6 +136,7 @@ void menuHandler::ClockFacePicker()
|
||||
screen->setFrames(Screen::FOCUS_CLOCK);
|
||||
}
|
||||
};
|
||||
bannerOptions.InitialSelected = uiconfig.is_clockface_analog ? 2 : 1;
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
}
|
||||
|
||||
@@ -337,8 +338,8 @@ void menuHandler::homeBaseMenu()
|
||||
} else if (selected == Freetext) {
|
||||
cannedMessageModule->LaunchFreetextWithDestination(NODENUM_BROADCAST);
|
||||
} else if (selected == Bluetooth) {
|
||||
InputEvent event = {.inputEvent = (input_broker_event)170, .kbchar = 170, .touchX = 0, .touchY = 0};
|
||||
inputBroker->injectInputEvent(&event);
|
||||
menuQueue = bluetooth_toggle_menu;
|
||||
screen->runNow();
|
||||
}
|
||||
};
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
@@ -358,6 +359,9 @@ void menuHandler::systemBaseMenu()
|
||||
static int optionsEnumArray[7] = {Back};
|
||||
int options = 1;
|
||||
|
||||
optionsArray[options] = "Reboot";
|
||||
optionsEnumArray[options++] = Reboot;
|
||||
|
||||
optionsArray[options] = "Beeps Action";
|
||||
optionsEnumArray[options++] = Beeps;
|
||||
|
||||
@@ -366,9 +370,6 @@ void menuHandler::systemBaseMenu()
|
||||
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;
|
||||
@@ -587,6 +588,23 @@ 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"};
|
||||
@@ -677,52 +695,52 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = 10;
|
||||
bannerOptions.bannerCallback = [display](int selected) -> void {
|
||||
uint8_t r = 0;
|
||||
uint8_t g = 0;
|
||||
uint8_t b = 0;
|
||||
uint8_t TFT_MESH_r = 0;
|
||||
uint8_t TFT_MESH_g = 0;
|
||||
uint8_t TFT_MESH_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");
|
||||
r = 103;
|
||||
g = 234;
|
||||
b = 148;
|
||||
TFT_MESH_r = 103;
|
||||
TFT_MESH_g = 234;
|
||||
TFT_MESH_b = 148;
|
||||
} else if (selected == 3) {
|
||||
LOG_INFO("Setting color to Yellow");
|
||||
r = 255;
|
||||
g = 255;
|
||||
b = 128;
|
||||
TFT_MESH_r = 255;
|
||||
TFT_MESH_g = 255;
|
||||
TFT_MESH_b = 128;
|
||||
} else if (selected == 4) {
|
||||
LOG_INFO("Setting color to Red");
|
||||
r = 255;
|
||||
g = 64;
|
||||
b = 64;
|
||||
TFT_MESH_r = 255;
|
||||
TFT_MESH_g = 64;
|
||||
TFT_MESH_b = 64;
|
||||
} else if (selected == 5) {
|
||||
LOG_INFO("Setting color to Orange");
|
||||
r = 255;
|
||||
g = 160;
|
||||
b = 20;
|
||||
TFT_MESH_r = 255;
|
||||
TFT_MESH_g = 160;
|
||||
TFT_MESH_b = 20;
|
||||
} else if (selected == 6) {
|
||||
LOG_INFO("Setting color to Purple");
|
||||
r = 204;
|
||||
g = 153;
|
||||
b = 255;
|
||||
TFT_MESH_r = 204;
|
||||
TFT_MESH_g = 153;
|
||||
TFT_MESH_b = 255;
|
||||
} else if (selected == 7) {
|
||||
LOG_INFO("Setting color to Teal");
|
||||
r = 64;
|
||||
g = 224;
|
||||
b = 208;
|
||||
TFT_MESH_r = 64;
|
||||
TFT_MESH_g = 224;
|
||||
TFT_MESH_b = 208;
|
||||
} else if (selected == 8) {
|
||||
LOG_INFO("Setting color to Pink");
|
||||
r = 255;
|
||||
g = 105;
|
||||
b = 180;
|
||||
TFT_MESH_r = 255;
|
||||
TFT_MESH_g = 105;
|
||||
TFT_MESH_b = 180;
|
||||
} else if (selected == 9) {
|
||||
LOG_INFO("Setting color to White");
|
||||
r = 255;
|
||||
g = 255;
|
||||
b = 255;
|
||||
TFT_MESH_r = 255;
|
||||
TFT_MESH_g = 255;
|
||||
TFT_MESH_b = 255;
|
||||
}
|
||||
|
||||
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
|
||||
@@ -731,14 +749,14 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
|
||||
display->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
display->setColor(WHITE);
|
||||
|
||||
if (r == 0 && g == 0 && b == 0) {
|
||||
if (TFT_MESH_r == 0 && TFT_MESH_g == 0 && TFT_MESH_b == 0) {
|
||||
#ifdef TFT_MESH_OVERRIDE
|
||||
TFT_MESH = TFT_MESH_OVERRIDE;
|
||||
#else
|
||||
TFT_MESH = COLOR565(0x67, 0xEA, 0x94);
|
||||
#endif
|
||||
} else {
|
||||
TFT_MESH = COLOR565(r, g, b);
|
||||
TFT_MESH = COLOR565(TFT_MESH_r, TFT_MESH_g, TFT_MESH_b);
|
||||
}
|
||||
|
||||
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190)
|
||||
@@ -746,10 +764,10 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
|
||||
#endif
|
||||
|
||||
screen->setFrames(graphics::Screen::FOCUS_SYSTEM);
|
||||
if (r == 0 && g == 0 && b == 0) {
|
||||
if (TFT_MESH_r == 0 && TFT_MESH_g == 0 && TFT_MESH_b == 0) {
|
||||
uiconfig.screen_rgb_color = 0;
|
||||
} else {
|
||||
uiconfig.screen_rgb_color = (r << 16) | (g << 8) | b;
|
||||
uiconfig.screen_rgb_color = (TFT_MESH_r << 16) | (TFT_MESH_g << 8) | TFT_MESH_b;
|
||||
}
|
||||
LOG_INFO("Storing Value of %d to uiconfig.screen_rgb_color", uiconfig.screen_rgb_color);
|
||||
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
|
||||
@@ -935,6 +953,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
|
||||
case wifi_toggle_menu:
|
||||
wifiToggleMenu();
|
||||
break;
|
||||
case bluetooth_toggle_menu:
|
||||
BluetoothToggleMenu();
|
||||
break;
|
||||
}
|
||||
menuQueue = menu_none;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,8 @@ class menuHandler
|
||||
remove_favorite,
|
||||
test_menu,
|
||||
number_test,
|
||||
wifi_toggle_menu
|
||||
wifi_toggle_menu,
|
||||
bluetooth_toggle_menu
|
||||
};
|
||||
static screenMenus menuQueue;
|
||||
|
||||
@@ -55,6 +56,7 @@ class menuHandler
|
||||
static void numberTest();
|
||||
static void wifiBaseMenu();
|
||||
static void wifiToggleMenu();
|
||||
static void BluetoothToggleMenu();
|
||||
};
|
||||
|
||||
} // namespace graphics
|
||||
@@ -42,7 +42,7 @@ uint32_t NotificationRenderer::currentNumber = 0;
|
||||
uint32_t pow_of_10(uint32_t n)
|
||||
{
|
||||
uint32_t ret = 1;
|
||||
for (int i = 0; i < n; i++) {
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
ret *= 10;
|
||||
}
|
||||
return ret;
|
||||
@@ -80,6 +80,9 @@ void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayU
|
||||
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);
|
||||
@@ -144,12 +147,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 (int i = 0; i < lineCount; i++) {
|
||||
for (uint16_t i = 0; i < lineCount; i++) {
|
||||
linePointers[i] = lineStarts[i];
|
||||
}
|
||||
std::string digits = " ";
|
||||
std::string arrowPointer = " ";
|
||||
for (int i = 0; i < numDigits; i++) {
|
||||
for (uint16_t 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) {
|
||||
|
||||
84
src/graphics/niche/Drivers/EInk/E0213A367.cpp
Normal file
84
src/graphics/niche/Drivers/EInk/E0213A367.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#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
|
||||
41
src/graphics/niche/Drivers/EInk/E0213A367.h
Normal file
41
src/graphics/niche/Drivers/EInk/E0213A367.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
|
||||
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
|
||||
41
src/graphics/niche/Drivers/EInk/SSD1682.cpp
Normal file
41
src/graphics/niche/Drivers/EInk/SSD1682.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#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
|
||||
31
src/graphics/niche/Drivers/EInk/SSD1682.h
Normal file
31
src/graphics/niche/Drivers/EInk/SSD1682.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
|
||||
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
|
||||
24
src/main.cpp
24
src/main.cpp
@@ -22,11 +22,6 @@
|
||||
#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>
|
||||
@@ -462,11 +457,7 @@ void setup()
|
||||
fsInit();
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_I2C
|
||||
// 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)
|
||||
#if defined(I2C_SDA1) && defined(ARCH_RP2040)
|
||||
Wire1.setSDA(I2C_SDA1);
|
||||
Wire1.setSCL(I2C_SCL1);
|
||||
Wire1.begin();
|
||||
@@ -524,9 +515,7 @@ void setup()
|
||||
LOG_INFO("Scan for i2c devices");
|
||||
#endif
|
||||
|
||||
#if defined(SENSECAP_INDICATOR)
|
||||
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1);
|
||||
#elif defined(I2C_SDA1) && defined(ARCH_RP2040)
|
||||
#if defined(I2C_SDA1) && defined(ARCH_RP2040)
|
||||
Wire1.setSDA(I2C_SDA1);
|
||||
Wire1.setSCL(I2C_SCL1);
|
||||
Wire1.begin();
|
||||
@@ -759,15 +748,6 @@ 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,
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
#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
|
||||
@@ -1,47 +0,0 @@
|
||||
#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
|
||||
@@ -369,6 +369,14 @@ 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)))
|
||||
|
||||
@@ -246,8 +246,10 @@ 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
|
||||
@@ -275,7 +277,9 @@ 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
|
||||
}
|
||||
|
||||
|
||||
@@ -645,10 +645,6 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
// 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
|
||||
@@ -1,38 +0,0 @@
|
||||
// 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
|
||||
@@ -1,97 +0,0 @@
|
||||
#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
|
||||
@@ -1,44 +0,0 @@
|
||||
#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
|
||||
@@ -193,6 +193,10 @@ 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
|
||||
@@ -232,6 +236,9 @@ 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
|
||||
@@ -533,6 +540,10 @@ 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;
|
||||
|
||||
166
src/modules/Telemetry/Sensor/IndicatorSensor.cpp
Normal file
166
src/modules/Telemetry/Sensor/IndicatorSensor.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
#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
|
||||
19
src/modules/Telemetry/Sensor/IndicatorSensor.h
Normal file
19
src/modules/Telemetry/Sensor/IndicatorSensor.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#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
|
||||
129
src/serialization/cobs.cpp
Normal file
129
src/serialization/cobs.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#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
|
||||
75
src/serialization/cobs.h
Normal file
75
src/serialization/cobs.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#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_ */
|
||||
@@ -1,15 +1,15 @@
|
||||
{
|
||||
// "USERPREFS_BUTTON_PIN": "36",
|
||||
// "USERPREFS_CHANNELS_TO_WRITE": "3",
|
||||
"USERPREFS_CHANNELS_TO_WRITE": "2",
|
||||
// "USERPREFS_CHANNEL_0_DOWNLINK_ENABLED": "false",
|
||||
// "USERPREFS_CHANNEL_0_NAME": "REPLACEME",
|
||||
"USERPREFS_CHANNEL_0_NAME": "Sauce",
|
||||
// "USERPREFS_CHANNEL_0_PRECISION": "14",
|
||||
// "USERPREFS_CHANNEL_0_PSK": "{ 0x38, 0x4b, 0xbc, 0xc0, 0x1d, 0xc0, 0x22, 0xd1, 0x81, 0xbf, 0x36, 0xb8, 0x61, 0x21, 0xe1, 0xfb, 0x96, 0xb7, 0x2e, 0x55, 0xbf, 0x74, 0x22, 0x7e, 0x9d, 0x6a, 0xfb, 0x48, 0xd6, 0x4c, 0xb1, 0xa1 }",
|
||||
// "USERPREFS_CHANNEL_0_UPLINK_ENABLED": "true",
|
||||
"USERPREFS_CHANNEL_0_PSK": "{ 0x38, 0x4b, 0xbc, 0xc0, 0x1d, 0xc0, 0x22, 0xd1, 0x81, 0xbf, 0x36, 0xb8, 0x61, 0x21, 0xe1, 0xfb, 0x96, 0xb7, 0x2e, 0x55, 0xbf, 0x74, 0x22, 0x7e, 0x9d, 0x6a, 0xfb, 0x48, 0xd6, 0x4c, 0xb1, 0xa1 }",
|
||||
"USERPREFS_CHANNEL_0_UPLINK_ENABLED": "true",
|
||||
// "USERPREFS_CHANNEL_1_DOWNLINK_ENABLED": "false",
|
||||
// "USERPREFS_CHANNEL_1_NAME": "NodeChat",
|
||||
"USERPREFS_CHANNEL_1_NAME": "Exhibits",
|
||||
// "USERPREFS_CHANNEL_1_PRECISION": "14",
|
||||
// "USERPREFS_CHANNEL_1_PSK": "{ 0x4e, 0x22, 0x1d, 0x8b, 0xc3, 0x09, 0x1b, 0xe2, 0x11, 0x9c, 0x89, 0x12, 0xf2, 0x25, 0x19, 0x5d, 0x15, 0x3e, 0x30, 0x7b, 0x86, 0xb6, 0xec, 0xc4, 0x6a, 0xc3, 0x96, 0x5e, 0x9e, 0x10, 0x9d, 0xd5 }",
|
||||
"USERPREFS_CHANNEL_1_PSK": "{ 0x4e, 0x22, 0x1d, 0x8b, 0xc3, 0x09, 0x1b, 0xe2, 0x11, 0x9c, 0x89, 0x12, 0xf2, 0x25, 0x19, 0x5d, 0x15, 0x3e, 0x30, 0x7b, 0x86, 0xb6, 0xec, 0xc4, 0x6a, 0xc3, 0x96, 0x5e, 0x9e, 0x10, 0x9d, 0xd5 }",
|
||||
// "USERPREFS_CHANNEL_1_UPLINK_ENABLED": "false",
|
||||
// "USERPREFS_CHANNEL_2_DOWNLINK_ENABLED": "false",
|
||||
// "USERPREFS_CHANNEL_2_NAME": "YardSale",
|
||||
@@ -17,12 +17,12 @@
|
||||
// "USERPREFS_CHANNEL_2_PSK": "{ 0x15, 0x6f, 0xfe, 0x46, 0xd4, 0x56, 0x63, 0x8a, 0x54, 0x43, 0x13, 0xf2, 0xef, 0x6c, 0x63, 0x89, 0xf0, 0x06, 0x30, 0x52, 0xce, 0x36, 0x5e, 0xb1, 0xe8, 0xbb, 0x86, 0xe6, 0x26, 0x5b, 0x1d, 0x58 }",
|
||||
// "USERPREFS_CHANNEL_2_UPLINK_ENABLED": "false",
|
||||
// "USERPREFS_CONFIG_GPS_MODE": "meshtastic_Config_PositionConfig_GpsMode_ENABLED",
|
||||
// "USERPREFS_CONFIG_LORA_IGNORE_MQTT": "true",
|
||||
"USERPREFS_CONFIG_LORA_IGNORE_MQTT": "true",
|
||||
// "USERPREFS_CONFIG_LORA_REGION": "meshtastic_Config_LoRaConfig_RegionCode_US",
|
||||
// "USERPREFS_CONFIG_OWNER_LONG_NAME": "My Long Name",
|
||||
// "USERPREFS_CONFIG_OWNER_SHORT_NAME": "MLN",
|
||||
// "USERPREFS_CONFIG_DEVICE_ROLE": "meshtastic_Config_DeviceConfig_Role_CLIENT", // Defaults to CLIENT. ROUTER*, LOST AND FOUND, and REPEATER roles are restricted.
|
||||
// "USERPREFS_EVENT_MODE": "1",
|
||||
"USERPREFS_EVENT_MODE": "1",
|
||||
// "USERPREFS_FIXED_BLUETOOTH": "121212",
|
||||
// "USERPREFS_FIXED_GPS": "",
|
||||
// "USERPREFS_FIXED_GPS_ALT": "0",
|
||||
@@ -32,16 +32,16 @@
|
||||
// "USERPREFS_CONFIG_GPS_UPDATE_INTERVAL": "600",
|
||||
// "USERPREFS_CONFIG_POSITION_BROADCAST_INTERVAL": "1800",
|
||||
// "USERPREFS_CONFIG_DEVICE_TELEM_UPDATE_INTERVAL": "900", // Device telemetry update interval in seconds
|
||||
// "USERPREFS_LORACONFIG_CHANNEL_NUM": "31",
|
||||
// "USERPREFS_LORACONFIG_MODEM_PRESET": "meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST",
|
||||
"USERPREFS_LORACONFIG_CHANNEL_NUM": "31",
|
||||
"USERPREFS_LORACONFIG_MODEM_PRESET": "meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO",
|
||||
// "USERPREFS_USE_ADMIN_KEY_0": "{ 0xcd, 0xc0, 0xb4, 0x3c, 0x53, 0x24, 0xdf, 0x13, 0xca, 0x5a, 0xa6, 0x0c, 0x0d, 0xec, 0x85, 0x5a, 0x4c, 0xf6, 0x1a, 0x96, 0x04, 0x1a, 0x3e, 0xfc, 0xbb, 0x8e, 0x33, 0x71, 0xe5, 0xfc, 0xff, 0x3c }",
|
||||
// "USERPREFS_USE_ADMIN_KEY_1": "{}",
|
||||
// "USERPREFS_USE_ADMIN_KEY_2": "{}",
|
||||
// "USERPREFS_OEM_TEXT": "Caterham Car Club",
|
||||
// "USERPREFS_OEM_FONT_SIZE": "0",
|
||||
// "USERPREFS_OEM_IMAGE_WIDTH": "50",
|
||||
// "USERPREFS_OEM_IMAGE_HEIGHT": "28",
|
||||
// "USERPREFS_OEM_IMAGE_DATA": "{ 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00, 0xC0, 0x07, 0x80, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xC7, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xC7, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0x67, 0x00, 0x00, 0x00, 0x18, 0x1F, 0xF0, 0x67, 0x00, 0x00, 0x00, 0x30, 0x1F, 0xF8, 0x33, 0x00, 0x00, 0x00, 0x30, 0x00, 0xFC, 0x31, 0x00, 0x00, 0x00, 0x60, 0x00, 0xFE, 0x18, 0x00, 0x00, 0x00, 0x60, 0x00, 0x7E, 0x18, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x3F, 0x0C, 0x00, 0x00, 0x00, 0xC0, 0x80, 0x1F, 0x0C, 0x00, 0x00, 0x00, 0x80, 0x81, 0x1F, 0x06, 0x00, 0x00, 0x00, 0x80, 0xC1, 0x0F, 0x06, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x0F, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x0F, 0x03, 0x00, 0x00, 0x00, 0x00, 0xE6, 0x8F, 0x01, 0x00, 0x00, 0x00, 0x00, 0xEE, 0xC7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x00}",
|
||||
"USERPREFS_OEM_TEXT": "OpenSauce 2025",
|
||||
"USERPREFS_OEM_FONT_SIZE": "0",
|
||||
"USERPREFS_OEM_IMAGE_WIDTH": "49",
|
||||
"USERPREFS_OEM_IMAGE_HEIGHT": "65",
|
||||
"USERPREFS_OEM_IMAGE_DATA": "{ 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0xfe, 0xc0, 0x03, 0x00, 0x00, 0x80, 0x07, 0xfe, 0x00, 0x3f, 0x00, 0x00, 0xf8, 0x01, 0xfe, 0x00, 0x38, 0x00, 0x00, 0x38, 0x00, 0xfe, 0x00, 0x30, 0x00, 0x00, 0x18, 0x00, 0xfe, 0x00, 0x70, 0x00, 0x00, 0x1c, 0x00, 0xfe, 0x00, 0xe0, 0x03, 0x81, 0x0f, 0x00, 0xfe, 0x00, 0x00, 0x8c, 0x43, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0xfe, 0x00, 0x80, 0x07, 0xc1, 0x03, 0x00, 0xfe, 0x00, 0xf0, 0x01, 0x01, 0x1f, 0x00, 0xfe, 0x00, 0x70, 0x00, 0x01, 0x1c, 0x00, 0xfe, 0x00, 0x30, 0x80, 0x03, 0x18, 0x00, 0xfe, 0x00, 0x3c, 0xf0, 0x0f, 0x78, 0x00, 0xfe, 0x80, 0x0f, 0xf8, 0x3f, 0xe0, 0x03, 0xfe, 0xe0, 0x00, 0xfc, 0x7f, 0x00, 0x0e, 0xfe, 0x00, 0x00, 0xfe, 0xff, 0x01, 0x00, 0xfe, 0x00, 0xf0, 0xff, 0xff, 0x1f, 0x00, 0xfe, 0x00, 0xff, 0xff, 0xff, 0xff, 0x03, 0xfe, 0xf8, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, 0xfc, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, 0xc1, 0xff, 0xff, 0xff, 0xff, 0x87, 0xff, 0x1f, 0xfc, 0xff, 0xff, 0x7f, 0xf0, 0xff, 0x9f, 0xc1, 0xff, 0xff, 0x07, 0x6f, 0xff, 0xbb, 0x07, 0xfc, 0x7f, 0xf0, 0xef, 0xff, 0xb3, 0xef, 0xc1, 0x0f, 0xdf, 0xfe, 0xfe, 0xb3, 0xed, 0x1b, 0xf0, 0xdb, 0x8f, 0xff, 0xb3, 0xed, 0x7b, 0xbc, 0xfb, 0xe0, 0xff, 0xb3, 0x6d, 0xf8, 0xb2, 0x7f, 0xd9, 0xff, 0xb3, 0x6d, 0xd8, 0xfe, 0x70, 0xbc, 0xff, 0xb3, 0x6d, 0xd8, 0x0e, 0x56, 0x7e, 0xff, 0xb3, 0x6f, 0xd8, 0x80, 0x76, 0x72, 0xff, 0xb3, 0x6f, 0xd8, 0xee, 0x75, 0x63, 0xff, 0xb3, 0xef, 0xd9, 0xf6, 0x73, 0x61, 0xff, 0xb3, 0xe1, 0xdb, 0x96, 0x53, 0x61, 0xff, 0xb3, 0xe1, 0xdb, 0x16, 0x77, 0x63, 0xff, 0xb3, 0x61, 0xd8, 0x0e, 0x77, 0x62, 0xff, 0xbf, 0x61, 0xd8, 0x0e, 0x77, 0xb6, 0xff, 0xbf, 0x61, 0xd8, 0x0e, 0x57, 0xbd, 0xff, 0x9c, 0x61, 0xd8, 0x16, 0x57, 0xc3, 0xff, 0x83, 0x61, 0xd8, 0x16, 0x73, 0xff, 0xff, 0x8f, 0xe0, 0xd8, 0xb6, 0x33, 0xff, 0xff, 0xef, 0xe0, 0xdb, 0xee, 0xcd, 0xff, 0xff, 0xeb, 0xc5, 0xdb, 0x1e, 0xfe, 0xc7, 0xff, 0xeb, 0x2d, 0xd8, 0xfe, 0xff, 0xb0, 0xff, 0x6b, 0x2d, 0xc3, 0xfe, 0x1f, 0xa4, 0xff, 0x63, 0xad, 0x07, 0xfe, 0x83, 0xa4, 0xff, 0x66, 0xad, 0xf7, 0x7e, 0x88, 0xa4, 0xff, 0xec, 0xad, 0xf5, 0x0e, 0x89, 0xa4, 0xff, 0xeb, 0xad, 0xb5, 0x0e, 0x89, 0xa4, 0xff, 0xeb, 0xad, 0x35, 0x1e, 0x89, 0x9c, 0xff, 0x6b, 0xad, 0x35, 0x1e, 0x89, 0xc7, 0xff, 0x6f, 0xad, 0x31, 0x1e, 0xf9, 0xf8, 0xff, 0x6f, 0xad, 0x71, 0x1e, 0x1d, 0xff, 0xfe, 0x6c, 0xad, 0xf1, 0xbe, 0xe7, 0x7f, 0xfe, 0x60, 0xad, 0xf5, 0xee, 0xfc, 0x1f, 0xfe, 0x00, 0xbd, 0x35, 0x1e, 0xff, 0x03, 0xfe, 0x00, 0xb8, 0x35, 0xfe, 0x7f, 0x00, 0xfe, 0x00, 0xb0, 0x37, 0xfe, 0x1f, 0x00, 0xfe, 0x00, 0x00, 0x37, 0xfe, 0x03, 0x00, 0xfe, 0x00, 0x00, 0x76, 0xfe, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xf0, 0x1e, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x80, 0x02, 0x00, 0x00, 0xfe }",
|
||||
// "USERPREFS_NETWORK_ENABLED_PROTOCOLS": "1", // Enable UDP mesh
|
||||
// "USERPREFS_NETWORK_WIFI_ENABLED": "true",
|
||||
// "USERPREFS_NETWORK_WIFI_SSID": "wifi_ssid",
|
||||
@@ -52,6 +52,6 @@
|
||||
// "USERPREFS_MQTT_PASSWORD": "large4cats",
|
||||
// "USERPREFS_MQTT_ENCRYPTION_ENABLED": "true",
|
||||
// "USERPREFS_MQTT_TLS_ENABLED": "false",
|
||||
// "USERPREFS_MQTT_ROOT_TOPIC": "event/REPLACEME",
|
||||
"USERPREFS_TZ_STRING": "tzplaceholder "
|
||||
"USERPREFS_MQTT_ROOT_TOPIC": "event/OpenSauce",
|
||||
"USERPREFS_TZ_STRING": "PST8PDT,M3.2.0,M11.1.0"
|
||||
}
|
||||
|
||||
@@ -61,8 +61,9 @@ void setupNicheGraphics()
|
||||
InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
|
||||
|
||||
// Customize default settings
|
||||
inkhud->persistence->settings.userTiles.maxCount = 2; // Two applets side-by-side
|
||||
inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery
|
||||
inkhud->persistence->settings.userTiles.maxCount = 2; // Two applets side-by-side
|
||||
inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery
|
||||
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
|
||||
|
||||
// Setup backlight controller
|
||||
// Note: button is attached further down
|
||||
@@ -70,14 +71,16 @@ void setupNicheGraphics()
|
||||
backlight->setPin(PIN_EINK_EN);
|
||||
|
||||
// Pick applets
|
||||
// Note: order of applets determines priority of "auto-show" feature
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0
|
||||
// Custom selection for OpenSauce
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
|
||||
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
|
||||
// Disabled by default
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
|
||||
|
||||
// Start running InkHUD
|
||||
inkhud->begin();
|
||||
|
||||
@@ -65,16 +65,19 @@ void setupNicheGraphics()
|
||||
inkhud->persistence->settings.rotation = (driver->height > driver->width ? 1 : 0); // Rotate 90deg to landscape, if needed
|
||||
inkhud->persistence->settings.userTiles.maxCount = 4;
|
||||
inkhud->persistence->settings.optionalFeatures.batteryIcon = true;
|
||||
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
|
||||
|
||||
// Pick applets
|
||||
// Note: order of applets determines priority of "auto-show" feature
|
||||
// Custom selection for OpenSauce
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
|
||||
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
|
||||
// Disabled by default
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, false, 3); // Default on tile 3
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 2); // Default on tile 2
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1));
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true, false, 1); // Default on tile 1
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet, true, false, 0); // Default on tile 0
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true); // Background
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
|
||||
|
||||
// Start running InkHUD
|
||||
inkhud->begin();
|
||||
|
||||
@@ -66,16 +66,20 @@ void setupNicheGraphics()
|
||||
inkhud->persistence->settings.rotation = (driver->height > driver->width ? 1 : 0); // Rotate 90deg to landscape, if needed
|
||||
inkhud->persistence->settings.userTiles.maxCount = 4;
|
||||
inkhud->persistence->settings.optionalFeatures.batteryIcon = true;
|
||||
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
|
||||
|
||||
// Pick applets
|
||||
// Note: order of applets determines priority of "auto-show" feature
|
||||
// Custom selection for OpenSauce
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
|
||||
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
|
||||
// Disabled by default
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, false, 3); // Default on tile 3
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 2); // Default on tile 2
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1));
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true, false, 1); // Default on tile 1
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet, true, false, 0); // Default on tile 0
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true); // Background
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
|
||||
// Background
|
||||
|
||||
// Start running InkHUD
|
||||
inkhud->begin();
|
||||
|
||||
@@ -59,16 +59,19 @@ void setupNicheGraphics()
|
||||
inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
|
||||
inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
|
||||
inkhud->persistence->settings.optionalMenuItems.nextTile = true;
|
||||
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
|
||||
|
||||
// Pick applets
|
||||
// Note: order of applets determines priority of "auto-show" feature
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0
|
||||
// Custom selection for OpenSauce
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
|
||||
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
|
||||
// Disabled by default
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
|
||||
|
||||
// Start running InkHUD
|
||||
inkhud->begin();
|
||||
|
||||
35
variants/heltec_vision_master_e213/einkDetect.h
Normal file
35
variants/heltec_vision_master_e213/einkDetect.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#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;
|
||||
}
|
||||
@@ -18,16 +18,22 @@
|
||||
|
||||
// Shared NicheGraphics components
|
||||
// --------------------------------
|
||||
#include "graphics/niche/Drivers/EInk/E0213A367.h"
|
||||
#include "graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h"
|
||||
#include "graphics/niche/Inputs/TwoButton.h"
|
||||
|
||||
// Button feedback
|
||||
#include "buzz.h"
|
||||
#include "buzz.h" // Button feedback
|
||||
#include "einkDetect.h" // Detect display model at runtime
|
||||
|
||||
void setupNicheGraphics()
|
||||
{
|
||||
using namespace NicheGraphics;
|
||||
|
||||
// Detect E-Ink Model
|
||||
// -------------------
|
||||
|
||||
EInkDetectionResult displayModel = detectEInk();
|
||||
|
||||
// SPI
|
||||
// -----------------------------
|
||||
|
||||
@@ -38,7 +44,13 @@ void setupNicheGraphics()
|
||||
// E-Ink Driver
|
||||
// -----------------------------
|
||||
|
||||
Drivers::EInk *driver = new Drivers::LCMEN213EFC1;
|
||||
Drivers::EInk *driver;
|
||||
|
||||
if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1 (unmarked)
|
||||
driver = new Drivers::LCMEN213EFC1;
|
||||
else if (displayModel == EInkDetectionResult::E0213A367) // V1.1
|
||||
driver = new Drivers::E0213A367;
|
||||
|
||||
driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
|
||||
|
||||
// InkHUD
|
||||
@@ -51,7 +63,11 @@ void setupNicheGraphics()
|
||||
|
||||
// Set how many FAST updates per FULL update
|
||||
// Set how unhealthy additional FAST updates beyond this number are
|
||||
inkhud->setDisplayResilience(10, 1.5);
|
||||
|
||||
if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1 (unmarked)
|
||||
inkhud->setDisplayResilience(10, 1.5);
|
||||
else if (displayModel == EInkDetectionResult::E0213A367) // V1.1
|
||||
inkhud->setDisplayResilience(15, 3);
|
||||
|
||||
// Select fonts
|
||||
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
|
||||
@@ -62,17 +78,20 @@ void setupNicheGraphics()
|
||||
inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle?
|
||||
inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
|
||||
inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
|
||||
inkhud->persistence->settings.optionalMenuItems.nextTile = false; // Behavior handled by aux button instead
|
||||
inkhud->persistence->settings.optionalMenuItems.nextTile = false; // Behavior handled by aux button instead
|
||||
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
|
||||
|
||||
// Pick applets
|
||||
// Note: order of applets determines priority of "auto-show" feature
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0
|
||||
// Custom selection for OpenSauce
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
|
||||
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
|
||||
// Disabled by default
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
|
||||
|
||||
// Start running InkHUD
|
||||
inkhud->begin();
|
||||
|
||||
@@ -7,7 +7,8 @@ build_flags =
|
||||
-Ivariants/heltec_vision_master_e213
|
||||
-DHELTEC_VISION_MASTER_E213
|
||||
-DUSE_EINK
|
||||
-DEINK_DISPLAY_MODEL=GxEPD2_213_FC1
|
||||
-DGXEPD2_DRIVER_0=GxEPD2_213_FC1
|
||||
-DGXEPD2_DRIVER_1=GxEPD2_213_E0213A367
|
||||
-DEINK_WIDTH=250
|
||||
-DEINK_HEIGHT=122
|
||||
-DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
|
||||
@@ -16,7 +17,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/b202ebfec6a4821e098cf7a625ba0f6f2400292d.zip
|
||||
https://github.com/meshtastic/GxEPD2/archive/1655054ba298e0e29fc2044741940f927f9c2a43.zip
|
||||
lewisxhe/PCF8563_Library@^1.0.1
|
||||
upload_speed = 115200
|
||||
|
||||
|
||||
@@ -75,17 +75,20 @@ void setupNicheGraphics()
|
||||
inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle?
|
||||
inkhud->persistence->settings.rotation = 1; // 90 degrees clockwise
|
||||
inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
|
||||
inkhud->persistence->settings.optionalMenuItems.nextTile = false; // Behavior handled by aux button instead
|
||||
inkhud->persistence->settings.optionalMenuItems.nextTile = false; // Behavior handled by aux button instead
|
||||
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
|
||||
|
||||
// Pick applets
|
||||
// Note: order of applets determines priority of "auto-show" feature
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0
|
||||
// Custom selection for OpenSauce
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
|
||||
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
|
||||
// Disabled by default
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
|
||||
|
||||
// Start running InkHUD
|
||||
inkhud->begin();
|
||||
|
||||
35
variants/heltec_wireless_paper/einkDetect.h
Normal file
35
variants/heltec_wireless_paper/einkDetect.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#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;
|
||||
}
|
||||
@@ -18,13 +18,21 @@
|
||||
|
||||
// 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
|
||||
// -----------------------------
|
||||
|
||||
@@ -35,7 +43,13 @@ void setupNicheGraphics()
|
||||
// E-Ink Driver
|
||||
// -----------------------------
|
||||
|
||||
Drivers::EInk *driver = new Drivers::LCMEN213EFC1;
|
||||
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;
|
||||
|
||||
driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
|
||||
|
||||
// InkHUD
|
||||
@@ -48,7 +62,11 @@ void setupNicheGraphics()
|
||||
|
||||
// Set how many FAST updates per FULL update
|
||||
// Set how unhealthy additional FAST updates beyond this number are
|
||||
inkhud->setDisplayResilience(10, 1.5);
|
||||
|
||||
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);
|
||||
|
||||
// Select fonts
|
||||
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
|
||||
@@ -59,16 +77,18 @@ void setupNicheGraphics()
|
||||
inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle?
|
||||
inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
|
||||
inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
|
||||
|
||||
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
|
||||
// Pick applets
|
||||
// Note: order of applets determines priority of "auto-show" feature
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0
|
||||
// Custom selection for OpenSauce
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
|
||||
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
|
||||
// Disabled by default
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
|
||||
|
||||
// Start running InkHUD
|
||||
inkhud->begin();
|
||||
|
||||
@@ -7,7 +7,8 @@ build_flags =
|
||||
${esp32s3_base.build_flags}
|
||||
-I variants/heltec_wireless_paper
|
||||
-D HELTEC_WIRELESS_PAPER
|
||||
-D EINK_DISPLAY_MODEL=GxEPD2_213_FC1
|
||||
-D GXEPD2_DRIVER_0=GxEPD2_213_FC1
|
||||
-D GXEPD2_DRIVER_1=GxEPD2_213_E0213A367
|
||||
-D EINK_WIDTH=250
|
||||
-D EINK_HEIGHT=122
|
||||
-D USE_EINK
|
||||
@@ -17,7 +18,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/b202ebfec6a4821e098cf7a625ba0f6f2400292d.zip
|
||||
https://github.com/meshtastic/GxEPD2/archive/1655054ba298e0e29fc2044741940f927f9c2a43.zip
|
||||
lewisxhe/PCF8563_Library@^1.0.1
|
||||
upload_speed = 115200
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ board_check = true
|
||||
board_build.partitions = default_8MB.csv
|
||||
upload_protocol = esptool
|
||||
|
||||
build_flags = ${esp32s3_base.build_flags}
|
||||
build_flags = ${esp32_base.build_flags}
|
||||
-Ivariants/seeed-sensecap-indicator
|
||||
-DSENSECAP_INDICATOR
|
||||
-DCONFIG_ARDUHAL_LOG_COLORS
|
||||
@@ -27,7 +27,7 @@ lib_deps = ${esp32s3_base.lib_deps}
|
||||
https://github.com/mverch67/LovyanGFX/archive/4c76238c1344162a234ae917b27651af146d6fb2.zip
|
||||
earlephilhower/ESP8266Audio@^1.9.9
|
||||
earlephilhower/ESP8266SAM@^1.0.1
|
||||
locoduino/RingBuffer@^1.0.5
|
||||
|
||||
|
||||
[env:seeed-sensecap-indicator-tft]
|
||||
extends = env:seeed-sensecap-indicator
|
||||
|
||||
@@ -44,7 +44,13 @@
|
||||
#define TOUCH_I2C_PORT 0
|
||||
#define TOUCH_SLAVE_ADDRESS 0x48
|
||||
|
||||
// in future, we may want to add a buzzer and add all sensors to the indicator via a data protocol for now only GPS is supported
|
||||
// // Buzzer
|
||||
// #define PIN_BUZZER 19
|
||||
|
||||
#define GPS_DEFAULT_NOT_PRESENT 1
|
||||
#define GPS_RX_PIN 20
|
||||
#define GPS_TX_PIN 19
|
||||
#define HAS_GPS 1
|
||||
|
||||
#define USE_SX1262
|
||||
@@ -72,7 +78,3 @@
|
||||
|
||||
#define USE_VIRTUAL_KEYBOARD 1
|
||||
#define DISPLAY_CLOCK_FRAME 1
|
||||
|
||||
// this powers the RP 2040 on boot.
|
||||
#define SENSOR_POWER_CTRL_EXPANDER (8 | IO_EXPANDER)
|
||||
#define SENSOR_POWER_ON_EXPANDER 1
|
||||
|
||||
@@ -67,6 +67,7 @@ void setupNicheGraphics()
|
||||
inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery
|
||||
inkhud->persistence->settings.optionalMenuItems.backlight = true; // Until proves capacitive button works by touching it
|
||||
inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
|
||||
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
|
||||
|
||||
// Setup backlight controller
|
||||
// Note: AUX button attached further down
|
||||
@@ -74,14 +75,16 @@ void setupNicheGraphics()
|
||||
backlight->setPin(PIN_EINK_EN);
|
||||
|
||||
// Pick applets
|
||||
// Note: order of applets determines priority of "auto-show" feature
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0
|
||||
// Custom selection for OpenSauce
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
|
||||
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
|
||||
// Disabled by default
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
|
||||
|
||||
inkhud->persistence->settings.rotation = 1;
|
||||
// inkhud->persistence->printSettings(&inkhud->persistence->settings);
|
||||
|
||||
@@ -62,10 +62,11 @@ void setupNicheGraphics()
|
||||
InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
|
||||
|
||||
// Customize default settings
|
||||
inkhud->persistence->settings.userTiles.maxCount = 2; // Two applets side-by-side
|
||||
inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
|
||||
inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery
|
||||
inkhud->persistence->settings.optionalMenuItems.backlight = true; // Until proves capacitive button works by touching it
|
||||
inkhud->persistence->settings.userTiles.maxCount = 2; // Two applets side-by-side
|
||||
inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
|
||||
inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery
|
||||
inkhud->persistence->settings.optionalMenuItems.backlight = true; // Until proves capacitive button works by touching it
|
||||
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
|
||||
|
||||
// Setup backlight controller
|
||||
// Note: AUX button attached further down
|
||||
@@ -73,14 +74,16 @@ void setupNicheGraphics()
|
||||
backlight->setPin(PIN_EINK_EN);
|
||||
|
||||
// Pick applets
|
||||
// Note: order of applets determines priority of "auto-show" feature
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0
|
||||
// Custom selection for OpenSauce
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
|
||||
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
|
||||
// Disabled by default
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
|
||||
|
||||
// Start running InkHUD
|
||||
inkhud->begin();
|
||||
|
||||
@@ -59,16 +59,19 @@ void setupNicheGraphics()
|
||||
inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle?
|
||||
inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
|
||||
inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
|
||||
inkhud->persistence->settings.optionalFeatures.notifications = false; // No notifications. Busy mesh.
|
||||
|
||||
// Pick applets
|
||||
// Note: order of applets determines priority of "auto-show" feature
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet); // -
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // -
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // -
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0
|
||||
// Custom selection for OpenSauce
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 0); // Default tile 0
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), true);
|
||||
inkhud->addApplet("Channel 2", new InkHUD::ThreadedMessageApplet(2), true);
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Autoshown if new message
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 1); // Default tile 1
|
||||
// Disabled by default
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet);
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet);
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
|
||||
|
||||
// Start running InkHUD
|
||||
inkhud->begin();
|
||||
|
||||
Reference in New Issue
Block a user