Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """ | |
| GPS SDR Signal Generator - Educational Interface | |
| Built with anycoder - https://huggingface.co/spaces/akhaliq/anycoder | |
| """ | |
| import tkinter as tk | |
| from tkinter import ttk, messagebox, scrolledtext | |
| from tkinter import font as tkfont | |
| import webbrowser | |
| import math | |
| import random | |
| from datetime import datetime, timedelta | |
| import threading | |
| import time | |
| class GPSSDRApp: | |
| def __init__(self, root): | |
| self.root = root | |
| self.root.title("GPS SDR Signal Generator | Educational Interface") | |
| self.root.geometry("1400x900") | |
| self.root.minsize(1200, 800) | |
| # Colors - Cyberpunk/GPS theme | |
| self.colors = { | |
| 'bg_dark': '#0a0e17', | |
| 'bg_card': '#141928', | |
| 'primary': '#00ff88', | |
| 'secondary': '#00ccff', | |
| 'danger': '#ff4444', | |
| 'text': '#e0e6ed', | |
| 'text_muted': '#8b95a8', | |
| 'border': '#00ff8820' | |
| } | |
| # Configure root | |
| self.root.configure(bg=self.colors['bg_dark']) | |
| # Custom fonts | |
| self.fonts = { | |
| 'header': tkfont.Font(family='Segoe UI', size=16, weight='bold'), | |
| 'title': tkfont.Font(family='Segoe UI', size=12, weight='bold'), | |
| 'normal': tkfont.Font(family='Segoe UI', size=10), | |
| 'mono': tkfont.Font(family='Courier New', size=10), | |
| 'small': tkfont.Font(family='Segoe UI', size=9) | |
| } | |
| # Variables | |
| self.lat_var = tk.DoubleVar(value=40.7128) | |
| self.lon_var = tk.DoubleVar(value=-74.0060) | |
| self.alt_var = tk.DoubleVar(value=10) | |
| self.duration_var = tk.IntVar(value=300) | |
| self.sample_rate_var = tk.StringVar(value='2.6') | |
| self.power_var = tk.IntVar(value=-80) | |
| self.mode_var = tk.StringVar(value='static') | |
| self.speed_var = tk.DoubleVar(value=5) | |
| self.direction_var = tk.DoubleVar(value=0) | |
| self.radius_var = tk.DoubleVar(value=100) | |
| self.prn_var = tk.StringVar(value='1,2,3,4,5,6,7,8') | |
| self.iono_var = tk.BooleanVar(value=True) | |
| self.tropo_var = tk.BooleanVar(value=True) | |
| self.noise_var = tk.BooleanVar(value=True) | |
| # Satellite animation | |
| self.satellites = [] | |
| self.animating = False | |
| self.setup_styles() | |
| self.create_ui() | |
| self.start_satellite_animation() | |
| # Initial log | |
| self.after_init() | |
| def setup_styles(self): | |
| """Configure ttk styles for modern look""" | |
| style = ttk.Style() | |
| style.theme_use('clam') | |
| # Configure styles | |
| style.configure('Custom.TFrame', background=self.colors['bg_dark']) | |
| style.configure('Card.TFrame', background=self.colors['bg_card']) | |
| style.configure('Custom.TLabel', | |
| background=self.colors['bg_card'], | |
| foreground=self.colors['text'], | |
| font=self.fonts['normal']) | |
| style.configure('Title.TLabel', | |
| background=self.colors['bg_card'], | |
| foreground=self.colors['primary'], | |
| font=self.fonts['title']) | |
| style.configure('Muted.TLabel', | |
| background=self.colors['bg_card'], | |
| foreground=self.colors['text_muted'], | |
| font=self.fonts['small']) | |
| style.configure('Custom.TButton', | |
| background=self.colors['primary'], | |
| foreground='#000000', | |
| font=self.fonts['normal'], | |
| padding=10) | |
| style.configure('Secondary.TButton', | |
| background=self.colors['bg_card'], | |
| foreground=self.colors['secondary'], | |
| font=self.fonts['normal']) | |
| style.configure('Danger.TButton', | |
| background=self.colors['bg_card'], | |
| foreground=self.colors['danger'], | |
| font=self.fonts['normal']) | |
| style.configure('Custom.TEntry', | |
| fieldbackground='#050810', | |
| foreground=self.colors['text'], | |
| insertcolor=self.colors['primary']) | |
| style.configure('Custom.TCombobox', | |
| fieldbackground='#050810', | |
| foreground=self.colors['text']) | |
| # Notebook (tabs) | |
| style.configure('Custom.TNotebook', | |
| background=self.colors['bg_card'], | |
| tabmargins=[2, 5, 2, 0]) | |
| style.configure('Custom.TNotebook.Tab', | |
| background=self.colors['bg_dark'], | |
| foreground=self.colors['text_muted'], | |
| padding=[15, 8], | |
| font=self.fonts['normal']) | |
| style.map('Custom.TNotebook.Tab', | |
| background=[('selected', self.colors['bg_card'])], | |
| foreground=[('selected', self.colors['primary'])], | |
| expand=[('selected', [1, 1, 1, 0])]) | |
| # Scale | |
| style.configure('Custom.Horizontal.TScale', | |
| background=self.colors['bg_card'], | |
| troughcolor='#050810') | |
| def create_ui(self): | |
| """Create the main user interface""" | |
| # Main container with padding | |
| main_container = tk.Frame(self.root, bg=self.colors['bg_dark']) | |
| main_container.pack(fill=tk.BOTH, expand=True, padx=20, pady=20) | |
| # Header | |
| self.create_header(main_container) | |
| # Warning banner | |
| self.create_warning_banner(main_container) | |
| # Content area | |
| content = tk.Frame(main_container, bg=self.colors['bg_dark']) | |
| content.pack(fill=tk.BOTH, expand=True, pady=15) | |
| # Two-column layout | |
| content.grid_columnconfigure(0, weight=1) | |
| content.grid_columnconfigure(1, weight=1) | |
| content.grid_rowconfigure(0, weight=1) | |
| # Left column - Map and Coordinates | |
| left_frame = self.create_left_column(content) | |
| left_frame.grid(row=0, column=0, sticky='nsew', padx=(0, 10)) | |
| # Right column - Configuration | |
| right_frame = self.create_right_column(content) | |
| right_frame.grid(row=0, column=1, sticky='nsew', padx=(10, 0)) | |
| # Bottom section - Satellite visualization and Command output | |
| bottom_frame = self.create_bottom_section(main_container) | |
| bottom_frame.pack(fill=tk.BOTH, expand=True, pady=(15, 0)) | |
| def create_header(self, parent): | |
| """Create the header with logo and built-with link""" | |
| header = tk.Frame(parent, bg='#0d1117', height=60) | |
| header.pack(fill=tk.X, pady=(0, 10)) | |
| header.pack_propagate(False) | |
| # Left side - Logo | |
| logo_frame = tk.Frame(header, bg='#0d1117') | |
| logo_frame.pack(side=tk.LEFT, padx=20, pady=10) | |
| # Satellite icon (using text as icon) | |
| icon_label = tk.Label(logo_frame, text='🛰️', | |
| bg='#0d1117', fg=self.colors['primary'], | |
| font=('Segoe UI', 20)) | |
| icon_label.pack(side=tk.LEFT, padx=(0, 10)) | |
| title_label = tk.Label(logo_frame, text='GPS SDR Generator', | |
| bg='#0d1117', fg=self.colors['primary'], | |
| font=self.fonts['header']) | |
| title_label.pack(side=tk.LEFT) | |
| # Right side - Built with link | |
| built_btn = tk.Label(header, text='🔧 Built with anycoder', | |
| bg='#0d1117', fg=self.colors['text_muted'], | |
| font=self.fonts['small'], | |
| cursor='hand2', | |
| padx=15, pady=5) | |
| built_btn.pack(side=tk.RIGHT, padx=20) | |
| built_btn.bind('<Enter>', lambda e: built_btn.config(fg=self.colors['primary'])) | |
| built_btn.bind('<Leave>', lambda e: built_btn.config(fg=self.colors['text_muted'])) | |
| built_btn.bind('<Button-1>', lambda e: webbrowser.open('https://huggingface.co/spaces/akhaliq/anycoder')) | |
| def create_warning_banner(self, parent): | |
| """Create the legal warning banner""" | |
| banner = tk.Frame(parent, bg='#ff444415', highlightbackground=self.colors['danger'], | |
| highlightthickness=2, highlightcolor=self.colors['danger']) | |
| banner.pack(fill=tk.X, pady=(0, 10)) | |
| # Left border accent | |
| left_accent = tk.Frame(banner, bg=self.colors['danger'], width=4) | |
| left_accent.pack(side=tk.LEFT, fill=tk.Y) | |
| content = tk.Frame(banner, bg='#ff444415', padx=15, pady=12) | |
| content.pack(fill=tk.BOTH, expand=True) | |
| # Warning icon | |
| icon_label = tk.Label(content, text='⚠️', bg='#ff444415', | |
| fg=self.colors['danger'], font=('Segoe UI', 24)) | |
| icon_label.pack(side=tk.LEFT, padx=(0, 15)) | |
| # Text content | |
| text_frame = tk.Frame(content, bg='#ff444415') | |
| text_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) | |
| title = tk.Label(text_frame, text='LEGAL NOTICE & SAFETY WARNING', | |
| bg='#ff444415', fg=self.colors['danger'], | |
| font=self.fonts['title']) | |
| title.pack(anchor='w') | |
| desc = tk.Label(text_frame, | |
| text='GPS signal transmission is regulated by law. This tool generates configuration for gps-sdr-sim for educational and authorized testing purposes only. Unauthorized GPS spoofing is illegal and dangerous. Always ensure you have proper authorization and are in a shielded environment before transmitting.', | |
| bg='#ff444415', fg=self.colors['text_muted'], | |
| font=self.fonts['small'], wraplength=800, justify=tk.LEFT) | |
| desc.pack(anchor='w', pady=(5, 0)) | |
| def create_left_column(self, parent): | |
| """Create left column with map and coordinates""" | |
| frame = tk.Frame(parent, bg=self.colors['bg_card'], | |
| highlightbackground=self.colors['border'], | |
| highlightthickness=1, padx=20, pady=20) | |
| frame.grid_propagate(False) | |
| # Header | |
| header = tk.Frame(frame, bg=self.colors['bg_card']) | |
| header.pack(fill=tk.X, pady=(0, 15)) | |
| tk.Label(header, text='🗺️', bg=self.colors['bg_card'], | |
| fg=self.colors['secondary'], font=('Segoe UI', 16)).pack(side=tk.LEFT) | |
| tk.Label(header, text='Target Coordinates', bg=self.colors['bg_card'], | |
| fg=self.colors['primary'], font=self.fonts['title']).pack(side=tk.LEFT, padx=10) | |
| # Map canvas (simulated map) | |
| self.map_canvas = tk.Canvas(frame, bg='#050810', height=300, | |
| highlightthickness=1, highlightbackground=self.colors['border']) | |
| self.map_canvas.pack(fill=tk.BOTH, expand=True, pady=(0, 15)) | |
| # Draw grid pattern on map | |
| self.draw_map_grid() | |
| # Map marker (draggable) | |
| self.map_marker = self.map_canvas.create_oval(0, 0, 20, 20, | |
| fill=self.colors['primary'], | |
| outline='white', width=2) | |
| self.update_map_marker() | |
| # Bind map interactions | |
| self.map_canvas.bind('<Button-1>', self.on_map_click) | |
| self.map_canvas.bind('<B1-Motion>', self.on_map_drag) | |
| # Status grid | |
| status_frame = tk.Frame(frame, bg=self.colors['bg_card']) | |
| status_frame.pack(fill=tk.X, pady=(0, 15)) | |
| status_frame.grid_columnconfigure(0, weight=1) | |
| status_frame.grid_columnconfigure(1, weight=1) | |
| # Status items | |
| self.status_items = {} | |
| status_data = [ | |
| ('Latitude', 'lat-display', '40.7128° N'), | |
| ('Longitude', 'lon-display', '74.0060° W'), | |
| ('Altitude', 'alt-display', '10 m'), | |
| ('Satellites', 'sat-count', '8 Visible') | |
| ] | |
| for i, (label, key, value) in enumerate(status_data): | |
| row = i // 2 | |
| col = i % 2 | |
| item = tk.Frame(status_frame, bg='#050810', padx=10, pady=8, | |
| highlightbackground=self.colors['border'], | |
| highlightthickness=1) | |
| item.grid(row=row, column=col, sticky='nsew', padx=5, pady=5) | |
| tk.Label(item, text=label, bg='#050810', | |
| fg=self.colors['text_muted'], font=self.fonts['small']).pack(anchor='w') | |
| value_label = tk.Label(item, text=value, bg='#050810', | |
| fg=self.colors['primary'], font=self.fonts['mono']) | |
| value_label.pack(anchor='w') | |
| self.status_items[key] = value_label | |
| # Manual input | |
| input_frame = tk.Frame(frame, bg=self.colors['bg_card']) | |
| input_frame.pack(fill=tk.X) | |
| tk.Label(input_frame, text='Manual Coordinates Input', bg=self.colors['bg_card'], | |
| fg=self.colors['text_muted'], font=self.fonts['small']).pack(anchor='w', pady=(0, 8)) | |
| input_row = tk.Frame(input_frame, bg=self.colors['bg_card']) | |
| input_row.pack(fill=tk.X) | |
| self.lat_entry = tk.Entry(input_row, textvariable=self.lat_var, | |
| bg='#050810', fg=self.colors['text'], | |
| insertbackground=self.colors['primary'], | |
| font=self.fonts['mono'], width=15) | |
| self.lat_entry.pack(side=tk.LEFT, padx=(0, 5)) | |
| self.lon_entry = tk.Entry(input_row, textvariable=self.lon_var, | |
| bg='#050810', fg=self.colors['text'], | |
| insertbackground=self.colors['primary'], | |
| font=self.fonts['mono'], width=15) | |
| self.lon_entry.pack(side=tk.LEFT, padx=5) | |
| update_btn = tk.Button(input_row, text='📍 Update', | |
| bg=self.colors['secondary'], fg='#000', | |
| font=self.fonts['small'], padx=10, pady=5, | |
| command=self.update_from_input, | |
| cursor='hand2', relief=tk.FLAT) | |
| update_btn.pack(side=tk.LEFT, padx=5) | |
| return frame | |
| def draw_map_grid(self): | |
| """Draw grid pattern on map canvas""" | |
| width = 400 | |
| height = 300 | |
| # Grid lines | |
| for i in range(0, width, 40): | |
| self.map_canvas.create_line(i, 0, i, height, fill='#00ff8810', width=1) | |
| for i in range(0, height, 40): | |
| self.map_canvas.create_line(0, i, width, i, fill='#00ff8810', width=1) | |
| # Some "streets" for visual interest | |
| self.map_canvas.create_line(50, 100, 350, 80, fill='#1a2332', width=3) | |
| self.map_canvas.create_line(100, 50, 120, 250, fill='#1a2332', width=3) | |
| self.map_canvas.create_line(200, 30, 280, 270, fill='#1a2332', width=2) | |
| self.map_canvas.create_line(30, 180, 370, 200, fill='#1a2332', width=2) | |
| def update_map_marker(self): | |
| """Update marker position based on coordinates""" | |
| # Convert lat/lon to canvas coordinates (simplified) | |
| # Center of canvas represents the current lat/lon | |
| cx = self.map_canvas.winfo_width() // 2 or 200 | |
| cy = self.map_canvas.winfo_height() // 2 or 150 | |
| self.map_canvas.coords(self.map_marker, cx-10, cy-10, cx+10, cy+10) | |
| self.update_status_display() | |
| def on_map_click(self, event): | |
| """Handle map click""" | |
| self.move_marker(event.x, event.y) | |
| def on_map_drag(self, event): | |
| """Handle marker drag""" | |
| self.move_marker(event.x, event.y) | |
| def move_marker(self, x, y): | |
| """Move marker to position and update coordinates""" | |
| cx = max(10, min(x, self.map_canvas.winfo_width() - 10)) | |
| cy = max(10, min(y, self.map_canvas.winfo_height() - 10)) | |
| self.map_canvas.coords(self.map_marker, cx-10, cy-10, cx+10, cy+10) | |
| # Small random offset to simulate map movement | |
| offset_x = (cx - self.map_canvas.winfo_width()//2) * 0.0001 | |
| offset_y = (cy - self.map_canvas.winfo_height()//2) * 0.0001 | |
| new_lat = 40.7128 + offset_y | |
| new_lon = -74.0060 + offset_x | |
| self.lat_var.set(round(new_lat, 4)) | |
| self.lon_var.set(round(new_lon, 4)) | |
| self.update_status_display() | |
| def update_from_input(self): | |
| """Update from manual input""" | |
| self.update_status_display() | |
| self.update_map_marker() | |
| def update_status_display(self): | |
| """Update status labels""" | |
| lat = self.lat_var.get() | |
| lon = self.lon_var.get() | |
| self.status_items['lat-display'].config( | |
| text=f'{abs(lat):.4f}° {"N" if lat >= 0 else "S"}') | |
| self.status_items['lon-display'].config( | |
| text=f'{abs(lon):.4f}° {"E" if lon >= 0 else "W"}') | |
| self.status_items['alt-display'].config(text=f'{self.alt_var.get():.0f} m') | |
| def create_right_column(self, parent): | |
| """Create right column with configuration tabs""" | |
| frame = tk.Frame(parent, bg=self.colors['bg_card'], | |
| highlightbackground=self.colors['border'], | |
| highlightthickness=1, padx=20, pady=20) | |
| # Header | |
| header = tk.Frame(frame, bg=self.colors['bg_card']) | |
| header.pack(fill=tk.X, pady=(0, 15)) | |
| tk.Label(header, text='⚙️', bg=self.colors['bg_card'], | |
| fg=self.colors['secondary'], font=('Segoe UI', 16)).pack(side=tk.LEFT) | |
| tk.Label(header, text='Signal Configuration', bg=self.colors['bg_card'], | |
| fg=self.colors['primary'], font=self.fonts['title']).pack(side=tk.LEFT, padx=10) | |
| # Notebook for tabs | |
| self.notebook = ttk.Notebook(frame, style='Custom.TNotebook') | |
| self.notebook.pack(fill=tk.BOTH, expand=True, pady=(0, 15)) | |
| # Basic tab | |
| basic_frame = tk.Frame(self.notebook, bg=self.colors['bg_card'], padx=15, pady=15) | |
| self.create_basic_tab(basic_frame) | |
| self.notebook.add(basic_frame, text=' Basic ') | |
| # Advanced tab | |
| advanced_frame = tk.Frame(self.notebook, bg=self.colors['bg_card'], padx=15, pady=15) | |
| self.create_advanced_tab(advanced_frame) | |
| self.notebook.add(advanced_frame, text=' Advanced ') | |
| # Trajectory tab | |
| trajectory_frame = tk.Frame(self.notebook, bg=self.colors['bg_card'], padx=15, pady=15) | |
| self.create_trajectory_tab(trajectory_frame) | |
| self.notebook.add(trajectory_frame, text=' Trajectory ') | |
| # Generate button | |
| gen_btn = tk.Button(frame, text='🖥️ Generate Windows Command', | |
| bg=self.colors['primary'], fg='#000', | |
| font=self.fonts['title'], pady=12, | |
| command=self.generate_command, | |
| cursor='hand2', relief=tk.FLAT, | |
| activebackground='#00cc6a') | |
| gen_btn.pack(fill=tk.X) | |
| return frame | |
| def create_basic_tab(self, parent): | |
| """Create basic configuration tab""" | |
| # Simulation Mode | |
| tk.Label(parent, text='Simulation Mode', bg=self.colors['bg_card'], | |
| fg=self.colors['text_muted'], font=self.fonts['small']).pack(anchor='w', pady=(0, 5)) | |
| mode_combo = ttk.Combobox(parent, textvariable=self.mode_var, | |
| values=['static', 'dynamic', 'circle', 'line'], | |
| state='readonly', width=30) | |
| mode_combo.pack(fill=tk.X, pady=(0, 15)) | |
| # Duration | |
| tk.Label(parent, text='Signal Duration (seconds)', bg=self.colors['bg_card'], | |
| fg=self.colors['text_muted'], font=self.fonts['small']).pack(anchor='w', pady=(0, 5)) | |
| tk.Spinbox(parent, from_=1, to=3600, textvariable=self.duration_var, | |
| bg='#050810', fg=self.colors['text'], font=self.fonts['normal'], | |
| insertbackground=self.colors['primary']).pack(fill=tk.X, pady=(0, 15)) | |
| # Sample Rate | |
| tk.Label(parent, text='Sample Rate (MHz)', bg=self.colors['bg_card'], | |
| fg=self.colors['text_muted'], font=self.fonts['small']).pack(anchor='w', pady=(0, 5)) | |
| rate_combo = ttk.Combobox(parent, textvariable=self.sample_rate_var, | |
| values=['2.6', '5.0', '10.0', '20.0'], | |
| state='readonly', width=30) | |
| rate_combo.pack(fill=tk.X, pady=(0, 15)) | |
| # GPS Time | |
| tk.Label(parent, text='GPS Date & Time', bg=self.colors['bg_card'], | |
| fg=self.colors['text_muted'], font=self.fonts['small']).pack(anchor='w', pady=(0, 5)) | |
| # Use current time as default | |
| now = datetime.now() | |
| self.gps_time_var = tk.StringVar(value=now.strftime('%Y-%m-%d %H:%M')) | |
| tk.Entry(parent, textvariable=self.gps_time_var, | |
| bg='#050810', fg=self.colors['text'], | |
| insertbackground=self.colors['primary'], | |
| font=self.fonts['mono']).pack(fill=tk.X) | |
| def create_advanced_tab(self, parent): | |
| """Create advanced configuration tab""" | |
| # Power slider | |
| tk.Label(parent, text='Signal Power (dB)', bg=self.colors['bg_card'], | |
| fg=self.colors['text_muted'], font=self.fonts['small']).pack(anchor='w', pady=(0, 5)) | |
| power_frame = tk.Frame(parent, bg=self.colors['bg_card']) | |
| power_frame.pack(fill=tk.X, pady=(0, 5)) | |
| power_scale = tk.Scale(power_frame, from_=-100, to=-50, | |
| orient=tk.HORIZONTAL, variable=self.power_var, | |
| bg=self.colors['bg_card'], fg=self.colors['primary'], | |
| highlightthickness=0, troughcolor='#050810', | |
| activebackground=self.colors['primary']) | |
| power_scale.pack(fill=tk.X, side=tk.LEFT, expand=True) | |
| self.power_label = tk.Label(power_frame, text='-80 dBm', bg=self.colors['bg_card'], | |
| fg=self.colors['primary'], font=self.fonts['mono'], width=10) | |
| self.power_label.pack(side=tk.RIGHT) | |
| self.power_var.trace('w', lambda *args: self.power_label.config( | |
| text=f'{self.power_var.get()} dBm')) | |
| # Checkboxes | |
| tk.Checkbutton(parent, text='Enable Ionospheric Model', variable=self.iono_var, | |
| bg=self.colors['bg_card'], fg=self.colors['text'], | |
| selectcolor='#050810', activebackground=self.colors['bg_card'], | |
| activeforeground=self.colors['primary']).pack(anchor='w', pady=8) | |
| tk.Checkbutton(parent, text='Enable Tropospheric Model', variable=self.tropo_var, | |
| bg=self.colors['bg_card'], fg=self.colors['text'], | |
| selectcolor='#050810', activebackground=self.colors['bg_card'], | |
| activeforeground=self.colors['primary']).pack(anchor='w', pady=8) | |
| tk.Checkbutton(parent, text='Add AWGN Noise', variable=self.noise_var, | |
| bg=self.colors['bg_card'], fg=self.colors['text'], | |
| selectcolor='#050810', activebackground=self.colors['bg_card'], | |
| activeforeground=self.colors['primary']).pack(anchor='w', pady=8) | |
| # PRN list | |
| tk.Label(parent, text='PRN Satellites (1-32, comma separated)', bg=self.colors['bg_card'], | |
| fg=self.colors['text_muted'], font=self.fonts['small']).pack(anchor='w', pady=(15, 5)) | |
| tk.Entry(parent, textvariable=self.prn_var, | |
| bg='#050810', fg=self.colors['text'], | |
| insertbackground=self.colors['primary'], | |
| font=self.fonts['mono']).pack(fill=tk.X) | |
| def create_trajectory_tab(self, parent): | |
| """Create trajectory configuration tab""" | |
| # Speed | |
| tk.Label(parent, text='Movement Speed (m/s)', bg=self.colors['bg_card'], | |
| fg=self.colors['text_muted'], font=self.fonts['small']).pack(anchor='w', pady=(0, 5)) | |
| tk.Spinbox(parent, from_=0, to=100, textvariable=self.speed_var, | |
| bg='#050810', fg=self.colors['text'], font=self.fonts['normal'], | |
| insertbackground=self.colors['primary']).pack(fill=tk.X, pady=(0, 15)) | |
| # Direction | |
| tk.Label(parent, text='Path Direction (degrees)', bg=self.colors['bg_card'], | |
| fg=self.colors['text_muted'], font=self.fonts['small']).pack(anchor='w', pady=(0, 5)) | |
| tk.Spinbox(parent, from_=0, to=360, textvariable=self.direction_var, | |
| bg='#050810', fg=self.colors['text'], font=self.fonts['normal'], | |
| insertbackground=self.colors['primary']).pack(fill=tk.X, pady=(0, 15)) | |
| # Radius | |
| tk.Label(parent, text='Circle Radius (m) - For circular mode', bg=self.colors['bg_card'], | |
| fg=self.colors['text_muted'], font=self.fonts['small']).pack(anchor='w', pady=(0, 5)) | |
| tk.Spinbox(parent, from_=10, to=10000, textvariable=self.radius_var, | |
| bg='#050810', fg=self.colors['text'], font=self.fonts['normal'], | |
| insertbackground=self.colors['primary']).pack(fill=tk.X) | |
| def create_bottom_section(self, parent): | |
| """Create bottom section with satellite viz and command output""" | |
| frame = tk.Frame(parent, bg=self.colors['bg_dark']) | |
| # Satellite visualization | |
| sat_frame = tk.Frame(frame, bg=self.colors['bg_card'], | |
| highlightbackground=self.colors['border'], | |
| highlightthickness=1, padx=20, pady=20) | |
| sat_frame.pack(fill=tk.X, pady=(0, 15)) | |
| # Header | |
| sat_header = tk.Frame(sat_frame, bg=self.colors['bg_card']) | |
| sat_header.pack(fill=tk.X, pady=(0, 15)) | |
| tk.Label(sat_header, text='🛰️', bg=self.colors['bg_card'], | |
| fg=self.colors['secondary'], font=('Segoe UI', 16)).pack(side=tk.LEFT) | |
| tk.Label(sat_header, text='GPS Constellation Visualization', bg=self.colors['bg_card'], | |
| fg=self.colors['primary'], font=self.fonts['title']).pack(side=tk.LEFT, padx=10) | |
| # Satellite canvas | |
| self.sat_canvas = tk.Canvas(sat_frame, bg='#050810', height=250, | |
| highlightthickness=1, highlightbackground=self.colors['border']) | |
| self.sat_canvas.pack(fill=tk.X) | |
| # Draw orbit rings | |
| cx, cy = 400, 125 | |
| for r in [50, 75, 100, 125]: | |
| self.sat_canvas.create_oval(cx-r, cy-r, cx+r, cy+r, | |
| outline='#00ff8820', width=1) | |
| # Earth center | |
| self.sat_canvas.create_oval(cx-15, cy-15, cx+15, cy+15, | |
| fill=self.colors['secondary'], | |
| outline='', tags='earth') | |
| self.sat_canvas.create_oval(cx-25, cy-25, cx+25, cy+25, | |
| outline=self.colors['secondary'], width=2) | |
| # Create satellites | |
| self.create_satellites() | |
| # Info text | |
| tk.Label(sat_frame, | |
| text='ℹ️ Visual representation of visible satellites based on selected location and time. Blue dot represents receiver position.', | |
| bg=self.colors['bg_card'], fg=self.colors['text_muted'], | |
| font=self.fonts['small'], wraplength=700).pack(anchor='w', pady=(10, 0)) | |
| # Command output | |
| cmd_frame = tk.Frame(frame, bg=self.colors['bg_card'], | |
| highlightbackground=self.colors['border'], | |
| highlightthickness=1, padx=20, pady=20) | |
| cmd_frame.pack(fill=tk.BOTH, expand=True) | |
| # Header | |
| cmd_header = tk.Frame(cmd_frame, bg=self.colors['bg_card']) | |
| cmd_header.pack(fill=tk.X, pady=(0, 15)) | |
| tk.Label(cmd_header, text='💻', bg=self.colors['bg_card'], | |
| fg=self.colors['secondary'], font=('Segoe UI', 16)).pack(side=tk.LEFT) | |
| tk.Label(cmd_header, text='Generated Command', bg=self.colors['bg_card'], | |
| fg=self.colors['primary'], font=self.fonts['title']).pack(side=tk.LEFT, padx=10) | |
| # Command display | |
| cmd_display_frame = tk.Frame(cmd_frame, bg='#050810', | |
| highlightbackground=self.colors['border'], | |
| highlightthickness=1) | |
| cmd_display_frame.pack(fill=tk.X, pady=(0, 10)) | |
| # Copy button | |
| copy_btn = tk.Button(cmd_display_frame, text='📋 Copy', | |
| bg='#1a2332', fg=self.colors['text'], | |
| font=self.fonts['small'], padx=10, pady=2, | |
| command=self.copy_command, cursor='hand2', | |
| relief=tk.FLAT) | |
| copy_btn.pack(side=tk.RIGHT, padx=5, pady=5) | |
| self.cmd_text = tk.Text(cmd_display_frame, bg='#050810', fg=self.colors['primary'], | |
| font=self.fonts['mono'], height=4, wrap=tk.WORD, | |
| padx=10, pady=10, insertbackground=self.colors['primary']) | |
| self.cmd_text.pack(fill=tk.X, side=tk.LEFT, expand=True) | |
| self.cmd_text.insert('1.0', '# Click "Generate Windows Command" to create gps-sdr-sim command...') | |
| self.cmd_text.config(state=tk.DISABLED) | |
| # Console | |
| tk.Label(cmd_frame, text='Console Output', bg=self.colors['bg_card'], | |
| fg=self.colors['text_muted'], font=self.fonts['small']).pack(anchor='w', pady=(0, 5)) | |
| self.console = scrolledtext.ScrolledText(cmd_frame, bg='#050810', fg=self.colors['primary'], | |
| font=self.fonts['mono'], height=8, | |
| padx=10, pady=10, wrap=tk.WORD, | |
| insertbackground=self.colors['primary']) | |
| self.console.pack(fill=tk.BOTH, expand=True) | |
| self.console.config(state=tk.DISABLED) | |
| # Info box | |
| info_box = tk.Frame(cmd_frame, bg='#050810', padx=15, pady=15, | |
| highlightbackground=self.colors['secondary'], | |
| highlightthickness=2) | |
| info_box.pack(fill=tk.X, pady=(15, 0)) | |
| tk.Label(info_box, text='ℹ️ Windows Setup Instructions', bg='#050810', | |
| fg=self.colors['secondary'], font=self.fonts['title']).pack(anchor='w') | |
| instructions = """1. Install gps-sdr-sim for Windows (requires Visual Studio Build Tools) | |
| 2. Install HackRF Tools for Windows | |
| 3. Ensure HackRF One is connected and drivers are installed (Zadig) | |
| 4. Run the generated command in Command Prompt or PowerShell | |
| 5. ⚠️ Verify you are in a shielded room or have authorization before transmitting!""" | |
| tk.Label(info_box, text=instructions, bg='#050810', | |
| fg=self.colors['text_muted'], font=self.fonts['small'], | |
| justify=tk.LEFT, wraplength=700).pack(anchor='w', pady=(10, 0)) | |
| return frame | |
| def create_satellites(self): | |
| """Create satellite objects on canvas""" | |
| cx, cy = 400, 125 | |
| sat_count = 8 | |
| for i in range(sat_count): | |
| angle = (i / sat_count) * 2 * math.pi | |
| radius = 60 + (i % 3) * 35 | |
| # Calculate position | |
| x = cx + radius * math.cos(angle) | |
| y = cy + radius * math.sin(angle) | |
| # Create satellite | |
| sat = self.sat_canvas.create_oval(x-6, y-6, x+6, y+6, | |
| fill=self.colors['primary'], | |
| outline='', tags=f'sat_{i}') | |
| # Label | |
| label = self.sat_canvas.create_text(x, y-15, text=f'G{i+1}', | |
| fill=self.colors['text_muted'], | |
| font=self.fonts['small'], tags=f'label_{i}') | |
| self.satellites.append({ | |
| 'id': sat, | |
| 'label': label, | |
| 'base_angle': angle, | |
| 'radius': radius, | |
| 'speed': 0.02 + random.random() * 0.02 | |
| }) | |
| def start_satellite_animation(self): | |
| """Start animating satellites""" | |
| self.animating = True | |
| self.animate_satellites() | |
| def animate_satellites(self): | |
| """Animate satellite orbits""" | |
| if not self.animating: | |
| return | |
| cx, cy = 400, 125 | |
| for sat in self.satellites: | |
| # Update angle | |
| sat['base_angle'] += sat['speed'] | |
| # Calculate new position | |
| x = cx + sat['radius'] * math.cos(sat['base_angle']) | |
| y = cy + sat['radius'] * math.sin(sat['base_angle']) | |
| # Update satellite position | |
| self.sat_canvas.coords(sat['id'], x-6, y-6, x+6, y+6) | |
| self.sat_canvas.coords(sat['label'], x, y-15) | |
| self.root.after(50, self.animate_satellites) | |
| def log(self, message, msg_type='info'): | |
| """Log message to console""" | |
| self.console.config(state=tk.NORMAL) | |
| timestamp = datetime.now().strftime('%H:%M:%S') | |
| # Color coding | |
| color = self.colors['primary'] | |
| if msg_type == 'error': | |
| color = self.colors['danger'] | |
| elif msg_type == 'success': | |
| color = '#00ff00' | |
| # Insert with tag | |
| tag_name = f'color_{msg_type}' | |
| self.console.tag_config(tag_name, foreground=color) | |
| self.console.insert(tk.END, f'[{timestamp}] ', 'muted') | |
| self.console.insert(tk.END, f'{message}\n', tag_name) | |
| self.console.tag_config('muted', foreground=self.colors['secondary']) | |
| self.console.see(tk.END) | |
| self.console.config(state=tk.DISABLED) | |
| def after_init(self): | |
| """Initialize after UI is ready""" | |
| self.root.after(500, lambda: [ | |
| self.log('GPS SDR Interface initialized'), | |
| self.log('HackRF One detected on USB (simulated)'), | |
| self.log('Waiting for user input...') | |
| ]) | |
| def generate_command(self): | |
| """Generate the gps-sdr-sim command""" | |
| lat = self.lat_var.get() | |
| lon = self.lon_var.get() | |
| mode = self.mode_var.get() | |
| duration = self.duration_var.get() | |
| sample_rate = self.sample_rate_var.get() | |
| power = self.power_var.get() | |
| time_str = self.gps_time_var.get() | |
| # Parse time | |
| try: | |
| dt = datetime.strptime(time_str, '%Y-%m-%d %H:%M') | |
| date_str = dt.strftime('%Y%m%d') | |
| time_formatted = dt.strftime('%H%M%S') | |
| except ValueError: | |
| self.log('Error: Invalid date/time format', 'error') | |
| return | |
| # Build command | |
| command = 'gps-sdr-sim.exe' | |
| command += f' -e brdc3540.14n' | |
| command += f' -l {lat:.4f},{lon:.4f},{self.alt_var.get():.1f}' | |
| command += f' -d {duration}' | |
| command += f' -s {sample_rate}' | |
| if mode == 'dynamic': | |
| command += f' -t {date_str},{time_formatted}' | |
| command += f' -v {self.speed_var.get():.1f},{self.direction_var.get():.1f}' | |
| elif mode == 'circle': | |
| command += f' -c {self.radius_var.get():.1f}' | |
| # Gain adjustment | |
| command += f' -g {power + 100}' | |
| # Output file | |
| command += ' -o gpssim.bin' | |
| # HackRF command | |
| hackrf_cmd = f'\n\n:: Then transmit with HackRF:\nhackrf_transfer.exe -t gpssim.bin -f 1575420000 -s {int(float(sample_rate) * 1000000)} -a 1 -x 40' | |
| full_command = command + hackrf_cmd | |
| # Update display | |
| self.cmd_text.config(state=tk.NORMAL) | |
| self.cmd_text.delete('1.0', tk.END) | |
| self.cmd_text.insert('1.0', full_command) | |
| self.cmd_text.config(state=tk.DISABLED) | |
| # Log | |
| self.log('Command generated successfully', 'success') | |
| self.log(f'Coordinates: {lat:.4f}, {lon:.4f}') | |
| self.log(f'Mode: {mode}, Duration: {duration}s') | |
| self.log('Ready for execution on Windows', 'success') | |
| def copy_command(self): | |
| """Copy command to clipboard""" | |
| self.cmd_text.config(state=tk.NORMAL) | |
| text = self.cmd_text.get('1.0', tk.END).strip() | |
| self.cmd_text.config(state=tk.DISABLED) | |
| self.root.clipboard_clear() | |
| self.root.clipboard_append(text) | |
| self.log('Command copied to clipboard', 'success') | |
| def main(): | |
| root = tk.Tk() | |
| app = GPSSDRApp(root) | |
| root.mainloop() | |
| if __name__ == '__main__': | |
| main() |