#!/usr/bin/env python3 import os import paramiko import logging import re # --- CONFIGURATION --- FILES = [ '/home/pi/eumetsat_all_tles.txt', '/home/pi/Spacetrack_downloaded_satellites.txt', '/home/pi/Celestrak_downloaded_satellites.txt', '/home/pi/SatNOGs_downloaded_satellites.txt' ] SATELLITES_LIST = '/home/pi/satellites.txt' FINAL_OUTPUT = '/home/pi/jdt_tle.txt' # SFTP CONFIGURATION SFTP_HOST = 'YOURhostname' SFTP_USER = 'YOURusername' SFTP_PASS = 'YOURpasswrd' REMOTE_PATH = 'www/david/satnogs' REMOTE_FILENAME = 'jdt_tle.txt' logging.basicConfig( filename='/home/pi/merger.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) def decode_alpha5(s): """Converts a 5-character Alpha-5 TLE string (e.g., 'A0001') into a true 6-digit ID string.""" s = s.strip() if not s: return "" # If it is purely numeric, just return it zero-padded if s.isdigit(): return s.zfill(5) # If it has an alpha character at the start, decode it per 18th SDS standard first_char = s[0].upper() if 'A' <= first_char <= 'Z': # 'A' corresponds to 100,000, 'B' to 110,000, etc. (skipping I and O if required, but standard sequence holds) # Space-Track baseline standard tracking calculation: val = (ord(first_char) - ord('A') + 10) * 10000 try: remainder = int(s[1:]) return str(val + remainder) except ValueError: return s return s def load_preferred_names(): name_map = {} try: with open(SATELLITES_LIST, 'r') as f: for line in f: line = line.strip() if not line or line.startswith('#'): continue # FIXED: Changed \d{5} to \d+ to handle 5, 6, or more digits natively match = re.search(r'(\d+)U?\s*#\s*(.+)', line) if match: sat_id = match.group(1).strip() name = match.group(2).strip() name_map[sat_id] = name except Exception as e: logging.error(f"Failed to load satellites.txt: {e}") print(f"Loaded {len(name_map)} named satellites from satellites.txt") return name_map PREFERRED_NAMES = load_preferred_names() def is_valid_line1(line): return line.startswith('1 ') and len(line) >= 60 def is_valid_line2(line): return line.startswith('2 ') and len(line) >= 60 def get_epoch(line1): try: return float(line1[18:32].strip()) except: return 0.0 def get_norad_id(line2): try: # FIXED: Extract the raw 5-char field, then decode Alpha-5 values to true integers raw_field = line2[2:7] return decode_alpha5(raw_field) except: return None def merge_tles(): master_data = {} logging.info("--- Starting Merge v9 (6-Digit Alpha-5 Decoding Patch) ---") for file_path in FILES: if not os.path.exists(file_path): continue with open(file_path, 'r') as f: lines = [line.strip() for line in f if line.strip()] i = 0 while i < len(lines): if is_valid_line1(lines[i]): l1 = lines[i] l2 = None sat_id = None for j in range(i+1, min(i+6, len(lines))): if is_valid_line2(lines[j]): l2 = lines[j] sat_id = get_norad_id(l2) break if sat_id and l2: epoch = get_epoch(l1) name = PREFERRED_NAMES.get(sat_id, f"NORAD_{sat_id}") if sat_id not in master_data or epoch > master_data[sat_id][0]: master_data[sat_id] = (epoch, name, l1, l2) i = j i += 1 # Write final compilation using network-standard CR+LF lines # FIXED: Handled natural string/integer key sorting safely with open(FINAL_OUTPUT, 'w', newline='\r\n') as f: for sat_id in sorted(master_data.keys(), key=lambda x: int(x) if x.isdigit() else 999999): _, name, l1, l2 = master_data[sat_id] f.write(f"{name}\n{l1}\n{l2}\n") print(f"✅ Final merge completed: {len(master_data)} satellites") # SFTP Deployment try: ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(SFTP_HOST, username=SFTP_USER, password=SFTP_PASS) sftp = ssh.open_sftp() sftp.chdir(REMOTE_PATH) sftp.put(FINAL_OUTPUT, REMOTE_FILENAME) sftp.close() ssh.close() print("✅ Uploaded successfully") except Exception as e: print(f"⚠️ SFTP failed: {e}") if __name__ == "__main__": merge_tles()