mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-10-31 04:01:59 +01:00
Enhance configuration and setup for KISS modem support
- Added support for Meshcore KISS modem firmware in configuration, allowing users to set `radio_type: kiss` and configure serial port and baud rate. - Updated `config.yaml.example` to include KISS modem settings. - Modified `manage.sh` to install with hardware extras for KISS support. - Enhanced `setup-radio-config.sh` to prompt for radio type and KISS modem settings. - Updated API endpoints to handle KISS modem configurations and hardware options. - Improved error handling for missing configuration sections. This update improves flexibility for users utilizing KISS modems alongside SX1262 hardware.
This commit is contained in:
1 parent
076d87dcde
commit
878ff8dc51
11 files changed
+329
-174
No files matched your search
@@ -30,7 +30,12 @@ The repeater daemon runs continuously as a background process, forwarding LoRa p
|
||||
|
||||
## Supported Hardware (Out of the Box)
|
||||
|
||||
The following hardware is currently supported out-of-the-box:
|
||||
The repeater supports two radio backends:
|
||||
|
||||
- **SX1262 (SPI)** — Direct connection to LoRa modules (HATs, etc.) as listed below.
|
||||
- **KISS modem** — Serial TNC using the KISS protocol. Requires a pyMC_core build with KISS support (e.g. [agessaman/pyMC_core (dev)](https://github.com/agessaman/pyMC_core/tree/dev)). Set `radio_type: kiss` in config and configure `kiss.port` and `kiss.baud_rate`. The setup script (`./setup-radio-config.sh`) offers a "KISS modem" option when configuring the repeater.
|
||||
|
||||
The following SX1262 hardware is currently supported out-of-the-box:
|
||||
|
||||
Waveshare LoRaWAN/GNSS HAT (SPI Version Only)
|
||||
|
||||
@@ -149,6 +154,11 @@ http://<repeater-ip>:8000
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
On **macOS** (or when using only the KISS modem), the base install is enough. On **Raspberry Pi** with SX1262 hardware, install with the optional hardware extra so SPI/spidev is available:
|
||||
```bash
|
||||
pip install -e .[hardware]
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The configuration file is created and configured during installation at:
|
||||
|
||||
+9
-1
@@ -1,4 +1,6 @@
|
||||
# Default Repeater Configuration
|
||||
# radio_type: sx1262 | kiss (use kiss for serial KISS TNC modem)
|
||||
radio_type: sx1262
|
||||
|
||||
repeater:
|
||||
# Node name for logging and identification
|
||||
@@ -127,6 +129,11 @@ radio:
|
||||
# Use implicit header mode
|
||||
implicit_header: false
|
||||
|
||||
# KISS modem (when radio_type: kiss). Requires pyMC_core with KISS support.
|
||||
# kiss:
|
||||
# port: "/dev/ttyUSB0"
|
||||
# baud_rate: 9600
|
||||
|
||||
# SX1262 Hardware Configuration
|
||||
sx1262:
|
||||
# SPI bus and chip select
|
||||
@@ -212,7 +219,8 @@ mqtt:
|
||||
|
||||
# Storage Configuration
|
||||
storage:
|
||||
# Directory for persistent storage files (SQLite, RRD)
|
||||
# Directory for persistent storage files (SQLite, RRD).
|
||||
# Use a writable path for local/dev (e.g. "./var/pymc_repeater" or "~/var/pymc_repeater").
|
||||
storage_dir: "/var/lib/pymc_repeater"
|
||||
|
||||
# Data retention settings
|
||||
|
||||
Binary file not shown.
@@ -336,7 +336,7 @@ EOF
|
||||
echo "Note: Using optimized binary wheels for faster installation"
|
||||
echo ""
|
||||
|
||||
if pip install --break-system-packages --no-cache-dir .; then
|
||||
if pip install --break-system-packages --no-cache-dir .[hardware]; then
|
||||
echo ""
|
||||
echo "✓ Python package installation completed successfully!"
|
||||
|
||||
@@ -589,7 +589,7 @@ EOF
|
||||
echo ""
|
||||
|
||||
# Upgrade packages (uses cache for unchanged dependencies - much faster)
|
||||
if python3 -m pip install --break-system-packages --upgrade --upgrade-strategy eager .; then
|
||||
if python3 -m pip install --break-system-packages --upgrade --upgrade-strategy eager .[hardware]; then
|
||||
echo ""
|
||||
echo "✓ Package and dependencies updated successfully!"
|
||||
else
|
||||
|
||||
+5
-1
@@ -31,7 +31,7 @@ keywords = ["mesh", "networking", "lora", "repeater", "daemon", "iot"]
|
||||
|
||||
|
||||
dependencies = [
|
||||
"pymc_core[hardware] @ git+https://github.com/rightup/pyMC_core.git@dev",
|
||||
"pymc_core",
|
||||
"pyyaml>=6.0.0",
|
||||
"cherrypy>=18.0.0",
|
||||
"paho-mqtt>=1.6.0",
|
||||
@@ -44,6 +44,10 @@ dependencies = [
|
||||
|
||||
|
||||
[project.optional-dependencies]
|
||||
# SX1262/SPI support (Linux only; required for Raspberry Pi HATs)
|
||||
hardware = [
|
||||
"pymc_core[hardware]",
|
||||
]
|
||||
dev = [
|
||||
"pytest>=7.4.0",
|
||||
"pytest-asyncio>=0.21.0",
|
||||
|
||||
+50
-2
@@ -197,7 +197,9 @@ def _load_or_create_identity_key(path: Optional[str] = None) -> bytes:
|
||||
|
||||
def get_radio_for_board(board_config: dict):
|
||||
|
||||
radio_type = board_config.get("radio_type", "sx1262").lower()
|
||||
radio_type = board_config.get("radio_type", "sx1262").lower().strip()
|
||||
if radio_type == "kiss-modem":
|
||||
radio_type = "kiss"
|
||||
|
||||
if radio_type == "sx1262":
|
||||
from pymc_core.hardware.sx1262_wrapper import SX1262Radio
|
||||
@@ -245,5 +247,51 @@ def get_radio_for_board(board_config: dict):
|
||||
|
||||
return radio
|
||||
|
||||
elif radio_type == "kiss":
|
||||
try:
|
||||
from pymc_core.hardware.kiss_modem_wrapper import KissModemWrapper
|
||||
except ImportError:
|
||||
try:
|
||||
from pymc_core.hardware.kiss_serial_wrapper import KissSerialWrapper as KissModemWrapper
|
||||
except ImportError:
|
||||
raise RuntimeError(
|
||||
"KISS modem support requires pyMC_core with KISS support. "
|
||||
"Install your fork with: pip install -e /path/to/pyMC_core"
|
||||
) from None
|
||||
|
||||
kiss_config = board_config.get("kiss")
|
||||
if not kiss_config:
|
||||
raise ValueError("Missing 'kiss' section in configuration file for radio_type: kiss")
|
||||
|
||||
port = kiss_config.get("port")
|
||||
if not port:
|
||||
raise ValueError("Missing 'port' in 'kiss' section (e.g. /dev/ttyUSB0)")
|
||||
|
||||
baudrate = int(kiss_config.get("baud_rate", 115200))
|
||||
radio_cfg = board_config.get("radio") or {}
|
||||
radio_config = {
|
||||
"frequency": int(radio_cfg.get("frequency", 869618000)),
|
||||
"bandwidth": int(radio_cfg.get("bandwidth", 62500)),
|
||||
"spreading_factor": int(radio_cfg.get("spreading_factor", 8)),
|
||||
"coding_rate": int(radio_cfg.get("coding_rate", 8)),
|
||||
"tx_power": int(radio_cfg.get("tx_power", 14)),
|
||||
}
|
||||
radio = KissModemWrapper(
|
||||
port=port,
|
||||
baudrate=baudrate,
|
||||
radio_config=radio_config,
|
||||
auto_configure=True,
|
||||
)
|
||||
|
||||
if hasattr(radio, "begin"):
|
||||
try:
|
||||
radio.begin()
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Failed to initialize KISS modem: {e}") from e
|
||||
|
||||
return radio
|
||||
|
||||
else:
|
||||
raise RuntimeError(f"Unknown radio type: {radio_type}. Supported: sx1262")
|
||||
raise RuntimeError(
|
||||
f"Unknown radio type: {radio_type}. Supported: sx1262, kiss (or kiss-modem)"
|
||||
)
|
||||
@@ -19,7 +19,8 @@ class StorageCollector:
|
||||
def __init__(self, config: dict, local_identity=None, repeater_handler=None):
|
||||
self.config = config
|
||||
self.repeater_handler = repeater_handler
|
||||
self.storage_dir = Path(config.get("storage_dir", "/var/lib/pymc_repeater"))
|
||||
storage_cfg = config.get("storage", {})
|
||||
self.storage_dir = Path(storage_cfg.get("storage_dir", "/var/lib/pymc_repeater"))
|
||||
self.storage_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
node_name = config.get("repeater", {}).get("node_name", "unknown")
|
||||
|
||||
+5
-1
@@ -56,7 +56,11 @@ class RepeaterDaemon:
|
||||
logger.info("Initializing radio hardware...")
|
||||
try:
|
||||
self.radio = get_radio_for_board(self.config)
|
||||
|
||||
|
||||
# KISS modem: schedule RX callbacks on the event loop for thread safety
|
||||
if hasattr(self.radio, "set_event_loop"):
|
||||
self.radio.set_event_loop(asyncio.get_running_loop())
|
||||
|
||||
if hasattr(self.radio, 'set_custom_cad_thresholds'):
|
||||
# Load CAD settings from config, with defaults
|
||||
cad_config = self.config.get("radio", {}).get("cad", {})
|
||||
|
||||
+131
-118
@@ -232,20 +232,17 @@ class APIEndpoints:
|
||||
def needs_setup(self):
|
||||
"""Check if the repeater needs initial setup configuration"""
|
||||
try:
|
||||
# Check if config has default values that indicate first-time setup
|
||||
config = self.config
|
||||
|
||||
# Check for default node name
|
||||
|
||||
# Check for default values that indicate first-time setup
|
||||
node_name = config.get('repeater', {}).get('node_name', '')
|
||||
has_default_name = node_name in ['mesh-repeater-01', '']
|
||||
|
||||
# Check for default admin password
|
||||
|
||||
admin_password = config.get('repeater', {}).get('security', {}).get('admin_password', '')
|
||||
has_default_password = admin_password in ['admin123', '']
|
||||
|
||||
# Needs setup if either condition is true
|
||||
|
||||
needs_setup = has_default_name or has_default_password
|
||||
|
||||
|
||||
return {'needs_setup': needs_setup, 'reasons': {
|
||||
'default_name': has_default_name,
|
||||
'default_password': has_default_password
|
||||
@@ -262,32 +259,35 @@ class APIEndpoints:
|
||||
import json
|
||||
|
||||
# Check config-based location first, then development location
|
||||
config_dir = Path(self.config.get("storage_dir", "/var/lib/pymc_repeater"))
|
||||
storage_cfg = self.config.get("storage", {})
|
||||
config_dir = Path(storage_cfg.get("storage_dir", "/var/lib/pymc_repeater"))
|
||||
installed_path = config_dir / 'radio-settings.json'
|
||||
dev_path = os.path.join(os.path.dirname(__file__), '..', '..', 'radio-settings.json')
|
||||
|
||||
hardware_file = str(installed_path) if installed_path.exists() else dev_path
|
||||
|
||||
if not os.path.exists(hardware_file):
|
||||
logger.error(f"Hardware file not found. Tried: {installed_path}, {dev_path}")
|
||||
return {'error': 'Hardware configuration file not found', 'hardware': []}
|
||||
|
||||
with open(hardware_file, 'r') as f:
|
||||
hardware_data = json.load(f)
|
||||
|
||||
# Parse hardware options from the "hardware" key
|
||||
hardware_list = []
|
||||
hardware_configs = hardware_data.get('hardware', {})
|
||||
|
||||
for hw_key, hw_config in hardware_configs.items():
|
||||
if isinstance(hw_config, dict):
|
||||
hardware_list.append({
|
||||
'key': hw_key,
|
||||
'name': hw_config.get('name', hw_key),
|
||||
'description': hw_config.get('description', ''),
|
||||
'config': hw_config
|
||||
})
|
||||
|
||||
|
||||
if os.path.exists(hardware_file):
|
||||
with open(hardware_file, 'r') as f:
|
||||
hardware_data = json.load(f)
|
||||
hardware_configs = hardware_data.get('hardware', {})
|
||||
for hw_key, hw_config in hardware_configs.items():
|
||||
if isinstance(hw_config, dict):
|
||||
hardware_list.append({
|
||||
'key': hw_key,
|
||||
'name': hw_config.get('name', hw_key),
|
||||
'description': hw_config.get('description', ''),
|
||||
'config': hw_config
|
||||
})
|
||||
|
||||
# Add MeshCore KISS modem option (serial TNC)
|
||||
hardware_list.append({
|
||||
'key': 'kiss',
|
||||
'name': 'KISS modem (serial)',
|
||||
'description': 'MeshCore KISS modem over serial – requires pyMC_core with KISS support',
|
||||
'config': {}
|
||||
})
|
||||
|
||||
return {'hardware': hardware_list}
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading hardware options: {e}")
|
||||
@@ -301,7 +301,8 @@ class APIEndpoints:
|
||||
import json
|
||||
|
||||
# Check config-based location first, then development location
|
||||
config_dir = Path(self.config.get("storage_dir", "/var/lib/pymc_repeater"))
|
||||
storage_cfg = self.config.get("storage", {})
|
||||
config_dir = Path(storage_cfg.get("storage_dir", "/var/lib/pymc_repeater"))
|
||||
installed_path = config_dir / 'radio-presets.json'
|
||||
dev_path = os.path.join(os.path.dirname(__file__), '..', '..', 'radio-presets.json')
|
||||
|
||||
@@ -351,105 +352,100 @@ class APIEndpoints:
|
||||
if not admin_password or len(admin_password) < 6:
|
||||
return {'success': False, 'error': 'Admin password must be at least 6 characters'}
|
||||
|
||||
# Load hardware configuration - check installed path first, then dev path
|
||||
import json
|
||||
config_dir = Path(self.config.get("storage_dir", "/var/lib/pymc_repeater"))
|
||||
installed_path = config_dir / 'radio-settings.json'
|
||||
dev_path = os.path.join(os.path.dirname(__file__), '..', '..', 'radio-settings.json')
|
||||
|
||||
hardware_file = str(installed_path) if installed_path.exists() else dev_path
|
||||
|
||||
if not os.path.exists(hardware_file):
|
||||
logger.error(f"Hardware file not found. Tried: {installed_path}, {dev_path}")
|
||||
return {'success': False, 'error': 'Hardware configuration file not found'}
|
||||
|
||||
with open(hardware_file, 'r') as f:
|
||||
hardware_data = json.load(f)
|
||||
|
||||
# Get hardware config from nested "hardware" key
|
||||
hardware_configs = hardware_data.get('hardware', {})
|
||||
hw_config = hardware_configs.get(hardware_key, {})
|
||||
if not hw_config:
|
||||
return {'success': False, 'error': f'Hardware configuration not found: {hardware_key}'}
|
||||
|
||||
# Prepare configuration updates
|
||||
import yaml
|
||||
|
||||
# Read current config
|
||||
|
||||
# Read current config first so we can update it
|
||||
with open(self._config_path, 'r') as f:
|
||||
config_yaml = yaml.safe_load(f)
|
||||
|
||||
|
||||
# Update repeater settings
|
||||
if 'repeater' not in config_yaml:
|
||||
config_yaml['repeater'] = {}
|
||||
config_yaml['repeater']['node_name'] = node_name
|
||||
|
||||
|
||||
if 'security' not in config_yaml['repeater']:
|
||||
config_yaml['repeater']['security'] = {}
|
||||
config_yaml['repeater']['security']['admin_password'] = admin_password
|
||||
|
||||
# Update radio settings - convert MHz/kHz to Hz
|
||||
|
||||
# Update radio settings - convert MHz/kHz to Hz (used for both SX1262 and KISS modem)
|
||||
if 'radio' not in config_yaml:
|
||||
config_yaml['radio'] = {}
|
||||
|
||||
freq_mhz = float(radio_preset.get('frequency', 0))
|
||||
bw_khz = float(radio_preset.get('bandwidth', 0))
|
||||
|
||||
config_yaml['radio']['frequency'] = int(freq_mhz * 1000000)
|
||||
config_yaml['radio']['spreading_factor'] = int(radio_preset.get('spreading_factor', 7))
|
||||
config_yaml['radio']['bandwidth'] = int(bw_khz * 1000)
|
||||
config_yaml['radio']['coding_rate'] = int(radio_preset.get('coding_rate', 5))
|
||||
|
||||
# Handle hardware-specific TX power (can be overridden by user later)
|
||||
if 'tx_power' in hw_config:
|
||||
config_yaml['radio']['tx_power'] = hw_config.get('tx_power', 22)
|
||||
|
||||
# Handle preamble length (goes in radio section)
|
||||
if 'preamble_length' in hw_config:
|
||||
config_yaml['radio']['preamble_length'] = hw_config.get('preamble_length', 17)
|
||||
|
||||
# Update hardware-specific settings under sx1262 section
|
||||
if 'sx1262' not in config_yaml:
|
||||
config_yaml['sx1262'] = {}
|
||||
|
||||
# SPI configuration
|
||||
if 'bus_id' in hw_config:
|
||||
config_yaml['sx1262']['bus_id'] = hw_config.get('bus_id', 0)
|
||||
if 'cs_id' in hw_config:
|
||||
config_yaml['sx1262']['cs_id'] = hw_config.get('cs_id', 0)
|
||||
|
||||
# Pin configuration
|
||||
if 'reset_pin' in hw_config:
|
||||
config_yaml['sx1262']['reset_pin'] = hw_config.get('reset_pin', 22)
|
||||
if 'busy_pin' in hw_config:
|
||||
config_yaml['sx1262']['busy_pin'] = hw_config.get('busy_pin', 17)
|
||||
if 'irq_pin' in hw_config:
|
||||
config_yaml['sx1262']['irq_pin'] = hw_config.get('irq_pin', 16)
|
||||
if 'txen_pin' in hw_config:
|
||||
config_yaml['sx1262']['txen_pin'] = hw_config.get('txen_pin', -1)
|
||||
if 'rxen_pin' in hw_config:
|
||||
config_yaml['sx1262']['rxen_pin'] = hw_config.get('rxen_pin', -1)
|
||||
if 'cs_pin' in hw_config:
|
||||
config_yaml['sx1262']['cs_pin'] = hw_config.get('cs_pin', -1)
|
||||
if 'txled_pin' in hw_config:
|
||||
config_yaml['sx1262']['txled_pin'] = hw_config.get('txled_pin', -1)
|
||||
if 'rxled_pin' in hw_config:
|
||||
config_yaml['sx1262']['rxled_pin'] = hw_config.get('rxled_pin', -1)
|
||||
|
||||
# Hardware flags
|
||||
if 'use_dio3_tcxo' in hw_config:
|
||||
config_yaml['sx1262']['use_dio3_tcxo'] = hw_config.get('use_dio3_tcxo', False)
|
||||
if 'use_dio2_rf' in hw_config:
|
||||
config_yaml['sx1262']['use_dio2_rf'] = hw_config.get('use_dio2_rf', False)
|
||||
if 'is_waveshare' in hw_config:
|
||||
config_yaml['sx1262']['is_waveshare'] = hw_config.get('is_waveshare', False)
|
||||
|
||||
|
||||
if hardware_key == 'kiss':
|
||||
# KISS modem: set radio_type and kiss section (port/baud from request or defaults)
|
||||
config_yaml['radio_type'] = 'kiss'
|
||||
kiss_port = (data.get('kiss_port') or '').strip() or '/dev/ttyUSB0'
|
||||
kiss_baud = int(data.get('kiss_baud_rate', data.get('kiss_baud', 115200)))
|
||||
config_yaml['kiss'] = {'port': kiss_port, 'baud_rate': kiss_baud}
|
||||
config_yaml['radio']['tx_power'] = int(radio_preset.get('tx_power', 14))
|
||||
if 'preamble_length' not in config_yaml['radio']:
|
||||
config_yaml['radio']['preamble_length'] = 17
|
||||
else:
|
||||
# SX1262: load hardware config from radio-settings.json
|
||||
storage_cfg = self.config.get("storage", {})
|
||||
config_dir = Path(storage_cfg.get("storage_dir", "/var/lib/pymc_repeater"))
|
||||
installed_path = config_dir / 'radio-settings.json'
|
||||
dev_path = os.path.join(os.path.dirname(__file__), '..', '..', 'radio-settings.json')
|
||||
hardware_file = str(installed_path) if installed_path.exists() else dev_path
|
||||
if not os.path.exists(hardware_file):
|
||||
return {'success': False, 'error': 'Hardware configuration file not found'}
|
||||
with open(hardware_file, 'r') as f:
|
||||
hardware_data = json.load(f)
|
||||
hardware_configs = hardware_data.get('hardware', {})
|
||||
hw_config = hardware_configs.get(hardware_key, {})
|
||||
if not hw_config:
|
||||
return {'success': False, 'error': f'Hardware configuration not found: {hardware_key}'}
|
||||
|
||||
config_yaml['radio_type'] = 'sx1262'
|
||||
if 'tx_power' in hw_config:
|
||||
config_yaml['radio']['tx_power'] = hw_config.get('tx_power', 22)
|
||||
if 'preamble_length' in hw_config:
|
||||
config_yaml['radio']['preamble_length'] = hw_config.get('preamble_length', 17)
|
||||
|
||||
if 'sx1262' not in config_yaml:
|
||||
config_yaml['sx1262'] = {}
|
||||
if 'bus_id' in hw_config:
|
||||
config_yaml['sx1262']['bus_id'] = hw_config.get('bus_id', 0)
|
||||
if 'cs_id' in hw_config:
|
||||
config_yaml['sx1262']['cs_id'] = hw_config.get('cs_id', 0)
|
||||
if 'reset_pin' in hw_config:
|
||||
config_yaml['sx1262']['reset_pin'] = hw_config.get('reset_pin', 22)
|
||||
if 'busy_pin' in hw_config:
|
||||
config_yaml['sx1262']['busy_pin'] = hw_config.get('busy_pin', 17)
|
||||
if 'irq_pin' in hw_config:
|
||||
config_yaml['sx1262']['irq_pin'] = hw_config.get('irq_pin', 16)
|
||||
if 'txen_pin' in hw_config:
|
||||
config_yaml['sx1262']['txen_pin'] = hw_config.get('txen_pin', -1)
|
||||
if 'rxen_pin' in hw_config:
|
||||
config_yaml['sx1262']['rxen_pin'] = hw_config.get('rxen_pin', -1)
|
||||
if 'cs_pin' in hw_config:
|
||||
config_yaml['sx1262']['cs_pin'] = hw_config.get('cs_pin', -1)
|
||||
if 'txled_pin' in hw_config:
|
||||
config_yaml['sx1262']['txled_pin'] = hw_config.get('txled_pin', -1)
|
||||
if 'rxled_pin' in hw_config:
|
||||
config_yaml['sx1262']['rxled_pin'] = hw_config.get('rxled_pin', -1)
|
||||
if 'use_dio3_tcxo' in hw_config:
|
||||
config_yaml['sx1262']['use_dio3_tcxo'] = hw_config.get('use_dio3_tcxo', False)
|
||||
if 'use_dio2_rf' in hw_config:
|
||||
config_yaml['sx1262']['use_dio2_rf'] = hw_config.get('use_dio2_rf', False)
|
||||
if 'is_waveshare' in hw_config:
|
||||
config_yaml['sx1262']['is_waveshare'] = hw_config.get('is_waveshare', False)
|
||||
|
||||
# Write updated config
|
||||
with open(self._config_path, 'w') as f:
|
||||
yaml.dump(config_yaml, f, default_flow_style=False, sort_keys=False)
|
||||
|
||||
logger.info(f"Setup wizard completed: node_name={node_name}, hardware={hardware_key}, freq={freq_mhz}MHz")
|
||||
|
||||
logger.info(
|
||||
f"Setup wizard completed: node_name={node_name}, hardware={hardware_key}, freq={freq_mhz}MHz"
|
||||
)
|
||||
|
||||
# Trigger service restart after setup
|
||||
import subprocess
|
||||
import threading
|
||||
@@ -467,18 +463,19 @@ class APIEndpoints:
|
||||
restart_thread = threading.Thread(target=delayed_restart, daemon=True)
|
||||
restart_thread.start()
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'message': 'Setup completed successfully. Service is restarting...',
|
||||
'config': {
|
||||
'node_name': node_name,
|
||||
'hardware': hardware_key,
|
||||
'frequency': freq_mhz,
|
||||
'spreading_factor': radio_preset.get('spreading_factor'),
|
||||
'bandwidth': radio_preset.get('bandwidth'),
|
||||
'coding_rate': radio_preset.get('coding_rate')
|
||||
}
|
||||
result_config = {
|
||||
'node_name': node_name,
|
||||
'hardware': hardware_key,
|
||||
'radio_type': config_yaml.get('radio_type', 'sx1262'),
|
||||
'frequency': freq_mhz,
|
||||
'spreading_factor': radio_preset.get('spreading_factor'),
|
||||
'bandwidth': radio_preset.get('bandwidth'),
|
||||
'coding_rate': radio_preset.get('coding_rate')
|
||||
}
|
||||
if hardware_key == 'kiss':
|
||||
result_config['kiss_port'] = config_yaml.get('kiss', {}).get('port')
|
||||
result_config['kiss_baud_rate'] = config_yaml.get('kiss', {}).get('baud_rate')
|
||||
return {'success': True, 'message': 'Setup completed successfully. Service is restarting...', 'config': result_config}
|
||||
|
||||
except cherrypy.HTTPError:
|
||||
raise
|
||||
@@ -1307,15 +1304,31 @@ class APIEndpoints:
|
||||
return self._error("Advert interval must be 0 (off) or 1-10080 minutes")
|
||||
self.config["repeater"]["advert_interval_minutes"] = mins
|
||||
applied.append(f"advert.interval={mins}m")
|
||||
|
||||
# KISS modem settings (only when radio_type is kiss)
|
||||
if "kiss_port" in data or "kiss_baud_rate" in data:
|
||||
if self.config.get("radio_type") != "kiss":
|
||||
return self._error("KISS settings only apply when radio_type is kiss")
|
||||
if "kiss" not in self.config:
|
||||
self.config["kiss"] = {}
|
||||
if "kiss_port" in data:
|
||||
self.config["kiss"]["port"] = str(data["kiss_port"]).strip()
|
||||
applied.append("kiss.port")
|
||||
if "kiss_baud_rate" in data:
|
||||
self.config["kiss"]["baud_rate"] = int(data["kiss_baud_rate"])
|
||||
applied.append("kiss.baud_rate")
|
||||
|
||||
if not applied:
|
||||
return self._error("No valid settings provided")
|
||||
|
||||
live_sections = ['repeater', 'delays', 'radio']
|
||||
if "kiss" in self.config:
|
||||
live_sections.append("kiss")
|
||||
# Save to config file and live update daemon in one operation
|
||||
result = self.config_manager.update_and_save(
|
||||
updates={}, # Updates already applied to self.config above
|
||||
live_update=True,
|
||||
live_update_sections=['repeater', 'delays', 'radio']
|
||||
live_update_sections=live_sections
|
||||
)
|
||||
|
||||
logger.info(f"Radio config updated: {', '.join(applied)}")
|
||||
|
||||
@@ -1 +1 @@
|
||||
import{d as A,r as l,c as P,a as W,o as I,b as a,e,f as B,_ as Y,t as i,u as o,n as J,g as k,F as N,h as z,i as K,w as h,v as C,j as _,k as V,l as q,T,m as Q,p as n,q as E,s as X,x as Z}from"./index-sHch0610.js";const ee=A("setup",()=>{const m=l(1),r=l(5),y=l(`pyRpt${Math.floor(Math.random()*1e4).toString().padStart(4,"0")}`),b=l(null),v=l(null),x=l(""),f=l(""),w=l(!1),c=l({frequency:"915.0",spreading_factor:"7",bandwidth:"125",coding_rate:"5"}),R=l([]),j=l([]),g=l(!1),S=l(!1),u=l(null),t=P(()=>{switch(m.value){case 1:return!0;case 2:return y.value.trim().length>0;case 3:return b.value!==null;case 4:return w.value?c.value.frequency&&c.value.spreading_factor&&c.value.bandwidth&&c.value.coding_rate:v.value!==null;case 5:return x.value.length>=6&&x.value===f.value;default:return!1}}),s=P(()=>m.value>1),M=P(()=>m.value===r.value);async function F(){g.value=!0,u.value=null;try{const p=await(await fetch("/api/hardware_options")).json();if(p.error)throw new Error(p.error);R.value=p.hardware||[]}catch(d){u.value=d instanceof Error?d.message:"Failed to load hardware options",console.error("Error fetching hardware options:",d)}finally{g.value=!1}}async function H(){g.value=!0,u.value=null;try{const p=await(await fetch("/api/radio_presets")).json();if(p.error)throw new Error(p.error);j.value=p.presets||[]}catch(d){u.value=d instanceof Error?d.message:"Failed to load radio presets",console.error("Error fetching radio presets:",d)}finally{g.value=!1}}async function U(){if(!t.value)return{success:!1,error:"Please complete all required fields"};S.value=!0,u.value=null;try{const d=w.value?{title:"Custom Configuration",description:"Custom radio settings",frequency:c.value.frequency,spreading_factor:c.value.spreading_factor,bandwidth:c.value.bandwidth,coding_rate:c.value.coding_rate}:v.value,L=await(await fetch("/api/setup_wizard",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({node_name:y.value.trim(),hardware_key:b.value?.key,radio_preset:d,admin_password:x.value})})).json();if(!L.success)throw new Error(L.error||"Setup failed");return{success:!0,data:L}}catch(d){const p=d instanceof Error?d.message:"Failed to complete setup";return u.value=p,{success:!1,error:p}}finally{S.value=!1}}function O(){t.value&&m.value<r.value&&m.value++}function $(){s.value&&m.value--}function G(d){d>=1&&d<=r.value&&(m.value=d)}function D(){m.value=1,y.value=`pyRpt${Math.floor(Math.random()*1e4).toString().padStart(4,"0")}`,b.value=null,v.value=null,w.value=!1,c.value={frequency:"915.0",spreading_factor:"7",bandwidth:"125",coding_rate:"5"},x.value="",f.value="",u.value=null}return{currentStep:m,totalSteps:r,nodeName:y,selectedHardware:b,selectedRadioPreset:v,useCustomRadio:w,customRadio:c,adminPassword:x,confirmPassword:f,hardwareOptions:R,radioPresets:j,isLoading:g,isSubmitting:S,error:u,canGoNext:t,canGoBack:s,isLastStep:M,fetchHardwareOptions:F,fetchRadioPresets:H,completeSetup:U,nextStep:O,previousStep:$,goToStep:G,reset:D}}),te={class:"min-h-screen bg-background dark:bg-background overflow-hidden relative flex items-center justify-center p-4"},re={class:"absolute top-4 right-4 z-20"},oe={class:"w-full max-w-4xl relative z-10"},se={class:"mb-8"},ae={class:"flex justify-between mb-2"},ne={class:"text-content-secondary dark:text-content-muted text-sm"},de={class:"text-content-secondary dark:text-content-muted text-sm"},ie={class:"h-2 bg-stroke-subtle dark:bg-stroke/10 rounded-full overflow-hidden"},le={class:"bg-white dark:bg-surface-elevated backdrop-blur-xl border border-stroke-subtle dark:border-white/10 rounded-[20px] p-6 sm:p-8 md:p-12"},ue={class:"flex justify-center mb-8"},ce={class:"flex gap-2"},pe={class:"mb-8"},me={class:"text-2xl sm:text-3xl font-bold text-content-primary dark:text-content-primary mb-2 text-center"},be={key:0,class:"space-y-6 mt-8"},fe={key:1,class:"space-y-6 mt-8"},xe={class:"max-w-md mx-auto"},ke={key:2,class:"space-y-6 mt-8"},ve={key:0,class:"text-center text-content-secondary dark:text-content-muted"},ge={key:1,class:"text-center text-content-secondary dark:text-content-muted"},ye={key:2,class:"grid grid-cols-1 md:grid-cols-2 gap-4 max-w-3xl mx-auto"},he=["onClick"],we={class:"font-medium text-content-primary dark:text-content-primary mb-1"},_e={class:"text-sm text-content-secondary dark:text-content-muted"},Se={key:3,class:"space-y-6 mt-8"},Ce={key:0,class:"text-center text-content-secondary dark:text-content-muted"},Re={key:1,class:"text-center text-content-secondary dark:text-content-muted"},je={key:2,class:"max-w-5xl mx-auto"},Pe={class:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-4"},Me=["onClick"],Le={class:"relative z-10"},Be={class:"font-medium text-content-primary dark:text-content-primary mb-1 flex items-start justify-between gap-2"},Ne={class:"flex items-center gap-2"},ze={class:"text-2xl"},Ve={key:0,class:"text-primary flex-shrink-0"},qe={class:"text-xs text-content-secondary dark:text-content-muted mb-3"},Te={class:"grid grid-cols-2 gap-2 text-xs"},Ee={clLine truncated
|
||||
import{d as A,r as l,c as P,a as W,o as I,b as a,e,f as B,_ as Y,t as i,u as o,n as J,g as k,F as N,h as z,i as K,w as h,v as C,j as _,k as V,l as q,T,m as Q,p as n,q as E,s as X,x as Z}from"./index-sHch0610.js";const ee=A("setup",()=>{const m=l(1),r=l(5),y=l(`pyRpt${Math.floor(Math.random()*1e4).toString().padStart(4,"0")}`),b=l(null),v=l(null),x=l(""),f=l(""),w=l(!1),c=l({frequency:"915.0",spreading_factor:"7",bandwidth:"125",coding_rate:"5"}),R=l([]),j=l([]),g=l(!1),S=l(!1),u=l(null),kp=l("/dev/ttyUSB0"),kb=l("115200"),t=P(()=>{switch(m.value){case 1:return!0;case 2:return y.value.trim().length>0;case 3:return b.value!==null&&(b.value?.key!=="kiss"||(kp.value&&String(kp.value).trim().length>0));case 4:return w.value?c.value.frequency&&c.value.spreading_factor&&c.value.bandwidth&&c.value.coding_rate:v.value!==null;case 5:return x.value.length>=6&&x.value===f.value;default:return!1}}),s=P(()=>m.value>1),M=P(()=>m.value===r.value);async function F(){g.value=!0,u.value=null;try{const p=await(await fetch("/api/hardware_options")).json();if(p.error)throw new Error(p.error);R.value=p.hardware||[]}catch(d){u.value=d instanceof Error?d.message:"Failed to load hardware options",console.error("Error fetching hardware options:",d)}finally{g.value=!1}}async function H(){g.value=!0,u.value=null;try{const p=await(await fetch("/api/radio_presets")).json();if(p.error)throw new Error(p.error);j.value=p.presets||[]}catch(d){u.value=d instanceof Error?d.message:"Failed to load radio presets",console.error("Error fetching radio presets:",d)}finally{g.value=!1}}async function U(){if(!t.value)return{success:!1,error:"Please complete all required fields"};S.value=!0,u.value=null;try{const d=w.value?{title:"Custom Configuration",description:"Custom radio settings",frequency:c.value.frequency,spreading_factor:c.value.spreading_factor,bandwidth:c.value.bandwidth,coding_rate:c.value.coding_rate}:v.value;const payload={node_name:y.value.trim(),hardware_key:b.value?.key,radio_preset:d,admin_password:x.value};if(b.value?.key==="kiss"){payload.kiss_port=kp.value||"/dev/ttyUSB0";payload.kiss_baud_rate=Number(kb.value)||115200;}const L=await(await fetch("/api/setup_wizard",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(payload)})).json();if(!L.success)throw new Error(L.error||"Setup failed");return{success:!0,data:L}}catch(d){const p=d instanceof Error?d.message:"Failed to complete setup";return u.value=p,{success:!1,error:p}}finally{S.value=!1}}function O(){t.value&&m.value<r.value&&m.value++}function $(){s.value&&m.value--}function G(d){d>=1&&d<=r.value&&(m.value=d)}function D(){m.value=1,y.value=`pyRpt${Math.floor(Math.random()*1e4).toString().padStart(4,"0")}`,b.value=null,v.value=null,w.value=!1,c.value={frequency:"915.0",spreading_factor:"7",bandwidth:"125",coding_rate:"5"},x.value="",f.value="",u.value=null,kp.value="/dev/ttyUSB0",kb.value="115200"}return{currentStep:m,totalSteps:r,nodeName:y,selectedHardware:b,selectedRadioPreset:v,useCustomRadio:w,customRadio:c,adminPassword:x,confirmPassword:f,hardwareOptions:R,radioPresets:j,isLoading:g,isSubmitting:S,error:u,canGoNext:t,canGoBack:s,isLastStep:M,fetchHardwareOptions:F,fetchRadioPresets:H,completeSetup:U,nextStep:O,previousStep:$,goToStep:G,reset:D,kissPort:kp,kissBaud:kb}}),te={class:"min-h-screen bg-background dark:bg-background overflow-hidden relative flex items-center justify-center p-4"},re={class:"absolute top-4 right-4 z-20"},oe={class:"w-full max-w-4xl relative z-10"},se={class:"mb-8"},ae={class:"flex justify-between mb-2"},ne={class:"text-content-secondary dark:text-content-muted text-sm"},de={class:"text-content-secondary dark:text-content-muted text-sm"},ie={class:"h-2 bg-stroke-subtle dark:bg-stroke/10 rounded-full overflow-hidden"},le={class:"bg-white dark:bg-surface-elevated backdrop-blur-xl border border-stroke-subtle dark:border-white/10 rounded-[20px] p-6 sm:p-8 md:p-12"},ue={class:"flex justify-center mb-8"},ce={class:"flex gap-2"},pe={class:"mb-8"},me={class:"text-2xl sm:text-3xl font-bold text-content-primary dark:text-content-primary mb-2 text-center"},be={key:0,class:"space-y-6 mt-8"},fe={key:1,class:"space-y-6 mt-8"},xe={class:"max-w-md mx-auto"},ke={key:2,class:"space-y-6 mt-8"},ve={key:0,class:"text-center text-content-secondary dark:text-content-muted"},ge={key:1,class:"text-center text-content-secondary dark:text-content-muted"},ye={key:2,class:"grid grid-cols-1 md:grid-cols-2 gap-4 max-w-3xl mx-auto"},he=["onClick"],we={class:"font-medium text-content-primary dark:text-content-primary mb-1"},_e={class:"text-sm text-content-secondary dark:text-content-muted"},Se={key:3,class:"space-y-6 mt-8"},Ce={key:0,class:"text-center text-content-secondary dark:text-content-muted"},Re={key:1,class:"text-center text-content-secondary dark:text-content-muted"},je={key:2,class:"max-w-5xl mx-auto"},Pe={class:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-4"},Me=["onClick"],Le={class:"relative z-10"},Be={class:"font-medium teLine truncated
|
||||
+113
-46
@@ -43,56 +43,76 @@ repeater_name=${repeater_name:-$default_name}
|
||||
|
||||
echo "Repeater name: $repeater_name"
|
||||
echo ""
|
||||
echo "=== Step 1: Select Hardware ==="
|
||||
|
||||
# Step 0.5: Radio type (SX1262 hardware vs KISS modem)
|
||||
echo "=== Step 0.5: Select Radio Type ==="
|
||||
echo ""
|
||||
echo " 1) SX1262 hardware (SPI LoRa module - Raspberry Pi HAT, etc.)"
|
||||
echo " 2) KISS modem (serial TNC - requires pyMC_core with KISS support)"
|
||||
echo ""
|
||||
read -p "Select radio type (1 or 2): " radio_type_sel
|
||||
|
||||
if [ ! -f "$HARDWARE_CONFIG" ]; then
|
||||
echo "Error: Hardware configuration file not found at $HARDWARE_CONFIG"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$radio_type_sel" = "2" ]; then
|
||||
RADIO_TYPE="kiss"
|
||||
hw_key="kiss"
|
||||
hw_name="KISS modem"
|
||||
echo "Selected: $hw_name"
|
||||
echo ""
|
||||
else
|
||||
RADIO_TYPE="sx1262"
|
||||
echo "Selected: SX1262 hardware"
|
||||
echo ""
|
||||
echo "=== Step 1: Select Hardware ==="
|
||||
echo ""
|
||||
|
||||
# Parse hardware options from radio-settings.json
|
||||
hw_index=0
|
||||
declare -a hw_keys
|
||||
declare -a hw_names
|
||||
|
||||
# Extract hardware keys and names using grep and sed
|
||||
hw_data=$(grep -o '"[^"]*":\s*{' "$HARDWARE_CONFIG" | grep -v hardware | sed 's/"\([^"]*\)".*/\1/' | while read hw_key; do
|
||||
hw_name=$(grep -A 1 "\"$hw_key\"" "$HARDWARE_CONFIG" | grep "\"name\"" | sed 's/.*"name":\s*"\([^"]*\)".*/\1/')
|
||||
if [ -n "$hw_name" ]; then
|
||||
echo "$hw_key|$hw_name"
|
||||
if [ ! -f "$HARDWARE_CONFIG" ]; then
|
||||
echo "Error: Hardware configuration file not found at $HARDWARE_CONFIG"
|
||||
exit 1
|
||||
fi
|
||||
done)
|
||||
|
||||
while IFS='|' read -r hw_key hw_name; do
|
||||
if [ -n "$hw_key" ] && [ -n "$hw_name" ]; then
|
||||
echo " $((hw_index + 1))) $hw_name ($hw_key)"
|
||||
hw_keys[$hw_index]="$hw_key"
|
||||
hw_names[$hw_index]="$hw_name"
|
||||
((hw_index++))
|
||||
# Parse hardware options from radio-settings.json
|
||||
hw_index=0
|
||||
declare -a hw_keys
|
||||
declare -a hw_names
|
||||
|
||||
# Extract hardware keys and names using grep and sed
|
||||
hw_data=$(grep -o '"[^"]*":\s*{' "$HARDWARE_CONFIG" | grep -v hardware | sed 's/"\([^"]*\)".*/\1/' | while read hw_key; do
|
||||
hw_name=$(grep -A 1 "\"$hw_key\"" "$HARDWARE_CONFIG" | grep "\"name\"" | sed 's/.*"name":\s*"\([^"]*\)".*/\1/')
|
||||
if [ -n "$hw_name" ]; then
|
||||
echo "$hw_key|$hw_name"
|
||||
fi
|
||||
done)
|
||||
|
||||
while IFS='|' read -r hw_key hw_name; do
|
||||
if [ -n "$hw_key" ] && [ -n "$hw_name" ]; then
|
||||
echo " $((hw_index + 1))) $hw_name ($hw_key)"
|
||||
hw_keys[$hw_index]="$hw_key"
|
||||
hw_names[$hw_index]="$hw_name"
|
||||
((hw_index++))
|
||||
fi
|
||||
done <<< "$hw_data"
|
||||
|
||||
if [ "$hw_index" -eq 0 ]; then
|
||||
echo "Error: No hardware configurations found"
|
||||
exit 1
|
||||
fi
|
||||
done <<< "$hw_data"
|
||||
|
||||
if [ "$hw_index" -eq 0 ]; then
|
||||
echo "Error: No hardware configurations found"
|
||||
exit 1
|
||||
echo ""
|
||||
read -p "Select hardware (1-$hw_index): " hw_selection
|
||||
|
||||
if ! [ "$hw_selection" -ge 1 ] 2>/dev/null || [ "$hw_selection" -gt "$hw_index" ]; then
|
||||
echo "Error: Invalid selection"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
selected_hw=$((hw_selection - 1))
|
||||
hw_key="${hw_keys[$selected_hw]}"
|
||||
hw_name="${hw_names[$selected_hw]}"
|
||||
|
||||
echo "Selected: $hw_name"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo ""
|
||||
read -p "Select hardware (1-$hw_index): " hw_selection
|
||||
|
||||
if ! [ "$hw_selection" -ge 1 ] 2>/dev/null || [ "$hw_selection" -gt "$hw_index" ]; then
|
||||
echo "Error: Invalid selection"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
selected_hw=$((hw_selection - 1))
|
||||
hw_key="${hw_keys[$selected_hw]}"
|
||||
hw_name="${hw_names[$selected_hw]}"
|
||||
|
||||
echo "Selected: $hw_name"
|
||||
echo ""
|
||||
|
||||
# Step 2: Radio Settings Selection
|
||||
echo "=== Step 2: Select Radio Settings ==="
|
||||
echo ""
|
||||
@@ -179,14 +199,44 @@ echo "Selected: $title"
|
||||
echo "Frequency: ${freq}MHz, SF: $sf, BW: $bw, CR: $cr"
|
||||
echo ""
|
||||
|
||||
# Update config.yaml
|
||||
# KISS modem: prompt for serial port and baud rate
|
||||
if [ "$RADIO_TYPE" = "kiss" ]; then
|
||||
echo "=== KISS Modem Settings ==="
|
||||
echo ""
|
||||
default_port="/dev/ttyUSB0"
|
||||
read -p "Serial port [$default_port]: " kiss_port
|
||||
kiss_port=${kiss_port:-$default_port}
|
||||
default_baud="9600"
|
||||
read -p "Baud rate [$default_baud]: " kiss_baud
|
||||
kiss_baud=${kiss_baud:-$default_baud}
|
||||
echo "KISS: port=$kiss_port, baud_rate=$kiss_baud"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Ensure config file exists (create from example if missing)
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
echo "Error: Config file not found at $CONFIG_FILE"
|
||||
exit 1
|
||||
if [ -f "$CONFIG_DIR/config.yaml.example" ]; then
|
||||
cp "$CONFIG_DIR/config.yaml.example" "$CONFIG_FILE"
|
||||
echo "Created $CONFIG_FILE from config.yaml.example"
|
||||
elif [ -f "$SCRIPT_DIR/config.yaml.example" ]; then
|
||||
cp "$SCRIPT_DIR/config.yaml.example" "$CONFIG_FILE"
|
||||
echo "Created $CONFIG_FILE from $SCRIPT_DIR/config.yaml.example"
|
||||
else
|
||||
echo "Error: Config file not found at $CONFIG_FILE"
|
||||
echo "Copy config.yaml.example to config.yaml or run from a directory that has it."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Updating configuration..."
|
||||
|
||||
# Radio type (sx1262 or kiss)
|
||||
if grep -q "^radio_type:" "$CONFIG_FILE"; then
|
||||
sed "${SED_OPTS[@]}" "s/^radio_type:.*/radio_type: $RADIO_TYPE/" "$CONFIG_FILE"
|
||||
else
|
||||
{ echo "radio_type: $RADIO_TYPE"; cat "$CONFIG_FILE"; } > "$CONFIG_FILE.tmp" && mv "$CONFIG_FILE.tmp" "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
# Repeater name
|
||||
sed "${SED_OPTS[@]}" "s/^ node_name:.*/ node_name: \"$repeater_name\"/" "$CONFIG_FILE"
|
||||
|
||||
@@ -196,7 +246,18 @@ sed "${SED_OPTS[@]}" "s/^ spreading_factor:.*/ spreading_factor: $sf/" "$CONFI
|
||||
sed "${SED_OPTS[@]}" "s/^ bandwidth:.*/ bandwidth: $bw_hz/" "$CONFIG_FILE"
|
||||
sed "${SED_OPTS[@]}" "s/^ coding_rate:.*/ coding_rate: $cr/" "$CONFIG_FILE"
|
||||
|
||||
# Extract hardware-specific settings from radio-settings.json
|
||||
# KISS modem: update kiss section
|
||||
if [ "$RADIO_TYPE" = "kiss" ]; then
|
||||
if grep -q "^kiss:" "$CONFIG_FILE"; then
|
||||
sed "${SED_OPTS[@]}" "s/^ port:.*/ port: \"$kiss_port\"/" "$CONFIG_FILE"
|
||||
sed "${SED_OPTS[@]}" "s/^ baud_rate:.*/ baud_rate: $kiss_baud/" "$CONFIG_FILE"
|
||||
else
|
||||
printf '\nkiss:\n port: "%s"\n baud_rate: %s\n' "$kiss_port" "$kiss_baud" >> "$CONFIG_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Extract hardware-specific settings from radio-settings.json (SX1262 only)
|
||||
if [ "$RADIO_TYPE" = "sx1262" ]; then
|
||||
echo "Extracting hardware configuration from $HARDWARE_CONFIG..."
|
||||
|
||||
# Use jq to extract all fields from the selected hardware
|
||||
@@ -285,6 +346,7 @@ else
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
rm -f /tmp/radio_*_* "$CONFIG_FILE.bak"
|
||||
@@ -293,14 +355,19 @@ echo "Configuration updated successfully!"
|
||||
echo ""
|
||||
echo "Applied Configuration:"
|
||||
echo " Repeater Name: $repeater_name"
|
||||
echo " Radio Type: $RADIO_TYPE"
|
||||
echo " Hardware: $hw_name ($hw_key)"
|
||||
echo " Frequency: ${freq}MHz (${freq_hz}Hz)"
|
||||
echo " Spreading Factor: $sf"
|
||||
echo " Bandwidth: ${bw}kHz (${bw_hz}Hz)"
|
||||
echo " Coding Rate: $cr"
|
||||
if [ "$RADIO_TYPE" = "kiss" ]; then
|
||||
echo " KISS Port: $kiss_port"
|
||||
echo " KISS Baud Rate: $kiss_baud"
|
||||
fi
|
||||
echo ""
|
||||
echo "Hardware GPIO Configuration:"
|
||||
if [ -n "$bus_id" ]; then
|
||||
if [ "$RADIO_TYPE" = "sx1262" ] && [ -n "$bus_id" ]; then
|
||||
echo " Bus ID: $bus_id"
|
||||
echo " Chip Select: $cs_id (pin $cs_pin)"
|
||||
echo " Reset Pin: $reset_pin"
|
||||
|
||||
Reference in new issue
Block a user