Drive Buildroot radio config from JSON

This commit is contained in:
Yellowcooln
2026-04-23 16:01:32 -04:00
parent cc9c81de2a
commit ab2c82db16
2 changed files with 327 additions and 68 deletions
+254 -68
View File
@@ -23,6 +23,9 @@ PIWHEELS_INDEX_URL="https://www.piwheels.org/simple"
R2_ENABLED=1
PYMC_CORE_REPO="${PYMC_CORE_REPO:-https://github.com/rightup/pyMC_core.git}"
PYMC_CORE_REF="${PYMC_CORE_REF:-dev}"
RADIO_SETTINGS_JSON="$SCRIPT_DIR/radio-settings.json"
RADIO_PRESETS_JSON="$SCRIPT_DIR/radio-presets.json"
BUILDROOT_RADIO_SETTINGS_JSON="$SCRIPT_DIR/radio-settings-buildroot.json"
set_wheel_dependencies() {
set -- \
"cherrypy>=18.0.0" \
@@ -73,10 +76,6 @@ prompt_value() {
local default_value="${2:-}"
local reply=""
if [ -n "${reply:-}" ]; then
:
fi
if [ ! -t 0 ]; then
printf '%s\n' "$default_value"
return 0
@@ -125,14 +124,6 @@ prompt_secret() {
done
}
normalize_radio_profile() {
case "$1" in
1|v2|V2|pimesh-v2|pimesh_v2|pimesh2|PiMeshV2) printf 'v2\n' ;;
2|v1|V1|meshadv|MeshAdv|pimesh-v1|pimesh_v1|pimesh1) printf 'v1\n' ;;
*) return 1 ;;
esac
}
ensure_root() {
[ "$(id -u 2>/dev/null || echo 1)" -eq 0 ] || fail "This command must be run as root."
}
@@ -466,6 +457,146 @@ else:
PY
}
list_buildroot_boards() {
python3 - "$BUILDROOT_RADIO_SETTINGS_JSON" <<'PY'
import json
import sys
with open(sys.argv[1], "r", encoding="utf-8") as fh:
data = json.load(fh)
for index, (key, entry) in enumerate(data.get("buildroot_hardware", {}).items(), start=1):
print(f"{index}|{key}|{entry.get('name', key)}|{entry.get('description', '')}")
PY
}
resolve_buildroot_board() {
local choice="$1"
python3 - "$BUILDROOT_RADIO_SETTINGS_JSON" "$choice" <<'PY'
import json
import sys
with open(sys.argv[1], "r", encoding="utf-8") as fh:
data = json.load(fh)
choice = sys.argv[2].strip().lower()
boards = list((data.get("buildroot_hardware") or {}).items())
for index, (key, entry) in enumerate(boards, start=1):
aliases = {str(index), key.lower(), str(entry.get("name", "")).lower()}
aliases.update(alias.lower() for alias in entry.get("aliases", []))
if choice in aliases:
print(key)
raise SystemExit(0)
raise SystemExit(1)
PY
}
get_default_buildroot_board() {
python3 - "$BUILDROOT_RADIO_SETTINGS_JSON" <<'PY'
import json
import sys
with open(sys.argv[1], "r", encoding="utf-8") as fh:
data = json.load(fh)
default_board = data.get("default_board")
boards = data.get("buildroot_hardware", {})
if default_board and default_board in boards:
print(default_board)
else:
for key in boards:
print(key)
break
PY
}
get_buildroot_board_label() {
local board_key="$1"
python3 - "$BUILDROOT_RADIO_SETTINGS_JSON" "$board_key" <<'PY'
import json
import sys
with open(sys.argv[1], "r", encoding="utf-8") as fh:
data = json.load(fh)
entry = (data.get("buildroot_hardware") or {}).get(sys.argv[2], {})
print(entry.get("name", sys.argv[2]))
PY
}
list_radio_presets() {
python3 - "$RADIO_PRESETS_JSON" <<'PY'
import json
import sys
with open(sys.argv[1], "r", encoding="utf-8") as fh:
data = json.load(fh)
entries = ((data.get("config") or {}).get("suggested_radio_settings") or {}).get("entries", [])
for index, entry in enumerate(entries, start=1):
print(f"{index}|{entry.get('title', '')}|{entry.get('description', '')}")
PY
}
resolve_radio_preset() {
local choice="$1"
python3 - "$RADIO_PRESETS_JSON" "$choice" <<'PY'
import json
import sys
with open(sys.argv[1], "r", encoding="utf-8") as fh:
data = json.load(fh)
choice = sys.argv[2].strip().lower()
entries = ((data.get("config") or {}).get("suggested_radio_settings") or {}).get("entries", [])
for index, entry in enumerate(entries, start=1):
title = entry.get("title", "")
if choice in {str(index), title.lower()}:
print(title)
raise SystemExit(0)
raise SystemExit(1)
PY
}
get_default_radio_preset() {
python3 - "$BUILDROOT_RADIO_SETTINGS_JSON" <<'PY'
import json
import sys
with open(sys.argv[1], "r", encoding="utf-8") as fh:
data = json.load(fh)
print(data.get("default_radio_preset", ""))
PY
}
get_radio_preset_field() {
local preset_title="$1"
local field="$2"
python3 - "$RADIO_PRESETS_JSON" "$preset_title" "$field" <<'PY'
import json
import sys
with open(sys.argv[1], "r", encoding="utf-8") as fh:
data = json.load(fh)
preset_title, field = sys.argv[2:4]
entries = ((data.get("config") or {}).get("suggested_radio_settings") or {}).get("entries", [])
for entry in entries:
if entry.get("title") == preset_title:
print(entry.get(field, ""))
raise SystemExit(0)
raise SystemExit(1)
PY
}
get_radio_frequency_mhz() {
python3 - "$CONFIG_DIR/config.yaml" <<'PY'
import yaml
@@ -488,6 +619,56 @@ print(f"{float(bw) / 1000:.3f}".rstrip("0").rstrip("."))
PY
}
select_buildroot_board() {
local choice="${LUCKFOX_RADIO_PROFILE:-${PYMC_RADIO_PROFILE:-${PYMC_BUILDROOT_BOARD:-}}}"
local default_board
default_board=$(get_default_buildroot_board)
if [ -n "$choice" ]; then
resolve_buildroot_board "$choice" || fail "Unknown Buildroot board choice: $choice"
return 0
fi
printf 'Select Luckfox radio board:\n' >&2
while IFS='|' read -r index key name description; do
[ -n "$index" ] || continue
printf ' %s) %s' "$index" "$name" >&2
[ -n "$description" ] && printf ' - %s' "$description" >&2
printf '\n' >&2
done <<EOF
$(list_buildroot_boards)
EOF
choice=$(prompt_value "Board" "$default_board")
resolve_buildroot_board "$choice" || fail "Unknown Buildroot board choice: $choice"
}
select_radio_preset() {
local choice="${PYMC_RADIO_PRESET:-}"
local default_preset
default_preset=$(get_default_radio_preset)
if [ -n "$choice" ]; then
resolve_radio_preset "$choice" || fail "Unknown radio preset choice: $choice"
return 0
fi
printf 'Select radio preset:\n' >&2
while IFS='|' read -r index title description; do
[ -n "$index" ] || continue
printf ' %s) %s' "$index" "$title" >&2
[ -n "$description" ] && printf ' - %s' "$description" >&2
printf '\n' >&2
done <<EOF
$(list_radio_presets)
EOF
choice=$(prompt_value "Preset" "$default_preset")
resolve_radio_preset "$choice" || fail "Unknown radio preset choice: $choice"
}
write_repeater_config() {
local node_name="$1"
local admin_password="$2"
@@ -497,21 +678,52 @@ write_repeater_config() {
local bw_khz="$6"
local coding_rate="$7"
local tx_power="$8"
local profile="$9"
local board_key="$9"
python3 - "$CONFIG_DIR/config.yaml" "$node_name" "$admin_password" "$jwt_secret" "$freq_mhz" "$sf" "$bw_khz" "$coding_rate" "$tx_power" "$profile" <<'PY'
python3 - "$CONFIG_DIR/config.yaml" "$RADIO_SETTINGS_JSON" "$BUILDROOT_RADIO_SETTINGS_JSON" "$node_name" "$admin_password" "$jwt_secret" "$freq_mhz" "$sf" "$bw_khz" "$coding_rate" "$tx_power" "$board_key" <<'PY'
import json
import sys
import yaml
config_path, node_name, admin_password, jwt_secret, freq_mhz, sf, bw_khz, coding_rate, tx_power, profile = sys.argv[1:11]
(
config_path,
radio_settings_path,
buildroot_settings_path,
node_name,
admin_password,
jwt_secret,
freq_mhz,
sf,
bw_khz,
coding_rate,
tx_power,
board_key,
) = sys.argv[1:13]
with open(config_path, "r", encoding="utf-8") as fh:
data = yaml.safe_load(fh) or {}
with open(radio_settings_path, "r", encoding="utf-8") as fh:
radio_settings = json.load(fh)
with open(buildroot_settings_path, "r", encoding="utf-8") as fh:
buildroot_settings = json.load(fh)
board = (buildroot_settings.get("buildroot_hardware") or {}).get(board_key)
if not board:
raise SystemExit(f"Unknown Buildroot board: {board_key}")
hardware = ((radio_settings.get("hardware") or {}).get(board.get("hardware_id")) or {}).copy()
sx1262 = hardware.copy()
sx1262.update(board.get("sx1262_overrides") or {})
sx1262.setdefault("bus_id", 0)
sx1262.setdefault("cs_id", 0)
sx1262.setdefault("txled_pin", -1)
sx1262.setdefault("rxled_pin", -1)
sx1262.setdefault("is_waveshare", False)
repeater = data.setdefault("repeater", {})
security = repeater.setdefault("security", {})
radio = data.setdefault("radio", {})
sx1262 = data.setdefault("sx1262", {})
repeater["node_name"] = node_name
security["admin_password"] = admin_password
@@ -523,55 +735,23 @@ radio["bandwidth"] = int(round(float(bw_khz) * 1000))
radio["coding_rate"] = int(coding_rate)
radio["tx_power"] = int(tx_power)
sx1262["bus_id"] = 0
sx1262["cs_id"] = 0
sx1262["txled_pin"] = -1
sx1262["rxled_pin"] = -1
sx1262["dio3_tcxo_voltage"] = 1.8
sx1262["use_dio3_tcxo"] = True
sx1262["is_waveshare"] = False
data["radio_type"] = hardware.get("radio_type", "sx1262")
data["sx1262"] = sx1262
if profile == "v2":
sx1262["cs_pin"] = -1
sx1262["reset_pin"] = 54
sx1262["busy_pin"] = 122
sx1262["irq_pin"] = 121
sx1262["en_pin"] = 0
sx1262["txen_pin"] = -1
sx1262["rxen_pin"] = -1
sx1262["use_dio2_rf"] = True
else:
sx1262["cs_pin"] = 145
sx1262["reset_pin"] = 54
sx1262["busy_pin"] = 123
sx1262["irq_pin"] = 55
sx1262["en_pin"] = -1
sx1262["txen_pin"] = 52
sx1262["rxen_pin"] = 53
sx1262["use_dio2_rf"] = False
if data["radio_type"] == "sx1262_ch341":
ch341 = data.setdefault("ch341", {})
if "vid" in hardware:
ch341["vid"] = hardware["vid"]
if "pid" in hardware:
ch341["pid"] = hardware["pid"]
with open(config_path, "w", encoding="utf-8") as fh:
yaml.safe_dump(data, fh, sort_keys=False)
PY
}
select_radio_profile() {
local choice="${LUCKFOX_RADIO_PROFILE:-${PYMC_RADIO_PROFILE:-}}"
if [ -n "$choice" ]; then
normalize_radio_profile "$choice" || fail "Unknown radio profile choice: $choice"
return 0
fi
printf 'Select Luckfox radio profile:\n' >&2
printf ' 1) PiMesh V2\n' >&2
printf ' 2) PiMesh V1 / MeshAdv\n' >&2
choice=$(prompt_value "Profile" "1")
normalize_radio_profile "$choice" || fail "Unknown radio profile choice: $choice"
}
seed_repeater_config() {
local node_name admin_password jwt_secret profile freq_mhz sf bw_khz coding_rate tx_power
local node_name admin_password jwt_secret board_key board_name preset_title freq_mhz sf bw_khz coding_rate tx_power
stage "Configuring repeater"
@@ -579,7 +759,9 @@ seed_repeater_config() {
[ -n "$node_name" ] || node_name=$(prompt_value "Repeater name" "$(get_config_value repeater.node_name luckfox-repeater)")
[ -n "$node_name" ] || fail "Repeater name cannot be empty."
profile=$(select_radio_profile)
board_key=$(select_buildroot_board)
board_name=$(get_buildroot_board_label "$board_key")
info "Selected board: $board_name"
admin_password="${PYMC_ADMIN_PASSWORD:-}"
[ -n "$admin_password" ] || admin_password=$(prompt_secret "Admin password")
@@ -587,22 +769,25 @@ seed_repeater_config() {
jwt_secret="${PYMC_JWT_SECRET:-}"
[ -n "$jwt_secret" ] || jwt_secret=$(python3 -c 'import secrets; print(secrets.token_hex(32))')
preset_title=$(select_radio_preset)
info "Using preset: $preset_title"
freq_mhz="${PYMC_RADIO_FREQUENCY_MHZ:-}"
[ -n "$freq_mhz" ] || freq_mhz=$(prompt_value "Frequency MHz" "$(get_radio_frequency_mhz)")
[ -n "$freq_mhz" ] || freq_mhz=$(prompt_value "Frequency MHz" "$(get_radio_preset_field "$preset_title" frequency)")
sf="${PYMC_RADIO_SF:-}"
[ -n "$sf" ] || sf=$(prompt_value "Spreading factor" "$(get_config_value radio.spreading_factor 7)")
[ -n "$sf" ] || sf=$(prompt_value "Spreading factor" "$(get_radio_preset_field "$preset_title" spreading_factor)")
bw_khz="${PYMC_RADIO_BANDWIDTH_KHZ:-}"
[ -n "$bw_khz" ] || bw_khz=$(prompt_value "Bandwidth kHz" "$(get_radio_bandwidth_khz)")
[ -n "$bw_khz" ] || bw_khz=$(prompt_value "Bandwidth kHz" "$(get_radio_preset_field "$preset_title" bandwidth)")
coding_rate="${PYMC_RADIO_CODING_RATE:-}"
[ -n "$coding_rate" ] || coding_rate=$(prompt_value "Coding rate" "$(get_config_value radio.coding_rate 5)")
[ -n "$coding_rate" ] || coding_rate=$(prompt_value "Coding rate" "$(get_radio_preset_field "$preset_title" coding_rate)")
tx_power="${PYMC_RADIO_TX_POWER_DBM:-}"
[ -n "$tx_power" ] || tx_power=$(prompt_value "TX power dBm" "$(get_config_value radio.tx_power 22)")
write_repeater_config "$node_name" "$admin_password" "$jwt_secret" "$freq_mhz" "$sf" "$bw_khz" "$coding_rate" "$tx_power" "$profile"
write_repeater_config "$node_name" "$admin_password" "$jwt_secret" "$freq_mhz" "$sf" "$bw_khz" "$coding_rate" "$tx_power" "$board_key"
info "Saved config for ${node_name}"
}
@@ -616,10 +801,11 @@ configure_repeater() {
}
configure_radio_profile() {
local profile
local board_key
ensure_root
[ -f "$CONFIG_DIR/config.yaml" ] || fail "Config file is missing. Run install first."
profile=$(select_radio_profile)
board_key=$(select_buildroot_board)
write_repeater_config \
"$(get_config_value repeater.node_name luckfox-repeater)" \
"$(get_config_value repeater.security.admin_password admin123)" \
@@ -629,8 +815,8 @@ configure_radio_profile() {
"$(get_radio_bandwidth_khz)" \
"$(get_config_value radio.coding_rate 5)" \
"$(get_config_value radio.tx_power 22)" \
"$profile"
info "Applied Luckfox radio profile: $profile"
"$board_key"
info "Applied board profile: $(get_buildroot_board_label "$board_key")"
if service_exists; then
"$INIT_SCRIPT" restart
fi
@@ -891,7 +1077,7 @@ Commands:
install Install pyMC Repeater on the Buildroot image
upgrade Upgrade the Buildroot installation from the checked-out repo
configure Prompt for repeater settings and rewrite config.yaml
radio-profile Reapply the Luckfox radio pin profile only
radio-profile Reapply the Luckfox board radio config only
config Run the stock interactive config flow
start Start the init.d service
stop Stop the init.d service
+73
View File
@@ -0,0 +1,73 @@
{
"default_board": "luckfox-pimesh-v2",
"default_radio_preset": "USA/Canada (Recommended)",
"buildroot_hardware": {
"luckfox-pimesh-v2": {
"name": "Luckfox PiMesh V2",
"description": "Luckfox Pico Pi with PiMesh-1W V2 / E22P wiring",
"hardware_id": "pimesh-1w-v2",
"aliases": [
"1",
"v2",
"pimesh-v2",
"pimesh-1w-v2"
],
"sx1262_overrides": {
"cs_pin": -1,
"reset_pin": 54,
"busy_pin": 122,
"irq_pin": 121,
"en_pin": 0,
"txen_pin": -1,
"rxen_pin": -1,
"use_dio2_rf": true,
"use_dio3_tcxo": true,
"dio3_tcxo_voltage": 1.8
}
},
"luckfox-pimesh-v1": {
"name": "Luckfox PiMesh V1",
"description": "Luckfox Pico Pi with PiMesh-1W V1 wiring",
"hardware_id": "pimesh-1w-v1",
"aliases": [
"2",
"v1",
"pimesh-v1",
"pimesh-1w-v1"
],
"sx1262_overrides": {
"cs_pin": 145,
"reset_pin": 54,
"busy_pin": 123,
"irq_pin": 55,
"en_pin": -1,
"txen_pin": 52,
"rxen_pin": 53,
"use_dio2_rf": false,
"use_dio3_tcxo": true,
"dio3_tcxo_voltage": 1.8
}
},
"luckfox-meshadv": {
"name": "Luckfox MeshAdv",
"description": "Luckfox Pico Pi with MeshAdv wiring",
"hardware_id": "meshadv",
"aliases": [
"3",
"meshadv"
],
"sx1262_overrides": {
"cs_pin": 145,
"reset_pin": 54,
"busy_pin": 123,
"irq_pin": 55,
"en_pin": -1,
"txen_pin": 52,
"rxen_pin": 53,
"use_dio2_rf": false,
"use_dio3_tcxo": true,
"dio3_tcxo_voltage": 1.8
}
}
}
}