Compare commits
41
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d237f8730 | ||
|
|
ca0679e999 | ||
|
|
bb8e7140a9 | ||
|
|
c244effb52 | ||
|
|
b475bde730 | ||
|
|
ae67293bcc | ||
|
|
3a6766b0fd | ||
|
|
9f81ffc692 | ||
|
|
4e1002029c | ||
|
|
8dcd4975d5 | ||
|
|
e0b19a1f60 | ||
|
|
93bd0f0869 | ||
|
|
d4febe7867 | ||
|
|
674beb0e47 | ||
|
|
ea3f7acb4d | ||
|
|
3281ea2924 | ||
|
|
517d3ed643 | ||
|
|
eac4f7201e | ||
|
|
7f02cbe80b | ||
|
|
6c20a1062f | ||
|
|
e608503aa0 | ||
|
|
fa9f3bd935 | ||
|
|
181ddb2268 | ||
|
|
8fadd31159 | ||
|
|
5a45d54afe | ||
|
|
aa400da945 | ||
|
|
a44137662e | ||
|
|
0243ec41e8 | ||
|
|
756268e2ee | ||
|
|
0702065dd2 | ||
|
|
c5d8ada27d | ||
|
|
9c2ac5aa1c | ||
|
|
577433ce47 | ||
|
|
6a53f6de27 | ||
|
|
fae6dd83fd | ||
|
|
8525b4e980 | ||
|
|
d8552e3344 | ||
|
|
4c393348df | ||
|
|
166f804da1 | ||
|
|
0ce1a55017 | ||
|
|
00c14cd88c |
No files matched your search
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
@@ -173,6 +173,32 @@ lib_deps =
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
|
||||
[env:Xiao_S3_WIO_companion_radio_ble_ps]
|
||||
extends = Xiao_S3_WIO
|
||||
platform_packages =
|
||||
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
|
||||
build_flags =
|
||||
${Xiao_S3_WIO.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_WIO.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_WIO.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
|
||||
[env:Xiao_S3_WIO_companion_radio_serial]
|
||||
extends = Xiao_S3_WIO
|
||||
build_flags =
|
||||
|
||||
Reference in new issue
Block a user