mirror of
https://github.com/meshtastic/firmware.git
synced 2025-09-21 17:20:01 +00:00
Merge branch 'meshtastic:master' into master
This commit is contained in:
commit
4427f80da3
1
.github/workflows/build_esp32.yml
vendored
1
.github/workflows/build_esp32.yml
vendored
@ -35,6 +35,7 @@ jobs:
|
|||||||
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini
|
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini
|
||||||
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini
|
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini
|
||||||
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini
|
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini
|
||||||
|
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini
|
||||||
|
|
||||||
- name: Build ESP32
|
- name: Build ESP32
|
||||||
run: bin/build-esp32.sh ${{ inputs.board }}
|
run: bin/build-esp32.sh ${{ inputs.board }}
|
||||||
|
62
.github/workflows/build_esp32_c3.yml
vendored
Normal file
62
.github/workflows/build_esp32_c3.yml
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
name: Build ESP32-C3
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
board:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-esp32-c3:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Build base
|
||||||
|
id: base
|
||||||
|
uses: ./.github/actions/setup-base
|
||||||
|
|
||||||
|
- name: Pull web ui
|
||||||
|
uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
|
||||||
|
with:
|
||||||
|
repo: meshtastic/web
|
||||||
|
file: build.tar
|
||||||
|
target: build.tar
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Unpack web ui
|
||||||
|
run: |
|
||||||
|
tar -xf build.tar -C data/static
|
||||||
|
rm build.tar
|
||||||
|
- name: Remove debug flags for release
|
||||||
|
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||||
|
run: |
|
||||||
|
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini
|
||||||
|
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini
|
||||||
|
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini
|
||||||
|
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini
|
||||||
|
- name: Build ESP32
|
||||||
|
run: bin/build-esp32.sh ${{ inputs.board }}
|
||||||
|
|
||||||
|
- name: Pull OTA Firmware
|
||||||
|
uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
|
||||||
|
with:
|
||||||
|
repo: meshtastic/firmware-ota
|
||||||
|
file: firmware-c3.bin
|
||||||
|
target: release/bleota-c3.bin
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Get release version string
|
||||||
|
shell: bash
|
||||||
|
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||||
|
id: version
|
||||||
|
|
||||||
|
- name: Store binaries as an artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
|
||||||
|
path: |
|
||||||
|
release/*.bin
|
||||||
|
release/*.elf
|
1
.github/workflows/build_esp32_s3.yml
vendored
1
.github/workflows/build_esp32_s3.yml
vendored
@ -34,6 +34,7 @@ jobs:
|
|||||||
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini
|
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini
|
||||||
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini
|
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini
|
||||||
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini
|
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini
|
||||||
|
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini
|
||||||
- name: Build ESP32
|
- name: Build ESP32
|
||||||
run: bin/build-esp32.sh ${{ inputs.board }}
|
run: bin/build-esp32.sh ${{ inputs.board }}
|
||||||
|
|
||||||
|
14
.github/workflows/main_matrix.yml
vendored
14
.github/workflows/main_matrix.yml
vendored
@ -67,7 +67,6 @@ jobs:
|
|||||||
- board: tlora-v2-1-1_6-tcxo
|
- board: tlora-v2-1-1_6-tcxo
|
||||||
- board: tlora-v2-1-1_8
|
- board: tlora-v2-1-1_8
|
||||||
- board: tbeam
|
- board: tbeam
|
||||||
- board: heltec-ht62-esp32c3-sx1262
|
|
||||||
- board: heltec-v2_0
|
- board: heltec-v2_0
|
||||||
- board: heltec-v2_1
|
- board: heltec-v2_1
|
||||||
- board: tbeam0_7
|
- board: tbeam0_7
|
||||||
@ -105,6 +104,16 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
board: ${{ matrix.board }}
|
board: ${{ matrix.board }}
|
||||||
|
|
||||||
|
build-esp32-c3:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- board: heltec-ht62-esp32c3-sx1262
|
||||||
|
uses: ./.github/workflows/build_esp32_c3.yml
|
||||||
|
with:
|
||||||
|
board: ${{ matrix.board }}
|
||||||
|
|
||||||
build-nrf52:
|
build-nrf52:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@ -226,6 +235,7 @@ jobs:
|
|||||||
[
|
[
|
||||||
build-esp32,
|
build-esp32,
|
||||||
build-esp32-s3,
|
build-esp32-s3,
|
||||||
|
build-esp32-c3,
|
||||||
build-nrf52,
|
build-nrf52,
|
||||||
build-raspbian,
|
build-raspbian,
|
||||||
build-native,
|
build-native,
|
||||||
@ -251,7 +261,7 @@ jobs:
|
|||||||
id: version
|
id: version
|
||||||
|
|
||||||
- name: Move files up
|
- name: Move files up
|
||||||
run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./*tbeam-s3*/bleota-s3.bin ./**/firmware*.bin ./*t-echo*/Meshtastic_nRF52_factory_erase_v2.uf2 ./**/firmware-*.uf2 ./**/firmware-*-ota.zip ./**/*.elf ./*native*/*device-*.sh ./*native*/*device-*.bat ./firmware-raspbian-*/release/meshtasticd_linux_aarch64 ./firmware-raspbian-*/bin/config-dist.yaml
|
run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./*tbeam-s3*/bleota-s3.bin ./*esp32c3*/bleota-c3.bin ./**/firmware*.bin ./*t-echo*/Meshtastic_nRF52_factory_erase_v2.uf2 ./**/firmware-*.uf2 ./**/firmware-*-ota.zip ./**/*.elf ./*native*/*device-*.sh ./*native*/*device-*.bat ./firmware-raspbian-*/release/meshtasticd_linux_aarch64 ./firmware-raspbian-*/bin/config-dist.yaml
|
||||||
|
|
||||||
- name: Repackage in single firmware zip
|
- name: Repackage in single firmware zip
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
|
@ -31,9 +31,13 @@ IF EXIST %FILENAME% IF x%FILENAME:update=%==x%FILENAME% (
|
|||||||
%PYTHON% -m esptool --baud 115200 erase_flash
|
%PYTHON% -m esptool --baud 115200 erase_flash
|
||||||
%PYTHON% -m esptool --baud 115200 write_flash 0x00 %FILENAME%
|
%PYTHON% -m esptool --baud 115200 write_flash 0x00 %FILENAME%
|
||||||
|
|
||||||
@REM Account for S3 board's different OTA partition
|
@REM Account for S3 and C3 board's different OTA partition
|
||||||
IF x%FILENAME:s3=%==x%FILENAME% IF x%FILENAME:v3=%==x%FILENAME% IF x%FILENAME:t-deck=%==x%FILENAME% IF x%FILENAME:wireless-paper=%==x%FILENAME% IF x%FILENAME:wireless-tracker=%==x%FILENAME% (
|
IF x%FILENAME:s3=%==x%FILENAME% IF x%FILENAME:v3=%==x%FILENAME% IF x%FILENAME:t-deck=%==x%FILENAME% IF x%FILENAME:wireless-paper=%==x%FILENAME% IF x%FILENAME:wireless-tracker=%==x%FILENAME% (
|
||||||
%PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota.bin
|
IF x%FILENAME:esp32c3=%==x%FILENAME% (
|
||||||
|
%PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota.bin
|
||||||
|
) else (
|
||||||
|
%PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota-c3.bin
|
||||||
|
)
|
||||||
) else (
|
) else (
|
||||||
%PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota-s3.bin
|
%PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota-s3.bin
|
||||||
)
|
)
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
|
PYTHON=${PYTHON:-$(which python3 python | head -n 1)}
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Usage info
|
# Usage info
|
||||||
show_help() {
|
show_help() {
|
||||||
cat << EOF
|
cat <<EOF
|
||||||
Usage: $(basename $0) [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME|FILENAME]
|
Usage: $(basename $0) [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME|FILENAME]
|
||||||
Flash image file to device, but first erasing and writing system information"
|
Flash image file to device, but first erasing and writing system information"
|
||||||
|
|
||||||
@ -18,44 +18,50 @@ Flash image file to device, but first erasing and writing system information"
|
|||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
while getopts ":hp:P:f:" opt; do
|
while getopts ":hp:P:f:" opt; do
|
||||||
case "${opt}" in
|
case "${opt}" in
|
||||||
h)
|
h)
|
||||||
show_help
|
show_help
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
p) export ESPTOOL_PORT=${OPTARG}
|
p)
|
||||||
;;
|
export ESPTOOL_PORT=${OPTARG}
|
||||||
P) PYTHON=${OPTARG}
|
;;
|
||||||
;;
|
P)
|
||||||
f) FILENAME=${OPTARG}
|
PYTHON=${OPTARG}
|
||||||
;;
|
;;
|
||||||
*)
|
f)
|
||||||
echo "Invalid flag."
|
FILENAME=${OPTARG}
|
||||||
show_help >&2
|
;;
|
||||||
exit 1
|
*)
|
||||||
;;
|
echo "Invalid flag."
|
||||||
esac
|
show_help >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
shift "$((OPTIND-1))"
|
shift "$((OPTIND - 1))"
|
||||||
|
|
||||||
[ -z "$FILENAME" -a -n "$1" ] && {
|
[ -z "$FILENAME" -a -n "$1" ] && {
|
||||||
FILENAME=$1
|
FILENAME=$1
|
||||||
shift
|
shift
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ -f "${FILENAME}" ] && [ ! -z "${FILENAME##*"update"*}" ]; then
|
if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
|
||||||
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
|
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
|
||||||
"$PYTHON" -m esptool erase_flash
|
"$PYTHON" -m esptool erase_flash
|
||||||
"$PYTHON" -m esptool write_flash 0x00 ${FILENAME}
|
"$PYTHON" -m esptool write_flash 0x00 ${FILENAME}
|
||||||
# Account for S3 board's different OTA partition
|
# Account for S3 board's different OTA partition
|
||||||
if [ ! -z "${FILENAME##*"s3"*}" ] && [ ! -z "${FILENAME##*"-v3"*}" ] && [ ! -z "${FILENAME##*"t-deck"*}" ] && [ ! -z "${FILENAME##*"wireless-paper"*}" ] && [ ! -z "${FILENAME##*"wireless-tracker"*}" ]; then
|
if [ -n "${FILENAME##*"s3"*}" ] && [ -n "${FILENAME##*"-v3"*}" ] && [ -n "${FILENAME##*"t-deck"*}" ] && [ -n "${FILENAME##*"wireless-paper"*}" ] && [ -n "${FILENAME##*"wireless-tracker"*}" ]; then
|
||||||
"$PYTHON" -m esptool write_flash 0x260000 bleota.bin
|
if [ -n "${FILENAME##*"esp32c3"*}" ]; then
|
||||||
|
"$PYTHON" -m esptool write_flash 0x260000 bleota.bin
|
||||||
|
else
|
||||||
|
"$PYTHON" -m esptool write_flash 0x260000 bleota-c3.bin
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
"$PYTHON" -m esptool write_flash 0x260000 bleota-s3.bin
|
"$PYTHON" -m esptool write_flash 0x260000 bleota-s3.bin
|
||||||
fi
|
fi
|
||||||
"$PYTHON" -m esptool write_flash 0x300000 littlefs-*.bin
|
"$PYTHON" -m esptool write_flash 0x300000 littlefs-*.bin
|
||||||
|
|
||||||
else
|
else
|
||||||
show_help
|
show_help
|
||||||
|
@ -103,12 +103,21 @@ class AccelerometerThread : public concurrency::OSThread
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct bma423_axes_remap remap_data;
|
struct bma423_axes_remap remap_data;
|
||||||
|
#ifdef T_WATCH_S3
|
||||||
|
remap_data.x_axis = 1;
|
||||||
|
remap_data.x_axis_sign = 0;
|
||||||
|
remap_data.y_axis = 0;
|
||||||
|
remap_data.y_axis_sign = 0;
|
||||||
|
remap_data.z_axis = 2;
|
||||||
|
remap_data.z_axis_sign = 1;
|
||||||
|
#else
|
||||||
remap_data.x_axis = 0;
|
remap_data.x_axis = 0;
|
||||||
remap_data.x_axis_sign = 1;
|
remap_data.x_axis_sign = 1;
|
||||||
remap_data.y_axis = 1;
|
remap_data.y_axis = 1;
|
||||||
remap_data.y_axis_sign = 0;
|
remap_data.y_axis_sign = 0;
|
||||||
remap_data.z_axis = 2;
|
remap_data.z_axis = 2;
|
||||||
remap_data.z_axis_sign = 1;
|
remap_data.z_axis_sign = 1;
|
||||||
|
#endif
|
||||||
// Need to raise the wrist function, need to set the correct axis
|
// Need to raise the wrist function, need to set the correct axis
|
||||||
bmaSensor.setRemapAxes(&remap_data);
|
bmaSensor.setRemapAxes(&remap_data);
|
||||||
// sensor.enableFeature(BMA423_STEP_CNTR, true);
|
// sensor.enableFeature(BMA423_STEP_CNTR, true);
|
||||||
|
@ -54,6 +54,19 @@ static const adc_atten_t atten = ADC_ATTENUATION;
|
|||||||
#endif
|
#endif
|
||||||
#endif // BATTERY_PIN && ARCH_ESP32
|
#endif // BATTERY_PIN && ARCH_ESP32
|
||||||
|
|
||||||
|
#ifdef EXT_CHRG_DETECT
|
||||||
|
#ifndef EXT_CHRG_DETECT_MODE
|
||||||
|
static const uint8_t ext_chrg_detect_mode = INPUT;
|
||||||
|
#else
|
||||||
|
static const uint8_t ext_chrg_detect_mode = EXT_CHRG_DETECT_MODE;
|
||||||
|
#endif
|
||||||
|
#ifndef EXT_CHRG_DETECT_VALUE
|
||||||
|
static const uint8_t ext_chrg_detect_value = HIGH;
|
||||||
|
#else
|
||||||
|
static const uint8_t ext_chrg_detect_value = EXT_CHRG_DETECT_VALUE;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
|
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
|
||||||
INA260Sensor ina260Sensor;
|
INA260Sensor ina260Sensor;
|
||||||
INA219Sensor ina219Sensor;
|
INA219Sensor ina219Sensor;
|
||||||
@ -322,7 +335,14 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
|
|
||||||
/// Assume charging if we have a battery and external power is connected.
|
/// Assume charging if we have a battery and external power is connected.
|
||||||
/// we can't be smart enough to say 'full'?
|
/// we can't be smart enough to say 'full'?
|
||||||
virtual bool isCharging() override { return isBatteryConnect() && isVbusIn(); }
|
virtual bool isCharging() override
|
||||||
|
{
|
||||||
|
#ifdef EXT_CHRG_DETECT
|
||||||
|
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
|
||||||
|
#else
|
||||||
|
return isBatteryConnect() && isVbusIn();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// If we see a battery voltage higher than physics allows - assume charger is pumping
|
/// If we see a battery voltage higher than physics allows - assume charger is pumping
|
||||||
@ -389,6 +409,9 @@ bool Power::analogInit()
|
|||||||
#ifdef EXT_PWR_DETECT
|
#ifdef EXT_PWR_DETECT
|
||||||
pinMode(EXT_PWR_DETECT, INPUT);
|
pinMode(EXT_PWR_DETECT, INPUT);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef EXT_CHRG_DETECT
|
||||||
|
pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef BATTERY_PIN
|
#ifdef BATTERY_PIN
|
||||||
LOG_DEBUG("Using analog input %d for battery level\n", BATTERY_PIN);
|
LOG_DEBUG("Using analog input %d for battery level\n", BATTERY_PIN);
|
||||||
|
@ -239,4 +239,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#define MESHTASTIC_EXCLUDE_NEIGHBORINFO 1
|
#define MESHTASTIC_EXCLUDE_NEIGHBORINFO 1
|
||||||
#define MESHTASTIC_EXCLUDE_TRACEROUTE 1
|
#define MESHTASTIC_EXCLUDE_TRACEROUTE 1
|
||||||
#define MESHTASTIC_EXCLUDE_WAYPOINT 1
|
#define MESHTASTIC_EXCLUDE_WAYPOINT 1
|
||||||
|
#define MESHTASTIC_EXCLUDE_INPUTBROKER 1
|
||||||
|
#define MESHTASTIC_EXCLUDE_SERIAL 1
|
||||||
#endif
|
#endif
|
||||||
|
@ -71,28 +71,24 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trigger the refresh in GxEPD2
|
||||||
LOG_DEBUG("Updating E-Paper... ");
|
LOG_DEBUG("Updating E-Paper... ");
|
||||||
|
|
||||||
#if false
|
|
||||||
// Currently unused; rescued from commented-out line during a refactor
|
|
||||||
// Use a meaningful macro here if variant doesn't want fast refresh
|
|
||||||
|
|
||||||
// Full update mode (slow)
|
|
||||||
adafruitDisplay->display(false)
|
|
||||||
#else
|
|
||||||
// Fast update mode
|
|
||||||
adafruitDisplay->nextPage();
|
adafruitDisplay->nextPage();
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef EINK_NO_HIBERNATE // Only hibernate if controller IC will preserve image memory
|
// End the update process
|
||||||
// Put screen to sleep to save power (possibly not necessary because we already did poweroff inside of display)
|
endUpdate();
|
||||||
adafruitDisplay->hibernate();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
LOG_DEBUG("done\n");
|
LOG_DEBUG("done\n");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// End the update process - virtual method, overriden in derived class
|
||||||
|
void EInkDisplay::endUpdate()
|
||||||
|
{
|
||||||
|
// Power off display hardware, then deep-sleep (Except Wireless Paper V1.1, no deep-sleep)
|
||||||
|
adafruitDisplay->hibernate();
|
||||||
|
}
|
||||||
|
|
||||||
// Write the buffer to the display memory
|
// Write the buffer to the display memory
|
||||||
void EInkDisplay::display(void)
|
void EInkDisplay::display(void)
|
||||||
{
|
{
|
||||||
@ -188,6 +184,7 @@ bool EInkDisplay::connect()
|
|||||||
// Init GxEPD2
|
// Init GxEPD2
|
||||||
adafruitDisplay->init();
|
adafruitDisplay->init();
|
||||||
adafruitDisplay->setRotation(3);
|
adafruitDisplay->setRotation(3);
|
||||||
|
adafruitDisplay->clearScreen(); // Clearing now, so the boot logo will draw nice and smoothe (fast refresh)
|
||||||
}
|
}
|
||||||
#elif defined(PCA10059)
|
#elif defined(PCA10059)
|
||||||
{
|
{
|
||||||
|
@ -45,6 +45,13 @@ class EInkDisplay : public OLEDDisplay
|
|||||||
*/
|
*/
|
||||||
virtual bool forceDisplay(uint32_t msecLimit = 1000);
|
virtual bool forceDisplay(uint32_t msecLimit = 1000);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run any code needed to complete an update, after the physical refresh has completed.
|
||||||
|
* Split from forceDisplay(), to enable async refresh in derived EInkDynamicDisplay class.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
virtual void endUpdate();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* shim to make the abstraction happy
|
* shim to make the abstraction happy
|
||||||
*
|
*
|
||||||
|
@ -25,19 +25,19 @@ EInkDynamicDisplay::~EInkDynamicDisplay()
|
|||||||
// Screen requests a BACKGROUND frame
|
// Screen requests a BACKGROUND frame
|
||||||
void EInkDynamicDisplay::display()
|
void EInkDynamicDisplay::display()
|
||||||
{
|
{
|
||||||
setFrameFlag(BACKGROUND);
|
addFrameFlag(BACKGROUND);
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Screen requests a RESPONSIVE frame
|
// Screen requests a RESPONSIVE frame
|
||||||
bool EInkDynamicDisplay::forceDisplay(uint32_t msecLimit)
|
bool EInkDynamicDisplay::forceDisplay(uint32_t msecLimit)
|
||||||
{
|
{
|
||||||
setFrameFlag(RESPONSIVE);
|
addFrameFlag(RESPONSIVE);
|
||||||
return update(); // (Unutilized) Base class promises to return true if update ran
|
return update(); // (Unutilized) Base class promises to return true if update ran
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add flag for the next frame
|
// Add flag for the next frame
|
||||||
void EInkDynamicDisplay::setFrameFlag(frameFlagTypes flag)
|
void EInkDynamicDisplay::addFrameFlag(frameFlagTypes flag)
|
||||||
{
|
{
|
||||||
// OR the new flag into the existing flags
|
// OR the new flag into the existing flags
|
||||||
this->frameFlags = (frameFlagTypes)(this->frameFlags | flag);
|
this->frameFlags = (frameFlagTypes)(this->frameFlags | flag);
|
||||||
@ -94,38 +94,75 @@ void EInkDynamicDisplay::adjustRefreshCounters()
|
|||||||
// Trigger the display update by calling base class
|
// Trigger the display update by calling base class
|
||||||
bool EInkDynamicDisplay::update()
|
bool EInkDynamicDisplay::update()
|
||||||
{
|
{
|
||||||
|
// Detemine the refresh mode to use, and start the update
|
||||||
bool refreshApproved = determineMode();
|
bool refreshApproved = determineMode();
|
||||||
if (refreshApproved)
|
if (refreshApproved) {
|
||||||
EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system
|
EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system
|
||||||
return refreshApproved; // (Unutilized) Base class promises to return true if update ran
|
storeAndReset(); // Store the result of this loop for next time. Note: call *before* endOrDetach()
|
||||||
|
endOrDetach(); // endUpdate() right now, or set the async refresh flag (if FULL and HAS_EINK_ASYNCFULL)
|
||||||
|
} else
|
||||||
|
storeAndReset(); // No update, no post-update code, just store the results
|
||||||
|
|
||||||
|
return refreshApproved; // (Unutilized) Base class promises to return true if update ran
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure out who runs the post-update code
|
||||||
|
void EInkDynamicDisplay::endOrDetach()
|
||||||
|
{
|
||||||
|
// If the GxEPD2 version reports that it has the async modifications
|
||||||
|
#ifdef HAS_EINK_ASYNCFULL
|
||||||
|
if (previousRefresh == FULL) {
|
||||||
|
asyncRefreshRunning = true; // Set the flag - picked up at start of determineMode(), next loop.
|
||||||
|
|
||||||
|
if (previousFrameFlags & BLOCKING)
|
||||||
|
awaitRefresh();
|
||||||
|
else
|
||||||
|
LOG_DEBUG("Async full-refresh begins\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast Refresh
|
||||||
|
else if (previousRefresh == FAST)
|
||||||
|
EInkDisplay::endUpdate(); // Still block while updating, but EInkDisplay needs us to call endUpdate() ourselves.
|
||||||
|
|
||||||
|
// Fallback - If using an unmodified version of GxEPD2 for some reason
|
||||||
|
#else
|
||||||
|
if (previousRefresh == FULL || previousRefresh == FAST) { // If refresh wasn't skipped (on unspecified..)
|
||||||
|
LOG_WARN(
|
||||||
|
"GxEPD2 version has not been modified to support async refresh; using fallback behavior. Please update lib_deps in "
|
||||||
|
"variant's platformio.ini file\n");
|
||||||
|
EInkDisplay::endUpdate();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assess situation, pick a refresh type
|
// Assess situation, pick a refresh type
|
||||||
bool EInkDynamicDisplay::determineMode()
|
bool EInkDynamicDisplay::determineMode()
|
||||||
{
|
{
|
||||||
checkWasFlooded();
|
checkInitialized();
|
||||||
|
checkForPromotion();
|
||||||
|
#if defined(HAS_EINK_ASYNCFULL)
|
||||||
|
checkAsyncFullRefresh();
|
||||||
|
#endif
|
||||||
checkRateLimiting();
|
checkRateLimiting();
|
||||||
|
|
||||||
// If too soon for a new time, abort here
|
// If too soon for a new frame, or display busy, abort early
|
||||||
if (refresh == SKIPPED) {
|
if (refresh == SKIPPED)
|
||||||
storeAndReset();
|
|
||||||
return false; // No refresh
|
return false; // No refresh
|
||||||
}
|
|
||||||
|
|
||||||
// -- New frame is due --
|
// -- New frame is due --
|
||||||
|
|
||||||
resetRateLimiting(); // Once determineMode() ends, will have to wait again
|
resetRateLimiting(); // Once determineMode() ends, will have to wait again
|
||||||
hashImage(); // Generate here, so we can still copy it to previousImageHash, even if we skip the comparison check
|
hashImage(); // Generate here, so we can still copy it to previousImageHash, even if we skip the comparison check
|
||||||
LOG_DEBUG("EInkDynamicDisplay: "); // Begin log entry
|
LOG_DEBUG("determineMode(): "); // Begin log entry
|
||||||
|
|
||||||
// Once mode determined, any remaining checks will bypass
|
// Once mode determined, any remaining checks will bypass
|
||||||
checkCosmetic();
|
checkCosmetic();
|
||||||
checkDemandingFast();
|
checkDemandingFast();
|
||||||
|
checkFrameMatchesPrevious();
|
||||||
checkConsecutiveFastRefreshes();
|
checkConsecutiveFastRefreshes();
|
||||||
#ifdef EINK_LIMIT_GHOSTING_PX
|
#ifdef EINK_LIMIT_GHOSTING_PX
|
||||||
checkExcessiveGhosting();
|
checkExcessiveGhosting();
|
||||||
#endif
|
#endif
|
||||||
checkFrameMatchesPrevious();
|
|
||||||
checkFastRequested();
|
checkFastRequested();
|
||||||
|
|
||||||
if (refresh == UNSPECIFIED)
|
if (refresh == UNSPECIFIED)
|
||||||
@ -142,22 +179,49 @@ bool EInkDynamicDisplay::determineMode()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Return - call a refresh or not?
|
// Return - call a refresh or not?
|
||||||
if (refresh == SKIPPED) {
|
if (refresh == SKIPPED)
|
||||||
storeAndReset();
|
|
||||||
return false; // Don't trigger a refresh
|
return false; // Don't trigger a refresh
|
||||||
} else {
|
else
|
||||||
storeAndReset();
|
|
||||||
return true; // Do trigger a refresh
|
return true; // Do trigger a refresh
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this the very first frame?
|
||||||
|
void EInkDynamicDisplay::checkInitialized()
|
||||||
|
{
|
||||||
|
if (!initialized) {
|
||||||
|
// Undo GxEPD2_BW::partialWindow(), if set by developer in EInkDisplay::connect()
|
||||||
|
configForFullRefresh();
|
||||||
|
|
||||||
|
// Clear any existing image, so we can draw logo with fast-refresh, but also to set GxEPD2_EPD::_initial_write
|
||||||
|
adafruitDisplay->clearScreen();
|
||||||
|
|
||||||
|
LOG_DEBUG("initialized, ");
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
// Use a fast-refresh for the next frame; no skipping or else blank screen when waking from deep sleep
|
||||||
|
addFrameFlag(DEMAND_FAST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Did RESPONSIVE frames previously exceed the rate-limit for fast refresh?
|
// Was a frame skipped (rate, display busy) that should have been a FAST refresh?
|
||||||
void EInkDynamicDisplay::checkWasFlooded()
|
void EInkDynamicDisplay::checkForPromotion()
|
||||||
{
|
{
|
||||||
if (previousReason == EXCEEDED_RATELIMIT_FAST) {
|
// If a frame was skipped (rate, display busy), then promote a BACKGROUND frame
|
||||||
// If so, allow a BACKGROUND frame to draw as RESPONSIVE
|
// Because we DID want a RESPONSIVE/COSMETIC/DEMAND_FULL frame last time, we just didn't get it
|
||||||
// Because we DID want a RESPONSIVE frame last time, we just didn't get it
|
|
||||||
setFrameFlag(RESPONSIVE);
|
switch (previousReason) {
|
||||||
|
case ASYNC_REFRESH_BLOCKED_DEMANDFAST:
|
||||||
|
addFrameFlag(DEMAND_FAST);
|
||||||
|
break;
|
||||||
|
case ASYNC_REFRESH_BLOCKED_COSMETIC:
|
||||||
|
addFrameFlag(COSMETIC);
|
||||||
|
break;
|
||||||
|
case ASYNC_REFRESH_BLOCKED_RESPONSIVE:
|
||||||
|
case EXCEEDED_RATELIMIT_FAST:
|
||||||
|
addFrameFlag(RESPONSIVE);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,7 +268,7 @@ void EInkDynamicDisplay::checkCosmetic()
|
|||||||
if (frameFlags & COSMETIC) {
|
if (frameFlags & COSMETIC) {
|
||||||
refresh = FULL;
|
refresh = FULL;
|
||||||
reason = FLAGGED_COSMETIC;
|
reason = FLAGGED_COSMETIC;
|
||||||
LOG_DEBUG("refresh=FULL, reason=FLAGGED_COSMETIC\n");
|
LOG_DEBUG("refresh=FULL, reason=FLAGGED_COSMETIC, frameFlags=0x%x\n", frameFlags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,22 +283,7 @@ void EInkDynamicDisplay::checkDemandingFast()
|
|||||||
if (frameFlags & DEMAND_FAST) {
|
if (frameFlags & DEMAND_FAST) {
|
||||||
refresh = FAST;
|
refresh = FAST;
|
||||||
reason = FLAGGED_DEMAND_FAST;
|
reason = FLAGGED_DEMAND_FAST;
|
||||||
LOG_DEBUG("refresh=FAST, reason=FLAGGED_DEMAND_FAST\n");
|
LOG_DEBUG("refresh=FAST, reason=FLAGGED_DEMAND_FAST, frameFlags=0x%x\n", frameFlags);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Have too many fast-refreshes occured consecutively, since last full refresh?
|
|
||||||
void EInkDynamicDisplay::checkConsecutiveFastRefreshes()
|
|
||||||
{
|
|
||||||
// If a decision was already reached, don't run the check
|
|
||||||
if (refresh != UNSPECIFIED)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// If too many FAST refreshes consecutively - force a FULL refresh
|
|
||||||
if (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) {
|
|
||||||
refresh = FULL;
|
|
||||||
reason = EXCEEDED_LIMIT_FASTREFRESH;
|
|
||||||
LOG_DEBUG("refresh=FULL, reason=EXCEEDED_LIMIT_FASTREFRESH\n");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,7 +303,7 @@ void EInkDynamicDisplay::checkFrameMatchesPrevious()
|
|||||||
if (frameFlags == BACKGROUND && fastRefreshCount > 0) {
|
if (frameFlags == BACKGROUND && fastRefreshCount > 0) {
|
||||||
refresh = FULL;
|
refresh = FULL;
|
||||||
reason = REDRAW_WITH_FULL;
|
reason = REDRAW_WITH_FULL;
|
||||||
LOG_DEBUG("refresh=FULL, reason=REDRAW_WITH_FULL\n");
|
LOG_DEBUG("refresh=FULL, reason=REDRAW_WITH_FULL, frameFlags=0x%x\n", frameFlags);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -262,7 +311,22 @@ void EInkDynamicDisplay::checkFrameMatchesPrevious()
|
|||||||
// Not redrawn, not COSMETIC, not DEMAND_FAST
|
// Not redrawn, not COSMETIC, not DEMAND_FAST
|
||||||
refresh = SKIPPED;
|
refresh = SKIPPED;
|
||||||
reason = FRAME_MATCHED_PREVIOUS;
|
reason = FRAME_MATCHED_PREVIOUS;
|
||||||
LOG_DEBUG("refresh=SKIPPED, reason=FRAME_MATCHED_PREVIOUS\n");
|
LOG_DEBUG("refresh=SKIPPED, reason=FRAME_MATCHED_PREVIOUS, frameFlags=0x%x\n", frameFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Have too many fast-refreshes occured consecutively, since last full refresh?
|
||||||
|
void EInkDynamicDisplay::checkConsecutiveFastRefreshes()
|
||||||
|
{
|
||||||
|
// If a decision was already reached, don't run the check
|
||||||
|
if (refresh != UNSPECIFIED)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If too many FAST refreshes consecutively - force a FULL refresh
|
||||||
|
if (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) {
|
||||||
|
refresh = FULL;
|
||||||
|
reason = EXCEEDED_LIMIT_FASTREFRESH;
|
||||||
|
LOG_DEBUG("refresh=FULL, reason=EXCEEDED_LIMIT_FASTREFRESH, frameFlags=0x%x\n", frameFlags);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No objections, we can perform fast-refresh, if desired
|
// No objections, we can perform fast-refresh, if desired
|
||||||
@ -276,7 +340,8 @@ void EInkDynamicDisplay::checkFastRequested()
|
|||||||
// If we want BACKGROUND to use fast. (FULL only when a limit is hit)
|
// If we want BACKGROUND to use fast. (FULL only when a limit is hit)
|
||||||
refresh = FAST;
|
refresh = FAST;
|
||||||
reason = BACKGROUND_USES_FAST;
|
reason = BACKGROUND_USES_FAST;
|
||||||
LOG_DEBUG("refresh=FAST, reason=BACKGROUND_USES_FAST, fastRefreshCount=%lu\n", fastRefreshCount);
|
LOG_DEBUG("refresh=FAST, reason=BACKGROUND_USES_FAST, fastRefreshCount=%lu, frameFlags=0x%x\n", fastRefreshCount,
|
||||||
|
frameFlags);
|
||||||
#else
|
#else
|
||||||
// If we do want to use FULL for BACKGROUND updates
|
// If we do want to use FULL for BACKGROUND updates
|
||||||
refresh = FULL;
|
refresh = FULL;
|
||||||
@ -289,7 +354,7 @@ void EInkDynamicDisplay::checkFastRequested()
|
|||||||
if (frameFlags & RESPONSIVE) {
|
if (frameFlags & RESPONSIVE) {
|
||||||
refresh = FAST;
|
refresh = FAST;
|
||||||
reason = NO_OBJECTIONS;
|
reason = NO_OBJECTIONS;
|
||||||
LOG_DEBUG("refresh=FAST, reason=NO_OBJECTIONS, fastRefreshCount=%lu\n", fastRefreshCount);
|
LOG_DEBUG("refresh=FAST, reason=NO_OBJECTIONS, fastRefreshCount=%lu, frameFlags=0x%x\n", fastRefreshCount, frameFlags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,6 +378,7 @@ void EInkDynamicDisplay::hashImage()
|
|||||||
// Store the results of determineMode() for future use, and reset for next call
|
// Store the results of determineMode() for future use, and reset for next call
|
||||||
void EInkDynamicDisplay::storeAndReset()
|
void EInkDynamicDisplay::storeAndReset()
|
||||||
{
|
{
|
||||||
|
previousFrameFlags = frameFlags;
|
||||||
previousRefresh = refresh;
|
previousRefresh = refresh;
|
||||||
previousReason = reason;
|
previousReason = reason;
|
||||||
|
|
||||||
@ -369,7 +435,7 @@ void EInkDynamicDisplay::checkExcessiveGhosting()
|
|||||||
if (ghostPixelCount > EINK_LIMIT_GHOSTING_PX) {
|
if (ghostPixelCount > EINK_LIMIT_GHOSTING_PX) {
|
||||||
refresh = FULL;
|
refresh = FULL;
|
||||||
reason = EXCEEDED_GHOSTINGLIMIT;
|
reason = EXCEEDED_GHOSTINGLIMIT;
|
||||||
LOG_DEBUG("refresh=FULL, reason=EXCEEDED_GHOSTINGLIMIT\n");
|
LOG_DEBUG("refresh=FULL, reason=EXCEEDED_GHOSTINGLIMIT, frameFlags=0x%x\n", frameFlags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -381,4 +447,54 @@ void EInkDynamicDisplay::resetGhostPixelTracking()
|
|||||||
}
|
}
|
||||||
#endif // EINK_LIMIT_GHOSTING_PX
|
#endif // EINK_LIMIT_GHOSTING_PX
|
||||||
|
|
||||||
|
#ifdef HAS_EINK_ASYNCFULL
|
||||||
|
// Check the status of an "async full-refresh", and run the finish-up code if the hardware is ready
|
||||||
|
void EInkDynamicDisplay::checkAsyncFullRefresh()
|
||||||
|
{
|
||||||
|
// No refresh taking place, continue with determineMode()
|
||||||
|
if (!asyncRefreshRunning)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Full refresh still running
|
||||||
|
if (adafruitDisplay->epd2.isBusy()) {
|
||||||
|
// No refresh
|
||||||
|
refresh = SKIPPED;
|
||||||
|
|
||||||
|
// Set the reason, marking what type of frame we're skipping
|
||||||
|
if (frameFlags & DEMAND_FAST)
|
||||||
|
reason = ASYNC_REFRESH_BLOCKED_DEMANDFAST;
|
||||||
|
else if (frameFlags & COSMETIC)
|
||||||
|
reason = ASYNC_REFRESH_BLOCKED_COSMETIC;
|
||||||
|
else if (frameFlags & RESPONSIVE)
|
||||||
|
reason = ASYNC_REFRESH_BLOCKED_RESPONSIVE;
|
||||||
|
else
|
||||||
|
reason = ASYNC_REFRESH_BLOCKED_BACKGROUND;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we asyncRefreshRunning flag is still set, but display's BUSY pin reports the refresh is done
|
||||||
|
adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code
|
||||||
|
EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override)
|
||||||
|
asyncRefreshRunning = false; // Unset the flag
|
||||||
|
LOG_DEBUG("Async full-refresh complete\n");
|
||||||
|
|
||||||
|
// Note: this code only works because of a modification to meshtastic/GxEPD2.
|
||||||
|
// It is only equipped to intercept calls to nextPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hold control while an async refresh runs
|
||||||
|
void EInkDynamicDisplay::awaitRefresh()
|
||||||
|
{
|
||||||
|
// Continually poll the BUSY pin
|
||||||
|
while (adafruitDisplay->epd2.isBusy())
|
||||||
|
yield();
|
||||||
|
|
||||||
|
// End the full-refresh process
|
||||||
|
adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code
|
||||||
|
EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override)
|
||||||
|
asyncRefreshRunning = false; // Unset the flag
|
||||||
|
}
|
||||||
|
#endif // HAS_EINK_ASYNCFULL
|
||||||
|
|
||||||
#endif // USE_EINK_DYNAMICDISPLAY
|
#endif // USE_EINK_DYNAMICDISPLAY
|
@ -28,8 +28,9 @@ class EInkDynamicDisplay : public EInkDisplay
|
|||||||
RESPONSIVE = (1 << 1), // For frames via forceDisplay()
|
RESPONSIVE = (1 << 1), // For frames via forceDisplay()
|
||||||
COSMETIC = (1 << 2), // For splashes
|
COSMETIC = (1 << 2), // For splashes
|
||||||
DEMAND_FAST = (1 << 3), // Special case only
|
DEMAND_FAST = (1 << 3), // Special case only
|
||||||
|
BLOCKING = (1 << 4), // Modifier - block while refresh runs
|
||||||
};
|
};
|
||||||
void setFrameFlag(frameFlagTypes flag);
|
void addFrameFlag(frameFlagTypes flag);
|
||||||
|
|
||||||
// Set the correct frame flag, then call universal "update()" method
|
// Set the correct frame flag, then call universal "update()" method
|
||||||
void display() override;
|
void display() override;
|
||||||
@ -44,6 +45,10 @@ class EInkDynamicDisplay : public EInkDisplay
|
|||||||
};
|
};
|
||||||
enum reasonTypes : uint8_t { // How was the decision reached
|
enum reasonTypes : uint8_t { // How was the decision reached
|
||||||
NO_OBJECTIONS,
|
NO_OBJECTIONS,
|
||||||
|
ASYNC_REFRESH_BLOCKED_DEMANDFAST,
|
||||||
|
ASYNC_REFRESH_BLOCKED_COSMETIC,
|
||||||
|
ASYNC_REFRESH_BLOCKED_RESPONSIVE,
|
||||||
|
ASYNC_REFRESH_BLOCKED_BACKGROUND,
|
||||||
EXCEEDED_RATELIMIT_FAST,
|
EXCEEDED_RATELIMIT_FAST,
|
||||||
EXCEEDED_RATELIMIT_FULL,
|
EXCEEDED_RATELIMIT_FULL,
|
||||||
FLAGGED_COSMETIC,
|
FLAGGED_COSMETIC,
|
||||||
@ -62,14 +67,16 @@ class EInkDynamicDisplay : public EInkDisplay
|
|||||||
void applyRefreshMode(); // Run any relevant GxEPD2 code, so next update will use correct refresh type
|
void applyRefreshMode(); // Run any relevant GxEPD2 code, so next update will use correct refresh type
|
||||||
void adjustRefreshCounters(); // Update fastRefreshCount
|
void adjustRefreshCounters(); // Update fastRefreshCount
|
||||||
bool update(); // Trigger the display update - determine mode, then call base class
|
bool update(); // Trigger the display update - determine mode, then call base class
|
||||||
|
void endOrDetach(); // Run the post-update code, or delegate it off to checkAsyncFullRefresh()
|
||||||
|
|
||||||
// Checks as part of determineMode()
|
// Checks as part of determineMode()
|
||||||
void checkWasFlooded(); // Was the previous frame skipped for exceeding EINK_LIMIT_RATE_RESPONSIVE_SEC?
|
void checkInitialized(); // Is this the very first frame?
|
||||||
|
void checkForPromotion(); // Was a frame skipped (rate, display busy) that should have been a FAST refresh?
|
||||||
void checkRateLimiting(); // Is this frame too soon?
|
void checkRateLimiting(); // Is this frame too soon?
|
||||||
void checkCosmetic(); // Was the COSMETIC flag set?
|
void checkCosmetic(); // Was the COSMETIC flag set?
|
||||||
void checkDemandingFast(); // Was the DEMAND_FAST flag set?
|
void checkDemandingFast(); // Was the DEMAND_FAST flag set?
|
||||||
void checkConsecutiveFastRefreshes(); // Too many fast-refreshes consecutively?
|
|
||||||
void checkFrameMatchesPrevious(); // Does the new frame match the existing display image?
|
void checkFrameMatchesPrevious(); // Does the new frame match the existing display image?
|
||||||
|
void checkConsecutiveFastRefreshes(); // Too many fast-refreshes consecutively?
|
||||||
void checkFastRequested(); // Was the flag set for RESPONSIVE, or only BACKGROUND?
|
void checkFastRequested(); // Was the flag set for RESPONSIVE, or only BACKGROUND?
|
||||||
|
|
||||||
void resetRateLimiting(); // Set previousRunMs - this now counts as an update, for rate-limiting
|
void resetRateLimiting(); // Set previousRunMs - this now counts as an update, for rate-limiting
|
||||||
@ -77,14 +84,16 @@ class EInkDynamicDisplay : public EInkDisplay
|
|||||||
void storeAndReset(); // Keep results of determineMode() for later, tidy-up for next call
|
void storeAndReset(); // Keep results of determineMode() for later, tidy-up for next call
|
||||||
|
|
||||||
// What we are determining for this frame
|
// What we are determining for this frame
|
||||||
frameFlagTypes frameFlags = BACKGROUND; // Frame type(s) - determineMode() input
|
frameFlagTypes frameFlags = BACKGROUND; // Frame characteristics - determineMode() input
|
||||||
refreshTypes refresh = UNSPECIFIED; // Refresh type - determineMode() output
|
refreshTypes refresh = UNSPECIFIED; // Refresh type - determineMode() output
|
||||||
reasonTypes reason = NO_OBJECTIONS; // Reason - why was refresh type used
|
reasonTypes reason = NO_OBJECTIONS; // Reason - why was refresh type used
|
||||||
|
|
||||||
// What happened last time determineMode() ran
|
// What happened last time determineMode() ran
|
||||||
refreshTypes previousRefresh = UNSPECIFIED; // (Previous) Outcome
|
frameFlagTypes previousFrameFlags = BACKGROUND; // (Previous) Frame flags
|
||||||
reasonTypes previousReason = NO_OBJECTIONS; // (Previous) Reason
|
refreshTypes previousRefresh = UNSPECIFIED; // (Previous) Outcome
|
||||||
|
reasonTypes previousReason = NO_OBJECTIONS; // (Previous) Reason
|
||||||
|
|
||||||
|
bool initialized = false; // Have we drawn at least one frame yet?
|
||||||
uint32_t previousRunMs = -1; // When did determineMode() last run (rather than rejecting for rate-limiting)
|
uint32_t previousRunMs = -1; // When did determineMode() last run (rather than rejecting for rate-limiting)
|
||||||
uint32_t imageHash = 0; // Hash of the current frame. Don't bother updating if nothing has changed!
|
uint32_t imageHash = 0; // Hash of the current frame. Don't bother updating if nothing has changed!
|
||||||
uint32_t previousImageHash = 0; // Hash of the previous update's frame
|
uint32_t previousImageHash = 0; // Hash of the previous update's frame
|
||||||
@ -99,6 +108,20 @@ class EInkDynamicDisplay : public EInkDisplay
|
|||||||
uint8_t *dirtyPixels; // Any pixels that have been black since last full-refresh (dynamically allocated mem)
|
uint8_t *dirtyPixels; // Any pixels that have been black since last full-refresh (dynamically allocated mem)
|
||||||
uint32_t ghostPixelCount = 0; // Number of pixels with problematic ghosting. Retained here for LOG_DEBUG use
|
uint32_t ghostPixelCount = 0; // Number of pixels with problematic ghosting. Retained here for LOG_DEBUG use
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Conditional - async full refresh - only with modified meshtastic/GxEPD2
|
||||||
|
#if defined(HAS_EINK_ASYNCFULL)
|
||||||
|
void checkAsyncFullRefresh(); // Check the status of "async full-refresh"; run the post-update code if the hardware is ready
|
||||||
|
void awaitRefresh(); // Hold control while an async refresh runs
|
||||||
|
void endUpdate() override {} // Disable base-class behavior of running post-update immediately after forceDisplay()
|
||||||
|
bool asyncRefreshRunning = false; // Flag, checked by checkAsyncFullRefresh()
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Tidier calls to addFrameFlag() from outside class
|
||||||
|
#define EINK_ADD_FRAMEFLAG(display, flag) static_cast<EInkDynamicDisplay *>(display)->addFrameFlag(EInkDynamicDisplay::flag)
|
||||||
|
|
||||||
|
#else // !USE_EINK_DYNAMICDISPLAY
|
||||||
|
// Dummy-macro, removes the need for include guards
|
||||||
|
#define EINK_ADD_FRAMEFLAG(display, flag)
|
||||||
#endif
|
#endif
|
@ -260,6 +260,10 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i
|
|||||||
/// Used on eink displays while in deep sleep
|
/// Used on eink displays while in deep sleep
|
||||||
static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
|
// Next frame should use full-refresh, and block while running, else device will sleep before async callback
|
||||||
|
EINK_ADD_FRAMEFLAG(display, COSMETIC);
|
||||||
|
EINK_ADD_FRAMEFLAG(display, BLOCKING);
|
||||||
|
|
||||||
drawIconScreen("Sleeping...", display, state, x, y);
|
drawIconScreen("Sleeping...", display, state, x, y);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -1170,6 +1174,7 @@ int32_t Screen::runOnce()
|
|||||||
break;
|
break;
|
||||||
case Cmd::STOP_BLUETOOTH_PIN_SCREEN:
|
case Cmd::STOP_BLUETOOTH_PIN_SCREEN:
|
||||||
case Cmd::STOP_BOOT_SCREEN:
|
case Cmd::STOP_BOOT_SCREEN:
|
||||||
|
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
|
||||||
setFrames();
|
setFrames();
|
||||||
break;
|
break;
|
||||||
case Cmd::PRINT:
|
case Cmd::PRINT:
|
||||||
@ -1350,6 +1355,7 @@ void Screen::handleStartBluetoothPinScreen(uint32_t pin)
|
|||||||
{
|
{
|
||||||
LOG_DEBUG("showing bluetooth screen\n");
|
LOG_DEBUG("showing bluetooth screen\n");
|
||||||
showingNormalScreen = false;
|
showingNormalScreen = false;
|
||||||
|
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
|
||||||
|
|
||||||
static FrameCallback frames[] = {drawFrameBluetooth};
|
static FrameCallback frames[] = {drawFrameBluetooth};
|
||||||
snprintf(btPIN, sizeof(btPIN), "%06u", pin);
|
snprintf(btPIN, sizeof(btPIN), "%06u", pin);
|
||||||
@ -1367,6 +1373,7 @@ void Screen::handleShutdownScreen()
|
|||||||
{
|
{
|
||||||
LOG_DEBUG("showing shutdown screen\n");
|
LOG_DEBUG("showing shutdown screen\n");
|
||||||
showingNormalScreen = false;
|
showingNormalScreen = false;
|
||||||
|
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
|
||||||
|
|
||||||
auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
|
auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
|
||||||
drawFrameText(display, state, x, y, "Shutting down...");
|
drawFrameText(display, state, x, y, "Shutting down...");
|
||||||
@ -1380,6 +1387,7 @@ void Screen::handleRebootScreen()
|
|||||||
{
|
{
|
||||||
LOG_DEBUG("showing reboot screen\n");
|
LOG_DEBUG("showing reboot screen\n");
|
||||||
showingNormalScreen = false;
|
showingNormalScreen = false;
|
||||||
|
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
|
||||||
|
|
||||||
auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
|
auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
|
||||||
drawFrameText(display, state, x, y, "Rebooting...");
|
drawFrameText(display, state, x, y, "Rebooting...");
|
||||||
@ -1392,6 +1400,7 @@ void Screen::handleStartFirmwareUpdateScreen()
|
|||||||
{
|
{
|
||||||
LOG_DEBUG("showing firmware screen\n");
|
LOG_DEBUG("showing firmware screen\n");
|
||||||
showingNormalScreen = false;
|
showingNormalScreen = false;
|
||||||
|
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
|
||||||
|
|
||||||
static FrameCallback frames[] = {drawFrameFirmware};
|
static FrameCallback frames[] = {drawFrameFirmware};
|
||||||
setFrameImmediateDraw(frames);
|
setFrameImmediateDraw(frames);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#include "CryptoEngine.h"
|
#include "CryptoEngine.h"
|
||||||
#include "DisplayFormatters.h"
|
#include "DisplayFormatters.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
|
#include "RadioInterface.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@ -254,6 +255,25 @@ const char *Channels::getName(size_t chIndex)
|
|||||||
return channelName;
|
return channelName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Channels::hasDefaultChannel()
|
||||||
|
{
|
||||||
|
// If we don't use a preset or the default frequency slot, or we override the frequency, we don't have a default channel
|
||||||
|
if (!config.lora.use_preset || !RadioInterface::uses_default_frequency_slot || config.lora.override_frequency)
|
||||||
|
return false;
|
||||||
|
// Check if any of the channels are using the default name and PSK
|
||||||
|
for (size_t i = 0; i < getNumChannels(); i++) {
|
||||||
|
const auto &ch = getByIndex(i);
|
||||||
|
if (ch.settings.psk.size == 1 && ch.settings.psk.bytes[0] == 1) {
|
||||||
|
const char *name = getName(i);
|
||||||
|
const char *presetName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false);
|
||||||
|
// Check if the name is the default derived from the modem preset
|
||||||
|
if (strcmp(name, presetName) == 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a short suffix used to disambiguate channels that might have the same "name" entered by the human but different PSKs.
|
* Generate a short suffix used to disambiguate channels that might have the same "name" entered by the human but different PSKs.
|
||||||
* The ideas is that the PSK changing should be visible to the user so that they see they probably messed up and that's why they
|
* The ideas is that the PSK changing should be visible to the user so that they see they probably messed up and that's why they
|
||||||
|
@ -102,6 +102,9 @@ class Channels
|
|||||||
*/
|
*/
|
||||||
int16_t setActiveByIndex(ChannelIndex channelIndex);
|
int16_t setActiveByIndex(ChannelIndex channelIndex);
|
||||||
|
|
||||||
|
// Returns true if we can be reached via a channel with the default settings given a region and modem preset
|
||||||
|
bool hasDefaultChannel();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/** Given a channel index, change to use the crypto key specified by that index
|
/** Given a channel index, change to use the crypto key specified by that index
|
||||||
*
|
*
|
||||||
|
@ -744,14 +744,17 @@ uint32_t sinceReceived(const meshtastic_MeshPacket *p)
|
|||||||
|
|
||||||
#define NUM_ONLINE_SECS (60 * 60 * 2) // 2 hrs to consider someone offline
|
#define NUM_ONLINE_SECS (60 * 60 * 2) // 2 hrs to consider someone offline
|
||||||
|
|
||||||
size_t NodeDB::getNumOnlineMeshNodes()
|
size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
|
||||||
{
|
{
|
||||||
size_t numseen = 0;
|
size_t numseen = 0;
|
||||||
|
|
||||||
// FIXME this implementation is kinda expensive
|
// FIXME this implementation is kinda expensive
|
||||||
for (int i = 0; i < *numMeshNodes; i++)
|
for (int i = 0; i < *numMeshNodes; i++) {
|
||||||
|
if (localOnly && meshNodes[i].via_mqtt)
|
||||||
|
continue;
|
||||||
if (sinceLastSeen(&meshNodes[i]) < NUM_ONLINE_SECS)
|
if (sinceLastSeen(&meshNodes[i]) < NUM_ONLINE_SECS)
|
||||||
numseen++;
|
numseen++;
|
||||||
|
}
|
||||||
|
|
||||||
return numseen;
|
return numseen;
|
||||||
}
|
}
|
||||||
|
@ -108,8 +108,10 @@ class NodeDB
|
|||||||
// get channel channel index we heard a nodeNum on, defaults to 0 if not found
|
// get channel channel index we heard a nodeNum on, defaults to 0 if not found
|
||||||
uint8_t getMeshNodeChannel(NodeNum n);
|
uint8_t getMeshNodeChannel(NodeNum n);
|
||||||
|
|
||||||
/// Return the number of nodes we've heard from recently (within the last 2 hrs?)
|
/* Return the number of nodes we've heard from recently (within the last 2 hrs?)
|
||||||
size_t getNumOnlineMeshNodes();
|
* @param localOnly if true, ignore nodes heard via MQTT
|
||||||
|
*/
|
||||||
|
size_t getNumOnlineMeshNodes(bool localOnly = false);
|
||||||
|
|
||||||
void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(uint nodeNum);
|
void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(uint nodeNum);
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "RadioInterface.h"
|
#include "RadioInterface.h"
|
||||||
#include "Channels.h"
|
#include "Channels.h"
|
||||||
|
#include "DisplayFormatters.h"
|
||||||
#include "MeshRadio.h"
|
#include "MeshRadio.h"
|
||||||
#include "MeshService.h"
|
#include "MeshService.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
@ -143,6 +144,7 @@ const RegionInfo regions[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const RegionInfo *myRegion;
|
const RegionInfo *myRegion;
|
||||||
|
bool RadioInterface::uses_default_frequency_slot = true;
|
||||||
|
|
||||||
static uint8_t bytes[MAX_RHPACKETLEN];
|
static uint8_t bytes[MAX_RHPACKETLEN];
|
||||||
|
|
||||||
@ -486,6 +488,10 @@ void RadioInterface::applyModemConfig()
|
|||||||
// channel_num is actually (channel_num - 1), since modulus (%) returns values from 0 to (numChannels - 1)
|
// channel_num is actually (channel_num - 1), since modulus (%) returns values from 0 to (numChannels - 1)
|
||||||
int channel_num = (loraConfig.channel_num ? loraConfig.channel_num - 1 : hash(channelName)) % numChannels;
|
int channel_num = (loraConfig.channel_num ? loraConfig.channel_num - 1 : hash(channelName)) % numChannels;
|
||||||
|
|
||||||
|
// Check if we use the default frequency slot
|
||||||
|
RadioInterface::uses_default_frequency_slot =
|
||||||
|
channel_num == hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false)) % numChannels;
|
||||||
|
|
||||||
// Old frequency selection formula
|
// Old frequency selection formula
|
||||||
// float freq = myRegion->freqStart + ((((myRegion->freqEnd - myRegion->freqStart) / numChannels) / 2) * channel_num);
|
// float freq = myRegion->freqStart + ((((myRegion->freqEnd - myRegion->freqStart) / numChannels) / 2) * channel_num);
|
||||||
|
|
||||||
|
@ -175,6 +175,9 @@ class RadioInterface
|
|||||||
/// Some boards (1st gen Pinetab Lora module) have broken IRQ wires, so we need to poll via i2c registers
|
/// Some boards (1st gen Pinetab Lora module) have broken IRQ wires, so we need to poll via i2c registers
|
||||||
virtual bool isIRQPending() { return false; }
|
virtual bool isIRQPending() { return false; }
|
||||||
|
|
||||||
|
// Whether we use the default frequency slot given our LoRa config (region and modem preset)
|
||||||
|
static bool uses_default_frequency_slot;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int8_t power = 17; // Set by applyModemConfig()
|
int8_t power = 17; // Set by applyModemConfig()
|
||||||
|
|
||||||
|
@ -167,8 +167,6 @@ bool ReliableRouter::stopRetransmission(GlobalPacketId key)
|
|||||||
auto old = findPendingPacket(key);
|
auto old = findPendingPacket(key);
|
||||||
if (old) {
|
if (old) {
|
||||||
auto p = old->packet;
|
auto p = old->packet;
|
||||||
auto numErased = pending.erase(key);
|
|
||||||
assert(numErased == 1);
|
|
||||||
/* Only when we already transmitted a packet via LoRa, we will cancel the packet in the Tx queue
|
/* Only when we already transmitted a packet via LoRa, we will cancel the packet in the Tx queue
|
||||||
to avoid canceling a transmission if it was ACKed super fast via MQTT */
|
to avoid canceling a transmission if it was ACKed super fast via MQTT */
|
||||||
if (old->numRetransmissions < NUM_RETRANSMISSIONS - 1) {
|
if (old->numRetransmissions < NUM_RETRANSMISSIONS - 1) {
|
||||||
@ -177,6 +175,8 @@ bool ReliableRouter::stopRetransmission(GlobalPacketId key)
|
|||||||
// now free the pooled copy for retransmission too
|
// now free the pooled copy for retransmission too
|
||||||
packetPool.release(p);
|
packetPool.release(p);
|
||||||
}
|
}
|
||||||
|
auto numErased = pending.erase(key);
|
||||||
|
assert(numErased == 1);
|
||||||
return true;
|
return true;
|
||||||
} else
|
} else
|
||||||
return false;
|
return false;
|
||||||
|
@ -39,11 +39,11 @@
|
|||||||
#if HAS_TELEMETRY
|
#if HAS_TELEMETRY
|
||||||
#include "modules/Telemetry/DeviceTelemetry.h"
|
#include "modules/Telemetry/DeviceTelemetry.h"
|
||||||
#endif
|
#endif
|
||||||
#if HAS_SENSOR && !EXCLUDE_ENVIRONMENTAL_SENSOR
|
#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||||
#include "modules/Telemetry/AirQualityTelemetry.h"
|
#include "modules/Telemetry/AirQualityTelemetry.h"
|
||||||
#include "modules/Telemetry/EnvironmentTelemetry.h"
|
#include "modules/Telemetry/EnvironmentTelemetry.h"
|
||||||
#endif
|
#endif
|
||||||
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !EXCLUDE_POWER_TELEMETRY
|
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY
|
||||||
#include "modules/Telemetry/PowerTelemetry.h"
|
#include "modules/Telemetry/PowerTelemetry.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
@ -138,13 +138,13 @@ void setupModules()
|
|||||||
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
|
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
|
||||||
new DeviceTelemetryModule();
|
new DeviceTelemetryModule();
|
||||||
#endif
|
#endif
|
||||||
#if HAS_SENSOR && !EXCLUDE_ENVIRONMENTAL_SENSOR
|
#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||||
new EnvironmentTelemetryModule();
|
new EnvironmentTelemetryModule();
|
||||||
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
|
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
|
||||||
new AirQualityTelemetryModule();
|
new AirQualityTelemetryModule();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !EXCLUDE_POWER_TELEMETRY
|
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY
|
||||||
new PowerTelemetryModule();
|
new PowerTelemetryModule();
|
||||||
#endif
|
#endif
|
||||||
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \
|
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \
|
||||||
|
@ -114,7 +114,7 @@ void RangeTestModuleRadio::sendPayload(NodeNum dest, bool wantReplies)
|
|||||||
p->to = dest;
|
p->to = dest;
|
||||||
p->decoded.want_response = wantReplies;
|
p->decoded.want_response = wantReplies;
|
||||||
p->hop_limit = 0;
|
p->hop_limit = 0;
|
||||||
p->want_ack = true;
|
p->want_ack = false;
|
||||||
|
|
||||||
packetSequence++;
|
packetSequence++;
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
|
|||||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR)
|
if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR)
|
||||||
p->priority = meshtastic_MeshPacket_Priority_RELIABLE;
|
p->priority = meshtastic_MeshPacket_Priority_RELIABLE;
|
||||||
else
|
else
|
||||||
p->priority = meshtastic_MeshPacket_Priority_MIN;
|
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||||
|
|
||||||
// release previous packet before occupying a new spot
|
// release previous packet before occupying a new spot
|
||||||
if (lastMeasurementPacket != nullptr)
|
if (lastMeasurementPacket != nullptr)
|
||||||
|
@ -91,7 +91,7 @@ bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
|
|||||||
meshtastic_MeshPacket *p = allocDataProtobuf(telemetry);
|
meshtastic_MeshPacket *p = allocDataProtobuf(telemetry);
|
||||||
p->to = dest;
|
p->to = dest;
|
||||||
p->decoded.want_response = false;
|
p->decoded.want_response = false;
|
||||||
p->priority = meshtastic_MeshPacket_Priority_MIN;
|
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||||
|
|
||||||
nodeDB.updateTelemetry(nodeDB.getNodeNum(), telemetry, RX_SRC_LOCAL);
|
nodeDB.updateTelemetry(nodeDB.getNodeNum(), telemetry, RX_SRC_LOCAL);
|
||||||
if (phoneOnly) {
|
if (phoneOnly) {
|
||||||
|
@ -253,7 +253,7 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
|
|||||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR)
|
if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR)
|
||||||
p->priority = meshtastic_MeshPacket_Priority_RELIABLE;
|
p->priority = meshtastic_MeshPacket_Priority_RELIABLE;
|
||||||
else
|
else
|
||||||
p->priority = meshtastic_MeshPacket_Priority_MIN;
|
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||||
// release previous packet before occupying a new spot
|
// release previous packet before occupying a new spot
|
||||||
if (lastMeasurementPacket != nullptr)
|
if (lastMeasurementPacket != nullptr)
|
||||||
packetPool.release(lastMeasurementPacket);
|
packetPool.release(lastMeasurementPacket);
|
||||||
|
@ -195,7 +195,7 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
|
|||||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR)
|
if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR)
|
||||||
p->priority = meshtastic_MeshPacket_Priority_RELIABLE;
|
p->priority = meshtastic_MeshPacket_Priority_RELIABLE;
|
||||||
else
|
else
|
||||||
p->priority = meshtastic_MeshPacket_Priority_MIN;
|
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||||
// release previous packet before occupying a new spot
|
// release previous packet before occupying a new spot
|
||||||
if (lastMeasurementPacket != nullptr)
|
if (lastMeasurementPacket != nullptr)
|
||||||
packetPool.release(lastMeasurementPacket);
|
packetPool.release(lastMeasurementPacket);
|
||||||
|
@ -12,7 +12,8 @@ int32_t SHT31Sensor::runOnce()
|
|||||||
if (!hasSensor()) {
|
if (!hasSensor()) {
|
||||||
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
|
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
|
||||||
}
|
}
|
||||||
status = sht31.begin();
|
sht31 = Adafruit_SHT31(nodeTelemetrySensorsMap[sensorType].second);
|
||||||
|
status = sht31.begin(nodeTelemetrySensorsMap[sensorType].first);
|
||||||
return initI2CSensor();
|
return initI2CSensor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
class SHT31Sensor : public TelemetrySensor
|
class SHT31Sensor : public TelemetrySensor
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
Adafruit_SHT31 sht31 = Adafruit_SHT31();
|
Adafruit_SHT31 sht31;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void setup() override;
|
virtual void setup() override;
|
||||||
|
@ -50,7 +50,7 @@ bool PaxcounterModule::sendInfo(NodeNum dest)
|
|||||||
meshtastic_MeshPacket *p = allocDataProtobuf(pl);
|
meshtastic_MeshPacket *p = allocDataProtobuf(pl);
|
||||||
p->to = dest;
|
p->to = dest;
|
||||||
p->decoded.want_response = false;
|
p->decoded.want_response = false;
|
||||||
p->priority = meshtastic_MeshPacket_Priority_MIN;
|
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||||
|
|
||||||
service.sendToMesh(p, RX_SRC_LOCAL, true);
|
service.sendToMesh(p, RX_SRC_LOCAL, true);
|
||||||
|
|
||||||
|
@ -255,7 +255,7 @@ void StoreForwardModule::sendMessage(NodeNum dest, const meshtastic_StoreAndForw
|
|||||||
|
|
||||||
p->to = dest;
|
p->to = dest;
|
||||||
|
|
||||||
p->priority = meshtastic_MeshPacket_Priority_MIN;
|
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||||
|
|
||||||
// FIXME - Determine if the delayed packet is broadcast or delayed. For now, assume
|
// FIXME - Determine if the delayed packet is broadcast or delayed. For now, assume
|
||||||
// everything is broadcast.
|
// everything is broadcast.
|
||||||
@ -334,7 +334,7 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m
|
|||||||
LOG_INFO("*** S&F - Busy. Try again shortly.\n");
|
LOG_INFO("*** S&F - Busy. Try again shortly.\n");
|
||||||
meshtastic_MeshPacket *pr = allocReply();
|
meshtastic_MeshPacket *pr = allocReply();
|
||||||
pr->to = getFrom(&mp);
|
pr->to = getFrom(&mp);
|
||||||
pr->priority = meshtastic_MeshPacket_Priority_MIN;
|
pr->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||||
pr->want_ack = false;
|
pr->want_ack = false;
|
||||||
pr->decoded.want_response = false;
|
pr->decoded.want_response = false;
|
||||||
pr->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
|
pr->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
|
||||||
|
@ -77,8 +77,7 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length)
|
|||||||
if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) {
|
if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) {
|
||||||
memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length());
|
memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length());
|
||||||
p->decoded.payload.size = jsonPayloadStr.length();
|
p->decoded.payload.size = jsonPayloadStr.length();
|
||||||
meshtastic_MeshPacket *packet = packetPool.allocCopy(*p);
|
service.sendToMesh(p, RX_SRC_LOCAL);
|
||||||
service.sendToMesh(packet, RX_SRC_LOCAL);
|
|
||||||
} else {
|
} else {
|
||||||
LOG_WARN("Received MQTT json payload too long, dropping\n");
|
LOG_WARN("Received MQTT json payload too long, dropping\n");
|
||||||
}
|
}
|
||||||
@ -186,10 +185,17 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE)
|
|||||||
statusTopic = moduleConfig.mqtt.root + statusTopic;
|
statusTopic = moduleConfig.mqtt.root + statusTopic;
|
||||||
cryptTopic = moduleConfig.mqtt.root + cryptTopic;
|
cryptTopic = moduleConfig.mqtt.root + cryptTopic;
|
||||||
jsonTopic = moduleConfig.mqtt.root + jsonTopic;
|
jsonTopic = moduleConfig.mqtt.root + jsonTopic;
|
||||||
|
mapTopic = moduleConfig.mqtt.root + mapTopic;
|
||||||
} else {
|
} else {
|
||||||
statusTopic = "msh" + statusTopic;
|
statusTopic = "msh" + statusTopic;
|
||||||
cryptTopic = "msh" + cryptTopic;
|
cryptTopic = "msh" + cryptTopic;
|
||||||
jsonTopic = "msh" + jsonTopic;
|
jsonTopic = "msh" + jsonTopic;
|
||||||
|
mapTopic = "msh" + mapTopic;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moduleConfig.mqtt.map_reporting_enabled && moduleConfig.mqtt.has_map_report_settings) {
|
||||||
|
map_position_precision = moduleConfig.mqtt.map_report_settings.position_precision;
|
||||||
|
map_publish_interval_secs = moduleConfig.mqtt.map_report_settings.publish_interval_secs;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAS_NETWORKING
|
#ifdef HAS_NETWORKING
|
||||||
@ -365,27 +371,30 @@ void MQTT::sendSubscriptions()
|
|||||||
|
|
||||||
bool MQTT::wantsLink() const
|
bool MQTT::wantsLink() const
|
||||||
{
|
{
|
||||||
bool hasChannel = false;
|
bool hasChannelorMapReport = false;
|
||||||
|
|
||||||
if (moduleConfig.mqtt.enabled) {
|
if (moduleConfig.mqtt.enabled) {
|
||||||
// No need for link if no channel needed it
|
hasChannelorMapReport = moduleConfig.mqtt.map_reporting_enabled;
|
||||||
size_t numChan = channels.getNumChannels();
|
if (!hasChannelorMapReport) {
|
||||||
for (size_t i = 0; i < numChan; i++) {
|
// No need for link if no channel needed it
|
||||||
const auto &ch = channels.getByIndex(i);
|
size_t numChan = channels.getNumChannels();
|
||||||
if (ch.settings.uplink_enabled || ch.settings.downlink_enabled) {
|
for (size_t i = 0; i < numChan; i++) {
|
||||||
hasChannel = true;
|
const auto &ch = channels.getByIndex(i);
|
||||||
break;
|
if (ch.settings.uplink_enabled || ch.settings.downlink_enabled) {
|
||||||
|
hasChannelorMapReport = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasChannel && moduleConfig.mqtt.proxy_to_client_enabled)
|
if (hasChannelorMapReport && moduleConfig.mqtt.proxy_to_client_enabled)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
#if HAS_WIFI
|
#if HAS_WIFI
|
||||||
return hasChannel && WiFi.isConnected();
|
return hasChannelorMapReport && WiFi.isConnected();
|
||||||
#endif
|
#endif
|
||||||
#if HAS_ETHERNET
|
#if HAS_ETHERNET
|
||||||
return hasChannel && Ethernet.linkStatus() == LinkON;
|
return hasChannelorMapReport && Ethernet.linkStatus() == LinkON;
|
||||||
#endif
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -397,6 +406,8 @@ int32_t MQTT::runOnce()
|
|||||||
|
|
||||||
bool wantConnection = wantsLink();
|
bool wantConnection = wantsLink();
|
||||||
|
|
||||||
|
perhapsReportToMap();
|
||||||
|
|
||||||
// If connected poll rapidly, otherwise only occasionally check for a wifi connection change and ability to contact server
|
// If connected poll rapidly, otherwise only occasionally check for a wifi connection change and ability to contact server
|
||||||
if (moduleConfig.mqtt.proxy_to_client_enabled) {
|
if (moduleConfig.mqtt.proxy_to_client_enabled) {
|
||||||
publishQueuedMessages();
|
publishQueuedMessages();
|
||||||
@ -536,6 +547,78 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket &
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MQTT::perhapsReportToMap()
|
||||||
|
{
|
||||||
|
if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (millis() - last_report_to_map < map_publish_interval_secs * 1000) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) {
|
||||||
|
LOG_WARN("MQTT Map reporting is enabled, but precision is 0 or no position available.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate ServiceEnvelope and fill it
|
||||||
|
meshtastic_ServiceEnvelope *se = mqttPool.allocZeroed();
|
||||||
|
se->channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()); // Use primary channel as the channel_id
|
||||||
|
se->gateway_id = owner.id;
|
||||||
|
|
||||||
|
// Allocate MeshPacket and fill it
|
||||||
|
meshtastic_MeshPacket *mp = packetPool.allocZeroed();
|
||||||
|
mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag;
|
||||||
|
mp->from = nodeDB.getNodeNum();
|
||||||
|
mp->to = NODENUM_BROADCAST;
|
||||||
|
mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP;
|
||||||
|
|
||||||
|
// Fill MapReport message
|
||||||
|
meshtastic_MapReport mapReport = meshtastic_MapReport_init_default;
|
||||||
|
memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name));
|
||||||
|
memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name));
|
||||||
|
mapReport.role = config.device.role;
|
||||||
|
mapReport.hw_model = owner.hw_model;
|
||||||
|
strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version));
|
||||||
|
mapReport.region = config.lora.region;
|
||||||
|
mapReport.modem_preset = config.lora.modem_preset;
|
||||||
|
mapReport.has_default_channel = channels.hasDefaultChannel();
|
||||||
|
|
||||||
|
// Set position with precision (same as in PositionModule)
|
||||||
|
if (map_position_precision < 32 && map_position_precision > 0) {
|
||||||
|
mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision));
|
||||||
|
mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision));
|
||||||
|
mapReport.latitude_i += (1 << (31 - map_position_precision));
|
||||||
|
mapReport.longitude_i += (1 << (31 - map_position_precision));
|
||||||
|
} else {
|
||||||
|
mapReport.latitude_i = localPosition.latitude_i;
|
||||||
|
mapReport.longitude_i = localPosition.longitude_i;
|
||||||
|
}
|
||||||
|
mapReport.altitude = localPosition.altitude;
|
||||||
|
mapReport.position_precision = map_position_precision;
|
||||||
|
|
||||||
|
mapReport.num_online_local_nodes = nodeDB.getNumOnlineMeshNodes(true);
|
||||||
|
|
||||||
|
// Encode MapReport message and set it to MeshPacket in ServiceEnvelope
|
||||||
|
mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes),
|
||||||
|
&meshtastic_MapReport_msg, &mapReport);
|
||||||
|
se->packet = mp;
|
||||||
|
|
||||||
|
// FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets
|
||||||
|
static uint8_t bytes[meshtastic_MeshPacket_size + 64];
|
||||||
|
size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se);
|
||||||
|
|
||||||
|
LOG_INFO("MQTT Publish map report to %s\n", mapTopic.c_str());
|
||||||
|
publish(mapTopic.c_str(), bytes, numBytes, false);
|
||||||
|
|
||||||
|
// Release the allocated memory for ServiceEnvelope and MeshPacket
|
||||||
|
mqttPool.release(se);
|
||||||
|
packetPool.release(mp);
|
||||||
|
|
||||||
|
// Update the last report time
|
||||||
|
last_report_to_map = millis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// converts a downstream packet into a json message
|
// converts a downstream packet into a json message
|
||||||
std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp)
|
std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp)
|
||||||
{
|
{
|
||||||
|
@ -79,11 +79,18 @@ class MQTT : private concurrency::OSThread
|
|||||||
virtual int32_t runOnce() override;
|
virtual int32_t runOnce() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string statusTopic = "/2/stat/";
|
std::string statusTopic = "/2/stat/"; // For "online"/"offline" message
|
||||||
std::string cryptTopic = "/2/e/"; // msh/2/e/CHANNELID/NODEID
|
std::string cryptTopic = "/2/e/"; // msh/2/e/CHANNELID/NODEID
|
||||||
std::string jsonTopic = "/2/json/"; // msh/2/json/CHANNELID/NODEID
|
std::string jsonTopic = "/2/json/"; // msh/2/json/CHANNELID/NODEID
|
||||||
/** return true if we have a channel that wants uplink/downlink
|
std::string mapTopic = "/2/map/"; // For protobuf-encoded MapReport messages
|
||||||
*/
|
|
||||||
|
// For map reporting (only applies when enabled)
|
||||||
|
uint32_t last_report_to_map = 0;
|
||||||
|
uint32_t map_position_precision = 32; // default to full precision
|
||||||
|
uint32_t map_publish_interval_secs = 60 * 15; // default to 15 minutes
|
||||||
|
|
||||||
|
/** return true if we have a channel that wants uplink/downlink or map reporting is enabled
|
||||||
|
*/
|
||||||
bool wantsLink() const;
|
bool wantsLink() const;
|
||||||
|
|
||||||
/** Tell the server what subscriptions we want (based on channels.downlink_enabled)
|
/** Tell the server what subscriptions we want (based on channels.downlink_enabled)
|
||||||
@ -102,6 +109,9 @@ class MQTT : private concurrency::OSThread
|
|||||||
void publishStatus();
|
void publishStatus();
|
||||||
void publishQueuedMessages();
|
void publishQueuedMessages();
|
||||||
|
|
||||||
|
// Check if we should report unencrypted information about our node for consumption by a map
|
||||||
|
void perhapsReportToMap();
|
||||||
|
|
||||||
// returns true if this is a valid JSON envelope which we accept on downlink
|
// returns true if this is a valid JSON envelope which we accept on downlink
|
||||||
bool isValidJsonEnvelope(JSONObject &json);
|
bool isValidJsonEnvelope(JSONObject &json);
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ build_flags =
|
|||||||
-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
|
-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${esp32s3_base.lib_deps}
|
${esp32s3_base.lib_deps}
|
||||||
https://github.com/meshtastic/GxEPD2
|
https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a
|
||||||
adafruit/Adafruit BusIO@^1.13.2
|
adafruit/Adafruit BusIO@^1.13.2
|
||||||
lewisxhe/PCF8563_Library@^1.0.1
|
lewisxhe/PCF8563_Library@^1.0.1
|
||||||
upload_speed = 115200
|
upload_speed = 115200
|
@ -5,7 +5,6 @@
|
|||||||
#define I2C_SCL SCL
|
#define I2C_SCL SCL
|
||||||
|
|
||||||
#define USE_EINK
|
#define USE_EINK
|
||||||
#define EINK_NO_HIBERNATE
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* eink display pins
|
* eink display pins
|
||||||
|
@ -16,7 +16,7 @@ build_flags =
|
|||||||
;-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
|
;-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${esp32s3_base.lib_deps}
|
${esp32s3_base.lib_deps}
|
||||||
https://github.com/meshtastic/GxEPD2/
|
https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a
|
||||||
adafruit/Adafruit BusIO@^1.13.2
|
adafruit/Adafruit BusIO@^1.13.2
|
||||||
lewisxhe/PCF8563_Library@^1.0.1
|
lewisxhe/PCF8563_Library@^1.0.1
|
||||||
upload_speed = 115200
|
upload_speed = 115200
|
@ -11,10 +11,16 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo
|
|||||||
-DEINK_DISPLAY_MODEL=GxEPD2_154_D67
|
-DEINK_DISPLAY_MODEL=GxEPD2_154_D67
|
||||||
-DEINK_WIDTH=200
|
-DEINK_WIDTH=200
|
||||||
-DEINK_HEIGHT=200
|
-DEINK_HEIGHT=200
|
||||||
|
-DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
|
||||||
|
-DEINK_LIMIT_FASTREFRESH=20 ; How many consecutive fast-refreshes are permitted
|
||||||
|
-DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates
|
||||||
|
-DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
|
||||||
|
-DEINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
|
||||||
|
-DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
|
||||||
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo>
|
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo>
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${nrf52840_base.lib_deps}
|
${nrf52840_base.lib_deps}
|
||||||
https://github.com/meshtastic/GxEPD2#afce87a97dda1ac31d8a28dc8fa7c6f55dc96a61
|
https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a
|
||||||
adafruit/Adafruit BusIO@^1.13.2
|
adafruit/Adafruit BusIO@^1.13.2
|
||||||
lewisxhe/PCF8563_Library@^1.0.1
|
lewisxhe/PCF8563_Library@^1.0.1
|
||||||
;upload_protocol = fs
|
;upload_protocol = fs
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[VERSION]
|
[VERSION]
|
||||||
major = 2
|
major = 2
|
||||||
minor = 3
|
minor = 3
|
||||||
build = 0
|
build = 1
|
||||||
|
Loading…
Reference in New Issue
Block a user