Compare commits
49
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d237f8730 | ||
|
|
ca0679e999 | ||
|
|
bb8e7140a9 | ||
|
|
c244effb52 | ||
|
|
b475bde730 | ||
|
|
ae67293bcc | ||
|
|
3a6766b0fd | ||
|
|
9f81ffc692 | ||
|
|
4e1002029c | ||
|
|
8dcd4975d5 | ||
|
|
e0b19a1f60 | ||
|
|
93bd0f0869 | ||
|
|
d4febe7867 | ||
|
|
674beb0e47 | ||
|
|
ea3f7acb4d | ||
|
|
3281ea2924 | ||
|
|
517d3ed643 | ||
|
|
eac4f7201e | ||
|
|
7f02cbe80b | ||
|
|
1a7b3614a8 | ||
|
|
6c20a1062f | ||
|
|
e608503aa0 | ||
|
|
fa9f3bd935 | ||
|
|
181ddb2268 | ||
|
|
8fadd31159 | ||
|
|
03a13aed30 | ||
|
|
fed8d36d03 | ||
|
|
b948369d71 | ||
|
|
34db93150a | ||
|
|
5a45d54afe | ||
|
|
aa400da945 | ||
|
|
a44137662e | ||
|
|
528bf3f61e | ||
|
|
0243ec41e8 | ||
|
|
756268e2ee | ||
|
|
68360157ec | ||
|
|
0702065dd2 | ||
|
|
c5d8ada27d | ||
|
|
9c2ac5aa1c | ||
|
|
ecd0cfc1c1 | ||
|
|
577433ce47 | ||
|
|
6a53f6de27 | ||
|
|
fae6dd83fd | ||
|
|
8525b4e980 | ||
|
|
d8552e3344 | ||
|
|
4c393348df | ||
|
|
166f804da1 | ||
|
|
0ce1a55017 | ||
|
|
00c14cd88c |
No files matched your search
@@ -0,0 +1 @@
|
||||
github: meshcore-dev
|
||||
@@ -117,4 +117,4 @@ There are a number of fairly major features in the pipeline, with no particular
|
||||
|
||||
- Report bugs and request features on the [GitHub Issues](https://github.com/ripplebiz/MeshCore/issues) page.
|
||||
- Find additional guides and components on [my site](https://buymeacoffee.com/ripplebiz).
|
||||
- Join [MeshCore Discord](https://discord.gg/BMwCtwHj5V) to chat with the developers and get help from the community.
|
||||
- Join [MeshCore Discord](https://meshcore.gg) to chat with the developers and get help from the community.
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env bash
|
||||
# ./build-iotthinks.sh
|
||||
export FIRMWARE_VERSION="PowerSaving15"
|
||||
|
||||
############# Repeaters #############
|
||||
# Commonly-used boards
|
||||
## ESP32 - 12 boards
|
||||
./build.sh build-firmware \
|
||||
Heltec_v3_repeater \
|
||||
Heltec_WSL3_repeater \
|
||||
heltec_v4_repeater \
|
||||
Station_G2_repeater \
|
||||
T_Beam_S3_Supreme_SX1262_repeater \
|
||||
Tbeam_SX1262_repeater \
|
||||
LilyGo_T3S3_sx1262_repeater \
|
||||
Xiao_S3_WIO_repeater \
|
||||
Xiao_C3_repeater \
|
||||
Xiao_C6_repeater_ \
|
||||
Heltec_E290_repeater \
|
||||
Heltec_Wireless_Tracker_repeater
|
||||
|
||||
## NRF52 - 13 boards
|
||||
./build.sh build-firmware \
|
||||
RAK_4631_repeater \
|
||||
Heltec_t114_repeater \
|
||||
Xiao_nrf52_repeater \
|
||||
Heltec_mesh_solar_repeater \
|
||||
ProMicro_repeater \
|
||||
SenseCap_Solar_repeater \
|
||||
t1000e_repeater \
|
||||
LilyGo_T-Echo_repeater \
|
||||
WioTrackerL1_repeater \
|
||||
RAK_3401_repeater \
|
||||
RAK_WisMesh_Tag_repeater \
|
||||
GAT562_30S_Mesh_Kit_repeater \
|
||||
GAT562_Mesh_Tracker_Pro_repeater
|
||||
|
||||
## ESP32, SX1276 - 3 boards
|
||||
./build.sh build-firmware \
|
||||
Heltec_v2_repeater \
|
||||
LilyGo_TLora_V2_1_1_6_repeater \
|
||||
Tbeam_SX1276_repeater
|
||||
|
||||
## Ikoka - 3 boards
|
||||
./build.sh build-firmware \
|
||||
ikoka_nano_nrf_22dbm_repeater \
|
||||
ikoka_nano_nrf_30dbm_repeater \
|
||||
ikoka_nano_nrf_33dbm_repeater
|
||||
|
||||
############# Room Server #############
|
||||
# ESP32
|
||||
./build.sh build-firmware \
|
||||
Heltec_v3_room_server \
|
||||
heltec_v4_room_server
|
||||
|
||||
# NRF52
|
||||
./build.sh build-firmware \
|
||||
RAK_4631_room_server \
|
||||
Heltec_t114_room_server \
|
||||
Xiao_nrf52_room_server \
|
||||
t1000e_room_server \
|
||||
WioTrackerL1_room_server \
|
||||
RAK_3401_room_server
|
||||
|
||||
############# Companions BLE #############
|
||||
# ESP32
|
||||
./build.sh build-firmware \
|
||||
Heltec_v3_companion_radio_ble_ps \
|
||||
heltec_v4_companion_radio_ble_ps \
|
||||
heltec_v4_companion_radio_ble_ps_femoff \
|
||||
Xiao_S3_WIO_companion_radio_ble \
|
||||
Heltec_Wireless_Paper_companion_radio_ble
|
||||
|
||||
# NRF52
|
||||
./build.sh build-firmware \
|
||||
RAK_4631_companion_radio_ble \
|
||||
Heltec_t114_companion_radio_ble \
|
||||
Xiao_nrf52_companion_radio_ble \
|
||||
t1000e_companion_radio_ble \
|
||||
LilyGo_T-Echo_companion_radio_ble \
|
||||
WioTrackerL1_companion_radio_ble \
|
||||
RAK_3401_companion_radio_ble \
|
||||
RAK_WisMesh_Tag_companion_radio_ble
|
||||
|
||||
############# Companions USB #############
|
||||
./build.sh build-firmware \
|
||||
Heltec_v3_companion_radio_usb
|
||||
|
||||
############# Companions BLE PS #############
|
||||
./build.sh build-firmware \
|
||||
Heltec_v3_companion_radio_ble_ps \
|
||||
heltec_v4_companion_radio_ble_ps \
|
||||
heltec_v4_3_companion_radio_ble_ps_femoff \
|
||||
Xiao_C3_companion_radio_ble_ps \
|
||||
Xiao_S3_companion_radio_ble_ps \
|
||||
Xiao_S3_WIO_companion_radio_ble_ps \
|
||||
Heltec_v2_companion_radio_ble_ps
|
||||
@@ -1,9 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ALL_PIO_ENVS=()
|
||||
PIO_CONFIG_JSON=""
|
||||
MENU_CHOICE=""
|
||||
SELECTED_TARGET=""
|
||||
|
||||
ENV_VARIANT_SUFFIX_PATTERN='companion_radio_serial|companion_radio_wifi|companion_radio_usb|comp_radio_usb|companion_usb|companion_radio_ble|companion_ble|repeater_bridge_rs232_serial1|repeater_bridge_rs232_serial2|repeater_bridge_rs232|repeater_bridge_espnow|terminal_chat|room_server|room_svr|kiss_modem|sensor|repeatr|repeater'
|
||||
BOARD_MODIFIER_WITHOUT_DISPLAY="_without_display"
|
||||
BOARD_MODIFIER_LOGGING="_logging"
|
||||
BOARD_MODIFIER_TFT="_tft"
|
||||
BOARD_MODIFIER_EINK="_eink"
|
||||
BOARD_MODIFIER_EINK_SUFFIX="Eink"
|
||||
BOARD_LABEL_WITHOUT_DISPLAY="without_display"
|
||||
BOARD_LABEL_LOGGING="logging"
|
||||
BOARD_LABEL_TFT="tft"
|
||||
BOARD_LABEL_EINK="eink"
|
||||
DEFAULT_VARIANT_LABEL="default"
|
||||
TAG_PREFIX_ROOM_SERVER="room-server"
|
||||
TAG_PREFIX_COMPANION="companion"
|
||||
TAG_PREFIX_REPEATER="repeater"
|
||||
BULK_BUILD_SUFFIX_REPEATER="_repeater"
|
||||
BULK_BUILD_SUFFIX_COMPANION_USB="_companion_radio_usb"
|
||||
BULK_BUILD_SUFFIX_COMPANION_BLE="_companion_radio_ble"
|
||||
BULK_BUILD_SUFFIX_ROOM_SERVER="_room_server"
|
||||
SUPPORTED_PLATFORM_PATTERN='ESP32_PLATFORM|NRF52_PLATFORM|STM32_PLATFORM|RP2040_PLATFORM'
|
||||
OUTPUT_DIR="out"
|
||||
FALLBACK_VERSION_PREFIX="dev"
|
||||
FALLBACK_VERSION_DATE_FORMAT='+%Y-%m-%d-%H-%M'
|
||||
|
||||
# External programs invoked by this script:
|
||||
# bash, cat, cp, date, git, grep, head, mkdir, pio, python3, rm, sed, sort
|
||||
# Keep this list in sync when adding or removing non-builtin command usage.
|
||||
|
||||
global_usage() {
|
||||
cat - <<EOF
|
||||
Usage:
|
||||
sh build.sh <command> [target]
|
||||
bash build.sh <command> [target]
|
||||
|
||||
Commands:
|
||||
help|usage|-h|--help: Shows this message.
|
||||
@@ -17,21 +49,26 @@ Commands:
|
||||
|
||||
Examples:
|
||||
Build firmware for the "RAK_4631_repeater" device target
|
||||
$ sh build.sh build-firmware RAK_4631_repeater
|
||||
$ bash build.sh build-firmware RAK_4631_repeater
|
||||
|
||||
Run without arguments to choose a target from an interactive menu
|
||||
$ bash build.sh
|
||||
|
||||
Build all firmwares for device targets containing the string "RAK_4631"
|
||||
$ sh build.sh build-matching-firmwares <build-match-spec>
|
||||
$ bash build.sh build-matching-firmwares <build-match-spec>
|
||||
|
||||
Build all companion firmwares
|
||||
$ sh build.sh build-companion-firmwares
|
||||
$ bash build.sh build-companion-firmwares
|
||||
|
||||
Build all repeater firmwares
|
||||
$ sh build.sh build-repeater-firmwares
|
||||
$ bash build.sh build-repeater-firmwares
|
||||
|
||||
Build all chat room server firmwares
|
||||
$ sh build.sh build-room-server-firmwares
|
||||
$ bash build.sh build-room-server-firmwares
|
||||
|
||||
Environment Variables:
|
||||
FIRMWARE_VERSION=vX.Y.Z: Firmware version to embed in the build output.
|
||||
If not set, build.sh derives a default from the latest matching git tag and appends "-dev".
|
||||
DISABLE_DEBUG=1: Disables all debug logging flags (MESH_DEBUG, MESH_PACKET_LOGGING, etc.)
|
||||
If not set, debug flags from variant platformio.ini files are used.
|
||||
|
||||
@@ -39,61 +76,427 @@ Examples:
|
||||
Build without debug logging:
|
||||
$ export FIRMWARE_VERSION=v1.0.0
|
||||
$ export DISABLE_DEBUG=1
|
||||
$ sh build.sh build-firmware RAK_4631_repeater
|
||||
$ bash build.sh build-firmware RAK_4631_repeater
|
||||
|
||||
Build with debug logging (default, uses flags from variant files):
|
||||
$ export FIRMWARE_VERSION=v1.0.0
|
||||
$ sh build.sh build-firmware RAK_4631_repeater
|
||||
$ bash build.sh build-firmware RAK_4631_repeater
|
||||
|
||||
Build with the derived default version from git tags:
|
||||
$ unset FIRMWARE_VERSION
|
||||
$ bash build.sh
|
||||
EOF
|
||||
}
|
||||
|
||||
# get a list of pio env names that start with "env:"
|
||||
get_pio_envs() {
|
||||
pio project config | grep 'env:' | sed 's/env://'
|
||||
init_project_context() {
|
||||
if [ ${#ALL_PIO_ENVS[@]} -eq 0 ]; then
|
||||
mapfile -t ALL_PIO_ENVS < <(pio project config | grep 'env:' | sed 's/env://')
|
||||
fi
|
||||
|
||||
if [ -z "$PIO_CONFIG_JSON" ]; then
|
||||
PIO_CONFIG_JSON=$(pio project config --json-output)
|
||||
fi
|
||||
}
|
||||
|
||||
# Catch cries for help before doing anything else.
|
||||
case $1 in
|
||||
help|usage|-h|--help)
|
||||
global_usage
|
||||
exit 1
|
||||
;;
|
||||
list|-l)
|
||||
get_pio_envs
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
get_pio_envs() {
|
||||
if [ ${#ALL_PIO_ENVS[@]} -gt 0 ]; then
|
||||
printf '%s\n' "${ALL_PIO_ENVS[@]}"
|
||||
else
|
||||
pio project config | grep 'env:' | sed 's/env://'
|
||||
fi
|
||||
}
|
||||
|
||||
# cache project config json for use in get_platform_for_env()
|
||||
PIO_CONFIG_JSON=$(pio project config --json-output)
|
||||
canonicalize_variant_suffix() {
|
||||
local variant_suffix=$1
|
||||
|
||||
# $1 should be the string to find (case insensitive)
|
||||
get_pio_envs_containing_string() {
|
||||
shopt -s nocasematch
|
||||
envs=($(get_pio_envs))
|
||||
for env in "${envs[@]}"; do
|
||||
if [[ "$env" == *${1}* ]]; then
|
||||
echo $env
|
||||
fi
|
||||
case "${variant_suffix,,}" in
|
||||
comp_radio_usb|companion_usb|companion_radio_usb)
|
||||
echo "companion_radio_usb"
|
||||
;;
|
||||
companion_ble|companion_radio_ble)
|
||||
echo "companion_radio_ble"
|
||||
;;
|
||||
room_svr|room_server)
|
||||
echo "room_server"
|
||||
;;
|
||||
repeatr|repeater)
|
||||
echo "repeater"
|
||||
;;
|
||||
*)
|
||||
echo "${variant_suffix,,}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
trim_trailing_underscores() {
|
||||
local value=$1
|
||||
|
||||
while [[ "$value" == *_ ]]; do
|
||||
value=${value%_}
|
||||
done
|
||||
|
||||
echo "$value"
|
||||
}
|
||||
|
||||
sort_lines_case_insensitive() {
|
||||
sort -f
|
||||
}
|
||||
|
||||
print_numbered_menu() {
|
||||
local items=("$@")
|
||||
local i
|
||||
|
||||
for i in "${!items[@]}"; do
|
||||
printf '%d) %s\n' "$((i + 1))" "${items[$i]}"
|
||||
done
|
||||
}
|
||||
|
||||
# $1 should be the string to find (case insensitive)
|
||||
get_pio_envs_ending_with_string() {
|
||||
prompt_menu_choice() {
|
||||
local prompt_label=$1
|
||||
local max_choice=$2
|
||||
local allow_back=${3:-0}
|
||||
local choice
|
||||
|
||||
while true; do
|
||||
if [ "$allow_back" -eq 1 ]; then
|
||||
read -r -p "${prompt_label} [1-${max_choice}, B=Back, Q=Quit]: " choice
|
||||
else
|
||||
read -r -p "${prompt_label} [1-${max_choice}, Q=Quit]: " choice
|
||||
fi
|
||||
|
||||
case "${choice^^}" in
|
||||
Q)
|
||||
MENU_CHOICE="QUIT"
|
||||
return 0
|
||||
;;
|
||||
B)
|
||||
if [ "$allow_back" -eq 1 ]; then
|
||||
MENU_CHOICE="BACK"
|
||||
return 0
|
||||
fi
|
||||
echo "Invalid selection."
|
||||
;;
|
||||
*)
|
||||
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "$max_choice" ]; then
|
||||
MENU_CHOICE="$choice"
|
||||
return 0
|
||||
fi
|
||||
echo "Invalid selection."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
get_env_metadata() {
|
||||
local env_name=$1
|
||||
local trimmed_env_name
|
||||
local board_part
|
||||
local variant_part
|
||||
local board_family
|
||||
local board_modifier
|
||||
local variant_label
|
||||
local tag_prefix
|
||||
|
||||
trimmed_env_name=$(trim_trailing_underscores "$env_name")
|
||||
board_part=$trimmed_env_name
|
||||
variant_part=""
|
||||
|
||||
shopt -s nocasematch
|
||||
envs=($(get_pio_envs))
|
||||
for env in "${envs[@]}"; do
|
||||
if [[ "$env" == *${1} ]]; then
|
||||
echo $env
|
||||
# Split a raw env name into board and variant pieces using the normalized
|
||||
# suffix vocabulary defined near the top of the file.
|
||||
if [[ "$trimmed_env_name" =~ ^(.+)[_-](${ENV_VARIANT_SUFFIX_PATTERN})$ ]]; then
|
||||
board_part=${BASH_REMATCH[1]}
|
||||
variant_part=$(canonicalize_variant_suffix "${BASH_REMATCH[2]}")
|
||||
fi
|
||||
|
||||
# Fold display and form-factor suffixes into the variant label so related
|
||||
# boards share one first-level menu entry.
|
||||
case "$board_part" in
|
||||
*"$BOARD_MODIFIER_WITHOUT_DISPLAY")
|
||||
board_family=${board_part%"$BOARD_MODIFIER_WITHOUT_DISPLAY"}
|
||||
board_modifier="$BOARD_LABEL_WITHOUT_DISPLAY"
|
||||
;;
|
||||
*"$BOARD_MODIFIER_LOGGING")
|
||||
board_family=${board_part%"$BOARD_MODIFIER_LOGGING"}
|
||||
board_modifier="$BOARD_LABEL_LOGGING"
|
||||
;;
|
||||
*"$BOARD_MODIFIER_TFT")
|
||||
board_family=${board_part%"$BOARD_MODIFIER_TFT"}
|
||||
board_modifier="$BOARD_LABEL_TFT"
|
||||
;;
|
||||
*"$BOARD_MODIFIER_EINK")
|
||||
board_family=${board_part%"$BOARD_MODIFIER_EINK"}
|
||||
board_modifier="$BOARD_LABEL_EINK"
|
||||
;;
|
||||
*"$BOARD_MODIFIER_EINK_SUFFIX")
|
||||
board_family=${board_part%"$BOARD_MODIFIER_EINK_SUFFIX"}
|
||||
board_modifier="$BOARD_LABEL_EINK"
|
||||
;;
|
||||
*)
|
||||
board_family=$board_part
|
||||
board_modifier=""
|
||||
;;
|
||||
esac
|
||||
shopt -u nocasematch
|
||||
|
||||
variant_label="$variant_part"
|
||||
if [ -n "$board_modifier" ]; then
|
||||
if [ -n "$variant_label" ]; then
|
||||
variant_label="${board_modifier}_${variant_label}"
|
||||
else
|
||||
variant_label="$board_modifier"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$variant_label" ]; then
|
||||
variant_label="$DEFAULT_VARIANT_LABEL"
|
||||
fi
|
||||
|
||||
case "$variant_part" in
|
||||
room_server)
|
||||
tag_prefix="$TAG_PREFIX_ROOM_SERVER"
|
||||
;;
|
||||
companion_radio_*)
|
||||
tag_prefix="$TAG_PREFIX_COMPANION"
|
||||
;;
|
||||
repeater*)
|
||||
tag_prefix="$TAG_PREFIX_REPEATER"
|
||||
;;
|
||||
*)
|
||||
tag_prefix=""
|
||||
;;
|
||||
esac
|
||||
|
||||
printf '%s\t%s\t%s\n' "$board_family" "$variant_label" "$tag_prefix"
|
||||
}
|
||||
|
||||
get_metadata_field() {
|
||||
local env_name=$1
|
||||
local field_index=$2
|
||||
local metadata
|
||||
|
||||
metadata=$(get_env_metadata "$env_name")
|
||||
case "$field_index" in
|
||||
1)
|
||||
echo "${metadata%%$'\t'*}"
|
||||
;;
|
||||
2)
|
||||
metadata=${metadata#*$'\t'}
|
||||
echo "${metadata%%$'\t'*}"
|
||||
;;
|
||||
3)
|
||||
echo "${metadata##*$'\t'}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
get_board_family_for_env() {
|
||||
get_metadata_field "$1" 1
|
||||
}
|
||||
|
||||
get_variant_name_for_env() {
|
||||
get_metadata_field "$1" 2
|
||||
}
|
||||
|
||||
get_release_tag_prefix_for_env() {
|
||||
get_metadata_field "$1" 3
|
||||
}
|
||||
|
||||
get_variants_for_board() {
|
||||
local board_family=$1
|
||||
local env
|
||||
|
||||
for env in "${ALL_PIO_ENVS[@]}"; do
|
||||
if [ "$(get_board_family_for_env "$env")" == "$board_family" ]; then
|
||||
echo "$env"
|
||||
fi
|
||||
done | sort_lines_case_insensitive
|
||||
}
|
||||
|
||||
prompt_for_variant_for_board() {
|
||||
local board=$1
|
||||
local -A seen_variant_labels=()
|
||||
local variants
|
||||
local variant_labels
|
||||
local i
|
||||
local j
|
||||
|
||||
mapfile -t variants < <(get_variants_for_board "$board")
|
||||
if [ ${#variants[@]} -eq 0 ]; then
|
||||
echo "No firmware variants were found for ${board}."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ${#variants[@]} -eq 1 ]; then
|
||||
SELECTED_TARGET="${variants[0]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
variant_labels=()
|
||||
for i in "${!variants[@]}"; do
|
||||
variant_labels[i]=$(get_variant_name_for_env "${variants[$i]}")
|
||||
seen_variant_labels["${variant_labels[$i]}"]=$(( ${seen_variant_labels["${variant_labels[$i]}"]:-0} + 1 ))
|
||||
done
|
||||
|
||||
# Stop early if normalization would present the user with ambiguous labels.
|
||||
for i in "${!variant_labels[@]}"; do
|
||||
if [ "${seen_variant_labels["${variant_labels[$i]}"]}" -gt 1 ]; then
|
||||
echo "Ambiguous firmware variants detected for ${board}: ${variant_labels[$i]}"
|
||||
echo "The normalized menu labels are not unique for this board family."
|
||||
for j in "${!variants[@]}"; do
|
||||
echo " ${variants[$j]}"
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Select a firmware variant for ${board}:"
|
||||
while true; do
|
||||
print_numbered_menu "${variant_labels[@]}"
|
||||
prompt_menu_choice "Variant selection" "${#variant_labels[@]}" 1
|
||||
if [ "$MENU_CHOICE" == "BACK" ]; then
|
||||
return 1
|
||||
fi
|
||||
if [ "$MENU_CHOICE" == "QUIT" ]; then
|
||||
echo "Cancelled."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SELECTED_TARGET="${variants[$((MENU_CHOICE - 1))]}"
|
||||
return 0
|
||||
done
|
||||
}
|
||||
|
||||
prompt_for_board_target() {
|
||||
local -A seen_boards=()
|
||||
local boards=()
|
||||
local board
|
||||
local env
|
||||
|
||||
if ! [ -t 0 ]; then
|
||||
echo "No command provided and no interactive terminal is available."
|
||||
global_usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ${#ALL_PIO_ENVS[@]} -eq 0 ]; then
|
||||
echo "No PlatformIO environments were found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for env in "${ALL_PIO_ENVS[@]}"; do
|
||||
board=$(get_board_family_for_env "$env")
|
||||
if [ -z "${seen_boards[$board]}" ]; then
|
||||
seen_boards["$board"]=1
|
||||
boards+=("$board")
|
||||
fi
|
||||
done
|
||||
|
||||
mapfile -t boards < <(printf '%s\n' "${boards[@]}" | sort_lines_case_insensitive)
|
||||
|
||||
echo "No command provided. Select a board family:"
|
||||
while true; do
|
||||
print_numbered_menu "${boards[@]}"
|
||||
prompt_menu_choice "Board selection" "${#boards[@]}"
|
||||
if [ "$MENU_CHOICE" == "QUIT" ]; then
|
||||
echo "Cancelled."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
board=${boards[$((MENU_CHOICE - 1))]}
|
||||
if prompt_for_variant_for_board "$board"; then
|
||||
echo "Building firmware for ${SELECTED_TARGET}"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# get platform flag for a given environment
|
||||
# $1 should be the environment name
|
||||
get_latest_version_from_tags() {
|
||||
local env_name=$1
|
||||
local tag_prefix
|
||||
local latest_tag
|
||||
local fallback_version
|
||||
|
||||
fallback_version="${FALLBACK_VERSION_PREFIX}-$(date "${FALLBACK_VERSION_DATE_FORMAT}")"
|
||||
tag_prefix=$(get_release_tag_prefix_for_env "$env_name")
|
||||
if [ -z "$tag_prefix" ]; then
|
||||
echo "$fallback_version"
|
||||
return 0
|
||||
fi
|
||||
|
||||
latest_tag=$(git tag --list "${tag_prefix}-v*" --sort=-version:refname | head -n 1)
|
||||
if [ -z "$latest_tag" ]; then
|
||||
echo "$fallback_version"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "${latest_tag#"${tag_prefix}"-}"
|
||||
}
|
||||
|
||||
derive_default_firmware_version() {
|
||||
local env_name=$1
|
||||
local base_version
|
||||
|
||||
base_version=$(get_latest_version_from_tags "$env_name")
|
||||
case "$base_version" in
|
||||
*-dev|dev-*)
|
||||
echo "$base_version"
|
||||
;;
|
||||
*)
|
||||
echo "${base_version}-dev"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
prompt_for_firmware_version() {
|
||||
local env_name=$1
|
||||
local suggested_version
|
||||
local entered_version
|
||||
|
||||
suggested_version=$(derive_default_firmware_version "$env_name")
|
||||
|
||||
if ! [ -t 0 ]; then
|
||||
FIRMWARE_VERSION="$suggested_version"
|
||||
echo "FIRMWARE_VERSION not set, using derived default: ${FIRMWARE_VERSION}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Suggested firmware version for ${env_name}: ${suggested_version}"
|
||||
read -r -e -i "${suggested_version}" -p "Firmware version: " entered_version
|
||||
FIRMWARE_VERSION="${entered_version:-$suggested_version}"
|
||||
}
|
||||
|
||||
get_pio_envs_containing_string() {
|
||||
local env
|
||||
|
||||
shopt -s nocasematch
|
||||
for env in "${ALL_PIO_ENVS[@]}"; do
|
||||
if [[ "$env" == *${1}* ]]; then
|
||||
echo "$env"
|
||||
fi
|
||||
done
|
||||
shopt -u nocasematch
|
||||
}
|
||||
|
||||
get_pio_envs_ending_with_string() {
|
||||
local env
|
||||
|
||||
shopt -s nocasematch
|
||||
for env in "${ALL_PIO_ENVS[@]}"; do
|
||||
if [[ "$env" == *${1} ]]; then
|
||||
echo "$env"
|
||||
fi
|
||||
done
|
||||
shopt -u nocasematch
|
||||
}
|
||||
|
||||
get_platform_for_env() {
|
||||
local env_name=$1
|
||||
echo "$PIO_CONFIG_JSON" | python3 -c "
|
||||
|
||||
# PlatformIO exposes project config as JSON; scan the selected env's
|
||||
# build_flags to recover the platform token used for artifact collection.
|
||||
# Feed the cached JSON via stdin to avoid shell echo quirks and argv/env size limits.
|
||||
python3 -c "
|
||||
import sys, json, re
|
||||
data = json.load(sys.stdin)
|
||||
for section, options in data:
|
||||
@@ -101,142 +504,154 @@ for section, options in data:
|
||||
for key, value in options:
|
||||
if key == 'build_flags':
|
||||
for flag in value:
|
||||
match = re.search(r'(ESP32_PLATFORM|NRF52_PLATFORM|STM32_PLATFORM|RP2040_PLATFORM)', flag)
|
||||
match = re.search(r'($SUPPORTED_PLATFORM_PATTERN)', flag)
|
||||
if match:
|
||||
print(match.group(1))
|
||||
sys.exit(0)
|
||||
"
|
||||
" <<<"$PIO_CONFIG_JSON"
|
||||
}
|
||||
|
||||
is_supported_platform() {
|
||||
local env_platform=$1
|
||||
|
||||
[[ "$env_platform" =~ ^(${SUPPORTED_PLATFORM_PATTERN})$ ]]
|
||||
}
|
||||
|
||||
# disable all debug logging flags if DISABLE_DEBUG=1 is set
|
||||
disable_debug_flags() {
|
||||
if [ "$DISABLE_DEBUG" == "1" ]; then
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UMESH_DEBUG -UBLE_DEBUG_LOGGING -UWIFI_DEBUG_LOGGING -UBRIDGE_DEBUG -UGPS_NMEA_DEBUG -UCORE_DEBUG_LEVEL -UESPNOW_DEBUG_LOGGING -UDEBUG_RP2040_WIRE -UDEBUG_RP2040_SPI -UDEBUG_RP2040_CORE -UDEBUG_RP2040_PORT -URADIOLIB_DEBUG_SPI -UCFG_DEBUG -URADIOLIB_DEBUG_BASIC -URADIOLIB_DEBUG_PROTOCOL"
|
||||
fi
|
||||
}
|
||||
|
||||
# build firmware for the provided pio env in $1
|
||||
copy_build_output() {
|
||||
local source_path=$1
|
||||
local output_path=$2
|
||||
|
||||
if [ -f "$source_path" ]; then
|
||||
cp -- "$source_path" "$output_path"
|
||||
fi
|
||||
}
|
||||
|
||||
collect_esp32_artifacts() {
|
||||
local env_name=$1
|
||||
local firmware_filename=$2
|
||||
|
||||
pio run -t mergebin -e "$env_name"
|
||||
copy_build_output ".pio/build/${env_name}/firmware.bin" "out/${firmware_filename}.bin"
|
||||
copy_build_output ".pio/build/${env_name}/firmware-merged.bin" "out/${firmware_filename}-merged.bin"
|
||||
}
|
||||
|
||||
collect_nrf52_artifacts() {
|
||||
local env_name=$1
|
||||
local firmware_filename=$2
|
||||
|
||||
python3 bin/uf2conv/uf2conv.py ".pio/build/${env_name}/firmware.hex" -c -o ".pio/build/${env_name}/firmware.uf2" -f 0xADA52840
|
||||
copy_build_output ".pio/build/${env_name}/firmware.uf2" "out/${firmware_filename}.uf2"
|
||||
copy_build_output ".pio/build/${env_name}/firmware.zip" "out/${firmware_filename}.zip"
|
||||
}
|
||||
|
||||
collect_stm32_artifacts() {
|
||||
local env_name=$1
|
||||
local firmware_filename=$2
|
||||
|
||||
copy_build_output ".pio/build/${env_name}/firmware.bin" "out/${firmware_filename}.bin"
|
||||
copy_build_output ".pio/build/${env_name}/firmware.hex" "out/${firmware_filename}.hex"
|
||||
}
|
||||
|
||||
collect_rp2040_artifacts() {
|
||||
local env_name=$1
|
||||
local firmware_filename=$2
|
||||
|
||||
copy_build_output ".pio/build/${env_name}/firmware.bin" "out/${firmware_filename}.bin"
|
||||
copy_build_output ".pio/build/${env_name}/firmware.uf2" "out/${firmware_filename}.uf2"
|
||||
}
|
||||
|
||||
collect_build_artifacts() {
|
||||
local env_name=$1
|
||||
local env_platform=$2
|
||||
local firmware_filename=$3
|
||||
|
||||
# Post-build outputs differ by platform, so dispatch to the matching
|
||||
# collector after the main firmware build succeeds.
|
||||
case "$env_platform" in
|
||||
ESP32_PLATFORM)
|
||||
collect_esp32_artifacts "$env_name" "$firmware_filename"
|
||||
;;
|
||||
NRF52_PLATFORM)
|
||||
collect_nrf52_artifacts "$env_name" "$firmware_filename"
|
||||
;;
|
||||
STM32_PLATFORM)
|
||||
collect_stm32_artifacts "$env_name" "$firmware_filename"
|
||||
;;
|
||||
RP2040_PLATFORM)
|
||||
collect_rp2040_artifacts "$env_name" "$firmware_filename"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
build_firmware() {
|
||||
# get env platform for post build actions
|
||||
ENV_PLATFORM=($(get_platform_for_env $1))
|
||||
local env_name=$1
|
||||
local env_platform
|
||||
local commit_hash
|
||||
local firmware_build_date
|
||||
local firmware_version_string
|
||||
local firmware_filename
|
||||
|
||||
# get git commit sha
|
||||
COMMIT_HASH=$(git rev-parse --short HEAD)
|
||||
|
||||
# set firmware build date
|
||||
FIRMWARE_BUILD_DATE=$(date '+%d-%b-%Y')
|
||||
|
||||
# get FIRMWARE_VERSION, which should be provided by the environment
|
||||
if [ -z "$FIRMWARE_VERSION" ]; then
|
||||
echo "FIRMWARE_VERSION must be set in environment"
|
||||
env_platform=$(get_platform_for_env "$env_name")
|
||||
if ! is_supported_platform "$env_platform"; then
|
||||
echo "Unsupported or unknown platform for env: $env_name"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# set firmware version string
|
||||
# e.g: v1.0.0-abcdef
|
||||
FIRMWARE_VERSION_STRING="${FIRMWARE_VERSION}-${COMMIT_HASH}"
|
||||
commit_hash=$(git rev-parse --short HEAD)
|
||||
firmware_build_date=$(date '+%d-%b-%Y')
|
||||
|
||||
# craft filename
|
||||
# e.g: RAK_4631_Repeater-v1.0.0-SHA
|
||||
FIRMWARE_FILENAME="$1-${FIRMWARE_VERSION_STRING}"
|
||||
if [ -z "$FIRMWARE_VERSION" ]; then
|
||||
prompt_for_firmware_version "$env_name"
|
||||
echo "Using firmware version: ${FIRMWARE_VERSION}"
|
||||
fi
|
||||
|
||||
# add firmware version info to end of existing platformio build flags in environment vars
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DFIRMWARE_BUILD_DATE='\"${FIRMWARE_BUILD_DATE}\"' -DFIRMWARE_VERSION='\"${FIRMWARE_VERSION_STRING}\"'"
|
||||
firmware_version_string="${FIRMWARE_VERSION}-${commit_hash}"
|
||||
firmware_filename="${env_name}-${firmware_version_string}"
|
||||
|
||||
# disable debug flags if requested
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DFIRMWARE_BUILD_DATE='\"${firmware_build_date}\"' -DFIRMWARE_VERSION='\"${firmware_version_string}\"'"
|
||||
disable_debug_flags
|
||||
|
||||
# build firmware target
|
||||
pio run -e $1
|
||||
|
||||
# build merge-bin for esp32 fresh install, copy .bins to out folder (e.g: Heltec_v3_room_server-v1.0.0-SHA.bin)
|
||||
if [ "$ENV_PLATFORM" == "ESP32_PLATFORM" ]; then
|
||||
pio run -t mergebin -e $1
|
||||
cp .pio/build/$1/firmware.bin out/${FIRMWARE_FILENAME}.bin 2>/dev/null || true
|
||||
cp .pio/build/$1/firmware-merged.bin out/${FIRMWARE_FILENAME}-merged.bin 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# build .uf2 for nrf52 boards, copy .uf2 and .zip to out folder (e.g: RAK_4631_Repeater-v1.0.0-SHA.uf2)
|
||||
if [ "$ENV_PLATFORM" == "NRF52_PLATFORM" ]; then
|
||||
python3 bin/uf2conv/uf2conv.py .pio/build/$1/firmware.hex -c -o .pio/build/$1/firmware.uf2 -f 0xADA52840
|
||||
cp .pio/build/$1/firmware.uf2 out/${FIRMWARE_FILENAME}.uf2 2>/dev/null || true
|
||||
cp .pio/build/$1/firmware.zip out/${FIRMWARE_FILENAME}.zip 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# for stm32, copy .bin and .hex to out folder
|
||||
if [ "$ENV_PLATFORM" == "STM32_PLATFORM" ]; then
|
||||
cp .pio/build/$1/firmware.bin out/${FIRMWARE_FILENAME}.bin 2>/dev/null || true
|
||||
cp .pio/build/$1/firmware.hex out/${FIRMWARE_FILENAME}.hex 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# for rp2040, copy .bin and .uf2 to out folder
|
||||
if [ "$ENV_PLATFORM" == "RP2040_PLATFORM" ]; then
|
||||
cp .pio/build/$1/firmware.bin out/${FIRMWARE_FILENAME}.bin 2>/dev/null || true
|
||||
cp .pio/build/$1/firmware.uf2 out/${FIRMWARE_FILENAME}.uf2 2>/dev/null || true
|
||||
fi
|
||||
|
||||
pio run -e "$env_name"
|
||||
collect_build_artifacts "$env_name" "$env_platform" "$firmware_filename"
|
||||
}
|
||||
|
||||
# firmwares containing $1 will be built
|
||||
build_all_firmwares_matching() {
|
||||
envs=($(get_pio_envs_containing_string "$1"))
|
||||
local envs
|
||||
local env
|
||||
|
||||
mapfile -t envs < <(get_pio_envs_containing_string "$1")
|
||||
for env in "${envs[@]}"; do
|
||||
build_firmware $env
|
||||
build_firmware "$env"
|
||||
done
|
||||
}
|
||||
|
||||
# firmwares ending with $1 will be built
|
||||
build_all_firmwares_by_suffix() {
|
||||
envs=($(get_pio_envs_ending_with_string "$1"))
|
||||
local envs
|
||||
local env
|
||||
|
||||
mapfile -t envs < <(get_pio_envs_ending_with_string "$1")
|
||||
for env in "${envs[@]}"; do
|
||||
build_firmware $env
|
||||
build_firmware "$env"
|
||||
done
|
||||
}
|
||||
|
||||
build_repeater_firmwares() {
|
||||
|
||||
# # build specific repeater firmwares
|
||||
# build_firmware "Heltec_v2_repeater"
|
||||
# build_firmware "Heltec_v3_repeater"
|
||||
# build_firmware "Xiao_C3_Repeater_sx1262"
|
||||
# build_firmware "Xiao_S3_WIO_Repeater"
|
||||
# build_firmware "LilyGo_T3S3_sx1262_Repeater"
|
||||
# build_firmware "RAK_4631_Repeater"
|
||||
|
||||
# build all repeater firmwares
|
||||
build_all_firmwares_by_suffix "_repeater"
|
||||
|
||||
build_all_firmwares_by_suffix "$BULK_BUILD_SUFFIX_REPEATER"
|
||||
}
|
||||
|
||||
build_companion_firmwares() {
|
||||
|
||||
# # build specific companion firmwares
|
||||
# build_firmware "Heltec_v2_companion_radio_usb"
|
||||
# build_firmware "Heltec_v2_companion_radio_ble"
|
||||
# build_firmware "Heltec_v3_companion_radio_usb"
|
||||
# build_firmware "Heltec_v3_companion_radio_ble"
|
||||
# build_firmware "Xiao_S3_WIO_companion_radio_ble"
|
||||
# build_firmware "LilyGo_T3S3_sx1262_companion_radio_usb"
|
||||
# build_firmware "LilyGo_T3S3_sx1262_companion_radio_ble"
|
||||
# build_firmware "RAK_4631_companion_radio_usb"
|
||||
# build_firmware "RAK_4631_companion_radio_ble"
|
||||
# build_firmware "t1000e_companion_radio_ble"
|
||||
|
||||
# build all companion firmwares
|
||||
build_all_firmwares_by_suffix "_companion_radio_usb"
|
||||
build_all_firmwares_by_suffix "_companion_radio_ble"
|
||||
|
||||
build_all_firmwares_by_suffix "$BULK_BUILD_SUFFIX_COMPANION_USB"
|
||||
build_all_firmwares_by_suffix "$BULK_BUILD_SUFFIX_COMPANION_BLE"
|
||||
}
|
||||
|
||||
build_room_server_firmwares() {
|
||||
|
||||
# # build specific room server firmwares
|
||||
# build_firmware "Heltec_v3_room_server"
|
||||
# build_firmware "RAK_4631_room_server"
|
||||
|
||||
# build all room server firmwares
|
||||
build_all_firmwares_by_suffix "_room_server"
|
||||
|
||||
build_all_firmwares_by_suffix "$BULK_BUILD_SUFFIX_ROOM_SERVER"
|
||||
}
|
||||
|
||||
build_firmwares() {
|
||||
@@ -245,34 +660,86 @@ build_firmwares() {
|
||||
build_room_server_firmwares
|
||||
}
|
||||
|
||||
# clean build dir
|
||||
rm -rf out
|
||||
mkdir -p out
|
||||
prepare_output_dir() {
|
||||
local output_dir="$OUTPUT_DIR"
|
||||
|
||||
# handle script args
|
||||
if [[ $1 == "build-firmware" ]]; then
|
||||
TARGETS=${@:2}
|
||||
if [ "$TARGETS" ]; then
|
||||
for env in $TARGETS; do
|
||||
build_firmware $env
|
||||
done
|
||||
else
|
||||
if [ -z "$output_dir" ] || [ "$output_dir" == "/" ] || [ "$output_dir" == "." ]; then
|
||||
echo "Refusing to clean unsafe output directory: $output_dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf -- "$output_dir"
|
||||
mkdir -p -- "$output_dir"
|
||||
}
|
||||
|
||||
run_build_firmware_command() {
|
||||
local targets=("${@:2}")
|
||||
local env
|
||||
|
||||
if [ ${#targets[@]} -eq 0 ]; then
|
||||
echo "usage: $0 build-firmware <target>"
|
||||
exit 1
|
||||
fi
|
||||
elif [[ $1 == "build-matching-firmwares" ]]; then
|
||||
if [ "$2" ]; then
|
||||
build_all_firmwares_matching $2
|
||||
else
|
||||
echo "usage: $0 build-matching-firmwares <build-match-spec>"
|
||||
exit 1
|
||||
|
||||
for env in "${targets[@]}"; do
|
||||
build_firmware "$env"
|
||||
done
|
||||
}
|
||||
|
||||
run_command() {
|
||||
case "$1" in
|
||||
build-firmware)
|
||||
run_build_firmware_command "$@"
|
||||
;;
|
||||
build-matching-firmwares)
|
||||
if [ -n "$2" ]; then
|
||||
build_all_firmwares_matching "$2"
|
||||
else
|
||||
echo "usage: $0 build-matching-firmwares <build-match-spec>"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
build-firmwares)
|
||||
build_firmwares
|
||||
;;
|
||||
build-companion-firmwares)
|
||||
build_companion_firmwares
|
||||
;;
|
||||
build-repeater-firmwares)
|
||||
build_repeater_firmwares
|
||||
;;
|
||||
build-room-server-firmwares)
|
||||
build_room_server_firmwares
|
||||
;;
|
||||
*)
|
||||
global_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main() {
|
||||
case "${1:-}" in
|
||||
help|usage|-h|--help)
|
||||
global_usage
|
||||
exit 0
|
||||
;;
|
||||
list|-l)
|
||||
init_project_context
|
||||
get_pio_envs
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
init_project_context
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
prompt_for_board_target
|
||||
set -- build-firmware "$SELECTED_TARGET"
|
||||
fi
|
||||
elif [[ $1 == "build-firmwares" ]]; then
|
||||
build_firmwares
|
||||
elif [[ $1 == "build-companion-firmwares" ]]; then
|
||||
build_companion_firmwares
|
||||
elif [[ $1 == "build-repeater-firmwares" ]]; then
|
||||
build_repeater_firmwares
|
||||
elif [[ $1 == "build-room-server-firmwares" ]]; then
|
||||
build_room_server_firmwares
|
||||
fi
|
||||
|
||||
prepare_output_dir
|
||||
run_command "$@"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
+243
-1
@@ -115,6 +115,28 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
|
||||
---
|
||||
|
||||
### Get or set recent repeater fallback prefix/SNR
|
||||
**Usage:**
|
||||
- `get recent.repeater`
|
||||
- `get recent.repeater <page>`
|
||||
- `get recent.repeater page <page>`
|
||||
- `set recent.repeater <prefix_hex_6> <snr_db>`
|
||||
|
||||
**Parameters:**
|
||||
- `prefix_hex_6`: Exactly 3 bytes of next-hop prefix in hex (6 chars)
|
||||
- `snr_db`: SNR in dB (supports decimals; stored at x4 precision)
|
||||
- `page`: 1-based page number
|
||||
|
||||
**Notes:**
|
||||
- `set` stores or updates the prefix in the recent repeater table.
|
||||
- Rows are sorted by prefix width (3-byte, 2-byte, 1-byte), then SNR descending.
|
||||
- A full direct retry failure lowers the stored SNR by `0.25 dB`.
|
||||
- If a full failure has no row yet, it first seeds the row at the active retry cutoff + `2.5 dB`, then applies the `0.25 dB` penalty.
|
||||
- Serial CLI page size is fixed at `128` rows; choose page with `get recent.repeater <page>`.
|
||||
- Over LoRa remote CLI, page size is fixed at `7` rows; choose page with `get recent.repeater <page>`.
|
||||
|
||||
---
|
||||
|
||||
## Statistics
|
||||
|
||||
### Clear Stats
|
||||
@@ -277,6 +299,20 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
|
||||
---
|
||||
|
||||
#### View or change the LoRa FEM receive-path gain state on supported boards
|
||||
**Usage:**
|
||||
- `get radio.fem.rxgain`
|
||||
- `set radio.fem.rxgain <state>`
|
||||
|
||||
**Parameters:**
|
||||
- `state`: `on`|`off`
|
||||
|
||||
**Notes:**
|
||||
- This controls the external LoRa FEM receive-path LNA where the board supports it.
|
||||
- This is separate from `radio.rxgain`, which controls the radio chip receive gain mode.
|
||||
|
||||
---
|
||||
|
||||
### System
|
||||
|
||||
#### View or change this node's name
|
||||
@@ -500,7 +536,116 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
**Parameters:**
|
||||
- `value`: Direct transmit delay factor (0-2)
|
||||
|
||||
**Default:** `0.2`
|
||||
**Default:** `0.3`
|
||||
|
||||
**Note:** Direct retry waits include the same airtime-based randomized delay calculation as direct retransmits, so this factor also controls retry echo windows.
|
||||
|
||||
---
|
||||
|
||||
#### View or change whether direct retries use the recent repeater blacklist
|
||||
**Usage:**
|
||||
- `get direct.retry.heard`
|
||||
- `set direct.retry.heard <state>`
|
||||
|
||||
**Parameters:**
|
||||
- `state`: `on`|`off`
|
||||
|
||||
**Default:** `on`
|
||||
|
||||
**Note:** When enabled, the recent repeater table is the only direct retry eligibility gate. Prefixes missing from the table are assumed reachable; prefixes in the table below the active SNR gate are blocked. Neighbor data is not used.
|
||||
|
||||
---
|
||||
|
||||
#### View or change adaptive coding rate for direct retry packets
|
||||
**Usage:**
|
||||
- `get direct.retry.cr`
|
||||
- `set direct.retry.cr <cr4_min>,<cr5_min>,<cr7_min>,<cr8_max>`
|
||||
- `set direct.retry.cr off`
|
||||
|
||||
**Parameters:**
|
||||
- `cr4_min`: SNR in dB where retry packets use `CR4`
|
||||
- `cr5_min`: SNR in dB where retry packets use `CR5`
|
||||
- `cr7_min`: SNR in dB where retry packets use `CR7`
|
||||
- `cr8_max`: SNR in dB where retry packets use `CR8`
|
||||
|
||||
**Default:** `10.0,7.5,2.5,2.5`
|
||||
|
||||
**Note:** DM retry packets use the next-hop SNR from a recent repeater table entry to pick a local transmit coding rate; if no recent entry is available, retry packets use `CR5`. With the default, SNR `10.0 dB` and up uses `CR4`, SNR `7.5 dB` and up uses `CR5`, SNR `2.5 dB` and down uses `CR8`, and the middle band uses `CR7`. `CR6` is never selected. Use `set direct.retry.cr off` to disable adaptive coding-rate overrides. If adaptive selection chooses `CR4`, retries after the third attempt use `CR5`.
|
||||
|
||||
---
|
||||
|
||||
#### View or change the SNR margin used for direct retry eligibility
|
||||
**Usage:**
|
||||
- `get direct.retry.margin`
|
||||
- `set direct.retry.margin <value>`
|
||||
|
||||
**Parameters:**
|
||||
- `value`: Rooftop preset margin in dB above the SF-specific receive floor (minimum `0`, maximum `40`, quarter-dB precision, default `5.0`)
|
||||
|
||||
**Default:** `5.0`
|
||||
|
||||
**Note:** `get direct.retry.margin` returns the active preset's effective margin. The retry gate uses the active SF floor of `SF5=-2.5`, `SF6=-5`, `SF7=-7.5`, `SF8=-10`, `SF9=-12.5`, `SF10=-15`, `SF11=-17.5`, `SF12=-20`, then adds this margin.
|
||||
|
||||
---
|
||||
|
||||
#### View or change the retry preset
|
||||
**Usage:**
|
||||
- `get retry.preset`
|
||||
- `set retry.preset <value>`
|
||||
|
||||
**Parameters:**
|
||||
- `value`: `infra`|`rooftop`|`mobile` or `0`|`1`|`2`
|
||||
|
||||
**Default:** `rooftop` (`1`)
|
||||
|
||||
**Presets:**
|
||||
- `infra` (`0`): `275 ms` direct base wait, `4` direct retries, `150 ms` added per direct retry, SNR gate is SF floor + `15 dB`; flood retry defaults to `1` retry and path gate `1`
|
||||
- `rooftop` (`1`): `175 ms` direct base wait, `15` direct retries, `100 ms` added per direct retry, SNR gate is SF floor + `5 dB`; flood retry defaults to `3` retries and path gate `2`
|
||||
- `mobile` (`2`): `175 ms` direct base wait, `15` direct retries, `50 ms` added per direct retry, SNR gate is the SF floor; flood retry defaults to `3` retries and path gate `1`
|
||||
|
||||
**Note:** Selecting a preset copies those values into the direct retry settings and resets flood retry defaults. You can refine `direct.retry.margin`, `direct.retry.count`, `direct.retry.base`, `direct.retry.step`, `flood.retry.count`, or `flood.retry.path` afterward. Retry delay is `direct.txdelay` jitter + base wait + packet-length airtime wait + per-attempt step.
|
||||
|
||||
---
|
||||
|
||||
#### View or change the number of direct retry attempts
|
||||
**Usage:**
|
||||
- `get direct.retry.count`
|
||||
- `set direct.retry.count <value>`
|
||||
|
||||
**Parameters:**
|
||||
- `value`: Maximum retry attempts after initial TX (`1`-`15`)
|
||||
|
||||
**Default:** `15`
|
||||
|
||||
**Note:** The effective value is capped by total direct path length: paths of `3` hops or less use at most `8` retries, `4` hops use at most `12`, and `5+` hops use at most `15`. A queued resend is canceled early when the next-hop echo is heard.
|
||||
|
||||
---
|
||||
|
||||
#### View or change the base direct retry wait (milliseconds)
|
||||
**Usage:**
|
||||
- `get direct.retry.base`
|
||||
- `set direct.retry.base <value>`
|
||||
|
||||
**Parameters:**
|
||||
- `value`: Base wait in milliseconds (`10`-`5000`)
|
||||
|
||||
**Default:** `175`
|
||||
|
||||
**Note:** The configured base is added to packet-length airtime and `direct.txdelay` jitter. Preset defaults are already reduced to account for the added `direct.txdelay` component.
|
||||
|
||||
---
|
||||
|
||||
#### View or change the direct retry per-attempt add time (milliseconds)
|
||||
**Usage:**
|
||||
- `get direct.retry.step`
|
||||
- `set direct.retry.step <value>`
|
||||
|
||||
**Parameters:**
|
||||
- `value`: Milliseconds added per retry attempt (`0`-`5000`)
|
||||
|
||||
**Default:** `100`
|
||||
|
||||
**Note:** This controls the linear add after the first retry wait. For example, `base=300` and `step=150` adds `0`, `150`, `300`, ... ms across retry attempts.
|
||||
|
||||
---
|
||||
|
||||
@@ -627,6 +772,103 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
|
||||
---
|
||||
|
||||
#### View or change the number of flood retry attempts
|
||||
**Usage:**
|
||||
- `get flood.retry.count`
|
||||
- `set flood.retry.count <value>`
|
||||
|
||||
**Parameters:**
|
||||
- `value`: Maximum retry attempts after initial flood TX (`0`-`15`)
|
||||
|
||||
**Default:** `3` for `rooftop` and `mobile`, `1` for `infra`
|
||||
|
||||
**Note:** `0` disables flood retry.
|
||||
|
||||
---
|
||||
|
||||
#### View or change the flood retry path gate
|
||||
**Usage:**
|
||||
- `get flood.retry.path`
|
||||
- `set flood.retry.path <value>`
|
||||
|
||||
**Parameters:**
|
||||
- `value`: Maximum flood path length eligible for retry (`0`-`63`), or `off` to disable the gate
|
||||
|
||||
**Default:** `2` for `rooftop`, `1` for `infra` and `mobile`
|
||||
|
||||
---
|
||||
|
||||
#### View or change whether advert packets are flood-retried
|
||||
**Usage:**
|
||||
- `get flood.retry.advert`
|
||||
- `set flood.retry.advert <state>`
|
||||
|
||||
**Parameters:**
|
||||
- `state`: `on` or `off`
|
||||
|
||||
**Default:** `off`
|
||||
|
||||
**Note:** When this is `off`, node advert packets (`PAYLOAD_TYPE_ADVERT`, type `4`) are not queued for flood retry.
|
||||
|
||||
---
|
||||
|
||||
#### View or change flood retry target prefixes
|
||||
**Usage:**
|
||||
- `get flood.retry.prefixes`
|
||||
- `set flood.retry.prefixes <prefixes>`
|
||||
|
||||
**Parameters:**
|
||||
- `prefixes`: Comma-separated 3-byte hex prefixes, such as `A1B2C3,D4E5F6`; use `none` or `off` to clear
|
||||
|
||||
**Default:** `none`
|
||||
|
||||
**Note:** Prefixes are stored as 3 bytes. Flood retry skips packets whose path already contains a matching target prefix. When prefixes are configured, only a downstream echo from one of those target prefixes cancels a queued retry; when no prefixes are configured, any downstream echo cancels it. Matching works with 3-byte, 2-byte, or 1-byte flood paths by comparing the matching leading bytes.
|
||||
|
||||
---
|
||||
|
||||
#### View or change flood retry bridge mode
|
||||
**Usage:**
|
||||
- `get flood.retry.bridge`
|
||||
- `set flood.retry.bridge <state>`
|
||||
|
||||
**Parameters:**
|
||||
- `state`: `on` or `off`
|
||||
|
||||
**Default:** `off`
|
||||
|
||||
**Note:** Bridge mode uses bucket definitions instead of the single `flood.retry.prefixes` target list. It also has an implicit unconfigured catch-all bucket. If a flood comes from one fresh configured bucket, retry continues until every other fresh configured bucket plus the catch-all bucket has been heard or `flood.retry.count` is exhausted. If a flood comes from an unconfigured or pathless source, retry targets every fresh configured bucket. This means one configured bucket bridges between that bucket and everything else. Prefixes in `flood.retry.ignore` never count as heard bridge targets.
|
||||
|
||||
---
|
||||
|
||||
#### View or change flood retry bridge buckets
|
||||
**Usage:**
|
||||
- `get flood.retry.bucket.<bucket>`
|
||||
- `set flood.retry.bucket <bucket> <prefixes>`
|
||||
|
||||
**Parameters:**
|
||||
- `bucket`: Bucket number (`1`-`6`)
|
||||
- `prefixes`: Up to 17 comma-separated 3-byte hex prefixes, such as `AABBCC,223344`; use `none` or `off` to clear
|
||||
|
||||
**Default:** all buckets empty
|
||||
|
||||
**Note:** Prefixes are stored as 3 bytes but match 3-byte, 2-byte, and 1-byte flood paths by comparing leading bytes. Bucket prefixes are included in bridge retry logic only if they were heard in the recent repeater table within the last hour.
|
||||
|
||||
---
|
||||
|
||||
#### View or change flood retry ignored prefixes
|
||||
**Usage:**
|
||||
- `get flood.retry.ignore`
|
||||
- `set flood.retry.ignore <prefixes>`
|
||||
|
||||
**Parameters:**
|
||||
- `prefixes`: Up to 8 comma-separated 3-byte hex prefixes, such as `AABBCC,223344`; use `none` or `off` to clear
|
||||
|
||||
**Default:** empty
|
||||
|
||||
**Note:** In non-bridge retry, an echo whose last hop matches an ignored prefix does not cancel a queued retry as successful. In bridge mode, ignored prefixes do not count as a heard bridge bucket or as the implicit catch-all bucket when bridge retry decides whether every target has repeated the flood.
|
||||
|
||||
---
|
||||
|
||||
### ACL
|
||||
|
||||
#### Add, update or remove permissions for a companion
|
||||
|
||||
+3
-11
@@ -111,7 +111,6 @@ Anyone is able to build anything they like on top of MeshCore without paying any
|
||||
- MeshCore Firmware on GitHub: [https://github.com/meshcore-dev/MeshCore](https://github.com/meshcore-dev/MeshCore)
|
||||
- MeshCore Companion Web App: [https://app.meshcore.nz](https://app.meshcore.nz)
|
||||
- MeshCore Map: [https://map.meshcore.io](https://map.meshcore.io)
|
||||
- Andy Kirby's [MeshCore Intro Video](https://www.youtube.com/watch?v=t1qne8uJBAc)
|
||||
- Liam Cottle's [MeshCore Technical Presentation](https://www.youtube.com/watch?v=OwmkVkZQTf4)
|
||||
|
||||
You need LoRa hardware devices to run MeshCore firmware as clients or server (repeater and room server).
|
||||
@@ -194,7 +193,7 @@ Recently, as of October 2025, many regions have moved to the "narrow" setting, a
|
||||
|
||||
After extensive testing, many regions have switched or about to switch over to BW62.5 and SF7, 8, or 9. Narrower bandwidth setting and lower SF setting allow MeshCore's radio signals to fit between interference in the ISM band, provide for a lower noise floor, better SNR, and faster transmissions.
|
||||
|
||||
If you have consensus from your community in your region to update your region's preset recommendation, please post your update request on the [#meshcore-app](https://discord.com/channels/1343693475589263471/1391681655911088241) channel on the [MeshCore Discord server ](https://discord.gg/cYtQNYCCRK) to let Liam Cottle know.
|
||||
If you have consensus from your community in your region to update your region's preset recommendation, please post your update request on the [#meshcore-app](https://discord.com/channels/1343693475589263471/1391681655911088241) channel on the [MeshCore Discord server ](https://meshcore.gg) to let Liam Cottle know.
|
||||
|
||||
|
||||
|
||||
@@ -402,10 +401,7 @@ Another way to download map tiles is to use this Python script to get the tiles
|
||||
<https://github.com/fistulareffigy/MTD-Script>
|
||||
|
||||
There is also a modified script that adds additional error handling and parallel downloads:
|
||||
<https://discord.com/channels/826570251612323860/1330643963501351004/1338775811548905572>
|
||||
|
||||
UK map tiles are available separately from Andy Kirby on his discord server:
|
||||
<https://discord.com/channels/826570251612323860/1330643963501351004/1331346597367386224>
|
||||
<https://github.com/TheBestJohn/MTD-Script>
|
||||
|
||||
### 4.8. Q: Where do the map tiles go?
|
||||
Once you have the tiles downloaded, copy the `\tiles` folder to the root of your T-Deck's SD card.
|
||||
@@ -526,7 +522,7 @@ The third character is the capital letter 'O', not zero `0`
|
||||
- Firmware repo: https://github.com/meshcore-dev/MeshCore
|
||||
|
||||
### 5.8. Q: How can I support MeshCore?
|
||||
**A:** Provide your honest feedback on GitHub and on [MeshCore Discord server](https://discord.gg/BMwCtwHj5V). Spread the word of MeshCore to your friends and communities; help them get started with MeshCore. Support Scott's MeshCore development at <https://buymeacoffee.com/ripplebiz>.
|
||||
**A:** Provide your honest feedback on GitHub and on [MeshCore Discord server](https://meshcore.gg). Spread the word of MeshCore to your friends and communities; help them get started with MeshCore. Support Scott's MeshCore development at <https://buymeacoffee.com/ripplebiz>.
|
||||
|
||||
Support Liam Cottle's smartphone client development by unlocking the server administration wait gate with in-app purchase
|
||||
|
||||
@@ -563,10 +559,6 @@ pio run -e RAK_4631_Repeater
|
||||
```
|
||||
then you'll find `firmware.zip` in `.pio/build/RAK_4631_Repeater`
|
||||
|
||||
Andy also has a video on how to build using VS Code:
|
||||
*How to build and flash Meshcore repeater firmware | Heltec V3*
|
||||
<https://www.youtube.com/watch?v=WJvg6dt13hk> *(Link referenced in the Discord post)*
|
||||
|
||||
### 5.10. Q: Are there other MeshCore related open source projects?
|
||||
|
||||
**A:** [Liam Cottle](https://liamcottle.net)'s MeshCore web client and MeshCore Javascript library are open source under MIT license.
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
# Halo and Keymind Branch Settings
|
||||
|
||||
This file covers only CLI settings added by the Halo or Keymind branches. Use
|
||||
`docs/cli_commands.md` for the general MeshCore CLI.
|
||||
|
||||
## Quick Start
|
||||
|
||||
|
||||
```text
|
||||
set retry.preset rooftop
|
||||
set direct.retry.heard on
|
||||
set flood.retry.advert off
|
||||
set flood.retry.bridge off
|
||||
set flood.retry.prefixes none
|
||||
set flood.retry.ignore none
|
||||
```
|
||||
|
||||
Then verify:
|
||||
|
||||
```text
|
||||
get retry.preset
|
||||
get direct.retry.heard
|
||||
get flood.retry.advert
|
||||
get flood.retry.prefixes
|
||||
get flood.retry.ignore
|
||||
```
|
||||
|
||||
Use prefixes from the analyzer or neighbors list or `get recent.repeater` after the repeater has been online for a few hours.
|
||||
|
||||
## Common Examples
|
||||
|
||||
Disable retrying advert packets:
|
||||
|
||||
```text
|
||||
set flood.retry.advert off
|
||||
get flood.retry.advert
|
||||
```
|
||||
|
||||
Ignore a repeater as a successful flood retry echo:
|
||||
Use this if you have a car repeater and a house repeater; have the house ignore the car.
|
||||
|
||||
```text
|
||||
set flood.retry.ignore 71CE82,C7618C
|
||||
get flood.retry.ignore
|
||||
```
|
||||
|
||||
Only accept specific downstream relays as flood retry success:
|
||||
You're in a hole and need to hit a mountain top repeater to get out; keep trying till one you see one of these send out your packet.
|
||||
|
||||
```text
|
||||
set flood.retry.prefixes A58296,860CCA,425E5C
|
||||
get flood.retry.prefixes
|
||||
```
|
||||
|
||||
Bridge two groups of repeaters:
|
||||
|
||||
```text
|
||||
set flood.retry.bridge on
|
||||
set flood.retry.bucket 1 71CE82,C7618C
|
||||
set flood.retry.bucket 2 BEEBB0,425E5C
|
||||
get flood.retry.bucket.1
|
||||
get flood.retry.bucket.2
|
||||
```
|
||||
|
||||
Return to simple non-bridge flood retry:
|
||||
|
||||
```text
|
||||
set flood.retry.bridge off
|
||||
set flood.retry.prefixes none
|
||||
set flood.retry.ignore none
|
||||
```
|
||||
|
||||
## Added Settings
|
||||
|
||||
| Setting | What it does | How to use | Example |
|
||||
| --- | --- | --- | --- |
|
||||
| `recent.repeater` | Shows or seeds the recent repeater prefix/SNR table used by direct retry and bridge freshness checks. | `get recent.repeater`, `get recent.repeater <page>`, `set recent.repeater <prefix> <snr_db>` | `set recent.repeater A1B2C3 -8.5` |
|
||||
| `radio.fem.rxgain` | Controls the external LoRa FEM receive-path LNA where the board supports it. | `get radio.fem.rxgain`, `set radio.fem.rxgain on/off` | `set radio.fem.rxgain on` |
|
||||
|
||||
## Recent Repeater Table
|
||||
|
||||
Direct retry uses the recent repeater table when `direct.retry.heard` is `on`.
|
||||
Bridge buckets also use this table: a configured bucket prefix is active only
|
||||
when it was heard within the last hour.
|
||||
|
||||
Show learned rows:
|
||||
|
||||
```text
|
||||
get recent.repeater
|
||||
get recent.repeater 2
|
||||
```
|
||||
|
||||
Seed or correct a prefix:
|
||||
|
||||
```text
|
||||
set recent.repeater A1B2C3 8.5
|
||||
```
|
||||
|
||||
Rows are sorted by prefix width, then SNR. A full direct retry failure lowers
|
||||
the matching row by `0.25 dB`.
|
||||
|
||||
## Direct Retry Settings
|
||||
|
||||
Direct retry applies to direct-routed packets. A queued resend is canceled when the next-hop echo is heard.
|
||||
|
||||
| Setting | What it does | How to use | Example |
|
||||
| --- | --- | --- | --- |
|
||||
| `retry.preset` | Applies shared direct and flood retry defaults. Values: `infra`, `rooftop`, `mobile` or `0`, `1`, `2`. | `get retry.preset`, `set retry.preset <value>` | `set retry.preset rooftop` |
|
||||
| `direct.retry.heard` | Uses the recent repeater table as the direct retry eligibility gate. | `get direct.retry.heard`, `set direct.retry.heard on/off` | `set direct.retry.heard on` |
|
||||
| `direct.retry.margin` | SNR margin in dB above the SF-specific receive floor. | `get direct.retry.margin`, `set direct.retry.margin <0-40>` | `set direct.retry.margin 5` |
|
||||
| `direct.retry.count` | Maximum direct retry attempts after initial TX. | `get direct.retry.count`, `set direct.retry.count <1-15>` | `set direct.retry.count 15` |
|
||||
| `direct.retry.base` | Base wait in milliseconds before retry. | `get direct.retry.base`, `set direct.retry.base <10-5000>` | `set direct.retry.base 175` |
|
||||
| `direct.retry.step` | Milliseconds added per retry attempt. | `get direct.retry.step`, `set direct.retry.step <0-5000>` | `set direct.retry.step 100` |
|
||||
| `direct.retry.cr` | Adaptive coding-rate thresholds for direct retry packets. Uses `CR4`, `CR5`, `CR7`, or `CR8`; `CR6` is never selected. | `get direct.retry.cr`, `set direct.retry.cr <cr4_min>,<cr5_min>,<cr7_min>,<cr8_max>`, `set direct.retry.cr off` | `set direct.retry.cr 10.0,7.5,2.5,0` |
|
||||
|
||||
The default adaptive coding-rate profile is `10.0,7.5,2.5,2.5`.
|
||||
SNR `10.0 dB` and up uses `CR4`, `7.5 dB` and up uses `CR5`,
|
||||
`2.5 dB` and down uses `CR8`, and the middle band uses `CR7`. If no
|
||||
recent repeater table entry is available, retry packets use `CR5`. Use
|
||||
`set direct.retry.cr off` to disable adaptive coding-rate overrides. If
|
||||
adaptive selection chooses `CR4`, retries after the third attempt use
|
||||
`CR5`.
|
||||
|
||||
Preset details:
|
||||
|
||||
| Preset | Base | Count | Step | SNR gate |
|
||||
| --- | ---: | ---: | ---: | --- |
|
||||
| `infra` | `275 ms` | `4` | `150 ms` | SF floor + `15 dB` |
|
||||
| `rooftop` | `175 ms` | `15` | `100 ms` | SF floor + `5 dB` |
|
||||
| `mobile` | `175 ms` | `15` | `50 ms` | SF floor |
|
||||
|
||||
Example for a quiet fixed repeater:
|
||||
|
||||
```text
|
||||
set retry.preset rooftop
|
||||
set direct.retry.heard on
|
||||
set direct.retry.margin 5
|
||||
```
|
||||
|
||||
Example for a moving or weak-link node:
|
||||
|
||||
```text
|
||||
set retry.preset mobile
|
||||
set direct.retry.margin 0
|
||||
```
|
||||
|
||||
## Flood And Advert Settings
|
||||
|
||||
Flood retry applies to flood-routed packets. A queued retry is canceled when a qualifying downstream echo is heard.
|
||||
|
||||
| Setting | What it does | How to use | Example |
|
||||
| --- | --- | --- | --- |
|
||||
| `flood.retry.count` | Maximum flood retry attempts after initial TX. `0` disables flood retry. | `get flood.retry.count`, `set flood.retry.count <0-15>` | `set flood.retry.count 7` |
|
||||
| `flood.retry.path` | Maximum path hash count eligible for flood retry, or `off` to disable the gate. | `get flood.retry.path`, `set flood.retry.path <0-63/off>` | `set flood.retry.path 1` |
|
||||
| `flood.retry.advert` | Allows or blocks retry for node advert packets (`type=4`). Default is `off`. | `get flood.retry.advert`, `set flood.retry.advert on/off` | `set flood.retry.advert off` |
|
||||
| `flood.retry.prefixes` | Target prefixes. If set, only matching downstream echoes cancel a retry. | `get flood.retry.prefixes`, `set flood.retry.prefixes <prefixes/none/off>` | `set flood.retry.prefixes BEEBB0,425E5C` |
|
||||
| `flood.retry.ignore` | Ignored prefixes. In non-bridge retry, ignored last-hop echoes do not cancel retry. | `get flood.retry.ignore`, `set flood.retry.ignore <prefixes/none/off>` | `set flood.retry.ignore 71CE82,C7618C` |
|
||||
| `flood.retry.bridge` | Enables bucket-based bridge retry logic. | `get flood.retry.bridge`, `set flood.retry.bridge on/off` | `set flood.retry.bridge on` |
|
||||
| `flood.retry.bucket.<n>` | Shows one bridge bucket. Buckets are numbered `1`-`6`. | `get flood.retry.bucket.<n>` | `get flood.retry.bucket.1` |
|
||||
| `flood.retry.bucket` | Sets bridge bucket prefixes. | `set flood.retry.bucket <1-6> <prefixes/none/off>` | `set flood.retry.bucket 1 71CE82,C7618C` |
|
||||
|
||||
The shared retry preset sets these flood defaults:
|
||||
|
||||
| Preset | Retry count | Path gate |
|
||||
| --- | ---: | ---: |
|
||||
| `infra` | `1` | `1` |
|
||||
| `rooftop` | `3` | `2` |
|
||||
| `mobile` | `3` | `1` |
|
||||
|
||||
Example for path-gated retry:
|
||||
|
||||
```text
|
||||
set retry.preset rooftop
|
||||
set flood.retry.path 1
|
||||
set flood.retry.advert off
|
||||
set flood.retry.ignore 71CE82,C7618C
|
||||
```
|
||||
|
||||
## North South Buckets
|
||||
|
||||
Buckets describe groups of repeaters on different sides of this relay. Bucket
|
||||
numbers do not have built-in meanings; this example uses bucket `1` for North
|
||||
and bucket `2` for South.
|
||||
|
||||
```text
|
||||
North bucket 1
|
||||
+-----------------------+
|
||||
| A1B2C3 D4E5F6 |
|
||||
| North A North B |
|
||||
+-----------+-----------+
|
||||
|
|
||||
v
|
||||
+-----------+
|
||||
| This node |
|
||||
+-----------+
|
||||
^
|
||||
|
|
||||
+-----------+-----------+
|
||||
| 71CE82 C7618C |
|
||||
| South A South B |
|
||||
+-----------------------+
|
||||
South bucket 2
|
||||
```
|
||||
|
||||
Configure the buckets:
|
||||
|
||||
```text
|
||||
set flood.retry.bridge on
|
||||
set flood.retry.bucket 1 A1B2C3,D4E5F6
|
||||
set flood.retry.bucket 2 71CE82,C7618C
|
||||
set flood.retry.ignore none
|
||||
```
|
||||
|
||||
Packet heard from the North:
|
||||
|
||||
```text
|
||||
heard source
|
||||
|
|
||||
v
|
||||
+--------------+ retry targets
|
||||
| North bucket | -----> South bucket
|
||||
| bucket 1 | -----> Other fresh/unbucketed relays
|
||||
+--------------+
|
||||
```
|
||||
|
||||
Packet heard from the South:
|
||||
|
||||
```text
|
||||
heard source
|
||||
|
|
||||
v
|
||||
+--------------+ retry targets
|
||||
| South bucket | -----> North bucket
|
||||
| bucket 2 | -----> Other fresh/unbucketed relays
|
||||
+--------------+
|
||||
```
|
||||
|
||||
Packet heard from an unbucketed or pathless source:
|
||||
|
||||
```text
|
||||
heard source
|
||||
|
|
||||
v
|
||||
+--------------+ retry targets
|
||||
| Other bucket | -----> North bucket
|
||||
| implicit | -----> South bucket
|
||||
+--------------+
|
||||
```
|
||||
|
||||
Bridge retry stays eligible until every target bucket has been heard or
|
||||
`flood.retry.count` is exhausted. A configured bucket is a target only when at
|
||||
least one of its prefixes is fresh in `recent.repeater`. Prefixes in
|
||||
`flood.retry.ignore` never count as bucket hits.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If advert packets are still retrying:
|
||||
|
||||
```text
|
||||
get flood.retry.advert
|
||||
set flood.retry.advert off
|
||||
```
|
||||
|
||||
If ignored prefixes still appear in `flood retry good` logs:
|
||||
|
||||
```text
|
||||
get flood.retry.ignore
|
||||
set flood.retry.ignore <prefix>
|
||||
```
|
||||
|
||||
The ignored prefix must match the last hop shown as `heard=<prefix>`. For example, this log needs `C7618C` in the ignore list:
|
||||
|
||||
```text
|
||||
flood retry good (... path=7773D0>C7618C, heard=C7618C ...)
|
||||
```
|
||||
|
||||
If retries are too aggressive:
|
||||
|
||||
```text
|
||||
set flood.retry.count 1
|
||||
set flood.retry.path 1
|
||||
set direct.retry.count 4
|
||||
```
|
||||
|
||||
If retries are too sparse:
|
||||
|
||||
```text
|
||||
set flood.retry.count 7
|
||||
set flood.retry.path 2
|
||||
```
|
||||
@@ -233,6 +233,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
||||
file.read((uint8_t *)&_prefs.rx_boosted_gain, sizeof(_prefs.rx_boosted_gain)); // 89
|
||||
file.read((uint8_t *)_prefs.default_scope_name, sizeof(_prefs.default_scope_name)); // 90
|
||||
file.read((uint8_t *)_prefs.default_scope_key, sizeof(_prefs.default_scope_key)); // 121
|
||||
file.read((uint8_t *)&_prefs.radio_fem_rxgain, sizeof(_prefs.radio_fem_rxgain)); // 122
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -273,6 +274,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
|
||||
file.write((uint8_t *)&_prefs.rx_boosted_gain, sizeof(_prefs.rx_boosted_gain)); // 89
|
||||
file.write((uint8_t *)_prefs.default_scope_name, sizeof(_prefs.default_scope_name)); // 90
|
||||
file.write((uint8_t *)_prefs.default_scope_key, sizeof(_prefs.default_scope_key)); // 121
|
||||
file.write((uint8_t *)&_prefs.radio_fem_rxgain, sizeof(_prefs.radio_fem_rxgain)); // 122
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
@@ -61,6 +61,8 @@
|
||||
#define CMD_SEND_CHANNEL_DATA 62
|
||||
#define CMD_SET_DEFAULT_FLOOD_SCOPE 63
|
||||
#define CMD_GET_DEFAULT_FLOOD_SCOPE 64
|
||||
#define CMD_GET_RADIO_FEM_RXGAIN 65
|
||||
#define CMD_SET_RADIO_FEM_RXGAIN 66
|
||||
|
||||
// Stats sub-types for CMD_GET_STATS
|
||||
#define STATS_TYPE_CORE 0
|
||||
@@ -876,6 +878,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
_prefs.rx_boosted_gain = 1; // enabled by default
|
||||
#endif
|
||||
#endif
|
||||
_prefs.radio_fem_rxgain = 1;
|
||||
}
|
||||
|
||||
void MyMesh::begin(bool has_display) {
|
||||
@@ -925,6 +928,7 @@ void MyMesh::begin(bool has_display) {
|
||||
_prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, -9, MAX_LORA_TX_POWER);
|
||||
_prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1
|
||||
_prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours
|
||||
_prefs.radio_fem_rxgain = constrain(_prefs.radio_fem_rxgain, 0, 1);
|
||||
|
||||
#ifdef BLE_PIN_CODE // 123456 by default
|
||||
if (_prefs.ble_pin == 0) {
|
||||
@@ -954,6 +958,7 @@ void MyMesh::begin(bool has_display) {
|
||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_set_tx_power(_prefs.tx_power_dbm);
|
||||
radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain);
|
||||
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
|
||||
MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s",
|
||||
radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled");
|
||||
}
|
||||
@@ -1798,6 +1803,30 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_GET_RADIO_FEM_RXGAIN) {
|
||||
if (!board.canControlLoRaFemLna()) {
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||
} else {
|
||||
out_frame[0] = RESP_CODE_OK;
|
||||
uint8_t value = board.isLoRaFemLnaEnabled() ? 1 : 0;
|
||||
memcpy(&out_frame[1], &value, 1);
|
||||
_serial->writeFrame(out_frame, 2);
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_SET_RADIO_FEM_RXGAIN && len >= 2) {
|
||||
uint8_t value = cmd_frame[1];
|
||||
if (!board.canControlLoRaFemLna()) {
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||
} else if (value <= 1) {
|
||||
_prefs.radio_fem_rxgain = value;
|
||||
if (board.setLoRaFemLnaEnabled(value != 0)) {
|
||||
savePrefs();
|
||||
writeOKFrame();
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||
}
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_GET_ADVERT_PATH && len >= PUB_KEY_SIZE+2) {
|
||||
// FUTURE use: uint8_t reserved = cmd_frame[1];
|
||||
uint8_t *pub_key = &cmd_frame[2];
|
||||
@@ -2191,3 +2220,11 @@ bool MyMesh::advert() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// To check if there is pending work
|
||||
bool MyMesh::hasPendingWork() const {
|
||||
#if defined(WITH_BRIDGE)
|
||||
if (bridge.isRunning()) return true; // bridge needs WiFi radio, can't sleep
|
||||
#endif
|
||||
return _mgr->getOutboundTotal() > 0;
|
||||
}
|
||||
@@ -165,7 +165,7 @@ protected:
|
||||
|
||||
public:
|
||||
void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); }
|
||||
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
void applyGpsPrefs() {
|
||||
sensors.setSettingValue("gps", _prefs.gps_enabled ? "1" : "0");
|
||||
@@ -177,6 +177,9 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
// To check if there is pending work
|
||||
bool hasPendingWork() const;
|
||||
|
||||
private:
|
||||
void writeOKFrame();
|
||||
void writeErrFrame(uint8_t err_code);
|
||||
|
||||
@@ -29,9 +29,10 @@ struct NodePrefs { // persisted to file
|
||||
uint32_t gps_interval; // GPS read interval in seconds
|
||||
uint8_t autoadd_config; // bitmask for auto-add contacts config
|
||||
uint8_t rx_boosted_gain; // SX126x RX boosted gain mode (0=power saving, 1=boosted)
|
||||
uint8_t radio_fem_rxgain; // LoRa FEM RX gain setting
|
||||
uint8_t client_repeat;
|
||||
uint8_t path_hash_mode; // which path mode to use when sending
|
||||
uint8_t autoadd_max_hops; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64)
|
||||
char default_scope_name[31];
|
||||
uint8_t default_scope_key[16];
|
||||
};
|
||||
};
|
||||
@@ -2,6 +2,11 @@
|
||||
#include <Mesh.h>
|
||||
#include "MyMesh.h"
|
||||
|
||||
#ifdef ESP32_PLATFORM
|
||||
#include "esp_pm.h"
|
||||
#include "esp_bt.h"
|
||||
#endif
|
||||
|
||||
// Believe it or not, this std C function is busted on some platforms!
|
||||
static uint32_t _atoi(const char* sp) {
|
||||
uint32_t n = 0;
|
||||
@@ -42,8 +47,8 @@ static uint32_t _atoi(const char* sp) {
|
||||
#define TCP_PORT 5000
|
||||
#endif
|
||||
#elif defined(BLE_PIN_CODE)
|
||||
#include <helpers/esp32/SerialBLEInterface.h>
|
||||
SerialBLEInterface serial_interface;
|
||||
#include <helpers/esp32/SerialBLEInterface.h>
|
||||
SerialBLEInterface serial_interface;
|
||||
#elif defined(SERIAL_RX)
|
||||
#include <helpers/ArduinoSerialInterface.h>
|
||||
ArduinoSerialInterface serial_interface;
|
||||
@@ -220,6 +225,33 @@ void setup() {
|
||||
#ifdef DISPLAY_CLASS
|
||||
ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved
|
||||
#endif
|
||||
|
||||
#ifdef ESP32_PLATFORM
|
||||
// Enable BLE sleep
|
||||
esp_err_t errBLESleep = esp_bt_sleep_enable();
|
||||
if (errBLESleep == ESP_OK) {
|
||||
Serial.println("Bluetooth sleep enabled successfully");
|
||||
} else {
|
||||
Serial.printf("Bluetooth sleep enable failed: %s\n", esp_err_to_name(errBLESleep));
|
||||
}
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
esp_pm_config_esp32c3_t pm_config;
|
||||
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||
esp_pm_config_esp32s3_t pm_config;
|
||||
#elif CONFIG_IDF_TARGET_ESP32
|
||||
esp_pm_config_esp32_t pm_config;
|
||||
#endif
|
||||
|
||||
// Configure Power Management
|
||||
pm_config = { .max_freq_mhz = 80, .min_freq_mhz = 40, .light_sleep_enable = true };
|
||||
esp_err_t errPM = esp_pm_configure(&pm_config);
|
||||
if (errPM == ESP_OK) {
|
||||
Serial.println("Power Management configured successfully");
|
||||
} else {
|
||||
Serial.printf("Power Management failed to configure: %d\r\n", errPM);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void loop() {
|
||||
@@ -229,4 +261,14 @@ void loop() {
|
||||
ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
|
||||
if (!the_mesh.hasPendingWork()) {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
board.sleep(0); // nrf ignores seconds param, sleeps whenever possible
|
||||
#else if defined(ESP32_PLATFORM)
|
||||
if (!serial_interface.isReadBusy() && !serial_interface.isWriteBusy()) { // BLE is not busy
|
||||
vTaskDelay(pdMS_TO_TICKS(10)); // attempt to sleep
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -99,6 +99,15 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
RegionEntry* recv_pkt_region;
|
||||
TransportKey default_scope;
|
||||
RateLimiter discover_limiter, anon_limiter;
|
||||
struct FloodRetryBridgeState {
|
||||
uint8_t key[MAX_HASH_SIZE];
|
||||
uint8_t source_bucket;
|
||||
uint8_t target_mask;
|
||||
uint8_t heard_mask;
|
||||
uint8_t progress_marker;
|
||||
bool active;
|
||||
};
|
||||
mutable FloodRetryBridgeState flood_retry_bridge_states[MAX_FLOOD_RETRY_SLOTS];
|
||||
uint32_t pending_discover_tag;
|
||||
unsigned long pending_discover_until;
|
||||
bool region_load_active;
|
||||
@@ -110,8 +119,11 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
unsigned long set_radio_at, revert_radio_at;
|
||||
float pending_freq;
|
||||
float pending_bw;
|
||||
float active_bw; // live BW, including temporary radio overrides
|
||||
uint8_t pending_sf;
|
||||
uint8_t active_sf; // live SF, including temporary radio overrides
|
||||
uint8_t pending_cr;
|
||||
uint8_t active_cr; // live CR, including temporary radio overrides
|
||||
int matching_peer_indexes[MAX_CLIENTS];
|
||||
#if defined(WITH_RS232_BRIDGE)
|
||||
RS232Bridge bridge;
|
||||
@@ -119,6 +131,30 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
ESPNowBridge bridge;
|
||||
#endif
|
||||
|
||||
bool extractDirectRetryPrefix(const mesh::Packet* packet, uint8_t* prefix, uint8_t& prefix_len) const;
|
||||
int8_t getDirectRetryMinSNRX4() const;
|
||||
uint8_t getDirectRetryCodingRateForSNR(int8_t snr_x4) const;
|
||||
uint8_t getDirectRetryPreset() const;
|
||||
uint8_t getDirectRetryConfiguredMaxAttempts() const;
|
||||
uint32_t getDirectRetryAttemptStepMillis() const;
|
||||
bool hasFloodRetryPrefixes() const;
|
||||
bool floodRetryPrefixMatches(const mesh::Packet* packet) const;
|
||||
bool floodRetryLastHopMatches(const mesh::Packet* packet) const;
|
||||
bool floodRetryPrefixIgnored(const uint8_t* prefix, uint8_t prefix_len) const;
|
||||
bool floodRetryPrefixFresh(const uint8_t* prefix, uint8_t prefix_len) const;
|
||||
int floodRetryBucketForPrefix(const uint8_t* prefix, uint8_t prefix_len, bool require_fresh,
|
||||
bool include_other) const;
|
||||
int floodRetryBucketForPathHop(const uint8_t* prefix, uint8_t prefix_len, uint8_t hop,
|
||||
uint8_t progress_marker) const;
|
||||
int floodRetrySourceBucket(const mesh::Packet* packet) const;
|
||||
uint8_t floodRetryBridgeTargetMask(uint8_t source_bucket) const;
|
||||
uint8_t floodRetryBridgeHeardMask(const mesh::Packet* packet, uint8_t source_bucket,
|
||||
uint8_t progress_marker) const;
|
||||
FloodRetryBridgeState* floodRetryBridgeStateFor(const mesh::Packet* packet, bool create) const;
|
||||
void clearFloodRetryBridgeState(const mesh::Packet* packet);
|
||||
void refreshFloodRetryHeardRecent(const mesh::Packet* packet);
|
||||
void formatFloodRetryPath(char* dest, size_t dest_len, const mesh::Packet* packet) const;
|
||||
bool formatFloodRetryHeard(char* dest, size_t dest_len, const mesh::Packet* packet) const;
|
||||
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr);
|
||||
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood);
|
||||
uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
|
||||
@@ -146,6 +182,19 @@ protected:
|
||||
|
||||
uint32_t getRetransmitDelay(const mesh::Packet* packet) override;
|
||||
uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override;
|
||||
uint8_t getDefaultTxCodingRate() const override { return active_cr; }
|
||||
bool allowDirectRetry(const mesh::Packet* packet, const uint8_t* next_hop_hash, uint8_t next_hop_hash_len) const override;
|
||||
void configureDirectRetryPacket(mesh::Packet* retry, const mesh::Packet* original, uint8_t retry_attempt) override;
|
||||
uint32_t getDirectRetryEchoDelay(const mesh::Packet* packet) const override;
|
||||
uint8_t getDirectRetryMaxAttempts(const mesh::Packet* packet) const override;
|
||||
uint32_t getDirectRetryAttemptDelay(const mesh::Packet* packet, uint8_t attempt_idx) override;
|
||||
void onDirectRetryEvent(const char* event, const mesh::Packet* packet, uint32_t delay_millis, uint8_t retry_attempt) override;
|
||||
bool allowFloodRetry(const mesh::Packet* packet) const override;
|
||||
void onFloodRetryEvent(const char* event, const mesh::Packet* packet, uint32_t delay_millis, uint8_t retry_attempt) override;
|
||||
bool hasFloodRetryTargetPrefix(const mesh::Packet* packet) const override;
|
||||
uint8_t getFloodRetryMaxPathLength(const mesh::Packet* packet) const override;
|
||||
uint8_t getFloodRetryMaxAttempts(const mesh::Packet* packet) const override;
|
||||
bool isFloodRetryEchoTarget(const mesh::Packet* packet, uint8_t progress_marker) const override;
|
||||
|
||||
int getInterferenceThreshold() const override {
|
||||
return _prefs.interference_threshold;
|
||||
|
||||
@@ -41,7 +41,8 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi
|
||||
}
|
||||
|
||||
// v1.2.3 (1 Jan 2025)
|
||||
sprintf(_version_info, "%s (%s)", version, build_date);
|
||||
snprintf(_version_info, sizeof(_version_info), "%s (%s)", version, build_date);
|
||||
free(version);
|
||||
}
|
||||
|
||||
void UITask::renderCurrScreen() {
|
||||
|
||||
@@ -20,8 +20,7 @@ void halt() {
|
||||
static char command[160];
|
||||
|
||||
// For power saving
|
||||
unsigned long lastActive = 0; // mark last active time
|
||||
unsigned long nextSleepinSecs = 120; // next sleep in seconds. The first sleep (if enabled) is after 2 minutes from boot
|
||||
unsigned long POWERSAVING_FIRSTSLEEP_SECS = 120; // The first sleep (if enabled) from boot
|
||||
|
||||
#if defined(PIN_USER_BTN) && defined(_SEEED_SENSECAP_SOLAR_H_)
|
||||
static unsigned long userBtnDownAt = 0;
|
||||
@@ -40,9 +39,6 @@ void setup() {
|
||||
delay(5000);
|
||||
#endif
|
||||
|
||||
// For power saving
|
||||
lastActive = millis(); // mark last active time since boot
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
if (display.begin()) {
|
||||
display.startFrame();
|
||||
@@ -155,16 +151,12 @@ void loop() {
|
||||
rtc_clock.tick();
|
||||
|
||||
if (the_mesh.getNodePrefs()->powersaving_enabled && !the_mesh.hasPendingWork()) {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
#if defined(NRF52_PLATFORM)
|
||||
board.sleep(1800); // nrf ignores seconds param, sleeps whenever possible
|
||||
#else
|
||||
if (the_mesh.millisHasNowPassed(lastActive + nextSleepinSecs * 1000)) { // To check if it is time to sleep
|
||||
board.sleep(1800); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet
|
||||
lastActive = millis();
|
||||
nextSleepinSecs = 5; // Default: To work for 5s and sleep again
|
||||
} else {
|
||||
nextSleepinSecs += 5; // When there is pending work, to work another 5s
|
||||
#else
|
||||
if (the_mesh.millisHasNowPassed(POWERSAVING_FIRSTSLEEP_SECS * 1000)) { // To check if it is time to sleep
|
||||
board.sleep(30); // Sleep. Wake up after some seconds or when receiving a LoRa packet
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -652,6 +652,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.gps_enabled = 0;
|
||||
_prefs.gps_interval = 0;
|
||||
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
|
||||
_prefs.radio_fem_rxgain = 1;
|
||||
|
||||
next_post_idx = 0;
|
||||
next_client_idx = 0;
|
||||
@@ -693,6 +694,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
|
||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_set_tx_power(_prefs.tx_power_dbm);
|
||||
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
@@ -1022,3 +1024,11 @@ void MyMesh::loop() {
|
||||
uptime_millis += now - last_millis;
|
||||
last_millis = now;
|
||||
}
|
||||
|
||||
// To check if there is pending work
|
||||
bool MyMesh::hasPendingWork() const {
|
||||
#if defined(WITH_BRIDGE)
|
||||
if (bridge.isRunning()) return true; // bridge needs WiFi radio, can't sleep
|
||||
#endif
|
||||
return _mgr->getOutboundTotal() > 0;
|
||||
}
|
||||
@@ -222,4 +222,7 @@ public:
|
||||
void clearStats() override;
|
||||
void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
|
||||
void loop();
|
||||
|
||||
// To check if there is pending work
|
||||
bool hasPendingWork() const;
|
||||
};
|
||||
@@ -41,7 +41,8 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi
|
||||
}
|
||||
|
||||
// v1.2.3 (1 Jan 2025)
|
||||
sprintf(_version_info, "%s (%s)", version, build_date);
|
||||
snprintf(_version_info, sizeof(_version_info), "%s (%s)", version, build_date);
|
||||
free(version);
|
||||
}
|
||||
|
||||
void UITask::renderCurrScreen() {
|
||||
|
||||
@@ -18,6 +18,9 @@ void halt() {
|
||||
|
||||
static char command[MAX_POST_TEXT_LEN+1];
|
||||
|
||||
// For power saving
|
||||
unsigned long POWERSAVING_FIRSTSLEEP_SECS = 120; // The first sleep (if enabled) from boot
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(1000);
|
||||
@@ -113,4 +116,14 @@ void loop() {
|
||||
ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
|
||||
if (the_mesh.getNodePrefs()->powersaving_enabled && !the_mesh.hasPendingWork()) {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
board.sleep(1800); // nrf ignores seconds param, sleeps whenever possible
|
||||
#else
|
||||
if (the_mesh.millisHasNowPassed(POWERSAVING_FIRSTSLEEP_SECS * 1000)) { // To check if it is time to sleep
|
||||
board.sleep(1800); // Sleep. Wake up after 30 minutes or when receiving a LoRa packet
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -731,6 +731,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
|
||||
_prefs.gps_enabled = 0;
|
||||
_prefs.gps_interval = 0;
|
||||
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
|
||||
_prefs.radio_fem_rxgain = 1;
|
||||
|
||||
memset(default_scope.key, 0, sizeof(default_scope.key));
|
||||
}
|
||||
@@ -766,6 +767,7 @@ void SensorMesh::begin(FILESYSTEM* fs) {
|
||||
|
||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_set_tx_power(_prefs.tx_power_dbm);
|
||||
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
|
||||
@@ -41,7 +41,8 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi
|
||||
}
|
||||
|
||||
// v1.2.3 (1 Jan 2025)
|
||||
sprintf(_version_info, "%s (%s)", version, build_date);
|
||||
snprintf(_version_info, sizeof(_version_info), "%s (%s)", version, build_date);
|
||||
free(version);
|
||||
}
|
||||
|
||||
void UITask::renderCurrScreen() {
|
||||
|
||||
+45
-7
@@ -52,6 +52,13 @@ void Dispatcher::updateTxBudget() {
|
||||
}
|
||||
}
|
||||
|
||||
void Dispatcher::restoreOutboundCodingRate() {
|
||||
if (outbound_restore_cr != 0) {
|
||||
_radio->setCodingRate(outbound_restore_cr);
|
||||
outbound_restore_cr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int Dispatcher::calcRxDelay(float score, uint32_t air_time) const {
|
||||
return (int) ((pow(10, 0.85f - score) - 1.0) * air_time);
|
||||
}
|
||||
@@ -105,7 +112,9 @@ void Dispatcher::loop() {
|
||||
}
|
||||
|
||||
_radio->onSendFinished();
|
||||
restoreOutboundCodingRate();
|
||||
logTx(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len);
|
||||
onSendComplete(outbound);
|
||||
if (outbound->isRouteFlood()) {
|
||||
n_sent_flood++;
|
||||
} else {
|
||||
@@ -117,7 +126,9 @@ void Dispatcher::loop() {
|
||||
MESH_DEBUG_PRINTLN("%s Dispatcher::loop(): WARNING: outbound packed send timed out!", getLogDateTime());
|
||||
|
||||
_radio->onSendFinished();
|
||||
restoreOutboundCodingRate();
|
||||
logTxFail(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len);
|
||||
onSendFail(outbound);
|
||||
|
||||
releasePacket(outbound); // return to pool
|
||||
outbound = NULL;
|
||||
@@ -148,6 +159,7 @@ void Dispatcher::loop() {
|
||||
bool Dispatcher::tryParsePacket(Packet* pkt, const uint8_t* raw, int len) {
|
||||
int i = 0;
|
||||
|
||||
pkt->tx_cr = 0;
|
||||
pkt->header = raw[i++];
|
||||
if (pkt->getPayloadVer() > PAYLOAD_VER_1) {
|
||||
MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): unsupported packet version", getLogDateTime());
|
||||
@@ -267,7 +279,10 @@ void Dispatcher::processRecvPacket(Packet* pkt) {
|
||||
uint8_t priority = (action >> 24) - 1;
|
||||
uint32_t _delay = action & 0xFFFFFF;
|
||||
|
||||
_mgr->queueOutbound(pkt, priority, futureMillis(_delay));
|
||||
if (!queueOutboundPacket(pkt, priority, _delay)) {
|
||||
onSendFail(pkt);
|
||||
releasePacket(pkt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,18 +333,32 @@ void Dispatcher::checkSend() {
|
||||
|
||||
if (len + outbound->payload_len > MAX_TRANS_UNIT) {
|
||||
MESH_DEBUG_PRINTLN("%s Dispatcher::checkSend(): FATAL: Invalid packet queued... too long, len=%d", getLogDateTime(), len + outbound->payload_len);
|
||||
_mgr->free(outbound);
|
||||
onSendFail(outbound);
|
||||
releasePacket(outbound);
|
||||
outbound = NULL;
|
||||
} else {
|
||||
memcpy(&raw[len], outbound->payload, outbound->payload_len); len += outbound->payload_len;
|
||||
|
||||
uint32_t max_airtime = _radio->getEstAirtimeFor(len)*3/2;
|
||||
outbound_restore_cr = 0;
|
||||
uint8_t default_cr = getDefaultTxCodingRate();
|
||||
if (outbound->tx_cr >= 4 && outbound->tx_cr <= 8 && default_cr >= 4 && default_cr <= 8
|
||||
&& outbound->tx_cr != default_cr) {
|
||||
if (_radio->setCodingRate(outbound->tx_cr)) {
|
||||
outbound_restore_cr = default_cr;
|
||||
max_airtime = _radio->getEstAirtimeFor(len)*3/2;
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("%s Dispatcher::checkSend(): WARN: failed to set packet CR%d", getLogDateTime(), (uint32_t)outbound->tx_cr);
|
||||
}
|
||||
}
|
||||
outbound_start = _ms->getMillis();
|
||||
bool success = _radio->startSendRaw(raw, len);
|
||||
if (!success) {
|
||||
MESH_DEBUG_PRINTLN("%s Dispatcher::loop(): ERROR: send start failed!", getLogDateTime());
|
||||
|
||||
restoreOutboundCodingRate();
|
||||
logTxFail(outbound, outbound->getRawLength());
|
||||
onSendFail(outbound);
|
||||
|
||||
releasePacket(outbound); // return to pool
|
||||
outbound = NULL;
|
||||
@@ -359,6 +388,7 @@ Packet* Dispatcher::obtainNewPacket() {
|
||||
} else {
|
||||
pkt->payload_len = pkt->path_len = 0;
|
||||
pkt->_snr = 0;
|
||||
pkt->tx_cr = 0;
|
||||
}
|
||||
return pkt;
|
||||
}
|
||||
@@ -367,13 +397,21 @@ void Dispatcher::releasePacket(Packet* packet) {
|
||||
_mgr->free(packet);
|
||||
}
|
||||
|
||||
void Dispatcher::sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis) {
|
||||
bool Dispatcher::queueOutboundPacket(Packet* packet, uint8_t priority, uint32_t delay_millis) {
|
||||
if (!Packet::isValidPathLen(packet->path_len) || packet->payload_len > MAX_PACKET_PAYLOAD) {
|
||||
MESH_DEBUG_PRINTLN("%s Dispatcher::sendPacket(): ERROR: invalid packet... path_len=%d, payload_len=%d", getLogDateTime(), (uint32_t) packet->path_len, (uint32_t) packet->payload_len);
|
||||
_mgr->free(packet);
|
||||
} else {
|
||||
_mgr->queueOutbound(packet, priority, futureMillis(delay_millis));
|
||||
return false;
|
||||
}
|
||||
return _mgr->queueOutbound(packet, priority, futureMillis(delay_millis));
|
||||
}
|
||||
|
||||
bool Dispatcher::sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis) {
|
||||
if (!queueOutboundPacket(packet, priority, delay_millis)) {
|
||||
onSendFail(packet);
|
||||
releasePacket(packet);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Utility function -- handles the case where millis() wraps around back to zero
|
||||
@@ -386,4 +424,4 @@ unsigned long Dispatcher::futureMillis(int millis_from_now) const {
|
||||
return _ms->getMillis() + millis_from_now;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+16
-2
@@ -46,6 +46,12 @@ public:
|
||||
*/
|
||||
virtual bool startSendRaw(const uint8_t* bytes, int len) = 0;
|
||||
|
||||
/**
|
||||
* \brief Sets LoRa coding rate for subsequent transmits/receives.
|
||||
* \returns true if the radio accepted the coding rate.
|
||||
*/
|
||||
virtual bool setCodingRate(uint8_t cr) { return false; }
|
||||
|
||||
/**
|
||||
* \returns true if the previous 'startSendRaw()' completed successfully.
|
||||
*/
|
||||
@@ -87,7 +93,7 @@ public:
|
||||
virtual Packet* allocNew() = 0;
|
||||
virtual void free(Packet* packet) = 0;
|
||||
|
||||
virtual void queueOutbound(Packet* packet, uint8_t priority, uint32_t scheduled_for) = 0;
|
||||
virtual bool queueOutbound(Packet* packet, uint8_t priority, uint32_t scheduled_for) = 0;
|
||||
virtual Packet* getNextOutbound(uint32_t now) = 0; // by priority
|
||||
virtual int getOutboundCount(uint32_t now) const = 0;
|
||||
virtual int getOutboundTotal() const = 0;
|
||||
@@ -116,6 +122,7 @@ typedef uint32_t DispatcherAction;
|
||||
class Dispatcher {
|
||||
Packet* outbound; // current outbound packet
|
||||
unsigned long outbound_expiry, outbound_start, total_air_time, rx_air_time;
|
||||
uint8_t outbound_restore_cr;
|
||||
unsigned long next_tx_time;
|
||||
unsigned long cad_busy_start;
|
||||
unsigned long radio_nonrx_start;
|
||||
@@ -128,6 +135,7 @@ class Dispatcher {
|
||||
unsigned long duty_cycle_window_ms;
|
||||
|
||||
void processRecvPacket(Packet* pkt);
|
||||
void restoreOutboundCodingRate();
|
||||
void updateTxBudget();
|
||||
|
||||
protected:
|
||||
@@ -140,6 +148,7 @@ protected:
|
||||
: _radio(&radio), _ms(&ms), _mgr(&mgr)
|
||||
{
|
||||
outbound = NULL;
|
||||
outbound_restore_cr = 0;
|
||||
total_air_time = rx_air_time = 0;
|
||||
next_tx_time = ms.getMillis();
|
||||
cad_busy_start = 0;
|
||||
@@ -159,15 +168,20 @@ protected:
|
||||
virtual void logRx(Packet* packet, int len, float score) { } // hooks for custom logging
|
||||
virtual void logTx(Packet* packet, int len) { }
|
||||
virtual void logTxFail(Packet* packet, int len) { }
|
||||
virtual void onSendComplete(Packet* packet) { }
|
||||
virtual void onSendFail(Packet* packet) { }
|
||||
virtual const char* getLogDateTime() { return ""; }
|
||||
|
||||
virtual float getAirtimeBudgetFactor() const;
|
||||
virtual int calcRxDelay(float score, uint32_t air_time) const;
|
||||
virtual uint32_t getCADFailRetryDelay() const;
|
||||
virtual uint32_t getCADFailMaxDuration() const;
|
||||
virtual uint8_t getDefaultTxCodingRate() const { return 0; }
|
||||
virtual int getInterferenceThreshold() const { return 0; } // disabled by default
|
||||
virtual int getAGCResetInterval() const { return 0; } // disabled by default
|
||||
virtual unsigned long getDutyCycleWindowMs() const { return 3600000; }
|
||||
const Packet* getOutboundInFlight() const { return outbound; }
|
||||
bool queueOutboundPacket(Packet* packet, uint8_t priority, uint32_t delay_millis);
|
||||
|
||||
public:
|
||||
void begin();
|
||||
@@ -175,7 +189,7 @@ public:
|
||||
|
||||
Packet* obtainNewPacket();
|
||||
void releasePacket(Packet* packet);
|
||||
void sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis=0);
|
||||
bool sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis=0);
|
||||
|
||||
unsigned long getTotalAirTime() const { return total_air_time; }
|
||||
unsigned long getReceiveAirTime() const {return rx_air_time; }
|
||||
|
||||
+739
-11
@@ -3,12 +3,127 @@
|
||||
|
||||
namespace mesh {
|
||||
|
||||
static const uint8_t DIRECT_RETRY_MAX_ATTEMPTS_DEFAULT = 15;
|
||||
static const uint8_t DIRECT_RETRY_MAX_ATTEMPTS_HARD_MAX = 15;
|
||||
static const uint8_t FLOOD_RETRY_MAX_ATTEMPTS_DEFAULT = 3;
|
||||
static const uint8_t FLOOD_RETRY_MAX_ATTEMPTS_HARD_MAX = 15;
|
||||
|
||||
static uint8_t decodeTraceHashSize(uint8_t flags, uint8_t route_bytes) {
|
||||
uint8_t code = flags & 0x03;
|
||||
uint8_t size_pow2 = (uint8_t)(1U << code); // legacy TRACE interpretation
|
||||
uint8_t size_linear = (uint8_t)(code + 1U); // packed-size interpretation (1..4)
|
||||
|
||||
bool pow2_ok = size_pow2 > 0 && (route_bytes % size_pow2) == 0;
|
||||
bool linear_ok = size_linear > 0 && (route_bytes % size_linear) == 0;
|
||||
|
||||
if (pow2_ok && !linear_ok) {
|
||||
return size_pow2;
|
||||
}
|
||||
if (linear_ok && !pow2_ok) {
|
||||
return size_linear;
|
||||
}
|
||||
if (pow2_ok) {
|
||||
return size_pow2;
|
||||
}
|
||||
return size_linear;
|
||||
}
|
||||
|
||||
void Mesh::begin() {
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
_direct_retries[i].packet = NULL;
|
||||
_direct_retries[i].trigger_packet = NULL;
|
||||
_direct_retries[i].retry_started_at = 0;
|
||||
_direct_retries[i].echo_wait_started_at = 0;
|
||||
_direct_retries[i].retry_at = 0;
|
||||
_direct_retries[i].retry_delay = 0;
|
||||
_direct_retries[i].retry_attempts_sent = 0;
|
||||
_direct_retries[i].priority = 0;
|
||||
_direct_retries[i].progress_marker = 0;
|
||||
_direct_retries[i].expect_path_growth = false;
|
||||
_direct_retries[i].waiting_final_echo = false;
|
||||
_direct_retries[i].queued = false;
|
||||
_direct_retries[i].active = false;
|
||||
}
|
||||
for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) {
|
||||
_flood_retries[i].packet = NULL;
|
||||
_flood_retries[i].trigger_packet = NULL;
|
||||
_flood_retries[i].retry_started_at = 0;
|
||||
_flood_retries[i].retry_at = 0;
|
||||
_flood_retries[i].retry_delay = 0;
|
||||
_flood_retries[i].retry_attempts_sent = 0;
|
||||
_flood_retries[i].priority = 0;
|
||||
_flood_retries[i].progress_marker = 0;
|
||||
_flood_retries[i].waiting_final_echo = false;
|
||||
_flood_retries[i].queued = false;
|
||||
_flood_retries[i].active = false;
|
||||
}
|
||||
Dispatcher::begin();
|
||||
}
|
||||
|
||||
void Mesh::loop() {
|
||||
Dispatcher::loop();
|
||||
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (!_direct_retries[i].active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_direct_retries[i].waiting_final_echo) {
|
||||
if (!millisHasNowPassed(_direct_retries[i].retry_at)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t elapsed_millis = _direct_retries[i].retry_started_at == 0
|
||||
? 0
|
||||
: (uint32_t)(_ms->getMillis() - _direct_retries[i].retry_started_at);
|
||||
onDirectRetryEvent("failed_all_tries", _direct_retries[i].packet, elapsed_millis, _direct_retries[i].retry_attempts_sent);
|
||||
onDirectRetryEvent("failure", _direct_retries[i].packet, elapsed_millis, _direct_retries[i].retry_attempts_sent);
|
||||
clearDirectRetrySlot(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_direct_retries[i].queued || !millisHasNowPassed(_direct_retries[i].retry_at)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isDirectRetryQueued(_direct_retries[i].packet)) {
|
||||
if (_direct_retries[i].packet == getOutboundInFlight()) {
|
||||
continue; // currently transmitting; keep slot until onSendComplete/onSendFail emits event
|
||||
}
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) {
|
||||
if (!_flood_retries[i].active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_flood_retries[i].waiting_final_echo) {
|
||||
if (!millisHasNowPassed(_flood_retries[i].retry_at)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t elapsed_millis = _flood_retries[i].retry_started_at == 0
|
||||
? 0
|
||||
: (uint32_t)(_ms->getMillis() - _flood_retries[i].retry_started_at);
|
||||
onFloodRetryEvent("failed_all_tries", _flood_retries[i].packet, elapsed_millis, _flood_retries[i].retry_attempts_sent);
|
||||
onFloodRetryEvent("failure", _flood_retries[i].packet, elapsed_millis, _flood_retries[i].retry_attempts_sent);
|
||||
clearFloodRetrySlot(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_flood_retries[i].queued || !millisHasNowPassed(_flood_retries[i].retry_at)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isFloodRetryQueued(_flood_retries[i].packet)) {
|
||||
if (_flood_retries[i].packet == getOutboundInFlight()) {
|
||||
continue;
|
||||
}
|
||||
clearFloodRetrySlot(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Mesh::allowPacketForward(const mesh::Packet* packet) {
|
||||
@@ -22,10 +137,56 @@ uint32_t Mesh::getRetransmitDelay(const mesh::Packet* packet) {
|
||||
uint32_t Mesh::getDirectRetransmitDelay(const Packet* packet) {
|
||||
return 0; // by default, no delay
|
||||
}
|
||||
bool Mesh::allowDirectRetry(const Packet* packet, const uint8_t* next_hop_hash, uint8_t next_hop_hash_len) const {
|
||||
return false;
|
||||
}
|
||||
uint32_t Mesh::getDirectRetryEchoDelay(const Packet* packet) const {
|
||||
// Keep the base fallback aligned with the repeater's minimum retry wait.
|
||||
return 200;
|
||||
}
|
||||
uint8_t Mesh::getDirectRetryMaxAttempts(const Packet* packet) const {
|
||||
return DIRECT_RETRY_MAX_ATTEMPTS_DEFAULT;
|
||||
}
|
||||
uint32_t Mesh::getDirectRetryAttemptDelay(const Packet* packet, uint8_t attempt_idx) {
|
||||
uint32_t base = getDirectRetryEchoDelay(packet);
|
||||
// Keep the historical linear spacing while allowing the base wait to vary by platform/profile.
|
||||
return base + ((uint32_t)attempt_idx * 100UL);
|
||||
}
|
||||
bool Mesh::allowFloodRetry(const Packet* packet) const {
|
||||
return true;
|
||||
}
|
||||
bool Mesh::hasFloodRetryTargetPrefix(const Packet* packet) const {
|
||||
return false;
|
||||
}
|
||||
uint8_t Mesh::getFloodRetryMaxPathLength(const Packet* packet) const {
|
||||
return 2;
|
||||
}
|
||||
uint8_t Mesh::getFloodRetryMaxAttempts(const Packet* packet) const {
|
||||
return FLOOD_RETRY_MAX_ATTEMPTS_DEFAULT;
|
||||
}
|
||||
uint32_t Mesh::getFloodRetryAttemptDelay(const Packet* packet, uint8_t attempt_idx) {
|
||||
if (packet == NULL) {
|
||||
return _radio->getEstAirtimeFor(MAX_TRANS_UNIT);
|
||||
}
|
||||
|
||||
uint32_t max_packet_airtime = _radio->getEstAirtimeFor(MAX_TRANS_UNIT);
|
||||
uint32_t packet_airtime = _radio->getEstAirtimeFor(packet->getRawLength());
|
||||
return max_packet_airtime + (20UL * packet_airtime);
|
||||
}
|
||||
uint8_t Mesh::getExtraAckTransmitCount() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Mesh::onSendComplete(Packet* packet) {
|
||||
armDirectRetryOnSendComplete(packet);
|
||||
armFloodRetryOnSendComplete(packet);
|
||||
}
|
||||
|
||||
void Mesh::onSendFail(Packet* packet) {
|
||||
clearPendingDirectRetryOnSendFail(packet);
|
||||
clearPendingFloodRetryOnSendFail(packet);
|
||||
}
|
||||
|
||||
uint32_t Mesh::getCADFailRetryDelay() const {
|
||||
return _rng->nextInt(1, 4)*120;
|
||||
}
|
||||
@@ -39,6 +200,12 @@ int Mesh::searchChannelsByHash(const uint8_t* hash, GroupChannel channels[], int
|
||||
}
|
||||
|
||||
DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
if (pkt->isRouteDirect()) {
|
||||
cancelDirectRetryOnEcho(pkt);
|
||||
} else if (pkt->isRouteFlood()) {
|
||||
cancelFloodRetryOnEcho(pkt);
|
||||
}
|
||||
|
||||
if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_TRACE) {
|
||||
if (pkt->path_len < MAX_PATH_SIZE) {
|
||||
uint8_t i = 0;
|
||||
@@ -47,17 +214,19 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
uint32_t auth_code;
|
||||
memcpy(&auth_code, &pkt->payload[i], 4); i += 4;
|
||||
uint8_t flags = pkt->payload[i++];
|
||||
uint8_t path_sz = flags & 0x03; // NEW v1.11+: lower 2 bits is path hash size
|
||||
|
||||
uint8_t len = pkt->payload_len - i;
|
||||
uint8_t offset = pkt->path_len << path_sz;
|
||||
uint8_t hash_size = decodeTraceHashSize(flags, len);
|
||||
uint16_t offset = (uint16_t)pkt->path_len * (uint16_t)hash_size;
|
||||
if (offset >= len) { // TRACE has reached end of given path
|
||||
onTraceRecv(pkt, trace_tag, auth_code, flags, pkt->path, &pkt->payload[i], len);
|
||||
} else if (self_id.isHashMatch(&pkt->payload[i + offset], 1 << path_sz) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) {
|
||||
} else if (hash_size > 0 && offset + hash_size <= len
|
||||
&& self_id.isHashMatch(&pkt->payload[i + offset], hash_size)
|
||||
&& allowPacketForward(pkt) && !_tables->hasSeen(pkt)) {
|
||||
// append SNR (Not hash!)
|
||||
pkt->path[pkt->path_len++] = (int8_t) (pkt->getSNR()*4);
|
||||
|
||||
uint32_t d = getDirectRetransmitDelay(pkt);
|
||||
maybeScheduleDirectRetry(pkt, 5);
|
||||
return ACTION_RETRANSMIT_DELAYED(5, d); // schedule with priority 5 (for now), maybe make configurable?
|
||||
}
|
||||
}
|
||||
@@ -98,6 +267,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
removeSelfFromPath(pkt);
|
||||
|
||||
uint32_t d = getDirectRetransmitDelay(pkt);
|
||||
maybeScheduleDirectRetry(pkt, 0);
|
||||
return ACTION_RETRANSMIT_DELAYED(0, d); // Routed traffic is HIGHEST priority
|
||||
}
|
||||
}
|
||||
@@ -334,8 +504,10 @@ DispatcherAction Mesh::routeRecvPacket(Packet* packet) {
|
||||
packet->setPathHashCount(n + 1);
|
||||
|
||||
uint32_t d = getRetransmitDelay(packet);
|
||||
uint8_t priority = packet->getPathHashCount();
|
||||
maybeScheduleFloodRetry(packet, priority);
|
||||
// as this propagates outwards, give it lower and lower priority
|
||||
return ACTION_RETRANSMIT_DELAYED(packet->getPathHashCount(), d); // give priority to closer sources, than ones further away
|
||||
return ACTION_RETRANSMIT_DELAYED(priority, d); // give priority to closer sources, than ones further away
|
||||
}
|
||||
return ACTION_RELEASE;
|
||||
}
|
||||
@@ -372,6 +544,7 @@ void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) {
|
||||
a1->path_len = Packet::copyPath(a1->path, packet->path, packet->path_len);
|
||||
a1->header &= ~PH_ROUTE_MASK;
|
||||
a1->header |= ROUTE_TYPE_DIRECT;
|
||||
maybeScheduleDirectRetry(a1, 0);
|
||||
sendPacket(a1, 0, delay_millis);
|
||||
}
|
||||
extra--;
|
||||
@@ -382,11 +555,565 @@ void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) {
|
||||
a2->path_len = Packet::copyPath(a2->path, packet->path, packet->path_len);
|
||||
a2->header &= ~PH_ROUTE_MASK;
|
||||
a2->header |= ROUTE_TYPE_DIRECT;
|
||||
maybeScheduleDirectRetry(a2, 0);
|
||||
sendPacket(a2, 0, delay_millis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Mesh::clearDirectRetrySlot(int idx) {
|
||||
if (_direct_retries[idx].waiting_final_echo && _direct_retries[idx].packet != NULL) {
|
||||
releasePacket(_direct_retries[idx].packet);
|
||||
}
|
||||
_direct_retries[idx].packet = NULL;
|
||||
_direct_retries[idx].trigger_packet = NULL;
|
||||
_direct_retries[idx].retry_started_at = 0;
|
||||
_direct_retries[idx].echo_wait_started_at = 0;
|
||||
_direct_retries[idx].retry_at = 0;
|
||||
_direct_retries[idx].retry_delay = 0;
|
||||
_direct_retries[idx].retry_attempts_sent = 0;
|
||||
_direct_retries[idx].priority = 0;
|
||||
_direct_retries[idx].progress_marker = 0;
|
||||
_direct_retries[idx].expect_path_growth = false;
|
||||
_direct_retries[idx].waiting_final_echo = false;
|
||||
_direct_retries[idx].queued = false;
|
||||
_direct_retries[idx].active = false;
|
||||
}
|
||||
|
||||
bool Mesh::isDirectRetryQueued(const Packet* packet) const {
|
||||
for (int i = 0; i < _mgr->getOutboundTotal(); i++) {
|
||||
if (_mgr->getOutboundByIdx(i) == packet) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Mesh::calculateDirectRetryKey(const Packet* packet, uint8_t* dest_key) const {
|
||||
uint8_t type = packet->getPayloadType();
|
||||
Utils::sha256(dest_key, MAX_HASH_SIZE, &type, 1, packet->payload, packet->payload_len);
|
||||
}
|
||||
|
||||
bool Mesh::cancelDirectRetryOnEcho(const Packet* packet) {
|
||||
uint8_t recv_key[MAX_HASH_SIZE];
|
||||
calculateDirectRetryKey(packet, recv_key);
|
||||
|
||||
bool cleared = false;
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (!_direct_retries[i].active || memcmp(recv_key, _direct_retries[i].retry_key, MAX_HASH_SIZE) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool is_echo = _direct_retries[i].expect_path_growth
|
||||
? packet->path_len > _direct_retries[i].progress_marker
|
||||
: packet->getPathHashCount() < _direct_retries[i].progress_marker;
|
||||
if (!is_echo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int8_t echo_snr_x4 = packet->_snr;
|
||||
if (_direct_retries[i].queued || _direct_retries[i].waiting_final_echo) {
|
||||
if (_direct_retries[i].packet != NULL) {
|
||||
// Success quality comes from the received downstream echo, not the original upstream RX.
|
||||
_direct_retries[i].packet->_snr = echo_snr_x4;
|
||||
}
|
||||
uint32_t echo_millis = _direct_retries[i].echo_wait_started_at == 0
|
||||
? 0
|
||||
: (uint32_t)(_ms->getMillis() - _direct_retries[i].echo_wait_started_at);
|
||||
uint8_t retry_attempt = _direct_retries[i].waiting_final_echo
|
||||
? _direct_retries[i].retry_attempts_sent
|
||||
: _direct_retries[i].retry_attempts_sent + 1;
|
||||
onDirectRetryEvent("good", _direct_retries[i].packet, echo_millis, retry_attempt);
|
||||
if (_direct_retries[i].queued) {
|
||||
for (int j = 0; j < _mgr->getOutboundTotal(); j++) {
|
||||
if (_mgr->getOutboundByIdx(j) == _direct_retries[i].packet) {
|
||||
Packet* pending = _mgr->removeOutboundByIdx(j);
|
||||
if (pending) {
|
||||
releasePacket(pending);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
clearDirectRetrySlot(i);
|
||||
} else {
|
||||
if (_direct_retries[i].trigger_packet != NULL) {
|
||||
_direct_retries[i].trigger_packet->_snr = echo_snr_x4;
|
||||
}
|
||||
uint32_t echo_millis = _direct_retries[i].echo_wait_started_at == 0
|
||||
? 0
|
||||
: (uint32_t)(_ms->getMillis() - _direct_retries[i].echo_wait_started_at);
|
||||
onDirectRetryEvent("good", _direct_retries[i].trigger_packet, echo_millis, _direct_retries[i].retry_attempts_sent + 1);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
cleared = true;
|
||||
}
|
||||
|
||||
return cleared;
|
||||
}
|
||||
|
||||
void Mesh::armDirectRetryOnSendComplete(const Packet* packet) {
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (!_direct_retries[i].active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_direct_retries[i].queued) {
|
||||
if (_direct_retries[i].packet == packet) {
|
||||
// The retry packet itself just finished transmitting; Dispatcher will release it after this hook.
|
||||
uint32_t elapsed_millis = _direct_retries[i].retry_started_at == 0
|
||||
? 0
|
||||
: (uint32_t)(_ms->getMillis() - _direct_retries[i].retry_started_at);
|
||||
onDirectRetryEvent("resent", packet, elapsed_millis, _direct_retries[i].retry_attempts_sent + 1);
|
||||
_direct_retries[i].echo_wait_started_at = _ms->getMillis();
|
||||
_direct_retries[i].retry_attempts_sent++;
|
||||
uint8_t max_attempts = getDirectRetryMaxAttempts(packet);
|
||||
if (max_attempts < 1) {
|
||||
max_attempts = 1;
|
||||
} else if (max_attempts > DIRECT_RETRY_MAX_ATTEMPTS_HARD_MAX) {
|
||||
max_attempts = DIRECT_RETRY_MAX_ATTEMPTS_HARD_MAX;
|
||||
}
|
||||
if (_direct_retries[i].retry_attempts_sent >= max_attempts) {
|
||||
Packet* final_wait = obtainNewPacket();
|
||||
if (final_wait == NULL) {
|
||||
onDirectRetryEvent("dropped_no_packet", packet, elapsed_millis, _direct_retries[i].retry_attempts_sent);
|
||||
onDirectRetryEvent("failure", packet, elapsed_millis, _direct_retries[i].retry_attempts_sent);
|
||||
clearDirectRetrySlot(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
*final_wait = *packet;
|
||||
_direct_retries[i].packet = final_wait;
|
||||
_direct_retries[i].retry_at = futureMillis(_direct_retries[i].retry_delay);
|
||||
_direct_retries[i].waiting_final_echo = true;
|
||||
_direct_retries[i].queued = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
Packet* retry = obtainNewPacket();
|
||||
if (retry == NULL) {
|
||||
onDirectRetryEvent("dropped_no_packet", packet, elapsed_millis, _direct_retries[i].retry_attempts_sent + 1);
|
||||
onDirectRetryEvent("failure", packet, elapsed_millis, _direct_retries[i].retry_attempts_sent + 1);
|
||||
clearDirectRetrySlot(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
*retry = *packet;
|
||||
retry->tx_cr = 0;
|
||||
uint8_t retry_attempt = _direct_retries[i].retry_attempts_sent + 1;
|
||||
configureDirectRetryPacket(retry, packet, retry_attempt);
|
||||
uint32_t retry_delay = getDirectRetryAttemptDelay(packet, _direct_retries[i].retry_attempts_sent);
|
||||
if (queueOutboundPacket(retry, _direct_retries[i].priority, retry_delay)) {
|
||||
_direct_retries[i].packet = retry;
|
||||
_direct_retries[i].retry_delay = retry_delay;
|
||||
_direct_retries[i].retry_at = futureMillis(retry_delay);
|
||||
_direct_retries[i].waiting_final_echo = false;
|
||||
onDirectRetryEvent("queued", retry, retry_delay, retry_attempt);
|
||||
} else {
|
||||
onDirectRetryEvent("dropped_queue_full", retry, retry_delay, retry_attempt);
|
||||
onDirectRetryEvent("failure", retry, elapsed_millis, retry_attempt);
|
||||
releasePacket(retry);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_direct_retries[i].trigger_packet != packet) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allocate the retry packet only after TX-complete so busy repeaters do not reserve pool slots early.
|
||||
Packet* retry = obtainNewPacket();
|
||||
if (retry == NULL) {
|
||||
onDirectRetryEvent("dropped_no_packet", packet, _direct_retries[i].retry_delay, 1);
|
||||
onDirectRetryEvent("failure", packet, 0, 1);
|
||||
clearDirectRetrySlot(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
*retry = *packet;
|
||||
retry->tx_cr = 0;
|
||||
configureDirectRetryPacket(retry, packet, 1);
|
||||
|
||||
// Start the echo wait only after the initial direct transmission actually completed.
|
||||
if (queueOutboundPacket(retry, _direct_retries[i].priority, _direct_retries[i].retry_delay)) {
|
||||
unsigned long now = _ms->getMillis();
|
||||
_direct_retries[i].packet = retry;
|
||||
_direct_retries[i].trigger_packet = NULL;
|
||||
_direct_retries[i].queued = true;
|
||||
_direct_retries[i].waiting_final_echo = false;
|
||||
_direct_retries[i].retry_at = futureMillis(_direct_retries[i].retry_delay);
|
||||
_direct_retries[i].retry_started_at = now;
|
||||
_direct_retries[i].echo_wait_started_at = now;
|
||||
onDirectRetryEvent("queued", retry, _direct_retries[i].retry_delay, 1);
|
||||
} else {
|
||||
onDirectRetryEvent("dropped_queue_full", retry, _direct_retries[i].retry_delay, 1);
|
||||
onDirectRetryEvent("failure", retry, 0, 1);
|
||||
releasePacket(retry);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Mesh::clearPendingDirectRetryOnSendFail(const Packet* packet) {
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (!_direct_retries[i].active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_direct_retries[i].queued) {
|
||||
if (_direct_retries[i].packet == packet) {
|
||||
// The queued retry itself failed; Dispatcher will release it after this hook.
|
||||
onDirectRetryEvent("dropped_send_fail", packet, 0, _direct_retries[i].retry_attempts_sent + 1);
|
||||
onDirectRetryEvent("failure", packet, 0, _direct_retries[i].retry_attempts_sent + 1);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_direct_retries[i].trigger_packet == packet) {
|
||||
onDirectRetryEvent("dropped_send_fail", packet, 0, 1);
|
||||
onDirectRetryEvent("failure", packet, 0, 1);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Mesh::getDirectRetryTarget(const Packet* packet, const uint8_t*& next_hop_hash, uint8_t& next_hop_hash_len,
|
||||
uint8_t& progress_marker, bool& expect_path_growth) const {
|
||||
switch (packet->getPayloadType()) {
|
||||
case PAYLOAD_TYPE_ACK:
|
||||
case PAYLOAD_TYPE_PATH:
|
||||
case PAYLOAD_TYPE_REQ:
|
||||
case PAYLOAD_TYPE_RESPONSE:
|
||||
case PAYLOAD_TYPE_TXT_MSG:
|
||||
case PAYLOAD_TYPE_ANON_REQ:
|
||||
// Allow retries even when only one downstream hop remains so fixed direct paths
|
||||
// (e.g. remote admin/login over 2-hop chains) use the same retry policy.
|
||||
if (packet->getPathHashCount() == 0) {
|
||||
return false;
|
||||
}
|
||||
next_hop_hash = packet->path;
|
||||
next_hop_hash_len = packet->getPathHashSize();
|
||||
progress_marker = packet->getPathHashCount();
|
||||
expect_path_growth = false;
|
||||
return true;
|
||||
|
||||
case PAYLOAD_TYPE_MULTIPART:
|
||||
if (packet->payload_len < 1 || (packet->payload[0] & 0x0F) != PAYLOAD_TYPE_ACK || packet->getPathHashCount() == 0) {
|
||||
return false;
|
||||
}
|
||||
next_hop_hash = packet->path;
|
||||
next_hop_hash_len = packet->getPathHashSize();
|
||||
progress_marker = packet->getPathHashCount();
|
||||
expect_path_growth = false;
|
||||
return true;
|
||||
|
||||
case PAYLOAD_TYPE_TRACE: {
|
||||
if (packet->payload_len < 9) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t route_bytes = packet->payload_len - 9;
|
||||
uint8_t hash_size = decodeTraceHashSize(packet->payload[8], route_bytes);
|
||||
uint16_t offset = (uint16_t)packet->path_len * (uint16_t)hash_size;
|
||||
if (offset + hash_size > route_bytes) {
|
||||
return false;
|
||||
}
|
||||
if (offset + (2 * hash_size) > route_bytes) {
|
||||
return false; // no downstream repeater means there will be no forward echo to overhear.
|
||||
}
|
||||
|
||||
next_hop_hash = &packet->payload[9 + offset];
|
||||
next_hop_hash_len = hash_size;
|
||||
progress_marker = packet->path_len;
|
||||
expect_path_growth = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Mesh::maybeScheduleDirectRetry(const Packet* packet, uint8_t priority) {
|
||||
const uint8_t* next_hop_hash;
|
||||
uint8_t next_hop_hash_len;
|
||||
uint8_t progress_marker;
|
||||
bool expect_path_growth;
|
||||
if (!getDirectRetryTarget(packet, next_hop_hash, next_hop_hash_len, progress_marker, expect_path_growth)
|
||||
|| !allowDirectRetry(packet, next_hop_hash, next_hop_hash_len)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int slot_idx = -1;
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (!_direct_retries[i].active) {
|
||||
slot_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (slot_idx < 0) {
|
||||
onDirectRetryEvent("dropped_no_slot", packet, 0, 0);
|
||||
onDirectRetryEvent("failure", packet, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only store retry metadata here; allocate the retry packet after the initial TX really completes.
|
||||
uint32_t retry_delay = getDirectRetryAttemptDelay(packet, 0);
|
||||
calculateDirectRetryKey(packet, _direct_retries[slot_idx].retry_key);
|
||||
_direct_retries[slot_idx].packet = NULL;
|
||||
_direct_retries[slot_idx].trigger_packet = const_cast<Packet*>(packet);
|
||||
_direct_retries[slot_idx].retry_started_at = 0;
|
||||
_direct_retries[slot_idx].echo_wait_started_at = 0;
|
||||
_direct_retries[slot_idx].retry_at = 0;
|
||||
_direct_retries[slot_idx].retry_delay = retry_delay;
|
||||
_direct_retries[slot_idx].retry_attempts_sent = 0;
|
||||
_direct_retries[slot_idx].priority = priority;
|
||||
_direct_retries[slot_idx].progress_marker = progress_marker;
|
||||
_direct_retries[slot_idx].expect_path_growth = expect_path_growth;
|
||||
_direct_retries[slot_idx].waiting_final_echo = false;
|
||||
_direct_retries[slot_idx].queued = false;
|
||||
_direct_retries[slot_idx].active = true;
|
||||
}
|
||||
|
||||
void Mesh::clearFloodRetrySlot(int idx) {
|
||||
if (_flood_retries[idx].waiting_final_echo && _flood_retries[idx].packet != NULL) {
|
||||
releasePacket(_flood_retries[idx].packet);
|
||||
}
|
||||
_flood_retries[idx].packet = NULL;
|
||||
_flood_retries[idx].trigger_packet = NULL;
|
||||
_flood_retries[idx].retry_started_at = 0;
|
||||
_flood_retries[idx].retry_at = 0;
|
||||
_flood_retries[idx].retry_delay = 0;
|
||||
_flood_retries[idx].retry_attempts_sent = 0;
|
||||
_flood_retries[idx].priority = 0;
|
||||
_flood_retries[idx].progress_marker = 0;
|
||||
_flood_retries[idx].waiting_final_echo = false;
|
||||
_flood_retries[idx].queued = false;
|
||||
_flood_retries[idx].active = false;
|
||||
}
|
||||
|
||||
bool Mesh::isFloodRetryQueued(const Packet* packet) const {
|
||||
for (int i = 0; i < _mgr->getOutboundTotal(); i++) {
|
||||
if (_mgr->getOutboundByIdx(i) == packet) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Mesh::isFloodRetryEchoTarget(const Packet* packet, uint8_t progress_marker) const {
|
||||
return packet->isRouteFlood() && packet->getPathHashCount() > progress_marker;
|
||||
}
|
||||
|
||||
bool Mesh::cancelFloodRetryOnEcho(const Packet* packet) {
|
||||
uint8_t recv_key[MAX_HASH_SIZE];
|
||||
packet->calculatePacketHash(recv_key);
|
||||
|
||||
bool cleared = false;
|
||||
for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) {
|
||||
if (!_flood_retries[i].active || memcmp(recv_key, _flood_retries[i].retry_key, MAX_HASH_SIZE) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (!isFloodRetryEchoTarget(packet, _flood_retries[i].progress_marker)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t echo_millis = _flood_retries[i].retry_started_at == 0
|
||||
? 0
|
||||
: (uint32_t)(_ms->getMillis() - _flood_retries[i].retry_started_at);
|
||||
uint8_t retry_attempt = _flood_retries[i].waiting_final_echo
|
||||
? _flood_retries[i].retry_attempts_sent
|
||||
: _flood_retries[i].retry_attempts_sent + 1;
|
||||
onFloodRetryEvent("good", packet, echo_millis, retry_attempt);
|
||||
|
||||
if (_flood_retries[i].queued) {
|
||||
for (int j = 0; j < _mgr->getOutboundTotal(); j++) {
|
||||
if (_mgr->getOutboundByIdx(j) == _flood_retries[i].packet) {
|
||||
Packet* pending = _mgr->removeOutboundByIdx(j);
|
||||
if (pending) {
|
||||
releasePacket(pending);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
clearFloodRetrySlot(i);
|
||||
cleared = true;
|
||||
}
|
||||
|
||||
return cleared;
|
||||
}
|
||||
|
||||
void Mesh::armFloodRetryOnSendComplete(const Packet* packet) {
|
||||
for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) {
|
||||
if (!_flood_retries[i].active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_flood_retries[i].queued) {
|
||||
if (_flood_retries[i].packet != packet) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t elapsed_millis = _flood_retries[i].retry_started_at == 0
|
||||
? 0
|
||||
: (uint32_t)(_ms->getMillis() - _flood_retries[i].retry_started_at);
|
||||
onFloodRetryEvent("resent", packet, elapsed_millis, _flood_retries[i].retry_attempts_sent + 1);
|
||||
_flood_retries[i].retry_attempts_sent++;
|
||||
|
||||
uint8_t max_attempts = getFloodRetryMaxAttempts(packet);
|
||||
if (max_attempts < 1) {
|
||||
max_attempts = 1;
|
||||
} else if (max_attempts > FLOOD_RETRY_MAX_ATTEMPTS_HARD_MAX) {
|
||||
max_attempts = FLOOD_RETRY_MAX_ATTEMPTS_HARD_MAX;
|
||||
}
|
||||
if (_flood_retries[i].retry_attempts_sent >= max_attempts) {
|
||||
Packet* final_wait = obtainNewPacket();
|
||||
if (final_wait == NULL) {
|
||||
onFloodRetryEvent("dropped_no_packet", packet, elapsed_millis, _flood_retries[i].retry_attempts_sent);
|
||||
onFloodRetryEvent("failure", packet, elapsed_millis, _flood_retries[i].retry_attempts_sent);
|
||||
clearFloodRetrySlot(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
*final_wait = *packet;
|
||||
_flood_retries[i].packet = final_wait;
|
||||
_flood_retries[i].retry_at = futureMillis(_flood_retries[i].retry_delay);
|
||||
_flood_retries[i].waiting_final_echo = true;
|
||||
_flood_retries[i].queued = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
Packet* retry = obtainNewPacket();
|
||||
if (retry == NULL) {
|
||||
onFloodRetryEvent("dropped_no_packet", packet, elapsed_millis, _flood_retries[i].retry_attempts_sent + 1);
|
||||
onFloodRetryEvent("failure", packet, elapsed_millis, _flood_retries[i].retry_attempts_sent + 1);
|
||||
clearFloodRetrySlot(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
*retry = *packet;
|
||||
uint32_t retry_delay = getFloodRetryAttemptDelay(packet, _flood_retries[i].retry_attempts_sent);
|
||||
if (queueOutboundPacket(retry, _flood_retries[i].priority, retry_delay)) {
|
||||
_flood_retries[i].packet = retry;
|
||||
_flood_retries[i].retry_delay = retry_delay;
|
||||
_flood_retries[i].retry_at = futureMillis(retry_delay);
|
||||
_flood_retries[i].retry_started_at = _ms->getMillis();
|
||||
_flood_retries[i].waiting_final_echo = false;
|
||||
onFloodRetryEvent("queued", retry, retry_delay, _flood_retries[i].retry_attempts_sent + 1);
|
||||
} else {
|
||||
onFloodRetryEvent("dropped_queue_full", retry, retry_delay, _flood_retries[i].retry_attempts_sent + 1);
|
||||
onFloodRetryEvent("failure", retry, elapsed_millis, _flood_retries[i].retry_attempts_sent + 1);
|
||||
releasePacket(retry);
|
||||
clearFloodRetrySlot(i);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_flood_retries[i].trigger_packet != packet) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Packet* retry = obtainNewPacket();
|
||||
if (retry == NULL) {
|
||||
onFloodRetryEvent("dropped_no_packet", packet, _flood_retries[i].retry_delay, 1);
|
||||
onFloodRetryEvent("failure", packet, 0, 1);
|
||||
clearFloodRetrySlot(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
*retry = *packet;
|
||||
if (queueOutboundPacket(retry, _flood_retries[i].priority, _flood_retries[i].retry_delay)) {
|
||||
unsigned long now = _ms->getMillis();
|
||||
_flood_retries[i].packet = retry;
|
||||
_flood_retries[i].trigger_packet = NULL;
|
||||
_flood_retries[i].queued = true;
|
||||
_flood_retries[i].waiting_final_echo = false;
|
||||
_flood_retries[i].retry_at = futureMillis(_flood_retries[i].retry_delay);
|
||||
_flood_retries[i].retry_started_at = now;
|
||||
onFloodRetryEvent("queued", retry, _flood_retries[i].retry_delay, 1);
|
||||
} else {
|
||||
onFloodRetryEvent("dropped_queue_full", retry, _flood_retries[i].retry_delay, 1);
|
||||
onFloodRetryEvent("failure", retry, 0, 1);
|
||||
releasePacket(retry);
|
||||
clearFloodRetrySlot(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Mesh::clearPendingFloodRetryOnSendFail(const Packet* packet) {
|
||||
for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) {
|
||||
if (!_flood_retries[i].active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_flood_retries[i].queued) {
|
||||
if (_flood_retries[i].packet == packet) {
|
||||
onFloodRetryEvent("dropped_send_fail", packet, 0, _flood_retries[i].retry_attempts_sent + 1);
|
||||
onFloodRetryEvent("failure", packet, 0, _flood_retries[i].retry_attempts_sent + 1);
|
||||
clearFloodRetrySlot(i);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_flood_retries[i].trigger_packet == packet) {
|
||||
onFloodRetryEvent("dropped_send_fail", packet, 0, 1);
|
||||
onFloodRetryEvent("failure", packet, 0, 1);
|
||||
clearFloodRetrySlot(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Mesh::maybeScheduleFloodRetry(const Packet* packet, uint8_t priority) {
|
||||
if (packet == NULL || !packet->isRouteFlood() || hasFloodRetryTargetPrefix(packet)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t max_path_len = getFloodRetryMaxPathLength(packet);
|
||||
if (max_path_len != FLOOD_RETRY_PATH_GATE_DISABLED && packet->getPathHashCount() > max_path_len) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t max_attempts = getFloodRetryMaxAttempts(packet);
|
||||
if (max_attempts == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int slot_idx = -1;
|
||||
for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) {
|
||||
if (!_flood_retries[i].active) {
|
||||
slot_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (slot_idx < 0) {
|
||||
onFloodRetryEvent("dropped_no_slot", packet, 0, 0);
|
||||
onFloodRetryEvent("failure", packet, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!allowFloodRetry(packet)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t retry_delay = getFloodRetryAttemptDelay(packet, 0);
|
||||
packet->calculatePacketHash(_flood_retries[slot_idx].retry_key);
|
||||
_flood_retries[slot_idx].packet = NULL;
|
||||
_flood_retries[slot_idx].trigger_packet = const_cast<Packet*>(packet);
|
||||
_flood_retries[slot_idx].retry_started_at = 0;
|
||||
_flood_retries[slot_idx].retry_at = 0;
|
||||
_flood_retries[slot_idx].retry_delay = retry_delay;
|
||||
_flood_retries[slot_idx].retry_attempts_sent = 0;
|
||||
_flood_retries[slot_idx].priority = priority;
|
||||
_flood_retries[slot_idx].progress_marker = packet->getPathHashCount();
|
||||
_flood_retries[slot_idx].waiting_final_echo = false;
|
||||
_flood_retries[slot_idx].queued = false;
|
||||
_flood_retries[slot_idx].active = true;
|
||||
}
|
||||
|
||||
Packet* Mesh::createAdvert(const LocalIdentity& id, const uint8_t* app_data, size_t app_data_len) {
|
||||
if (app_data_len > MAX_ADVERT_DATA_SIZE) return NULL;
|
||||
|
||||
@@ -634,7 +1361,7 @@ void Mesh::sendFlood(Packet* packet, uint32_t delay_millis, uint8_t path_hash_si
|
||||
packet->header |= ROUTE_TYPE_FLOOD;
|
||||
packet->setPathHashSizeAndCount(path_hash_size, 0);
|
||||
|
||||
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
_tables->markSent(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
|
||||
uint8_t pri;
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) {
|
||||
@@ -663,7 +1390,7 @@ void Mesh::sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_m
|
||||
packet->transport_codes[1] = transport_codes[1];
|
||||
packet->setPathHashSizeAndCount(path_hash_size, 0);
|
||||
|
||||
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
_tables->markSent(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
|
||||
uint8_t pri;
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) {
|
||||
@@ -696,7 +1423,8 @@ void Mesh::sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uin
|
||||
pri = 0;
|
||||
}
|
||||
}
|
||||
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
_tables->markSent(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
maybeScheduleDirectRetry(packet, pri);
|
||||
sendPacket(packet, pri, delay_millis);
|
||||
}
|
||||
|
||||
@@ -706,7 +1434,7 @@ void Mesh::sendZeroHop(Packet* packet, uint32_t delay_millis) {
|
||||
|
||||
packet->path_len = 0; // path_len of zero means Zero Hop
|
||||
|
||||
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
_tables->markSent(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
|
||||
sendPacket(packet, 0, delay_millis);
|
||||
}
|
||||
@@ -719,9 +1447,9 @@ void Mesh::sendZeroHop(Packet* packet, uint16_t* transport_codes, uint32_t delay
|
||||
|
||||
packet->path_len = 0; // path_len of zero means Zero Hop
|
||||
|
||||
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
_tables->markSent(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
|
||||
sendPacket(packet, 0, delay_millis);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+130
@@ -4,6 +4,18 @@
|
||||
|
||||
namespace mesh {
|
||||
|
||||
#ifndef MAX_DIRECT_RETRY_SLOTS
|
||||
#define MAX_DIRECT_RETRY_SLOTS 6
|
||||
#endif
|
||||
|
||||
#ifndef MAX_FLOOD_RETRY_SLOTS
|
||||
#define MAX_FLOOD_RETRY_SLOTS 6
|
||||
#endif
|
||||
|
||||
#ifndef FLOOD_RETRY_PATH_GATE_DISABLED
|
||||
#define FLOOD_RETRY_PATH_GATE_DISABLED 0xFF
|
||||
#endif
|
||||
|
||||
class GroupChannel {
|
||||
public:
|
||||
uint8_t hash[PATH_HASH_SIZE];
|
||||
@@ -16,6 +28,7 @@ public:
|
||||
class MeshTables {
|
||||
public:
|
||||
virtual bool hasSeen(const Packet* packet) = 0;
|
||||
virtual void markSent(const Packet* packet) = 0;
|
||||
virtual void clear(const Packet* packet) = 0; // remove this packet hash from table
|
||||
};
|
||||
|
||||
@@ -24,17 +37,68 @@ public:
|
||||
* and provides virtual methods for sub-classes on handling incoming, and also preparing outbound Packets.
|
||||
*/
|
||||
class Mesh : public Dispatcher {
|
||||
struct DirectRetryEntry {
|
||||
Packet* packet;
|
||||
Packet* trigger_packet;
|
||||
unsigned long retry_started_at;
|
||||
unsigned long echo_wait_started_at;
|
||||
unsigned long retry_at;
|
||||
uint32_t retry_delay;
|
||||
uint8_t retry_attempts_sent;
|
||||
uint8_t retry_key[MAX_HASH_SIZE];
|
||||
uint8_t priority;
|
||||
uint8_t progress_marker;
|
||||
bool expect_path_growth;
|
||||
bool waiting_final_echo;
|
||||
bool queued;
|
||||
bool active;
|
||||
};
|
||||
|
||||
struct FloodRetryEntry {
|
||||
Packet* packet;
|
||||
Packet* trigger_packet;
|
||||
unsigned long retry_started_at;
|
||||
unsigned long retry_at;
|
||||
uint32_t retry_delay;
|
||||
uint8_t retry_attempts_sent;
|
||||
uint8_t retry_key[MAX_HASH_SIZE];
|
||||
uint8_t priority;
|
||||
uint8_t progress_marker;
|
||||
bool waiting_final_echo;
|
||||
bool queued;
|
||||
bool active;
|
||||
};
|
||||
|
||||
RTCClock* _rtc;
|
||||
RNG* _rng;
|
||||
MeshTables* _tables;
|
||||
DirectRetryEntry _direct_retries[MAX_DIRECT_RETRY_SLOTS];
|
||||
FloodRetryEntry _flood_retries[MAX_FLOOD_RETRY_SLOTS];
|
||||
|
||||
void removeSelfFromPath(Packet* packet);
|
||||
void routeDirectRecvAcks(Packet* packet, uint32_t delay_millis);
|
||||
void clearDirectRetrySlot(int idx);
|
||||
bool isDirectRetryQueued(const Packet* packet) const;
|
||||
void calculateDirectRetryKey(const Packet* packet, uint8_t* dest_key) const;
|
||||
bool cancelDirectRetryOnEcho(const Packet* packet);
|
||||
void armDirectRetryOnSendComplete(const Packet* packet);
|
||||
void clearPendingDirectRetryOnSendFail(const Packet* packet);
|
||||
bool getDirectRetryTarget(const Packet* packet, const uint8_t*& next_hop_hash, uint8_t& next_hop_hash_len,
|
||||
uint8_t& progress_marker, bool& expect_path_growth) const;
|
||||
void maybeScheduleDirectRetry(const Packet* packet, uint8_t priority);
|
||||
void clearFloodRetrySlot(int idx);
|
||||
bool isFloodRetryQueued(const Packet* packet) const;
|
||||
bool cancelFloodRetryOnEcho(const Packet* packet);
|
||||
void armFloodRetryOnSendComplete(const Packet* packet);
|
||||
void clearPendingFloodRetryOnSendFail(const Packet* packet);
|
||||
void maybeScheduleFloodRetry(const Packet* packet, uint8_t priority);
|
||||
//void routeRecvAcks(Packet* packet, uint32_t delay_millis);
|
||||
DispatcherAction forwardMultipartDirect(Packet* pkt);
|
||||
|
||||
protected:
|
||||
DispatcherAction onRecvPacket(Packet* pkt) override;
|
||||
void onSendComplete(Packet* packet) override;
|
||||
void onSendFail(Packet* packet) override;
|
||||
|
||||
virtual uint32_t getCADFailRetryDelay() const override;
|
||||
|
||||
@@ -65,11 +129,77 @@ protected:
|
||||
*/
|
||||
virtual uint32_t getDirectRetransmitDelay(const Packet* packet);
|
||||
|
||||
/**
|
||||
* \brief Decide whether a DIRECT packet should get one delayed retry if the next hop echo is not overheard.
|
||||
* Sub-classes can use neighbour tables or other link-quality data to opt in selectively.
|
||||
*/
|
||||
virtual bool allowDirectRetry(const Packet* packet, const uint8_t* next_hop_hash, uint8_t next_hop_hash_len) const;
|
||||
|
||||
/**
|
||||
* \returns milliseconds to wait for the next-hop echo before queueing one retry of the DIRECT packet.
|
||||
*/
|
||||
virtual uint32_t getDirectRetryEchoDelay(const Packet* packet) const;
|
||||
|
||||
/**
|
||||
* \returns maximum number of retry transmissions after the initial direct TX.
|
||||
*/
|
||||
virtual uint8_t getDirectRetryMaxAttempts(const Packet* packet) const;
|
||||
|
||||
/**
|
||||
* \returns delay before a specific retry attempt, where attempt_idx=0 is the first retry.
|
||||
*/
|
||||
virtual uint32_t getDirectRetryAttemptDelay(const Packet* packet, uint8_t attempt_idx);
|
||||
|
||||
/**
|
||||
* \brief Decide whether a FLOOD packet should retry when no downstream echo is overheard.
|
||||
*/
|
||||
virtual bool allowFloodRetry(const Packet* packet) const;
|
||||
|
||||
/**
|
||||
* \brief Return true when this FLOOD packet already carries an application-defined target prefix.
|
||||
*/
|
||||
virtual bool hasFloodRetryTargetPrefix(const Packet* packet) const;
|
||||
|
||||
/**
|
||||
* \returns maximum flood path hash count eligible for retry, or FLOOD_RETRY_PATH_GATE_DISABLED.
|
||||
*/
|
||||
virtual uint8_t getFloodRetryMaxPathLength(const Packet* packet) const;
|
||||
|
||||
/**
|
||||
* \returns maximum number of FLOOD retry transmissions after the initial TX.
|
||||
*/
|
||||
virtual uint8_t getFloodRetryMaxAttempts(const Packet* packet) const;
|
||||
|
||||
/**
|
||||
* \brief Return true when a received FLOOD echo is enough to cancel a pending retry.
|
||||
*/
|
||||
virtual bool isFloodRetryEchoTarget(const Packet* packet, uint8_t progress_marker) const;
|
||||
|
||||
/**
|
||||
* \returns delay before a specific flood retry attempt, where attempt_idx=0 is the first retry.
|
||||
*/
|
||||
virtual uint32_t getFloodRetryAttemptDelay(const Packet* packet, uint8_t attempt_idx);
|
||||
|
||||
/**
|
||||
* \brief Optional hook for logging flood-retry lifecycle events.
|
||||
*/
|
||||
virtual void onFloodRetryEvent(const char* event, const Packet* packet, uint32_t delay_millis, uint8_t retry_attempt) { }
|
||||
|
||||
/**
|
||||
* \returns number of extra (Direct) ACK transmissions wanted.
|
||||
*/
|
||||
virtual uint8_t getExtraAckTransmitCount() const;
|
||||
|
||||
/**
|
||||
* \brief Optional hook for logging direct-retry lifecycle events.
|
||||
*/
|
||||
virtual void onDirectRetryEvent(const char* event, const Packet* packet, uint32_t delay_millis, uint8_t retry_attempt) { }
|
||||
|
||||
/**
|
||||
* \brief Optional hook to set local-only transmit options on a retry packet before it is queued.
|
||||
*/
|
||||
virtual void configureDirectRetryPacket(Packet* retry, const Packet* original, uint8_t retry_attempt) { }
|
||||
|
||||
/**
|
||||
* \brief Perform search of local DB of peers/contacts.
|
||||
* \returns Number of peers with matching hash
|
||||
|
||||
@@ -52,12 +52,16 @@ public:
|
||||
virtual void onAfterTransmit() { }
|
||||
virtual void reboot() = 0;
|
||||
virtual void powerOff() { /* no op */ }
|
||||
virtual uint32_t getIRQGpio() { return -1; } // not supported. Returns DIO1 (SX1262) and DIO0 (SX127x)
|
||||
virtual void sleep(uint32_t secs) { /* no op */ }
|
||||
virtual uint32_t getGpio() { return 0; }
|
||||
virtual void setGpio(uint32_t values) {}
|
||||
virtual uint8_t getStartupReason() const = 0;
|
||||
virtual bool getBootloaderVersion(char* version, size_t max_len) { return false; }
|
||||
virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported
|
||||
virtual bool setLoRaFemLnaEnabled(bool enable) { return false; }
|
||||
virtual bool canControlLoRaFemLna() const { return false; }
|
||||
virtual bool isLoRaFemLnaEnabled() const { return false; }
|
||||
|
||||
// Power management interface (boards with power management override these)
|
||||
virtual bool isExternalPowered() { return false; }
|
||||
|
||||
+3
-1
@@ -8,6 +8,7 @@ Packet::Packet() {
|
||||
header = 0;
|
||||
path_len = 0;
|
||||
payload_len = 0;
|
||||
tx_cr = 0;
|
||||
}
|
||||
|
||||
bool Packet::isValidPathLen(uint8_t path_len) {
|
||||
@@ -64,6 +65,7 @@ uint8_t Packet::writeTo(uint8_t dest[]) const {
|
||||
|
||||
bool Packet::readFrom(const uint8_t src[], uint8_t len) {
|
||||
uint8_t i = 0;
|
||||
tx_cr = 0;
|
||||
header = src[i++];
|
||||
if (hasTransportCodes()) {
|
||||
memcpy(&transport_codes[0], &src[i], 2); i += 2;
|
||||
@@ -84,4 +86,4 @@ bool Packet::readFrom(const uint8_t src[], uint8_t len) {
|
||||
return true; // success
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,7 @@ public:
|
||||
uint8_t path[MAX_PATH_SIZE];
|
||||
uint8_t payload[MAX_PACKET_PAYLOAD];
|
||||
int8_t _snr;
|
||||
uint8_t tx_cr; // volatile local-only TX coding-rate override; not serialized
|
||||
|
||||
/**
|
||||
* \brief calculate the hash of payload + type
|
||||
|
||||
@@ -17,6 +17,10 @@ bool ArduinoSerialInterface::isConnected() const {
|
||||
return true; // no way of knowing, so assume yes
|
||||
}
|
||||
|
||||
bool ArduinoSerialInterface::isReadBusy() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ArduinoSerialInterface::isWriteBusy() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ public:
|
||||
|
||||
bool isConnected() const override;
|
||||
|
||||
bool isReadBusy() const override;
|
||||
bool isWriteBusy() const override;
|
||||
size_t writeFrame(const uint8_t src[], size_t len) override;
|
||||
size_t checkRecvFrame(uint8_t dest[]) override;
|
||||
};
|
||||
};
|
||||
@@ -15,6 +15,7 @@ public:
|
||||
|
||||
virtual bool isConnected() const = 0;
|
||||
|
||||
virtual bool isReadBusy() const = 0;
|
||||
virtual bool isWriteBusy() const = 0;
|
||||
virtual size_t writeFrame(const uint8_t src[], size_t len) = 0;
|
||||
virtual size_t checkRecvFrame(uint8_t dest[]) = 0;
|
||||
|
||||
+1400
-15
File diff suppressed because it is too large.
Load diff
+73
-2
@@ -19,6 +19,57 @@
|
||||
#define LOOP_DETECT_MODERATE 2
|
||||
#define LOOP_DETECT_STRICT 3
|
||||
|
||||
#define RETRY_PRESET_INFRA 0
|
||||
#define RETRY_PRESET_ROOFTOP 1
|
||||
#define RETRY_PRESET_MOBILE 2
|
||||
|
||||
#define DIRECT_RETRY_INFRA_BASE_MS 275
|
||||
#define DIRECT_RETRY_INFRA_COUNT 4
|
||||
#define DIRECT_RETRY_INFRA_STEP_MS 150
|
||||
#define DIRECT_RETRY_INFRA_MARGIN_X4 60
|
||||
|
||||
#define DIRECT_RETRY_ROOFTOP_BASE_MS 175
|
||||
#define DIRECT_RETRY_ROOFTOP_COUNT 15
|
||||
#define DIRECT_RETRY_ROOFTOP_STEP_MS 100
|
||||
#define DIRECT_RETRY_ROOFTOP_MARGIN_X4 20
|
||||
|
||||
#define DIRECT_RETRY_MOBILE_BASE_MS 175
|
||||
#define DIRECT_RETRY_MOBILE_COUNT 15
|
||||
#define DIRECT_RETRY_MOBILE_STEP_MS 50
|
||||
#define DIRECT_RETRY_MOBILE_MARGIN_X4 0
|
||||
|
||||
#ifndef FLOOD_RETRY_PREFIX_SLOTS
|
||||
#define FLOOD_RETRY_PREFIX_SLOTS 8
|
||||
#endif
|
||||
#ifndef FLOOD_RETRY_PREFIX_LEN
|
||||
#define FLOOD_RETRY_PREFIX_LEN 3
|
||||
#endif
|
||||
#ifndef FLOOD_RETRY_BRIDGE_BUCKETS
|
||||
#define FLOOD_RETRY_BRIDGE_BUCKETS 6
|
||||
#endif
|
||||
#ifndef FLOOD_RETRY_BUCKET_PREFIXES
|
||||
#define FLOOD_RETRY_BUCKET_PREFIXES 17
|
||||
#endif
|
||||
#ifndef FLOOD_RETRY_IGNORE_PREFIXES
|
||||
#define FLOOD_RETRY_IGNORE_PREFIXES 8
|
||||
#endif
|
||||
#ifndef FLOOD_RETRY_LIST_PREFIXES
|
||||
#define FLOOD_RETRY_LIST_PREFIXES ((FLOOD_RETRY_IGNORE_PREFIXES > FLOOD_RETRY_BUCKET_PREFIXES) ? FLOOD_RETRY_IGNORE_PREFIXES : FLOOD_RETRY_BUCKET_PREFIXES)
|
||||
#endif
|
||||
#ifndef FLOOD_RETRY_LIST_TEXT_MAX
|
||||
#define FLOOD_RETRY_LIST_TEXT_MAX (FLOOD_RETRY_LIST_PREFIXES * FLOOD_RETRY_PREFIX_LEN * 2 + FLOOD_RETRY_LIST_PREFIXES)
|
||||
#endif
|
||||
#ifndef COMMON_CLI_TMP_LEN
|
||||
#define COMMON_CLI_TMP_LEN ((FLOOD_RETRY_LIST_TEXT_MAX > (PRV_KEY_SIZE * 2 + 4)) ? FLOOD_RETRY_LIST_TEXT_MAX : (PRV_KEY_SIZE * 2 + 4))
|
||||
#endif
|
||||
|
||||
#define DIRECT_RETRY_CR4_MIN_SNR_X4_DEFAULT 40 // 10.0 dB and up => CR4
|
||||
#define DIRECT_RETRY_CR5_MIN_SNR_X4_DEFAULT 30 // 7.5 dB and up => CR5
|
||||
#define DIRECT_RETRY_CR7_MIN_SNR_X4_DEFAULT 10 // 2.5 dB and up => CR7
|
||||
#define DIRECT_RETRY_CR8_MAX_SNR_X4_DEFAULT 10 // 2.5 dB and down => CR8
|
||||
#define DIRECT_RETRY_CR_SNR_X4_MIN -128
|
||||
#define DIRECT_RETRY_CR_SNR_X4_MAX 127
|
||||
|
||||
struct NodePrefs { // persisted to file
|
||||
float airtime_factor;
|
||||
char node_name[32];
|
||||
@@ -33,7 +84,9 @@ struct NodePrefs { // persisted to file
|
||||
float tx_delay_factor;
|
||||
char guest_password[16];
|
||||
float direct_tx_delay_factor;
|
||||
uint32_t guard;
|
||||
uint8_t direct_retry_recent_enabled;
|
||||
uint8_t direct_retry_snr_margin_db; // stored in quarter-dB units (x4)
|
||||
uint8_t direct_retry_prefs_magic[2];
|
||||
uint8_t sf;
|
||||
uint8_t cr;
|
||||
uint8_t allow_read_only;
|
||||
@@ -59,8 +112,26 @@ struct NodePrefs { // persisted to file
|
||||
float adc_multiplier;
|
||||
char owner_info[120];
|
||||
uint8_t rx_boosted_gain; // power settings
|
||||
uint8_t radio_fem_rxgain; // LoRa FEM RX gain setting
|
||||
uint8_t path_hash_mode; // which path mode to use when sending
|
||||
uint8_t loop_detect;
|
||||
uint8_t direct_retry_attempts;
|
||||
uint16_t direct_retry_base_ms;
|
||||
uint8_t direct_retry_timing_magic[2];
|
||||
uint8_t retry_preset;
|
||||
uint16_t direct_retry_step_ms;
|
||||
uint8_t flood_retry_attempts;
|
||||
uint8_t flood_retry_path_gate;
|
||||
uint8_t flood_retry_prefs_magic[2];
|
||||
uint8_t flood_retry_prefixes[FLOOD_RETRY_PREFIX_SLOTS][FLOOD_RETRY_PREFIX_LEN];
|
||||
uint8_t flood_retry_bridge_enabled;
|
||||
uint8_t flood_retry_bridge_buckets[FLOOD_RETRY_BRIDGE_BUCKETS][FLOOD_RETRY_BUCKET_PREFIXES][FLOOD_RETRY_PREFIX_LEN];
|
||||
uint8_t flood_retry_ignore_prefixes[FLOOD_RETRY_IGNORE_PREFIXES][FLOOD_RETRY_PREFIX_LEN];
|
||||
uint8_t flood_retry_advert_enabled;
|
||||
int8_t direct_retry_cr4_snr_x4;
|
||||
int8_t direct_retry_cr5_snr_x4;
|
||||
int8_t direct_retry_cr7_snr_x4;
|
||||
int8_t direct_retry_cr8_snr_x4;
|
||||
};
|
||||
|
||||
class CommonCLICallbacks {
|
||||
@@ -120,7 +191,7 @@ class CommonCLI {
|
||||
SensorManager* _sensors;
|
||||
RegionMap* _region_map;
|
||||
ClientACL* _acl;
|
||||
char tmp[PRV_KEY_SIZE*2 + 4];
|
||||
char tmp[COMMON_CLI_TMP_LEN];
|
||||
|
||||
mesh::RTCClock* getRTCClock() { return _rtc; }
|
||||
void savePrefs();
|
||||
|
||||
+106
-24
@@ -12,12 +12,14 @@
|
||||
#include <rom/rtc.h>
|
||||
#include <sys/time.h>
|
||||
#include <Wire.h>
|
||||
#include "driver/rtc_io.h"
|
||||
#include "soc/rtc.h"
|
||||
#include "esp_system.h"
|
||||
|
||||
class ESP32Board : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
bool inhibit_sleep = false;
|
||||
static inline portMUX_TYPE sleepMux = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
public:
|
||||
void begin() {
|
||||
@@ -60,25 +62,48 @@ public:
|
||||
return raw / 4;
|
||||
}
|
||||
|
||||
void enterLightSleep(uint32_t secs) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(P_LORA_DIO_1) // Supported ESP32 variants
|
||||
if (rtc_gpio_is_valid_gpio((gpio_num_t)P_LORA_DIO_1)) { // Only enter sleep mode if P_LORA_DIO_1 is RTC pin
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // To wake up when receiving a LoRa packet
|
||||
|
||||
if (secs > 0) {
|
||||
esp_sleep_enable_timer_wakeup(secs * 1000000); // To wake up every hour to do periodically jobs
|
||||
}
|
||||
|
||||
esp_light_sleep_start(); // CPU enters light sleep
|
||||
}
|
||||
#endif
|
||||
uint32_t getIRQGpio() {
|
||||
return P_LORA_DIO_1; // default for SX1262
|
||||
}
|
||||
|
||||
void sleep(uint32_t secs) override {
|
||||
if (!inhibit_sleep) {
|
||||
enterLightSleep(secs); // To wake up after "secs" seconds or when receiving a LoRa packet
|
||||
// Skip if not allow to sleep
|
||||
if (inhibit_sleep) {
|
||||
delay(1); // Give MCU to OTA to run
|
||||
return;
|
||||
}
|
||||
|
||||
// Set GPIO wakeup
|
||||
gpio_num_t wakeupPin = (gpio_num_t)getIRQGpio();
|
||||
|
||||
// Configure timer wakeup
|
||||
if (secs > 0) {
|
||||
esp_sleep_enable_timer_wakeup(secs * 1000000ULL); // Wake up periodically to do scheduled jobs
|
||||
}
|
||||
|
||||
// Disable CPU interrupt servicing
|
||||
portENTER_CRITICAL(&sleepMux);
|
||||
|
||||
// Skip sleep if there is a LoRa packet
|
||||
if (gpio_get_level(wakeupPin) == HIGH) {
|
||||
portEXIT_CRITICAL(&sleepMux);
|
||||
delay(1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure GPIO wakeup
|
||||
esp_sleep_enable_gpio_wakeup();
|
||||
gpio_wakeup_enable((gpio_num_t)wakeupPin, GPIO_INTR_HIGH_LEVEL); // Wake up when receiving a LoRa packet
|
||||
|
||||
// MCU enters light sleep
|
||||
esp_light_sleep_start();
|
||||
|
||||
// Avoid ISR flood during wakeup due to HIGH LEVEL interrupt
|
||||
gpio_wakeup_disable(wakeupPin);
|
||||
gpio_set_intr_type(wakeupPin, GPIO_INTR_POSEDGE);
|
||||
|
||||
// Enable CPU interrupt servicing
|
||||
portEXIT_CRITICAL(&sleepMux);
|
||||
}
|
||||
|
||||
uint8_t getStartupReason() const override { return startup_reason; }
|
||||
@@ -102,7 +127,7 @@ public:
|
||||
#endif
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
#ifdef PIN_VBAT_READ
|
||||
#ifdef PIN_VBAT_READ
|
||||
analogReadResolution(12);
|
||||
|
||||
uint32_t raw = 0;
|
||||
@@ -130,31 +155,88 @@ public:
|
||||
void setInhibitSleep(bool inhibit) {
|
||||
inhibit_sleep = inhibit;
|
||||
}
|
||||
|
||||
uint32_t getResetReason() const override {
|
||||
return esp_reset_reason();
|
||||
}
|
||||
|
||||
// https://docs.espressif.com/projects/esp-idf/en/v4.4.7/esp32/api-reference/system/system.html
|
||||
const char *getResetReasonString(uint32_t reason) {
|
||||
switch (reason) {
|
||||
case ESP_RST_UNKNOWN:
|
||||
return "Unknown or first boot";
|
||||
case ESP_RST_POWERON:
|
||||
return "Power-on reset";
|
||||
case ESP_RST_EXT:
|
||||
return "External reset";
|
||||
case ESP_RST_SW:
|
||||
return "Software reset";
|
||||
case ESP_RST_PANIC:
|
||||
return "Panic / exception reset";
|
||||
case ESP_RST_INT_WDT:
|
||||
return "Interrupt watchdog reset";
|
||||
case ESP_RST_TASK_WDT:
|
||||
return "Task watchdog reset";
|
||||
case ESP_RST_WDT:
|
||||
return "Other watchdog reset";
|
||||
case ESP_RST_DEEPSLEEP:
|
||||
return "Wake from deep sleep";
|
||||
case ESP_RST_BROWNOUT:
|
||||
return "Brownout (low voltage)";
|
||||
case ESP_RST_SDIO:
|
||||
return "SDIO reset";
|
||||
default:
|
||||
static char buf[40];
|
||||
snprintf(buf, sizeof(buf), "Unknown reset reason (%d)", reason);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static RTC_NOINIT_ATTR uint32_t _rtc_backup_time;
|
||||
static RTC_NOINIT_ATTR uint32_t _rtc_backup_magic;
|
||||
#define RTC_BACKUP_MAGIC 0xAA55CC33
|
||||
#define RTC_TIME_MIN 1772323200 // 1 Mar 2026
|
||||
|
||||
class ESP32RTCClock : public mesh::RTCClock {
|
||||
public:
|
||||
ESP32RTCClock() { }
|
||||
void begin() {
|
||||
esp_reset_reason_t reason = esp_reset_reason();
|
||||
if (reason == ESP_RST_POWERON) {
|
||||
// start with some date/time in the recent past
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 1715770351; // 15 May 2024, 8:50pm
|
||||
tv.tv_usec = 0;
|
||||
settimeofday(&tv, NULL);
|
||||
if (reason == ESP_RST_DEEPSLEEP) {
|
||||
return; // ESP-IDF preserves system time across deep sleep
|
||||
}
|
||||
// All other resets (power-on, crash, WDT, brownout) lose system time.
|
||||
// Restore from RTC backup if valid, otherwise use hardcoded seed.
|
||||
struct timeval tv;
|
||||
if (_rtc_backup_magic == RTC_BACKUP_MAGIC && _rtc_backup_time > RTC_TIME_MIN) {
|
||||
tv.tv_sec = _rtc_backup_time;
|
||||
} else {
|
||||
tv.tv_sec = 1772323200; // 1 Mar 2026
|
||||
}
|
||||
tv.tv_usec = 0;
|
||||
settimeofday(&tv, NULL);
|
||||
}
|
||||
uint32_t getCurrentTime() override {
|
||||
time_t _now;
|
||||
time(&_now);
|
||||
return _now;
|
||||
}
|
||||
void setCurrentTime(uint32_t time) override {
|
||||
void setCurrentTime(uint32_t time) override {
|
||||
struct timeval tv;
|
||||
tv.tv_sec = time;
|
||||
tv.tv_usec = 0;
|
||||
settimeofday(&tv, NULL);
|
||||
_rtc_backup_time = time;
|
||||
_rtc_backup_magic = RTC_BACKUP_MAGIC;
|
||||
}
|
||||
void tick() override {
|
||||
time_t now;
|
||||
time(&now);
|
||||
if (now > RTC_TIME_MIN && (uint32_t)now != _rtc_backup_time) {
|
||||
_rtc_backup_time = (uint32_t)now;
|
||||
_rtc_backup_magic = RTC_BACKUP_MAGIC;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <CayenneLPP.h>
|
||||
#include "sensors/LocationProvider.h"
|
||||
#include <Wire.h>
|
||||
|
||||
#define TELEM_PERM_BASE 0x01 // 'base' permission includes battery
|
||||
#define TELEM_PERM_LOCATION 0x02
|
||||
@@ -15,6 +16,7 @@ public:
|
||||
double node_altitude; // altitude in meters
|
||||
|
||||
SensorManager() { node_lat = 0; node_lon = 0; node_altitude = 0; }
|
||||
virtual bool i2c_probe(TwoWire& wire, uint8_t addr) { return false; }
|
||||
virtual bool begin() { return false; }
|
||||
virtual bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { return false; }
|
||||
virtual void loop() { }
|
||||
|
||||
+366
-23
@@ -1,6 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <Mesh.h>
|
||||
#if ARDUINO
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
#include <FS.h>
|
||||
@@ -8,13 +11,138 @@
|
||||
|
||||
#define MAX_PACKET_HASHES 128
|
||||
#define MAX_PACKET_ACKS 64
|
||||
#ifndef MAX_RECENT_REPEATERS
|
||||
// Platform defaults. Can be overridden with -D MAX_RECENT_REPEATERS=<n>.
|
||||
#if defined(ESP32) || defined(ESP32_PLATFORM)
|
||||
#define MAX_RECENT_REPEATERS 2048
|
||||
#elif defined(NRF52_PLATFORM)
|
||||
#define MAX_RECENT_REPEATERS 512
|
||||
#else
|
||||
#define MAX_RECENT_REPEATERS 64
|
||||
#endif
|
||||
#endif
|
||||
#define MAX_ROUTE_HASH_BYTES 3
|
||||
|
||||
class SimpleMeshTables : public mesh::MeshTables {
|
||||
public:
|
||||
typedef bool (*RecentRepeaterAllowFn)(const uint8_t* prefix, uint8_t prefix_len, void* ctx);
|
||||
|
||||
struct RecentRepeaterInfo {
|
||||
// Identity and link quality for a next-hop path prefix.
|
||||
uint8_t prefix[MAX_ROUTE_HASH_BYTES];
|
||||
uint8_t prefix_len;
|
||||
int8_t snr_x4;
|
||||
uint8_t snr_locked;
|
||||
uint32_t last_heard_millis;
|
||||
};
|
||||
|
||||
private:
|
||||
uint8_t _hashes[MAX_PACKET_HASHES*MAX_HASH_SIZE];
|
||||
int _next_idx;
|
||||
uint32_t _acks[MAX_PACKET_ACKS];
|
||||
int _next_ack_idx;
|
||||
uint32_t _direct_dups, _flood_dups;
|
||||
RecentRepeaterInfo _recent_repeaters[MAX_RECENT_REPEATERS];
|
||||
int _next_recent_repeater_idx;
|
||||
int8_t _recent_repeater_min_snr_x4;
|
||||
RecentRepeaterAllowFn _recent_repeater_allow_fn;
|
||||
void* _recent_repeater_allow_ctx;
|
||||
|
||||
bool hasSeenAck(uint32_t ack) const {
|
||||
for (int i = 0; i < MAX_PACKET_ACKS; i++) {
|
||||
if (ack == _acks[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void storeAck(uint32_t ack) {
|
||||
_acks[_next_ack_idx] = ack;
|
||||
_next_ack_idx = (_next_ack_idx + 1) % MAX_PACKET_ACKS;
|
||||
}
|
||||
|
||||
bool hasSeenHash(const uint8_t* hash) const {
|
||||
const uint8_t* sp = _hashes;
|
||||
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) {
|
||||
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void storeHash(const uint8_t* hash) {
|
||||
memcpy(&_hashes[_next_idx*MAX_HASH_SIZE], hash, MAX_HASH_SIZE);
|
||||
_next_idx = (_next_idx + 1) % MAX_PACKET_HASHES;
|
||||
}
|
||||
|
||||
bool prefixesOverlap(const uint8_t* a, uint8_t a_len, const uint8_t* b, uint8_t b_len) const {
|
||||
uint8_t n = a_len < b_len ? a_len : b_len;
|
||||
return n > 0 && memcmp(a, b, n) == 0;
|
||||
}
|
||||
|
||||
int8_t weightedSnrX4RoundUp(int8_t curr_snr_x4, int8_t new_snr_x4) const {
|
||||
// Keep existing SNR heavier than a single new sample: 75% existing + 25% new.
|
||||
int32_t weighted_sum = ((int32_t)curr_snr_x4 * 3) + (int32_t)new_snr_x4;
|
||||
int32_t blended = weighted_sum / 4; // truncates toward zero
|
||||
// "Round up" means ceil(), which only differs from truncation for positive remainders.
|
||||
if (weighted_sum > 0 && (weighted_sum % 4) != 0) {
|
||||
blended++;
|
||||
}
|
||||
if (blended > 127) {
|
||||
blended = 127;
|
||||
} else if (blended < -128) {
|
||||
blended = -128;
|
||||
}
|
||||
return (int8_t)blended;
|
||||
}
|
||||
|
||||
bool extractRecentRepeater(const mesh::Packet* packet, uint8_t* prefix, uint8_t& prefix_len) const {
|
||||
// Learn repeater prefixes only from packet shapes that expose a trustworthy repeater ID.
|
||||
// For flood traffic, the last path entry is the repeater we directly heard.
|
||||
if (packet->isRouteFlood() && packet->getPathHashCount() > 0) {
|
||||
prefix_len = packet->getPathHashSize();
|
||||
if (prefix_len > MAX_ROUTE_HASH_BYTES) {
|
||||
prefix_len = MAX_ROUTE_HASH_BYTES;
|
||||
}
|
||||
|
||||
const uint8_t* last_hop = &packet->path[(packet->getPathHashCount() - 1) * packet->getPathHashSize()];
|
||||
memcpy(prefix, last_hop, prefix_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If there is no flood path to inspect, fall back to payload-derived identities.
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && packet->payload_len >= PUB_KEY_SIZE) {
|
||||
memcpy(prefix, packet->payload, MAX_ROUTE_HASH_BYTES);
|
||||
prefix_len = MAX_ROUTE_HASH_BYTES;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_CONTROL
|
||||
&& packet->isRouteDirect()
|
||||
&& packet->getPathHashCount() == 0
|
||||
&& packet->payload_len >= 6 + MAX_ROUTE_HASH_BYTES
|
||||
&& (packet->payload[0] & 0xF0) == 0x90) {
|
||||
memcpy(prefix, &packet->payload[6], MAX_ROUTE_HASH_BYTES);
|
||||
prefix_len = MAX_ROUTE_HASH_BYTES;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void recordRecentRepeater(const mesh::Packet* packet) {
|
||||
uint8_t prefix[MAX_ROUTE_HASH_BYTES] = {0};
|
||||
uint8_t prefix_len = 0;
|
||||
if (!extractRecentRepeater(packet, prefix, prefix_len) || prefix_len == 0) {
|
||||
return;
|
||||
}
|
||||
if (packet->_snr < _recent_repeater_min_snr_x4) {
|
||||
return;
|
||||
}
|
||||
setRecentRepeater(prefix, prefix_len, packet->_snr);
|
||||
}
|
||||
|
||||
public:
|
||||
SimpleMeshTables() {
|
||||
@@ -23,6 +151,11 @@ public:
|
||||
memset(_acks, 0, sizeof(_acks));
|
||||
_next_ack_idx = 0;
|
||||
_direct_dups = _flood_dups = 0;
|
||||
memset(_recent_repeaters, 0, sizeof(_recent_repeaters));
|
||||
_next_recent_repeater_idx = 0;
|
||||
_recent_repeater_min_snr_x4 = -128;
|
||||
_recent_repeater_allow_fn = NULL;
|
||||
_recent_repeater_allow_ctx = NULL;
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
@@ -31,6 +164,11 @@ public:
|
||||
f.read((uint8_t *) &_next_idx, sizeof(_next_idx));
|
||||
f.read((uint8_t *) &_acks[0], sizeof(_acks));
|
||||
f.read((uint8_t *) &_next_ack_idx, sizeof(_next_ack_idx));
|
||||
// Recent repeater entries are intentionally not restored across boots.
|
||||
// This avoids struct-layout migration issues and keeps stale path quality
|
||||
// stats from persisting indefinitely.
|
||||
memset(_recent_repeaters, 0, sizeof(_recent_repeaters));
|
||||
_next_recent_repeater_idx = 0;
|
||||
}
|
||||
void saveTo(File f) {
|
||||
f.write(_hashes, sizeof(_hashes));
|
||||
@@ -42,30 +180,12 @@ public:
|
||||
|
||||
bool hasSeen(const mesh::Packet* packet) override {
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ACK) {
|
||||
recordRecentRepeater(packet);
|
||||
|
||||
uint32_t ack;
|
||||
memcpy(&ack, packet->payload, 4);
|
||||
for (int i = 0; i < MAX_PACKET_ACKS; i++) {
|
||||
if (ack == _acks[i]) {
|
||||
if (packet->isRouteDirect()) {
|
||||
_direct_dups++; // keep some stats
|
||||
} else {
|
||||
_flood_dups++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
_acks[_next_ack_idx] = ack;
|
||||
_next_ack_idx = (_next_ack_idx + 1) % MAX_PACKET_ACKS; // cyclic table
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t hash[MAX_HASH_SIZE];
|
||||
packet->calculatePacketHash(hash);
|
||||
|
||||
const uint8_t* sp = _hashes;
|
||||
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) {
|
||||
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) {
|
||||
if (hasSeenAck(ack)) {
|
||||
if (packet->isRouteDirect()) {
|
||||
_direct_dups++; // keep some stats
|
||||
} else {
|
||||
@@ -73,13 +193,46 @@ public:
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
storeAck(ack);
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(&_hashes[_next_idx*MAX_HASH_SIZE], hash, MAX_HASH_SIZE);
|
||||
_next_idx = (_next_idx + 1) % MAX_PACKET_HASHES; // cyclic table
|
||||
uint8_t hash[MAX_HASH_SIZE];
|
||||
packet->calculatePacketHash(hash);
|
||||
|
||||
if (hasSeenHash(hash)) {
|
||||
if (packet->isRouteDirect()) {
|
||||
_direct_dups++; // keep some stats
|
||||
} else {
|
||||
_flood_dups++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
storeHash(hash);
|
||||
recordRecentRepeater(packet);
|
||||
return false;
|
||||
}
|
||||
|
||||
void markSent(const mesh::Packet* packet) override {
|
||||
// Outbound packets must be marked as already-sent without teaching the recent-heard cache about ourselves.
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ACK) {
|
||||
uint32_t ack;
|
||||
memcpy(&ack, packet->payload, 4);
|
||||
if (!hasSeenAck(ack)) {
|
||||
storeAck(ack);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t hash[MAX_HASH_SIZE];
|
||||
packet->calculatePacketHash(hash);
|
||||
if (!hasSeenHash(hash)) {
|
||||
storeHash(hash);
|
||||
}
|
||||
}
|
||||
|
||||
void clear(const mesh::Packet* packet) override {
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ACK) {
|
||||
uint32_t ack;
|
||||
@@ -107,5 +260,195 @@ public:
|
||||
uint32_t getNumDirectDups() const { return _direct_dups; }
|
||||
uint32_t getNumFloodDups() const { return _flood_dups; }
|
||||
|
||||
void setRecentRepeaterMinSNRX4(int8_t min_snr_x4) {
|
||||
_recent_repeater_min_snr_x4 = min_snr_x4;
|
||||
}
|
||||
void setRecentRepeaterAllowFilter(RecentRepeaterAllowFn fn, void* ctx) {
|
||||
_recent_repeater_allow_fn = fn;
|
||||
_recent_repeater_allow_ctx = ctx;
|
||||
}
|
||||
bool setRecentRepeater(const uint8_t* prefix, uint8_t prefix_len, int8_t snr_x4, bool snr_locked = false,
|
||||
bool bypass_allow_filter = false) {
|
||||
if (prefix == NULL || prefix_len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prefix_len > MAX_ROUTE_HASH_BYTES) {
|
||||
prefix_len = MAX_ROUTE_HASH_BYTES;
|
||||
}
|
||||
|
||||
if (!bypass_allow_filter && _recent_repeater_allow_fn != NULL
|
||||
&& !_recent_repeater_allow_fn(prefix, prefix_len, _recent_repeater_allow_ctx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Keep one slot for overlapping prefixes so 1/2/3-byte paths share the same entry.
|
||||
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
|
||||
RecentRepeaterInfo& existing = _recent_repeaters[i];
|
||||
if (existing.prefix_len == 0 || !prefixesOverlap(existing.prefix, existing.prefix_len, prefix, prefix_len)) {
|
||||
continue;
|
||||
}
|
||||
if (prefix_len > existing.prefix_len) {
|
||||
memset(existing.prefix, 0, sizeof(existing.prefix));
|
||||
memcpy(existing.prefix, prefix, prefix_len);
|
||||
existing.prefix_len = prefix_len;
|
||||
}
|
||||
if (snr_locked) {
|
||||
existing.snr_x4 = snr_x4;
|
||||
existing.snr_locked = 1;
|
||||
} else if (!existing.snr_locked) {
|
||||
existing.snr_x4 = weightedSnrX4RoundUp(existing.snr_x4, snr_x4);
|
||||
}
|
||||
#if ARDUINO
|
||||
existing.last_heard_millis = millis();
|
||||
#else
|
||||
existing.last_heard_millis = 0;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
int slot_idx = -1;
|
||||
// Prefer empty slots first while preserving newest-order iteration.
|
||||
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
|
||||
int idx = (_next_recent_repeater_idx + i) % MAX_RECENT_REPEATERS;
|
||||
if (_recent_repeaters[idx].prefix_len == 0) {
|
||||
slot_idx = idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (slot_idx < 0) {
|
||||
// Table is full: evict the weakest observed SNR entry.
|
||||
slot_idx = 0;
|
||||
int8_t min_snr_x4 = _recent_repeaters[0].snr_x4;
|
||||
for (int i = 1; i < MAX_RECENT_REPEATERS; i++) {
|
||||
if (_recent_repeaters[i].snr_x4 < min_snr_x4) {
|
||||
min_snr_x4 = _recent_repeaters[i].snr_x4;
|
||||
slot_idx = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RecentRepeaterInfo& slot = _recent_repeaters[slot_idx];
|
||||
memset(slot.prefix, 0, sizeof(slot.prefix));
|
||||
memcpy(slot.prefix, prefix, prefix_len);
|
||||
slot.prefix_len = prefix_len;
|
||||
slot.snr_x4 = snr_x4;
|
||||
slot.snr_locked = snr_locked ? 1 : 0;
|
||||
#if ARDUINO
|
||||
slot.last_heard_millis = millis();
|
||||
#else
|
||||
slot.last_heard_millis = 0;
|
||||
#endif
|
||||
_next_recent_repeater_idx = (slot_idx + 1) % MAX_RECENT_REPEATERS;
|
||||
return true;
|
||||
}
|
||||
bool decrementRecentRepeaterSnrX4(const uint8_t* prefix, uint8_t prefix_len, uint8_t amount_x4 = 1) {
|
||||
if (prefix == NULL || prefix_len == 0 || amount_x4 == 0) {
|
||||
return false;
|
||||
}
|
||||
if (prefix_len > MAX_ROUTE_HASH_BYTES) {
|
||||
prefix_len = MAX_ROUTE_HASH_BYTES;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
|
||||
RecentRepeaterInfo& existing = _recent_repeaters[i];
|
||||
if (existing.prefix_len == 0 || !prefixesOverlap(existing.prefix, existing.prefix_len, prefix, prefix_len)) {
|
||||
continue;
|
||||
}
|
||||
if (prefix_len > existing.prefix_len) {
|
||||
memset(existing.prefix, 0, sizeof(existing.prefix));
|
||||
memcpy(existing.prefix, prefix, prefix_len);
|
||||
existing.prefix_len = prefix_len;
|
||||
}
|
||||
if (!existing.snr_locked) {
|
||||
int16_t lowered = (int16_t)existing.snr_x4 - (int16_t)amount_x4;
|
||||
if (lowered < -128) {
|
||||
lowered = -128;
|
||||
}
|
||||
existing.snr_x4 = (int8_t)lowered;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const RecentRepeaterInfo* getLatestRecentRepeater() const {
|
||||
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
|
||||
int idx = (_next_recent_repeater_idx - 1 - i + MAX_RECENT_REPEATERS) % MAX_RECENT_REPEATERS;
|
||||
const RecentRepeaterInfo* info = &_recent_repeaters[idx];
|
||||
if (info->prefix_len > 0) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
int getRecentRepeaterCount() const {
|
||||
int count = 0;
|
||||
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
|
||||
if (_recent_repeaters[i].prefix_len > 0) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
const RecentRepeaterInfo* getRecentRepeaterNewestByIdx(int idx_wanted) const {
|
||||
if (idx_wanted < 0) {
|
||||
return NULL;
|
||||
}
|
||||
int idx_seen = 0;
|
||||
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
|
||||
int idx = (_next_recent_repeater_idx - 1 - i + MAX_RECENT_REPEATERS) % MAX_RECENT_REPEATERS;
|
||||
const RecentRepeaterInfo* info = &_recent_repeaters[idx];
|
||||
if (info->prefix_len == 0) {
|
||||
continue;
|
||||
}
|
||||
if (idx_seen == idx_wanted) {
|
||||
return info;
|
||||
}
|
||||
idx_seen++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
const RecentRepeaterInfo* getRecentRepeaterOldestByIdx(int idx_wanted) const {
|
||||
if (idx_wanted < 0) {
|
||||
return NULL;
|
||||
}
|
||||
int idx_seen = 0;
|
||||
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
|
||||
int idx = (_next_recent_repeater_idx + i) % MAX_RECENT_REPEATERS;
|
||||
const RecentRepeaterInfo* info = &_recent_repeaters[idx];
|
||||
if (info->prefix_len == 0) {
|
||||
continue;
|
||||
}
|
||||
if (idx_seen == idx_wanted) {
|
||||
return info;
|
||||
}
|
||||
idx_seen++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const RecentRepeaterInfo* findRecentRepeaterByHash(const uint8_t* hash, uint8_t hash_len) const {
|
||||
if (hash == NULL || hash_len == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Search newest-to-oldest and allow 1/2/3-byte prefixes to overlap-match.
|
||||
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
|
||||
int idx = (_next_recent_repeater_idx - 1 - i + MAX_RECENT_REPEATERS) % MAX_RECENT_REPEATERS;
|
||||
const RecentRepeaterInfo* info = &_recent_repeaters[idx];
|
||||
if (info->prefix_len == 0) {
|
||||
continue;
|
||||
}
|
||||
if (prefixesOverlap(info->prefix, info->prefix_len, hash, hash_len)) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
void clearRecentRepeaters() {
|
||||
memset(_recent_repeaters, 0, sizeof(_recent_repeaters));
|
||||
_next_recent_repeater_idx = 0;
|
||||
}
|
||||
|
||||
void resetStats() { _direct_dups = _flood_dups = 0; }
|
||||
};
|
||||
@@ -83,11 +83,12 @@ void StaticPoolPacketManager::free(mesh::Packet* packet) {
|
||||
unused.add(packet, 0, 0);
|
||||
}
|
||||
|
||||
void StaticPoolPacketManager::queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) {
|
||||
bool StaticPoolPacketManager::queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) {
|
||||
if (!send_queue.add(packet, priority, scheduled_for)) {
|
||||
MESH_DEBUG_PRINTLN("queueOutbound: send queue full, dropping packet");
|
||||
free(packet);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
mesh::Packet* StaticPoolPacketManager::getNextOutbound(uint32_t now) {
|
||||
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
|
||||
mesh::Packet* allocNew() override;
|
||||
void free(mesh::Packet* packet) override;
|
||||
void queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) override;
|
||||
bool queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) override;
|
||||
mesh::Packet* getNextOutbound(uint32_t now) override;
|
||||
int getOutboundCount(uint32_t now) const override;
|
||||
int getOutboundTotal() const override;
|
||||
@@ -35,4 +35,4 @@ public:
|
||||
mesh::Packet* removeOutboundByIdx(int i) override;
|
||||
void queueInbound(mesh::Packet* packet, uint32_t scheduled_for) override;
|
||||
mesh::Packet* getNextInbound(uint32_t now) override;
|
||||
};
|
||||
};
|
||||
@@ -182,6 +182,10 @@ size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) {
|
||||
|
||||
#define BLE_WRITE_MIN_INTERVAL 60
|
||||
|
||||
bool SerialBLEInterface::isReadBusy() const {
|
||||
return (recv_queue_len > 0);
|
||||
}
|
||||
|
||||
bool SerialBLEInterface::isWriteBusy() const {
|
||||
return millis() < _last_write + BLE_WRITE_MIN_INTERVAL; // still too soon to start another write?
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ public:
|
||||
|
||||
bool isConnected() const override;
|
||||
|
||||
bool isReadBusy() const override;
|
||||
bool isWriteBusy() const override;
|
||||
size_t writeFrame(const uint8_t src[], size_t len) override;
|
||||
size_t checkRecvFrame(uint8_t dest[]) override;
|
||||
|
||||
@@ -59,13 +59,13 @@
|
||||
// uint32_t P_LORA_BUSY = 0; //shared, so define at run
|
||||
// uint32_t P_LORA_DIO_2 = 0; //SX1276 only, so define at run
|
||||
|
||||
#define P_LORA_DIO_0 26
|
||||
#define P_LORA_DIO_1 33
|
||||
#define P_LORA_NSS 18
|
||||
#define P_LORA_RESET 23
|
||||
#define P_LORA_SCLK 5
|
||||
#define P_LORA_MISO 19
|
||||
#define P_LORA_MOSI 27
|
||||
// #define P_LORA_DIO_0 26
|
||||
// #define P_LORA_DIO_1 33
|
||||
// #define P_LORA_NSS 18
|
||||
// #define P_LORA_RESET 23
|
||||
// #define P_LORA_SCLK 5
|
||||
// #define P_LORA_MISO 19
|
||||
// #define P_LORA_MOSI 27
|
||||
|
||||
// #define PIN_GPS_RX 34
|
||||
// #define PIN_GPS_TX 12
|
||||
|
||||
@@ -401,6 +401,10 @@ bool SerialBLEInterface::isConnected() const {
|
||||
return _isDeviceConnected && Bluefruit.connected() > 0;
|
||||
}
|
||||
|
||||
bool SerialBLEInterface::isReadBusy() const {
|
||||
return (recv_queue_len > 0);
|
||||
}
|
||||
|
||||
bool SerialBLEInterface::isWriteBusy() const {
|
||||
return send_queue_len >= (FRAME_QUEUE_SIZE * 2 / 3);
|
||||
}
|
||||
@@ -66,6 +66,7 @@ public:
|
||||
void disable() override;
|
||||
bool isEnabled() const override { return _isEnabled; }
|
||||
bool isConnected() const override;
|
||||
bool isReadBusy() const override;
|
||||
bool isWriteBusy() const override;
|
||||
size_t writeFrame(const uint8_t src[], size_t len) override;
|
||||
size_t checkRecvFrame(uint8_t dest[]) override;
|
||||
|
||||
@@ -20,6 +20,15 @@ public:
|
||||
int sf = ((CustomLLCC68 *)_radio)->spreadingFactor;
|
||||
return packetScoreInt(snr, sf, packet_len);
|
||||
}
|
||||
bool setCodingRate(uint8_t cr) override {
|
||||
idle();
|
||||
int err = ((CustomLLCC68 *)_radio)->setCodingRate(cr);
|
||||
if (err != RADIOLIB_ERR_NONE) {
|
||||
MESH_DEBUG_PRINTLN("CustomLLCC68Wrapper: error: setCodingRate(%d)=%d", (uint32_t)cr, err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
|
||||
|
||||
|
||||
@@ -25,6 +25,16 @@ public:
|
||||
float getLastRSSI() const override { return ((CustomLR1110 *)_radio)->getRSSI(); }
|
||||
float getLastSNR() const override { return ((CustomLR1110 *)_radio)->getSNR(); }
|
||||
|
||||
bool setCodingRate(uint8_t cr) override {
|
||||
idle();
|
||||
int err = ((CustomLR1110 *)_radio)->setCodingRate(cr);
|
||||
if (err != RADIOLIB_ERR_NONE) {
|
||||
MESH_DEBUG_PRINTLN("CustomLR1110Wrapper: error: setCodingRate(%d)=%d", (uint32_t)cr, err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void setRxBoostedGainMode(bool en) override {
|
||||
((CustomLR1110 *)_radio)->setRxBoostedGainMode(en);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,15 @@ public:
|
||||
int sf = ((CustomSTM32WLx *)_radio)->spreadingFactor;
|
||||
return packetScoreInt(snr, sf, packet_len);
|
||||
}
|
||||
bool setCodingRate(uint8_t cr) override {
|
||||
idle();
|
||||
int err = ((CustomSTM32WLx *)_radio)->setCodingRate(cr);
|
||||
if (err != RADIOLIB_ERR_NONE) {
|
||||
MESH_DEBUG_PRINTLN("CustomSTM32WLxWrapper: error: setCodingRate(%d)=%d", (uint32_t)cr, err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
|
||||
};
|
||||
@@ -24,6 +24,15 @@ public:
|
||||
int sf = ((CustomSX1262 *)_radio)->spreadingFactor;
|
||||
return packetScoreInt(snr, sf, packet_len);
|
||||
}
|
||||
bool setCodingRate(uint8_t cr) override {
|
||||
idle();
|
||||
int err = ((CustomSX1262 *)_radio)->setCodingRate(cr);
|
||||
if (err != RADIOLIB_ERR_NONE) {
|
||||
MESH_DEBUG_PRINTLN("CustomSX1262Wrapper: error: setCodingRate(%d)=%d", (uint32_t)cr, err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
virtual void powerOff() override {
|
||||
((CustomSX1262 *)_radio)->sleep(false);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,15 @@ public:
|
||||
int sf = ((CustomSX1268 *)_radio)->spreadingFactor;
|
||||
return packetScoreInt(snr, sf, packet_len);
|
||||
}
|
||||
bool setCodingRate(uint8_t cr) override {
|
||||
idle();
|
||||
int err = ((CustomSX1268 *)_radio)->setCodingRate(cr);
|
||||
if (err != RADIOLIB_ERR_NONE) {
|
||||
MESH_DEBUG_PRINTLN("CustomSX1268Wrapper: error: setCodingRate(%d)=%d", (uint32_t)cr, err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
|
||||
|
||||
|
||||
@@ -23,4 +23,13 @@ public:
|
||||
int sf = ((CustomSX1276 *)_radio)->spreadingFactor;
|
||||
return packetScoreInt(snr, sf, packet_len);
|
||||
}
|
||||
bool setCodingRate(uint8_t cr) override {
|
||||
idle();
|
||||
int err = ((CustomSX1276 *)_radio)->setCodingRate(cr);
|
||||
if (err != RADIOLIB_ERR_NONE) {
|
||||
MESH_DEBUG_PRINTLN("CustomSX1276Wrapper: error: setCodingRate(%d)=%d", (uint32_t)cr, err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -7,8 +7,10 @@
|
||||
#endif
|
||||
|
||||
#ifdef ENV_INCLUDE_BME680
|
||||
#ifndef TELEM_BME680_ADDRESS
|
||||
#define TELEM_BME680_ADDRESS 0x76
|
||||
#if defined(TELEM_BME680_ADDRESS)
|
||||
uint8_t TELEM_BME680_ADDRESSES[] = { TELEM_BME680_ADDRESS };
|
||||
#else
|
||||
uint8_t TELEM_BME680_ADDRESSES[] = { 0x76, 0x77 }; // Known I2C addresses for BME680
|
||||
#endif
|
||||
#define TELEM_BME680_SEALEVELPRESSURE_HPA (1013.25)
|
||||
#include <Adafruit_BME680.h>
|
||||
@@ -28,8 +30,10 @@ static Adafruit_AHTX0 AHTX0;
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BME280
|
||||
#ifndef TELEM_BME280_ADDRESS
|
||||
#define TELEM_BME280_ADDRESS 0x76 // BME280 environmental sensor I2C address
|
||||
#if defined(TELEM_BME280_ADDRESS)
|
||||
uint8_t TELEM_BME280_ADDRESSES[] = { TELEM_BME280_ADDRESS };
|
||||
#else
|
||||
uint8_t TELEM_BME280_ADDRESSES[] = { 0x76, 0x77 }; // Known I2C addresses for BME280
|
||||
#endif
|
||||
#define TELEM_BME280_SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level
|
||||
#include <Adafruit_BME280.h>
|
||||
@@ -37,8 +41,10 @@ static Adafruit_BME280 BME280;
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BMP280
|
||||
#ifndef TELEM_BMP280_ADDRESS
|
||||
#define TELEM_BMP280_ADDRESS 0x76 // BMP280 environmental sensor I2C address
|
||||
#if defined(TELEM_BMP280_ADDRESS)
|
||||
uint8_t TELEM_BMP280_ADDRESSES[] = { TELEM_BMP280_ADDRESS };
|
||||
#else
|
||||
uint8_t TELEM_BMP280_ADDRESSES[] = { 0x76, 0x77 }; // Known I2C addresses for BMP280
|
||||
#endif
|
||||
#define TELEM_BMP280_SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level
|
||||
#include <Adafruit_BMP280.h>
|
||||
@@ -161,6 +167,12 @@ public:
|
||||
static RAK12500LocationProvider RAK12500_provider;
|
||||
#endif
|
||||
|
||||
bool EnvironmentSensorManager::i2c_probe(TwoWire &wire, uint8_t addr) {
|
||||
wire.beginTransmission(addr);
|
||||
uint8_t error = wire.endTransmission();
|
||||
return (error == 0); // If found i2c device, the error is 0
|
||||
}
|
||||
|
||||
bool EnvironmentSensorManager::begin() {
|
||||
#if ENV_INCLUDE_GPS
|
||||
#ifdef RAK_WISBLOCK_GPS
|
||||
@@ -182,7 +194,7 @@ bool EnvironmentSensorManager::begin() {
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_AHTX0
|
||||
if (AHTX0.begin(TELEM_WIRE, 0, TELEM_AHTX_ADDRESS)) {
|
||||
if (i2c_probe(*TELEM_WIRE, TELEM_AHTX_ADDRESS) && AHTX0.begin(TELEM_WIRE, 0, TELEM_AHTX_ADDRESS)) {
|
||||
MESH_DEBUG_PRINTLN("Found AHT10/AHT20 at address: %02X", TELEM_AHTX_ADDRESS);
|
||||
AHTX0_initialized = true;
|
||||
} else {
|
||||
@@ -192,46 +204,55 @@ bool EnvironmentSensorManager::begin() {
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BME680
|
||||
if (BME680.begin(TELEM_BME680_ADDRESS)) {
|
||||
MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESS);
|
||||
BME680_initialized = true;
|
||||
} else {
|
||||
BME680_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("BME680 was not found at I2C address %02X", TELEM_BME680_ADDRESS);
|
||||
for (size_t i = 0; i < sizeof(TELEM_BME680_ADDRESSES) / sizeof(TELEM_BME680_ADDRESSES[0]); i++) {
|
||||
if (i2c_probe(*TELEM_WIRE, TELEM_BME680_ADDRESSES[i]) && BME680.begin(TELEM_BME680_ADDRESSES[i], TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESSES[i]);
|
||||
BME680_initialized = true;
|
||||
break;
|
||||
} else {
|
||||
BME680_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("BME680 was not found at I2C address %02X", TELEM_BME680_ADDRESSES[i]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BME280
|
||||
if (BME280.begin(TELEM_BME280_ADDRESS, TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESS);
|
||||
MESH_DEBUG_PRINTLN("BME sensor ID: %02X", BME280.sensorID());
|
||||
// Reduce self-heating: single-shot conversions, light oversampling, long standby.
|
||||
BME280.setSampling(Adafruit_BME280::MODE_FORCED,
|
||||
for (size_t i = 0; i < sizeof(TELEM_BME280_ADDRESSES) / sizeof(TELEM_BME280_ADDRESSES[0]); i++) {
|
||||
if (i2c_probe(*TELEM_WIRE, TELEM_BME280_ADDRESSES[i]) && BME280.begin(TELEM_BME280_ADDRESSES[i], TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESSES[i]);
|
||||
MESH_DEBUG_PRINTLN("BME sensor ID: %02X", BME280.sensorID());
|
||||
// Reduce self-heating: single-shot conversions, light oversampling, long standby.
|
||||
BME280.setSampling(Adafruit_BME280::MODE_FORCED,
|
||||
Adafruit_BME280::SAMPLING_X1, // temperature
|
||||
Adafruit_BME280::SAMPLING_X1, // pressure
|
||||
Adafruit_BME280::SAMPLING_X1, // humidity
|
||||
Adafruit_BME280::FILTER_OFF,
|
||||
Adafruit_BME280::STANDBY_MS_1000);
|
||||
BME280_initialized = true;
|
||||
} else {
|
||||
BME280_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("BME280 was not found at I2C address %02X", TELEM_BME280_ADDRESS);
|
||||
BME280_initialized = true;
|
||||
break;
|
||||
} else {
|
||||
BME280_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("BME280 was not found at I2C address %02X", TELEM_BME280_ADDRESSES[i]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BMP280
|
||||
if (BMP280.begin(TELEM_BMP280_ADDRESS)) {
|
||||
MESH_DEBUG_PRINTLN("Found BMP280 at address: %02X", TELEM_BMP280_ADDRESS);
|
||||
MESH_DEBUG_PRINTLN("BMP sensor ID: %02X", BMP280.sensorID());
|
||||
BMP280_initialized = true;
|
||||
} else {
|
||||
BMP280_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("BMP280 was not found at I2C address %02X", TELEM_BMP280_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
for (size_t i = 0; i < sizeof(TELEM_BMP280_ADDRESSES) / sizeof(TELEM_BMP280_ADDRESSES[0]); i++) {
|
||||
if (i2c_probe(*TELEM_WIRE, TELEM_BMP280_ADDRESSES[i]) && BMP280.begin(TELEM_BMP280_ADDRESSES[i])) {
|
||||
MESH_DEBUG_PRINTLN("Found BMP280 at address: %02X", TELEM_BMP280_ADDRESSES[i]);
|
||||
MESH_DEBUG_PRINTLN("BMP sensor ID: %02X", BMP280.sensorID());
|
||||
BMP280_initialized = true;
|
||||
break;
|
||||
} else {
|
||||
BMP280_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("BMP280 was not found at I2C address %02X", TELEM_BMP280_ADDRESSES[i]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_SHTC3
|
||||
if (SHTC3.begin(TELEM_WIRE)) {
|
||||
if (i2c_probe(*TELEM_WIRE, 0x70) && SHTC3.begin(TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found sensor: SHTC3");
|
||||
SHTC3_initialized = true;
|
||||
} else {
|
||||
@@ -242,21 +263,23 @@ bool EnvironmentSensorManager::begin() {
|
||||
|
||||
|
||||
#if ENV_INCLUDE_SHT4X
|
||||
SHT4X.begin(*TELEM_WIRE, TELEM_SHT4X_ADDRESS);
|
||||
uint32_t serialNumber = 0;
|
||||
int16_t sht4x_error;
|
||||
sht4x_error = SHT4X.serialNumber(serialNumber);
|
||||
if (sht4x_error == 0) {
|
||||
MESH_DEBUG_PRINTLN("Found SHT4X at address: %02X", TELEM_SHT4X_ADDRESS);
|
||||
SHT4X_initialized = true;
|
||||
} else {
|
||||
SHT4X_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("SHT4X was not found at I2C address %02X", TELEM_SHT4X_ADDRESS);
|
||||
if (i2c_probe(*TELEM_WIRE, TELEM_SHT4X_ADDRESS)) {
|
||||
SHT4X.begin(*TELEM_WIRE, TELEM_SHT4X_ADDRESS);
|
||||
uint32_t serialNumber = 0;
|
||||
int16_t sht4x_error;
|
||||
sht4x_error = SHT4X.serialNumber(serialNumber);
|
||||
if (sht4x_error == 0) {
|
||||
MESH_DEBUG_PRINTLN("Found SHT4X at address: %02X", TELEM_SHT4X_ADDRESS);
|
||||
SHT4X_initialized = true;
|
||||
} else {
|
||||
SHT4X_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("SHT4X was not found at I2C address %02X", TELEM_SHT4X_ADDRESS);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_LPS22HB
|
||||
if (LPS22HB.begin()) {
|
||||
if (i2c_probe(*TELEM_WIRE, 0x5C) && LPS22HB.begin()) {
|
||||
MESH_DEBUG_PRINTLN("Found sensor: LPS22HB");
|
||||
LPS22HB_initialized = true;
|
||||
} else {
|
||||
@@ -266,7 +289,7 @@ bool EnvironmentSensorManager::begin() {
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA3221
|
||||
if (INA3221.begin(TELEM_INA3221_ADDRESS, TELEM_WIRE)) {
|
||||
if (i2c_probe(*TELEM_WIRE, TELEM_INA3221_ADDRESS) && INA3221.begin(TELEM_INA3221_ADDRESS, TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", TELEM_INA3221_ADDRESS);
|
||||
MESH_DEBUG_PRINTLN("%04X %04X", INA3221.getDieID(), INA3221.getManufacturerID());
|
||||
|
||||
@@ -281,7 +304,7 @@ bool EnvironmentSensorManager::begin() {
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA219
|
||||
if (INA219.begin(TELEM_WIRE)) {
|
||||
if (i2c_probe(*TELEM_WIRE, TELEM_INA219_ADDRESS) && INA219.begin(TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", TELEM_INA219_ADDRESS);
|
||||
INA219_initialized = true;
|
||||
} else {
|
||||
@@ -291,17 +314,17 @@ bool EnvironmentSensorManager::begin() {
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA260
|
||||
if (INA260.begin(TELEM_INA260_ADDRESS, TELEM_WIRE)) {
|
||||
if (i2c_probe(*TELEM_WIRE, TELEM_INA260_ADDRESS) && INA260.begin(TELEM_INA260_ADDRESS, TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found INA260 at address: %02X", TELEM_INA260_ADDRESS);
|
||||
INA260_initialized = true;
|
||||
} else {
|
||||
INA260_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("INA260 was not found at I2C address %02X", TELEM_INA260_ADDRESS);
|
||||
MESH_DEBUG_PRINTLN("INA260 was not found at I2C address %02X", TELEM_INA219_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA226
|
||||
if (INA226.begin()) {
|
||||
if (i2c_probe(*TELEM_WIRE, TELEM_INA226_ADDRESS) && INA226.begin()) {
|
||||
MESH_DEBUG_PRINTLN("Found INA226 at address: %02X", TELEM_INA226_ADDRESS);
|
||||
INA226.setMaxCurrentShunt(TELEM_INA226_MAX_AMP, TELEM_INA226_SHUNT_VALUE);
|
||||
INA226_initialized = true;
|
||||
@@ -312,7 +335,7 @@ bool EnvironmentSensorManager::begin() {
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_MLX90614
|
||||
if (MLX90614.begin(TELEM_MLX90614_ADDRESS, TELEM_WIRE)) {
|
||||
if (i2c_probe(*TELEM_WIRE, TELEM_MLX90614_ADDRESS) && MLX90614.begin(TELEM_MLX90614_ADDRESS, TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found MLX90614 at address: %02X", TELEM_MLX90614_ADDRESS);
|
||||
MLX90614_initialized = true;
|
||||
} else {
|
||||
@@ -322,7 +345,7 @@ bool EnvironmentSensorManager::begin() {
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_VL53L0X
|
||||
if (VL53L0X.begin(TELEM_VL53L0X_ADDRESS, false, TELEM_WIRE)) {
|
||||
if (i2c_probe(*TELEM_WIRE, TELEM_VL53L0X_ADDRESS) && VL53L0X.begin(TELEM_VL53L0X_ADDRESS, false, TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found VL53L0X at address: %02X", TELEM_VL53L0X_ADDRESS);
|
||||
VL53L0X_initialized = true;
|
||||
} else {
|
||||
@@ -334,7 +357,7 @@ bool EnvironmentSensorManager::begin() {
|
||||
#if ENV_INCLUDE_BMP085
|
||||
// First argument is MODE (aka oversampling)
|
||||
// choose ULTRALOWPOWER
|
||||
if (BMP085.begin(0, TELEM_WIRE)) {
|
||||
if (i2c_probe(*TELEM_WIRE, 0x77) && BMP085.begin(0, TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found sensor BMP085");
|
||||
BMP085_initialized = true;
|
||||
} else {
|
||||
@@ -345,7 +368,7 @@ bool EnvironmentSensorManager::begin() {
|
||||
|
||||
#if ENV_INCLUDE_RAK12035
|
||||
RAK12035.setup(*TELEM_WIRE);
|
||||
if (RAK12035.begin(TELEM_RAK12035_ADDRESS)) {
|
||||
if (i2c_probe(*TELEM_WIRE, TELEM_RAK12035_ADDRESS) && RAK12035.begin(TELEM_RAK12035_ADDRESS)) {
|
||||
MESH_DEBUG_PRINTLN("Found sensor RAK12035 at address: %02X", TELEM_RAK12035_ADDRESS);
|
||||
RAK12035_initialized = true;
|
||||
} else {
|
||||
@@ -716,7 +739,7 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){
|
||||
gps_detected = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
pinMode(ioPin, INPUT);
|
||||
MESH_DEBUG_PRINTLN("GPS did not init with this IO pin... try the next");
|
||||
return false;
|
||||
@@ -759,7 +782,7 @@ void EnvironmentSensorManager::loop() {
|
||||
|
||||
#if ENV_INCLUDE_GPS
|
||||
if (gps_active) {
|
||||
_location->loop();
|
||||
_location->loop();
|
||||
}
|
||||
if (millis() > next_gps_update) {
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ public:
|
||||
#else
|
||||
EnvironmentSensorManager(){};
|
||||
#endif
|
||||
bool i2c_probe(TwoWire& wire, uint8_t addr) override;
|
||||
bool begin() override;
|
||||
bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override;
|
||||
#if ENV_INCLUDE_GPS
|
||||
|
||||
@@ -7,7 +7,7 @@ build_flags = ${nrf52_base.build_flags}
|
||||
-I variants/gat562_30s_mesh_kit
|
||||
-D RAK_4631
|
||||
-D RAK_BOARD
|
||||
-D NRF52_POWER_MANAGEMENT
|
||||
; -D NRF52_POWER_MANAGEMENT
|
||||
-D PIN_BOARD_SCL=14
|
||||
-D PIN_BOARD_SDA=13
|
||||
-D PIN_OLED_RESET=-1
|
||||
|
||||
@@ -5,7 +5,7 @@ board_check = true
|
||||
build_flags = ${nrf52_base.build_flags}
|
||||
${sensor_base.build_flags}
|
||||
-I variants/gat562_mesh_evb_pro
|
||||
-D NRF52_POWER_MANAGEMENT
|
||||
; -D NRF52_POWER_MANAGEMENT
|
||||
-D PIN_BOARD_SCL=14
|
||||
-D PIN_BOARD_SDA=13
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
|
||||
@@ -5,7 +5,7 @@ board_check = true
|
||||
build_flags = ${nrf52_base.build_flags}
|
||||
${sensor_base.build_flags}
|
||||
-I variants/gat562_mesh_tracker_pro
|
||||
-D NRF52_POWER_MANAGEMENT
|
||||
; -D NRF52_POWER_MANAGEMENT
|
||||
-D PIN_BOARD_SCL=14
|
||||
-D PIN_BOARD_SDA=13
|
||||
-D PIN_OLED_RESET=-1
|
||||
|
||||
@@ -8,7 +8,7 @@ build_flags = ${nrf52_base.build_flags}
|
||||
-I variants/gat562_mesh_watch13
|
||||
-D RAK_4631
|
||||
-D RAK_BOARD
|
||||
-D NRF52_POWER_MANAGEMENT
|
||||
; -D NRF52_POWER_MANAGEMENT
|
||||
-D PIN_BOARD_SCL=14
|
||||
-D PIN_BOARD_SDA=13
|
||||
-D PIN_OLED_RESET=-1
|
||||
|
||||
@@ -12,10 +12,11 @@ class LoRaFEMControl
|
||||
void setRxModeEnable(void);
|
||||
void setRxModeEnableWhenMCUSleep(void);
|
||||
void setLNAEnable(bool enabled);
|
||||
bool isLnaCanControl(void) { return lna_can_control; }
|
||||
bool isLnaCanControl(void) const { return lna_can_control; }
|
||||
void setLnaCanControl(bool can_control) { lna_can_control = can_control; }
|
||||
bool isLNAEnabled(void) const { return lna_enabled; }
|
||||
|
||||
private:
|
||||
bool lna_enabled = false;
|
||||
bool lna_enabled = true;
|
||||
bool lna_can_control = false;
|
||||
};
|
||||
@@ -123,4 +123,22 @@ void T096Board::powerOff() {
|
||||
|
||||
const char* T096Board::getManufacturerName() const {
|
||||
return "Heltec T096";
|
||||
}
|
||||
}
|
||||
|
||||
bool T096Board::setLoRaFemLnaEnabled(bool enable) {
|
||||
if (!loRaFEMControl.isLnaCanControl()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
loRaFEMControl.setLNAEnable(enable);
|
||||
loRaFEMControl.setRxModeEnable();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool T096Board::canControlLoRaFemLna() const {
|
||||
return loRaFEMControl.isLnaCanControl();
|
||||
}
|
||||
|
||||
bool T096Board::isLoRaFemLnaEnabled() const {
|
||||
return loRaFEMControl.isLNAEnabled();
|
||||
}
|
||||
@@ -25,4 +25,7 @@ public:
|
||||
uint16_t getBattMilliVolts() override;
|
||||
const char* getManufacturerName() const override ;
|
||||
void powerOff() override;
|
||||
bool setLoRaFemLnaEnabled(bool enable) override;
|
||||
bool canControlLoRaFemLna() const override;
|
||||
bool isLoRaFemLnaEnabled() const override;
|
||||
};
|
||||
@@ -9,7 +9,7 @@ build_flags = ${nrf52_base.build_flags}
|
||||
-I variants/heltec_t096
|
||||
-I src/helpers/ui
|
||||
-D HELTEC_T096
|
||||
-D NRF52_POWER_MANAGEMENT
|
||||
; -D NRF52_POWER_MANAGEMENT
|
||||
-D P_LORA_DIO_1=21
|
||||
-D P_LORA_NSS=5
|
||||
-D P_LORA_RESET=16
|
||||
@@ -126,6 +126,30 @@ lib_deps =
|
||||
${Heltec_t096.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_t096_companion_radio_ble_femoff]
|
||||
extends = Heltec_t096
|
||||
board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld
|
||||
board_upload.maximum_size = 712704
|
||||
build_flags =
|
||||
${Heltec_t096.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D ENV_INCLUDE_GPS=1 ; enable the GPS page in UI
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
-D RADIO_FEM_RXGAIN=0 ; undefined (default on), 1=on, 0=off
|
||||
build_src_filter = ${Heltec_t096.build_src_filter}
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_t096.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_t096_companion_radio_usb]
|
||||
extends = Heltec_t096
|
||||
board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld
|
||||
|
||||
@@ -12,7 +12,7 @@ build_flags = ${nrf52_base.build_flags}
|
||||
-I variants/heltec_t114
|
||||
-I src/helpers/ui
|
||||
-D HELTEC_T114
|
||||
-D NRF52_POWER_MANAGEMENT
|
||||
; -D NRF52_POWER_MANAGEMENT
|
||||
-D P_LORA_DIO_1=20
|
||||
-D P_LORA_NSS=24
|
||||
-D P_LORA_RESET=25
|
||||
|
||||
@@ -99,6 +99,33 @@ lib_deps =
|
||||
${Heltec_tracker_base.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_Wireless_Tracker_companion_radio_ble_ps]
|
||||
extends = Heltec_tracker_base
|
||||
platform_packages =
|
||||
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
|
||||
build_flags =
|
||||
${Heltec_tracker_base.build_flags}
|
||||
-I src/helpers/ui
|
||||
-I examples/companion_radio/ui-new
|
||||
-D DISPLAY_ROTATION=1
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456 ; HWT will use display for pin
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_base.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
lib_deps =
|
||||
${Heltec_tracker_base.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_Wireless_Tracker_repeater]
|
||||
extends = Heltec_tracker_base
|
||||
build_flags =
|
||||
|
||||
@@ -82,3 +82,21 @@ void HeltecTrackerV2Board::begin() {
|
||||
const char* HeltecTrackerV2Board::getManufacturerName() const {
|
||||
return "Heltec Tracker V2";
|
||||
}
|
||||
|
||||
bool HeltecTrackerV2Board::setLoRaFemLnaEnabled(bool enable) {
|
||||
if (!loRaFEMControl.isLnaCanControl()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
loRaFEMControl.setLNAEnable(enable);
|
||||
loRaFEMControl.setRxModeEnable();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HeltecTrackerV2Board::canControlLoRaFemLna() const {
|
||||
return loRaFEMControl.isLnaCanControl();
|
||||
}
|
||||
|
||||
bool HeltecTrackerV2Board::isLoRaFemLnaEnabled() const {
|
||||
return loRaFEMControl.isLNAEnabled();
|
||||
}
|
||||
@@ -21,5 +21,8 @@ public:
|
||||
void powerOff() override;
|
||||
uint16_t getBattMilliVolts() override;
|
||||
const char* getManufacturerName() const override ;
|
||||
bool setLoRaFemLnaEnabled(bool enable) override;
|
||||
bool canControlLoRaFemLna() const override;
|
||||
bool isLoRaFemLnaEnabled() const override;
|
||||
|
||||
};
|
||||
@@ -12,10 +12,11 @@ class LoRaFEMControl
|
||||
void setRxModeEnable(void);
|
||||
void setRxModeEnableWhenMCUSleep(void);
|
||||
void setLNAEnable(bool enabled);
|
||||
bool isLnaCanControl(void) { return lna_can_control; }
|
||||
bool isLnaCanControl(void) const { return lna_can_control; }
|
||||
void setLnaCanControl(bool can_control) { lna_can_control = can_control; }
|
||||
bool isLNAEnabled(void) const { return lna_enabled; }
|
||||
|
||||
private:
|
||||
bool lna_enabled = false;
|
||||
bool lna_enabled = true;
|
||||
bool lna_can_control = false;
|
||||
};
|
||||
@@ -176,6 +176,33 @@ lib_deps =
|
||||
${Heltec_tracker_v2.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:heltec_tracker_v2_companion_radio_ble_ps]
|
||||
extends = Heltec_tracker_v2
|
||||
platform_packages =
|
||||
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
|
||||
build_flags =
|
||||
${Heltec_tracker_v2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_v2.build_src_filter}
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_tracker_v2.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:heltec_tracker_v2_companion_radio_wifi]
|
||||
extends = Heltec_tracker_v2
|
||||
build_flags =
|
||||
|
||||
@@ -17,12 +17,12 @@ public:
|
||||
esp_reset_reason_t reason = esp_reset_reason();
|
||||
if (reason == ESP_RST_DEEPSLEEP) {
|
||||
long wakeup_source = esp_sleep_get_ext1_wakeup_status();
|
||||
if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep)
|
||||
if (wakeup_source & (1 << P_LORA_DIO_0)) { // received a LoRa packet (while in deep sleep)
|
||||
startup_reason = BD_STARTUP_RX_PACKET;
|
||||
}
|
||||
|
||||
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
|
||||
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
|
||||
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,15 +30,15 @@ public:
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
|
||||
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
|
||||
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
|
||||
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
|
||||
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_0, RTC_GPIO_MODE_INPUT_ONLY);
|
||||
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_0);
|
||||
|
||||
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
|
||||
|
||||
if (pin_wake_btn < 0) {
|
||||
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
|
||||
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_0), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
|
||||
} else {
|
||||
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
|
||||
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_0) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
|
||||
}
|
||||
|
||||
if (secs > 0) {
|
||||
@@ -64,4 +64,8 @@ public:
|
||||
const char* getManufacturerName() const override {
|
||||
return "Heltec V2";
|
||||
}
|
||||
|
||||
uint32_t getIRQGpio() override {
|
||||
return P_LORA_DIO_0; // default for SX1276
|
||||
}
|
||||
};
|
||||
@@ -7,10 +7,10 @@ build_flags =
|
||||
-D HELTEC_LORA_V2
|
||||
-D RADIO_CLASS=CustomSX1276
|
||||
-D WRAPPER_CLASS=CustomSX1276Wrapper
|
||||
-D P_LORA_DIO_1=26
|
||||
-D P_LORA_DIO_0=26
|
||||
-D P_LORA_DIO_1=35
|
||||
-D P_LORA_NSS=18
|
||||
-D P_LORA_RESET=RADIOLIB_NC
|
||||
-D P_LORA_BUSY=RADIOLIB_NC
|
||||
-D P_LORA_RESET=14
|
||||
-D P_LORA_SCLK=5
|
||||
-D P_LORA_MISO=19
|
||||
-D P_LORA_MOSI=27
|
||||
@@ -172,6 +172,31 @@ lib_deps =
|
||||
${Heltec_lora32_v2.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_v2_companion_radio_ble_ps]
|
||||
extends = Heltec_lora32_v2
|
||||
platform_packages =
|
||||
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
|
||||
build_flags =
|
||||
${Heltec_lora32_v2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=160
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v2.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v2.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_v2_companion_radio_wifi]
|
||||
extends = Heltec_lora32_v2
|
||||
build_flags =
|
||||
|
||||
@@ -5,9 +5,9 @@ HeltecV2Board board;
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
static SPIClass spi;
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1, spi);
|
||||
#else
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1);
|
||||
#endif
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
@@ -53,6 +53,10 @@ public:
|
||||
}
|
||||
|
||||
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
|
||||
// Clear stale wakeup sources to avoid ghost wakeup
|
||||
// This is required when Power Management and automatic lightsleep are enabled
|
||||
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
|
||||
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
|
||||
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
|
||||
|
||||
@@ -179,6 +179,32 @@ lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_v3_companion_radio_ble_ps]
|
||||
extends = Heltec_lora32_v3
|
||||
platform_packages =
|
||||
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_v3_companion_radio_wifi]
|
||||
extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
@@ -320,6 +346,26 @@ lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_WSL3_companion_radio_ble_ps]
|
||||
extends = Heltec_lora32_v3
|
||||
platform_packages =
|
||||
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_WSL3_companion_radio_usb]
|
||||
extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
|
||||
@@ -33,6 +33,10 @@ void HeltecV4Board::begin() {
|
||||
}
|
||||
|
||||
void HeltecV4Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) {
|
||||
// Clear stale wakeup sources to avoid ghost wakeup
|
||||
// This is required when Power Management and automatic lightsleep are enabled
|
||||
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
|
||||
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
|
||||
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
|
||||
@@ -83,3 +87,25 @@ void HeltecV4Board::begin() {
|
||||
return loRaFEMControl.getFEMType() == KCT8103L_PA ? "Heltec V4.3 OLED" : "Heltec V4 OLED";
|
||||
#endif
|
||||
}
|
||||
|
||||
bool HeltecV4Board::setLoRaFemLnaEnabled(bool enable) {
|
||||
#if defined(RADIO_FEM_RXGAIN) && (RADIO_FEM_RXGAIN == 0)
|
||||
enable = false;
|
||||
#endif
|
||||
|
||||
if (!loRaFEMControl.isLnaCanControl()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
loRaFEMControl.setLNAEnable(enable);
|
||||
loRaFEMControl.setRxModeEnable();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HeltecV4Board::canControlLoRaFemLna() const {
|
||||
return loRaFEMControl.isLnaCanControl();
|
||||
}
|
||||
|
||||
bool HeltecV4Board::isLoRaFemLnaEnabled() const {
|
||||
return loRaFEMControl.isLNAEnabled();
|
||||
}
|
||||
@@ -17,6 +17,9 @@ public:
|
||||
void onAfterTransmit(void) override;
|
||||
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1);
|
||||
void powerOff() override;
|
||||
bool setLoRaFemLnaEnabled(bool enable) override;
|
||||
bool canControlLoRaFemLna() const override;
|
||||
bool isLoRaFemLnaEnabled() const override;
|
||||
uint16_t getBattMilliVolts() override;
|
||||
const char* getManufacturerName() const override ;
|
||||
|
||||
|
||||
@@ -18,8 +18,9 @@ class LoRaFEMControl
|
||||
void setRxModeEnable(void);
|
||||
void setRxModeEnableWhenMCUSleep(void);
|
||||
void setLNAEnable(bool enabled);
|
||||
bool isLnaCanControl(void) { return lna_can_control; }
|
||||
bool isLnaCanControl(void) const { return lna_can_control; }
|
||||
void setLnaCanControl(bool can_control) { lna_can_control = can_control; }
|
||||
bool isLNAEnabled(void) const { return lna_enabled; }
|
||||
LoRaFEMType getFEMType(void) const { return fem_type; }
|
||||
private:
|
||||
LoRaFEMType fem_type=OTHER_FEM_TYPES;
|
||||
|
||||
@@ -102,6 +102,28 @@ lib_deps =
|
||||
${esp32_ota.lib_deps}
|
||||
bakercp/CRC32 @ ^2.0.0
|
||||
|
||||
[env:heltec_v4_expansionkit_repeater]
|
||||
extends = heltec_v4_oled
|
||||
build_flags =
|
||||
${heltec_v4_oled.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"Heltec Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
-D ENV_PIN_SDA=4
|
||||
-D ENV_PIN_SCL=3
|
||||
build_src_filter = ${heltec_v4_oled.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${heltec_v4_oled.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
bakercp/CRC32 @ ^2.0.0
|
||||
|
||||
[env:heltec_v4_repeater_bridge_espnow]
|
||||
extends = heltec_v4_oled
|
||||
build_flags =
|
||||
@@ -200,6 +222,59 @@ lib_deps =
|
||||
${heltec_v4_oled.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:heltec_v4_companion_radio_ble_ps]
|
||||
extends = heltec_v4_oled
|
||||
platform_packages =
|
||||
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
|
||||
build_flags =
|
||||
${heltec_v4_oled.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${heltec_v4_oled.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${heltec_v4_oled.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:heltec_v4_3_companion_radio_ble_ps_femoff]
|
||||
extends = heltec_v4_oled
|
||||
platform_packages =
|
||||
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
|
||||
build_flags =
|
||||
${heltec_v4_oled.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
-D RADIO_FEM_RXGAIN=0 ; undefined (default on), 1=on, 0=off
|
||||
build_src_filter = ${heltec_v4_oled.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${heltec_v4_oled.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:heltec_v4_companion_radio_wifi]
|
||||
extends = heltec_v4_oled
|
||||
build_flags =
|
||||
@@ -207,7 +282,6 @@ build_flags =
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
-D WIFI_SSID='"myssid"'
|
||||
|
||||
@@ -64,6 +64,29 @@ lib_deps =
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
bakercp/CRC32 @ ^2.0.0
|
||||
|
||||
[env:Heltec_Wireless_Paper_companion_radio_ble_ps]
|
||||
extends = Heltec_Wireless_Paper_base
|
||||
platform_packages =
|
||||
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
|
||||
build_flags =
|
||||
${Heltec_Wireless_Paper_base.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=E213Display
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter}
|
||||
+<helpers/ui/E213Display.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_Wireless_Paper_base.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
bakercp/CRC32 @ ^2.0.0
|
||||
|
||||
[env:Heltec_Wireless_Paper_companion_radio_usb]
|
||||
extends = Heltec_Wireless_Paper_base
|
||||
build_flags =
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <helpers/ESP32Board.h>
|
||||
|
||||
class LilygoT3S3SX1276Board : public ESP32Board {
|
||||
public:
|
||||
uint32_t getIRQGpio() override {
|
||||
return P_LORA_DIO_0; // default for SX1276
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
|
||||
ESP32Board board;
|
||||
LilygoT3S3SX1276Board board;
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
static SPIClass spi;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <helpers/ESP32Board.h>
|
||||
#include <LilygoT3S3SX1276Board.h>
|
||||
#include <helpers/radiolib/CustomSX1276Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <helpers/ui/MomentaryButton.h>
|
||||
#endif
|
||||
|
||||
extern ESP32Board board;
|
||||
extern LilygoT3S3SX1276Board board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern SensorManager sensors;
|
||||
|
||||
@@ -146,6 +146,31 @@ lib_deps =
|
||||
${LilyGo_TBeam_1W.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
; === LILYGO T-Beam 1W Companion Radio PS (BLE PS) ===
|
||||
[env:LilyGo_TBeam_1W_companion_radio_ble_ps]
|
||||
extends = LilyGo_TBeam_1W
|
||||
platform_packages =
|
||||
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
|
||||
build_flags =
|
||||
${LilyGo_TBeam_1W.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D PERSISTANT_GPS=1
|
||||
-D ENV_SKIP_GPS_DETECT=1
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_TBeam_1W.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${LilyGo_TBeam_1W.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
; === LILYGO T-Beam 1W Companion Radio (WiFi) ===
|
||||
[env:LilyGo_TBeam_1W_companion_radio_wifi]
|
||||
extends = LilyGo_TBeam_1W
|
||||
|
||||
@@ -5,6 +5,13 @@ build_flags =
|
||||
${esp32_base.build_flags}
|
||||
-I variants/lilygo_tbeam_SX1262
|
||||
-D TBEAM_SX1262
|
||||
-D P_LORA_DIO_0=26
|
||||
-D P_LORA_DIO_1=33
|
||||
-D P_LORA_NSS=18
|
||||
-D P_LORA_RESET=23
|
||||
-D P_LORA_SCLK=5
|
||||
-D P_LORA_MISO=19
|
||||
-D P_LORA_MOSI=27
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=true
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <helpers/esp32/TBeamBoard.h>
|
||||
|
||||
class LilygoTBeamSX1276Board : public TBeamBoard {
|
||||
public:
|
||||
uint32_t getIRQGpio() override {
|
||||
return P_LORA_DIO_0; // default for SX1276
|
||||
}
|
||||
};
|
||||
@@ -5,6 +5,13 @@ build_flags =
|
||||
${esp32_base.build_flags}
|
||||
-I variants/lilygo_tbeam_SX1276
|
||||
-D TBEAM_SX1276
|
||||
-D P_LORA_DIO_0=26
|
||||
-D P_LORA_DIO_1=33
|
||||
-D P_LORA_NSS=18
|
||||
-D P_LORA_RESET=23
|
||||
-D P_LORA_SCLK=5
|
||||
-D P_LORA_MISO=19
|
||||
-D P_LORA_MOSI=27
|
||||
-D SX127X_CURRENT_LIMIT=120
|
||||
-D RADIO_CLASS=CustomSX1276
|
||||
-D WRAPPER_CLASS=CustomSX1276Wrapper
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
|
||||
TBeamBoard board;
|
||||
LilygoTBeamSX1276Board board;
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
static SPIClass spi;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
//#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <helpers/esp32/TBeamBoard.h>
|
||||
#include <LilygoTBeamSX1276Board.h>
|
||||
#include <helpers/radiolib/CustomSX1276Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <helpers/ui/MomentaryButton.h>
|
||||
#endif
|
||||
|
||||
extern TBeamBoard board;
|
||||
extern LilygoTBeamSX1276Board board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern EnvironmentSensorManager sensors;
|
||||
|
||||
@@ -5,6 +5,13 @@ build_flags =
|
||||
${esp32_base.build_flags}
|
||||
-I variants/lilygo_tbeam_supreme_SX1262
|
||||
-D TBEAM_SUPREME_SX1262
|
||||
-D P_LORA_DIO_0=26
|
||||
-D P_LORA_DIO_1=33
|
||||
-D P_LORA_NSS=18
|
||||
-D P_LORA_RESET=23
|
||||
-D P_LORA_SCLK=5
|
||||
-D P_LORA_MISO=19
|
||||
-D P_LORA_MOSI=27
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D USE_SX1262
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <helpers/ESP32Board.h>
|
||||
|
||||
class TETHEliteBoard : public ESP32Board {
|
||||
public:
|
||||
const char* getManufacturerName() const override {
|
||||
return "LilyGO T-ETH Elite";
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
[LilyGo_TETH_Elite_sx1262]
|
||||
extends = esp32_base
|
||||
board = esp32s3box
|
||||
board_build.partitions = default_16MB.csv
|
||||
board_upload.flash_size = 16MB
|
||||
build_flags =
|
||||
${esp32_base.build_flags}
|
||||
-I variants/lilygo_teth_elite
|
||||
-D BOARD_HAS_PSRAM
|
||||
-D LILYGO_TETH_ELITE
|
||||
-D LILYGO_T_ETH_ELITE_ESP32S3
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
-D P_LORA_DIO_1=8
|
||||
-D P_LORA_NSS=40
|
||||
-D P_LORA_RESET=46
|
||||
-D P_LORA_BUSY=16
|
||||
-D P_LORA_SCLK=10
|
||||
-D P_LORA_MISO=9
|
||||
-D P_LORA_MOSI=11
|
||||
-D P_LORA_TX_LED=38
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=true
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D USE_SX1262
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=8
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
build_src_filter = ${esp32_base.build_src_filter}
|
||||
+<../variants/lilygo_teth_elite>
|
||||
lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
|
||||
[env:LilyGo_TETH_Elite_sx1262_repeater]
|
||||
extends = LilyGo_TETH_Elite_sx1262
|
||||
build_flags =
|
||||
${LilyGo_TETH_Elite_sx1262.build_flags}
|
||||
-D ADVERT_NAME='"T-ETH Elite Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_TETH_Elite_sx1262.build_src_filter}
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${LilyGo_TETH_Elite_sx1262.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:LilyGo_TETH_Elite_sx1262_room_server]
|
||||
extends = LilyGo_TETH_Elite_sx1262
|
||||
build_flags =
|
||||
${LilyGo_TETH_Elite_sx1262.build_flags}
|
||||
-D ADVERT_NAME='"T-ETH Elite Room"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ROOM_PASSWORD='"hello"'
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_TETH_Elite_sx1262.build_src_filter}
|
||||
+<../examples/simple_room_server>
|
||||
lib_deps =
|
||||
${LilyGo_TETH_Elite_sx1262.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:LilyGo_TETH_Elite_sx1262_companion_radio_usb]
|
||||
extends = LilyGo_TETH_Elite_sx1262
|
||||
build_flags =
|
||||
${LilyGo_TETH_Elite_sx1262.build_flags}
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_TETH_Elite_sx1262.build_src_filter}
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
lib_deps =
|
||||
${LilyGo_TETH_Elite_sx1262.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:LilyGo_TETH_Elite_sx1262_companion_radio_ble]
|
||||
extends = LilyGo_TETH_Elite_sx1262
|
||||
build_flags =
|
||||
${LilyGo_TETH_Elite_sx1262.build_flags}
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_TETH_Elite_sx1262.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
lib_deps =
|
||||
${LilyGo_TETH_Elite_sx1262.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
@@ -0,0 +1,43 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
|
||||
TETHEliteBoard board;
|
||||
|
||||
static SPIClass spi(HSPI);
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
ESP32RTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
SensorManager sensors;
|
||||
|
||||
#ifndef LORA_CR
|
||||
#define LORA_CR 5
|
||||
#endif
|
||||
|
||||
bool radio_init() {
|
||||
fallback_clock.begin();
|
||||
rtc_clock.begin(Wire);
|
||||
|
||||
return radio.std_init(&spi);
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(int8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
#include "TETHEliteBoard.h"
|
||||
|
||||
extern TETHEliteBoard board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern SensorManager sensors;
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||
void radio_set_tx_power(int8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
@@ -21,4 +21,8 @@ public:
|
||||
|
||||
return (2 * raw);
|
||||
}
|
||||
|
||||
uint32_t getIRQGpio() override {
|
||||
return P_LORA_DIO_0; // default for SX1276
|
||||
}
|
||||
};
|
||||
@@ -6,7 +6,7 @@ build_flags = ${nrf52_base.build_flags}
|
||||
${sensor_base.build_flags}
|
||||
-I variants/rak3401
|
||||
-D RAK_3401
|
||||
-D NRF52_POWER_MANAGEMENT
|
||||
; -D NRF52_POWER_MANAGEMENT
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
|
||||
@@ -7,7 +7,7 @@ build_flags = ${nrf52_base.build_flags}
|
||||
-I variants/rak4631
|
||||
-D RAK_4631
|
||||
-D RAK_BOARD
|
||||
-D NRF52_POWER_MANAGEMENT
|
||||
; -D NRF52_POWER_MANAGEMENT
|
||||
-D PIN_BOARD_SCL=14
|
||||
-D PIN_BOARD_SDA=13
|
||||
-D PIN_GPS_TX=PIN_SERIAL1_RX
|
||||
|
||||
@@ -10,7 +10,7 @@ build_flags = ${nrf52_base.build_flags}
|
||||
-I src/helpers/nrf52
|
||||
-D NRF52_PLATFORM=1
|
||||
-D USE_SX1262
|
||||
-D NRF52_POWER_MANAGEMENT
|
||||
; -D NRF52_POWER_MANAGEMENT
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D P_LORA_TX_LED=11
|
||||
|
||||
@@ -90,6 +90,28 @@ lib_deps =
|
||||
${esp32_ota.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Xiao_C3_companion_radio_ble_ps]
|
||||
extends = Xiao_esp32_C3
|
||||
platform_packages =
|
||||
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
|
||||
build_src_filter = ${Xiao_esp32_C3.build_src_filter}
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
build_flags =
|
||||
${Xiao_esp32_C3.build_flags}
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps =
|
||||
${Xiao_esp32_C3.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
board_build.partitions = min_spiffs.csv ; get around 4mb flash limit
|
||||
|
||||
[env:Xiao_C3_companion_radio_usb]
|
||||
extends = Xiao_esp32_C3
|
||||
build_src_filter = ${Xiao_esp32_C3.build_src_filter}
|
||||
|
||||
@@ -9,7 +9,7 @@ build_flags = ${nrf52_base.build_flags}
|
||||
-I variants/xiao_nrf52
|
||||
-UENV_INCLUDE_GPS
|
||||
-D NRF52_PLATFORM
|
||||
-D NRF52_POWER_MANAGEMENT
|
||||
; -D NRF52_POWER_MANAGEMENT
|
||||
-D XIAO_NRF52
|
||||
-D USE_SX1262
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/ESP32Board.h>
|
||||
|
||||
class XiaoS3Board : public ESP32Board {
|
||||
public:
|
||||
XiaoS3Board() { }
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "Xiao S3";
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,180 @@
|
||||
[Xiao_S3]
|
||||
extends = esp32_base
|
||||
board = seeed_xiao_esp32s3
|
||||
board_check = true
|
||||
board_build.mcu = esp32s3
|
||||
build_flags = ${esp32_base.build_flags}
|
||||
${sensor_base.build_flags}
|
||||
-I variants/xiao_s3
|
||||
-UENV_INCLUDE_GPS
|
||||
-D SEEED_XIAO_S3
|
||||
-D PIN_VBAT_READ=1 ; D0
|
||||
-D P_LORA_DIO_1=2 ; D1
|
||||
-D P_LORA_NSS=5 ; D4
|
||||
-D P_LORA_RESET=3 ; D2
|
||||
-D P_LORA_BUSY=4 ; D3
|
||||
-D P_LORA_SCLK=7 ; D8
|
||||
-D P_LORA_MISO=8 ; D0
|
||||
-D P_LORA_MOSI=9 ; D10
|
||||
-D PIN_USER_BTN=-1 ; NC
|
||||
-D PIN_STATUS_LED=21 ; Orange user led, LOW=On
|
||||
-D PIN_BOARD_SDA=D6 ; D6=43
|
||||
-D PIN_BOARD_SCL=D7 ; D7=44
|
||||
-D SX126X_RXEN=6 ; D5
|
||||
-D SX126X_TXEN=RADIOLIB_NC
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=true
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
build_src_filter = ${esp32_base.build_src_filter}
|
||||
+<../variants/xiao_s3>
|
||||
+<helpers/sensors>
|
||||
lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
${sensor_base.lib_deps}
|
||||
|
||||
[env:Xiao_S3_repeater]
|
||||
extends = Xiao_S3
|
||||
build_src_filter = ${Xiao_S3.build_src_filter}
|
||||
+<../examples/simple_repeater/*.cpp>
|
||||
build_flags =
|
||||
${Xiao_S3.build_flags}
|
||||
-D ADVERT_NAME='"Xiao S3 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps =
|
||||
${Xiao_S3.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Xiao_S3_repeater_bridge_espnow]
|
||||
extends = Xiao_S3
|
||||
build_src_filter = ${Xiao_S3.build_src_filter}
|
||||
+<helpers/bridges/ESPNowBridge.cpp>
|
||||
+<../examples/simple_repeater/*.cpp>
|
||||
build_flags =
|
||||
${Xiao_S3.build_flags}
|
||||
-D ADVERT_NAME='"ESPNow Bridge"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps =
|
||||
${Xiao_S3.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Xiao_S3_room_server]
|
||||
extends = Xiao_S3
|
||||
build_src_filter = ${Xiao_S3.build_src_filter}
|
||||
+<../examples/simple_room_server>
|
||||
build_flags =
|
||||
${Xiao_S3.build_flags}
|
||||
-D ADVERT_NAME='"Xiao S3 Room"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ROOM_PASSWORD='"hello"'
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps =
|
||||
${Xiao_S3.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Xiao_S3_companion_radio_ble]
|
||||
extends = Xiao_S3
|
||||
build_flags =
|
||||
${Xiao_S3.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Xiao_S3.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Xiao_S3.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
|
||||
[env:Xiao_S3_companion_radio_ble_ps]
|
||||
extends = Xiao_S3
|
||||
platform_packages =
|
||||
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
|
||||
build_flags =
|
||||
${Xiao_S3.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Xiao_S3.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Xiao_S3.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
|
||||
[env:Xiao_S3_companion_radio_usb]
|
||||
extends = Xiao_S3
|
||||
build_flags =
|
||||
${Xiao_S3.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Xiao_S3.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Xiao_S3.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
|
||||
[env:Xiao_S3_sensor]
|
||||
extends = Xiao_S3
|
||||
build_src_filter = ${Xiao_S3.build_src_filter}
|
||||
+<../examples/simple_sensor>
|
||||
build_flags =
|
||||
${Xiao_S3.build_flags}
|
||||
-D ADVERT_NAME='"Xiao S3 Sensor"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps =
|
||||
${Xiao_S3.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
@@ -0,0 +1,56 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
|
||||
XiaoS3Board board;
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
static SPIClass spi;
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
|
||||
#else
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#endif
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
ESP32RTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
EnvironmentSensorManager sensors;
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
DISPLAY_CLASS display;
|
||||
MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
|
||||
#endif
|
||||
|
||||
bool radio_init() {
|
||||
fallback_clock.begin();
|
||||
rtc_clock.begin(Wire);
|
||||
pinMode(21, INPUT);
|
||||
pinMode(48, OUTPUT);
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
|
||||
return radio.std_init(&spi);
|
||||
#else
|
||||
return radio.std_init();
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(int8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <helpers/ESP32Board.h>
|
||||
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include <helpers/ui/SSD1306Display.h>
|
||||
#include <helpers/ui/MomentaryButton.h>
|
||||
#endif
|
||||
#include "XiaoS3Board.h"
|
||||
|
||||
extern XiaoS3Board board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern EnvironmentSensorManager sensors;
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
extern DISPLAY_CLASS display;
|
||||
extern MomentaryButton user_btn;
|
||||
#endif
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||
void radio_set_tx_power(int8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
Loaded 100 of 101 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user