mirror of
https://github.com/meshtastic/firmware.git
synced 2025-10-27 15:02:41 +00:00
638 lines
25 KiB
Python
638 lines
25 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Simple ELRS to Meshtastic Target Converter
|
|
|
|
Converts ELRS target definitions to Meshtastic variant.h files.
|
|
"""
|
|
|
|
# TODO: - Add support for amplifier chips
|
|
# TODO: - Clean up names of targets to avoid names like dual-dual
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import argparse
|
|
import subprocess
|
|
import shutil
|
|
import tempfile
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional
|
|
|
|
class SimpleELRSConverter:
|
|
def __init__(self):
|
|
self.targets_data = {}
|
|
self.hardware_data = {}
|
|
self.temp_repo_path = None
|
|
|
|
# Supported ESP32 platforms
|
|
self.supported_platforms = {'esp32', 'esp32-c3', 'esp32-s3'}
|
|
|
|
# Radio chip mappings
|
|
self.radio_chips = {
|
|
'sx1276': {'define': 'USE_RF95', 'freq': '900', 'type': 'rf95'},
|
|
'sx1277': {'define': 'USE_RF95', 'freq': '900', 'type': 'rf95'},
|
|
'sx1280': {'define': 'USE_SX1280', 'freq': '2400', 'type': 'sx128x'},
|
|
'lr1121': {'define': 'USE_LR1121', 'freq': 'dual', 'type': 'lr1121'}
|
|
}
|
|
|
|
# Pin mappings from ELRS to Meshtastic
|
|
self.pin_mappings = {
|
|
# Basic SPI pins
|
|
'radio_mosi': 'LORA_MOSI',
|
|
'radio_miso': 'LORA_MISO',
|
|
'radio_sck': 'LORA_SCK',
|
|
'radio_nss': 'LORA_CS',
|
|
'radio_rst': 'LORA_RESET',
|
|
|
|
# Radio specific pins
|
|
'radio_dio0': 'LORA_DIO0',
|
|
'radio_dio1': 'LORA_DIO1',
|
|
'radio_busy': 'LORA_BUSY',
|
|
'radio_txen': 'LORA_TXEN',
|
|
'radio_rxen': 'LORA_RXEN',
|
|
|
|
# Second radio pins (for true diversity)
|
|
'radio_nss_2': 'LORA_CS_2',
|
|
'radio_rst_2': 'LORA_RESET_2',
|
|
'radio_dio0_2': 'LORA_DIO0_2',
|
|
'radio_dio1_2': 'LORA_DIO1_2',
|
|
'radio_busy_2': 'LORA_BUSY_2',
|
|
'radio_txen_2': 'LORA_TXEN_2',
|
|
'radio_rxen_2': 'LORA_RXEN_2',
|
|
|
|
# LED pins
|
|
'led': 'LED_PIN',
|
|
'led_red': 'LED_PIN',
|
|
'led_blue': 'LED_PIN',
|
|
'led_rgb': 'NEOPIXEL_DATA',
|
|
|
|
# Other pins
|
|
'pin_button': 'BUTTON_PIN',
|
|
'serial_rx': 'SERIAL_RX_PIN',
|
|
'serial_tx': 'SERIAL_TX_PIN',
|
|
}
|
|
|
|
def clone_elrs_repo(self) -> bool:
|
|
"""Clone the ELRS targets repository to a temporary directory"""
|
|
try:
|
|
# Create temporary directory
|
|
self.temp_repo_path = Path(tempfile.mkdtemp(prefix="elrs_targets_"))
|
|
print(f"Cloning ELRS targets repository to {self.temp_repo_path}")
|
|
|
|
# Clone the repository
|
|
result = subprocess.run([
|
|
'git', 'clone', '--depth', '1',
|
|
'https://github.com/ExpressLRS/targets.git',
|
|
str(self.temp_repo_path)
|
|
], capture_output=True, text=True)
|
|
|
|
if result.returncode != 0:
|
|
print(f"Failed to clone repository: {result.stderr}")
|
|
return False
|
|
|
|
print("Successfully cloned ELRS targets repository")
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"Error cloning repository: {e}")
|
|
return False
|
|
|
|
def cleanup_repo(self):
|
|
"""Remove the temporary repository directory"""
|
|
if self.temp_repo_path and self.temp_repo_path.exists():
|
|
try:
|
|
shutil.rmtree(self.temp_repo_path)
|
|
print(f"Cleaned up temporary repository at {self.temp_repo_path}")
|
|
except Exception as e:
|
|
print(f"Warning: Failed to cleanup temporary directory: {e}")
|
|
|
|
def load_elrs_data(self) -> bool:
|
|
"""Load ELRS targets.json and hardware definitions from local repository"""
|
|
if not self.temp_repo_path or not self.temp_repo_path.exists():
|
|
print("Error: Repository not cloned. Call clone_elrs_repo() first.")
|
|
return False
|
|
|
|
print("Loading ELRS data from local repository...")
|
|
|
|
# Load targets.json
|
|
targets_file = self.temp_repo_path / 'targets.json'
|
|
if not targets_file.exists():
|
|
print(f"Error: targets.json not found at {targets_file}")
|
|
return False
|
|
|
|
print(" Loading targets.json...")
|
|
with open(targets_file, 'r') as f:
|
|
self.targets_data = json.load(f)
|
|
|
|
print(f" Loaded {len(self.targets_data)} target brands")
|
|
|
|
# Load hardware definitions from RX directory only
|
|
hw_dir = self.temp_repo_path / 'RX'
|
|
if not hw_dir.exists():
|
|
print(" Warning: RX directory not found")
|
|
return False
|
|
|
|
print(" Loading RX directory...")
|
|
hw_files = list(hw_dir.glob('*.json'))
|
|
|
|
if not hw_files:
|
|
print(" No JSON files found in RX")
|
|
return False
|
|
|
|
print(f" Found {len(hw_files)} files")
|
|
|
|
for hw_file in hw_files:
|
|
try:
|
|
with open(hw_file, 'r') as f:
|
|
hw_data = json.load(f)
|
|
self.hardware_data[hw_file.name] = hw_data
|
|
except Exception as e:
|
|
print(f" Warning: Failed to load {hw_file.name}: {e}")
|
|
|
|
print(f" Successfully loaded {len(self.hardware_data)} hardware definitions")
|
|
return True
|
|
|
|
def extract_targets(self) -> List[Dict]:
|
|
"""Extract and process all supported targets"""
|
|
processed_targets = []
|
|
|
|
for brand, categories in self.targets_data.items():
|
|
if not isinstance(categories, dict):
|
|
continue
|
|
|
|
for category, targets in categories.items():
|
|
if not isinstance(targets, dict) or not category.startswith('rx_'):
|
|
continue # Only process RX targets
|
|
|
|
for target_name, target_data in targets.items():
|
|
if not isinstance(target_data, dict):
|
|
continue
|
|
|
|
# Get hardware layout file
|
|
layout_file = target_data.get('layout_file', '')
|
|
if not layout_file:
|
|
continue
|
|
|
|
# Find the hardware definition
|
|
hw_filename = layout_file if layout_file.endswith('.json') else f"{layout_file}.json"
|
|
hw_data = self.hardware_data.get(hw_filename)
|
|
|
|
if not hw_data:
|
|
print(f"Warning: No hardware definition found for {hw_filename}")
|
|
continue
|
|
|
|
# Process this target
|
|
target_config = self.process_target(brand, target_name, category, target_data, hw_data)
|
|
if target_config:
|
|
processed_targets.append(target_config)
|
|
|
|
return processed_targets
|
|
|
|
def process_target(self, brand: str, target_name: str, category: str, target_data: Dict, hw_data: Dict) -> Optional[Dict]:
|
|
"""Process a single target"""
|
|
# Get firmware info
|
|
firmware = target_data.get('firmware', '')
|
|
|
|
# Determine platform from firmware first, then fallback to target_data
|
|
platform = self.detect_platform_from_firmware(firmware) or target_data.get('platform', '')
|
|
|
|
# Check if platform is supported
|
|
if platform not in self.supported_platforms:
|
|
return None
|
|
|
|
# Determine radio chip - use firmware only
|
|
radio_chip = self.detect_radio_chip(firmware)
|
|
if not radio_chip:
|
|
return None
|
|
|
|
radio_info = self.radio_chips[radio_chip]
|
|
pins = self.extract_pins(hw_data)
|
|
is_diversity = self.detect_true_diversity(hw_data)
|
|
define_name = self.generate_define_name(brand, target_name, radio_info['freq'])
|
|
|
|
return {
|
|
'brand': brand,
|
|
'name': target_name,
|
|
'category': category,
|
|
'platform': platform,
|
|
'firmware': firmware,
|
|
'define_name': define_name,
|
|
'radio_chip': radio_chip,
|
|
'radio_info': radio_info,
|
|
'pins': pins,
|
|
'is_diversity': is_diversity,
|
|
'hw_data': hw_data
|
|
}
|
|
|
|
def detect_radio_chip(self, firmware: str) -> Optional[str]:
|
|
"""Detect radio chip from firmware ONLY"""
|
|
if not firmware:
|
|
return None
|
|
|
|
firmware_upper = firmware.upper()
|
|
|
|
# Check for explicit radio chip mentions first
|
|
if 'LR1121' in firmware_upper:
|
|
return 'lr1121'
|
|
elif 'SX1280' in firmware_upper:
|
|
return 'sx1280'
|
|
elif 'SX1276' in firmware_upper or 'SX127X' in firmware_upper or 'SX1277' in firmware_upper:
|
|
return 'sx1276'
|
|
|
|
# Fall back to frequency detection
|
|
if '2400' in firmware_upper:
|
|
return 'sx1280'
|
|
elif '900' in firmware_upper:
|
|
return 'sx1276'
|
|
|
|
return None
|
|
|
|
def detect_platform_from_firmware(self, firmware: str) -> Optional[str]:
|
|
"""Detect platform from firmware string"""
|
|
if not firmware:
|
|
return None
|
|
|
|
firmware_upper = firmware.upper()
|
|
|
|
# Check for platform in firmware name
|
|
if 'ESP32C3' in firmware_upper:
|
|
return 'esp32-c3'
|
|
elif 'ESP32S3' in firmware_upper:
|
|
return 'esp32-s3'
|
|
elif 'ESP32' in firmware_upper:
|
|
return 'esp32'
|
|
|
|
return None
|
|
|
|
def detect_true_diversity(self, hw_data: Dict) -> bool:
|
|
"""Detect if this is a true diversity setup by checking for second radio pins"""
|
|
diversity_pins = ['radio_dio0_2', 'radio_dio1_2', 'radio_rst_2', 'radio_nss_2', 'radio_busy_2']
|
|
|
|
return any(pin in hw_data and isinstance(hw_data[pin], int) for pin in diversity_pins)
|
|
|
|
def extract_pins(self, hw_data: Dict) -> Dict[str, int]:
|
|
"""Extract pin definitions from hardware data"""
|
|
pins = {}
|
|
|
|
for elrs_pin, meshtastic_pin in self.pin_mappings.items():
|
|
if elrs_pin in hw_data and isinstance(hw_data[elrs_pin], int):
|
|
pins[meshtastic_pin] = hw_data[elrs_pin]
|
|
|
|
return pins
|
|
|
|
def generate_define_name(self, brand: str, target_name: str, freq: str) -> str:
|
|
"""Generate a clean define name for the target"""
|
|
# Clean up strings and remove common noise words
|
|
brand_clean = brand.upper().replace(' ', '_').replace('-', '_')
|
|
target_clean = target_name.upper().replace(' ', '_').replace('-', '_')
|
|
freq_clean = freq.upper()
|
|
|
|
for word in ['MODULE', 'EXPRESSLRS', 'ELRS']:
|
|
brand_clean = brand_clean.replace(word, '')
|
|
target_clean = target_clean.replace(word, '')
|
|
|
|
# Clean up extra underscores
|
|
brand_clean = '_'.join(filter(None, brand_clean.split('_')))
|
|
target_clean = '_'.join(filter(None, target_clean.split('_')))
|
|
|
|
return f"ELRS_{brand_clean}_{target_clean}_{freq_clean}"
|
|
|
|
def group_targets_by_pins(self, targets: List[Dict]) -> Dict[str, List[Dict]]:
|
|
"""Group targets that have identical pin configurations"""
|
|
pin_groups = {}
|
|
|
|
for target in targets:
|
|
# Create a signature of the pins and radio chip for grouping
|
|
pin_sig = self.create_pin_signature(target)
|
|
|
|
if pin_sig not in pin_groups:
|
|
pin_groups[pin_sig] = []
|
|
pin_groups[pin_sig].append(target)
|
|
|
|
return pin_groups
|
|
|
|
def create_pin_signature(self, target: Dict) -> str:
|
|
"""Create a unique signature for pin configuration AND radio chip AND diversity status"""
|
|
# Sort pins to create consistent signature
|
|
pin_items = sorted(target['pins'].items())
|
|
radio_chip = target['radio_info']['type']
|
|
is_diversity = target['is_diversity']
|
|
return f"{pin_items}_{radio_chip}_{is_diversity}"
|
|
|
|
def generate_variant_file(self, platform: str, pin_groups: Dict, output_dir: Path):
|
|
"""Generate variant.h file for a platform"""
|
|
variant_dir = output_dir / platform
|
|
variant_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
variant_file = variant_dir / 'variant.h'
|
|
|
|
with open(variant_file, 'w') as f:
|
|
f.write(self.generate_variant_content(platform, pin_groups))
|
|
|
|
print(f"Generated {variant_file}")
|
|
|
|
def generate_variant_content(self, platform: str, pin_groups: Dict) -> str:
|
|
"""Generate the content of variant.h file"""
|
|
content = []
|
|
|
|
# Header
|
|
content.append("// ELRS Target Selection - automatically defined by PlatformIO environment")
|
|
|
|
# Generate target selection defines
|
|
all_targets = []
|
|
for targets_list in pin_groups.values():
|
|
all_targets.extend(targets_list)
|
|
|
|
for i, target in enumerate(all_targets):
|
|
content.append(f"// #define {target['define_name']}")
|
|
|
|
content.append("")
|
|
content.append("// Common settings")
|
|
content.append("#undef HAS_GPS")
|
|
content.append("#undef GPS_RX_PIN")
|
|
content.append("#undef GPS_TX_PIN")
|
|
content.append("#undef EXT_NOTIFY_OUT")
|
|
content.append("")
|
|
|
|
# Add global chip-specific mappings at the top
|
|
content.append("// Global chip-specific pin mappings")
|
|
content.append("// SX128X mappings (2.4GHz)")
|
|
content.append("#define SX128X_CS LORA_CS")
|
|
content.append("#define SX128X_DIO1 LORA_DIO1")
|
|
content.append("#define SX128X_BUSY LORA_BUSY")
|
|
content.append("#define SX128X_RESET LORA_RESET")
|
|
content.append("")
|
|
content.append("// LR1121 mappings (dual-band)")
|
|
content.append("#define LR1121_SPI_NSS_PIN LORA_CS")
|
|
content.append("#define LR1121_SPI_SCK_PIN LORA_SCK")
|
|
content.append("#define LR1121_SPI_MOSI_PIN LORA_MOSI")
|
|
content.append("#define LR1121_SPI_MISO_PIN LORA_MISO")
|
|
content.append("#define LR1121_NRESET_PIN LORA_RESET")
|
|
content.append("#define LR1121_BUSY_PIN LORA_BUSY")
|
|
content.append("#define LR1121_IRQ_PIN LORA_DIO1")
|
|
content.append("")
|
|
content.append("// Second radio mappings for true diversity")
|
|
content.append("#define SX128X_CS_2 LORA_CS_2")
|
|
content.append("#define SX128X_DIO0_2 LORA_DIO0_2")
|
|
content.append("#define SX128X_DIO1_2 LORA_DIO1_2")
|
|
content.append("#define SX128X_BUSY_2 LORA_BUSY_2")
|
|
content.append("#define SX128X_RESET_2 LORA_RESET_2")
|
|
content.append("#define LR1121_SPI_NSS_2_PIN LORA_CS_2")
|
|
content.append("#define LR1121_NRESET_2_PIN LORA_RESET_2")
|
|
content.append("#define LR1121_BUSY_2_PIN LORA_BUSY_2")
|
|
content.append("#define LR1121_IRQ_2_PIN LORA_DIO1_2")
|
|
content.append("")
|
|
|
|
# Generate pin configurations for each group
|
|
group_num = 1
|
|
for pin_sig, targets_list in pin_groups.items():
|
|
target = targets_list[0] # All targets in group have same pins/radio chip
|
|
|
|
if len(targets_list) > 1:
|
|
content.append(f"// Pin Configuration {group_num} - Shared by multiple targets")
|
|
conditions = " || ".join([f"defined({t['define_name']})" for t in targets_list])
|
|
content.append(f"#if {conditions}")
|
|
else:
|
|
content.append(f"// {target['brand']} {target['name']}")
|
|
content.append(f"#ifdef {target['define_name']}")
|
|
|
|
content.append(f"#define {target['radio_info']['define']}")
|
|
|
|
# Add TWO_RADIOS define if this is a diversity setup
|
|
if target['is_diversity']:
|
|
content.append("#define TWO_RADIOS")
|
|
|
|
content.append("")
|
|
|
|
# Separate pins into categories
|
|
lora_pins = {k: v for k, v in target['pins'].items() if k.startswith('LORA_') and not k.endswith('_2')}
|
|
lora2_pins = {k: v for k, v in target['pins'].items() if k.startswith('LORA_') and k.endswith('_2')}
|
|
other_pins = {k: v for k, v in target['pins'].items() if not k.startswith('LORA_')}
|
|
|
|
# Output radio pins
|
|
if lora_pins:
|
|
content.append("// Radio pins")
|
|
content.extend(f"#define {pin_name} {pin_value}" for pin_name, pin_value in sorted(lora_pins.items()))
|
|
content.append("")
|
|
|
|
# Output second radio pins for diversity
|
|
if lora2_pins:
|
|
content.append("// Second radio pins (true diversity)")
|
|
content.extend(f"#define {pin_name} {pin_value}" for pin_name, pin_value in sorted(lora2_pins.items()))
|
|
content.append("")
|
|
|
|
# Output other pins
|
|
if other_pins:
|
|
content.append("// Other pins")
|
|
content.extend(f"#define {pin_name} {pin_value}" for pin_name, pin_value in sorted(other_pins.items()))
|
|
content.append("")
|
|
# Add special features
|
|
if 'NEOPIXEL_DATA' in target['pins']:
|
|
content.extend([
|
|
"",
|
|
"// RGB LED",
|
|
"#define HAS_NEOPIXEL",
|
|
"#define NEOPIXEL_COUNT 1",
|
|
"#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800)"
|
|
])
|
|
|
|
if 'BUTTON_PIN' in target['pins']:
|
|
content.append("#define BUTTON_NEED_PULLUP")
|
|
|
|
# Close conditional block
|
|
if len(targets_list) > 1:
|
|
conditions = " || ".join([f"defined({t['define_name']})" for t in targets_list])
|
|
content.append(f"#endif // {conditions}")
|
|
else:
|
|
content.append(f"#endif // {target['define_name']}")
|
|
content.append("")
|
|
|
|
group_num += 1
|
|
|
|
# Set PIN_ENABLE_HIGH to second radio CS pin for diversity setups
|
|
has_diversity = any(target['is_diversity'] for targets_list in pin_groups.values() for target in targets_list)
|
|
if has_diversity:
|
|
# Find the CS_2 pin from any diversity target
|
|
for targets_list in pin_groups.values():
|
|
for target in targets_list:
|
|
if target['is_diversity'] and 'LORA_CS_2' in target['pins']:
|
|
content.extend([
|
|
"// Set second radio CS pin high at startup to disable it",
|
|
"#if defined(TWO_RADIOS)",
|
|
"#define PIN_ENABLE_HIGH LORA_CS_2",
|
|
"#endif // TWO_RADIOS",
|
|
""
|
|
])
|
|
break
|
|
else:
|
|
continue
|
|
break
|
|
|
|
# Set LR11X0_DIO_AS_RF_SWITCH only if USE_LR1121 is defined
|
|
has_lr1121 = any(target['radio_chip'] == 'lr1121' for targets_list in pin_groups.values() for target in targets_list)
|
|
if has_lr1121:
|
|
content.extend([
|
|
"// LR1121 RF switch control",
|
|
"#if defined(USE_LR1121)",
|
|
"#define LR11X0_DIO_AS_RF_SWITCH",
|
|
"#endif // USE_LR1121",
|
|
""
|
|
])
|
|
|
|
# Footer - ensure only one target is selected
|
|
all_defines = [t['define_name'] for targets_list in pin_groups.values() for t in targets_list]
|
|
condition = " + ".join([f"defined({define})" for define in all_defines])
|
|
content.extend([
|
|
"// Ensure only one target is selected",
|
|
f"#if {condition} != 1",
|
|
'#error "Exactly one ELRS target must be defined"',
|
|
"#endif"
|
|
])
|
|
|
|
return '\n'.join(content)
|
|
|
|
def generate_platformio_ini(self, platform: str, targets: List[Dict], output_dir: Path):
|
|
"""Generate platformio.ini with individual environments for each target"""
|
|
variant_dir = output_dir / platform
|
|
variant_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Platform-specific configuration
|
|
platform_config = {
|
|
'esp32': {
|
|
'base': 'esp32_base',
|
|
'board': 'esp32-pico-d4',
|
|
'board_level': None
|
|
},
|
|
'esp32-c3': {
|
|
'base': 'esp32c3_base',
|
|
'board': 'esp32-c3-devkitm-1',
|
|
'board_level': 'extra'
|
|
},
|
|
'esp32-s3': {
|
|
'base': 'esp32c3_base', # Using esp32c3_base as reference from your example
|
|
'board': 'esp32-s3-devkitc-1',
|
|
'board_level': 'extra'
|
|
}
|
|
}
|
|
|
|
config = platform_config.get(platform, platform_config['esp32'])
|
|
content = []
|
|
|
|
# Generate individual environment for each target
|
|
for target in targets:
|
|
env_name = f"elrs-{target['define_name'].lower().replace('elrs_', '').replace('_', '-')}"
|
|
target_dir = f"variants/esp32/elrs/{platform}"
|
|
|
|
content.extend([
|
|
f"[env:{env_name}]",
|
|
f"extends = {config['base']}",
|
|
"platform = espressif32",
|
|
f"board = {config['board']}",
|
|
"framework = arduino"
|
|
])
|
|
|
|
if config['board_level']:
|
|
content.append(f"board_level = {config['board_level']}")
|
|
|
|
content.extend([
|
|
"build_flags =",
|
|
f" ${{{config['base']}.build_flags}}",
|
|
f" -I {target_dir}",
|
|
f" -D {target['define_name']}",
|
|
f" -D PRIVATE_HW",
|
|
" -O2",
|
|
" -D CONFIG_DISABLE_HAL_LOCKS=1"
|
|
])
|
|
|
|
# Add monitor speed for non-esp32 platforms
|
|
if platform != 'esp32':
|
|
content.append("monitor_speed = 115200")
|
|
|
|
content.extend([
|
|
"upload_protocol = esptool",
|
|
"upload_speed = 460800" if platform == 'esp32' else "upload_speed = 921600",
|
|
"lib_deps =",
|
|
f" ${{{config['base']}.lib_deps}}",
|
|
""
|
|
])
|
|
|
|
with open(variant_dir / 'platformio.ini', 'w') as f:
|
|
f.write('\n'.join(content))
|
|
|
|
print(f"Generated {variant_dir / 'platformio.ini'} with {len(targets)} environments")
|
|
|
|
def copy_rfswitch_file(self, platform: str, output_dir: Path):
|
|
"""Copy rfswitch.h file to the variant directory"""
|
|
variant_dir = output_dir / platform
|
|
variant_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Source rfswitch.h file (in the same directory as this script)
|
|
source_file = Path(__file__).parent / 'rfswitch.h'
|
|
target_file = variant_dir / 'rfswitch.h'
|
|
|
|
if source_file.exists():
|
|
shutil.copy2(source_file, target_file)
|
|
print(f"Copied rfswitch.h to {target_file}")
|
|
else:
|
|
print(f"Warning: rfswitch.h not found at {source_file}")
|
|
|
|
def convert(self, output_dir: str) -> bool:
|
|
"""Main conversion function"""
|
|
output_path = Path(output_dir)
|
|
|
|
# Clone the repository
|
|
if not self.clone_elrs_repo():
|
|
return False
|
|
|
|
try:
|
|
if not self.load_elrs_data():
|
|
return False
|
|
|
|
targets = self.extract_targets()
|
|
if not targets:
|
|
print("No targets found to convert")
|
|
return False
|
|
|
|
print(f"Found {len(targets)} targets to convert")
|
|
|
|
# Group targets by platform
|
|
by_platform = {}
|
|
for target in targets:
|
|
platform = target['platform']
|
|
by_platform.setdefault(platform, []).append(target)
|
|
|
|
# Generate files for each platform
|
|
for platform, platform_targets in by_platform.items():
|
|
print(f"Processing {len(platform_targets)} targets for {platform}")
|
|
|
|
pin_groups = self.group_targets_by_pins(platform_targets)
|
|
self.generate_variant_file(platform, pin_groups, output_path)
|
|
self.generate_platformio_ini(platform, platform_targets, output_path)
|
|
self.copy_rfswitch_file(platform, output_path)
|
|
|
|
return True
|
|
|
|
finally:
|
|
self.cleanup_repo()
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Convert ELRS targets to Meshtastic format')
|
|
parser.add_argument('--output-dir', default='.',
|
|
help='Output directory for generated files (default: current directory)')
|
|
|
|
args = parser.parse_args()
|
|
|
|
converter = SimpleELRSConverter()
|
|
|
|
if converter.convert(args.output_dir):
|
|
print("Conversion completed successfully!")
|
|
return 0
|
|
else:
|
|
print("Conversion failed!")
|
|
return 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|