SWLScheduleTool/
├── pyproject.toml # Build config, entry points, dependencies
├── CLAUDE.md # Claude Code instructions
├── README.md # Project overview
├── USER-GUIDE.md # End-user documentation
├── DEV-GUIDE.md # This file
├── packaging/
│ ├── swl-sched.desktop # Desktop entry for app menu
│ └── archlinux/PKGBUILD # Arch Linux package build
└── src/eibi_swl/ # Python package (src-layout)
├── __init__.py # __version__ = "1.0.2"
├── _paths.py # XDG path resolution (dev vs installed)
├── swl.py # Interactive TUI dashboard (Textual)
├── checksked.py # CLI frequency query tool (Rich)
├── updatesked.py # Schedule downloader & converter
├── swlconfig.conf.sample# Sample config for distribution
├── countrycode.dat # ITU country codes
├── targetcode # Target area code definitions
├── transmittersite # Transmitter site locations (text)
└── swl-schedules-data/ # Schedule data directory
├── sked-current.csv # Active schedule (semicolon CSV)
├── freq-current.dat # Frequency-sorted list
├── bc-current.dat # Time-sorted list
├── README-current.TXT # EiBi documentation
├── transmitter-sites.json # Extracted sites with coordinates
└── sked-{a|b}##.csv# Archived seasonal files
The main application built with Textual.
Theme & Styling:
- Tokyo Night theme (
theme = "tokyo-night") with black backgrounds - Starship-style powerline input prompts using Nerd Font glyphs
- Color palette from user's Starship config:
#769ff0,#a3aed2,#394260; all backgrounds black - ON AIR rows: bold green; inactive rows:
#aaaaaa(light grey); zoom rows: bold#769ff0(blue) - Detail modal: round
#769ff0border,#a9b1d6text
Key Classes:
SWLApp(App)— Main application; handles compose, search, update, tick, radio tuning, azmap-gtk IPCDetailScreen(ModalScreen)— Station detail popupLogEntryScreen(ModalScreen)— SWL log entry form (listener, station, freq, mode, BW, SINPO, remarks)QTHScreen(ModalScreen)— QTH location selector
Data Loading (on startup):
load_config()— QTH fromswlconfig.confviaconfigparserload_sites()— Transmitter sites fromtransmitter-sites.jsonload_schedule()— Schedule rows fromsked-current.csv(UTF-8, semicolon-delimited)load_country_names()— ITU codes fromcountrycode.datload_target_names()— Target areas fromtargetcodewith compound code expansionload_language_names()— Language codes parsed fromREADME-current.TXTSection I
Core Functions:
compute_on_air(time_range, current_time)— Returns(duration, is_active, status_str). Active →◄ ON AIR HHhMM, inactive →→ NEXT HHhMM, unparseable →"—"resolve_site_info(row, sites_index)— Resolves transmitter site by(country, site_code)with fallback to default sitehaversine(lat1, lon1, lat2, lon2)— Great-circle distance in kmbearing(lat1, lon1, lat2, lon2)— Initial bearing in degreescompass_label(deg)— 8-point compass label (N, NE, E, etc.)
Event Flow:
on_input_submitted— Dispatches to_do_search()or_run_update()based on input ID_do_search()— Filters schedule by frequency, computes ON AIR/NEXT status, populates DataTable via_build_result()and_populate_table()helpers_run_update()— Validates period (^[ab]\d{2}$), runsupdatesked.pyas subprocess in worker thread_apply_reload(sites, schedule)— Thread-safe callback to update data on main thread after successful downloadon_data_table_row_selected— OpensDetailScreenwith resolved station detailsaction_zoom()— Finds nearest on-air stations below/above selected frequency, inserts them into displayed_rows as blue-highlighted zoom rows via_rebuild_table_rows()action_tune()— Tunes radio via CAT server, sends station to SWLDemodTool FIFO, updates azmap-gtk if runningaction_show_map()— Sends target to azmap-gtk via FIFO at$XDG_RUNTIME_DIR/azmap-target.fifo, or launches new instanceaction_log()— OpensLogEntryScreento log station to~/Documents/swl-log.csv_tick()— Updates UTC clock every second
Standalone Rich-based CLI tool. Reads the same sked-current.csv and displays a formatted table for a given frequency. Active broadcasts shown in bold green with ◄ ON AIR indicator.
Downloads EiBi schedule files from http://eibispace.de/dx via plain HTTP:
sked-{period}.csv→sked-current.csvfreq-{period}.txt→freq-current.datbc-{period}.txt→bc-current.datREADME.TXT→README-current.TXT
Converts all files from ISO-8859-1 to UTF-8. Extracts transmitter sites from README Section IV into transmitter-sites.json.
Site Extraction:
parse_dms_coord()— Converts DMS coordinates (e.g.34N32,26S07'40") to decimal degreesextract_transmitter_sites()— Parses country codes, site codes, names, coordinates_parse_site_entry()/_parse_multi_site()— Handle single and multi-site entries
sked-current.csv — semicolon-delimited, 11 columns:
| # | Column | Example |
|---|---|---|
| 0 | kHz | 6070 |
| 1 | Time(UTC) | 0000-0400 |
| 2 | Days | Sa,Su |
| 3 | ITU | CAN |
| 4 | Station | CFRX |
| 5 | Lng | E |
| 6 | Target | NAm |
| 7 | Remarks/Site | k |
| 8 | P | 50 |
| 9 | Start | 0901 |
| 10 | Stop | 1004 |
All times are UTC, stored as HHMM integers (e.g. 1430 = 14:30 UTC).
Midnight-crossing broadcasts: When end_time < start_time, duration adds 2400. Active check: current >= start OR current < end.
NEXT calculation: For inactive stations, compute minutes from current time to start time. If start is earlier than current, add 24 hours (next day).
The site_code field in the CSV can be:
- A simple code (e.g.
k) — lookup as(country, "k") - A
/COUNTRYreference (e.g./USA) — lookup as("USA", "") - A
/COUNTRY-codereference (e.g./USA-g) — lookup as("USA", "g")
Fallback: if exact (country, site_code) not found, try (country, "").
Target codes can be:
- Direct (e.g.
FE,Eu) — simple lookup - Compound (e.g.
CAf= Central Africa,NAm= North America) — prefix expansion using_prefixes+_regionsdicts - Country codes — fallback to
countrycode.dat
Standard library: argparse, os, sys, csv, json, re, configparser, socket, subprocess, math, datetime, urllib.request, urllib.error, ssl, shutil
External packages:
rich— Terminal formatting (checksked.py, swl.py)textual— TUI framework (swl.py)
Optional: cowsay, lolcat — Completion message in updatesked.py
GPLv3