mirror of
https://github.com/meshtastic/firmware.git
synced 2026-01-27 04:02:05 +00:00
Compare commits
117 Commits
InkHUD-Imp
...
sfpp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f36406e5ef | ||
|
|
57a3ff8dfc | ||
|
|
6cff13623f | ||
|
|
b312f226b4 | ||
|
|
7221fc4d4b | ||
|
|
6b88d37b73 | ||
|
|
d407ec1975 | ||
|
|
6d6a0734b0 | ||
|
|
0157a769c3 | ||
|
|
73932dd1c3 | ||
|
|
bc2abf3db4 | ||
|
|
073eb2c672 | ||
|
|
4744010295 | ||
|
|
3e3299f549 | ||
|
|
fb3bf783dd | ||
|
|
fc268d43d0 | ||
|
|
c38aff7e52 | ||
|
|
7f565fd524 | ||
|
|
5af6a48326 | ||
|
|
5633848d75 | ||
|
|
8b8c1881a8 | ||
|
|
a1d6978626 | ||
|
|
a67cf0f726 | ||
|
|
456fa3ddeb | ||
|
|
5adc9663b7 | ||
|
|
5de0654819 | ||
|
|
ab781e9f2d | ||
|
|
595b5f19b3 | ||
|
|
4bb93c1ed2 | ||
|
|
5582e94009 | ||
|
|
e33fbca8d6 | ||
|
|
aca7fe9f95 | ||
|
|
5a0644cd4f | ||
|
|
e990198628 | ||
|
|
6c69d9e74c | ||
|
|
e03f1b5c5e | ||
|
|
f46a9dfe7b | ||
|
|
9824357c50 | ||
|
|
942f2cb3d1 | ||
|
|
76beeda392 | ||
|
|
821735495a | ||
|
|
9ab2ee3483 | ||
|
|
4e92f7fa09 | ||
|
|
ae2a06eccd | ||
|
|
3ae331eb89 | ||
|
|
74a6c9f447 | ||
|
|
c77709a327 | ||
|
|
325f7d2e55 | ||
|
|
c6fc7986f1 | ||
|
|
8ecce1eb5c | ||
|
|
21c0dcaabb | ||
|
|
1b13f872db | ||
|
|
8b5141ddb7 | ||
|
|
ee25a0a0e1 | ||
|
|
436f174bce | ||
|
|
a34cd4ca6f | ||
|
|
8c37669213 | ||
|
|
8a059bae23 | ||
|
|
1869f2108d | ||
|
|
f5b41c2f2c | ||
|
|
83c8875060 | ||
|
|
9134239faa | ||
|
|
b3d1d563e9 | ||
|
|
c7f816e63f | ||
|
|
dd4fb6b0bc | ||
|
|
87798429fa | ||
|
|
78baaf4484 | ||
|
|
b3b115b6a6 | ||
|
|
b90b5ff40e | ||
|
|
b7028fff08 | ||
|
|
7d6a0f20c6 | ||
|
|
9b7384507d | ||
|
|
7d7091ef94 | ||
|
|
baccd0c532 | ||
|
|
1fecdc7603 | ||
|
|
1625fd88d7 | ||
|
|
fe22460f25 | ||
|
|
f634b7dd60 | ||
|
|
f56e651787 | ||
|
|
55af6c4726 | ||
|
|
d272b28ed4 | ||
|
|
f8c27d1714 | ||
|
|
25383c9523 | ||
|
|
6d90b6536e | ||
|
|
0759197ab3 | ||
|
|
bbfca12d50 | ||
|
|
d44c3a8e1a | ||
|
|
1cef1094a0 | ||
|
|
02d4ca2983 | ||
|
|
36e8a498f1 | ||
|
|
d63b583ea2 | ||
|
|
14073e2c9f | ||
|
|
39a6ffc664 | ||
|
|
8be790890c | ||
|
|
426a7c19dd | ||
|
|
39c0824abb | ||
|
|
a8a5086b6d | ||
|
|
428b839254 | ||
|
|
a70d350ce3 | ||
|
|
00a3249c56 | ||
|
|
b51235d4fd | ||
|
|
d07f5be548 | ||
|
|
739ad0dc31 | ||
|
|
e8fd5174ec | ||
|
|
96726d22cd | ||
|
|
3330d297b1 | ||
|
|
e9ed2c0335 | ||
|
|
3cbc5b7a8d | ||
|
|
20bf822a48 | ||
|
|
14ee1ed075 | ||
|
|
4d48d517e0 | ||
|
|
ffdb3bc393 | ||
|
|
6e83a9a0b3 | ||
|
|
73cfa3c884 | ||
|
|
f2b6383cbb | ||
|
|
28d507f043 | ||
|
|
d508de9568 |
@@ -203,6 +203,16 @@ HostMetrics:
|
|||||||
# UserStringCommand: cat /sys/firmware/devicetree/base/serial-number # Command to execute, to send the results as the userString
|
# UserStringCommand: cat /sys/firmware/devicetree/base/serial-number # Command to execute, to send the results as the userString
|
||||||
|
|
||||||
|
|
||||||
|
StoreAndForward:
|
||||||
|
# Enabled: true # Enable Store and Forward++, true by default
|
||||||
|
# DBPath: /var/lib/meshtasticd/ # Path to the S&F++ Sqlite DB
|
||||||
|
# Stratum0: false # Specify if this node is a Stratum 0 node, the controller node.
|
||||||
|
# InitialSync: 10 # Number of messages to
|
||||||
|
# Hops: 3 # Number of hops to use for SF++ messages
|
||||||
|
# AnnounceInterval: 5 # Interval in minutes between announcing tip of chain hash
|
||||||
|
# MaxChainLength: 1000 # Maximum number of messages to store in a chain
|
||||||
|
|
||||||
|
|
||||||
Config:
|
Config:
|
||||||
# DisplayMode: TWOCOLOR # uncomment to force BaseUI
|
# DisplayMode: TWOCOLOR # uncomment to force BaseUI
|
||||||
# DisplayMode: COLOR # uncomment to force MUI
|
# DisplayMode: COLOR # uncomment to force MUI
|
||||||
|
|||||||
@@ -32,6 +32,19 @@ if ! command -v jq >/dev/null 2>&1; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# esptool v5 supports commands with dashes and deprecates commands with
|
||||||
|
# underscores. Prior versions only support commands with underscores
|
||||||
|
if ${ESPTOOL_CMD} | grep --quiet write-flash
|
||||||
|
then
|
||||||
|
ESPTOOL_WRITE_FLASH=write-flash
|
||||||
|
ESPTOOL_ERASE_FLASH=erase-flash
|
||||||
|
ESPTOOL_READ_FLASH_STATUS=read-flash-status
|
||||||
|
else
|
||||||
|
ESPTOOL_WRITE_FLASH=write_flash
|
||||||
|
ESPTOOL_ERASE_FLASH=erase_flash
|
||||||
|
ESPTOOL_READ_FLASH_STATUS=read_flash_status
|
||||||
|
fi
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Usage info
|
# Usage info
|
||||||
@@ -83,7 +96,7 @@ while [ $# -gt 0 ]; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
if [[ $BPS_RESET == true ]]; then
|
if [[ $BPS_RESET == true ]]; then
|
||||||
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
|
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset ${ESPTOOL_READ_FLASH_STATUS}
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -144,12 +157,12 @@ if [[ -f "$FILENAME" && "$FILENAME" == *.factory.bin ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
|
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
|
||||||
$ESPTOOL_CMD erase-flash
|
$ESPTOOL_CMD ${ESPTOOL_ERASE_FLASH}
|
||||||
$ESPTOOL_CMD write-flash $FIRMWARE_OFFSET "${FILENAME}"
|
$ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $FIRMWARE_OFFSET "${FILENAME}"
|
||||||
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
|
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
|
||||||
$ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}"
|
$ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $OTA_OFFSET "${OTAFILE}"
|
||||||
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
|
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
|
||||||
$ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}"
|
$ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $OFFSET "${SPIFFSFILE}"
|
||||||
|
|
||||||
else
|
else
|
||||||
show_help
|
show_help
|
||||||
|
|||||||
@@ -20,6 +20,17 @@ else
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# esptool v5 supports commands with dashes and deprecates commands with
|
||||||
|
# underscores. Prior versions only support commands with underscores
|
||||||
|
if ${ESPTOOL_CMD} | grep --quiet write-flash
|
||||||
|
then
|
||||||
|
ESPTOOL_WRITE_FLASH=write-flash
|
||||||
|
ESPTOOL_READ_FLASH_STATUS=read-flash-status
|
||||||
|
else
|
||||||
|
ESPTOOL_WRITE_FLASH=write_flash
|
||||||
|
ESPTOOL_READ_FLASH_STATUS=read_flash_status
|
||||||
|
fi
|
||||||
|
|
||||||
# Usage info
|
# Usage info
|
||||||
show_help() {
|
show_help() {
|
||||||
cat << EOF
|
cat << EOF
|
||||||
@@ -69,7 +80,7 @@ done
|
|||||||
shift "$((OPTIND-1))"
|
shift "$((OPTIND-1))"
|
||||||
|
|
||||||
if [ "$CHANGE_MODE" = true ]; then
|
if [ "$CHANGE_MODE" = true ]; then
|
||||||
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
|
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset ${ESPTOOL_READ_FLASH_STATUS}
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -80,7 +91,7 @@ fi
|
|||||||
|
|
||||||
if [[ -f "$FILENAME" && "$FILENAME" != *.factory.bin ]]; then
|
if [[ -f "$FILENAME" && "$FILENAME" != *.factory.bin ]]; then
|
||||||
echo "Trying to flash update ${FILENAME}"
|
echo "Trying to flash update ${FILENAME}"
|
||||||
$ESPTOOL_CMD --baud $FLASH_BAUD write-flash $UPDATE_OFFSET "${FILENAME}"
|
$ESPTOOL_CMD --baud $FLASH_BAUD ${ESPTOOL_WRITE_FLASH} $UPDATE_OFFSET "${FILENAME}"
|
||||||
else
|
else
|
||||||
show_help
|
show_help
|
||||||
echo "Invalid file: ${FILENAME}"
|
echo "Invalid file: ${FILENAME}"
|
||||||
|
|||||||
@@ -87,6 +87,9 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="2.7.19" date="2026-01-22">
|
||||||
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.19</url>
|
||||||
|
</release>
|
||||||
<release version="2.7.18" date="2026-01-02">
|
<release version="2.7.18" date="2026-01-02">
|
||||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.18</url>
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.18</url>
|
||||||
</release>
|
</release>
|
||||||
|
|||||||
50
boards/minimesh_lite.json
Normal file
50
boards/minimesh_lite.json
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"arduino": {
|
||||||
|
"ldscript": "nrf52840_s140_v6.ld"
|
||||||
|
},
|
||||||
|
"core": "nRF5",
|
||||||
|
"cpu": "cortex-m4",
|
||||||
|
"extra_flags": "-DMINIMESH_LITE -DNRF52840_XXAA",
|
||||||
|
"f_cpu": "64000000L",
|
||||||
|
"hwids": [
|
||||||
|
["0x239A", "0x8029"],
|
||||||
|
["0x239A", "0x0029"],
|
||||||
|
["0x239A", "0x002A"]
|
||||||
|
],
|
||||||
|
"usb_product": "Minimesh Lite",
|
||||||
|
"mcu": "nrf52840",
|
||||||
|
"variant": "dls_Minimesh_Lite",
|
||||||
|
"bsp": {
|
||||||
|
"name": "adafruit"
|
||||||
|
},
|
||||||
|
"softdevice": {
|
||||||
|
"sd_flags": "-DS140",
|
||||||
|
"sd_name": "s140",
|
||||||
|
"sd_version": "6.1.1",
|
||||||
|
"sd_fwid": "0x00B6"
|
||||||
|
},
|
||||||
|
"bootloader": {
|
||||||
|
"settings_addr": "0xFF000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"connectivity": ["bluetooth"],
|
||||||
|
"debug": {
|
||||||
|
"jlink_device": "nRF52840_xxAA",
|
||||||
|
"svd_path": "nrf52840.svd"
|
||||||
|
},
|
||||||
|
"frameworks": ["arduino"],
|
||||||
|
"name": "Minimesh Lite",
|
||||||
|
"upload": {
|
||||||
|
"maximum_ram_size": 248832,
|
||||||
|
"maximum_size": 815104,
|
||||||
|
"speed": 115200,
|
||||||
|
"protocol": "nrfutil",
|
||||||
|
"protocols": ["nrfutil", "jlink", "nrfjprog", "stlink"],
|
||||||
|
"use_1200bps_touch": true,
|
||||||
|
"require_upload_port": true,
|
||||||
|
"wait_for_upload_port": true
|
||||||
|
},
|
||||||
|
"url": "https://deeplabstudio.com",
|
||||||
|
"vendor": "Deeplab Studio"
|
||||||
|
}
|
||||||
6
debian/changelog
vendored
6
debian/changelog
vendored
@@ -1,3 +1,9 @@
|
|||||||
|
meshtasticd (2.7.19.0) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Version 2.7.19
|
||||||
|
|
||||||
|
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Thu, 22 Jan 2026 22:17:40 +0000
|
||||||
|
|
||||||
meshtasticd (2.7.18.0) unstable; urgency=medium
|
meshtasticd (2.7.18.0) unstable; urgency=medium
|
||||||
|
|
||||||
* Version 2.7.18
|
* Version 2.7.18
|
||||||
|
|||||||
@@ -43,13 +43,11 @@ class Esp32C3ExceptionDecoder(DeviceMonitorFilterBase):
|
|||||||
self.enabled = self.setup_paths()
|
self.enabled = self.setup_paths()
|
||||||
|
|
||||||
if self.config.get("env:" + self.environment, "build_type") != "debug":
|
if self.config.get("env:" + self.environment, "build_type") != "debug":
|
||||||
print(
|
print("""
|
||||||
"""
|
|
||||||
Please build project in debug configuration to get more details about an exception.
|
Please build project in debug configuration to get more details about an exception.
|
||||||
See https://docs.platformio.org/page/projectconf/build_configurations.html
|
See https://docs.platformio.org/page/projectconf/build_configurations.html
|
||||||
|
|
||||||
"""
|
""")
|
||||||
)
|
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ lib_deps =
|
|||||||
# renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=end2endzone/library/NonBlockingRTTTL
|
# renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=end2endzone/library/NonBlockingRTTTL
|
||||||
end2endzone/NonBlockingRTTTL@1.4.0
|
end2endzone/NonBlockingRTTTL@1.4.0
|
||||||
build_flags = ${env.build_flags} -Os
|
build_flags = ${env.build_flags} -Os
|
||||||
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/>
|
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/> -<modules/Native/>
|
||||||
|
|
||||||
; Common libs for communicating over TCP/IP networks such as MQTT
|
; Common libs for communicating over TCP/IP networks such as MQTT
|
||||||
[networking_base]
|
[networking_base]
|
||||||
@@ -119,7 +119,7 @@ lib_deps =
|
|||||||
[device-ui_base]
|
[device-ui_base]
|
||||||
lib_deps =
|
lib_deps =
|
||||||
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
||||||
https://github.com/meshtastic/device-ui/archive/3480b731d28b10d73414cf0dd7975bff745de8cf.zip
|
https://github.com/meshtastic/device-ui/archive/613c0953313bbd236f4ddc5ede447e9edf8e890a.zip
|
||||||
|
|
||||||
; Common libs for environmental measurements in telemetry module
|
; Common libs for environmental measurements in telemetry module
|
||||||
[environmental_base]
|
[environmental_base]
|
||||||
|
|||||||
204
src/Power.cpp
204
src/Power.cpp
@@ -1,11 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* @file Power.cpp
|
* @file Power.cpp
|
||||||
* @brief This file contains the implementation of the Power class, which is responsible for managing power-related functionality
|
* @brief This file contains the implementation of the Power class, which is
|
||||||
* of the device. It includes battery level sensing, power management unit (PMU) control, and power state machine management. The
|
* responsible for managing power-related functionality of the device. It
|
||||||
* Power class is used by the main device class to manage power-related functionality.
|
* includes battery level sensing, power management unit (PMU) control, and
|
||||||
|
* power state machine management. The Power class is used by the main device
|
||||||
|
* class to manage power-related functionality.
|
||||||
*
|
*
|
||||||
* The file also includes implementations of various battery level sensors, such as the AnalogBatteryLevel class, which assumes
|
* The file also includes implementations of various battery level sensors, such
|
||||||
* the battery voltage is attached via a voltage-divider to an analog input.
|
* as the AnalogBatteryLevel class, which assumes the battery voltage is
|
||||||
|
* attached via a voltage-divider to an analog input.
|
||||||
*
|
*
|
||||||
* This file is part of the Meshtastic project.
|
* This file is part of the Meshtastic project.
|
||||||
* For more information, see: https://meshtastic.org/
|
* For more information, see: https://meshtastic.org/
|
||||||
@@ -19,6 +22,7 @@
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "meshUtils.h"
|
#include "meshUtils.h"
|
||||||
|
#include "power/PowerHAL.h"
|
||||||
#include "sleep.h"
|
#include "sleep.h"
|
||||||
|
|
||||||
#if defined(ARCH_PORTDUINO)
|
#if defined(ARCH_PORTDUINO)
|
||||||
@@ -171,22 +175,12 @@ Power *power;
|
|||||||
|
|
||||||
using namespace meshtastic;
|
using namespace meshtastic;
|
||||||
|
|
||||||
#ifndef AREF_VOLTAGE
|
// NRF52 has AREF_VOLTAGE defined in architecture.h but
|
||||||
#if defined(ARCH_NRF52)
|
// make sure it's included. If something is wrong with NRF52
|
||||||
/*
|
// definition - compilation will fail on missing definition
|
||||||
* Internal Reference is +/-0.6V, with an adjustable gain of 1/6, 1/5, 1/4,
|
#if !defined(AREF_VOLTAGE) && !defined(ARCH_NRF52)
|
||||||
* 1/3, 1/2 or 1, meaning 3.6, 3.0, 2.4, 1.8, 1.2 or 0.6V for the ADC levels.
|
|
||||||
*
|
|
||||||
* External Reference is VDD/4, with an adjustable gain of 1, 2 or 4, meaning
|
|
||||||
* VDD/4, VDD/2 or VDD for the ADC levels.
|
|
||||||
*
|
|
||||||
* Default settings are internal reference with 1/6 gain (GND..3.6V ADC range)
|
|
||||||
*/
|
|
||||||
#define AREF_VOLTAGE 3.6
|
|
||||||
#else
|
|
||||||
#define AREF_VOLTAGE 3.3
|
#define AREF_VOLTAGE 3.3
|
||||||
#endif
|
#endif
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this board has a battery level sensor, set this to a valid implementation
|
* If this board has a battery level sensor, set this to a valid implementation
|
||||||
@@ -233,7 +227,8 @@ static void battery_adcDisable()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple battery level sensor that assumes the battery voltage is attached via a voltage-divider to an analog input
|
* A simple battery level sensor that assumes the battery voltage is attached
|
||||||
|
* via a voltage-divider to an analog input
|
||||||
*/
|
*/
|
||||||
class AnalogBatteryLevel : public HasBatteryLevel
|
class AnalogBatteryLevel : public HasBatteryLevel
|
||||||
{
|
{
|
||||||
@@ -311,7 +306,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
|
|
||||||
#ifndef BATTERY_SENSE_SAMPLES
|
#ifndef BATTERY_SENSE_SAMPLES
|
||||||
#define BATTERY_SENSE_SAMPLES \
|
#define BATTERY_SENSE_SAMPLES \
|
||||||
15 // Set the number of samples, it has an effect of increasing sensitivity in complex electromagnetic environment.
|
15 // Set the number of samples, it has an effect of increasing sensitivity in
|
||||||
|
// complex electromagnetic environment.
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef BATTERY_PIN
|
#ifdef BATTERY_PIN
|
||||||
@@ -341,7 +337,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
battery_adcDisable();
|
battery_adcDisable();
|
||||||
|
|
||||||
if (!initial_read_done) {
|
if (!initial_read_done) {
|
||||||
// Flush the smoothing filter with an ADC reading, if the reading is plausibly correct
|
// Flush the smoothing filter with an ADC reading, if the reading is
|
||||||
|
// plausibly correct
|
||||||
if (scaled > last_read_value)
|
if (scaled > last_read_value)
|
||||||
last_read_value = scaled;
|
last_read_value = scaled;
|
||||||
initial_read_done = true;
|
initial_read_done = true;
|
||||||
@@ -350,8 +347,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF
|
last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF
|
||||||
}
|
}
|
||||||
|
|
||||||
// LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u", BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t)
|
// LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u",
|
||||||
// (last_read_value));
|
// BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t) (last_read_value));
|
||||||
}
|
}
|
||||||
return last_read_value;
|
return last_read_value;
|
||||||
#endif // BATTERY_PIN
|
#endif // BATTERY_PIN
|
||||||
@@ -420,7 +417,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
/**
|
/**
|
||||||
* return true if there is a battery installed in this unit
|
* return true if there is a battery installed in this unit
|
||||||
*/
|
*/
|
||||||
// if we have a integrated device with a battery, we can assume that the battery is always connected
|
// if we have a integrated device with a battery, we can assume that the
|
||||||
|
// battery is always connected
|
||||||
#ifdef BATTERY_IMMUTABLE
|
#ifdef BATTERY_IMMUTABLE
|
||||||
virtual bool isBatteryConnect() override { return true; }
|
virtual bool isBatteryConnect() override { return true; }
|
||||||
#elif defined(ADC_V)
|
#elif defined(ADC_V)
|
||||||
@@ -441,10 +439,10 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
virtual bool isBatteryConnect() override { return getBatteryPercent() != -1; }
|
virtual bool isBatteryConnect() override { return getBatteryPercent() != -1; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// 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
|
||||||
/// in power
|
/// pumping in power On some boards we don't have the power management chip
|
||||||
/// On some boards we don't have the power management chip (like AXPxxxx)
|
/// (like AXPxxxx) so we use EXT_PWR_DETECT GPIO pin to detect external power
|
||||||
/// so we use EXT_PWR_DETECT GPIO pin to detect external power source
|
/// source
|
||||||
virtual bool isVbusIn() override
|
virtual bool isVbusIn() override
|
||||||
{
|
{
|
||||||
#ifdef EXT_PWR_DETECT
|
#ifdef EXT_PWR_DETECT
|
||||||
@@ -461,8 +459,12 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
}
|
}
|
||||||
// if it's not HIGH - check the battery
|
// if it's not HIGH - check the battery
|
||||||
#endif
|
#endif
|
||||||
#elif defined(MUZI_BASE)
|
|
||||||
return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk;
|
// technically speaking this should work for all(?) NRF52 boards
|
||||||
|
// but needs testing across multiple devices. NRF52 USB would not even work if
|
||||||
|
// VBUS was not properly connected and detected by the CPU
|
||||||
|
#elif defined(MUZI_BASE) || defined(PROMICRO_DIY_TCXO)
|
||||||
|
return powerHAL_isVBUSConnected();
|
||||||
#endif
|
#endif
|
||||||
return getBattVoltage() > chargingVolt;
|
return getBattVoltage() > chargingVolt;
|
||||||
}
|
}
|
||||||
@@ -485,8 +487,9 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
#else
|
#else
|
||||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION)
|
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION)
|
||||||
if (hasINA()) {
|
if (hasINA()) {
|
||||||
// get current flow from INA sensor - negative value means power flowing into the battery
|
// get current flow from INA sensor - negative value means power flowing
|
||||||
// default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT RESISTOR <--> INA_VIN- <--> LOAD
|
// into the battery default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT
|
||||||
|
// RESISTOR <--> INA_VIN- <--> LOAD
|
||||||
LOG_DEBUG("Using INA on I2C addr 0x%x for charging detection", config.power.device_battery_ina_address);
|
LOG_DEBUG("Using INA on I2C addr 0x%x for charging detection", config.power.device_battery_ina_address);
|
||||||
#if defined(INA_CHARGING_DETECTION_INVERT)
|
#if defined(INA_CHARGING_DETECTION_INVERT)
|
||||||
return getINACurrent() > 0;
|
return getINACurrent() > 0;
|
||||||
@@ -502,8 +505,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
/// in power
|
/// pumping in power
|
||||||
|
|
||||||
/// For heltecs with no battery connected, the measured voltage is 2204, so
|
/// For heltecs with no battery connected, the measured voltage is 2204, so
|
||||||
// need to be higher than that, in this case is 2500mV (3000-500)
|
// need to be higher than that, in this case is 2500mV (3000-500)
|
||||||
@@ -512,7 +515,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
const float noBatVolt = (OCV[NUM_OCV_POINTS - 1] - 500) * NUM_CELLS;
|
const float noBatVolt = (OCV[NUM_OCV_POINTS - 1] - 500) * NUM_CELLS;
|
||||||
// Start value from minimum voltage for the filter to not start from 0
|
// Start value from minimum voltage for the filter to not start from 0
|
||||||
// that could trigger some events.
|
// that could trigger some events.
|
||||||
// This value is over-written by the first ADC reading, it the voltage seems reasonable.
|
// This value is over-written by the first ADC reading, it the voltage seems
|
||||||
|
// reasonable.
|
||||||
bool initial_read_done = false;
|
bool initial_read_done = false;
|
||||||
float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS);
|
float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS);
|
||||||
uint32_t last_read_time_ms = 0;
|
uint32_t last_read_time_ms = 0;
|
||||||
@@ -654,7 +658,8 @@ bool Power::analogInit()
|
|||||||
#ifdef CONFIG_IDF_TARGET_ESP32S3
|
#ifdef CONFIG_IDF_TARGET_ESP32S3
|
||||||
// ESP32S3
|
// ESP32S3
|
||||||
else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) {
|
else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) {
|
||||||
LOG_INFO("ADC config based on Two Point values and fitting curve coefficients stored in eFuse");
|
LOG_INFO("ADC config based on Two Point values and fitting curve "
|
||||||
|
"coefficients stored in eFuse");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
else {
|
else {
|
||||||
@@ -662,13 +667,7 @@ bool Power::analogInit()
|
|||||||
}
|
}
|
||||||
#endif // ARCH_ESP32
|
#endif // ARCH_ESP32
|
||||||
|
|
||||||
#ifdef ARCH_NRF52
|
// NRF52 ADC init moved to powerHAL_init in nrf52 platform
|
||||||
#ifdef VBAT_AR_INTERNAL
|
|
||||||
analogReference(VBAT_AR_INTERNAL);
|
|
||||||
#else
|
|
||||||
analogReference(AR_INTERNAL); // 3.6V
|
|
||||||
#endif
|
|
||||||
#endif // ARCH_NRF52
|
|
||||||
|
|
||||||
#ifndef ARCH_ESP32
|
#ifndef ARCH_ESP32
|
||||||
analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS);
|
analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS);
|
||||||
@@ -723,6 +722,16 @@ bool Power::setup()
|
|||||||
runASAP = true;
|
runASAP = true;
|
||||||
},
|
},
|
||||||
CHANGE);
|
CHANGE);
|
||||||
|
#endif
|
||||||
|
#ifdef EXT_CHRG_DETECT
|
||||||
|
attachInterrupt(
|
||||||
|
EXT_CHRG_DETECT,
|
||||||
|
[]() {
|
||||||
|
power->setIntervalFromNow(0);
|
||||||
|
runASAP = true;
|
||||||
|
BaseType_t higherWake = 0;
|
||||||
|
},
|
||||||
|
CHANGE);
|
||||||
#endif
|
#endif
|
||||||
enabled = found;
|
enabled = found;
|
||||||
low_voltage_counter = 0;
|
low_voltage_counter = 0;
|
||||||
@@ -769,7 +778,8 @@ void Power::reboot()
|
|||||||
HAL_NVIC_SystemReset();
|
HAL_NVIC_SystemReset();
|
||||||
#else
|
#else
|
||||||
rebootAtMsec = -1;
|
rebootAtMsec = -1;
|
||||||
LOG_WARN("FIXME implement reboot for this platform. Note that some settings require a restart to be applied");
|
LOG_WARN("FIXME implement reboot for this platform. Note that some settings "
|
||||||
|
"require a restart to be applied");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -779,9 +789,12 @@ void Power::shutdown()
|
|||||||
#if HAS_SCREEN
|
#if HAS_SCREEN
|
||||||
if (screen) {
|
if (screen) {
|
||||||
#ifdef T_DECK_PRO
|
#ifdef T_DECK_PRO
|
||||||
screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button
|
screen->showSimpleBanner("Device is powered off.\nConnect USB to start!",
|
||||||
|
0); // T-Deck Pro has no power button
|
||||||
#elif defined(USE_EINK)
|
#elif defined(USE_EINK)
|
||||||
screen->showSimpleBanner("Shutting Down...", 2250); // dismiss after 3 seconds to avoid the banner on the sleep screen
|
screen->showSimpleBanner("Shutting Down...",
|
||||||
|
2250); // dismiss after 3 seconds to avoid the
|
||||||
|
// banner on the sleep screen
|
||||||
#else
|
#else
|
||||||
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
|
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
|
||||||
#endif
|
#endif
|
||||||
@@ -820,7 +833,8 @@ void Power::readPowerStatus()
|
|||||||
int32_t batteryVoltageMv = -1; // Assume unknown
|
int32_t batteryVoltageMv = -1; // Assume unknown
|
||||||
int8_t batteryChargePercent = -1;
|
int8_t batteryChargePercent = -1;
|
||||||
OptionalBool usbPowered = OptUnknown;
|
OptionalBool usbPowered = OptUnknown;
|
||||||
OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM code doesn't run every time
|
OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM
|
||||||
|
// code doesn't run every time
|
||||||
OptionalBool isChargingNow = OptUnknown;
|
OptionalBool isChargingNow = OptUnknown;
|
||||||
|
|
||||||
if (batteryLevel) {
|
if (batteryLevel) {
|
||||||
@@ -833,9 +847,10 @@ void Power::readPowerStatus()
|
|||||||
if (batteryLevel->getBatteryPercent() >= 0) {
|
if (batteryLevel->getBatteryPercent() >= 0) {
|
||||||
batteryChargePercent = batteryLevel->getBatteryPercent();
|
batteryChargePercent = batteryLevel->getBatteryPercent();
|
||||||
} else {
|
} else {
|
||||||
// If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error
|
// If the AXP192 returns a percentage less than 0, the feature is either
|
||||||
// In that case, we compute an estimate of the charge percent based on open circuit voltage table defined
|
// not supported or there is an error In that case, we compute an
|
||||||
// in power.h
|
// estimate of the charge percent based on open circuit voltage table
|
||||||
|
// defined in power.h
|
||||||
batteryChargePercent = clamp((int)(((batteryVoltageMv - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS)) * 1e2) /
|
batteryChargePercent = clamp((int)(((batteryVoltageMv - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS)) * 1e2) /
|
||||||
((OCV[0] * NUM_CELLS) - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS))),
|
((OCV[0] * NUM_CELLS) - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS))),
|
||||||
0, 100);
|
0, 100);
|
||||||
@@ -843,12 +858,12 @@ void Power::readPowerStatus()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way better instead to make a Nrf52IsUsbPowered subclass
|
// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way
|
||||||
// (which shares a superclass with the BatteryLevel stuff)
|
// better instead to make a Nrf52IsUsbPowered subclass (which shares a
|
||||||
// that just provides a few methods. But in the interest of fixing this bug I'm going to follow current
|
// superclass with the BatteryLevel stuff) that just provides a few methods. But
|
||||||
// practice.
|
// in the interest of fixing this bug I'm going to follow current practice.
|
||||||
#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates the power states. Takes 20 seconds or so to detect
|
#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates
|
||||||
// changes.
|
// the power states. Takes 20 seconds or so to detect changes.
|
||||||
|
|
||||||
nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get();
|
nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get();
|
||||||
// LOG_DEBUG("NRF Power %d", nrf_usb_state);
|
// LOG_DEBUG("NRF Power %d", nrf_usb_state);
|
||||||
@@ -922,8 +937,9 @@ void Power::readPowerStatus()
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// If we have a battery at all and it is less than 0%, force deep sleep if we have more than 10 low readings in
|
// If we have a battery at all and it is less than 0%, force deep sleep if we
|
||||||
// a row. NOTE: min LiIon/LiPo voltage is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough.
|
// have more than 10 low readings in a row. NOTE: min LiIon/LiPo voltage
|
||||||
|
// is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough.
|
||||||
//
|
//
|
||||||
|
|
||||||
if (batteryLevel && powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) {
|
if (batteryLevel && powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) {
|
||||||
@@ -945,8 +961,8 @@ int32_t Power::runOnce()
|
|||||||
readPowerStatus();
|
readPowerStatus();
|
||||||
|
|
||||||
#ifdef HAS_PMU
|
#ifdef HAS_PMU
|
||||||
// WE no longer use the IRQ line to wake the CPU (due to false wakes from sleep), but we do poll
|
// WE no longer use the IRQ line to wake the CPU (due to false wakes from
|
||||||
// the IRQ status by reading the registers over I2C
|
// sleep), but we do poll the IRQ status by reading the registers over I2C
|
||||||
if (PMU) {
|
if (PMU) {
|
||||||
|
|
||||||
PMU->getIrqStatus();
|
PMU->getIrqStatus();
|
||||||
@@ -988,7 +1004,8 @@ int32_t Power::runOnce()
|
|||||||
PMU->clearIrqStatus();
|
PMU->clearIrqStatus();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// Only read once every 20 seconds once the power status for the app has been initialized
|
// Only read once every 20 seconds once the power status for the app has been
|
||||||
|
// initialized
|
||||||
return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME;
|
return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -996,10 +1013,12 @@ int32_t Power::runOnce()
|
|||||||
* Init the power manager chip
|
* Init the power manager chip
|
||||||
*
|
*
|
||||||
* axp192 power
|
* axp192 power
|
||||||
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the
|
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose
|
||||||
axp192 share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this
|
comms to the axp192 because the OLED and the axp192 share the same i2c bus,
|
||||||
on!) LDO1 30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of
|
instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max ->
|
||||||
days), can not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
|
ESP32 (keep this on!) LDO1 30mA -> charges GPS backup battery // charges the
|
||||||
|
tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can
|
||||||
|
not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
bool Power::axpChipInit()
|
bool Power::axpChipInit()
|
||||||
@@ -1044,9 +1063,10 @@ bool Power::axpChipInit()
|
|||||||
|
|
||||||
if (!PMU) {
|
if (!PMU) {
|
||||||
/*
|
/*
|
||||||
* In XPowersLib, if the XPowersAXPxxx object is released, Wire.end() will be called at the same time.
|
* In XPowersLib, if the XPowersAXPxxx object is released, Wire.end() will
|
||||||
* In order not to affect other devices, if the initialization of the PMU fails, Wire needs to be re-initialized once,
|
* be called at the same time. In order not to affect other devices, if the
|
||||||
* if there are multiple devices sharing the bus.
|
* initialization of the PMU fails, Wire needs to be re-initialized once, if
|
||||||
|
* there are multiple devices sharing the bus.
|
||||||
* * */
|
* * */
|
||||||
#ifndef PMU_USE_WIRE1
|
#ifndef PMU_USE_WIRE1
|
||||||
w->begin(I2C_SDA, I2C_SCL);
|
w->begin(I2C_SDA, I2C_SCL);
|
||||||
@@ -1063,8 +1083,8 @@ bool Power::axpChipInit()
|
|||||||
PMU->enablePowerOutput(XPOWERS_LDO2);
|
PMU->enablePowerOutput(XPOWERS_LDO2);
|
||||||
|
|
||||||
// oled module power channel,
|
// oled module power channel,
|
||||||
// disable it will cause abnormal communication between boot and AXP power supply,
|
// disable it will cause abnormal communication between boot and AXP power
|
||||||
// do not turn it off
|
// supply, do not turn it off
|
||||||
PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300);
|
PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300);
|
||||||
// enable oled power
|
// enable oled power
|
||||||
PMU->enablePowerOutput(XPOWERS_DCDC1);
|
PMU->enablePowerOutput(XPOWERS_DCDC1);
|
||||||
@@ -1091,7 +1111,8 @@ bool Power::axpChipInit()
|
|||||||
PMU->setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2);
|
PMU->setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2);
|
||||||
} else if (PMU->getChipModel() == XPOWERS_AXP2101) {
|
} else if (PMU->getChipModel() == XPOWERS_AXP2101) {
|
||||||
|
|
||||||
/*The alternative version of T-Beam 1.1 differs from T-Beam V1.1 in that it uses an AXP2101 power chip*/
|
/*The alternative version of T-Beam 1.1 differs from T-Beam V1.1 in that it
|
||||||
|
* uses an AXP2101 power chip*/
|
||||||
if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) {
|
if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) {
|
||||||
// Unuse power channel
|
// Unuse power channel
|
||||||
PMU->disablePowerOutput(XPOWERS_DCDC2);
|
PMU->disablePowerOutput(XPOWERS_DCDC2);
|
||||||
@@ -1126,8 +1147,8 @@ bool Power::axpChipInit()
|
|||||||
// t-beam s3 core
|
// t-beam s3 core
|
||||||
/**
|
/**
|
||||||
* gnss module power channel
|
* gnss module power channel
|
||||||
* The default ALDO4 is off, you need to turn on the GNSS power first, otherwise it will be invalid during
|
* The default ALDO4 is off, you need to turn on the GNSS power first,
|
||||||
* initialization
|
* otherwise it will be invalid during initialization
|
||||||
*/
|
*/
|
||||||
PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300);
|
PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300);
|
||||||
PMU->enablePowerOutput(XPOWERS_ALDO4);
|
PMU->enablePowerOutput(XPOWERS_ALDO4);
|
||||||
@@ -1177,7 +1198,8 @@ bool Power::axpChipInit()
|
|||||||
// disable all axp chip interrupt
|
// disable all axp chip interrupt
|
||||||
PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
|
PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
|
||||||
|
|
||||||
// Set the constant current charging current of AXP2101, temporarily use 500mA by default
|
// Set the constant current charging current of AXP2101, temporarily use
|
||||||
|
// 500mA by default
|
||||||
PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA);
|
PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA);
|
||||||
|
|
||||||
// Set up the charging voltage
|
// Set up the charging voltage
|
||||||
@@ -1243,11 +1265,12 @@ bool Power::axpChipInit()
|
|||||||
PMU->getPowerChannelVoltage(XPOWERS_BLDO2));
|
PMU->getPowerChannelVoltage(XPOWERS_BLDO2));
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can safely ignore this approach for most (or all) boards because MCU turned off
|
// We can safely ignore this approach for most (or all) boards because MCU
|
||||||
// earlier than battery discharged to 2.6V.
|
// turned off earlier than battery discharged to 2.6V.
|
||||||
//
|
//
|
||||||
// Unfortanly for now we can't use this killswitch for RAK4630-based boards because they have a bug with
|
// Unfortunately for now we can't use this killswitch for RAK4630-based boards
|
||||||
// battery voltage measurement. Probably it sometimes drops to low values.
|
// because they have a bug with battery voltage measurement. Probably it
|
||||||
|
// sometimes drops to low values.
|
||||||
#ifndef RAK4630
|
#ifndef RAK4630
|
||||||
// Set PMU shutdown voltage at 2.6V to maximize battery utilization
|
// Set PMU shutdown voltage at 2.6V to maximize battery utilization
|
||||||
PMU->setSysPowerDownVoltage(2600);
|
PMU->setSysPowerDownVoltage(2600);
|
||||||
@@ -1266,10 +1289,12 @@ bool Power::axpChipInit()
|
|||||||
attachInterrupt(
|
attachInterrupt(
|
||||||
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
||||||
|
|
||||||
// we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ because it occurs repeatedly while there is
|
// we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ
|
||||||
// no battery also it could cause inadvertent waking from light sleep just because the battery filled
|
// because it occurs repeatedly while there is no battery also it could cause
|
||||||
// we don't look for AXPXXX_BATT_REMOVED_IRQ because it occurs repeatedly while no battery installed
|
// inadvertent waking from light sleep just because the battery filled we
|
||||||
// we don't look at AXPXXX_VBUS_REMOVED_IRQ because we don't have anything hooked to vbus
|
// don't look for AXPXXX_BATT_REMOVED_IRQ because it occurs repeatedly while
|
||||||
|
// no battery installed we don't look at AXPXXX_VBUS_REMOVED_IRQ because we
|
||||||
|
// don't have anything hooked to vbus
|
||||||
PMU->enableIRQ(pmuIrqMask);
|
PMU->enableIRQ(pmuIrqMask);
|
||||||
|
|
||||||
PMU->clearIrqStatus();
|
PMU->clearIrqStatus();
|
||||||
@@ -1385,8 +1410,8 @@ class LipoCharger : public HasBatteryLevel
|
|||||||
bool result = PPM->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR);
|
bool result = PPM->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR);
|
||||||
if (result) {
|
if (result) {
|
||||||
LOG_INFO("PPM BQ25896 init succeeded");
|
LOG_INFO("PPM BQ25896 init succeeded");
|
||||||
// Set the minimum operating voltage. Below this voltage, the PPM will protect
|
// Set the minimum operating voltage. Below this voltage, the PPM will
|
||||||
// PPM->setSysPowerDownVoltage(3100);
|
// protect PPM->setSysPowerDownVoltage(3100);
|
||||||
|
|
||||||
// Set input current limit, default is 500mA
|
// Set input current limit, default is 500mA
|
||||||
// PPM->setInputCurrentLimit(800);
|
// PPM->setInputCurrentLimit(800);
|
||||||
@@ -1409,7 +1434,8 @@ class LipoCharger : public HasBatteryLevel
|
|||||||
PPM->enableMeasure();
|
PPM->enableMeasure();
|
||||||
|
|
||||||
// Turn on charging function
|
// Turn on charging function
|
||||||
// If there is no battery connected, do not turn on the charging function
|
// If there is no battery connected, do not turn on the charging
|
||||||
|
// function
|
||||||
PPM->enableCharge();
|
PPM->enableCharge();
|
||||||
} else {
|
} else {
|
||||||
LOG_WARN("PPM BQ25896 init failed");
|
LOG_WARN("PPM BQ25896 init failed");
|
||||||
@@ -1444,7 +1470,8 @@ class LipoCharger : public HasBatteryLevel
|
|||||||
virtual int getBatteryPercent() override
|
virtual int getBatteryPercent() override
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
// return bq->getChargePercent(); // don't use BQ27220 for battery percent, it is not calibrated
|
// return bq->getChargePercent(); // don't use BQ27220 for battery percent,
|
||||||
|
// it is not calibrated
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1566,7 +1593,8 @@ bool Power::meshSolarInit()
|
|||||||
|
|
||||||
#else
|
#else
|
||||||
/**
|
/**
|
||||||
* The meshSolar battery level sensor is unavailable - default to AnalogBatteryLevel
|
* The meshSolar battery level sensor is unavailable - default to
|
||||||
|
* AnalogBatteryLevel
|
||||||
*/
|
*/
|
||||||
bool Power::meshSolarInit()
|
bool Power::meshSolarInit()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
#include "MessageStore.h"
|
#include "MessageStore.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "UIRenderer.h"
|
#include "UIRenderer.h"
|
||||||
#include "configuration.h"
|
|
||||||
#include "gps/RTC.h"
|
#include "gps/RTC.h"
|
||||||
#include "graphics/Screen.h"
|
#include "graphics/Screen.h"
|
||||||
#include "graphics/ScreenFonts.h"
|
#include "graphics/ScreenFonts.h"
|
||||||
@@ -20,7 +19,6 @@
|
|||||||
|
|
||||||
// External declarations
|
// External declarations
|
||||||
extern bool hasUnreadMessage;
|
extern bool hasUnreadMessage;
|
||||||
extern meshtastic_DeviceState devicestate;
|
|
||||||
extern graphics::Screen *screen;
|
extern graphics::Screen *screen;
|
||||||
|
|
||||||
using graphics::Emote;
|
using graphics::Emote;
|
||||||
@@ -49,7 +47,7 @@ static inline size_t utf8CharLen(uint8_t c)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove variation selectors (FE0F) and skin tone modifiers from emoji so they match your labels
|
// Remove variation selectors (FE0F) and skin tone modifiers from emoji so they match your labels
|
||||||
std::string normalizeEmoji(const std::string &s)
|
static std::string normalizeEmoji(const std::string &s)
|
||||||
{
|
{
|
||||||
std::string out;
|
std::string out;
|
||||||
for (size_t i = 0; i < s.size();) {
|
for (size_t i = 0; i < s.size();) {
|
||||||
@@ -82,6 +80,7 @@ uint32_t pauseStart = 0;
|
|||||||
bool waitingToReset = false;
|
bool waitingToReset = false;
|
||||||
bool scrollStarted = false;
|
bool scrollStarted = false;
|
||||||
static bool didReset = false;
|
static bool didReset = false;
|
||||||
|
static constexpr int MESSAGE_BLOCK_GAP = 6;
|
||||||
|
|
||||||
void scrollUp()
|
void scrollUp()
|
||||||
{
|
{
|
||||||
@@ -111,22 +110,6 @@ void scrollDown()
|
|||||||
|
|
||||||
void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string &line, const Emote *emotes, int emoteCount)
|
void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string &line, const Emote *emotes, int emoteCount)
|
||||||
{
|
{
|
||||||
std::string renderLine;
|
|
||||||
for (size_t i = 0; i < line.size();) {
|
|
||||||
uint8_t c = (uint8_t)line[i];
|
|
||||||
size_t len = utf8CharLen(c);
|
|
||||||
if (c == 0xEF && i + 2 < line.size() && (uint8_t)line[i + 1] == 0xB8 && (uint8_t)line[i + 2] == 0x8F) {
|
|
||||||
i += 3;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (c == 0xF0 && i + 3 < line.size() && (uint8_t)line[i + 1] == 0x9F && (uint8_t)line[i + 2] == 0x8F &&
|
|
||||||
((uint8_t)line[i + 3] >= 0xBB && (uint8_t)line[i + 3] <= 0xBF)) {
|
|
||||||
i += 4;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
renderLine.append(line, i, len);
|
|
||||||
i += len;
|
|
||||||
}
|
|
||||||
int cursorX = x;
|
int cursorX = x;
|
||||||
const int fontHeight = FONT_HEIGHT_SMALL;
|
const int fontHeight = FONT_HEIGHT_SMALL;
|
||||||
|
|
||||||
@@ -203,8 +186,7 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string
|
|||||||
|
|
||||||
// Render the emote (if found)
|
// Render the emote (if found)
|
||||||
if (matchedEmote && i == nextEmotePos) {
|
if (matchedEmote && i == nextEmotePos) {
|
||||||
// Vertically center emote relative to font baseline (not just midline)
|
int iconY = y + (lineHeight - matchedEmote->height) / 2;
|
||||||
int iconY = fontY + (fontHeight - matchedEmote->height) / 2;
|
|
||||||
display->drawXbm(cursorX, iconY, matchedEmote->width, matchedEmote->height, matchedEmote->bitmap);
|
display->drawXbm(cursorX, iconY, matchedEmote->width, matchedEmote->height, matchedEmote->bitmap);
|
||||||
cursorX += matchedEmote->width + 1;
|
cursorX += matchedEmote->width + 1;
|
||||||
i += emojiLen;
|
i += emojiLen;
|
||||||
@@ -423,6 +405,102 @@ static inline int getRenderedLineWidth(OLEDDisplay *display, const std::string &
|
|||||||
return totalWidth;
|
return totalWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct MessageBlock {
|
||||||
|
size_t start;
|
||||||
|
size_t end;
|
||||||
|
bool mine;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int getDrawnLinePixelBottom(int lineTopY, const std::string &line, bool isHeaderLine)
|
||||||
|
{
|
||||||
|
if (isHeaderLine) {
|
||||||
|
return lineTopY + (FONT_HEIGHT_SMALL - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int tallest = FONT_HEIGHT_SMALL;
|
||||||
|
for (int e = 0; e < numEmotes; ++e) {
|
||||||
|
if (line.find(emotes[e].label) != std::string::npos) {
|
||||||
|
if (emotes[e].height > tallest)
|
||||||
|
tallest = emotes[e].height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int lineHeight = std::max(FONT_HEIGHT_SMALL, tallest);
|
||||||
|
const int iconTop = lineTopY + (lineHeight - tallest) / 2;
|
||||||
|
|
||||||
|
return iconTop + tallest - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawRoundedRectOutline(OLEDDisplay *display, int x, int y, int w, int h, int r)
|
||||||
|
{
|
||||||
|
if (w <= 1 || h <= 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (r < 0)
|
||||||
|
r = 0;
|
||||||
|
|
||||||
|
int maxR = (std::min(w, h) / 2) - 1;
|
||||||
|
if (r > maxR)
|
||||||
|
r = maxR;
|
||||||
|
|
||||||
|
if (r == 0) {
|
||||||
|
display->drawRect(x, y, w, h);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int x0 = x;
|
||||||
|
const int y0 = y;
|
||||||
|
const int x1 = x + w - 1;
|
||||||
|
const int y1 = y + h - 1;
|
||||||
|
|
||||||
|
// sides
|
||||||
|
if (x0 + r <= x1 - r) {
|
||||||
|
display->drawLine(x0 + r, y0, x1 - r, y0); // top
|
||||||
|
display->drawLine(x0 + r, y1, x1 - r, y1); // bottom
|
||||||
|
}
|
||||||
|
if (y0 + r <= y1 - r) {
|
||||||
|
display->drawLine(x0, y0 + r, x0, y1 - r); // left
|
||||||
|
display->drawLine(x1, y0 + r, x1, y1 - r); // right
|
||||||
|
}
|
||||||
|
|
||||||
|
// corner arcs
|
||||||
|
display->drawCircleQuads(x0 + r, y0 + r, r, 2); // top left
|
||||||
|
display->drawCircleQuads(x1 - r, y0 + r, r, 1); // top right
|
||||||
|
display->drawCircleQuads(x1 - r, y1 - r, r, 8); // bottom right
|
||||||
|
display->drawCircleQuads(x0 + r, y1 - r, r, 4); // bottom left
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<MessageBlock> buildMessageBlocks(const std::vector<bool> &isHeaderVec, const std::vector<bool> &isMineVec)
|
||||||
|
{
|
||||||
|
std::vector<MessageBlock> blocks;
|
||||||
|
if (isHeaderVec.empty())
|
||||||
|
return blocks;
|
||||||
|
|
||||||
|
size_t start = 0;
|
||||||
|
bool mine = isMineVec[0];
|
||||||
|
|
||||||
|
for (size_t i = 1; i < isHeaderVec.size(); ++i) {
|
||||||
|
if (isHeaderVec[i]) {
|
||||||
|
MessageBlock b;
|
||||||
|
b.start = start;
|
||||||
|
b.end = i - 1;
|
||||||
|
b.mine = mine;
|
||||||
|
blocks.push_back(b);
|
||||||
|
|
||||||
|
start = i;
|
||||||
|
mine = isMineVec[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageBlock last;
|
||||||
|
last.start = start;
|
||||||
|
last.end = isHeaderVec.size() - 1;
|
||||||
|
last.mine = mine;
|
||||||
|
blocks.push_back(last);
|
||||||
|
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
static void drawMessageScrollbar(OLEDDisplay *display, int visibleHeight, int totalHeight, int scrollOffset, int startY)
|
static void drawMessageScrollbar(OLEDDisplay *display, int visibleHeight, int totalHeight, int scrollOffset, int startY)
|
||||||
{
|
{
|
||||||
if (totalHeight <= visibleHeight)
|
if (totalHeight <= visibleHeight)
|
||||||
@@ -482,9 +560,14 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
constexpr int LEFT_MARGIN = 2;
|
constexpr int LEFT_MARGIN = 2;
|
||||||
constexpr int RIGHT_MARGIN = 2;
|
constexpr int RIGHT_MARGIN = 2;
|
||||||
constexpr int SCROLLBAR_WIDTH = 3;
|
constexpr int SCROLLBAR_WIDTH = 3;
|
||||||
|
constexpr int BUBBLE_PAD_X = 3;
|
||||||
|
constexpr int BUBBLE_PAD_Y = 4;
|
||||||
|
constexpr int BUBBLE_RADIUS = 4;
|
||||||
|
constexpr int BUBBLE_MIN_W = 24;
|
||||||
|
constexpr int BUBBLE_TEXT_INDENT = 2;
|
||||||
|
|
||||||
const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN;
|
// Derived widths
|
||||||
|
const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - (BUBBLE_PAD_X * 2);
|
||||||
const int rightTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - SCROLLBAR_WIDTH;
|
const int rightTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - SCROLLBAR_WIDTH;
|
||||||
|
|
||||||
// Title string depending on mode
|
// Title string depending on mode
|
||||||
@@ -547,7 +630,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
char chanType[32] = "";
|
char chanType[32] = "";
|
||||||
if (currentMode == ThreadMode::ALL) {
|
if (currentMode == ThreadMode::ALL) {
|
||||||
if (m.dest == NODENUM_BROADCAST) {
|
if (m.dest == NODENUM_BROADCAST) {
|
||||||
snprintf(chanType, sizeof(chanType), "#%s", channels.getName(m.channelIndex));
|
const char *name = channels.getName(m.channelIndex);
|
||||||
|
if (currentResolution == ScreenResolution::Low || currentResolution == ScreenResolution::UltraLow) {
|
||||||
|
if (strcmp(name, "ShortTurbo") == 0)
|
||||||
|
name = "ShortT";
|
||||||
|
else if (strcmp(name, "ShortSlow") == 0)
|
||||||
|
name = "ShortS";
|
||||||
|
else if (strcmp(name, "ShortFast") == 0)
|
||||||
|
name = "ShortF";
|
||||||
|
else if (strcmp(name, "MediumSlow") == 0)
|
||||||
|
name = "MedS";
|
||||||
|
else if (strcmp(name, "MediumFast") == 0)
|
||||||
|
name = "MedF";
|
||||||
|
else if (strcmp(name, "LongSlow") == 0)
|
||||||
|
name = "LongS";
|
||||||
|
else if (strcmp(name, "LongFast") == 0)
|
||||||
|
name = "LongF";
|
||||||
|
else if (strcmp(name, "LongTurbo") == 0)
|
||||||
|
name = "LongT";
|
||||||
|
else if (strcmp(name, "LongMod") == 0)
|
||||||
|
name = "LongM";
|
||||||
|
}
|
||||||
|
snprintf(chanType, sizeof(chanType), "#%s", name);
|
||||||
} else {
|
} else {
|
||||||
snprintf(chanType, sizeof(chanType), "(DM)");
|
snprintf(chanType, sizeof(chanType), "(DM)");
|
||||||
}
|
}
|
||||||
@@ -614,8 +718,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Shrink Sender name if needed
|
// Shrink Sender name if needed
|
||||||
int availWidth = SCREEN_WIDTH - display->getStringWidth(timeBuf) - display->getStringWidth(chanType) -
|
int availWidth = (mine ? rightTextWidth : leftTextWidth) - display->getStringWidth(timeBuf) -
|
||||||
display->getStringWidth(" @...") - 10;
|
display->getStringWidth(chanType) - display->getStringWidth(" @...");
|
||||||
if (availWidth < 0)
|
if (availWidth < 0)
|
||||||
availWidth = 0;
|
availWidth = 0;
|
||||||
|
|
||||||
@@ -667,6 +771,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
cachedLines = allLines;
|
cachedLines = allLines;
|
||||||
cachedHeights = calculateLineHeights(cachedLines, emotes, isHeader);
|
cachedHeights = calculateLineHeights(cachedLines, emotes, isHeader);
|
||||||
|
|
||||||
|
std::vector<MessageBlock> blocks = buildMessageBlocks(isHeader, isMine);
|
||||||
|
|
||||||
// Scrolling logic (unchanged)
|
// Scrolling logic (unchanged)
|
||||||
int totalHeight = 0;
|
int totalHeight = 0;
|
||||||
for (size_t i = 0; i < cachedHeights.size(); ++i)
|
for (size_t i = 0; i < cachedHeights.size(); ++i)
|
||||||
@@ -714,12 +820,123 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
|
|
||||||
int finalScroll = (int)scrollY;
|
int finalScroll = (int)scrollY;
|
||||||
int yOffset = -finalScroll + getTextPositions(display)[1];
|
int yOffset = -finalScroll + getTextPositions(display)[1];
|
||||||
|
const int contentTop = getTextPositions(display)[1];
|
||||||
|
const int contentBottom = scrollBottom; // already excludes nav line
|
||||||
|
const int rightEdge = SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN;
|
||||||
|
const int bubbleGapY = std::max(1, MESSAGE_BLOCK_GAP / 2);
|
||||||
|
|
||||||
|
std::vector<int> lineTop;
|
||||||
|
lineTop.resize(cachedLines.size());
|
||||||
|
{
|
||||||
|
int acc = 0;
|
||||||
|
for (size_t i = 0; i < cachedLines.size(); ++i) {
|
||||||
|
lineTop[i] = yOffset + acc;
|
||||||
|
acc += cachedHeights[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw bubbles
|
||||||
|
for (size_t bi = 0; bi < blocks.size(); ++bi) {
|
||||||
|
const auto &b = blocks[bi];
|
||||||
|
if (b.start >= cachedLines.size() || b.end >= cachedLines.size() || b.start > b.end)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int visualTop = lineTop[b.start];
|
||||||
|
|
||||||
|
int topY;
|
||||||
|
if (isHeader[b.start]) {
|
||||||
|
// Header start
|
||||||
|
constexpr int BUBBLE_PAD_TOP_HEADER = 1; // try 1 or 2
|
||||||
|
topY = visualTop - BUBBLE_PAD_TOP_HEADER;
|
||||||
|
} else {
|
||||||
|
// Body start
|
||||||
|
bool thisLineHasEmote = false;
|
||||||
|
for (int e = 0; e < numEmotes; ++e) {
|
||||||
|
if (cachedLines[b.start].find(emotes[e].label) != std::string::npos) {
|
||||||
|
thisLineHasEmote = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (thisLineHasEmote) {
|
||||||
|
constexpr int EMOTE_PADDING_ABOVE = 4;
|
||||||
|
visualTop -= EMOTE_PADDING_ABOVE;
|
||||||
|
}
|
||||||
|
topY = visualTop - BUBBLE_PAD_Y;
|
||||||
|
}
|
||||||
|
int visualBottom = getDrawnLinePixelBottom(lineTop[b.end], cachedLines[b.end], isHeader[b.end]);
|
||||||
|
int bottomY = visualBottom + BUBBLE_PAD_Y;
|
||||||
|
|
||||||
|
if (bi + 1 < blocks.size()) {
|
||||||
|
int nextHeaderIndex = (int)blocks[bi + 1].start;
|
||||||
|
int nextTop = lineTop[nextHeaderIndex];
|
||||||
|
int maxBottom = nextTop - 1 - bubbleGapY;
|
||||||
|
if (bottomY > maxBottom)
|
||||||
|
bottomY = maxBottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bottomY <= topY + 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (bottomY < contentTop || topY > contentBottom - 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int maxLineW = 0;
|
||||||
|
|
||||||
|
for (size_t i = b.start; i <= b.end; ++i) {
|
||||||
|
int w = 0;
|
||||||
|
if (isHeader[i]) {
|
||||||
|
w = display->getStringWidth(cachedLines[i].c_str());
|
||||||
|
if (b.mine)
|
||||||
|
w += 12; // room for ACK/NACK/relay mark
|
||||||
|
} else {
|
||||||
|
w = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes);
|
||||||
|
}
|
||||||
|
if (w > maxLineW)
|
||||||
|
maxLineW = w;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bubbleW = std::max(BUBBLE_MIN_W, maxLineW + (BUBBLE_PAD_X * 2));
|
||||||
|
int bubbleH = (bottomY - topY) + 1;
|
||||||
|
int bubbleX = 0;
|
||||||
|
if (b.mine) {
|
||||||
|
bubbleX = rightEdge - bubbleW;
|
||||||
|
} else {
|
||||||
|
bubbleX = x;
|
||||||
|
}
|
||||||
|
if (bubbleX < x)
|
||||||
|
bubbleX = x;
|
||||||
|
if (bubbleX + bubbleW > rightEdge)
|
||||||
|
bubbleW = std::max(1, rightEdge - bubbleX);
|
||||||
|
|
||||||
|
if (bubbleW > 1 && bubbleH > 1) {
|
||||||
|
int r = BUBBLE_RADIUS;
|
||||||
|
int maxR = (std::min(bubbleW, bubbleH) / 2) - 1;
|
||||||
|
if (maxR < 0)
|
||||||
|
maxR = 0;
|
||||||
|
if (r > maxR)
|
||||||
|
r = maxR;
|
||||||
|
|
||||||
|
drawRoundedRectOutline(display, bubbleX, topY, bubbleW, bubbleH, r);
|
||||||
|
const int extra = 3;
|
||||||
|
const int rr = r + extra;
|
||||||
|
int x1 = bubbleX + bubbleW - 1;
|
||||||
|
int y1 = topY + bubbleH - 1;
|
||||||
|
|
||||||
|
if (!b.mine) {
|
||||||
|
// top-left corner square
|
||||||
|
display->drawLine(bubbleX, topY, bubbleX + rr, topY);
|
||||||
|
display->drawLine(bubbleX, topY, bubbleX, topY + rr);
|
||||||
|
} else {
|
||||||
|
// bottom-right corner square
|
||||||
|
display->drawLine(x1 - rr, y1, x1, y1);
|
||||||
|
display->drawLine(x1, y1 - rr, x1, y1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Render visible lines
|
// Render visible lines
|
||||||
for (size_t i = 0; i < cachedLines.size(); ++i) {
|
|
||||||
int lineY = yOffset;
|
int lineY = yOffset;
|
||||||
for (size_t j = 0; j < i; ++j)
|
for (size_t i = 0; i < cachedLines.size(); ++i) {
|
||||||
lineY += cachedHeights[j];
|
|
||||||
|
|
||||||
if (lineY > -cachedHeights[i] && lineY < scrollBottom) {
|
if (lineY > -cachedHeights[i] && lineY < scrollBottom) {
|
||||||
if (isHeader[i]) {
|
if (isHeader[i]) {
|
||||||
@@ -728,14 +945,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
int headerX;
|
int headerX;
|
||||||
if (isMine[i]) {
|
if (isMine[i]) {
|
||||||
// push header left to avoid overlap with scrollbar
|
// push header left to avoid overlap with scrollbar
|
||||||
headerX = SCREEN_WIDTH - w - SCROLLBAR_WIDTH - RIGHT_MARGIN;
|
headerX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - w - BUBBLE_TEXT_INDENT;
|
||||||
if (headerX < LEFT_MARGIN)
|
if (headerX < LEFT_MARGIN)
|
||||||
headerX = LEFT_MARGIN;
|
headerX = LEFT_MARGIN;
|
||||||
} else {
|
} else {
|
||||||
headerX = x;
|
headerX = x + BUBBLE_PAD_X + BUBBLE_TEXT_INDENT;
|
||||||
}
|
}
|
||||||
display->drawString(headerX, lineY, cachedLines[i].c_str());
|
display->drawString(headerX, lineY, cachedLines[i].c_str());
|
||||||
|
|
||||||
|
// Draw underline just under header text
|
||||||
|
int underlineY = lineY + FONT_HEIGHT_SMALL;
|
||||||
|
|
||||||
|
int underlineW = w;
|
||||||
|
int maxW = rightEdge - headerX;
|
||||||
|
if (maxW < 0)
|
||||||
|
maxW = 0;
|
||||||
|
if (underlineW > maxW)
|
||||||
|
underlineW = maxW;
|
||||||
|
|
||||||
|
for (int px = 0; px < underlineW; ++px) {
|
||||||
|
display->setPixel(headerX + px, underlineY);
|
||||||
|
}
|
||||||
|
|
||||||
// Draw ACK/NACK mark for our own messages
|
// Draw ACK/NACK mark for our own messages
|
||||||
if (isMine[i]) {
|
if (isMine[i]) {
|
||||||
int markX = headerX - 10;
|
int markX = headerX - 10;
|
||||||
@@ -753,32 +984,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
// AckStatus::NONE → show nothing
|
// AckStatus::NONE → show nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw underline just under header text
|
|
||||||
int underlineY = lineY + FONT_HEIGHT_SMALL;
|
|
||||||
for (int px = 0; px < w; ++px) {
|
|
||||||
display->setPixel(headerX + px, underlineY);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Render message line
|
// Render message line
|
||||||
if (isMine[i]) {
|
if (isMine[i]) {
|
||||||
// Calculate actual rendered width including emotes
|
// Calculate actual rendered width including emotes
|
||||||
int renderedWidth = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes);
|
int renderedWidth = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes);
|
||||||
int rightX = SCREEN_WIDTH - renderedWidth - SCROLLBAR_WIDTH - RIGHT_MARGIN;
|
int rightX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - renderedWidth - BUBBLE_TEXT_INDENT;
|
||||||
if (rightX < LEFT_MARGIN)
|
if (rightX < LEFT_MARGIN)
|
||||||
rightX = LEFT_MARGIN;
|
rightX = LEFT_MARGIN;
|
||||||
|
|
||||||
drawStringWithEmotes(display, rightX, lineY, cachedLines[i], emotes, numEmotes);
|
drawStringWithEmotes(display, rightX, lineY, cachedLines[i], emotes, numEmotes);
|
||||||
} else {
|
} else {
|
||||||
drawStringWithEmotes(display, x, lineY, cachedLines[i], emotes, numEmotes);
|
drawStringWithEmotes(display, x + BUBBLE_PAD_X + BUBBLE_TEXT_INDENT, lineY, cachedLines[i], emotes,
|
||||||
|
numEmotes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lineY += cachedHeights[i];
|
||||||
}
|
}
|
||||||
int totalContentHeight = totalHeight;
|
|
||||||
int visibleHeight = usableHeight;
|
|
||||||
|
|
||||||
// Draw scrollbar
|
// Draw scrollbar
|
||||||
drawMessageScrollbar(display, visibleHeight, totalContentHeight, finalScroll, getTextPositions(display)[1]);
|
drawMessageScrollbar(display, usableHeight, totalHeight, finalScroll, getTextPositions(display)[1]);
|
||||||
graphics::drawCommonHeader(display, x, y, titleStr);
|
graphics::drawCommonHeader(display, x, y, titleStr);
|
||||||
graphics::drawCommonFooter(display, x, y);
|
graphics::drawCommonFooter(display, x, y);
|
||||||
}
|
}
|
||||||
@@ -841,7 +1068,6 @@ std::vector<int> calculateLineHeights(const std::vector<std::string> &lines, con
|
|||||||
constexpr int HEADER_UNDERLINE_GAP = 0; // space between underline and first body line
|
constexpr int HEADER_UNDERLINE_GAP = 0; // space between underline and first body line
|
||||||
constexpr int HEADER_UNDERLINE_PIX = 1; // underline thickness (1px row drawn)
|
constexpr int HEADER_UNDERLINE_PIX = 1; // underline thickness (1px row drawn)
|
||||||
constexpr int BODY_LINE_LEADING = -4; // default vertical leading for normal body lines
|
constexpr int BODY_LINE_LEADING = -4; // default vertical leading for normal body lines
|
||||||
constexpr int MESSAGE_BLOCK_GAP = 4; // gap after a message block before a new header
|
|
||||||
constexpr int EMOTE_PADDING_ABOVE = 4; // space above emote line (added to line above)
|
constexpr int EMOTE_PADDING_ABOVE = 4; // space above emote line (added to line above)
|
||||||
constexpr int EMOTE_PADDING_BELOW = 3; // space below emote line (added to emote line)
|
constexpr int EMOTE_PADDING_BELOW = 3; // space below emote line (added to emote line)
|
||||||
|
|
||||||
@@ -851,6 +1077,7 @@ std::vector<int> calculateLineHeights(const std::vector<std::string> &lines, con
|
|||||||
for (size_t idx = 0; idx < lines.size(); ++idx) {
|
for (size_t idx = 0; idx < lines.size(); ++idx) {
|
||||||
const auto &line = lines[idx];
|
const auto &line = lines[idx];
|
||||||
const int baseHeight = FONT_HEIGHT_SMALL;
|
const int baseHeight = FONT_HEIGHT_SMALL;
|
||||||
|
int lineHeight = baseHeight;
|
||||||
|
|
||||||
// Detect if THIS line or NEXT line contains an emote
|
// Detect if THIS line or NEXT line contains an emote
|
||||||
bool hasEmote = false;
|
bool hasEmote = false;
|
||||||
@@ -872,8 +1099,6 @@ std::vector<int> calculateLineHeights(const std::vector<std::string> &lines, con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int lineHeight = baseHeight;
|
|
||||||
|
|
||||||
if (isHeaderVec[idx]) {
|
if (isHeaderVec[idx]) {
|
||||||
// Header line spacing
|
// Header line spacing
|
||||||
lineHeight = baseHeight + HEADER_UNDERLINE_PIX + HEADER_UNDERLINE_GAP;
|
lineHeight = baseHeight + HEADER_UNDERLINE_PIX + HEADER_UNDERLINE_GAP;
|
||||||
@@ -922,7 +1147,7 @@ void handleNewMessage(OLEDDisplay *display, const StoredMessage &sm, const mesht
|
|||||||
|
|
||||||
// Banner logic
|
// Banner logic
|
||||||
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet.from);
|
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet.from);
|
||||||
char longName[48] = "???";
|
char longName[48] = "?";
|
||||||
if (node && node->user.long_name) {
|
if (node && node->user.long_name) {
|
||||||
strncpy(longName, node->user.long_name, sizeof(longName) - 1);
|
strncpy(longName, node->user.long_name, sizeof(longName) - 1);
|
||||||
longName[sizeof(longName) - 1] = '\0';
|
longName[sizeof(longName) - 1] = '\0';
|
||||||
|
|||||||
@@ -155,18 +155,6 @@ void InkHUD::LogoApplet::onShutdown()
|
|||||||
// This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update, after InkHUD's flash write is complete
|
// This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update, after InkHUD's flash write is complete
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::LogoApplet::onApplyingChanges()
|
|
||||||
{
|
|
||||||
bringToForeground();
|
|
||||||
|
|
||||||
textLeft = "";
|
|
||||||
textRight = "";
|
|
||||||
textTitle = "Applying changes";
|
|
||||||
fontTitle = fontSmall;
|
|
||||||
|
|
||||||
inkhud->forceUpdate(Drivers::EInk::FAST, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::LogoApplet::onReboot()
|
void InkHUD::LogoApplet::onReboot()
|
||||||
{
|
{
|
||||||
bringToForeground();
|
bringToForeground();
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ class LogoApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
void onBackground() override;
|
void onBackground() override;
|
||||||
void onShutdown() override;
|
void onShutdown() override;
|
||||||
void onReboot() override;
|
void onReboot() override;
|
||||||
void onApplyingChanges();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int32_t runOnce() override;
|
int32_t runOnce() override;
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ enum MenuAction {
|
|||||||
STORE_CANNEDMESSAGE_SELECTION,
|
STORE_CANNEDMESSAGE_SELECTION,
|
||||||
SEND_CANNEDMESSAGE,
|
SEND_CANNEDMESSAGE,
|
||||||
SHUTDOWN,
|
SHUTDOWN,
|
||||||
BACK,
|
|
||||||
NEXT_TILE,
|
NEXT_TILE,
|
||||||
TOGGLE_BACKLIGHT,
|
TOGGLE_BACKLIGHT,
|
||||||
TOGGLE_GPS,
|
TOGGLE_GPS,
|
||||||
@@ -37,84 +36,6 @@ enum MenuAction {
|
|||||||
TOGGLE_NOTIFICATIONS,
|
TOGGLE_NOTIFICATIONS,
|
||||||
TOGGLE_INVERT_COLOR,
|
TOGGLE_INVERT_COLOR,
|
||||||
TOGGLE_12H_CLOCK,
|
TOGGLE_12H_CLOCK,
|
||||||
// Regions
|
|
||||||
SET_REGION_US,
|
|
||||||
SET_REGION_EU_868,
|
|
||||||
SET_REGION_EU_433,
|
|
||||||
SET_REGION_CN,
|
|
||||||
SET_REGION_JP,
|
|
||||||
SET_REGION_ANZ,
|
|
||||||
SET_REGION_KR,
|
|
||||||
SET_REGION_TW,
|
|
||||||
SET_REGION_RU,
|
|
||||||
SET_REGION_IN,
|
|
||||||
SET_REGION_NZ_865,
|
|
||||||
SET_REGION_TH,
|
|
||||||
SET_REGION_LORA_24,
|
|
||||||
SET_REGION_UA_433,
|
|
||||||
SET_REGION_UA_868,
|
|
||||||
SET_REGION_MY_433,
|
|
||||||
SET_REGION_MY_919,
|
|
||||||
SET_REGION_SG_923,
|
|
||||||
SET_REGION_PH_433,
|
|
||||||
SET_REGION_PH_868,
|
|
||||||
SET_REGION_PH_915,
|
|
||||||
SET_REGION_ANZ_433,
|
|
||||||
SET_REGION_KZ_433,
|
|
||||||
SET_REGION_KZ_863,
|
|
||||||
SET_REGION_NP_865,
|
|
||||||
SET_REGION_BR_902,
|
|
||||||
// Device Roles
|
|
||||||
SET_ROLE_CLIENT,
|
|
||||||
SET_ROLE_CLIENT_MUTE,
|
|
||||||
SET_ROLE_ROUTER,
|
|
||||||
SET_ROLE_REPEATER,
|
|
||||||
// Presets
|
|
||||||
SET_PRESET_LONG_SLOW,
|
|
||||||
SET_PRESET_LONG_MODERATE,
|
|
||||||
SET_PRESET_LONG_FAST,
|
|
||||||
SET_PRESET_MEDIUM_SLOW,
|
|
||||||
SET_PRESET_MEDIUM_FAST,
|
|
||||||
SET_PRESET_SHORT_SLOW,
|
|
||||||
SET_PRESET_SHORT_FAST,
|
|
||||||
SET_PRESET_SHORT_TURBO,
|
|
||||||
// Timezones
|
|
||||||
SET_TZ_US_HAWAII,
|
|
||||||
SET_TZ_US_ALASKA,
|
|
||||||
SET_TZ_US_PACIFIC,
|
|
||||||
SET_TZ_US_ARIZONA,
|
|
||||||
SET_TZ_US_MOUNTAIN,
|
|
||||||
SET_TZ_US_CENTRAL,
|
|
||||||
SET_TZ_US_EASTERN,
|
|
||||||
SET_TZ_BR_BRAZILIA,
|
|
||||||
SET_TZ_UTC,
|
|
||||||
SET_TZ_EU_WESTERN,
|
|
||||||
SET_TZ_EU_CENTRAL,
|
|
||||||
SET_TZ_EU_EASTERN,
|
|
||||||
SET_TZ_ASIA_KOLKATA,
|
|
||||||
SET_TZ_ASIA_HONG_KONG,
|
|
||||||
SET_TZ_AU_AWST,
|
|
||||||
SET_TZ_AU_ACST,
|
|
||||||
SET_TZ_AU_AEST,
|
|
||||||
SET_TZ_PACIFIC_NZ,
|
|
||||||
// Power
|
|
||||||
TOGGLE_POWER_SAVE,
|
|
||||||
CALIBRATE_ADC,
|
|
||||||
// Bluetooth
|
|
||||||
TOGGLE_BLUETOOTH,
|
|
||||||
TOGGLE_BLUETOOTH_PAIR_MODE,
|
|
||||||
// Channel
|
|
||||||
TOGGLE_CHANNEL_UPLINK,
|
|
||||||
TOGGLE_CHANNEL_DOWNLINK,
|
|
||||||
TOGGLE_CHANNEL_POSITION,
|
|
||||||
SET_CHANNEL_PRECISION,
|
|
||||||
// Display
|
|
||||||
TOGGLE_DISPLAY_UNITS,
|
|
||||||
// Network
|
|
||||||
TOGGLE_WIFI,
|
|
||||||
// Administration
|
|
||||||
RESET_NODEDB_ALL,
|
|
||||||
RESET_NODEDB_KEEP_FAVORITES,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace NicheGraphics::InkHUD
|
} // namespace NicheGraphics::InkHUD
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -35,7 +35,6 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
void onRender() override;
|
void onRender() override;
|
||||||
|
|
||||||
void show(Tile *t); // Open the menu, onto a user tile
|
void show(Tile *t); // Open the menu, onto a user tile
|
||||||
void setStartPage(MenuPage page);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton
|
Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton
|
||||||
@@ -57,7 +56,6 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
void sendText(NodeNum dest, ChannelIndex channel, const char *message); // Send a text message to mesh
|
void sendText(NodeNum dest, ChannelIndex channel, const char *message); // Send a text message to mesh
|
||||||
void freeCannedMessageResources(); // Clear MenuApplet's canned message processing data
|
void freeCannedMessageResources(); // Clear MenuApplet's canned message processing data
|
||||||
|
|
||||||
MenuPage startPageOverride = MenuPage::ROOT;
|
|
||||||
MenuPage currentPage = MenuPage::ROOT;
|
MenuPage currentPage = MenuPage::ROOT;
|
||||||
MenuPage previousPage = MenuPage::EXIT;
|
MenuPage previousPage = MenuPage::EXIT;
|
||||||
uint8_t cursor = 0; // Which menu item is currently highlighted
|
uint8_t cursor = 0; // Which menu item is currently highlighted
|
||||||
@@ -66,14 +64,6 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
uint16_t systemInfoPanelHeight = 0; // Need to know before we render
|
uint16_t systemInfoPanelHeight = 0; // Need to know before we render
|
||||||
|
|
||||||
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
|
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
|
||||||
std::vector<std::string> nodeConfigLabels; // Persistent labels for Node Config pages
|
|
||||||
uint8_t selectedChannelIndex = 0; // Currently selected LoRa channel (Node Config → Radio → Channel)
|
|
||||||
bool channelPositionEnabled = false;
|
|
||||||
bool gpsEnabled = false;
|
|
||||||
|
|
||||||
// Recents menu checkbox state (derived from settings.recentlyActiveSeconds)
|
|
||||||
static constexpr uint8_t RECENTS_COUNT = 6;
|
|
||||||
bool recentsSelected[RECENTS_COUNT] = {};
|
|
||||||
|
|
||||||
// Data for selecting and sending canned messages via the menu
|
// Data for selecting and sending canned messages via the menu
|
||||||
// Placed into a sub-class for organization only
|
// Placed into a sub-class for organization only
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ class MenuItem
|
|||||||
MenuAction action = NO_ACTION;
|
MenuAction action = NO_ACTION;
|
||||||
MenuPage nextPage = EXIT;
|
MenuPage nextPage = EXIT;
|
||||||
bool *checkState = nullptr;
|
bool *checkState = nullptr;
|
||||||
bool isHeader = false; // Non-selectable section label
|
|
||||||
|
|
||||||
// Various constructors, depending on the intended function of the item
|
// Various constructors, depending on the intended function of the item
|
||||||
|
|
||||||
@@ -41,12 +40,6 @@ class MenuItem
|
|||||||
: label(label), action(action), nextPage(nextPage), checkState(checkState)
|
: label(label), action(action), nextPage(nextPage), checkState(checkState)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
static MenuItem Header(const char *label)
|
|
||||||
{
|
|
||||||
MenuItem item(label, NO_ACTION, EXIT);
|
|
||||||
item.isHeader = true;
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace NicheGraphics::InkHUD
|
} // namespace NicheGraphics::InkHUD
|
||||||
|
|||||||
@@ -20,26 +20,9 @@ enum MenuPage : uint8_t {
|
|||||||
SEND,
|
SEND,
|
||||||
CANNEDMESSAGE_RECIPIENT, // Select destination for a canned message
|
CANNEDMESSAGE_RECIPIENT, // Select destination for a canned message
|
||||||
OPTIONS,
|
OPTIONS,
|
||||||
NODE_CONFIG,
|
|
||||||
NODE_CONFIG_LORA,
|
|
||||||
NODE_CONFIG_CHANNELS, // List of channels
|
|
||||||
NODE_CONFIG_CHANNEL_DETAIL, // Per-channel options
|
|
||||||
NODE_CONFIG_CHANNEL_PRECISION,
|
|
||||||
NODE_CONFIG_PRESET,
|
|
||||||
NODE_CONFIG_DEVICE,
|
|
||||||
NODE_CONFIG_DEVICE_ROLE,
|
|
||||||
NODE_CONFIG_POWER,
|
|
||||||
NODE_CONFIG_POWER_ADC_CAL,
|
|
||||||
NODE_CONFIG_NETWORK,
|
|
||||||
NODE_CONFIG_DISPLAY,
|
|
||||||
NODE_CONFIG_BLUETOOTH,
|
|
||||||
NODE_CONFIG_POSITION,
|
|
||||||
NODE_CONFIG_ADMIN_RESET,
|
|
||||||
TIMEZONE,
|
|
||||||
APPLETS,
|
APPLETS,
|
||||||
AUTOSHOW,
|
AUTOSHOW,
|
||||||
RECENTS, // Select length of "recentlyActiveSeconds"
|
RECENTS, // Select length of "recentlyActiveSeconds"
|
||||||
REGION,
|
|
||||||
EXIT, // Dismiss the menu applet
|
EXIT, // Dismiss the menu applet
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,37 +10,34 @@ using namespace NicheGraphics;
|
|||||||
|
|
||||||
InkHUD::TipsApplet::TipsApplet()
|
InkHUD::TipsApplet::TipsApplet()
|
||||||
{
|
{
|
||||||
bool needsRegion = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET);
|
// Decide which tips (if any) should be shown to user after the boot screen
|
||||||
|
|
||||||
bool showTutorialTips = (settings->tips.firstBoot || needsRegion);
|
|
||||||
|
|
||||||
// Welcome screen
|
// Welcome screen
|
||||||
if (showTutorialTips)
|
if (settings->tips.firstBoot)
|
||||||
tipQueue.push_back(Tip::WELCOME);
|
tipQueue.push_back(Tip::WELCOME);
|
||||||
|
|
||||||
// Finish setup
|
// Antenna, region, timezone
|
||||||
if (needsRegion)
|
// Shown at boot if region not yet set
|
||||||
|
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET)
|
||||||
tipQueue.push_back(Tip::FINISH_SETUP);
|
tipQueue.push_back(Tip::FINISH_SETUP);
|
||||||
|
|
||||||
// Using the UI
|
|
||||||
if (showTutorialTips) {
|
|
||||||
tipQueue.push_back(Tip::CUSTOMIZATION);
|
|
||||||
tipQueue.push_back(Tip::BUTTONS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown info
|
// Shutdown info
|
||||||
// Shown until user performs one valid shutdown
|
// Shown until user performs one valid shutdown
|
||||||
if (!settings->tips.safeShutdownSeen)
|
if (!settings->tips.safeShutdownSeen)
|
||||||
tipQueue.push_back(Tip::SAFE_SHUTDOWN);
|
tipQueue.push_back(Tip::SAFE_SHUTDOWN);
|
||||||
|
|
||||||
|
// Using the UI
|
||||||
|
if (settings->tips.firstBoot) {
|
||||||
|
tipQueue.push_back(Tip::CUSTOMIZATION);
|
||||||
|
tipQueue.push_back(Tip::BUTTONS);
|
||||||
|
}
|
||||||
|
|
||||||
// Catch an incorrect attempt at rotating display
|
// Catch an incorrect attempt at rotating display
|
||||||
if (config.display.flip_screen)
|
if (config.display.flip_screen)
|
||||||
tipQueue.push_back(Tip::ROTATION);
|
tipQueue.push_back(Tip::ROTATION);
|
||||||
|
|
||||||
// Region picker
|
// Applet is foreground immediately at boot, but is obscured by LogoApplet, which is also foreground
|
||||||
if (needsRegion)
|
// LogoApplet can be considered to have a higher Z-index, because it is placed before TipsApplet in the systemApplets vector
|
||||||
tipQueue.push_back(Tip::PICK_REGION);
|
|
||||||
|
|
||||||
if (!tipQueue.empty())
|
if (!tipQueue.empty())
|
||||||
bringToForeground();
|
bringToForeground();
|
||||||
}
|
}
|
||||||
@@ -54,109 +51,81 @@ void InkHUD::TipsApplet::onRender()
|
|||||||
|
|
||||||
case Tip::FINISH_SETUP: {
|
case Tip::FINISH_SETUP: {
|
||||||
setFont(fontMedium);
|
setFont(fontMedium);
|
||||||
const char *title = "Tip: Finish Setup";
|
printAt(0, 0, "Tip: Finish Setup");
|
||||||
uint16_t h = getWrappedTextHeight(0, width(), title);
|
|
||||||
printWrapped(0, 0, width(), title);
|
|
||||||
|
|
||||||
setFont(fontSmall);
|
setFont(fontSmall);
|
||||||
int16_t cursorY = h + fontSmall.lineHeight();
|
int16_t cursorY = fontMedium.lineHeight() * 1.5;
|
||||||
|
printAt(0, cursorY, "- connect antenna");
|
||||||
|
|
||||||
auto drawBullet = [&](const char *text) {
|
cursorY += fontSmall.lineHeight() * 1.2;
|
||||||
uint16_t bh = getWrappedTextHeight(0, width(), text);
|
printAt(0, cursorY, "- connect a client app");
|
||||||
printWrapped(0, cursorY, width(), text);
|
|
||||||
cursorY += bh + (fontSmall.lineHeight() / 3);
|
|
||||||
};
|
|
||||||
|
|
||||||
drawBullet("- connect antenna");
|
// Only if region not set
|
||||||
drawBullet("- connect a client app");
|
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
|
||||||
|
cursorY += fontSmall.lineHeight() * 1.2;
|
||||||
|
printAt(0, cursorY, "- set region");
|
||||||
|
}
|
||||||
|
|
||||||
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET)
|
// Only if tz not set
|
||||||
drawBullet("- set region");
|
if (!(*config.device.tzdef && config.device.tzdef[0] != 0)) {
|
||||||
|
cursorY += fontSmall.lineHeight() * 1.2;
|
||||||
|
printAt(0, cursorY, "- set timezone");
|
||||||
|
}
|
||||||
|
|
||||||
if (!(*config.device.tzdef && config.device.tzdef[0] != 0))
|
cursorY += fontSmall.lineHeight() * 1.5;
|
||||||
drawBullet("- set timezone");
|
printAt(0, cursorY, "More info at meshtastic.org");
|
||||||
|
|
||||||
cursorY += fontSmall.lineHeight() / 2;
|
|
||||||
drawBullet("More info at meshtastic.org");
|
|
||||||
|
|
||||||
|
setFont(fontSmall);
|
||||||
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Tip::PICK_REGION: {
|
|
||||||
setFont(fontMedium);
|
|
||||||
printAt(0, 0, "Set Region");
|
|
||||||
|
|
||||||
setFont(fontSmall);
|
|
||||||
printWrapped(0, fontMedium.lineHeight() * 1.5, width(), "Please select your LoRa region to complete setup.");
|
|
||||||
|
|
||||||
printAt(0, Y(1.0), "Press button to choose", LEFT, BOTTOM);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case Tip::SAFE_SHUTDOWN: {
|
case Tip::SAFE_SHUTDOWN: {
|
||||||
setFont(fontMedium);
|
setFont(fontMedium);
|
||||||
|
printAt(0, 0, "Tip: Shutdown");
|
||||||
const char *title = "Tip: Shutdown";
|
|
||||||
uint16_t h = getWrappedTextHeight(0, width(), title);
|
|
||||||
printWrapped(0, 0, width(), title);
|
|
||||||
|
|
||||||
setFont(fontSmall);
|
setFont(fontSmall);
|
||||||
int16_t cursorY = h + fontSmall.lineHeight();
|
std::string shutdown;
|
||||||
|
shutdown += "Before removing power, please shut down from InkHUD menu, or a client app. \n";
|
||||||
const char *body = "Before removing power, please shut down from InkHUD menu, or a client app.\n\n"
|
shutdown += "\n";
|
||||||
"This ensures data is saved.";
|
shutdown += "This ensures data is saved.";
|
||||||
|
printWrapped(0, fontMedium.lineHeight() * 1.5, width(), shutdown);
|
||||||
uint16_t bodyH = getWrappedTextHeight(0, width(), body);
|
|
||||||
printWrapped(0, cursorY, width(), body);
|
|
||||||
cursorY += bodyH + (fontSmall.lineHeight() / 2);
|
|
||||||
|
|
||||||
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
||||||
|
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Tip::CUSTOMIZATION: {
|
case Tip::CUSTOMIZATION: {
|
||||||
setFont(fontMedium);
|
setFont(fontMedium);
|
||||||
|
printAt(0, 0, "Tip: Customization");
|
||||||
const char *title = "Tip: Customization";
|
|
||||||
uint16_t h = getWrappedTextHeight(0, width(), title);
|
|
||||||
printWrapped(0, 0, width(), title);
|
|
||||||
|
|
||||||
setFont(fontSmall);
|
setFont(fontSmall);
|
||||||
int16_t cursorY = h + fontSmall.lineHeight();
|
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
|
||||||
|
"Configure & control display with the InkHUD menu. Optional features, layout, rotation, and more.");
|
||||||
const char *body = "Configure & control display with the InkHUD menu. "
|
|
||||||
"Optional features, layout, rotation, and more.";
|
|
||||||
|
|
||||||
uint16_t bodyH = getWrappedTextHeight(0, width(), body);
|
|
||||||
printWrapped(0, cursorY, width(), body);
|
|
||||||
cursorY += bodyH + (fontSmall.lineHeight() / 2);
|
|
||||||
|
|
||||||
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Tip::BUTTONS: {
|
case Tip::BUTTONS: {
|
||||||
setFont(fontMedium);
|
setFont(fontMedium);
|
||||||
|
printAt(0, 0, "Tip: Buttons");
|
||||||
const char *title = "Tip: Buttons";
|
|
||||||
uint16_t h = getWrappedTextHeight(0, width(), title);
|
|
||||||
printWrapped(0, 0, width(), title);
|
|
||||||
|
|
||||||
setFont(fontSmall);
|
setFont(fontSmall);
|
||||||
int16_t cursorY = h + fontSmall.lineHeight();
|
int16_t cursorY = fontMedium.lineHeight() * 1.5;
|
||||||
|
|
||||||
auto drawBullet = [&](const char *text) {
|
|
||||||
uint16_t bh = getWrappedTextHeight(0, width(), text);
|
|
||||||
printWrapped(0, cursorY, width(), text);
|
|
||||||
cursorY += bh + (fontSmall.lineHeight() / 3);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!settings->joystick.enabled) {
|
if (!settings->joystick.enabled) {
|
||||||
drawBullet("User Button");
|
printAt(0, cursorY, "User Button");
|
||||||
drawBullet("- short press: next");
|
cursorY += fontSmall.lineHeight() * 1.2;
|
||||||
drawBullet("- long press: select or open menu");
|
printAt(0, cursorY, "- short press: next");
|
||||||
|
cursorY += fontSmall.lineHeight() * 1.2;
|
||||||
|
printAt(0, cursorY, "- long press: select / open menu");
|
||||||
} else {
|
} else {
|
||||||
drawBullet("Joystick");
|
printAt(0, cursorY, "Joystick");
|
||||||
drawBullet("- press: open menu or select");
|
cursorY += fontSmall.lineHeight() * 1.2;
|
||||||
drawBullet("Exit Button");
|
printAt(0, cursorY, "- open menu / select");
|
||||||
drawBullet("- press: switch tile or close menu");
|
cursorY += fontSmall.lineHeight() * 1.5;
|
||||||
|
printAt(0, cursorY, "Exit Button");
|
||||||
|
cursorY += fontSmall.lineHeight() * 1.2;
|
||||||
|
printAt(0, cursorY, "- switch tile / close menu");
|
||||||
}
|
}
|
||||||
|
|
||||||
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
||||||
@@ -164,21 +133,12 @@ void InkHUD::TipsApplet::onRender()
|
|||||||
|
|
||||||
case Tip::ROTATION: {
|
case Tip::ROTATION: {
|
||||||
setFont(fontMedium);
|
setFont(fontMedium);
|
||||||
|
printAt(0, 0, "Tip: Rotation");
|
||||||
const char *title = "Tip: Rotation";
|
|
||||||
uint16_t h = getWrappedTextHeight(0, width(), title);
|
|
||||||
printWrapped(0, 0, width(), title);
|
|
||||||
|
|
||||||
setFont(fontSmall);
|
setFont(fontSmall);
|
||||||
if (!settings->joystick.enabled) {
|
if (!settings->joystick.enabled) {
|
||||||
int16_t cursorY = h + fontSmall.lineHeight();
|
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
|
||||||
|
"To rotate the display, use the InkHUD menu. Long-press the user button > Options > Rotate.");
|
||||||
const char *body = "To rotate the display, use the InkHUD menu. "
|
|
||||||
"Long-press the user button > Options > Rotate.";
|
|
||||||
|
|
||||||
uint16_t bh = getWrappedTextHeight(0, width(), body);
|
|
||||||
printWrapped(0, cursorY, width(), body);
|
|
||||||
cursorY += bh + (fontSmall.lineHeight() / 2);
|
|
||||||
} else {
|
} else {
|
||||||
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
|
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
|
||||||
"To rotate the display, use the InkHUD menu. Press the user button > Options > Rotate.");
|
"To rotate the display, use the InkHUD menu. Press the user button > Options > Rotate.");
|
||||||
@@ -199,15 +159,12 @@ void InkHUD::TipsApplet::renderWelcome()
|
|||||||
{
|
{
|
||||||
uint16_t padW = X(0.05);
|
uint16_t padW = X(0.05);
|
||||||
|
|
||||||
// Detect portrait orientation
|
|
||||||
bool portrait = height() > width();
|
|
||||||
|
|
||||||
// Block 1 - logo & title
|
// Block 1 - logo & title
|
||||||
// ========================
|
// ========================
|
||||||
|
|
||||||
// Logo size
|
// Logo size
|
||||||
uint16_t logoWLimit = portrait ? X(0.5) : X(0.3);
|
uint16_t logoWLimit = X(0.3);
|
||||||
uint16_t logoHLimit = portrait ? Y(0.25) : Y(0.3);
|
uint16_t logoHLimit = Y(0.3);
|
||||||
uint16_t logoW = getLogoWidth(logoWLimit, logoHLimit);
|
uint16_t logoW = getLogoWidth(logoWLimit, logoHLimit);
|
||||||
uint16_t logoH = getLogoHeight(logoWLimit, logoHLimit);
|
uint16_t logoH = getLogoHeight(logoWLimit, logoHLimit);
|
||||||
|
|
||||||
@@ -220,7 +177,7 @@ void InkHUD::TipsApplet::renderWelcome()
|
|||||||
|
|
||||||
// Center the block
|
// Center the block
|
||||||
// Desired effect: equal margin from display edge for logo left and title right
|
// Desired effect: equal margin from display edge for logo left and title right
|
||||||
int16_t block1Y = portrait ? Y(0.2) : Y(0.3);
|
int16_t block1Y = Y(0.3);
|
||||||
int16_t block1CX = X(0.5) + (logoW / 2) - (titleW / 2);
|
int16_t block1CX = X(0.5) + (logoW / 2) - (titleW / 2);
|
||||||
int16_t logoCX = block1CX - (logoW / 2) - (padW / 2);
|
int16_t logoCX = block1CX - (logoW / 2) - (padW / 2);
|
||||||
int16_t titleCX = block1CX + (titleW / 2) + (padW / 2);
|
int16_t titleCX = block1CX + (titleW / 2) + (padW / 2);
|
||||||
@@ -235,7 +192,7 @@ void InkHUD::TipsApplet::renderWelcome()
|
|||||||
std::string subtitle = "InkHUD";
|
std::string subtitle = "InkHUD";
|
||||||
if (width() >= 200)
|
if (width() >= 200)
|
||||||
subtitle += " - A Heads-Up Display"; // Future proofing: narrower for tiny display
|
subtitle += " - A Heads-Up Display"; // Future proofing: narrower for tiny display
|
||||||
printAt(X(0.5), portrait ? Y(0.45) : Y(0.6), subtitle, CENTER, MIDDLE);
|
printAt(X(0.5), Y(0.6), subtitle, CENTER, MIDDLE);
|
||||||
|
|
||||||
// Block 3 - press to continue
|
// Block 3 - press to continue
|
||||||
// ============================
|
// ============================
|
||||||
@@ -267,37 +224,26 @@ void InkHUD::TipsApplet::onBackground()
|
|||||||
// While our SystemApplet::handleInput flag is true
|
// While our SystemApplet::handleInput flag is true
|
||||||
void InkHUD::TipsApplet::onButtonShortPress()
|
void InkHUD::TipsApplet::onButtonShortPress()
|
||||||
{
|
{
|
||||||
bool needsRegion = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET);
|
|
||||||
// If we're prompting the user to pick a region, hand off to the menu
|
|
||||||
if (!tipQueue.empty() && tipQueue.front() == Tip::PICK_REGION) {
|
|
||||||
tipQueue.pop_front();
|
|
||||||
|
|
||||||
// Signal InkHUD to open the menu on Region page
|
|
||||||
inkhud->forceRegionMenu = true;
|
|
||||||
|
|
||||||
// Close tips and open menu
|
|
||||||
sendToBackground();
|
|
||||||
inkhud->openMenu();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Consume current tip
|
|
||||||
tipQueue.pop_front();
|
tipQueue.pop_front();
|
||||||
|
|
||||||
// All tips done
|
// All tips done
|
||||||
if (tipQueue.empty()) {
|
if (tipQueue.empty()) {
|
||||||
// Record that user has now seen the "tutorial" set of tips
|
// Record that user has now seen the "tutorial" set of tips
|
||||||
// Don't show them on subsequent boots
|
// Don't show them on subsequent boots
|
||||||
if (settings->tips.firstBoot && !needsRegion) {
|
if (settings->tips.firstBoot) {
|
||||||
settings->tips.firstBoot = false;
|
settings->tips.firstBoot = false;
|
||||||
inkhud->persistence->saveSettings();
|
inkhud->persistence->saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close applet and clean the screen
|
// Close applet, and full refresh to clean the screen
|
||||||
|
// Need to force update, because our request would be ignored otherwise, as we are now background
|
||||||
sendToBackground();
|
sendToBackground();
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
} else {
|
|
||||||
requestUpdate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// More tips left
|
||||||
|
else
|
||||||
|
requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Functions the same as the user button in this instance
|
// Functions the same as the user button in this instance
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ class TipsApplet : public SystemApplet
|
|||||||
enum class Tip {
|
enum class Tip {
|
||||||
WELCOME,
|
WELCOME,
|
||||||
FINISH_SETUP,
|
FINISH_SETUP,
|
||||||
PICK_REGION,
|
|
||||||
SAFE_SHUTDOWN,
|
SAFE_SHUTDOWN,
|
||||||
CUSTOMIZATION,
|
CUSTOMIZATION,
|
||||||
BUTTONS,
|
BUTTONS,
|
||||||
|
|||||||
@@ -276,15 +276,6 @@ int InkHUD::Events::beforeDeepSleep(void *unused)
|
|||||||
return 0; // We agree: deep sleep now
|
return 0; // We agree: deep sleep now
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display an intermediate screen while configuration changes are applied
|
|
||||||
void InkHUD::Events::applyingChanges()
|
|
||||||
{
|
|
||||||
// Bring the logo applet forward with a temporary message
|
|
||||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
|
||||||
sa->onApplyingChanges();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback for rebootObserver
|
// Callback for rebootObserver
|
||||||
// Same as shutdown, without drawing the logoApplet
|
// Same as shutdown, without drawing the logoApplet
|
||||||
// Makes sure we don't lose message history / InkHUD config
|
// Makes sure we don't lose message history / InkHUD config
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ class Events
|
|||||||
|
|
||||||
void onButtonShort(); // User button: short press
|
void onButtonShort(); // User button: short press
|
||||||
void onButtonLong(); // User button: long press
|
void onButtonLong(); // User button: long press
|
||||||
void applyingChanges();
|
|
||||||
void onExitShort(); // Exit button: short press
|
void onExitShort(); // Exit button: short press
|
||||||
void onExitLong(); // Exit button: long press
|
void onExitLong(); // Exit button: long press
|
||||||
void onNavUp(); // Navigate up
|
void onNavUp(); // Navigate up
|
||||||
|
|||||||
@@ -53,13 +53,6 @@ void InkHUD::InkHUD::addApplet(const char *name, Applet *a, bool defaultActive,
|
|||||||
windowManager->addApplet(name, a, defaultActive, defaultAutoshow, onTile);
|
windowManager->addApplet(name, a, defaultActive, defaultAutoshow, onTile);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::InkHUD::notifyApplyingChanges()
|
|
||||||
{
|
|
||||||
if (events) {
|
|
||||||
events->applyingChanges();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start InkHUD!
|
// Start InkHUD!
|
||||||
// Call this only after you have configured InkHUD
|
// Call this only after you have configured InkHUD
|
||||||
void InkHUD::InkHUD::begin()
|
void InkHUD::InkHUD::begin()
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ class InkHUD
|
|||||||
void setDriver(Drivers::EInk *driver);
|
void setDriver(Drivers::EInk *driver);
|
||||||
void setDisplayResilience(uint8_t fastPerFull = 5, float stressMultiplier = 2.0);
|
void setDisplayResilience(uint8_t fastPerFull = 5, float stressMultiplier = 2.0);
|
||||||
void addApplet(const char *name, Applet *a, bool defaultActive = false, bool defaultAutoshow = false, uint8_t onTile = -1);
|
void addApplet(const char *name, Applet *a, bool defaultActive = false, bool defaultAutoshow = false, uint8_t onTile = -1);
|
||||||
void notifyApplyingChanges();
|
|
||||||
|
|
||||||
void begin();
|
void begin();
|
||||||
|
|
||||||
@@ -77,9 +76,6 @@ class InkHUD
|
|||||||
void rotateJoystick(uint8_t angle = 1); // rotate 90 deg by default
|
void rotateJoystick(uint8_t angle = 1); // rotate 90 deg by default
|
||||||
void toggleBatteryIcon();
|
void toggleBatteryIcon();
|
||||||
|
|
||||||
// Used by TipsApplet to force menu to start on Region selection
|
|
||||||
bool forceRegionMenu = false;
|
|
||||||
|
|
||||||
// Updating the display
|
// Updating the display
|
||||||
// - called by various InkHUD components
|
// - called by various InkHUD components
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ class SystemApplet : public Applet
|
|||||||
bool lockRequests = false; // - prevent other applets from triggering display updates
|
bool lockRequests = false; // - prevent other applets from triggering display updates
|
||||||
|
|
||||||
virtual void onReboot() { onShutdown(); } // - handle reboot specially
|
virtual void onReboot() { onShutdown(); } // - handle reboot specially
|
||||||
virtual void onApplyingChanges() {}
|
|
||||||
|
|
||||||
// Other system applets may take precedence over our own system applet though
|
// Other system applets may take precedence over our own system applet though
|
||||||
// The order an applet is passed to WindowManager::addSystemApplet determines this hierarchy (added earlier = higher rank)
|
// The order an applet is passed to WindowManager::addSystemApplet determines this hierarchy (added earlier = higher rank)
|
||||||
|
|||||||
46
src/main.cpp
46
src/main.cpp
@@ -10,6 +10,7 @@
|
|||||||
#include "ReliableRouter.h"
|
#include "ReliableRouter.h"
|
||||||
#include "airtime.h"
|
#include "airtime.h"
|
||||||
#include "buzz.h"
|
#include "buzz.h"
|
||||||
|
#include "power/PowerHAL.h"
|
||||||
|
|
||||||
#include "FSCommon.h"
|
#include "FSCommon.h"
|
||||||
#include "Led.h"
|
#include "Led.h"
|
||||||
@@ -332,6 +333,43 @@ __attribute__((weak, noinline)) bool loopCanSleep()
|
|||||||
void lateInitVariant() __attribute__((weak));
|
void lateInitVariant() __attribute__((weak));
|
||||||
void lateInitVariant() {}
|
void lateInitVariant() {}
|
||||||
|
|
||||||
|
// NRF52 (and probably other platforms) can report when system is in power failure mode
|
||||||
|
// (eg. too low battery voltage) and operating it is unsafe (data corruption, bootloops, etc).
|
||||||
|
// For example NRF52 will prevent any flash writes in that case automatically
|
||||||
|
// (but it causes issues we need to handle).
|
||||||
|
// This detection is independent from whatever ADC or dividers used in Meshtastic
|
||||||
|
// boards and is internal to chip.
|
||||||
|
|
||||||
|
// we use powerHAL layer to get this info and delay booting until power level is safe
|
||||||
|
|
||||||
|
// wait until power level is safe to continue booting (to avoid bootloops)
|
||||||
|
// blink user led in 3 flashes sequence to indicate what is happening
|
||||||
|
void waitUntilPowerLevelSafe()
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifdef LED_PIN
|
||||||
|
pinMode(LED_PIN, OUTPUT);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
while (powerHAL_isPowerLevelSafe() == false) {
|
||||||
|
|
||||||
|
#ifdef LED_PIN
|
||||||
|
|
||||||
|
// 3x: blink for 300 ms, pause for 300 ms
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
digitalWrite(LED_PIN, LED_STATE_ON);
|
||||||
|
delay(300);
|
||||||
|
digitalWrite(LED_PIN, LED_STATE_OFF);
|
||||||
|
delay(300);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// sleep for 2s
|
||||||
|
delay(2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print info as a structured log message (for automated log processing)
|
* Print info as a structured log message (for automated log processing)
|
||||||
*/
|
*/
|
||||||
@@ -342,6 +380,14 @@ void printInfo()
|
|||||||
#ifndef PIO_UNIT_TESTING
|
#ifndef PIO_UNIT_TESTING
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// initialize power HAL layer as early as possible
|
||||||
|
powerHAL_init();
|
||||||
|
|
||||||
|
// prevent booting if device is in power failure mode
|
||||||
|
// boot sequence will follow when battery level raises to safe mode
|
||||||
|
waitUntilPowerLevelSafe();
|
||||||
|
|
||||||
#if defined(R1_NEO)
|
#if defined(R1_NEO)
|
||||||
pinMode(DCDC_EN_HOLD, OUTPUT);
|
pinMode(DCDC_EN_HOLD, OUTPUT);
|
||||||
digitalWrite(DCDC_EN_HOLD, HIGH);
|
digitalWrite(DCDC_EN_HOLD, HIGH);
|
||||||
|
|||||||
@@ -61,11 +61,6 @@ bool CryptoEngine::regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
void CryptoEngine::clearKeys()
|
|
||||||
{
|
|
||||||
memset(public_key, 0, sizeof(public_key));
|
|
||||||
memset(private_key, 0, sizeof(private_key));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt a packet's payload using a key generated with Curve25519 and SHA256
|
* Encrypt a packet's payload using a key generated with Curve25519 and SHA256
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ class CryptoEngine
|
|||||||
virtual bool regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey);
|
virtual bool regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
void clearKeys();
|
|
||||||
void setDHPrivateKey(uint8_t *_private_key);
|
void setDHPrivateKey(uint8_t *_private_key);
|
||||||
virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic,
|
virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic,
|
||||||
uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut);
|
uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut);
|
||||||
|
|||||||
@@ -145,6 +145,18 @@ bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
|
|||||||
tosend->hop_start -= (tosend->hop_limit - 2);
|
tosend->hop_start -= (tosend->hop_limit - 2);
|
||||||
tosend->hop_limit = 2;
|
tosend->hop_limit = 2;
|
||||||
}
|
}
|
||||||
|
#elif ARCH_PORTDUINO
|
||||||
|
if (tosend->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
|
||||||
|
portduino_config.nohop_ports.size()) {
|
||||||
|
for (const auto &port : portduino_config.nohop_ports) {
|
||||||
|
if (port == tosend->decoded.portnum) {
|
||||||
|
LOG_DEBUG("0-hopping portnum %u", tosend->decoded.portnum);
|
||||||
|
tosend->hop_start -= tosend->hop_limit;
|
||||||
|
tosend->hop_limit = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (p->next_hop == NO_NEXT_HOP_PREFERENCE) {
|
if (p->next_hop == NO_NEXT_HOP_PREFERENCE) {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <pb_decode.h>
|
#include <pb_decode.h>
|
||||||
#include <pb_encode.h>
|
#include <pb_encode.h>
|
||||||
|
#include <power/PowerHAL.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
@@ -1418,6 +1419,14 @@ void NodeDB::loadFromDisk()
|
|||||||
bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct,
|
bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct,
|
||||||
bool fullAtomic)
|
bool fullAtomic)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// do not try to save anything if power level is not safe. In many cases flash will be lock-protected
|
||||||
|
// and all writes will fail anyway. Device should be sleeping at this point anyway.
|
||||||
|
if (!powerHAL_isPowerLevelSafe()) {
|
||||||
|
LOG_ERROR("Error: trying to saveProto() on unsafe device power level.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool okay = false;
|
bool okay = false;
|
||||||
#ifdef FSCom
|
#ifdef FSCom
|
||||||
auto f = SafeFile(filename, fullAtomic);
|
auto f = SafeFile(filename, fullAtomic);
|
||||||
@@ -1444,6 +1453,14 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_
|
|||||||
|
|
||||||
bool NodeDB::saveChannelsToDisk()
|
bool NodeDB::saveChannelsToDisk()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// do not try to save anything if power level is not safe. In many cases flash will be lock-protected
|
||||||
|
// and all writes will fail anyway.
|
||||||
|
if (!powerHAL_isPowerLevelSafe()) {
|
||||||
|
LOG_ERROR("Error: trying to saveChannelsToDisk() on unsafe device power level.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef FSCom
|
#ifdef FSCom
|
||||||
spiLock->lock();
|
spiLock->lock();
|
||||||
FSCom.mkdir("/prefs");
|
FSCom.mkdir("/prefs");
|
||||||
@@ -1454,6 +1471,14 @@ bool NodeDB::saveChannelsToDisk()
|
|||||||
|
|
||||||
bool NodeDB::saveDeviceStateToDisk()
|
bool NodeDB::saveDeviceStateToDisk()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// do not try to save anything if power level is not safe. In many cases flash will be lock-protected
|
||||||
|
// and all writes will fail anyway. Device should be sleeping at this point anyway.
|
||||||
|
if (!powerHAL_isPowerLevelSafe()) {
|
||||||
|
LOG_ERROR("Error: trying to saveDeviceStateToDisk() on unsafe device power level.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef FSCom
|
#ifdef FSCom
|
||||||
spiLock->lock();
|
spiLock->lock();
|
||||||
FSCom.mkdir("/prefs");
|
FSCom.mkdir("/prefs");
|
||||||
@@ -1466,6 +1491,14 @@ bool NodeDB::saveDeviceStateToDisk()
|
|||||||
|
|
||||||
bool NodeDB::saveNodeDatabaseToDisk()
|
bool NodeDB::saveNodeDatabaseToDisk()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// do not try to save anything if power level is not safe. In many cases flash will be lock-protected
|
||||||
|
// and all writes will fail anyway. Device should be sleeping at this point anyway.
|
||||||
|
if (!powerHAL_isPowerLevelSafe()) {
|
||||||
|
LOG_ERROR("Error: trying to saveNodeDatabaseToDisk() on unsafe device power level.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef FSCom
|
#ifdef FSCom
|
||||||
spiLock->lock();
|
spiLock->lock();
|
||||||
FSCom.mkdir("/prefs");
|
FSCom.mkdir("/prefs");
|
||||||
@@ -1478,6 +1511,14 @@ bool NodeDB::saveNodeDatabaseToDisk()
|
|||||||
|
|
||||||
bool NodeDB::saveToDiskNoRetry(int saveWhat)
|
bool NodeDB::saveToDiskNoRetry(int saveWhat)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// do not try to save anything if power level is not safe. In many cases flash will be lock-protected
|
||||||
|
// and all writes will fail anyway. Device should be sleeping at this point anyway.
|
||||||
|
if (!powerHAL_isPowerLevelSafe()) {
|
||||||
|
LOG_ERROR("Error: trying to saveToDiskNoRetry() on unsafe device power level.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
#ifdef FSCom
|
#ifdef FSCom
|
||||||
spiLock->lock();
|
spiLock->lock();
|
||||||
@@ -1533,6 +1574,14 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
|
|||||||
bool NodeDB::saveToDisk(int saveWhat)
|
bool NodeDB::saveToDisk(int saveWhat)
|
||||||
{
|
{
|
||||||
LOG_DEBUG("Save to disk %d", saveWhat);
|
LOG_DEBUG("Save to disk %d", saveWhat);
|
||||||
|
|
||||||
|
// do not try to save anything if power level is not safe. In many cases flash will be lock-protected
|
||||||
|
// and all writes will fail anyway. Device should be sleeping at this point anyway.
|
||||||
|
if (!powerHAL_isPowerLevelSafe()) {
|
||||||
|
LOG_ERROR("Error: trying to saveToDisk() on unsafe device power level.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool success = saveToDiskNoRetry(saveWhat);
|
bool success = saveToDiskNoRetry(saveWhat);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
|||||||
@@ -326,9 +326,9 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(meshtastic_MeshPacket *p)
|
|||||||
void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
|
void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
|
||||||
{
|
{
|
||||||
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
||||||
std::string out =
|
std::string out = DEBUG_PORT.mt_sprintf(
|
||||||
DEBUG_PORT.mt_sprintf("%s (id=0x%08x fr=0x%08x to=0x%08x, transport = %u, WantAck=%d, HopLim=%d Ch=0x%x", prefix, p->id,
|
"%s (id=0x%08x fr=0x%08x to=0x%08x, transport = %u, WantAck=%d, HopLim=%d HopStart=%d Ch=0x%x", prefix, p->id, p->from,
|
||||||
p->from, p->to, p->transport_mechanism, p->want_ack, p->hop_limit, p->channel);
|
p->to, p->transport_mechanism, p->want_ack, p->hop_limit, p->hop_start, p->channel);
|
||||||
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||||||
auto &s = p->decoded;
|
auto &s = p->decoded;
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#endif
|
#endif
|
||||||
#include "Default.h"
|
#include "Default.h"
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
|
#include "modules/Native/StoreForwardPlusPlus.h"
|
||||||
#include "platform/portduino/PortduinoGlue.h"
|
#include "platform/portduino/PortduinoGlue.h"
|
||||||
#endif
|
#endif
|
||||||
#if ENABLE_JSON_LOGGING || ARCH_PORTDUINO
|
#if ENABLE_JSON_LOGGING || ARCH_PORTDUINO
|
||||||
@@ -359,6 +360,12 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
|
|||||||
abortSendAndNak(encodeResult, p);
|
abortSendAndNak(encodeResult, p);
|
||||||
return encodeResult; // FIXME - this isn't a valid ErrorCode
|
return encodeResult; // FIXME - this isn't a valid ErrorCode
|
||||||
}
|
}
|
||||||
|
#if ARCH_PORTDUINO
|
||||||
|
if (p_decoded->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP &&
|
||||||
|
(p->from == 0 || p->from == nodeDB->getNodeNum()) && storeForwardPlusPlusModule && portduino_config.sfpp_enabled) {
|
||||||
|
storeForwardPlusPlusModule->handleEncrypted(p_decoded, p);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||||
// Only publish to MQTT if we're the original transmitter of the packet
|
// Only publish to MQTT if we're the original transmitter of the packet
|
||||||
if (moduleConfig.mqtt.enabled && isFromUs(p) && mqtt) {
|
if (moduleConfig.mqtt.enabled && isFromUs(p) && mqtt) {
|
||||||
@@ -735,6 +742,22 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
|
|||||||
cancelSending(p->from, p->id);
|
cancelSending(p->from, p->id);
|
||||||
skipHandle = true;
|
skipHandle = true;
|
||||||
}
|
}
|
||||||
|
#if ARCH_PORTDUINO
|
||||||
|
if (portduino_config.whitelist_enabled) {
|
||||||
|
bool allowed = false;
|
||||||
|
for (const auto &port : portduino_config.whitelist_ports) {
|
||||||
|
if (port == p->decoded.portnum) {
|
||||||
|
allowed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!allowed) {
|
||||||
|
LOG_DEBUG("Dropping packet not on Portduino Whitelist");
|
||||||
|
cancelSending(p->from, p->id);
|
||||||
|
skipHandle = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
printPacket("packet decoding failed or skipped (no PSK?)", p);
|
printPacket("packet decoding failed or skipped (no PSK?)", p);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@
|
|||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
#include "input/LinuxInputImpl.h"
|
#include "input/LinuxInputImpl.h"
|
||||||
#include "input/SeesawRotary.h"
|
#include "input/SeesawRotary.h"
|
||||||
|
#include "modules/Native/StoreForwardPlusPlus.h"
|
||||||
#include "modules/Telemetry/HostMetrics.h"
|
#include "modules/Telemetry/HostMetrics.h"
|
||||||
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
|
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
|
||||||
#include "modules/StoreForwardModule.h"
|
#include "modules/StoreForwardModule.h"
|
||||||
@@ -243,6 +244,11 @@ void setupModules()
|
|||||||
#endif
|
#endif
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
new HostMetricsModule();
|
new HostMetricsModule();
|
||||||
|
#if SFPP_ENABLED
|
||||||
|
if (portduino_config.sfpp_enabled) {
|
||||||
|
storeForwardPlusPlusModule = new StoreForwardPlusPlusModule();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#if HAS_TELEMETRY
|
#if HAS_TELEMETRY
|
||||||
new DeviceTelemetryModule();
|
new DeviceTelemetryModule();
|
||||||
|
|||||||
2113
src/modules/Native/StoreForwardPlusPlus.cpp
Normal file
2113
src/modules/Native/StoreForwardPlusPlus.cpp
Normal file
File diff suppressed because it is too large
Load Diff
310
src/modules/Native/StoreForwardPlusPlus.h
Normal file
310
src/modules/Native/StoreForwardPlusPlus.h
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
#pragma once
|
||||||
|
#if __has_include("sqlite3.h")
|
||||||
|
#define SFPP_ENABLED 1
|
||||||
|
#include "Channels.h"
|
||||||
|
#include "ProtobufModule.h"
|
||||||
|
#include "Router.h"
|
||||||
|
#include "SinglePortModule.h"
|
||||||
|
#include "sqlite3.h"
|
||||||
|
|
||||||
|
#define SFPP_HASH_SIZE 16
|
||||||
|
#define SFPP_SHORT_HASH_SIZE 8
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store and forward ++ module
|
||||||
|
* There's an obvious need for a store-and-forward mechanism in Meshtastic.
|
||||||
|
* This module takes heavy inspiration from Git, building a chain of messages that can be synced between nodes.
|
||||||
|
* Each message is hashed, and the chain is built by hashing the previous commit hash and the current message hash.
|
||||||
|
* Nodes can request missing messages by requesting the next message after a given commit hash.
|
||||||
|
*
|
||||||
|
* The current focus is text messages, limited to the primary channel.
|
||||||
|
*
|
||||||
|
* Each chain is identified by a root hash, which is derived from the channelHash, the local nodenum, and the timestamp when
|
||||||
|
* created.
|
||||||
|
*
|
||||||
|
* Each message is also given a message hash, derived from the encrypted payload, the to, from, id.
|
||||||
|
* Notably not the timestamp, as we want these to match across nodes, even if the timestamps differ.
|
||||||
|
*
|
||||||
|
* The authoritative node for the chain will generate a commit hash for each message when adding it to the chain.
|
||||||
|
* The first message's commit hash is derived from the root hash and the message hash.
|
||||||
|
* Subsequent messages' commit hashes are derived from the previous commit hash and the current message hash.
|
||||||
|
* This allows a node to see only the last commit hash, and confirm it hasn't missed any messages.
|
||||||
|
*
|
||||||
|
* Nodes can request the next message in the chain by sending a LINK_REQUEST message with the root hash and the last known commit
|
||||||
|
* hash. Any node that has the next message can respond with a LINK_PROVIDE message containing the next message.
|
||||||
|
*
|
||||||
|
* When a satellite node sees a new text message, it stores it in a scratch database.
|
||||||
|
* These messages are periodically offered to the authoritative node for inclusion in the chain.
|
||||||
|
*
|
||||||
|
* The LINK_PROVIDE message does double-duty, sending both on-chain and off-chain messages.
|
||||||
|
* The differentiator is whether the commit hash is set or left empty.
|
||||||
|
*
|
||||||
|
* When a satellite node receives a canonical link message, it checks if it has the message in scratch.
|
||||||
|
* And evicts it when adding it to the canonical chain.
|
||||||
|
*
|
||||||
|
* This approach allows a node to know whether it has seen a given message before, or if it is new coming via SFPP.
|
||||||
|
* If new, and the timestamp is within the rebroadcast timeout, it will process that message as if it were just received from the
|
||||||
|
* mesh, allowing it to be decrypted, shown to the user, and rebroadcast.
|
||||||
|
*/
|
||||||
|
class StoreForwardPlusPlusModule : public ProtobufModule<meshtastic_StoreForwardPlusPlus>, private concurrency::OSThread
|
||||||
|
{
|
||||||
|
struct link_object {
|
||||||
|
uint32_t to;
|
||||||
|
uint32_t from;
|
||||||
|
uint32_t id;
|
||||||
|
uint32_t rx_time = 0;
|
||||||
|
ChannelHash channel_hash;
|
||||||
|
uint8_t encrypted_bytes[256] = {0};
|
||||||
|
size_t encrypted_len;
|
||||||
|
uint8_t message_hash[SFPP_HASH_SIZE] = {0};
|
||||||
|
size_t message_hash_len = 0;
|
||||||
|
uint8_t root_hash[SFPP_HASH_SIZE] = {0};
|
||||||
|
size_t root_hash_len = 0;
|
||||||
|
uint8_t commit_hash[SFPP_HASH_SIZE] = {0};
|
||||||
|
size_t commit_hash_len = 0;
|
||||||
|
uint32_t counter = 0;
|
||||||
|
std::string payload;
|
||||||
|
bool validObject = true; // set this false when a chain calulation fails, etc.
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Constructor
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
StoreForwardPlusPlusModule();
|
||||||
|
|
||||||
|
/*
|
||||||
|
-Override the wantPacket method.
|
||||||
|
*/
|
||||||
|
virtual bool wantPacket(const meshtastic_MeshPacket *p) override
|
||||||
|
{
|
||||||
|
if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP ||
|
||||||
|
p->decoded.portnum == (portduino_config.sfpp_steal_port ? meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP
|
||||||
|
: meshtastic_PortNum_STORE_FORWARD_PLUSPLUS_APP)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleEncrypted(const meshtastic_MeshPacket *, const meshtastic_MeshPacket *);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** Called to handle a particular incoming message
|
||||||
|
@return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for
|
||||||
|
it
|
||||||
|
*/
|
||||||
|
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
||||||
|
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_StoreForwardPlusPlus *t) override;
|
||||||
|
|
||||||
|
virtual int32_t runOnce() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
sqlite3 *ppDb;
|
||||||
|
sqlite3_stmt *chain_insert_stmt;
|
||||||
|
sqlite3_stmt *scratch_insert_stmt;
|
||||||
|
sqlite3_stmt *checkDupMessageHash;
|
||||||
|
sqlite3_stmt *checkDupCommitHash;
|
||||||
|
sqlite3_stmt *checkScratch;
|
||||||
|
sqlite3_stmt *removeScratch;
|
||||||
|
sqlite3_stmt *updatePayloadStmt;
|
||||||
|
sqlite3_stmt *getPayloadFromScratchStmt;
|
||||||
|
sqlite3_stmt *fromScratchStmt;
|
||||||
|
sqlite3_stmt *fromScratchByHashStmt;
|
||||||
|
sqlite3_stmt *getNextHashStmt;
|
||||||
|
sqlite3_stmt *getChainEndStmt;
|
||||||
|
sqlite3_stmt *getLinkStmt;
|
||||||
|
sqlite3_stmt *getLinkFromMessageHashStmt;
|
||||||
|
sqlite3_stmt *getHashFromRootStmt;
|
||||||
|
sqlite3_stmt *addRootToMappingsStmt;
|
||||||
|
sqlite3_stmt *getRootFromChannelHashStmt;
|
||||||
|
sqlite3_stmt *getFullRootHashStmt;
|
||||||
|
sqlite3_stmt *setChainCountStmt;
|
||||||
|
sqlite3_stmt *getChainCountStmt;
|
||||||
|
sqlite3_stmt *getScratchCountStmt;
|
||||||
|
sqlite3_stmt *getRootCanonScratchCountStmt;
|
||||||
|
sqlite3_stmt *pruneScratchQueueStmt;
|
||||||
|
sqlite3_stmt *trimOldestLinkStmt;
|
||||||
|
sqlite3_stmt *maybeAddPeerStmt;
|
||||||
|
sqlite3_stmt *getPeerStmt;
|
||||||
|
sqlite3_stmt *updatePeerStmt;
|
||||||
|
sqlite3_stmt *clearChainStmt;
|
||||||
|
sqlite3_stmt *canon_scratch_insert_stmt;
|
||||||
|
sqlite3_stmt *getCanonScratchCountStmt;
|
||||||
|
sqlite3_stmt *getCanonScratchStmt;
|
||||||
|
sqlite3_stmt *removeCanonScratch;
|
||||||
|
sqlite3_stmt *clearCanonScratchStmt;
|
||||||
|
|
||||||
|
// For a given Meshtastic ChannelHash, fills the root_hash buffer with a 32-byte root hash
|
||||||
|
// returns true if the root hash was found
|
||||||
|
bool getRootFromChannelHash(ChannelHash, uint8_t *);
|
||||||
|
|
||||||
|
// For a given root hash, returns the ChannelHash
|
||||||
|
// can handle partial root hashes
|
||||||
|
ChannelHash getChannelHashFromRoot(uint8_t *_root_hash, size_t);
|
||||||
|
|
||||||
|
// given a root hash and commit hash, returns the next commit hash in the chain
|
||||||
|
// can handle partial root and commit hashes, always fills the buffer with 32 bytes
|
||||||
|
// returns true if a next hash was found
|
||||||
|
bool getNextHash(uint8_t *, size_t, uint8_t *, size_t, uint8_t *);
|
||||||
|
|
||||||
|
// For a given Meshtastic ChannelHash, fills the root_hash buffer with a 32-byte root hash
|
||||||
|
// but this function will add the root hash if it is not already present
|
||||||
|
// returns hash size or 0 if not found/added
|
||||||
|
size_t getOrAddRootFromChannelHash(ChannelHash, uint8_t *);
|
||||||
|
|
||||||
|
// adds the ChannelHash and root_hash to the mappings table
|
||||||
|
void addRootToMappings(ChannelHash, uint8_t *);
|
||||||
|
|
||||||
|
// requests the next message in the chain from the mesh network
|
||||||
|
// Sends a LINK_REQUEST message
|
||||||
|
void requestNextMessage(uint8_t *, size_t, uint8_t *, size_t);
|
||||||
|
|
||||||
|
// request the message X entries from the end.
|
||||||
|
// used to bootstrap a chain, without downloading all of the history
|
||||||
|
void requestMessageCount(uint8_t *, size_t, uint32_t);
|
||||||
|
|
||||||
|
// sends a LINK_PROVIDE message broadcasting the given link object
|
||||||
|
void broadcastLink(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// sends a LINK_PROVIDE message broadcasting the given link object
|
||||||
|
void broadcastLink(link_object &, bool, bool = false);
|
||||||
|
|
||||||
|
// sends a LINK_PROVIDE message broadcasting the given link object from scratch message store
|
||||||
|
bool sendFromScratch(uint8_t *);
|
||||||
|
|
||||||
|
// Adds the given link object to the canonical chain database
|
||||||
|
bool addToChain(link_object &);
|
||||||
|
|
||||||
|
// Adds an incoming text message to the scratch database
|
||||||
|
bool addToScratch(link_object &);
|
||||||
|
|
||||||
|
// sends a CANON_ANNOUNCE message, specifying the given root and commit hashes
|
||||||
|
void canonAnnounce(link_object &);
|
||||||
|
|
||||||
|
// checks if the message hash is present in the canonical chain database
|
||||||
|
bool isInDB(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// checks if the commit hash is present in the canonical chain database
|
||||||
|
bool isCommitInDB(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// checks if the message hash is present in the scratch database
|
||||||
|
bool isInScratch(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// retrieves a link object from the scratch database
|
||||||
|
link_object getFromScratch(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// removes a link object from the scratch database
|
||||||
|
void removeFromScratch(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// iterate through our scratch database, and see if we can speculate a chain up to the given commit hash
|
||||||
|
bool speculateScratchChain(uint8_t *, size_t, uint8_t *, uint8_t *);
|
||||||
|
|
||||||
|
// retrieves the next link object from scratch given a root hash
|
||||||
|
link_object getNextScratchObject(uint8_t *);
|
||||||
|
|
||||||
|
// fills the payload section with the decrypted data for the given message hash
|
||||||
|
// probably not needed for production, but useful for testing
|
||||||
|
void updatePayload(uint8_t *, size_t, std::string);
|
||||||
|
|
||||||
|
// Takes the decrypted MeshPacket and the encrypted packet copy, and builds a link_object
|
||||||
|
// Generates a message hash, but does not set the commit hash
|
||||||
|
link_object ingestTextPacket(const meshtastic_MeshPacket &, const meshtastic_MeshPacket *);
|
||||||
|
|
||||||
|
// ingests a LINK_PROVIDE message and builds a link_object
|
||||||
|
// confirms the root hash and commit hash
|
||||||
|
link_object ingestLinkMessage(meshtastic_StoreForwardPlusPlus *);
|
||||||
|
|
||||||
|
// retrieves a link object from the canonical chain database given a commit hash
|
||||||
|
link_object getLink(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// retrieves a link object from the canonical chain database given a message hash
|
||||||
|
link_object getLinkFromMessageHash(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// puts the encrypted payload back into the queue as if it were just received
|
||||||
|
void rebroadcastLinkObject(link_object &);
|
||||||
|
|
||||||
|
// Check if an incoming link object's commit hash matches the calculated commit hash
|
||||||
|
bool checkCommitHash(link_object &lo, uint8_t *commit_hash_bytes, size_t hash_len);
|
||||||
|
|
||||||
|
// given a partial root hash, looks up the full 32-byte root hash
|
||||||
|
// returns true if found
|
||||||
|
bool lookUpFullRootHash(uint8_t *partial_root_hash, size_t partial_root_hash_len, uint8_t *full_root_hash);
|
||||||
|
|
||||||
|
// update the mappings table to set the chain count for the given root hash
|
||||||
|
void setChainCount(uint8_t *, size_t, uint32_t);
|
||||||
|
|
||||||
|
// get the chain count for the given root hash
|
||||||
|
uint32_t getChainCount(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// get the scratch count for the given root hash
|
||||||
|
uint32_t getScratchCount(uint8_t *, size_t);
|
||||||
|
|
||||||
|
// get the canon scratch count for the given root hash
|
||||||
|
uint32_t getCanonScratchCount(uint8_t *, size_t);
|
||||||
|
|
||||||
|
link_object getLinkFromPositionFromTip(uint32_t, uint8_t *, size_t);
|
||||||
|
|
||||||
|
void pruneScratchQueue();
|
||||||
|
|
||||||
|
void trimOldestLink(uint8_t *, size_t);
|
||||||
|
|
||||||
|
void clearChain(uint8_t *, size_t);
|
||||||
|
|
||||||
|
void recalculateMessageHash(link_object &);
|
||||||
|
|
||||||
|
// given a link object with a payload and other fields, recalculates the message hash
|
||||||
|
// returns true if a match
|
||||||
|
bool recalculateHash(link_object &, uint8_t *, size_t, uint8_t *, size_t);
|
||||||
|
|
||||||
|
void updatePeers(const meshtastic_MeshPacket &, meshtastic_StoreForwardPlusPlus_SFPP_message_type);
|
||||||
|
|
||||||
|
void maybeMoveFromCanonScratch(uint8_t *, size_t);
|
||||||
|
|
||||||
|
void addToCanonScratch(link_object &);
|
||||||
|
|
||||||
|
link_object getfromCanonScratch(uint8_t *, size_t);
|
||||||
|
void removeFromCanonScratch(uint8_t *, size_t);
|
||||||
|
|
||||||
|
void clearCanonScratch(uint8_t *, size_t, uint32_t);
|
||||||
|
|
||||||
|
bool isInCanonScratch(uint8_t *, size_t);
|
||||||
|
|
||||||
|
void logLinkObject(link_object &);
|
||||||
|
|
||||||
|
// Track if we have a scheduled runOnce pending
|
||||||
|
// useful to not accudentally delay a scheduled runOnce
|
||||||
|
bool pendingRun = false;
|
||||||
|
|
||||||
|
// Once we have multiple chain types, we can extend this
|
||||||
|
enum chain_types {
|
||||||
|
channel_chain = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
uint32_t rebroadcastTimeout = 3600; // Messages older than this (in seconds) will not be rebroadcast
|
||||||
|
bool doing_split_send = false;
|
||||||
|
link_object split_link_out;
|
||||||
|
|
||||||
|
bool doing_split_receive = false;
|
||||||
|
link_object split_link_in;
|
||||||
|
|
||||||
|
bool did_announce_last = false;
|
||||||
|
|
||||||
|
uint32_t texts_rebroadcast = 0;
|
||||||
|
uint32_t links_speculated = 0;
|
||||||
|
uint32_t canon_announces = 0;
|
||||||
|
uint32_t links_requested = 0;
|
||||||
|
uint32_t links_provided = 0;
|
||||||
|
uint32_t links_added = 0;
|
||||||
|
uint32_t links_from_canon_scratch = 0;
|
||||||
|
uint32_t links_from_scratch = 0;
|
||||||
|
uint32_t split_links_sent = 0;
|
||||||
|
uint32_t split_links_received = 0;
|
||||||
|
uint32_t links_pruned = 0;
|
||||||
|
uint32_t scratch_timed_out = 0;
|
||||||
|
uint32_t sent_from_scratch = 0;
|
||||||
|
uint32_t received_from_scratch = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern StoreForwardPlusPlusModule *storeForwardPlusPlusModule;
|
||||||
|
#endif
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "Default.h"
|
#include "Default.h"
|
||||||
#include "MeshService.h"
|
#include "MeshService.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
|
#include "NodeStatus.h"
|
||||||
#include "RTC.h"
|
#include "RTC.h"
|
||||||
#include "Router.h"
|
#include "Router.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
@@ -129,14 +130,17 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
|
|||||||
LOG_DEBUG("Skip send NodeInfo > 40%% ch. util");
|
LOG_DEBUG("Skip send NodeInfo > 40%% ch. util");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
// If we sent our NodeInfo less than 5 min. ago, don't send it again as it may be still underway.
|
|
||||||
if (!shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 5 * 60 * 1000)) {
|
// Use graduated scaling based on active mesh size (10 minute base, scales with congestion coefficient)
|
||||||
LOG_DEBUG("Skip send NodeInfo since we sent it <5min ago");
|
uint32_t timeoutMs = Default::getConfiguredOrDefaultMsScaled(0, 10 * 60, nodeStatus->getNumOnline());
|
||||||
|
if (!shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, timeoutMs)) {
|
||||||
|
LOG_DEBUG("Skip send NodeInfo since we sent it <%us ago", timeoutMs / 1000);
|
||||||
ignoreRequest = true; // Mark it as ignored for MeshModule
|
ignoreRequest = true; // Mark it as ignored for MeshModule
|
||||||
return NULL;
|
return NULL;
|
||||||
} else if (shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 60 * 1000)) {
|
} else if (shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 60 * 1000)) {
|
||||||
|
// For interactive/urgent requests (e.g., user-triggered or implicit requests), use a shorter 60s timeout
|
||||||
LOG_DEBUG("Skip send NodeInfo since we sent it <60s ago");
|
LOG_DEBUG("Skip send NodeInfo since we sent it <60s ago");
|
||||||
ignoreRequest = true; // Mark it as ignored for MeshModule
|
ignoreRequest = true;
|
||||||
return NULL;
|
return NULL;
|
||||||
} else {
|
} else {
|
||||||
ignoreRequest = false; // Don't ignore requests anymore
|
ignoreRequest = false; // Don't ignore requests anymore
|
||||||
|
|||||||
@@ -4,6 +4,15 @@
|
|||||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||||
#include "RAK12035Sensor.h"
|
#include "RAK12035Sensor.h"
|
||||||
|
|
||||||
|
// The RAK12035 library's sensor_sleep() sets WB_IO2 (GPIO 34) LOW, which controls
|
||||||
|
// the 3.3V switched power rail (PIN_3V3_EN). This turns off power to ALL peripherals
|
||||||
|
// including GPS. We need to restore power after the library turns it off.
|
||||||
|
#ifdef PIN_3V3_EN
|
||||||
|
#define RESTORE_3V3_POWER() digitalWrite(PIN_3V3_EN, HIGH)
|
||||||
|
#else
|
||||||
|
#define RESTORE_3V3_POWER()
|
||||||
|
#endif
|
||||||
|
|
||||||
RAK12035Sensor::RAK12035Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RAK12035, "RAK12035") {}
|
RAK12035Sensor::RAK12035Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RAK12035, "RAK12035") {}
|
||||||
|
|
||||||
bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
|
bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
|
||||||
@@ -13,7 +22,6 @@ bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
|
|||||||
delay(100);
|
delay(100);
|
||||||
sensor.begin(dev->address.address);
|
sensor.begin(dev->address.address);
|
||||||
|
|
||||||
// Get sensor firmware version
|
|
||||||
uint8_t data = 0;
|
uint8_t data = 0;
|
||||||
sensor.get_sensor_version(&data);
|
sensor.get_sensor_version(&data);
|
||||||
if (data != 0) {
|
if (data != 0) {
|
||||||
@@ -21,8 +29,8 @@ bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
|
|||||||
LOG_INFO("RAK12035Sensor Init Succeed \nSensor1 Firmware version: %i, Sensor Name: %s", data, sensorName);
|
LOG_INFO("RAK12035Sensor Init Succeed \nSensor1 Firmware version: %i, Sensor Name: %s", data, sensorName);
|
||||||
status = true;
|
status = true;
|
||||||
sensor.sensor_sleep();
|
sensor.sensor_sleep();
|
||||||
|
RESTORE_3V3_POWER();
|
||||||
} else {
|
} else {
|
||||||
// If we reach here, it means the sensor did not initialize correctly.
|
|
||||||
LOG_INFO("Init sensor: %s", sensorName);
|
LOG_INFO("Init sensor: %s", sensorName);
|
||||||
LOG_ERROR("RAK12035Sensor Init Failed");
|
LOG_ERROR("RAK12035Sensor Init Failed");
|
||||||
status = false;
|
status = false;
|
||||||
@@ -38,8 +46,6 @@ bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
|
|||||||
|
|
||||||
void RAK12035Sensor::setup()
|
void RAK12035Sensor::setup()
|
||||||
{
|
{
|
||||||
// Set the calibration values
|
|
||||||
// Reading the saved calibration values from the sensor.
|
|
||||||
// TODO:: Check for and run calibration check for up to 2 additional sensors if present.
|
// TODO:: Check for and run calibration check for up to 2 additional sensors if present.
|
||||||
uint16_t zero_val = 0;
|
uint16_t zero_val = 0;
|
||||||
uint16_t hundred_val = 0;
|
uint16_t hundred_val = 0;
|
||||||
@@ -71,6 +77,7 @@ void RAK12035Sensor::setup()
|
|||||||
LOG_INFO("Wet calibration reset complete. New value is %d", hundred_val);
|
LOG_INFO("Wet calibration reset complete. New value is %d", hundred_val);
|
||||||
}
|
}
|
||||||
sensor.sensor_sleep();
|
sensor.sensor_sleep();
|
||||||
|
RESTORE_3V3_POWER();
|
||||||
delay(200);
|
delay(200);
|
||||||
LOG_INFO("Dry calibration value is %d", zero_val);
|
LOG_INFO("Dry calibration value is %d", zero_val);
|
||||||
LOG_INFO("Wet calibration value is %d", hundred_val);
|
LOG_INFO("Wet calibration value is %d", hundred_val);
|
||||||
@@ -79,10 +86,6 @@ void RAK12035Sensor::setup()
|
|||||||
bool RAK12035Sensor::getMetrics(meshtastic_Telemetry *measurement)
|
bool RAK12035Sensor::getMetrics(meshtastic_Telemetry *measurement)
|
||||||
{
|
{
|
||||||
// TODO:: read and send metrics for up to 2 additional soil monitors if present.
|
// TODO:: read and send metrics for up to 2 additional soil monitors if present.
|
||||||
// -- how to do this.. this could get a little complex..
|
|
||||||
// ie - 1> we combine them into an average and send that, 2> we send them as separate metrics
|
|
||||||
// ^-- these scenarios would require different handling of the metrics in the receiving end and maybe a setting in the
|
|
||||||
// device ui and an additional proto for that?
|
|
||||||
measurement->variant.environment_metrics.has_soil_temperature = true;
|
measurement->variant.environment_metrics.has_soil_temperature = true;
|
||||||
measurement->variant.environment_metrics.has_soil_moisture = true;
|
measurement->variant.environment_metrics.has_soil_moisture = true;
|
||||||
|
|
||||||
@@ -97,6 +100,7 @@ bool RAK12035Sensor::getMetrics(meshtastic_Telemetry *measurement)
|
|||||||
success &= sensor.get_sensor_temperature(&temp);
|
success &= sensor.get_sensor_temperature(&temp);
|
||||||
delay(200);
|
delay(200);
|
||||||
sensor.sensor_sleep();
|
sensor.sensor_sleep();
|
||||||
|
RESTORE_3V3_POWER();
|
||||||
|
|
||||||
if (success == false) {
|
if (success == false) {
|
||||||
LOG_ERROR("Failed to read sensor data");
|
LOG_ERROR("Failed to read sensor data");
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ void startAdv(void)
|
|||||||
Bluefruit.Advertising.addService(meshBleService);
|
Bluefruit.Advertising.addService(meshBleService);
|
||||||
/* Start Advertising
|
/* Start Advertising
|
||||||
* - Enable auto advertising if disconnected
|
* - Enable auto advertising if disconnected
|
||||||
* - Interval: fast mode = 20 ms, slow mode = 152.5 ms
|
* - Interval: fast mode = 20 ms, slow mode = 417,5 ms
|
||||||
* - Timeout for fast mode is 30 seconds
|
* - Timeout for fast mode is 30 seconds
|
||||||
* - Start(timeout) with timeout = 0 will advertise forever (until connected)
|
* - Start(timeout) with timeout = 0 will advertise forever (until connected)
|
||||||
*
|
*
|
||||||
@@ -127,7 +127,7 @@ void startAdv(void)
|
|||||||
* https://developer.apple.com/library/content/qa/qa1931/_index.html
|
* https://developer.apple.com/library/content/qa/qa1931/_index.html
|
||||||
*/
|
*/
|
||||||
Bluefruit.Advertising.restartOnDisconnect(true);
|
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||||
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
|
Bluefruit.Advertising.setInterval(32, 668); // in unit of 0.625 ms
|
||||||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds. FIXME, we should stop advertising after X
|
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds. FIXME, we should stop advertising after X
|
||||||
}
|
}
|
||||||
@@ -240,6 +240,12 @@ int NRF52Bluetooth::getRssi()
|
|||||||
{
|
{
|
||||||
return 0; // FIXME figure out where to source this
|
return 0; // FIXME figure out where to source this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Valid BLE TX power levels as per nRF52840 Product Specification are: "-20 to +8 dBm TX power, configurable in 4 dB steps".
|
||||||
|
// See https://docs.nordicsemi.com/bundle/ps_nrf52840/page/keyfeatures_html5.html
|
||||||
|
#define VALID_BLE_TX_POWER(x) \
|
||||||
|
((x) == -20 || (x) == -16 || (x) == -12 || (x) == -8 || (x) == -4 || (x) == 0 || (x) == 4 || (x) == 8)
|
||||||
|
|
||||||
void NRF52Bluetooth::setup()
|
void NRF52Bluetooth::setup()
|
||||||
{
|
{
|
||||||
// Initialise the Bluefruit module
|
// Initialise the Bluefruit module
|
||||||
@@ -251,6 +257,9 @@ void NRF52Bluetooth::setup()
|
|||||||
Bluefruit.Advertising.stop();
|
Bluefruit.Advertising.stop();
|
||||||
Bluefruit.Advertising.clearData();
|
Bluefruit.Advertising.clearData();
|
||||||
Bluefruit.ScanResponse.clearData();
|
Bluefruit.ScanResponse.clearData();
|
||||||
|
#if defined(NRF52_BLE_TX_POWER) && VALID_BLE_TX_POWER(NRF52_BLE_TX_POWER)
|
||||||
|
Bluefruit.setTxPower(NRF52_BLE_TX_POWER);
|
||||||
|
#endif
|
||||||
if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) {
|
if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) {
|
||||||
configuredPasskey = config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_FIXED_PIN
|
configuredPasskey = config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_FIXED_PIN
|
||||||
? config.bluetooth.fixed_pin
|
? config.bluetooth.fixed_pin
|
||||||
@@ -272,6 +281,29 @@ void NRF52Bluetooth::setup()
|
|||||||
// Set the connect/disconnect callback handlers
|
// Set the connect/disconnect callback handlers
|
||||||
Bluefruit.Periph.setConnectCallback(onConnect);
|
Bluefruit.Periph.setConnectCallback(onConnect);
|
||||||
Bluefruit.Periph.setDisconnectCallback(onDisconnect);
|
Bluefruit.Periph.setDisconnectCallback(onDisconnect);
|
||||||
|
|
||||||
|
// Do not change Slave Latency to value other than 0 !!!
|
||||||
|
// There is probably a bug in SoftDevice + certain Apple iOS versions being
|
||||||
|
// brain damaged causing connectivity problems.
|
||||||
|
|
||||||
|
// On one side it seems SoftDevice is using SlaveLatency value even
|
||||||
|
// if connection parameter negotation failed and phone sees it as connectivity errors.
|
||||||
|
|
||||||
|
// On the other hand Apple can randomly refuse any parameter negotiation and shutdown connection
|
||||||
|
// even if you meet Apple Developer Guidelines for BLE devices. Because f* you, that's why.
|
||||||
|
|
||||||
|
// While this API call sets preferred connection parameters (PPCP) - many phones ignore it (yeah) and it seems SoftDevice
|
||||||
|
// will try to renegotiate connection parameters based on those values after phone connection.
|
||||||
|
// So those are relatively safe values so Apple braindead firmware won't get angry and at least we may try
|
||||||
|
// to negotiate some longer connection interval to save battery.
|
||||||
|
|
||||||
|
// See https://github.com/meshtastic/firmware/pull/8858 for measurements. We are dealing with microamp savings anyway so not
|
||||||
|
// worth dying on a hill here.
|
||||||
|
|
||||||
|
Bluefruit.Periph.setConnSlaveLatency(0);
|
||||||
|
// 1.25 ms units - so min, max is 15, 100 ms range.
|
||||||
|
Bluefruit.Periph.setConnInterval(12, 80);
|
||||||
|
|
||||||
#ifndef BLE_DFU_SECURE
|
#ifndef BLE_DFU_SECURE
|
||||||
bledfu.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM);
|
bledfu.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM);
|
||||||
bledfu.begin(); // Install the DFU helper
|
bledfu.begin(); // Install the DFU helper
|
||||||
@@ -300,7 +332,7 @@ void NRF52Bluetooth::setup()
|
|||||||
void NRF52Bluetooth::resumeAdvertising()
|
void NRF52Bluetooth::resumeAdvertising()
|
||||||
{
|
{
|
||||||
Bluefruit.Advertising.restartOnDisconnect(true);
|
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||||
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
|
Bluefruit.Advertising.setInterval(32, 668); // in unit of 0.625 ms
|
||||||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||||
Bluefruit.Advertising.start(0);
|
Bluefruit.Advertising.start(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,25 @@
|
|||||||
//
|
//
|
||||||
// defaults for NRF52 architecture
|
// defaults for NRF52 architecture
|
||||||
//
|
//
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Internal Reference is +/-0.6V, with an adjustable gain of 1/6, 1/5, 1/4,
|
||||||
|
* 1/3, 1/2 or 1, meaning 3.6, 3.0, 2.4, 1.8, 1.2 or 0.6V for the ADC levels.
|
||||||
|
*
|
||||||
|
* External Reference is VDD/4, with an adjustable gain of 1, 2 or 4, meaning
|
||||||
|
* VDD/4, VDD/2 or VDD for the ADC levels.
|
||||||
|
*
|
||||||
|
* Default settings are internal reference with 1/6 gain (GND..3.6V ADC range)
|
||||||
|
* Some variants overwrite it.
|
||||||
|
*/
|
||||||
|
#ifndef AREF_VOLTAGE
|
||||||
|
#define AREF_VOLTAGE 3.6
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef BATTERY_SENSE_RESOLUTION_BITS
|
||||||
|
#define BATTERY_SENSE_RESOLUTION_BITS 10
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef HAS_BLUETOOTH
|
#ifndef HAS_BLUETOOTH
|
||||||
#define HAS_BLUETOOTH 1
|
#define HAS_BLUETOOTH 1
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
#define NRFX_WDT_ENABLED 1
|
#define NRFX_WDT_ENABLED 1
|
||||||
#define NRFX_WDT0_ENABLED 1
|
#define NRFX_WDT0_ENABLED 1
|
||||||
#define NRFX_WDT_CONFIG_NO_IRQ 1
|
#define NRFX_WDT_CONFIG_NO_IRQ 1
|
||||||
#include <nrfx_wdt.c>
|
#include "nrfx_power.h"
|
||||||
#include <nrfx_wdt.h>
|
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <ble_gap.h>
|
#include <ble_gap.h>
|
||||||
#include <memory.h>
|
#include <memory.h>
|
||||||
|
#include <nrfx_wdt.c>
|
||||||
|
#include <nrfx_wdt.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
// #include <Adafruit_USBD_Device.h>
|
// #include <Adafruit_USBD_Device.h>
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "meshUtils.h"
|
#include "meshUtils.h"
|
||||||
#include "power.h"
|
#include "power.h"
|
||||||
|
#include <power/PowerHAL.h>
|
||||||
|
|
||||||
#include <hal/nrf_lpcomp.h>
|
#include <hal/nrf_lpcomp.h>
|
||||||
|
|
||||||
@@ -30,6 +31,21 @@
|
|||||||
#include "BQ25713.h"
|
#include "BQ25713.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// WARNING! THRESHOLD + HYSTERESIS should be less than regulated VDD voltage - which depends on board
|
||||||
|
// and is 3.0 or 3.3V. Also VDD likes to read values like 2.9999 so make sure you account for that
|
||||||
|
// otherwise board will not boot at all. Before you modify this part - please triple read NRF52840 power design
|
||||||
|
// section in datasheet and you understand how REG0 and REG1 regulators work together.
|
||||||
|
#ifndef SAFE_VDD_VOLTAGE_THRESHOLD
|
||||||
|
#define SAFE_VDD_VOLTAGE_THRESHOLD 2.7
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// hysteresis value
|
||||||
|
#ifndef SAFE_VDD_VOLTAGE_THRESHOLD_HYST
|
||||||
|
#define SAFE_VDD_VOLTAGE_THRESHOLD_HYST 0.2
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint16_t getVDDVoltage();
|
||||||
|
|
||||||
// Weak empty variant initialization function.
|
// Weak empty variant initialization function.
|
||||||
// May be redefined by variant files.
|
// May be redefined by variant files.
|
||||||
void variant_shutdown() __attribute__((weak));
|
void variant_shutdown() __attribute__((weak));
|
||||||
@@ -38,12 +54,95 @@ void variant_shutdown() {}
|
|||||||
static nrfx_wdt_t nrfx_wdt = NRFX_WDT_INSTANCE(0);
|
static nrfx_wdt_t nrfx_wdt = NRFX_WDT_INSTANCE(0);
|
||||||
static nrfx_wdt_channel_id nrfx_wdt_channel_id_nrf52_main;
|
static nrfx_wdt_channel_id nrfx_wdt_channel_id_nrf52_main;
|
||||||
|
|
||||||
|
// This is a public global so that the debugger can set it to false automatically from our gdbinit
|
||||||
|
// @phaseloop comment: most part of codebase, including filesystem flash driver depend on softdevice
|
||||||
|
// methods so disabling it may actually crash thing. Proceed with caution.
|
||||||
|
|
||||||
|
bool useSoftDevice = true; // Set to false for easier debugging
|
||||||
|
|
||||||
static inline void debugger_break(void)
|
static inline void debugger_break(void)
|
||||||
{
|
{
|
||||||
__asm volatile("bkpt #0x01\n\t"
|
__asm volatile("bkpt #0x01\n\t"
|
||||||
"mov pc, lr\n\t");
|
"mov pc, lr\n\t");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PowerHAL NRF52 specific function implementations
|
||||||
|
bool powerHAL_isVBUSConnected()
|
||||||
|
{
|
||||||
|
return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool powerHAL_isPowerLevelSafe()
|
||||||
|
{
|
||||||
|
|
||||||
|
static bool powerLevelSafe = true;
|
||||||
|
|
||||||
|
uint16_t threshold = SAFE_VDD_VOLTAGE_THRESHOLD * 1000; // convert V to mV
|
||||||
|
uint16_t hysteresis = SAFE_VDD_VOLTAGE_THRESHOLD_HYST * 1000;
|
||||||
|
|
||||||
|
if (powerLevelSafe) {
|
||||||
|
if (getVDDVoltage() < threshold) {
|
||||||
|
powerLevelSafe = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// power level is only safe again when it raises above threshold + hysteresis
|
||||||
|
if (getVDDVoltage() >= (threshold + hysteresis)) {
|
||||||
|
powerLevelSafe = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return powerLevelSafe;
|
||||||
|
}
|
||||||
|
|
||||||
|
void powerHAL_platformInit()
|
||||||
|
{
|
||||||
|
|
||||||
|
// Enable POF power failure comparator. It will prevent writing to NVMC flash when supply voltage is too low.
|
||||||
|
// Set to some low value as last resort - powerHAL_isPowerLevelSafe uses different method and should manage proper node
|
||||||
|
// behaviour on its own.
|
||||||
|
|
||||||
|
// POFWARN is pretty useless for node power management because it triggers only once and clearing this event will not
|
||||||
|
// re-trigger it again until voltage rises to safe level and drops again. So we will use SAADC routed to VDD to read safely
|
||||||
|
// voltage.
|
||||||
|
|
||||||
|
// @phaseloop: I disable POFCON for now because it seems to be unreliable or buggy. Even when set at 2.0V it
|
||||||
|
// triggers below 2.8V and corrupts data when pairing bluetooth - because it prevents filesystem writes and
|
||||||
|
// adafruit BLE library triggers lfs_assert which reboots node and formats filesystem.
|
||||||
|
// I did experiments with bench power supply and no matter what is set to POFCON, it always triggers right below
|
||||||
|
// 2.8V. I compared raw registry values with datasheet.
|
||||||
|
|
||||||
|
NRF_POWER->POFCON =
|
||||||
|
((POWER_POFCON_THRESHOLD_V22 << POWER_POFCON_THRESHOLD_Pos) | (POWER_POFCON_POF_Enabled << POWER_POFCON_POF_Pos));
|
||||||
|
|
||||||
|
// remember to always match VBAT_AR_INTERNAL with AREF_VALUE in variant definition file
|
||||||
|
#ifdef VBAT_AR_INTERNAL
|
||||||
|
analogReference(VBAT_AR_INTERNAL);
|
||||||
|
#else
|
||||||
|
analogReference(AR_INTERNAL); // 3.6V
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// get VDD voltage (in millivolts)
|
||||||
|
uint16_t getVDDVoltage()
|
||||||
|
{
|
||||||
|
// we use the same values as regular battery read so there is no conflict on SAADC
|
||||||
|
analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS);
|
||||||
|
|
||||||
|
// VDD range on NRF52840 is 1.8-3.3V so we need to remap analog reference to 3.6V
|
||||||
|
// let's hope battery reading runs in same task and we don't have race condition
|
||||||
|
analogReference(AR_INTERNAL);
|
||||||
|
|
||||||
|
uint16_t vddADCRead = analogReadVDD();
|
||||||
|
float voltage = ((1000 * 3.6) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * vddADCRead;
|
||||||
|
|
||||||
|
// restore default battery reading reference
|
||||||
|
#ifdef VBAT_AR_INTERNAL
|
||||||
|
analogReference(VBAT_AR_INTERNAL);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return voltage;
|
||||||
|
}
|
||||||
|
|
||||||
bool loopCanSleep()
|
bool loopCanSleep()
|
||||||
{
|
{
|
||||||
// turn off sleep only while connected via USB
|
// turn off sleep only while connected via USB
|
||||||
@@ -72,22 +171,6 @@ void getMacAddr(uint8_t *dmac)
|
|||||||
dmac[0] = src[5] | 0xc0; // MSB high two bits get set elsewhere in the bluetooth stack
|
dmac[0] = src[5] | 0xc0; // MSB high two bits get set elsewhere in the bluetooth stack
|
||||||
}
|
}
|
||||||
|
|
||||||
static void initBrownout()
|
|
||||||
{
|
|
||||||
auto vccthresh = POWER_POFCON_THRESHOLD_V24;
|
|
||||||
|
|
||||||
auto err_code = sd_power_pof_enable(POWER_POFCON_POF_Enabled);
|
|
||||||
assert(err_code == NRF_SUCCESS);
|
|
||||||
|
|
||||||
err_code = sd_power_pof_threshold_set(vccthresh);
|
|
||||||
assert(err_code == NRF_SUCCESS);
|
|
||||||
|
|
||||||
// We don't bother with setting up brownout if soft device is disabled - because during production we always use softdevice
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a public global so that the debugger can set it to false automatically from our gdbinit
|
|
||||||
bool useSoftDevice = true; // Set to false for easier debugging
|
|
||||||
|
|
||||||
#if !MESHTASTIC_EXCLUDE_BLUETOOTH
|
#if !MESHTASTIC_EXCLUDE_BLUETOOTH
|
||||||
void setBluetoothEnable(bool enable)
|
void setBluetoothEnable(bool enable)
|
||||||
{
|
{
|
||||||
@@ -106,7 +189,6 @@ void setBluetoothEnable(bool enable)
|
|||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
nrf52Bluetooth = new NRF52Bluetooth();
|
nrf52Bluetooth = new NRF52Bluetooth();
|
||||||
nrf52Bluetooth->startDisabled();
|
nrf52Bluetooth->startDisabled();
|
||||||
initBrownout();
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -120,9 +202,6 @@ void setBluetoothEnable(bool enable)
|
|||||||
LOG_DEBUG("Init NRF52 Bluetooth");
|
LOG_DEBUG("Init NRF52 Bluetooth");
|
||||||
nrf52Bluetooth = new NRF52Bluetooth();
|
nrf52Bluetooth = new NRF52Bluetooth();
|
||||||
nrf52Bluetooth->setup();
|
nrf52Bluetooth->setup();
|
||||||
|
|
||||||
// We delay brownout init until after BLE because BLE starts soft device
|
|
||||||
initBrownout();
|
|
||||||
}
|
}
|
||||||
// Already setup, apparently
|
// Already setup, apparently
|
||||||
else
|
else
|
||||||
@@ -192,9 +271,24 @@ extern "C" void lfs_assert(const char *reason)
|
|||||||
delay(500); // Give the serial port a bit of time to output that last message.
|
delay(500); // Give the serial port a bit of time to output that last message.
|
||||||
// Try setting GPREGRET with the SoftDevice first. If that fails (perhaps because the SD hasn't been initialize yet) then set
|
// Try setting GPREGRET with the SoftDevice first. If that fails (perhaps because the SD hasn't been initialize yet) then set
|
||||||
// NRF_POWER->GPREGRET directly.
|
// NRF_POWER->GPREGRET directly.
|
||||||
if (!(sd_power_gpregret_clr(0, 0xFF) == NRF_SUCCESS && sd_power_gpregret_set(0, NRF52_MAGIC_LFS_IS_CORRUPT) == NRF_SUCCESS)) {
|
|
||||||
|
// TODO: this will/can crash CPU if bluetooth stack is not compiled in or bluetooth is not initialized
|
||||||
|
// (regardless if enabled or disabled) - as there is no live SoftDevice stack
|
||||||
|
// implement "safe" functions detecting softdevice stack state and using proper method to set registers
|
||||||
|
|
||||||
|
// do not set GPREGRET if POFWARN is triggered because it means lfs_assert reports flash undervoltage protection
|
||||||
|
// and not data corruption. Reboot is fine as boot procedure will wait until power level is safe again
|
||||||
|
|
||||||
|
if (!NRF_POWER->EVENTS_POFWARN) {
|
||||||
|
if (!(sd_power_gpregret_clr(0, 0xFF) == NRF_SUCCESS &&
|
||||||
|
sd_power_gpregret_set(0, NRF52_MAGIC_LFS_IS_CORRUPT) == NRF_SUCCESS)) {
|
||||||
NRF_POWER->GPREGRET = NRF52_MAGIC_LFS_IS_CORRUPT;
|
NRF_POWER->GPREGRET = NRF52_MAGIC_LFS_IS_CORRUPT;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this should not be done when SoftDevice is enabled as device will not boot back on soft reset
|
||||||
|
// as some data is retained in RAM which will prevent re-enabling bluetooth stack
|
||||||
|
// Google what Nordic has to say about NVIC_* + SoftDevice
|
||||||
NVIC_SystemReset();
|
NVIC_SystemReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,11 +61,12 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state)
|
|||||||
{
|
{
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'p':
|
case 'p':
|
||||||
if (sscanf(arg, "%d", &TCPPort) < 1)
|
if (sscanf(arg, "%d", &TCPPort) < 1) {
|
||||||
return ARGP_ERR_UNKNOWN;
|
return ARGP_ERR_UNKNOWN;
|
||||||
else
|
} else {
|
||||||
checkConfigPort = false;
|
checkConfigPort = false;
|
||||||
printf("Using config file %d\n", TCPPort);
|
printf("Using config file %d\n", TCPPort);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
configPath = arg;
|
configPath = arg;
|
||||||
@@ -179,6 +180,7 @@ void portduinoSetup()
|
|||||||
|
|
||||||
if (portduino_config.force_simradio == true) {
|
if (portduino_config.force_simradio == true) {
|
||||||
portduino_config.lora_module = use_simradio;
|
portduino_config.lora_module = use_simradio;
|
||||||
|
portduino_config.sfpp_enabled = false;
|
||||||
} else if (configPath != nullptr) {
|
} else if (configPath != nullptr) {
|
||||||
if (loadConfig(configPath)) {
|
if (loadConfig(configPath)) {
|
||||||
if (!yamlOnly)
|
if (!yamlOnly)
|
||||||
@@ -874,6 +876,28 @@ bool loadConfig(const char *configPath)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (yamlConfig["StoreAndForward"]) {
|
||||||
|
portduino_config.sfpp_stratum0 = (yamlConfig["StoreAndForward"]["Stratum0"]).as<bool>(false);
|
||||||
|
portduino_config.sfpp_enabled = (yamlConfig["StoreAndForward"]["Enabled"]).as<bool>(true);
|
||||||
|
portduino_config.sfpp_db_path = (yamlConfig["StoreAndForward"]["DBPath"]).as<std::string>("/var/lib/meshtasticd/");
|
||||||
|
portduino_config.sfpp_initial_sync = (yamlConfig["StoreAndForward"]["InitialSync"]).as<int>(10);
|
||||||
|
portduino_config.sfpp_hops = (yamlConfig["StoreAndForward"]["Hops"]).as<int>(3);
|
||||||
|
portduino_config.sfpp_announce_interval = (yamlConfig["StoreAndForward"]["AnnounceInterval"]).as<int>(5);
|
||||||
|
portduino_config.sfpp_max_chain = (yamlConfig["StoreAndForward"]["MaxChainLength"]).as<uint32_t>(1000);
|
||||||
|
portduino_config.sfpp_backlog_limit = (yamlConfig["StoreAndForward"]["BacklogLimit"]).as<uint32_t>(100);
|
||||||
|
portduino_config.sfpp_steal_port = (yamlConfig["StoreAndForward"]["StealPort"]).as<bool>(false);
|
||||||
|
}
|
||||||
|
if (yamlConfig["Routing"]) {
|
||||||
|
if (yamlConfig["Routing"]["WhitelistPorts"]) {
|
||||||
|
portduino_config.whitelist_ports = (yamlConfig["Routing"]["WhitelistPorts"]).as<std::vector<int>>();
|
||||||
|
if (portduino_config.whitelist_ports.size() > 0) {
|
||||||
|
portduino_config.whitelist_enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (yamlConfig["Routing"]["NoHopPorts"]) {
|
||||||
|
portduino_config.nohop_ports = (yamlConfig["Routing"]["NoHopPorts"]).as<std::vector<int>>();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (yamlConfig["General"]) {
|
if (yamlConfig["General"]) {
|
||||||
portduino_config.MaxNodes = (yamlConfig["General"]["MaxNodes"]).as<int>(200);
|
portduino_config.MaxNodes = (yamlConfig["General"]["MaxNodes"]).as<int>(200);
|
||||||
portduino_config.maxtophone = (yamlConfig["General"]["MaxMessageQueue"]).as<int>(100);
|
portduino_config.maxtophone = (yamlConfig["General"]["MaxMessageQueue"]).as<int>(100);
|
||||||
@@ -887,9 +911,7 @@ bool loadConfig(const char *configPath)
|
|||||||
}
|
}
|
||||||
if (checkConfigPort) {
|
if (checkConfigPort) {
|
||||||
portduino_config.api_port = (yamlConfig["General"]["APIPort"]).as<int>(-1);
|
portduino_config.api_port = (yamlConfig["General"]["APIPort"]).as<int>(-1);
|
||||||
if (portduino_config.api_port != -1 &&
|
if (portduino_config.api_port != -1 && portduino_config.api_port > 1023 && portduino_config.api_port < 65536) {
|
||||||
portduino_config.api_port > 1023 &&
|
|
||||||
portduino_config.api_port < 65536) {
|
|
||||||
TCPPort = (portduino_config.api_port);
|
TCPPort = (portduino_config.api_port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,6 +173,26 @@ extern struct portduino_config_struct {
|
|||||||
int configDisplayMode = 0;
|
int configDisplayMode = 0;
|
||||||
bool has_configDisplayMode = false;
|
bool has_configDisplayMode = false;
|
||||||
|
|
||||||
|
// Store and Forward++
|
||||||
|
std::string sfpp_db_path = "/var/lib/meshtasticd/";
|
||||||
|
bool sfpp_stratum0 = false;
|
||||||
|
bool sfpp_enabled = true;
|
||||||
|
bool sfpp_steal_port = false;
|
||||||
|
int sfpp_initial_sync = 10;
|
||||||
|
int sfpp_hops = 3;
|
||||||
|
int sfpp_announce_interval = 5; // minutes
|
||||||
|
uint32_t sfpp_max_chain = 1000;
|
||||||
|
uint32_t sfpp_backlog_limit = 100;
|
||||||
|
// allowed root hashes
|
||||||
|
// upstream node
|
||||||
|
// Are we allowing unknown channel hashes? Does this even make sense?
|
||||||
|
// Allow DMs
|
||||||
|
|
||||||
|
// Routing
|
||||||
|
bool whitelist_enabled = false;
|
||||||
|
std::vector<int> whitelist_ports = {};
|
||||||
|
std::vector<int> nohop_ports = {};
|
||||||
|
|
||||||
// General
|
// General
|
||||||
std::string mac_address = "";
|
std::string mac_address = "";
|
||||||
bool mac_address_explicit = false;
|
bool mac_address_explicit = false;
|
||||||
@@ -518,6 +538,29 @@ extern struct portduino_config_struct {
|
|||||||
out << YAML::EndMap; // Config
|
out << YAML::EndMap; // Config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StoreAndForward
|
||||||
|
if (sfpp_enabled) {
|
||||||
|
out << YAML::Key << "StoreAndForward" << YAML::Value << YAML::BeginMap;
|
||||||
|
out << YAML::Key << "Enabled" << YAML::Value << sfpp_enabled;
|
||||||
|
out << YAML::Key << "DBPath" << YAML::Value << sfpp_db_path;
|
||||||
|
out << YAML::Key << "Stratum0" << YAML::Value << sfpp_stratum0;
|
||||||
|
out << YAML::Key << "InitialSync" << YAML::Value << sfpp_initial_sync;
|
||||||
|
out << YAML::Key << "Hops" << YAML::Value << sfpp_hops;
|
||||||
|
out << YAML::Key << "AnnounceInterval" << YAML::Value << sfpp_announce_interval;
|
||||||
|
out << YAML::Key << "BacklogLimit" << YAML::Value << sfpp_backlog_limit;
|
||||||
|
out << YAML::Key << "MaxChainLength" << YAML::Value << sfpp_max_chain;
|
||||||
|
out << YAML::Key << "StealPort" << YAML::Value << sfpp_steal_port;
|
||||||
|
out << YAML::EndMap; // StoreAndForward
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routing
|
||||||
|
if (whitelist_enabled || nohop_ports.size() > 0) {
|
||||||
|
out << YAML::Key << "Routing" << YAML::Value << YAML::BeginMap;
|
||||||
|
out << YAML::Key << "WhitelistPorts" << YAML::Value << whitelist_ports;
|
||||||
|
out << YAML::Key << "NoHopPorts" << YAML::Value << nohop_ports;
|
||||||
|
out << YAML::EndMap; // Routing
|
||||||
|
}
|
||||||
|
|
||||||
// General
|
// General
|
||||||
out << YAML::Key << "General" << YAML::Value << YAML::BeginMap;
|
out << YAML::Key << "General" << YAML::Value << YAML::BeginMap;
|
||||||
if (config_directory != "")
|
if (config_directory != "")
|
||||||
|
|||||||
19
src/power/PowerHAL.cpp
Normal file
19
src/power/PowerHAL.cpp
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
#include "PowerHAL.h"
|
||||||
|
|
||||||
|
void powerHAL_init()
|
||||||
|
{
|
||||||
|
return powerHAL_platformInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((weak, noinline)) void powerHAL_platformInit() {}
|
||||||
|
|
||||||
|
__attribute__((weak, noinline)) bool powerHAL_isPowerLevelSafe()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((weak, noinline)) bool powerHAL_isVBUSConnected()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
26
src/power/PowerHAL.h
Normal file
26
src/power/PowerHAL.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Power Hardware Abstraction Layer. Set of API calls to offload power management, measurements, reboots, etc
|
||||||
|
to the platform and variant code to avoid #ifdef spaghetti hell and limitless device-based edge cases
|
||||||
|
in the main firmware code
|
||||||
|
|
||||||
|
Functions declared here (with exception of powerHAL_init) should be defined in platform specific codebase.
|
||||||
|
Default function body does usually nothing.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Initialize HAL layer. Call it as early as possible during device boot
|
||||||
|
// do not overwrite it as it's not declared with "weak" attribute.
|
||||||
|
void powerHAL_init();
|
||||||
|
|
||||||
|
// platform specific init code if needed to be run early on boot
|
||||||
|
void powerHAL_platformInit();
|
||||||
|
|
||||||
|
// Return true if current battery level is safe for device operation (for example flash writes).
|
||||||
|
// This should be reported by power failure comparator (NRF52) or similar circuits on other platforms.
|
||||||
|
// Do not use battery ADC as improper ADC configuration may prevent device from booting.
|
||||||
|
bool powerHAL_isPowerLevelSafe();
|
||||||
|
|
||||||
|
// return if USB voltage is connected
|
||||||
|
bool powerHAL_isVBUSConnected();
|
||||||
@@ -6,6 +6,8 @@ build_flags =
|
|||||||
${esp32_base.build_flags}
|
${esp32_base.build_flags}
|
||||||
-D CHATTER_2
|
-D CHATTER_2
|
||||||
-I variants/esp32/chatter2
|
-I variants/esp32/chatter2
|
||||||
|
-DMESHTASTIC_EXCLUDE_WEBSERVER=1
|
||||||
|
-DMESHTASTIC_EXCLUDE_PAXCOUNTER=1
|
||||||
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${esp32_base.lib_deps}
|
${esp32_base.lib_deps}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ build_flags =
|
|||||||
${esp32_base.build_flags}
|
${esp32_base.build_flags}
|
||||||
-I variants/esp32/m5stack_core
|
-I variants/esp32/m5stack_core
|
||||||
-DM5STACK
|
-DM5STACK
|
||||||
|
-DMESHTASTIC_EXCLUDE_WEBSERVER=1
|
||||||
|
-DMESHTASTIC_EXCLUDE_PAXCOUNTER=1
|
||||||
-DUSER_SETUP_LOADED
|
-DUSER_SETUP_LOADED
|
||||||
-DTFT_SDA_READ
|
-DTFT_SDA_READ
|
||||||
-DTFT_DRIVER=0x9341
|
-DTFT_DRIVER=0x9341
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
// DIO6 -> RFSW1_V2
|
// DIO6 -> RFSW1_V2
|
||||||
// DIO7 -> not connected on E80 module - note that GNSS and Wifi scanning are not possible.
|
// DIO7 -> not connected on E80 module - note that GNSS and Wifi scanning are not possible.
|
||||||
|
|
||||||
static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_LR11X0_DIO7, RADIOLIB_NC, RADIOLIB_NC};
|
static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_LR11X0_DIO7, RADIOLIB_NC,
|
||||||
|
RADIOLIB_NC};
|
||||||
|
|
||||||
static const Module::RfSwitchMode_t rfswitch_table[] = {
|
static const Module::RfSwitchMode_t rfswitch_table[] = {
|
||||||
// mode DIO5 DIO6 DIO7
|
// mode DIO5 DIO6 DIO7
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ lib_deps =
|
|||||||
# renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028
|
# renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028
|
||||||
melopero/Melopero RV3028@1.2.0
|
melopero/Melopero RV3028@1.2.0
|
||||||
|
|
||||||
build_src_filter = ${portduino_base.build_src_filter}
|
build_src_filter = ${portduino_base.build_src_filter} +<modules/Native/>
|
||||||
|
|
||||||
[env:native]
|
[env:native]
|
||||||
extends = native_base
|
extends = native_base
|
||||||
@@ -20,6 +20,7 @@ build_flags = ${native_base.build_flags}
|
|||||||
!pkg-config --libs openssl --silence-errors || :
|
!pkg-config --libs openssl --silence-errors || :
|
||||||
!pkg-config --cflags --libs sdl2 --silence-errors || :
|
!pkg-config --cflags --libs sdl2 --silence-errors || :
|
||||||
!pkg-config --cflags --libs libbsd-overlay --silence-errors || :
|
!pkg-config --cflags --libs libbsd-overlay --silence-errors || :
|
||||||
|
!pkg-config --cflags --libs sqlite3 --silence-errors || :
|
||||||
|
|
||||||
[env:native-tft]
|
[env:native-tft]
|
||||||
extends = native_base
|
extends = native_base
|
||||||
@@ -46,6 +47,7 @@ build_flags = ${native_base.build_flags} -Os -lX11 -linput -lxkbcommon -ffunctio
|
|||||||
!pkg-config --libs openssl --silence-errors || :
|
!pkg-config --libs openssl --silence-errors || :
|
||||||
!pkg-config --cflags --libs sdl2 --silence-errors || :
|
!pkg-config --cflags --libs sdl2 --silence-errors || :
|
||||||
!pkg-config --cflags --libs libbsd-overlay --silence-errors || :
|
!pkg-config --cflags --libs libbsd-overlay --silence-errors || :
|
||||||
|
!pkg-config --cflags --libs sqlite3 --silence-errors || :
|
||||||
build_src_filter =
|
build_src_filter =
|
||||||
${native_base.build_src_filter}
|
${native_base.build_src_filter}
|
||||||
|
|
||||||
@@ -75,6 +77,7 @@ build_flags = ${native_base.build_flags} -Os -ffunction-sections -fdata-sections
|
|||||||
!pkg-config --libs libulfius --silence-errors || :
|
!pkg-config --libs libulfius --silence-errors || :
|
||||||
!pkg-config --libs openssl --silence-errors || :
|
!pkg-config --libs openssl --silence-errors || :
|
||||||
!pkg-config --cflags --libs libbsd-overlay --silence-errors || :
|
!pkg-config --cflags --libs libbsd-overlay --silence-errors || :
|
||||||
|
!pkg-config --cflags --libs sqlite3 --silence-errors || :
|
||||||
build_src_filter =
|
build_src_filter =
|
||||||
${native_base.build_src_filter}
|
${native_base.build_src_filter}
|
||||||
|
|
||||||
@@ -108,6 +111,7 @@ build_flags = ${native_base.build_flags} -O0 -fsanitize=address -lX11 -linput -l
|
|||||||
!pkg-config --libs libulfius --silence-errors || :
|
!pkg-config --libs libulfius --silence-errors || :
|
||||||
!pkg-config --libs openssl --silence-errors || :
|
!pkg-config --libs openssl --silence-errors || :
|
||||||
!pkg-config --cflags --libs libbsd-overlay --silence-errors || :
|
!pkg-config --cflags --libs libbsd-overlay --silence-errors || :
|
||||||
|
!pkg-config --cflags --libs sqlite3 --silence-errors || :
|
||||||
build_src_filter = ${env:native-tft.build_src_filter}
|
build_src_filter = ${env:native-tft.build_src_filter}
|
||||||
|
|
||||||
[env:coverage]
|
[env:coverage]
|
||||||
|
|||||||
@@ -67,4 +67,8 @@ void variant_shutdown()
|
|||||||
nrf_gpio_cfg_input(PIN_BUTTON1, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input
|
nrf_gpio_cfg_input(PIN_BUTTON1, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input
|
||||||
nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW;
|
nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW;
|
||||||
nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense1);
|
nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense1);
|
||||||
|
|
||||||
|
nrf_gpio_cfg_input(EXT_CHRG_DETECT, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input
|
||||||
|
nrf_gpio_pin_sense_t sense2 = NRF_GPIO_PIN_SENSE_LOW;
|
||||||
|
nrf_gpio_cfg_sense_set(EXT_CHRG_DETECT, sense2);
|
||||||
}
|
}
|
||||||
|
|||||||
9
variants/nrf52840/dls_Minimesh_Lite/platformio.ini
Normal file
9
variants/nrf52840/dls_Minimesh_Lite/platformio.ini
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[env:minimesh_lite]
|
||||||
|
extends = nrf52840_base
|
||||||
|
board = minimesh_lite
|
||||||
|
board_level = extra
|
||||||
|
build_flags = ${nrf52840_base.build_flags}
|
||||||
|
-Ivariants/nrf52840/dls_Minimesh_Lite
|
||||||
|
-DPRIVATE_HW
|
||||||
|
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/dls_Minimesh_Lite>
|
||||||
|
debug_tool = jlink
|
||||||
38
variants/nrf52840/dls_Minimesh_Lite/variant.cpp
Normal file
38
variants/nrf52840/dls_Minimesh_Lite/variant.cpp
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2014-2015 Arduino LLC. All right reserved.
|
||||||
|
Copyright (c) 2016 Sandeep Mistry All right reserved.
|
||||||
|
Copyright (c) 2018, Adafruit Industries (adafruit.com)
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
See the GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "variant.h"
|
||||||
|
#include "nrf.h"
|
||||||
|
#include "wiring_constants.h"
|
||||||
|
#include "wiring_digital.h"
|
||||||
|
|
||||||
|
const uint32_t g_ADigitalPinMap[] = {
|
||||||
|
// P0
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
||||||
|
|
||||||
|
// P1
|
||||||
|
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||||
|
|
||||||
|
void initVariant()
|
||||||
|
{
|
||||||
|
// 3V3 Power Rail
|
||||||
|
pinMode(PIN_3V3_EN, OUTPUT);
|
||||||
|
digitalWrite(PIN_3V3_EN, HIGH);
|
||||||
|
}
|
||||||
104
variants/nrf52840/dls_Minimesh_Lite/variant.h
Normal file
104
variants/nrf52840/dls_Minimesh_Lite/variant.h
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
#ifndef _VARIANT_MINIMESH_LITE_
|
||||||
|
#define _VARIANT_MINIMESH_LITE_
|
||||||
|
|
||||||
|
#define VARIANT_MCK (64000000ul)
|
||||||
|
#define USE_LFRC
|
||||||
|
|
||||||
|
#include "WVariant.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MINIMESH_LITE
|
||||||
|
|
||||||
|
// Number of pins defined in PinDescription array
|
||||||
|
#define PINS_COUNT (48)
|
||||||
|
#define NUM_DIGITAL_PINS (48)
|
||||||
|
#define NUM_ANALOG_INPUTS (1)
|
||||||
|
#define NUM_ANALOG_OUTPUTS (0)
|
||||||
|
|
||||||
|
#define PIN_3V3_EN (0 + 13) // P0.13
|
||||||
|
|
||||||
|
// Analog pins
|
||||||
|
#define BATTERY_PIN (0 + 31) // P0.31 Battery ADC
|
||||||
|
#define ADC_CHANNEL ADC1_GPIO4_CHANNEL
|
||||||
|
#define ADC_RESOLUTION 14
|
||||||
|
#define BATTERY_SENSE_RESOLUTION_BITS 12
|
||||||
|
#define BATTERY_SENSE_RESOLUTION 4096.0
|
||||||
|
#define VBAT_MV_PER_LSB (0.73242188F)
|
||||||
|
#define VBAT_DIVIDER (0.6F)
|
||||||
|
#define VBAT_DIVIDER_COMP (1.73)
|
||||||
|
#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB)
|
||||||
|
#undef AREF_VOLTAGE
|
||||||
|
#define AREF_VOLTAGE 3.0
|
||||||
|
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
|
||||||
|
#define ADC_MULTIPLIER VBAT_DIVIDER_COMP
|
||||||
|
#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x)
|
||||||
|
|
||||||
|
// WIRE IC AND IIC PINS
|
||||||
|
#define WIRE_INTERFACES_COUNT 1
|
||||||
|
|
||||||
|
#define PIN_WIRE_SDA (32 + 4)
|
||||||
|
#define PIN_WIRE_SCL (0 + 11)
|
||||||
|
|
||||||
|
// LED
|
||||||
|
#define PIN_LED1 (0 + 15)
|
||||||
|
#define LED_BUILTIN PIN_LED1
|
||||||
|
// Actually red
|
||||||
|
#define LED_BLUE PIN_LED1
|
||||||
|
#define LED_STATE_ON 1
|
||||||
|
|
||||||
|
// Button
|
||||||
|
#define BUTTON_PIN (32 + 0)
|
||||||
|
|
||||||
|
// GPS
|
||||||
|
#define GPS_TX_PIN (0 + 20)
|
||||||
|
#define GPS_RX_PIN (0 + 22)
|
||||||
|
|
||||||
|
#define PIN_GPS_EN (0 + 24)
|
||||||
|
#define GPS_UBLOX
|
||||||
|
// define GPS_DEBUG
|
||||||
|
|
||||||
|
// UART interfaces
|
||||||
|
#define PIN_SERIAL1_TX GPS_TX_PIN
|
||||||
|
#define PIN_SERIAL1_RX GPS_RX_PIN
|
||||||
|
|
||||||
|
#define PIN_SERIAL2_RX (0 + 6)
|
||||||
|
#define PIN_SERIAL2_TX (0 + 8)
|
||||||
|
|
||||||
|
// Serial interfaces
|
||||||
|
#define SPI_INTERFACES_COUNT 1
|
||||||
|
|
||||||
|
#define PIN_SPI_MISO (0 + 2)
|
||||||
|
#define PIN_SPI_MOSI (32 + 15)
|
||||||
|
#define PIN_SPI_SCK (32 + 11)
|
||||||
|
|
||||||
|
#define LORA_MISO PIN_SPI_MISO
|
||||||
|
#define LORA_MOSI PIN_SPI_MOSI
|
||||||
|
#define LORA_SCK PIN_SPI_SCK
|
||||||
|
#define LORA_CS (32 + 13)
|
||||||
|
|
||||||
|
// LORA MODULES
|
||||||
|
#define USE_LLCC68
|
||||||
|
#define USE_SX1262
|
||||||
|
#define USE_SX1268
|
||||||
|
|
||||||
|
// SX126X CONFIG
|
||||||
|
#define SX126X_CS (32 + 13)
|
||||||
|
#define SX126X_DIO1 (0 + 10)
|
||||||
|
#define SX126X_DIO2_AS_RF_SWITCH
|
||||||
|
|
||||||
|
#define SX126X_BUSY (0 + 29)
|
||||||
|
#define SX126X_RESET (0 + 9)
|
||||||
|
#define SX126X_RXEN (0 + 17)
|
||||||
|
#define SX126X_TXEN RADIOLIB_NC
|
||||||
|
|
||||||
|
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
|
||||||
|
#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // _VARIANT_MINIMESH_LITE_
|
||||||
@@ -150,6 +150,14 @@ No longer populated on PCB
|
|||||||
#define PIN_SPI1_MOSI ST7789_SDA
|
#define PIN_SPI1_MOSI ST7789_SDA
|
||||||
#define PIN_SPI1_SCK ST7789_SCK
|
#define PIN_SPI1_SCK ST7789_SCK
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Bluetooth
|
||||||
|
*/
|
||||||
|
|
||||||
|
// The bluetooth transmit power on the nRF52840 is adjustable from -20dB to +8dB in steps of 4dB
|
||||||
|
// so NRF52_BLE_TX_POWER can be set to -20, -16, -12, -8, -4, 0 (default), 4, and 8.
|
||||||
|
//#define NRF52_BLE_TX_POWER 8
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* GPS pins
|
* GPS pins
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
[VERSION]
|
[VERSION]
|
||||||
major = 2
|
major = 2
|
||||||
minor = 7
|
minor = 7
|
||||||
build = 18
|
build = 19
|
||||||
|
|||||||
Reference in New Issue
Block a user