├── .DS_Store ├── .gitignore ├── .idea ├── .gitignore ├── inspectionProfiles │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── vcs.xml └── wsprdaemon.iml ├── .readthedocs.yaml ├── .show-wav.awk ├── .vimrc ├── Detailed Raspbian Buster wsprdaemon install instructions.txt ├── README.md ├── atsc.sh ├── bash-aliases ├── best_spots.awk ├── bin ├── jt9.patched.aarch64 ├── jt9.patched.armv71 ├── jt9.patched.ubuntu.22.x86 ├── wsprd and jt9 patches.txt ├── wsprd.aarch64 ├── wsprd.spread.nodrift.aarch64 ├── wsprd.spread.nodrift.arm7l ├── wsprd.spread.ubuntu.22.x86 └── wsprd.ubuntu.22.x86 ├── c2_noise.py ├── config_utils.sh ├── decoding.sh ├── derived_calc.py ├── derived_calc_2.py ├── docs ├── .DS_Store ├── Makefile ├── build │ ├── doctrees │ │ ├── FAQ.doctree │ │ ├── configuration │ │ │ ├── ka9q-web.doctree │ │ │ ├── radiod@.conf │ │ │ │ ├── channels.doctree │ │ │ │ ├── global.doctree │ │ │ │ ├── hardware.doctree │ │ │ │ └── radiod@rx888-wsprdaemon.conf.doctree │ │ │ ├── radiod_conf.doctree │ │ │ ├── wd_conf.doctree │ │ │ └── wsprdaemon.conf.d │ │ │ │ ├── computer.doctree │ │ │ │ ├── ka9q-radio.doctree │ │ │ │ ├── receivers.doctree │ │ │ │ ├── reporting.doctree │ │ │ │ ├── schedule.doctree │ │ │ │ └── wsprdaemon.conf.doctree │ │ ├── contributors.doctree │ │ ├── description │ │ │ ├── collaborations.doctree │ │ │ ├── history.doctree │ │ │ ├── how_it_works.doctree │ │ │ └── validity.doctree │ │ ├── environment.pickle │ │ ├── external_links.doctree │ │ ├── index.doctree │ │ ├── installation │ │ │ ├── git.doctree │ │ │ └── preparation.doctree │ │ ├── maintenance │ │ │ ├── aliases.doctree │ │ │ ├── monitoring.doctree │ │ │ └── operating.doctree │ │ ├── network │ │ │ ├── basics.doctree │ │ │ └── multicast.doctree │ │ ├── requirements │ │ │ ├── network.doctree │ │ │ ├── os.doctree │ │ │ └── radios.doctree │ │ ├── results │ │ │ ├── grape.doctree │ │ │ ├── psk.doctree │ │ │ └── wspr.doctree │ │ └── troubleshooting │ │ │ ├── overview.doctree │ │ │ └── typicals.doctree │ └── html │ │ ├── .buildinfo │ │ ├── FAQ.html │ │ ├── _images │ │ ├── IGMP_switch.png │ │ ├── WD-data-architecture.png │ │ ├── btop.png │ │ ├── pskreporter.png │ │ ├── psws.png │ │ ├── wd-file-structure.png │ │ ├── wd-functions.png │ │ └── wwv_obs.png │ │ ├── _sources │ │ ├── FAQ.md.txt │ │ ├── configuration │ │ │ ├── ka9q-web.md.txt │ │ │ ├── radiod@.conf │ │ │ │ ├── channels.md.txt │ │ │ │ ├── global.md.txt │ │ │ │ ├── hardware.md.txt │ │ │ │ └── radiod@rx888-wsprdaemon.conf.md.txt │ │ │ ├── radiod_conf.md.txt │ │ │ ├── wd_conf.md.txt │ │ │ └── wsprdaemon.conf.d │ │ │ │ ├── computer.md.txt │ │ │ │ ├── ka9q-radio.md.txt │ │ │ │ ├── receivers.md.txt │ │ │ │ ├── reporting.md.txt │ │ │ │ ├── schedule.md.txt │ │ │ │ └── wsprdaemon.conf.md.txt │ │ ├── contributors.md.txt │ │ ├── description │ │ │ ├── collaborations.md.txt │ │ │ ├── history.md.txt │ │ │ ├── how_it_works.md.txt │ │ │ └── validity.md.txt │ │ ├── external_links.md.txt │ │ ├── index.md.txt │ │ ├── installation │ │ │ ├── git.md.txt │ │ │ └── preparation.md.txt │ │ ├── maintenance │ │ │ ├── aliases.md.txt │ │ │ ├── monitoring.md.txt │ │ │ └── operating.md.txt │ │ ├── network │ │ │ ├── basics.md.txt │ │ │ └── multicast.md.txt │ │ ├── requirements │ │ │ ├── network.md.txt │ │ │ ├── os.md.txt │ │ │ └── radios.md.txt │ │ ├── results │ │ │ ├── grape.md.txt │ │ │ ├── psk.md.txt │ │ │ └── wspr.md.txt │ │ └── troubleshooting │ │ │ ├── overview.md.txt │ │ │ └── typicals.md.txt │ │ ├── _static │ │ ├── _sphinx_javascript_frameworks_compat.js │ │ ├── basic.css │ │ ├── css │ │ │ ├── badge_only.css │ │ │ ├── fonts │ │ │ │ ├── Roboto-Slab-Bold.woff │ │ │ │ ├── Roboto-Slab-Bold.woff2 │ │ │ │ ├── Roboto-Slab-Regular.woff │ │ │ │ ├── Roboto-Slab-Regular.woff2 │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ ├── fontawesome-webfont.woff2 │ │ │ │ ├── lato-bold-italic.woff │ │ │ │ ├── lato-bold-italic.woff2 │ │ │ │ ├── lato-bold.woff │ │ │ │ ├── lato-bold.woff2 │ │ │ │ ├── lato-normal-italic.woff │ │ │ │ ├── lato-normal-italic.woff2 │ │ │ │ ├── lato-normal.woff │ │ │ │ └── lato-normal.woff2 │ │ │ └── theme.css │ │ ├── doctools.js │ │ ├── documentation_options.js │ │ ├── file.png │ │ ├── fonts │ │ │ ├── Lato │ │ │ │ ├── lato-bold.eot │ │ │ │ ├── lato-bold.ttf │ │ │ │ ├── lato-bold.woff │ │ │ │ ├── lato-bold.woff2 │ │ │ │ ├── lato-bolditalic.eot │ │ │ │ ├── lato-bolditalic.ttf │ │ │ │ ├── lato-bolditalic.woff │ │ │ │ ├── lato-bolditalic.woff2 │ │ │ │ ├── lato-italic.eot │ │ │ │ ├── lato-italic.ttf │ │ │ │ ├── lato-italic.woff │ │ │ │ ├── lato-italic.woff2 │ │ │ │ ├── lato-regular.eot │ │ │ │ ├── lato-regular.ttf │ │ │ │ ├── lato-regular.woff │ │ │ │ └── lato-regular.woff2 │ │ │ └── RobotoSlab │ │ │ │ ├── roboto-slab-v7-bold.eot │ │ │ │ ├── roboto-slab-v7-bold.ttf │ │ │ │ ├── roboto-slab-v7-bold.woff │ │ │ │ ├── roboto-slab-v7-bold.woff2 │ │ │ │ ├── roboto-slab-v7-regular.eot │ │ │ │ ├── roboto-slab-v7-regular.ttf │ │ │ │ ├── roboto-slab-v7-regular.woff │ │ │ │ └── roboto-slab-v7-regular.woff2 │ │ ├── jquery.js │ │ ├── js │ │ │ ├── badge_only.js │ │ │ ├── theme.js │ │ │ └── versions.js │ │ ├── language_data.js │ │ ├── minus.png │ │ ├── mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ └── sphinx_highlight.js │ │ ├── configuration │ │ ├── ka9q-web.html │ │ ├── radiod@.conf │ │ │ ├── channels.html │ │ │ ├── global.html │ │ │ ├── hardware.html │ │ │ └── radiod@rx888-wsprdaemon.conf.html │ │ ├── radiod_conf.html │ │ ├── wd_conf.html │ │ └── wsprdaemon.conf.d │ │ │ ├── computer.html │ │ │ ├── ka9q-radio.html │ │ │ ├── receivers.html │ │ │ ├── reporting.html │ │ │ ├── schedule.html │ │ │ └── wsprdaemon.conf.html │ │ ├── contributors.html │ │ ├── description │ │ ├── collaborations.html │ │ ├── history.html │ │ ├── how_it_works.html │ │ └── validity.html │ │ ├── external_links.html │ │ ├── genindex.html │ │ ├── index.html │ │ ├── installation │ │ ├── git.html │ │ └── preparation.html │ │ ├── maintenance │ │ ├── aliases.html │ │ ├── monitoring.html │ │ └── operating.html │ │ ├── network │ │ ├── basics.html │ │ └── multicast.html │ │ ├── objects.inv │ │ ├── requirements │ │ ├── network.html │ │ ├── os.html │ │ └── radios.html │ │ ├── results │ │ ├── grape.html │ │ ├── psk.html │ │ └── wspr.html │ │ ├── search.html │ │ ├── searchindex.js │ │ └── troubleshooting │ │ ├── overview.html │ │ └── typicals.html ├── make.bat ├── requirements.txt └── source │ ├── .DS_Store │ ├── FAQ.md │ ├── _images │ ├── .DS_Store │ ├── IGMP_switch.png │ ├── WD-data-architecture.png │ ├── btop.png │ ├── pskreporter.png │ ├── psws.png │ ├── wd-file-structure.png │ ├── wd-functions.png │ └── wwv_obs.png │ ├── conf.py │ ├── configuration │ ├── ka9q-web.md │ ├── radiod@.conf │ │ ├── channels.md │ │ ├── global.md │ │ ├── hardware.md │ │ └── radiod@rx888-wsprdaemon.conf.md │ ├── radiod_conf.md │ ├── wd_conf.md │ └── wsprdaemon.conf.d │ │ ├── computer.md │ │ ├── ka9q-radio.md │ │ ├── receivers.md │ │ ├── reporting.md │ │ ├── schedule.md │ │ └── wsprdaemon.conf.md │ ├── contributors.md │ ├── description │ ├── collaborations.md │ ├── history.md │ ├── how_it_works.md │ └── validity.md │ ├── external_links.md │ ├── index.md │ ├── installation │ ├── git.md │ └── preparation.md │ ├── maintenance │ ├── aliases.md │ ├── monitoring.md │ └── operating.md │ ├── network │ ├── basics.md │ └── multicast.md │ ├── requirements │ ├── network.md │ ├── os.md │ └── radios.md │ ├── results │ ├── grape.md │ ├── psk.md │ └── wspr.md │ └── troubleshooting │ ├── overview.md │ └── typicals.md ├── get-peak-wav-sample.py ├── grape-utils.sh ├── job_management.sh ├── ka9q-ft-cleanup.sh ├── ka9q-utils.sh ├── kiwi-utils.sh ├── kiwi-watchdog.sh ├── noise_graphing.sh ├── noise_graphs_daemon.sh ├── noise_graphs_reporter_index_template.html ├── noise_graphs_root_index_template.html ├── noise_plot.py ├── one-minute-silent-float.wv ├── pcmcat1.c ├── pcmrecord.c ├── posting.sh ├── ppm.sh ├── proxy_utils.sh ├── radiod@rx888-wsprdaemon-template.conf ├── recording.sh ├── remote_access_service.sh ├── rx888-record.sh ├── show-memory-usage.sh ├── silent_iq.flac ├── suntimes.py ├── ts_batch_upload.py ├── ts_insert_wd_noise.sql ├── ts_insert_wd_spots.sql ├── ts_insert_wn_spots.sql ├── ts_noise.awk ├── upload_client_utils.sh ├── upload_server_utils.sh ├── usage.sh ├── watchdog.sh ├── wav-archive.sh ├── wav2grape.conf ├── wav2grape.py ├── wav2grape.sh ├── wav_window.py ├── wd-compare-server-spot-counts.sh ├── wd-grape-statistics.sh ├── wd-record.c ├── wd-validate-wav-logs.py ├── wd_remote_access_daemon.sh ├── wd_setup.sh ├── wd_spots_to_ts.awk ├── wd_template.conf ├── wd_utils.sh ├── wd_version.txt ├── wn_from_wd_spot_file.awk ├── wspr.rotate ├── wsprdaemon.sh ├── wsprnet-scraper.awk ├── wsprnet-scraper.sh ├── wsprnet_azi_calc.py └── wwv_start.py /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | *.deb 3 | save 4 | uploads.d 5 | expected.jobs 6 | hashtable.d 7 | hhmm.sched 8 | kiwiclient 9 | noise_plot 10 | running.jobs 11 | signal_levels 12 | suntimes 13 | wsprdaemon.log 14 | wsprdaemon.pid 15 | wsprdaemon.conf 16 | wsprdaemon.status 17 | proxy.pid 18 | noise_plot.log 19 | watchdog_daemon.log 20 | watchdog_daemon.pid 21 | wav-archive.d 22 | suntimes.txt 23 | 24 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/wsprdaemon.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.8" 7 | 8 | sphinx: 9 | configuration: docs/source/conf.py 10 | 11 | python: 12 | install: 13 | - requirements: docs/requirements.txt 14 | 15 | -------------------------------------------------------------------------------- /.show-wav.awk: -------------------------------------------------------------------------------- 1 | #!/bin/awk 2 | 3 | function abs(x){ 4 | return ((x < 0.0) ? -x : x) 5 | } 6 | 7 | BEGIN { 8 | error_level = 0.90 9 | rec_length_secs_max=120.10 10 | rec_length_secs_min=119.90 11 | } 12 | 13 | /Bit-depth/{ 14 | split(FILENAME, path, "/") 15 | rx_name = path[5] 16 | rx_band = path[6] 17 | split($0, line, "UTC: ") 18 | date = line[1] 19 | sox_stats = line[2] 20 | split(sox_stats, sox_values) 21 | min_val = sox_values[15] 22 | gsub( "min=", "", min_val ) 23 | gsub( ",", "", min_val ) 24 | max_val = sox_values[16] 25 | gsub( "max=", "", max_val ) 26 | gsub( ",", "", max_val ) 27 | rec_length = sox_values[6] 28 | rec_length_secs = rec_length 29 | gsub(/.*=/, "", rec_length_secs) 30 | bit_depth = sox_values[10] 31 | gsub( ":", "", bit_depth) 32 | min_string = sox_values[15] 33 | gsub( ",", "", min_string) 34 | max_string = sox_values[16] 35 | found_overload = "OV=0" 36 | if (abs(min_val) > error_level) found_overload = "OV=1" 37 | if ( max_val > error_level) found_overload = "OV=1" 38 | length_error = "LEN=0" 39 | if ( (rec_length_secs > rec_length_secs_max) || (rec_length_secs < rec_length_secs_min) ) length_error = "LEN=1" 40 | printf "%12s %4sM: %sUTC: %s %-15s %s %s %s %s\n", rx_name, rx_band, date, rec_length, bit_depth, min_string, max_string, found_overload, length_error 41 | } 42 | -------------------------------------------------------------------------------- /.vimrc: -------------------------------------------------------------------------------- 1 | colorscheme industry 2 | if has ("autocmd") 3 | filetype plugin indent on 4 | endif 5 | 6 | set expandtab 7 | set shiftwidth=4 8 | set softtabstop=4 9 | 10 | set autoindent 11 | set smartindent 12 | set cindent 13 | 14 | set nowrap 15 | 16 | set term=xterm-256color 17 | filetype plugin indent on 18 | syntax on 19 | 20 | if exists('$TERM_PROGRAM') && $TERM_PROGRAM == 'iTerm2' 21 | " Change cursor to a vertical bar in Insert mode 22 | let &t_SI = "\e[6 q" 23 | " Change cursor to a block in Normal mode 24 | let &t_EI = "\e[2 q" 25 | endif 26 | if exists('$TMUX') 27 | " tmux-specific cursor changes for Insert/Normal mode 28 | " Insert mode: vertical bar cursor 29 | let &t_SI = "\e[6 q" 30 | " Normal mode: block cursor 31 | let &t_EI = "\e[2 q" 32 | endif 33 | " F2: Horizontal split 34 | nnoremap :split 35 | 36 | " F3: Vertical split 37 | nnoremap :vsplit 38 | 39 | " F4: Increase current split size 40 | nnoremap :resize +5 41 | nnoremap :vertical resize +5 " Shift+F4 for vertical resizing 42 | 43 | " F5: Move between splits 44 | nnoremap w 45 | 46 | -------------------------------------------------------------------------------- /Detailed Raspbian Buster wsprdaemon install instructions.txt: -------------------------------------------------------------------------------- 1 | Detailed instructions for a greenfield install for the Raspberry Pi 4 2 | (because I always forget) 3 | Optional steps included : xrdp and cockpit 4 | 5 | https://www.raspberrypi.com/software/operating-systems/ 6 | 7 | Raspberry Pi OS (Legacy) 8 | A stable legacy version of Raspberry Pi OS Buster. 9 | 10 | Compatible with: 11 | 12 | All Raspberry Pi models 13 | Raspberry Pi OS (Legacy) with desktop 14 | Release date: September 22nd 2022 15 | System: 32-bit 16 | Kernel version: 5.10 17 | Debian version: 10 (buster) 18 | Size: 757MB 19 | 20 | Raspberry Pi Imager : choose file from disk 21 | 22 | Put a new file with name ssh in root folder of SD card so we can later ssh into the Raspberry Pi 23 | 24 | eject card reader / disk system 25 | 26 | Connect ethernet via cable, insert SD card, sudo power on Raspberry Pi 27 | 28 | PuTTy ip-address (on Windows when needed <> arp -a) 29 | 30 | sudo raspi-config 31 | 1 S5 B4 32 | 3 P2 P3 33 | 5 L4 FR 34 | reboot 35 | 36 | ssh OK 37 | vnc via VNC Viewer can not show desktop 38 | RDP NOK 39 | 40 | sudo apt update 41 | sudo apt upgrade 42 | sudo apt install cockpit 43 | in browser 44 | Enable stored metrics install cockpit-pcp 45 | sudo apt install xrdp 46 | RDP OK 47 | 48 | https://github.com/rrobinett/wsprdaemon/blob/master/README.md 49 | 50 | cd ~ 51 | git clone https://github.com/rrobinett/wsprdaemon.git 52 | cd wsprdaemon 53 | ./wsprdaemon.sh -V 54 | 55 | I copied a previous wsprdaemon.conf; else follow the instructions in the README 56 | I use WinSCP for this kind of filetransfers 57 | I use Notepad++ for editing as it can be switched from the Windows CR/LF to Linux 58 | 59 | ./wsprdaemon.sh -V (should be -a but this works as well) 60 | Requesting permission after about 1 1/2 minute of working 61 | after 4 minutes the numpy install takes the most time (7 minutes) 62 | Version = 3.0.6 63 | 64 | source ~/wsprdaemon/.wd_bash_aliases 65 | wd-rci 66 | wd-help 67 | 68 | And we are on after : 69 | wda 70 | wds 71 | 72 | My first spot : 230217 1008 80.00 -7 0.14 14.0970103 G3ZIL IO90 33 0 3 73 | Amazing 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WsprDaemon (WD) 3.3.2 is the current master version 2 | 3 | Wsprdaemon (WD) runs as a Linux service to decode WSPR and FST4W spots from one or more [Kiwis](http://kiwisdr.com) and/or RX888 SDRs and *reliably* post them to [wsprnet.org](http://wsprnet.org). It includes many features not found in WSJT-x, including multiple band and/or multiple receiver support. WD also records additional information about spots like doppler shift and background noise level which permit much deeper understanding of propagation conditions. For systems like the KiwiSDR which have a limited number of receive channels, schedules can be configured to switch between bands at different hours of the day or at sunrise/sunset-relative times. It can "merge" spots obtained from multiple receivers on the same band (e.g a 40M vertical and 500' Beverage) and report only the one with the best SNR to [wsprnet.org](http://wsprnet.org). WD can create graphs of the background noise level for display locally and/or at [graphs.wsprdaemon.org](http://graphs.wsprdaemon.org) or [wspr.live](http://wspr.live/gui/). 4 | 5 | After configuration, WD runs like a home appliance: it recovers on its own from power and Internet outages and caches all spots and other data it gathers until it confirms delivered to wsprnet.org and/or wsprdaemon.net. Most of the 20+ 'top spotting' sites at http://wspr.rocks/topspotters/ run WD, and in aggregate they report about 33% of the 7+M spots recorded each day at wsprnet.org. 6 | 7 | For further documentation on prerequisites, installation, configuration, and running WD, please refer to the [documentation](https://wsprdaemon.readthedocs.io/en/latest/). -------------------------------------------------------------------------------- /best_spots.awk: -------------------------------------------------------------------------------- 1 | #!/bin/awk 2 | # 11/19/23 RR 3 | # This code reads an ALL_WSPR.TXT format file and outputs spot lines which contain only one instance of each call sign 4 | # while choosing the best spots with spreading infomation in the last column 5 | # spots which are reported only with no spreading information are reported with spreding of 9.999 hz 6 | # Since the WSJT-x WSPR-2 decoder 'wsprd' includes drift compensation, it may report some spots which are not reported 7 | # by Ryan's enhaned wsprd decoder which has drift compensation disabled so that the spreading values it reports are meaningful 8 | # 9 | { 10 | spot_line = $0 11 | if ( $NF ~ /^[0-9\-]+$/ ) { 12 | ### The last column is an integer, so this spot line comes from the WSJT-x wsprd 13 | spot_line = spot_line " 9.999" 14 | this_spot_has_spreding_infomation = 0 15 | } else { 16 | this_spot_has_spreding_infomation = 1 17 | } 18 | spot_lines_with_this_call[$6]++ 19 | if ( spot_lines_with_this_call[$6] == 1 ) { 20 | spot_lines_for_calls[$6] = spot_line 21 | spot_line_snr[$6] = $3 22 | spot_line_has_spread[$6] = this_spot_has_spreding_infomation 23 | } else { 24 | if ( ( this_spot_has_spreding_infomation == spot_line_has_spread[$6] && $3 > spot_line_snr[$6] ) \ 25 | || ( this_spot_has_spreding_infomation == 1 && spot_line_has_spread[$6] == 0 ) ) { 26 | # If there are two spots both with the sane speading information from the same call, then choose the one with better SNR 27 | # And always choose spots with spreading over those without spreading 28 | spot_lines_for_calls[$6] = spot_line 29 | spot_line_snr[$6] = $3 30 | spot_line_has_spread[$6] = this_spot_has_spreding_infomation 31 | } 32 | } 33 | } 34 | 35 | ### Pipe the output through 'sort -k 5,5n' to get a nicely ascending frequency output file 36 | END { 37 | for (spot_call in spot_lines_for_calls) 38 | printf "%s\n", spot_lines_for_calls[spot_call] 39 | } 40 | -------------------------------------------------------------------------------- /bin/jt9.patched.aarch64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/bin/jt9.patched.aarch64 -------------------------------------------------------------------------------- /bin/jt9.patched.armv71: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/bin/jt9.patched.armv71 -------------------------------------------------------------------------------- /bin/jt9.patched.ubuntu.22.x86: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/bin/jt9.patched.ubuntu.22.x86 -------------------------------------------------------------------------------- /bin/wsprd.aarch64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/bin/wsprd.aarch64 -------------------------------------------------------------------------------- /bin/wsprd.spread.nodrift.aarch64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/bin/wsprd.spread.nodrift.aarch64 -------------------------------------------------------------------------------- /bin/wsprd.spread.nodrift.arm7l: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/bin/wsprd.spread.nodrift.arm7l -------------------------------------------------------------------------------- /bin/wsprd.spread.ubuntu.22.x86: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/bin/wsprd.spread.ubuntu.22.x86 -------------------------------------------------------------------------------- /bin/wsprd.ubuntu.22.x86: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/bin/wsprd.ubuntu.22.x86 -------------------------------------------------------------------------------- /c2_noise.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Filename: c2_noise.py 4 | # Program to extract the noise level from the 'wsprd -c' C2 format file 5 | ## V1 by Christoph Mayer. This version V1.1 by Gwyn Griffiths to output a single value 6 | ## being the total power (dB arbitary scale) in the lowest 30% of the Fourier coefficients 7 | ## between 1369.5 and 1630.5 Hz where the passband is flat. 8 | 9 | import struct 10 | import sys 11 | import numpy as np 12 | 13 | fn = sys.argv[1] ## '000000_0001.c2' 14 | 15 | with open(fn, 'rb') as fp: 16 | ## decode the header: 17 | filename,wspr_type,wspr_freq = struct.unpack('<14sid', fp.read(14+4+8)) 18 | 19 | ## extract I/Q samples 20 | samples = np.fromfile(fp, dtype=np.float32) 21 | z = samples[0::2]+1j*samples[1::2] 22 | #print(filename,wspr_type,wspr_freq,samples[:100], len(samples), z[:10]) 23 | 24 | ## z contains 45000 I/Q samples 25 | ## we perform 180 FFTs, each 250 samples long 26 | a = z.reshape(180,250) 27 | a *= np.hanning(250) 28 | freqs = np.arange(-125,125, dtype=np.float32)/250*375 ## was just np.abs, square to get power 29 | w = np.square(np.abs(np.fft.fftshift(np.fft.fft(a, axis=1), axes=1))) 30 | ## these expressions first trim the frequency range to 1369.5 to 1630.5 Hz to ensure 31 | ## a flat passband without bias from the shoulders of the bandpass filter 32 | ## i.e. array indices 38:213 33 | w_bandpass=w[0:179,38:213] 34 | ## partitioning is done on the flattened array of coefficients 35 | w_flat_sorted=np.partition(w_bandpass, 9345, axis=None) 36 | noise_level_flat=10*np.log10(np.sum(w_flat_sorted[0:9344])) 37 | print(' %6.2f' % (noise_level_flat)) 38 | -------------------------------------------------------------------------------- /docs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/.DS_Store -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/build/doctrees/FAQ.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/FAQ.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/configuration/ka9q-web.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/configuration/ka9q-web.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/configuration/radiod@.conf/channels.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/configuration/radiod@.conf/channels.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/configuration/radiod@.conf/global.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/configuration/radiod@.conf/global.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/configuration/radiod@.conf/hardware.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/configuration/radiod@.conf/hardware.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/configuration/radiod@.conf/radiod@rx888-wsprdaemon.conf.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/configuration/radiod@.conf/radiod@rx888-wsprdaemon.conf.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/configuration/radiod_conf.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/configuration/radiod_conf.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/configuration/wd_conf.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/configuration/wd_conf.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/configuration/wsprdaemon.conf.d/computer.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/configuration/wsprdaemon.conf.d/computer.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/configuration/wsprdaemon.conf.d/ka9q-radio.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/configuration/wsprdaemon.conf.d/ka9q-radio.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/configuration/wsprdaemon.conf.d/receivers.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/configuration/wsprdaemon.conf.d/receivers.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/configuration/wsprdaemon.conf.d/reporting.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/configuration/wsprdaemon.conf.d/reporting.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/configuration/wsprdaemon.conf.d/schedule.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/configuration/wsprdaemon.conf.d/schedule.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/configuration/wsprdaemon.conf.d/wsprdaemon.conf.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/configuration/wsprdaemon.conf.d/wsprdaemon.conf.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/contributors.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/contributors.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/description/collaborations.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/description/collaborations.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/description/history.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/description/history.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/description/how_it_works.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/description/how_it_works.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/description/validity.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/description/validity.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/environment.pickle -------------------------------------------------------------------------------- /docs/build/doctrees/external_links.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/external_links.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/index.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/installation/git.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/installation/git.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/installation/preparation.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/installation/preparation.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/maintenance/aliases.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/maintenance/aliases.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/maintenance/monitoring.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/maintenance/monitoring.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/maintenance/operating.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/maintenance/operating.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/network/basics.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/network/basics.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/network/multicast.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/network/multicast.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/requirements/network.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/requirements/network.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/requirements/os.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/requirements/os.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/requirements/radios.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/requirements/radios.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/results/grape.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/results/grape.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/results/psk.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/results/psk.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/results/wspr.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/results/wspr.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/troubleshooting/overview.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/troubleshooting/overview.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/troubleshooting/typicals.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/doctrees/troubleshooting/typicals.doctree -------------------------------------------------------------------------------- /docs/build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file records the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 4e694c985699d56c256011ccbac630e6 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/build/html/_images/IGMP_switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_images/IGMP_switch.png -------------------------------------------------------------------------------- /docs/build/html/_images/WD-data-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_images/WD-data-architecture.png -------------------------------------------------------------------------------- /docs/build/html/_images/btop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_images/btop.png -------------------------------------------------------------------------------- /docs/build/html/_images/pskreporter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_images/pskreporter.png -------------------------------------------------------------------------------- /docs/build/html/_images/psws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_images/psws.png -------------------------------------------------------------------------------- /docs/build/html/_images/wd-file-structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_images/wd-file-structure.png -------------------------------------------------------------------------------- /docs/build/html/_images/wd-functions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_images/wd-functions.png -------------------------------------------------------------------------------- /docs/build/html/_images/wwv_obs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_images/wwv_obs.png -------------------------------------------------------------------------------- /docs/build/html/_sources/FAQ.md.txt: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/build/html/_sources/configuration/ka9q-web.md.txt: -------------------------------------------------------------------------------- 1 | # ka9q-web 2 | 3 | This software builds in reference to ka9q-radio and requires no specific configuration. 4 | WD typically starts it automatically. You invoke it manually, if necessary, thus: 5 | ``` 6 | ka9q-web -m your-radiod-status-stream.local -p 8081 -n "callsign grid antenna" & 7 | ``` 8 | where "your-radiod-status-stream" is the name specified in the [GLOBAL] of your radiod@.conf (often hf.local or hf-status.local) 9 | 10 | ## Viewing the ka9q-web spectrum display 11 | 12 | Logged in an running everything locally, direct your browser to http://localhost:8081. 13 | 14 | If managing the computer remotely using ssh, you can set up an ssh tunnel from the remote computer to your local computer like this: 15 | 16 | From your local machine, run: 17 | ``` 18 | ssh -L 8081:localhost:8081 wsprdaemon@aa.bb.cc.dd 19 | ``` 20 | where aa.bb.cc.dd is the ip address or name of the remote computer running ka9q-web. (Substitute another username if not running WD eponymously.) 21 | 22 | Then direct your browser to http://localhost:8081 to view ka9q-web served from the aa.bb.cc.dd remote computer. 23 | If you happen to be using port 8081 on your local computer for another purpose, simply replace the port number after -L in the command above to an unused port YYYY. Then direct your browser to http://localhost:YYYY. 24 | 25 | The port will disappear when you close the ssh session. 26 | 27 | **John Melton G0ORX** started this with a proof-of-concept version in late 2023. This adjunct to ka9q-radio displays a spectrum, waterfall, and other data from radiod. **Scott Newell N5TNL** has since improved it dramatically in collaboration with **Rob Robinett AI6VN**, **Phil Karn KA9Q**, **Glenn Elmore N6GN**, **Jim Lill WA2ZKD**, and desultory kibbitzers. 28 | 29 | - Web Server by John Melton, G0ORX (https://github.com/g0orx/ka9q-radio) 30 | - ka9q-radio by Phil Karn, KA9Q (https://github.com/ka9q/ka9q-radio) 31 | - Onion Web Framework by David Moreno (https://github.com/davidmoreno/onion) 32 | - Spectrum/Waterfall Display by Jeppe Ledet-Pedersen (https://github.com/jledet/waterfall) 33 | -------------------------------------------------------------------------------- /docs/build/html/_sources/configuration/radiod@.conf/radiod@rx888-wsprdaemon.conf.md.txt: -------------------------------------------------------------------------------- 1 | # Example radiod@rx888-wsprdaemon.conf 2 | 3 | ## minimalist setup on a single computer for wspr, wwv, ft4 and ft8. 4 | 5 | The following directs radiod to use a RX888 to present simultaneous multicast streams of 16 wspr channels, streams of 7 wwv and 3 chu channels, and streams of 9 ft4 and 11 ft8 channels. 6 | 7 | You will find more detailed descriptions of these sections in: 8 | - [global](./global.md) 9 | - [hardware](./hardware.md) 10 | - [channels](./channels.md) 11 | 12 | --- 13 | 14 | ``` 15 | [global] 16 | hardware = rx888 17 | status = bee1-hf-status.local 18 | samprate = 12000 19 | mode = usb 20 | ttl = 0 21 | fft-threads = 0 22 | 23 | [rx888] 24 | device = "rx888" 25 | description = "AC0G @EM38ww dipole" # good to put callsign, gridsquare, and antenna description in here 26 | samprate = 64800000 # or 129600000 27 | 28 | [WSPR] 29 | encoding = float 30 | disable = no 31 | data = bee1-wspr-pcm.local 32 | agc=0 33 | gain=0 34 | samprate = 12000 35 | mode = usb 36 | low=1300 37 | high=1700 38 | freq = "136k000 474k200 1m836600 3m568600 3m592600 5m287200 5m364700 7m038600 10m138700 13m553900 14m095600 18m104600 21m094600 24m924600 28m124600 50m293000"" 39 | 40 | [WWV-IQ] 41 | disable = no 42 | encoding=float 43 | data = bee1-wwv-iq.local 44 | agc=0 45 | gain=0 46 | samprate = 16k 47 | mode = iq 48 | freq = "60000 2m500000 5m000000 10m000000 15m000000 20m000000 25m000000 3m330000 7m850000 14m670000" ### Added the three CHU frequencies 49 | 50 | [FT8] 51 | disable = no 52 | data = ft8-pcm.local 53 | mode = usb 54 | freq = "1m840000 3m573000 5m357000 7m074000 10m136000 14m074000 18m100000 21m074000 24m915000 28m074000 50m313000" 55 | 56 | [FT4] 57 | disable = no 58 | data = ft4-pcm.local 59 | mode = usb 60 | freq = "3m575000 7m047500 10m140000 14m080000 18m104000 21m140000 24m919000 28m180000 50m318000" 61 | ``` 62 | 63 | 64 | -------------------------------------------------------------------------------- /docs/build/html/_sources/configuration/radiod_conf.md.txt: -------------------------------------------------------------------------------- 1 | # Configuring ka9q-radio: radiod@.conf 2 | 3 | Likewise, the radiod@.conf, located in /etc/radio/, has lots of options. 4 | 5 | However, it boils down to setting up the following: 6 | - [global settings](./radiod@.conf/global.md) 7 | - [hardware settings](./radiod@.conf/hardware.md) 8 | - [channel settings](./radiod@.conf/channels.md) 9 | 10 | ## [Example radiod@rx888-wsprdaemon.conf](./radiod@.conf/radiod@rx888-wsprdaemon.conf.md) 11 | -------------------------------------------------------------------------------- /docs/build/html/_sources/configuration/wd_conf.md.txt: -------------------------------------------------------------------------------- 1 | # Configuring wsprdaemon: wsprdaemon.conf 2 | 3 | The template for a wsprdaemon.conf file, located in /home/wsprdaemon/wsprdaemon/, includes copious information and looks dauntingly complicated. 4 | 5 | However, it boils down to setting up the following: 6 | 1. [computer-related parameters](wsprdaemon.conf.d/computer.md) 7 | 2. [ka9q-radio parameters](wsprdaemon.conf.d/ka9q-radio.md) 8 | 3. [reporting parameters](wsprdaemon.conf.d/reporting.md) 9 | 4. [receiver definitions](wsprdaemon.conf.d/receivers.md) 10 | 5. [schedule definitions](wsprdaemon.conf.d/schedule.md) 11 | 12 | ## [Example wsprdaemon.conf](wsprdaemon.conf.d/wsprdaemon.conf.md) -------------------------------------------------------------------------------- /docs/build/html/_sources/configuration/wsprdaemon.conf.d/computer.md.txt: -------------------------------------------------------------------------------- 1 | 2 | # Computer Issues 3 | 4 | ## CPUs 5 | 6 | ### Ryzen 5560, 5700, 5800, 5825, and above 7 | Known to work running both WD and radiod. 8 | 9 | ### Intel 10 | 11 | ### Raspberry Pi (4 or 5) 12 | Can work in constrained use -- but not supporting the full bandwidth of a RX888 plus WD. 13 | Known to work with RTL-SDR, funcube dongle, AirspyR2, etc. 14 | 15 | ### Orange Pi-5 16 | Known to work running both WD and radiod if configured correctly. 17 | 18 | ## Memory 19 | 20 | ## Disk storage 21 | 22 | -------------------------------------------------------------------------------- /docs/build/html/_sources/configuration/wsprdaemon.conf.d/ka9q-radio.md.txt: -------------------------------------------------------------------------------- 1 | # Configuration with ka9q-radio and ka9q-web 2 | 3 | ## Running radiod locally or remotely 4 | 5 | ## Dependence on ka9q-radio processes 6 | 7 | ## radiod configuration file 8 | 9 | ## ka9q-web title 10 | 11 | ## code commits 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/build/html/_sources/configuration/wsprdaemon.conf.d/receivers.md.txt: -------------------------------------------------------------------------------- 1 | 2 | # Receiver definitions 3 | 4 | ## KiwiSDR 5 | 6 | ## KA9Q 7 | 8 | ``` 9 | ############################################################## 10 | ### The RECEIVER_LIST() array defines the physical (KIWI_xxx or KA9Q...) and logical (MERG...) receive devices available on this server 11 | ### Each element of RECEIVER_LIST is a string with 5 space-separated fields: 12 | ### " ID(no spaces) IP:PORT or RTL:n MyCall MyGrid KiwPassword Optional SIGNAL_LEVEL_ADJUSTMENTS 13 | ### [[DEFAULT:ADJUST,]BAND_0:ADJUST[,BAND_N:ADJUST_N]...] 14 | ### A comma-separated list of BAND:ADJUST pairs 15 | ### BAND is one of 2200..10, while ADJUST is in dBs TO BE ADDED to the raw data 16 | ### So If you have a +10 dB LNA, ADJUST '-10' will LOWER the reported level so that your reports reflect the level at the input of the LNA 17 | ### DEFAULT defaults to zero and is applied to all bands not specified with a BAND:ADJUST 18 | 19 | declare RECEIVER_LIST=( 20 | "KA9Q_0 wspr-pcm.local OE3GBB/Q JN87aq NULL" ### A receiver name which starts with 'KA9Q_...' will decode wav files supplied by the KA9Q-radio multicast RTP streams 21 | ### In WD 3.1.0 WD assumes all WSPR audio streams come from a local instance of KA9Q 22 | ### which by default outputs all the WSPR audio stream on the multicast DNS address wspr-pcm.local 23 | "KA9Q_1 wspr1-pcm.local AI6VN CM88mc NULL" ### Multicast streams from remote KA9Q receivers can be sources, and not just RX-888s 24 | "KA9Q_0_WSPR_IQ wspr-iq.local AI6VN CM88mc NULL" ### Multicast IQ streams from the local RX888 + KA9Q receiver 25 | "KA9Q_0_WWV_IQ wwv-iq.local AI6VN CM88mc NULL" ### Those streams are not enabled by default in the radiod.conf file. So if you configue an IQ rx job, 26 | ### you will need to set 'disabled = no' for one or both in radiod@rx888-wsprdaemon.conf and then restart radiod 27 | 28 | "KIWI_0 10.11.12.100:8073 AI6VN CM88mc NULL" 29 | "KIWI_1 10.11.12.101:8073 AI6VN CM88mc foobar DEFAULT:-10,80:-12,30:-8,20:2,15:6" ### You can optionally adjust noise levels for the antenna factor 30 | "KIWI_2 10.11.12.102:8073 AI6VN CM88mc foobar" 31 | 32 | "MERG_K01_Q01 KIWI_0,KIWI_1,KA9Q_0,KA9Q_1 AI6VN CM88mc foobar" ### For a receiver with a name starting with "MERG", the IP field is a list of two or more 'real' receivers a defined above. For a logical MERG receiver 33 | ) 34 | 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/build/html/_sources/configuration/wsprdaemon.conf.d/reporting.md.txt: -------------------------------------------------------------------------------- 1 | 2 | # Reporting 3 | 4 | ## WSPR Reporting 5 | 6 | ``` 7 | ################### The following variables are used in normally running installations ################### 8 | SIGNAL_LEVEL_UPLOAD="noise" ### Whether and how to upload extended spots to wsprdaemon.org. WD always attempts to upload spots to wsprnet.org 9 | ### SIGNAL_LEVEL_UPLOAD="no" => (Default) Only upload spots directly to wsprnet.org 10 | ### SIGNAL_LEVEL_UPLOAD_MODE="noise" => In addition, upload extended spots and noise data to wsprdaemon.org 11 | ### SIGNAL_LEVEL_UPLOAD_MODE="proxy" => Don't directly upload spots to wsprdaemon.org. Instead, after uploading extended spots and noise data to wsprdaemon.org have it regenerate and upload those spots to wsp 12 | ### This mode minimizes the use of Internet bandwidth, but makes getting spots to wsprnet.org dependent upon the wsprdameon.org services. 13 | 14 | # If SIGNAL_LEVEL_UPLOAD in NOT "no", then you must modify SIGNAL_LEVEL_UPLOAD_ID from "AI6VN" to your call sign. SIGNAL_LEVEL_UPLOAD_ID cannot include '/ 15 | SIGNAL_LEVEL_UPLOAD_ID="OE3GBB_Q" ### The name put in upload log records, the title bar of the graph, and the name used to view spots and noise at that server. 16 | # SIGNAL_LEVEL_UPLOAD_GRAPHS="yes" ### If this variable is defined as "yes" AND SIGNAL_LEVEL_UPLOAD_ID is defined, then FTP graphs of the last 24 hours to http://wsprdaemon.org/graphs/SIGNAL_LEVEL_UPLOAD_ID 17 | # SIGNAL_LEVEL_LOCAL_GRAPHS="yes" ### If this variable is defined as "yes" AND SIGNAL_LEVEL_UPLOAD_ID is defined, then make graphs visible at http://localhost/ 18 | # 19 | ### Graphs default to y-axis minimum of -175 dB to maximum of -105 dB. X pixels default to 40, Y pixels default to 30. If the graph of your system isn't pleasing, you can change the graph's appearance by 20 | ### uncommenting one or more of these variables and changing their values 21 | # NOISE_GRAPHS_Y_MIN=-175 22 | # NOISE_GRAPHS_Y_MAX=-105 23 | # NOISE_GRAPHS_X_PIXEL=40 24 | # NOISE_GRAPHS_Y_PIXEL=30 25 | ``` 26 | 27 | ## HamSCI -- Grape Reporting 28 | 29 | WD includes configurable support for the [HamSCI GRAPE WWV Doppler shift project](https://hamsci.org/grape). From local or remote RX888 receivers, WD can record a continuous series of one minute long 16000 sps flac-compressed IQ wav files. Soon after 00:00 UTC, WD creates a single 3.8 MB 24hour-10hz-iq.wav file which is uploaded to WD's WD1 server at grape.wsprdaemon.org. WD software on WD1 then converts the one or more time station band recordings into [Digital RF (DRF)](https://github.com/MITHaystack/digital_rf) file format and uploads those DRF files to the HamSCI server. 30 | 31 | You must create an account at https://pswsnetwork.caps.ua.edu/. This account enables you to manage your site(s) and instrument(s). 32 | 33 | After establishing your account, you create a "site" having a SITE_ID and a TOKEN for each WD instance contributing to the GRAPE project. The SITE_ID takes the form "S000NNN". You typically name the site with your callsign and a useful discriminator if you have more than one site, for instance, "AC0G_B1", "AC0G_B2". For each site, you add an "instrument" of particular type, for instance, "magnetometer" or "rx888", with an INSTRUMENT_ID. You can create multiple sites but each site has only one instrument. The SITE_ID and TOKEN function as username and password for uploading data. The SITE_ID and INSTRUMENT_ID function to identify the data in DRF. 34 | 35 | On your WD server, you then 36 | - [configure KA9Q-radio](../radiod@.conf/channels.md) to output WWV-IQ channels. 37 | - [configure WD receivers](./receivers.md) listen to those channels. 38 | - [configure a WD schedule](./schedule.md) for listening on each channel. 39 | - [configure WD reporting](./reporting.md) with GRAPE_PSWS_ID="_" 40 | 41 | Finally, enable automatic uploads of HamSCI data. For example, with SITE_ID="S000987" run: 42 | ``` 43 | ssh-copy-id S000987@pswsnetwork.caps.ua.edu 44 | ``` 45 | The site will respond by asking for a password. Enter the TOKEN for that site and you should get a message of success. The most common cause of failure at this point is errant copy and paste with a character missing or an added space at the beginning or end of the token string. 46 | 47 | You can check if auto login works by executing the 'wdssp' alias. 48 | 49 | Source : https://groups.io/g/wsprdaemon/message/3319 Rob Robinett Source : https://github.com/rrobinett/wsprdaemon/blob/master/wd_template.conf Source : https://groups.io/g/wsprdaemon/message/3301 Rob Robinett 50 | 51 | ## PSKReporter 52 | 53 | -------------------------------------------------------------------------------- /docs/build/html/_sources/configuration/wsprdaemon.conf.d/wsprdaemon.conf.md.txt: -------------------------------------------------------------------------------- 1 | # Example of a working wsprdaemon.conf 2 | 3 | Minimalist configuration for a stand-alone machine that listens to wspr and WWV/CHU streams from radiod (ka9q-radio), then processes and uploads the results to wsprnet, wsprdaemon.org, pskreporter, and pswsnetwork.caps.ua.edu. 4 | 5 | You will find further details on these parameters and definitions in 6 | - [Computer-related parameters](./computer.md) 7 | - [ka9q-radio/web parameters](./ka9q-radio.md) 8 | - [reporting parameters](./reporting.md) 9 | - [receiver definitions](./receivers.md) 10 | - [schedule definitions](./schedule.md) 11 | 12 | ``` 13 | # 1. Computer-related parameters: 14 | # RAC setup enables WD supporters to access your machine remotely. Get a RAC # from Rob Robinett. 15 | # WD will run without this 16 | REMOTE_ACCESS_CHANNEL=27 17 | REMOTE_ACCESS_ID="AC0G-BEE1" 18 | 19 | # CPU/CORE TUNING if neccessary 20 | # the following will restrict wd processes to particular cores if necessary (e.g., Ryzen 7 - 5700 series) 21 | # WD will run without this 22 | WD_CPU_CORES="2-15" 23 | RADIOD_CPU_CORES="0-1" 24 | 25 | # 2. ka9q-radio/web parameters 26 | KA9Q_RADIO_COMMIT="main" 27 | KA9Q_RUNS_ONLY_REMOTELY="no" 28 | KA9Q_CONF_NAME="ac0g-bee1-rx888" 29 | KA9Q_WEB_COMMIT_CHECK="main" 30 | # If you don't set the title here, it will default to the description in the radiod@config file 31 | KA9Q_WEB_TITLE="AC0G_@EM38ww_Longwire" 32 | 33 | # 3. Reporting parameters 34 | # for contributing to HamSCI monitoring of WWV/CHU 35 | GRAPE_PSWS_ID="S000171_172" 36 | # for reporting to wsprdaemon.org 37 | SIGNAL_LEVEL_UPLOAD="noise" 38 | SIGNAL_LEVEL_UPLOAD_ID="AC0G_BEE1" 39 | SIGNAL_LEVEL_UPLOAD_GRAPHS="yes" 40 | 41 | # 4. Receiver definitions -- REQUIRED 42 | # two radiod receivers -- one for wspr and one for wwv 43 | declare RECEIVER_LIST=( 44 | "KA9Q_0_WSPR wspr-pcm.local AI6VN CM88mc NULL" 45 | "KA9Q_0_WWV_IQ wwv-iq.local AI6VN CM88mc NULL" 46 | ) 47 | 48 | # 5. Schedule definitions -- REQUIRED 49 | # SCHEDULE 50 | declare WSPR_SCHEDULE=( 51 | "00:00 KA9Q_0_WSPR,2200,W2:F2:F5:F15:F30 KA9Q_0_WSPR,630,W2:F2:F5 KA9Q_0_WSPR,160,W2:F2:F5 52 | KA9Q_0_WSPR,80,W2:F2:F5 KA9Q_0_WSPR,80eu,W2:F2:F5 KA9Q_0_WSPR,60,W2:F2:F5 53 | KA9Q_0_WSPR,60eu,W2:F2:F5 KA9Q_0_WSPR,40,W2:F2:F5 KA9Q_0_WSPR,30,W2:F2:F5 54 | KA9Q_0_WSPR,22,W2 KA9Q_0_WSPR,20,W2:F2:F5 KA9Q_0_WSPR,17,W2:F2:F5 55 | KA9Q_0_WSPR,15,W2:F2:F5 KA9Q_0_WSPR,12,W2:F2:F5 KA9Q_0_WSPR,10,W2:F2:F5 56 | 57 | KA9Q_0_WWV_IQ,WWV_2_5,I1 KA9Q_0_WWV_IQ,WWV_5,I1 KA9Q_0_WWV_IQ,WWV_10,I1 58 | KA9Q_0_WWV_IQ,WWV_15,I1 KA9Q_0_WWV_IQ,WWV_20,I1 KA9Q_0_WWV_IQ,WWV_25,I1 59 | KA9Q_0_WWV_IQ,CHU_3,I1 KA9Q_0_WWV_IQ,CHU_7,I1 KA9Q_0_WWV_IQ,CHU_14,I1" 60 | ) 61 | ``` -------------------------------------------------------------------------------- /docs/build/html/_sources/contributors.md.txt: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | ## Creator and Mastermind: 4 | 5 | - Rob Robinett AI6VN 6 | 7 | ## Others lending a hand: 8 | 9 | - Phil Karn KA9Q 10 | - Gwyn Griffiths 11 | - Christoph Mayer 12 | - Scott Newell N5TNL 13 | - Franco Venturi K4VZ 14 | - Philip Barnard 15 | - Michael Hauan AC0G 16 | -------------------------------------------------------------------------------- /docs/build/html/_sources/description/collaborations.md.txt: -------------------------------------------------------------------------------- 1 | # Collaborations 2 | 3 | ## wsprnet.org 4 | 5 | ## HamSCI 6 | 7 | ## SuperDARN 8 | 9 | ## WSPR.rocks 10 | 11 | ## WSPR.live 12 | 13 | ## PSKReporter 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/build/html/_sources/description/history.md.txt: -------------------------------------------------------------------------------- 1 | # History 2 | 3 | WD 4 | -------------------------------------------------------------------------------- /docs/build/html/_sources/description/how_it_works.md.txt: -------------------------------------------------------------------------------- 1 | # How wsprdaemon Works 2 | 3 | Wsprdaemon (WD) runs as a Linux service to decode WSPR and FST4W spots from one or more Kiwis and/or RX888 SDRs and reliably posts them to wsprnet.org. It includes many features not found in WSJT-x, including multiple band and/or multiple receiver support. WD also records additional information about spots like doppler shift and background noise level which permit much deeper understanding of propagation conditions. For systems like the KiwiSDR which have a limited number of receive channels, schedules can be configured to switch between bands at different hours of the day or at sunrise/sunset-relative times. Spots obtained from multiple receivers on the same band ( e.g a 40M vertical and 500' Beverage ) can be merged together with only the best SNR posted to wsprnet.org. WD can be configured to create graphs of the background noise level for display locally and/or at graphs.wsprdaemon.org. 4 | 5 | After configuration, WD runs like a home appliance: it recovers on its own from power and internet outages and caches all spots and other data it gathers until wsprnet.org and/or wsprdaemon.net confirm delivery. Most of the 20+ 'top spotting' sites at http://wspr.rocks/topspotters/ are running WD, and in aggregate they report about 33% of the 7+M spots recorded each day at wsprnet.org. 6 | 7 | WD runs on almost any Debian Linux system running Ubuntu 22.04 LTS on x86. Although WD on a Pi 4 can decode 10+ bands, most sites run WD on a x86 CPU. 8 | 9 | ## Basic components 10 | 11 | - receiver integration (KiwiSDR, ka9q-radio) 12 | - wspr decoding and reporting to wsprnet 13 | - grape recording, conversion to digital_rf, and reporting to HamSCI 14 | - pskreporter 15 | 16 | ![](../_images/wd-file-structure.png) 17 | 18 | ## A running instance of wsprdaemon performs several tasks: 19 | 20 | - configuration and installation checks 21 | - preparation of recording and posting directories 22 | - "listening" functions for defined KIWI and KA9Q receivers 23 | - decoding and posting wspr and fst4w spots 24 | - recording 16 kHz I/Q around WWV and CHU broadcasts for upload to HamSCI 25 | - monitoring and logging results and errors 26 | 27 | ![](../_images/wd-functions.png) 28 | 29 | ## What will make it not start or then stop working? 30 | 31 | - data stream not defined -- wd requires at least one receiver (KIWI or KA9Q) 32 | - schedule not defined -- wd requires a schedule definition 33 | - wd requires a working hardware radio (KiwiSDR or RX888) with proper configuration 34 | - [See Troubleshooting](../troubleshooting/overview.md) 35 | 36 | ## What happens to the data? 37 | 38 | WD sends the collected data to several upstream servers (depending on configuration): 39 | - 1. wsprdaemon.org (wspr spots and other information) 40 | - 2. wspr.net (wspr spots only) 41 | - 3. pskreporter.info (FT4 and FT8 spots) 42 | - 4. pswsnetwork.caps.ua.edu (WWV/H and CHU monitoring) 43 | 44 | A diagram of the WSPR reports: 45 | 46 | ![](../_images/WD-data-architecture.png) -------------------------------------------------------------------------------- /docs/build/html/_sources/description/validity.md.txt: -------------------------------------------------------------------------------- 1 | # Validity of data path and results 2 | 3 | describe the data path and validity checks 4 | -------------------------------------------------------------------------------- /docs/build/html/_sources/external_links.md.txt: -------------------------------------------------------------------------------- 1 | # External Links 2 | 3 | ## [Technical Descriptions](https://wsprdaemon.org/technical.html) 4 | ## [wsprdaemon](https://github.com/rrobinett/wsprdaemon.git) 5 | ## [ka9q-radio](https://ka9q-radio.org) 6 | ## [Personal Space Weather Station Data](https://pswsnetwork.caps.ua.edu/home) 7 | ## [HamSCI](https://hamsci.org/grape) 8 | ## [WSPR Rocks!](https://wspr.rocks) 9 | ## [WSPR.net](https://wsprnet.org) 10 | ## [PSKReporter](https://pskreporter.info) 11 | ## [SuperDARN](https://superdarn.ca/) -------------------------------------------------------------------------------- /docs/build/html/_sources/index.md.txt: -------------------------------------------------------------------------------- 1 | 2 | # WSPRDAEMON Documentation 3 | 4 | ```{toctree} 5 | :maxdepth: 2 6 | :caption: Description 7 | description/how_it_works.md 8 | description/validity.md 9 | ``` 10 | 11 | ```{toctree} 12 | :maxdepth: 2 13 | :caption: Requirements 14 | 15 | requirements/os.md 16 | requirements/radios.md 17 | requirements/network.md 18 | ``` 19 | 20 | ```{toctree} 21 | :maxdepth: 2 22 | :caption: Software Installation 23 | 24 | installation/preparation.md 25 | installation/git.md 26 | ``` 27 | 28 | ```{toctree} 29 | :maxdepth: 1 30 | :caption: Software Configuration 31 | 32 | configuration/wd_conf.md 33 | configuration/radiod_conf.md 34 | configuration/ka9q-web.md 35 | ``` 36 | 37 | ```{toctree} 38 | :maxdepth: 2 39 | :caption: Working Results 40 | 41 | results/wspr.md 42 | results/grape.md 43 | results/psk.md 44 | ``` 45 | 46 | ```{toctree} 47 | :maxdepth: 2 48 | :caption: Maintenance & Updates 49 | 50 | maintenance/operating.md 51 | maintenance/monitoring.md 52 | maintenance/aliases.md 53 | ``` 54 | 55 | ```{toctree} 56 | :maxdepth: 2 57 | :caption: Troubleshooting 58 | 59 | troubleshooting/overview.md 60 | troubleshooting/typicals.md 61 | ``` 62 | 63 | ```{toctree} 64 | :maxdepth: 1 65 | :caption: FAQ 66 | 67 | FAQ.md 68 | ``` 69 | 70 | ```{toctree} 71 | :maxdepth: 1 72 | :caption: External Links 73 | 74 | external_links.md 75 | ``` 76 | 77 | ```{toctree} 78 | :maxdepth: 1 79 | :caption: Contributors 80 | 81 | contributors.md 82 | ``` 83 | -------------------------------------------------------------------------------- /docs/build/html/_sources/installation/git.md.txt: -------------------------------------------------------------------------------- 1 | # Download the software with GIT 2 | 3 | GitHub hosts the repository for all versions of WD. Presently, the current master provides version 3.2.3. The latest development version, 3.3.1, remains in a branch. 4 | 5 | ## Clone wsprdaemon from github.com 6 | 7 | From /home/wsprdaemon (or the installing user's home directory) [See Preparing the Installation](./preparation.md) 8 | ``` 9 | git clone https://github.com/rrobinett/wsprdaemon.git 10 | cd wsprdaemon 11 | ``` 12 | Execute all further git commands in the /home/wsprdaemon/wsprdaemon directory. 13 | 14 | Ensure you have the latest stable version: 15 | ``` 16 | git checkout master 17 | git status 18 | git log 19 | ``` 20 | 21 | Subsequently, to apply any updates of the latest version, use: 22 | ``` 23 | git pull 24 | ``` 25 | 26 | "master" generally refers to the latest stable version of the code. As development of the code proceeds, 27 | you may elect to switch to a development branch, e.g., 3.3.1. To do this, use: 28 | ``` 29 | git checkout 3.3.1 30 | git pull 31 | ``` 32 | 33 | 34 | WD provides lots of shell "aliases" to important and otherwise useful functions. To have immediate access to these, run: 35 | ``` 36 | source bash-aliases ../.bash_aliases 37 | ``` 38 | 39 | Having prepared and cloned the wsprdaemon software, now you can run it: 40 | ``` 41 | wd 42 | ``` 43 | 44 | This sets the stage and prompts you to configure your setup: 45 | - [wsprdaemon configuration](../configuration/wd_conf.md) 46 | - [radiod configuration](../configuration/radiod_conf.md) 47 | - KiwiSDR 48 | 49 | Once you have defined a new wsprdaemon.conf (or restored your previous one) then invoke a command like: 50 | ``` 51 | wdv 52 | ``` 53 | Nominally, this reports the current version of wsprdaemon but it will use wsprdaemon.conf to proceed with setting up wsprdaemon to run -- downloading and compiling any required software (e.g., ka9q-radio and ka9q-web), setting up the proper directories, etc. 54 | 55 | Finally, you can start wsprdaemon with: 56 | ``` 57 | wd -A 58 | ``` 59 | which will pipe all messages to stdout (the screen from which you invoked the command) which can help if something doesn't function properly. 60 | 61 | Or simply run: 62 | ``` 63 | wda 64 | ``` 65 | which starts wsprdaemon "quietly" piping all messages to log files. 66 | 67 | # To install ka9q-radio independently: 68 | 69 | ka9q-radio has many uses outside its integrated role with WD. You can install and run it without WD. 70 | Keep in mind that Rob checks out a particular version of ka9q-radio that he knows works with WD. 71 | So, if you use another version, you may find its interaction with WD problematic. YMMV. 72 | 73 | For details of ka9q-radio installation, consult the docs sub-directory in the ka9q-radio created after performing a `git clone`. 74 | 75 | - [KA9Q_RADIO_GIT_URL](https://github.com/ka9q/ka9q-radio.git) 76 | - [KA9Q_FT8_GIT_URL](https://github.com/ka9q/ft8_lib.git) 77 | - [PSK_UPLOADER_GIT_URL](https://github.com/pjsg/ftlib-pskreporter.git) 78 | 79 | ## To install ka9q-web: 80 | 81 | ka9q-web requires ka9q-radio of course, but also the web server package, onion, produced by David Moreno, so install it first. 82 | 83 | - [ONION_GIT_URL](https://github.com/davidmoreno/onion) 84 | 85 | Use the following bash scripts as scripts or just as a guide to installation: 86 | 87 | ``` 88 | declare ONION_LIBS_NEEDED="libgnutls28-dev libgcrypt20-dev cmake" 89 | if [[ ${OS_RELEASE} =~ 24.04 ]]; then 90 | ONION_LIBS_NEEDED="${ONION_LIBS_NEEDED} libgnutls30t64 libgcrypt20" 91 | fi 92 | 93 | function build_onion() { 94 | local project_subdir=$1 95 | local project_logfile="${project_subdir}-build.log" 96 | 97 | wd_logger 2 "Building ${project_subdir}" 98 | ( 99 | cd ${project_subdir} 100 | mkdir -p build 101 | cd build 102 | cmake -DONION_USE_PAM=false -DONION_USE_PNG=false -DONION_USE_JPEG=false -DONION_USE_XML2=false -DONION_USE_SYSTEMD=false -DONION_USE_SQLITE3=false -DONION_USE_REDIS=false -DONION_USE_GC=false -DONION_USE_TESTS=false -DONION_EXAMPLES=false -DONION_USE_BINDINGS_CPP=false .. 103 | make 104 | sudo make install 105 | sudo ldconfig 106 | ) >& ${project_logfile} 107 | rc=$? 108 | if [[ ${rc} -ne 0 ]]; then 109 | wd_logger 1 "ERROR: compile of '${project_subdir}' returned ${rc}:\n$( < ${project_logfile} )" 110 | exit 1 111 | fi 112 | wd_logger 2 "Done" 113 | return 0 114 | } 115 | ``` 116 | 117 | - [KA9Q_WEB_GIT_URL](https://github.com/scottnewell/ka9q-web) 118 | 119 | ``` 120 | function build_ka9q_web() { 121 | local project_subdir=$1 122 | local project_logfile="${project_subdir}_build.log" 123 | 124 | wd_logger 2 "Building ${project_subdir}" 125 | ( 126 | cd ${project_subdir} 127 | make 128 | sudo make install 129 | ) >& ${project_logfile} 130 | rc=$? ; if (( rc )); then 131 | wd_logger 1 "ERROR: compile of 'ka9q-web' returned ${rc}:\n$(< ${project_logfile})" 132 | exit 1 133 | fi 134 | wd_logger 2 "Done" 135 | return 0 136 | } 137 | ``` -------------------------------------------------------------------------------- /docs/build/html/_sources/maintenance/monitoring.md.txt: -------------------------------------------------------------------------------- 1 | # Monitoring WD operation 2 | 3 | ## recordings 4 | 5 | ## postings 6 | 7 | ## logs 8 | 9 | ## using BTOP 10 | 11 | ![](../_images/btop.png) 12 | 13 | ## Useful wd aliases 14 | 15 | - *wd-query* -- usage: wd-query {-r | -t} ID [HOURS_TO_SEARCH] -r => search for ID of reporter -t => search for ID of transmit beacon" 16 | - *wd-wsprlog-check* 17 | - watch "ls -lt `find -name pcmrecord-errors.log`" 18 | - *wdrl* -- show the syslog entries for the radiod service. add -f to watch new log lines appear 19 | - *wdln* -- watch the upload to wsprnet log 20 | - *wdle* -- search all log files for ERROR lines -------------------------------------------------------------------------------- /docs/build/html/_sources/maintenance/operating.md.txt: -------------------------------------------------------------------------------- 1 | # Operating WD 2 | 3 | ## Start and Stop 4 | 5 | After installation, there are two different ways to run WD. Each of these is invoked from the command line. 6 | 7 | The first method is the usual one where WD is run as a 'systemctl' service. In this mode it automatically starts when Linux boots or reboots after a power cycle. The command line (aliased) commands associated with this mode are 'wd -a' and 'wd -z'. 'wd -a' starts this mode while 'wd -z' terminates it. However, this method does not post system errors back to the command line. For this reason, when first verifying at startup or for verifying operation after wsprdaemon.conf has been modified, it can be useful to temporarily use the second mode. 8 | 9 | This second mode is invoked with 'wd -A' and terminated with 'wd -Z'. Note the capitalization differences. 'wd -A' invokes WD from the command line rather than automatically from the systemctl environment. This means that it does post errors back to the command line where they can be viewed. This mode is exited by typing 'wd -Z'. 10 | 11 | Once an installation and wsprdaemon.conf changes are verified, 'wd -Z' followed by 'wd -a' will make operation automatic. Any future changes should be made by first stopping this automatic operation and then temporarily using 'wd -A' to re-verify them followed by 'wd -Z' and 'wd -a' once they are acceptable. 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/build/html/_sources/network/basics.md.txt: -------------------------------------------------------------------------------- 1 | # Basic Network Requirements 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/build/html/_sources/network/multicast.md.txt: -------------------------------------------------------------------------------- 1 | # Multicast Requirements 2 | 3 | ![](../_images/IGMP_switch.png) 4 | 5 | -------------------------------------------------------------------------------- /docs/build/html/_sources/requirements/network.md.txt: -------------------------------------------------------------------------------- 1 | # Networking 2 | 3 | WD using ka9q-radio typically runs on a stand-alone computer. In this scenario, a standard ethernet or WiFi connection to the computer will suffice for remote management and for reporting. 4 | 5 | However, ka9q-radio uses RTP (multicast) streams to manage interprocess communications. This means one can run ka9q-radio with a hardware radio on one computer and process the output on another. However, multicast streams produce significant network traffic -- enough to bring a WiFi network to a standstill. 6 | 7 | When running on a stand-alone computer, one should set the parameter ttl = 0 in radiod@.conf. This directs radiod to put no RTP streams on the LAN. When distributing the functions between computers, however, one sets ttl = 1 in radiod@.conf and, especially with a connected WiFi LAN, interposes an IGMP-aware ethernet switch (with IGMP snooping ON) between the computers using RTP and the rest of your network. This will confine the RTP streams to the connections between your radiod and WD computers so they don't flood the rest of your LAN. 8 | 9 | ![](../_images/IGMP_switch.png) -------------------------------------------------------------------------------- /docs/build/html/_sources/requirements/os.md.txt: -------------------------------------------------------------------------------- 1 | # Operating Sytems 2 | 3 | I currently do most of my installation and run-time testing on the recently released Ubuntu 24.04 LTS server OS. Desktop Ubuntu includes a lot of software auto-upgrade features which can disrupt the operation of WD and other applications, so I suggest you avoid using the desktop version. If you do use it, then at least follow [Clint KA7OEI's "de-snap" instructions](http://www.sdrutah.org/info/websdr_Ubuntu_2204_install_notes.html#snapd) 4 | 5 | [Download Ubuntu server](https://ubuntu.com/download/server) and run the 'Raspberry Pi Imager' program on your PC to copy the Ubuntu.iso file to a 8 GB or larger USB thumb drive. 6 | 7 | In Imager select: 8 | 9 | Rasperry Pi Device => "NO FILTERING" Operating System => Use custom => browse to the Ubuntu image file you have downloaded to your PC Storage => the thumb drive 10 | 11 | You will then insert the USB thumb drive into your host and boot from that drive. Frequently you will need to press the DEL key or a function key (e.g. F7) immediately after power-up in order to instruct the BIOS of the server to boot from the thumb drive. 12 | 13 | I suggest that you create a user 'wsprdaemon' with sudo privileges after installation is complete. 14 | 15 | While WD can run on many different x86 and ARM CPU's, the RX888 is best run on a i5-6500T-class or newer x86 server. 16 | 17 | For new installations I have found the Beelink brand SER 5 Ryzen 5800U offers excellent price, performance and low power consumption for a WD system. Today Sept 9, 2024 Amazon offers it for $270 (after $18 discount) at [Amazon](https://www.amazon.com/Beelink-SER5-Computer-Graphics-Support/dp/B0D6G965BC), but the same Beelink may be offered at several different prices on Amazon, so search for price including 'discount coupons'. The Beelink Ser 5 5560U is another excellent choice which until today I was able to purchase for $219. Also consider the Ryzen 5800 series chips, (5800U, 5800H, 5825H) but avoid the 5700 series as these have a divided L3 cache which may introduce gaps in the USB stream as processing switches from one set of cores to another. 18 | 19 | Whatever server you choose, WD runs a little better on 16 GB of ram. 20 | 21 | The Beelink comes with Windows installed but WD runs on Linux, so I usually first install Windows and associate the Beelink with my Microsoft account to be sure the server hardware and software are functional, and in case I want to restore Windows on that server. 22 | 23 | Maximizing CPU performance on the Beelink requires that the 'always slow' default fan setting be changed in the BIOS to 'fan on a 40C, fan max at 60C, speed ramp 8'. The 'btop' program which I run to monitor the CPU usage displays CPU temperature among many other things. If it shows the CPU at much more than 60C during the 100% busy periods which start every even 2 minutes, then your fan is not running fast enough. 24 | 25 | I also find the 'power always on' setting deeply buried in the ACH sub menu. 26 | -------------------------------------------------------------------------------- /docs/build/html/_sources/requirements/radios.md.txt: -------------------------------------------------------------------------------- 1 | # Compatible Radios 2 | 3 | Please note, for the present and in collaboration with HamSCI efforts, WD supports kiwiSDR and RX888. The documentation here also primarily addresses the configuration and use of WD with those two radios. 4 | 5 | ## KiwiSDR 6 | 7 | ## Radios that work with ka9q-radio 8 | 9 | ### RX888 10 | 11 | The RX888 connects to your computer via a USB 3 -- SuperSpeed connection. These typically have a BLUE tab on the computer socket as distinct from the white or black for USB 2. 12 | 13 | As delivered the RX888 has sub-optimal thermal protection and for radio science applications it needs an external GPSDO @ 27 MHz clock (although you can alter this). 14 | 15 | Paul Elliott WB6CXC created a screwdriver-only kit which enhances the thermal protection and adds a ground-isolated external clock SMA input port. Paul describes the installation and use of his kit on [his website](https://turnislandsystems.com/wp-content/uploads/2024/05/RX888-Kit-2.pdf) 16 | 17 | The kit is available at [the TAPR web store](https://tapr.org/product/rx888-clock-kit-and-thermal-pad/). 18 | 19 | ### Airspy variants 20 | 21 | - Airspy R2 22 | - Airspy HF+ 23 | 24 | ### RTL-SDR 25 | 26 | ### SDRPLAY variants 27 | 28 | Not directly suppported by radiod. 29 | - RSPduo 30 | - RSPdx 31 | 32 | ### FobosSDR 33 | 34 | ### Others... 35 | 36 | - Funcube Dongle 37 | - OpenHPSDR variants 38 | -------------------------------------------------------------------------------- /docs/build/html/_sources/results/grape.md.txt: -------------------------------------------------------------------------------- 1 | # HamSCI WWV/H and CHU Monitoring 2 | 3 | WD software captures data in support of the [HamSCI Personal Space Weather Station project](https://hamsci.org/psws-overview). 4 | It makes simultaneous I/Q stream recordings of WWV, WWVH, and CHU broadcasts, converts these into digital_rf data repositories and uploads them to the [PSWS server at the University of Alabama](https://pswsnetwork.caps.ua.edu/). 5 | 6 | ![](../_images/psws.png) 7 | 8 | ![](../_images/wwv_obs.png) 9 | -------------------------------------------------------------------------------- /docs/build/html/_sources/results/psk.md.txt: -------------------------------------------------------------------------------- 1 | # PSKReporter 2 | 3 | Managed by Philip Gladstone, the [website](https://pskreporter.info) aggregates lots of information. A WD/radiod installation uploads spots to this site. 4 | 5 | ![](../_images/pskreporter.png) -------------------------------------------------------------------------------- /docs/build/html/_sources/results/wspr.md.txt: -------------------------------------------------------------------------------- 1 | # WSPR spots 2 | 3 | WSPR, which stands for Weak Signal Propagation Reporter, defines a protocol used in amateur radio for assessing radio signal propagation. It allows users to send and receive very low-power transmissions over long distances, that a receiving station then decodes and reports. Here are some key points about WSPR: 4 | 5 | **Purpose**: WSPR is primarily used for scientific and testing purposes, helping amateur radio operators analyze radio wave propagation conditions. 6 | 7 | **Operation**: WSPR operates on specific frequencies and uses a special encoding method for its transmissions. Unlike traditional radio communication, WSPR does not support two-way conversations; it focuses on sending short, beacon-like signals. 8 | 9 | **Implementation**: The WSPR protocol can be implemented in software, and it is compatible with various hardware setups, making it accessible for many amateur radio operators. 10 | 11 | **Community and Reporting**: WSPR signals are often received by automated software-defined radio (SDR) receivers, which can report back the signal's strength and other details via the Internet, contributing to a global database of propagation conditions. 12 | 13 | For more detailed information, you may refer to the [WSPR Wikipedia page](https://en.wikipedia.org/wiki/WSPR_(amateur_radio_software)). 14 | 15 | ## WSPR.Rocks 16 | 17 | Managed by Philip Barnard VK7JJ. 18 | Presents an interface to a Clickhouse DB courtesy of Arne at wspr.live and hosted at wsprdaemon.org. 19 | 20 | [wspr.rocks](https://wspr.rocks) 21 | 22 | ## WSPR.Live 23 | 24 | WSPR.live allows you to do analysis on the real-time wspr spot data. The database contains all spots ever reported to wsprnet.org and allows public access to the data. 25 | 26 | [wspr.live](https://wspr.live) 27 | 28 | ## WSPRnet 29 | 30 | Amateur radio operators using K1JT's MEPT_JT digital mode collaborate via the Weak Signal Propagation Reporter Network to probe radio frequency propagation conditions using very low power (QRP/QRPp) transmissions. The software is open source, and the data collected are available to the public through [this site](https://wsprnet.org/). 31 | 32 | 33 | -------------------------------------------------------------------------------- /docs/build/html/_sources/troubleshooting/overview.md.txt: -------------------------------------------------------------------------------- 1 | # Troubleshooting wsprdaemon 2 | 3 | Nothing ever goes wrong! 4 | 5 | ## Use tmux or screen 6 | 7 | Especially useful in managing a computer remotely. This will enable you to maintain a session between logins or should your network connection drop. 8 | 9 | ## Use btop! 10 | 11 | ![](../_images/btop.png) 12 | 13 | ## Use wdln 14 | 15 | This displays the wsprnet upload log. 16 | 17 | ## Use wdle 18 | 19 | ## Check recordings 20 | 21 | Change to the temporary directory. 22 | 23 | ``` 24 | cdt 25 | ``` 26 | Then enter the sub-directory for a receiver and channel, for instance, the following moves to receiver KA9Q_0 and a 20m wspr channel: 27 | ``` 28 | cd recording.d/KA9Q_0/20/ 29 | ``` 30 | Here, you can invoke the wdww alias that dynamically lists the wav files for that channel. A problem exists if you see no files or don't see regular incrementing of the latest file size. 31 | 32 | ``` 33 | wdww 34 | ``` 35 | 36 | ## Check the logs 37 | 38 | You can locate logs using something like the following command: 39 | ``` 40 | find . -type f -name "*.log" ! -name "*sox.log" ! -name "*error.log" 41 | ``` 42 | where: 43 | - "." indicates starting the search from the current directory 44 | - "-type" directs the search only for files 45 | - "-name" specifies the filename of interest 46 | - "! -name" specifies names to exclude 47 | 48 | ### logs in /var/log 49 | 50 | - /var/log/wspr.log 51 | - /var/log/ft8.log 52 | - /var/log/ft4.log 53 | 54 | ### logs in ~/wsprdaemon 55 | 56 | - ./wav-archive.d/grape_upload_daemon.log 57 | - ./ka9q-radio_build.log 58 | - ./grep.log 59 | - ./ps.log 60 | - ./diff.log 61 | - ./git.log 62 | - ./uploads.d/wsprdaemon.d/upload_to_wsprdaemon_daemon.log 63 | - ./uploads.d/wsprnet.d/spots.d/upload_to_wsprnet_daemon.log 64 | - ./ft8_lib_build.log 65 | - ./onion-build.log 66 | - ./noise_plot.log 67 | - ./ka9q_web_daemon.log 68 | - ./ka9q-web_build.log 69 | - ./watchdog_daemon.log 70 | - ./ka9q_web_service_8081.log 71 | 72 | ### logs in /dev/shm/wsprdaemon 73 | 74 | Note: the 2nd and 3rd subdirectories will vary according to your receivers and channels. 75 | 76 | - ./recording.d/KA9Q_LONGWIRE/80eu/posting_daemon.log 77 | - ./recording.d/KA9Q_LONGWIRE/80eu/decoding_daemon.log 78 | - ./recording.d/KA9Q_LONGWIRE/80eu/find.log 79 | - ./recording.d/KA9Q_LONGWIRE/80eu/metadump.log 80 | - ./recording.d/KA9Q_LONGWIRE/80eu/ka9q_status.log 81 | - ./recording.d/KA9Q_LONGWIRE/80eu/sox-stats.log 82 | - ./recording.d/KA9Q_LONGWIRE/80eu/adc_overloads.log 83 | - ./recording.d/KA9Q_LONGWIRE/80eu/sox.log 84 | - ./recording.d/KA9Q_LONGWIRE/80eu/get-peak-wav-sample.log 85 | - ./recording.d/KA9Q_LONGWIRE/80eu/W_120/decoding_daemon.log 86 | - ./recording.d/KA9Q_LONGWIRE/80eu/wav_status.log 87 | - ./recording.d/KA9Q_LONGWIRE/80eu/add_derived.log 88 | - ./recording.d/KA9Q_LONGWIRE/80eu/printf.log 89 | 90 | - ./recording.d/KA9Q_LONGWIRE_WWV/wav-record-daemon-all.log 91 | - ./recording.d/KA9Q_LONGWIRE_WWV/pcmrecord-errors.log 92 | - ./recording.d/KA9Q_LONGWIRE_WWV/pcmrecord-outs.log 93 | 94 | - ./uploads.d/wsprdaemon.d/grep.log 95 | - ./uploads.d/wsprnet.d/curl.log 96 | -------------------------------------------------------------------------------- /docs/build/html/_sources/troubleshooting/typicals.md.txt: -------------------------------------------------------------------------------- 1 | # Some typical problems 2 | 3 | ## Consider usb loading issues. 4 | - is the RX888 plugged into a USB3 superspeed socket? 5 | - has radiod successfully loading the firmware to the RX888? 6 | 7 | ## Consider system service issues 8 | - systemctl service enabled? 9 | - configured to always restart on boot? 10 | - does your computer or OS support what you want the RX888 or wsprdaemon to do? 11 | - for Beelink, set the fan to 'auto' with the fan turning on at 40C and getting to max speed at 60C with at ramp of '8' 12 | - have you configured the computer to resume function after loss/restoration of power? 13 | 14 | ## Consider avahi address translation issues 15 | - avahi installed (see pre-requisite libraries) 16 | 17 | ## Consider ethernet hubs/architecture 18 | 19 | - ttl = 0 by default (assuming a stand-alone setup) so as not to flood the LAN or WLAN with multicast packets 20 | - if using multicast between computers, ttl = 1 required on sending computer and on the right NIC 21 | - multicast will require an IGMP-capable switch with snooping ON to isolate the computers using radiod multicast from the rest of your LAN or WLAN, particularly if your LAN uses WiFi. 22 | - have you defined and enabled a device in radiod@rx888-XXX.conf? 23 | 24 | ## Brute force recovery 25 | 26 | One method of recovery involves, in effect, starting from (almost) scratch. 27 | 28 | The important "almost" refers to preserving your configuration. DON'T FORGET THIS! 29 | 30 | First, critically, copy your ~/wsprdaemon/wsprdaemon.conf file to somewhere safe, e.g., the home directory: 31 | ``` 32 | cp ~/wsprdaemon/wsprdaemon.conf ~ 33 | ``` 34 | Then delete the wsprdaemon subdirectory: 35 | ``` 36 | rm -rf ~/wsprdaemon/ 37 | ``` 38 | 39 | Clean out /shm/wsprdaemon: 40 | ``` 41 | rm -rf /shm/wsprdaemon/* 42 | ``` 43 | 44 | Clone the WD repository (while in /home/wsprdaemon/): 45 | ``` 46 | git clone https://github.com/rrobinett/wsprdaemon.git 47 | ``` 48 | 49 | Copy the wsprdaemon.conf back into the new ~/wsprdaemon: 50 | ``` 51 | cp ~/wsprdaemon.conf ~/wsrdaemon 52 | ``` 53 | 54 | Run wd to get everything built and installed: 55 | ``` 56 | wd 57 | ``` 58 | 59 | Restart WD: 60 | ``` 61 | wda 62 | ``` 63 | 64 | Check the usual places (see above) to ensure things are functioning as expected. -------------------------------------------------------------------------------- /docs/build/html/_static/_sphinx_javascript_frameworks_compat.js: -------------------------------------------------------------------------------- 1 | /* Compatability shim for jQuery and underscores.js. 2 | * 3 | * Copyright Sphinx contributors 4 | * Released under the two clause BSD licence 5 | */ 6 | 7 | /** 8 | * small helper function to urldecode strings 9 | * 10 | * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL 11 | */ 12 | jQuery.urldecode = function(x) { 13 | if (!x) { 14 | return x 15 | } 16 | return decodeURIComponent(x.replace(/\+/g, ' ')); 17 | }; 18 | 19 | /** 20 | * small helper function to urlencode strings 21 | */ 22 | jQuery.urlencode = encodeURIComponent; 23 | 24 | /** 25 | * This function returns the parsed url parameters of the 26 | * current request. Multiple values per key are supported, 27 | * it will always return arrays of strings for the value parts. 28 | */ 29 | jQuery.getQueryParameters = function(s) { 30 | if (typeof s === 'undefined') 31 | s = document.location.search; 32 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 33 | var result = {}; 34 | for (var i = 0; i < parts.length; i++) { 35 | var tmp = parts[i].split('=', 2); 36 | var key = jQuery.urldecode(tmp[0]); 37 | var value = jQuery.urldecode(tmp[1]); 38 | if (key in result) 39 | result[key].push(value); 40 | else 41 | result[key] = [value]; 42 | } 43 | return result; 44 | }; 45 | 46 | /** 47 | * highlight a given string on a jquery object by wrapping it in 48 | * span elements with the given class name. 49 | */ 50 | jQuery.fn.highlightText = function(text, className) { 51 | function highlight(node, addItems) { 52 | if (node.nodeType === 3) { 53 | var val = node.nodeValue; 54 | var pos = val.toLowerCase().indexOf(text); 55 | if (pos >= 0 && 56 | !jQuery(node.parentNode).hasClass(className) && 57 | !jQuery(node.parentNode).hasClass("nohighlight")) { 58 | var span; 59 | var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); 60 | if (isInSVG) { 61 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 62 | } else { 63 | span = document.createElement("span"); 64 | span.className = className; 65 | } 66 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 67 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 68 | document.createTextNode(val.substr(pos + text.length)), 69 | node.nextSibling)); 70 | node.nodeValue = val.substr(0, pos); 71 | if (isInSVG) { 72 | var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); 73 | var bbox = node.parentElement.getBBox(); 74 | rect.x.baseVal.value = bbox.x; 75 | rect.y.baseVal.value = bbox.y; 76 | rect.width.baseVal.value = bbox.width; 77 | rect.height.baseVal.value = bbox.height; 78 | rect.setAttribute('class', className); 79 | addItems.push({ 80 | "parent": node.parentNode, 81 | "target": rect}); 82 | } 83 | } 84 | } 85 | else if (!jQuery(node).is("button, select, textarea")) { 86 | jQuery.each(node.childNodes, function() { 87 | highlight(this, addItems); 88 | }); 89 | } 90 | } 91 | var addItems = []; 92 | var result = this.each(function() { 93 | highlight(this, addItems); 94 | }); 95 | for (var i = 0; i < addItems.length; ++i) { 96 | jQuery(addItems[i].parent).before(addItems[i].target); 97 | } 98 | return result; 99 | }; 100 | 101 | /* 102 | * backward compatibility for jQuery.browser 103 | * This will be supported until firefox bug is fixed. 104 | */ 105 | if (!jQuery.browser) { 106 | jQuery.uaMatch = function(ua) { 107 | ua = ua.toLowerCase(); 108 | 109 | var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || 110 | /(webkit)[ \/]([\w.]+)/.exec(ua) || 111 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || 112 | /(msie) ([\w.]+)/.exec(ua) || 113 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || 114 | []; 115 | 116 | return { 117 | browser: match[ 1 ] || "", 118 | version: match[ 2 ] || "0" 119 | }; 120 | }; 121 | jQuery.browser = {}; 122 | jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; 123 | } 124 | -------------------------------------------------------------------------------- /docs/build/html/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px} -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/css/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/css/fonts/lato-bold-italic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/css/fonts/lato-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/css/fonts/lato-bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/css/fonts/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/css/fonts/lato-normal-italic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/css/fonts/lato-normal-italic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/css/fonts/lato-normal.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/css/fonts/lato-normal.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Base JavaScript utilities for all Sphinx HTML documentation. 3 | */ 4 | "use strict"; 5 | 6 | const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ 7 | "TEXTAREA", 8 | "INPUT", 9 | "SELECT", 10 | "BUTTON", 11 | ]); 12 | 13 | const _ready = (callback) => { 14 | if (document.readyState !== "loading") { 15 | callback(); 16 | } else { 17 | document.addEventListener("DOMContentLoaded", callback); 18 | } 19 | }; 20 | 21 | /** 22 | * Small JavaScript module for the documentation. 23 | */ 24 | const Documentation = { 25 | init: () => { 26 | Documentation.initDomainIndexTable(); 27 | Documentation.initOnKeyListeners(); 28 | }, 29 | 30 | /** 31 | * i18n support 32 | */ 33 | TRANSLATIONS: {}, 34 | PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), 35 | LOCALE: "unknown", 36 | 37 | // gettext and ngettext don't access this so that the functions 38 | // can safely bound to a different name (_ = Documentation.gettext) 39 | gettext: (string) => { 40 | const translated = Documentation.TRANSLATIONS[string]; 41 | switch (typeof translated) { 42 | case "undefined": 43 | return string; // no translation 44 | case "string": 45 | return translated; // translation exists 46 | default: 47 | return translated[0]; // (singular, plural) translation tuple exists 48 | } 49 | }, 50 | 51 | ngettext: (singular, plural, n) => { 52 | const translated = Documentation.TRANSLATIONS[singular]; 53 | if (typeof translated !== "undefined") 54 | return translated[Documentation.PLURAL_EXPR(n)]; 55 | return n === 1 ? singular : plural; 56 | }, 57 | 58 | addTranslations: (catalog) => { 59 | Object.assign(Documentation.TRANSLATIONS, catalog.messages); 60 | Documentation.PLURAL_EXPR = new Function( 61 | "n", 62 | `return (${catalog.plural_expr})` 63 | ); 64 | Documentation.LOCALE = catalog.locale; 65 | }, 66 | 67 | /** 68 | * helper function to focus on search bar 69 | */ 70 | focusSearchBar: () => { 71 | document.querySelectorAll("input[name=q]")[0]?.focus(); 72 | }, 73 | 74 | /** 75 | * Initialise the domain index toggle buttons 76 | */ 77 | initDomainIndexTable: () => { 78 | const toggler = (el) => { 79 | const idNumber = el.id.substr(7); 80 | const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); 81 | if (el.src.substr(-9) === "minus.png") { 82 | el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; 83 | toggledRows.forEach((el) => (el.style.display = "none")); 84 | } else { 85 | el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; 86 | toggledRows.forEach((el) => (el.style.display = "")); 87 | } 88 | }; 89 | 90 | const togglerElements = document.querySelectorAll("img.toggler"); 91 | togglerElements.forEach((el) => 92 | el.addEventListener("click", (event) => toggler(event.currentTarget)) 93 | ); 94 | togglerElements.forEach((el) => (el.style.display = "")); 95 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); 96 | }, 97 | 98 | initOnKeyListeners: () => { 99 | // only install a listener if it is really needed 100 | if ( 101 | !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && 102 | !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS 103 | ) 104 | return; 105 | 106 | document.addEventListener("keydown", (event) => { 107 | // bail for input elements 108 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 109 | // bail with special keys 110 | if (event.altKey || event.ctrlKey || event.metaKey) return; 111 | 112 | if (!event.shiftKey) { 113 | switch (event.key) { 114 | case "ArrowLeft": 115 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 116 | 117 | const prevLink = document.querySelector('link[rel="prev"]'); 118 | if (prevLink && prevLink.href) { 119 | window.location.href = prevLink.href; 120 | event.preventDefault(); 121 | } 122 | break; 123 | case "ArrowRight": 124 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 125 | 126 | const nextLink = document.querySelector('link[rel="next"]'); 127 | if (nextLink && nextLink.href) { 128 | window.location.href = nextLink.href; 129 | event.preventDefault(); 130 | } 131 | break; 132 | } 133 | } 134 | 135 | // some keyboard layouts may need Shift to get / 136 | switch (event.key) { 137 | case "/": 138 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; 139 | Documentation.focusSearchBar(); 140 | event.preventDefault(); 141 | } 142 | }); 143 | }, 144 | }; 145 | 146 | // quick alias for translations 147 | const _ = Documentation.gettext; 148 | 149 | _ready(Documentation.init); 150 | -------------------------------------------------------------------------------- /docs/build/html/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | const DOCUMENTATION_OPTIONS = { 2 | VERSION: '3.3.1', 3 | LANGUAGE: 'en', 4 | COLLAPSE_INDEX: false, 5 | BUILDER: 'html', 6 | FILE_SUFFIX: '.html', 7 | LINK_SUFFIX: '.html', 8 | HAS_SOURCE: true, 9 | SOURCELINK_SUFFIX: '.txt', 10 | NAVIGATION_WITH_KEYS: false, 11 | SHOW_SEARCH_SUMMARY: true, 12 | ENABLE_SEARCH_SHORTCUTS: true, 13 | }; -------------------------------------------------------------------------------- /docs/build/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/file.png -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/Lato/lato-bold.eot -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/Lato/lato-bold.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/Lato/lato-bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/Lato/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bolditalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/Lato/lato-bolditalic.eot -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/Lato/lato-bolditalic.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bolditalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/Lato/lato-bolditalic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bolditalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/Lato/lato-bolditalic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/Lato/lato-italic.eot -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/Lato/lato-italic.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/Lato/lato-italic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/Lato/lato-italic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/Lato/lato-regular.eot -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/Lato/lato-regular.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/Lato/lato-regular.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/Lato/lato-regular.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/js/badge_only.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}({4:function(e,t,r){}}); -------------------------------------------------------------------------------- /docs/build/html/_static/js/theme.js: -------------------------------------------------------------------------------- 1 | !function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0 56 | var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 57 | var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 58 | var s_v = "^(" + C + ")?" + v; // vowel in stem 59 | 60 | this.stemWord = function (w) { 61 | var stem; 62 | var suffix; 63 | var firstch; 64 | var origword = w; 65 | 66 | if (w.length < 3) 67 | return w; 68 | 69 | var re; 70 | var re2; 71 | var re3; 72 | var re4; 73 | 74 | firstch = w.substr(0,1); 75 | if (firstch == "y") 76 | w = firstch.toUpperCase() + w.substr(1); 77 | 78 | // Step 1a 79 | re = /^(.+?)(ss|i)es$/; 80 | re2 = /^(.+?)([^s])s$/; 81 | 82 | if (re.test(w)) 83 | w = w.replace(re,"$1$2"); 84 | else if (re2.test(w)) 85 | w = w.replace(re2,"$1$2"); 86 | 87 | // Step 1b 88 | re = /^(.+?)eed$/; 89 | re2 = /^(.+?)(ed|ing)$/; 90 | if (re.test(w)) { 91 | var fp = re.exec(w); 92 | re = new RegExp(mgr0); 93 | if (re.test(fp[1])) { 94 | re = /.$/; 95 | w = w.replace(re,""); 96 | } 97 | } 98 | else if (re2.test(w)) { 99 | var fp = re2.exec(w); 100 | stem = fp[1]; 101 | re2 = new RegExp(s_v); 102 | if (re2.test(stem)) { 103 | w = stem; 104 | re2 = /(at|bl|iz)$/; 105 | re3 = new RegExp("([^aeiouylsz])\\1$"); 106 | re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 107 | if (re2.test(w)) 108 | w = w + "e"; 109 | else if (re3.test(w)) { 110 | re = /.$/; 111 | w = w.replace(re,""); 112 | } 113 | else if (re4.test(w)) 114 | w = w + "e"; 115 | } 116 | } 117 | 118 | // Step 1c 119 | re = /^(.+?)y$/; 120 | if (re.test(w)) { 121 | var fp = re.exec(w); 122 | stem = fp[1]; 123 | re = new RegExp(s_v); 124 | if (re.test(stem)) 125 | w = stem + "i"; 126 | } 127 | 128 | // Step 2 129 | re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; 130 | if (re.test(w)) { 131 | var fp = re.exec(w); 132 | stem = fp[1]; 133 | suffix = fp[2]; 134 | re = new RegExp(mgr0); 135 | if (re.test(stem)) 136 | w = stem + step2list[suffix]; 137 | } 138 | 139 | // Step 3 140 | re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; 141 | if (re.test(w)) { 142 | var fp = re.exec(w); 143 | stem = fp[1]; 144 | suffix = fp[2]; 145 | re = new RegExp(mgr0); 146 | if (re.test(stem)) 147 | w = stem + step3list[suffix]; 148 | } 149 | 150 | // Step 4 151 | re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; 152 | re2 = /^(.+?)(s|t)(ion)$/; 153 | if (re.test(w)) { 154 | var fp = re.exec(w); 155 | stem = fp[1]; 156 | re = new RegExp(mgr1); 157 | if (re.test(stem)) 158 | w = stem; 159 | } 160 | else if (re2.test(w)) { 161 | var fp = re2.exec(w); 162 | stem = fp[1] + fp[2]; 163 | re2 = new RegExp(mgr1); 164 | if (re2.test(stem)) 165 | w = stem; 166 | } 167 | 168 | // Step 5 169 | re = /^(.+?)e$/; 170 | if (re.test(w)) { 171 | var fp = re.exec(w); 172 | stem = fp[1]; 173 | re = new RegExp(mgr1); 174 | re2 = new RegExp(meq1); 175 | re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 176 | if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) 177 | w = stem; 178 | } 179 | re = /ll$/; 180 | re2 = new RegExp(mgr1); 181 | if (re.test(w) && re2.test(w)) { 182 | re = /.$/; 183 | w = w.replace(re,""); 184 | } 185 | 186 | // and turn initial Y back to y 187 | if (firstch == "y") 188 | w = firstch.toLowerCase() + w.substr(1); 189 | return w; 190 | } 191 | } 192 | 193 | -------------------------------------------------------------------------------- /docs/build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/minus.png -------------------------------------------------------------------------------- /docs/build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/_static/plus.png -------------------------------------------------------------------------------- /docs/build/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | pre { line-height: 125%; } 2 | td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 3 | span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 4 | td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 5 | span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 6 | .highlight .hll { background-color: #ffffcc } 7 | .highlight { background: #f8f8f8; } 8 | .highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ 9 | .highlight .err { border: 1px solid #F00 } /* Error */ 10 | .highlight .k { color: #008000; font-weight: bold } /* Keyword */ 11 | .highlight .o { color: #666 } /* Operator */ 12 | .highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ 13 | .highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ 14 | .highlight .cp { color: #9C6500 } /* Comment.Preproc */ 15 | .highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ 16 | .highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ 17 | .highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ 18 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 19 | .highlight .ge { font-style: italic } /* Generic.Emph */ 20 | .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ 21 | .highlight .gr { color: #E40000 } /* Generic.Error */ 22 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 23 | .highlight .gi { color: #008400 } /* Generic.Inserted */ 24 | .highlight .go { color: #717171 } /* Generic.Output */ 25 | .highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 26 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 27 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 28 | .highlight .gt { color: #04D } /* Generic.Traceback */ 29 | .highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 30 | .highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 31 | .highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 32 | .highlight .kp { color: #008000 } /* Keyword.Pseudo */ 33 | .highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 34 | .highlight .kt { color: #B00040 } /* Keyword.Type */ 35 | .highlight .m { color: #666 } /* Literal.Number */ 36 | .highlight .s { color: #BA2121 } /* Literal.String */ 37 | .highlight .na { color: #687822 } /* Name.Attribute */ 38 | .highlight .nb { color: #008000 } /* Name.Builtin */ 39 | .highlight .nc { color: #00F; font-weight: bold } /* Name.Class */ 40 | .highlight .no { color: #800 } /* Name.Constant */ 41 | .highlight .nd { color: #A2F } /* Name.Decorator */ 42 | .highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ 43 | .highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ 44 | .highlight .nf { color: #00F } /* Name.Function */ 45 | .highlight .nl { color: #767600 } /* Name.Label */ 46 | .highlight .nn { color: #00F; font-weight: bold } /* Name.Namespace */ 47 | .highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ 48 | .highlight .nv { color: #19177C } /* Name.Variable */ 49 | .highlight .ow { color: #A2F; font-weight: bold } /* Operator.Word */ 50 | .highlight .w { color: #BBB } /* Text.Whitespace */ 51 | .highlight .mb { color: #666 } /* Literal.Number.Bin */ 52 | .highlight .mf { color: #666 } /* Literal.Number.Float */ 53 | .highlight .mh { color: #666 } /* Literal.Number.Hex */ 54 | .highlight .mi { color: #666 } /* Literal.Number.Integer */ 55 | .highlight .mo { color: #666 } /* Literal.Number.Oct */ 56 | .highlight .sa { color: #BA2121 } /* Literal.String.Affix */ 57 | .highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ 58 | .highlight .sc { color: #BA2121 } /* Literal.String.Char */ 59 | .highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ 60 | .highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ 61 | .highlight .s2 { color: #BA2121 } /* Literal.String.Double */ 62 | .highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ 63 | .highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ 64 | .highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ 65 | .highlight .sx { color: #008000 } /* Literal.String.Other */ 66 | .highlight .sr { color: #A45A77 } /* Literal.String.Regex */ 67 | .highlight .s1 { color: #BA2121 } /* Literal.String.Single */ 68 | .highlight .ss { color: #19177C } /* Literal.String.Symbol */ 69 | .highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ 70 | .highlight .fm { color: #00F } /* Name.Function.Magic */ 71 | .highlight .vc { color: #19177C } /* Name.Variable.Class */ 72 | .highlight .vg { color: #19177C } /* Name.Variable.Global */ 73 | .highlight .vi { color: #19177C } /* Name.Variable.Instance */ 74 | .highlight .vm { color: #19177C } /* Name.Variable.Magic */ 75 | .highlight .il { color: #666 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/build/html/_static/sphinx_highlight.js: -------------------------------------------------------------------------------- 1 | /* Highlighting utilities for Sphinx HTML documentation. */ 2 | "use strict"; 3 | 4 | const SPHINX_HIGHLIGHT_ENABLED = true 5 | 6 | /** 7 | * highlight a given string on a node by wrapping it in 8 | * span elements with the given class name. 9 | */ 10 | const _highlight = (node, addItems, text, className) => { 11 | if (node.nodeType === Node.TEXT_NODE) { 12 | const val = node.nodeValue; 13 | const parent = node.parentNode; 14 | const pos = val.toLowerCase().indexOf(text); 15 | if ( 16 | pos >= 0 && 17 | !parent.classList.contains(className) && 18 | !parent.classList.contains("nohighlight") 19 | ) { 20 | let span; 21 | 22 | const closestNode = parent.closest("body, svg, foreignObject"); 23 | const isInSVG = closestNode && closestNode.matches("svg"); 24 | if (isInSVG) { 25 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 26 | } else { 27 | span = document.createElement("span"); 28 | span.classList.add(className); 29 | } 30 | 31 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 32 | const rest = document.createTextNode(val.substr(pos + text.length)); 33 | parent.insertBefore( 34 | span, 35 | parent.insertBefore( 36 | rest, 37 | node.nextSibling 38 | ) 39 | ); 40 | node.nodeValue = val.substr(0, pos); 41 | /* There may be more occurrences of search term in this node. So call this 42 | * function recursively on the remaining fragment. 43 | */ 44 | _highlight(rest, addItems, text, className); 45 | 46 | if (isInSVG) { 47 | const rect = document.createElementNS( 48 | "http://www.w3.org/2000/svg", 49 | "rect" 50 | ); 51 | const bbox = parent.getBBox(); 52 | rect.x.baseVal.value = bbox.x; 53 | rect.y.baseVal.value = bbox.y; 54 | rect.width.baseVal.value = bbox.width; 55 | rect.height.baseVal.value = bbox.height; 56 | rect.setAttribute("class", className); 57 | addItems.push({ parent: parent, target: rect }); 58 | } 59 | } 60 | } else if (node.matches && !node.matches("button, select, textarea")) { 61 | node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); 62 | } 63 | }; 64 | const _highlightText = (thisNode, text, className) => { 65 | let addItems = []; 66 | _highlight(thisNode, addItems, text, className); 67 | addItems.forEach((obj) => 68 | obj.parent.insertAdjacentElement("beforebegin", obj.target) 69 | ); 70 | }; 71 | 72 | /** 73 | * Small JavaScript module for the documentation. 74 | */ 75 | const SphinxHighlight = { 76 | 77 | /** 78 | * highlight the search words provided in localstorage in the text 79 | */ 80 | highlightSearchWords: () => { 81 | if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight 82 | 83 | // get and clear terms from localstorage 84 | const url = new URL(window.location); 85 | const highlight = 86 | localStorage.getItem("sphinx_highlight_terms") 87 | || url.searchParams.get("highlight") 88 | || ""; 89 | localStorage.removeItem("sphinx_highlight_terms") 90 | url.searchParams.delete("highlight"); 91 | window.history.replaceState({}, "", url); 92 | 93 | // get individual terms from highlight string 94 | const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); 95 | if (terms.length === 0) return; // nothing to do 96 | 97 | // There should never be more than one element matching "div.body" 98 | const divBody = document.querySelectorAll("div.body"); 99 | const body = divBody.length ? divBody[0] : document.querySelector("body"); 100 | window.setTimeout(() => { 101 | terms.forEach((term) => _highlightText(body, term, "highlighted")); 102 | }, 10); 103 | 104 | const searchBox = document.getElementById("searchbox"); 105 | if (searchBox === null) return; 106 | searchBox.appendChild( 107 | document 108 | .createRange() 109 | .createContextualFragment( 110 | '" 114 | ) 115 | ); 116 | }, 117 | 118 | /** 119 | * helper function to hide the search marks again 120 | */ 121 | hideSearchWords: () => { 122 | document 123 | .querySelectorAll("#searchbox .highlight-link") 124 | .forEach((el) => el.remove()); 125 | document 126 | .querySelectorAll("span.highlighted") 127 | .forEach((el) => el.classList.remove("highlighted")); 128 | localStorage.removeItem("sphinx_highlight_terms") 129 | }, 130 | 131 | initEscapeListener: () => { 132 | // only install a listener if it is really needed 133 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; 134 | 135 | document.addEventListener("keydown", (event) => { 136 | // bail for input elements 137 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 138 | // bail with special keys 139 | if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; 140 | if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { 141 | SphinxHighlight.hideSearchWords(); 142 | event.preventDefault(); 143 | } 144 | }); 145 | }, 146 | }; 147 | 148 | _ready(() => { 149 | /* Do not call highlightSearchWords() when we are on the search page. 150 | * It will highlight words from the *previous* search query. 151 | */ 152 | if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); 153 | SphinxHighlight.initEscapeListener(); 154 | }); 155 | -------------------------------------------------------------------------------- /docs/build/html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/build/html/objects.inv -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx-rtd-theme 3 | myst-nb 4 | -------------------------------------------------------------------------------- /docs/source/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/source/.DS_Store -------------------------------------------------------------------------------- /docs/source/FAQ.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/source/_images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/source/_images/.DS_Store -------------------------------------------------------------------------------- /docs/source/_images/IGMP_switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/source/_images/IGMP_switch.png -------------------------------------------------------------------------------- /docs/source/_images/WD-data-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/source/_images/WD-data-architecture.png -------------------------------------------------------------------------------- /docs/source/_images/btop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/source/_images/btop.png -------------------------------------------------------------------------------- /docs/source/_images/pskreporter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/source/_images/pskreporter.png -------------------------------------------------------------------------------- /docs/source/_images/psws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/source/_images/psws.png -------------------------------------------------------------------------------- /docs/source/_images/wd-file-structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/source/_images/wd-file-structure.png -------------------------------------------------------------------------------- /docs/source/_images/wd-functions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/source/_images/wd-functions.png -------------------------------------------------------------------------------- /docs/source/_images/wwv_obs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/docs/source/_images/wwv_obs.png -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | project = 'wsprdaemon' 10 | copyright = '2025, Rob Robinett' 11 | author = 'Rob Robinett' 12 | release = '3.3.1' 13 | 14 | # -- General configuration --------------------------------------------------- 15 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 16 | 17 | extensions = [ 18 | 'myst_nb', 19 | 'sphinx.ext.autodoc', 20 | 'sphinx.ext.viewcode', 21 | 'sphinx.ext.napoleon', 22 | 'sphinx.ext.mathjax' 23 | ] 24 | 25 | myst_enable_extentions = [ 26 | "amsmath", # Enables AMS-style math environments like align 27 | "html_image" 28 | ] 29 | 30 | html_theme = 'sphinx_rtd_theme' 31 | 32 | templates_path = ['_templates'] 33 | exclude_patterns = [] 34 | 35 | html_static_path = ['_static'] 36 | -------------------------------------------------------------------------------- /docs/source/configuration/ka9q-web.md: -------------------------------------------------------------------------------- 1 | # ka9q-web 2 | 3 | This software builds in reference to ka9q-radio and requires no specific configuration. 4 | WD typically starts it automatically. You invoke it manually, if necessary, thus: 5 | ``` 6 | ka9q-web -m your-radiod-status-stream.local -p 8081 -n "callsign grid antenna" & 7 | ``` 8 | where "your-radiod-status-stream" is the name specified in the [GLOBAL] of your radiod@.conf (often hf.local or hf-status.local) 9 | 10 | ## Viewing the ka9q-web spectrum display 11 | 12 | Logged in an running everything locally, direct your browser to http://localhost:8081. 13 | 14 | If managing the computer remotely using ssh, you can set up an ssh tunnel from the remote computer to your local computer like this: 15 | 16 | From your local machine, run: 17 | ``` 18 | ssh -L 8081:localhost:8081 wsprdaemon@aa.bb.cc.dd 19 | ``` 20 | where aa.bb.cc.dd is the ip address or name of the remote computer running ka9q-web. (Substitute another username if not running WD eponymously.) 21 | 22 | Then direct your browser to http://localhost:8081 to view ka9q-web served from the aa.bb.cc.dd remote computer. 23 | If you happen to be using port 8081 on your local computer for another purpose, simply replace the port number after -L in the command above to an unused port YYYY. Then direct your browser to http://localhost:YYYY. 24 | 25 | The port will disappear when you close the ssh session. 26 | 27 | **John Melton G0ORX** started this with a proof-of-concept version in late 2023. This adjunct to ka9q-radio displays a spectrum, waterfall, and other data from radiod. **Scott Newell N5TNL** has since improved it dramatically in collaboration with **Rob Robinett AI6VN**, **Phil Karn KA9Q**, **Glenn Elmore N6GN**, **Jim Lill WA2ZKD**, and desultory kibbitzers. 28 | 29 | - Web Server by John Melton, G0ORX (https://github.com/g0orx/ka9q-radio) 30 | - ka9q-radio by Phil Karn, KA9Q (https://github.com/ka9q/ka9q-radio) 31 | - Onion Web Framework by David Moreno (https://github.com/davidmoreno/onion) 32 | - Spectrum/Waterfall Display by Jeppe Ledet-Pedersen (https://github.com/jledet/waterfall) 33 | -------------------------------------------------------------------------------- /docs/source/configuration/radiod@.conf/radiod@rx888-wsprdaemon.conf.md: -------------------------------------------------------------------------------- 1 | # Example radiod@rx888-wsprdaemon.conf 2 | 3 | ## minimalist setup on a single computer for wspr, wwv, ft4 and ft8. 4 | 5 | The following directs radiod to use a RX888 to present simultaneous multicast streams of 16 wspr channels, streams of 7 wwv and 3 chu channels, and streams of 9 ft4 and 11 ft8 channels. 6 | 7 | You will find more detailed descriptions of these sections in: 8 | - [global](./global.md) 9 | - [hardware](./hardware.md) 10 | - [channels](./channels.md) 11 | 12 | --- 13 | 14 | ``` 15 | [global] 16 | hardware = rx888 17 | status = bee1-hf-status.local 18 | samprate = 12000 19 | mode = usb 20 | ttl = 0 21 | fft-threads = 0 22 | 23 | [rx888] 24 | device = "rx888" 25 | description = "AC0G @EM38ww dipole" # good to put callsign, gridsquare, and antenna description in here 26 | samprate = 64800000 # or 129600000 27 | 28 | [WSPR] 29 | encoding = float 30 | disable = no 31 | data = bee1-wspr-pcm.local 32 | agc=0 33 | gain=0 34 | samprate = 12000 35 | mode = usb 36 | low=1300 37 | high=1700 38 | freq = "136k000 474k200 1m836600 3m568600 3m592600 5m287200 5m364700 7m038600 10m138700 13m553900 14m095600 18m104600 21m094600 24m924600 28m124600 50m293000"" 39 | 40 | [WWV-IQ] 41 | disable = no 42 | encoding=float 43 | data = bee1-wwv-iq.local 44 | agc=0 45 | gain=0 46 | samprate = 16k 47 | mode = iq 48 | freq = "60000 2m500000 5m000000 10m000000 15m000000 20m000000 25m000000 3m330000 7m850000 14m670000" ### Added the three CHU frequencies 49 | 50 | [FT8] 51 | disable = no 52 | data = ft8-pcm.local 53 | mode = usb 54 | freq = "1m840000 3m573000 5m357000 7m074000 10m136000 14m074000 18m100000 21m074000 24m915000 28m074000 50m313000" 55 | 56 | [FT4] 57 | disable = no 58 | data = ft4-pcm.local 59 | mode = usb 60 | freq = "3m575000 7m047500 10m140000 14m080000 18m104000 21m140000 24m919000 28m180000 50m318000" 61 | ``` 62 | 63 | 64 | -------------------------------------------------------------------------------- /docs/source/configuration/radiod_conf.md: -------------------------------------------------------------------------------- 1 | # Configuring ka9q-radio: radiod@.conf 2 | 3 | Likewise, the radiod@.conf, located in /etc/radio/, has lots of options. 4 | 5 | However, it boils down to setting up the following: 6 | - [global settings](./radiod@.conf/global.md) 7 | - [hardware settings](./radiod@.conf/hardware.md) 8 | - [channel settings](./radiod@.conf/channels.md) 9 | 10 | ## [Example radiod@rx888-wsprdaemon.conf](./radiod@.conf/radiod@rx888-wsprdaemon.conf.md) 11 | -------------------------------------------------------------------------------- /docs/source/configuration/wd_conf.md: -------------------------------------------------------------------------------- 1 | # Configuring wsprdaemon: wsprdaemon.conf 2 | 3 | The template for a wsprdaemon.conf file, located in /home/wsprdaemon/wsprdaemon/, includes copious information and looks dauntingly complicated. 4 | 5 | However, it boils down to setting up the following: 6 | 1. [computer-related parameters](wsprdaemon.conf.d/computer.md) 7 | 2. [ka9q-radio parameters](wsprdaemon.conf.d/ka9q-radio.md) 8 | 3. [reporting parameters](wsprdaemon.conf.d/reporting.md) 9 | 4. [receiver definitions](wsprdaemon.conf.d/receivers.md) 10 | 5. [schedule definitions](wsprdaemon.conf.d/schedule.md) 11 | 12 | ## [Example wsprdaemon.conf](wsprdaemon.conf.d/wsprdaemon.conf.md) -------------------------------------------------------------------------------- /docs/source/configuration/wsprdaemon.conf.d/computer.md: -------------------------------------------------------------------------------- 1 | 2 | # Computer Issues 3 | 4 | ## CPUs 5 | 6 | ### Ryzen 5560, 5700, 5800, 5825, and above 7 | Known to work running both WD and radiod. 8 | 9 | ### Intel 10 | 11 | ### Raspberry Pi (4 or 5) 12 | Can work in constrained use -- but not supporting the full bandwidth of a RX888 plus WD. 13 | Known to work with RTL-SDR, funcube dongle, AirspyR2, etc. 14 | 15 | ### Orange Pi-5 16 | Known to work running both WD and radiod if configured correctly. 17 | 18 | ## Memory 19 | 20 | ## Disk storage 21 | 22 | -------------------------------------------------------------------------------- /docs/source/configuration/wsprdaemon.conf.d/ka9q-radio.md: -------------------------------------------------------------------------------- 1 | # Configuration with ka9q-radio and ka9q-web 2 | 3 | ## Running radiod locally or remotely 4 | 5 | ## Dependence on ka9q-radio processes 6 | 7 | ## radiod configuration file 8 | 9 | ## ka9q-web title 10 | 11 | ## code commits 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/source/configuration/wsprdaemon.conf.d/receivers.md: -------------------------------------------------------------------------------- 1 | 2 | # Receiver definitions 3 | 4 | ## KiwiSDR 5 | 6 | ## KA9Q 7 | 8 | ``` 9 | ############################################################## 10 | ### The RECEIVER_LIST() array defines the physical (KIWI_xxx or KA9Q...) and logical (MERG...) receive devices available on this server 11 | ### Each element of RECEIVER_LIST is a string with 5 space-separated fields: 12 | ### " ID(no spaces) IP:PORT or RTL:n MyCall MyGrid KiwPassword Optional SIGNAL_LEVEL_ADJUSTMENTS 13 | ### [[DEFAULT:ADJUST,]BAND_0:ADJUST[,BAND_N:ADJUST_N]...] 14 | ### A comma-separated list of BAND:ADJUST pairs 15 | ### BAND is one of 2200..10, while ADJUST is in dBs TO BE ADDED to the raw data 16 | ### So If you have a +10 dB LNA, ADJUST '-10' will LOWER the reported level so that your reports reflect the level at the input of the LNA 17 | ### DEFAULT defaults to zero and is applied to all bands not specified with a BAND:ADJUST 18 | 19 | declare RECEIVER_LIST=( 20 | "KA9Q_0 wspr-pcm.local OE3GBB/Q JN87aq NULL" ### A receiver name which starts with 'KA9Q_...' will decode wav files supplied by the KA9Q-radio multicast RTP streams 21 | ### In WD 3.1.0 WD assumes all WSPR audio streams come from a local instance of KA9Q 22 | ### which by default outputs all the WSPR audio stream on the multicast DNS address wspr-pcm.local 23 | "KA9Q_1 wspr1-pcm.local AI6VN CM88mc NULL" ### Multicast streams from remote KA9Q receivers can be sources, and not just RX-888s 24 | "KA9Q_0_WSPR_IQ wspr-iq.local AI6VN CM88mc NULL" ### Multicast IQ streams from the local RX888 + KA9Q receiver 25 | "KA9Q_0_WWV_IQ wwv-iq.local AI6VN CM88mc NULL" ### Those streams are not enabled by default in the radiod.conf file. So if you configue an IQ rx job, 26 | ### you will need to set 'disabled = no' for one or both in radiod@rx888-wsprdaemon.conf and then restart radiod 27 | 28 | "KIWI_0 10.11.12.100:8073 AI6VN CM88mc NULL" 29 | "KIWI_1 10.11.12.101:8073 AI6VN CM88mc foobar DEFAULT:-10,80:-12,30:-8,20:2,15:6" ### You can optionally adjust noise levels for the antenna factor 30 | "KIWI_2 10.11.12.102:8073 AI6VN CM88mc foobar" 31 | 32 | "MERG_K01_Q01 KIWI_0,KIWI_1,KA9Q_0,KA9Q_1 AI6VN CM88mc foobar" ### For a receiver with a name starting with "MERG", the IP field is a list of two or more 'real' receivers a defined above. For a logical MERG receiver 33 | ) 34 | 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/source/configuration/wsprdaemon.conf.d/reporting.md: -------------------------------------------------------------------------------- 1 | 2 | # Reporting 3 | 4 | ## WSPR Reporting 5 | 6 | ``` 7 | ################### The following variables are used in normally running installations ################### 8 | SIGNAL_LEVEL_UPLOAD="noise" ### Whether and how to upload extended spots to wsprdaemon.org. WD always attempts to upload spots to wsprnet.org 9 | ### SIGNAL_LEVEL_UPLOAD="no" => (Default) Only upload spots directly to wsprnet.org 10 | ### SIGNAL_LEVEL_UPLOAD_MODE="noise" => In addition, upload extended spots and noise data to wsprdaemon.org 11 | ### SIGNAL_LEVEL_UPLOAD_MODE="proxy" => Don't directly upload spots to wsprdaemon.org. Instead, after uploading extended spots and noise data to wsprdaemon.org have it regenerate and upload those spots to wsp 12 | ### This mode minimizes the use of Internet bandwidth, but makes getting spots to wsprnet.org dependent upon the wsprdameon.org services. 13 | 14 | # If SIGNAL_LEVEL_UPLOAD in NOT "no", then you must modify SIGNAL_LEVEL_UPLOAD_ID from "AI6VN" to your call sign. SIGNAL_LEVEL_UPLOAD_ID cannot include '/ 15 | SIGNAL_LEVEL_UPLOAD_ID="OE3GBB_Q" ### The name put in upload log records, the title bar of the graph, and the name used to view spots and noise at that server. 16 | # SIGNAL_LEVEL_UPLOAD_GRAPHS="yes" ### If this variable is defined as "yes" AND SIGNAL_LEVEL_UPLOAD_ID is defined, then FTP graphs of the last 24 hours to http://wsprdaemon.org/graphs/SIGNAL_LEVEL_UPLOAD_ID 17 | # SIGNAL_LEVEL_LOCAL_GRAPHS="yes" ### If this variable is defined as "yes" AND SIGNAL_LEVEL_UPLOAD_ID is defined, then make graphs visible at http://localhost/ 18 | # 19 | ### Graphs default to y-axis minimum of -175 dB to maximum of -105 dB. X pixels default to 40, Y pixels default to 30. If the graph of your system isn't pleasing, you can change the graph's appearance by 20 | ### uncommenting one or more of these variables and changing their values 21 | # NOISE_GRAPHS_Y_MIN=-175 22 | # NOISE_GRAPHS_Y_MAX=-105 23 | # NOISE_GRAPHS_X_PIXEL=40 24 | # NOISE_GRAPHS_Y_PIXEL=30 25 | ``` 26 | 27 | ## HamSCI -- Grape Reporting 28 | 29 | WD includes configurable support for the [HamSCI GRAPE WWV Doppler shift project](https://hamsci.org/grape). From local or remote RX888 receivers, WD can record a continuous series of one minute long 16000 sps flac-compressed IQ wav files. Soon after 00:00 UTC, WD creates a single 3.8 MB 24hour-10hz-iq.wav file which is uploaded to WD's WD1 server at grape.wsprdaemon.org. WD software on WD1 then converts the one or more time station band recordings into [Digital RF (DRF)](https://github.com/MITHaystack/digital_rf) file format and uploads those DRF files to the HamSCI server. 30 | 31 | You must create an account at https://pswsnetwork.caps.ua.edu/. This account enables you to manage your site(s) and instrument(s). 32 | 33 | After establishing your account, you create a "site" having a SITE_ID and a TOKEN for each WD instance contributing to the GRAPE project. The SITE_ID takes the form "S000NNN". You typically name the site with your callsign and a useful discriminator if you have more than one site, for instance, "AC0G_B1", "AC0G_B2". For each site, you add an "instrument" of particular type, for instance, "magnetometer" or "rx888", with an INSTRUMENT_ID. You can create multiple sites but each site has only one instrument. The SITE_ID and TOKEN function as username and password for uploading data. The SITE_ID and INSTRUMENT_ID function to identify the data in DRF. 34 | 35 | On your WD server, you then 36 | - [configure KA9Q-radio](../radiod@.conf/channels.md) to output WWV-IQ channels. 37 | - [configure WD receivers](./receivers.md) listen to those channels. 38 | - [configure a WD schedule](./schedule.md) for listening on each channel. 39 | - [configure WD reporting](./reporting.md) with GRAPE_PSWS_ID="_" 40 | 41 | Finally, enable automatic uploads of HamSCI data. For example, with SITE_ID="S000987" run: 42 | ``` 43 | ssh-copy-id S000987@pswsnetwork.caps.ua.edu 44 | ``` 45 | The site will respond by asking for a password. Enter the TOKEN for that site and you should get a message of success. The most common cause of failure at this point is errant copy and paste with a character missing or an added space at the beginning or end of the token string. 46 | 47 | You can check if auto login works by executing the 'wdssp' alias. 48 | 49 | Source : https://groups.io/g/wsprdaemon/message/3319 Rob Robinett Source : https://github.com/rrobinett/wsprdaemon/blob/master/wd_template.conf Source : https://groups.io/g/wsprdaemon/message/3301 Rob Robinett 50 | 51 | ## PSKReporter 52 | 53 | -------------------------------------------------------------------------------- /docs/source/configuration/wsprdaemon.conf.d/wsprdaemon.conf.md: -------------------------------------------------------------------------------- 1 | # Example of a working wsprdaemon.conf 2 | 3 | Minimalist configuration for a stand-alone machine that listens to wspr and WWV/CHU streams from radiod (ka9q-radio), then processes and uploads the results to wsprnet, wsprdaemon.org, pskreporter, and pswsnetwork.caps.ua.edu. 4 | 5 | You will find further details on these parameters and definitions in 6 | - [Computer-related parameters](./computer.md) 7 | - [ka9q-radio/web parameters](./ka9q-radio.md) 8 | - [reporting parameters](./reporting.md) 9 | - [receiver definitions](./receivers.md) 10 | - [schedule definitions](./schedule.md) 11 | 12 | ``` 13 | # 1. Computer-related parameters: 14 | # RAC setup enables WD supporters to access your machine remotely. Get a RAC # from Rob Robinett. 15 | # WD will run without this 16 | REMOTE_ACCESS_CHANNEL=27 17 | REMOTE_ACCESS_ID="AC0G-BEE1" 18 | 19 | # CPU/CORE TUNING if neccessary 20 | # the following will restrict wd processes to particular cores if necessary (e.g., Ryzen 7 - 5700 series) 21 | # WD will run without this 22 | WD_CPU_CORES="2-15" 23 | RADIOD_CPU_CORES="0-1" 24 | 25 | # 2. ka9q-radio/web parameters 26 | KA9Q_RADIO_COMMIT="main" 27 | KA9Q_RUNS_ONLY_REMOTELY="no" 28 | KA9Q_CONF_NAME="ac0g-bee1-rx888" 29 | KA9Q_WEB_COMMIT_CHECK="main" 30 | # If you don't set the title here, it will default to the description in the radiod@config file 31 | KA9Q_WEB_TITLE="AC0G_@EM38ww_Longwire" 32 | 33 | # 3. Reporting parameters 34 | # for contributing to HamSCI monitoring of WWV/CHU 35 | GRAPE_PSWS_ID="S000171_172" 36 | # for reporting to wsprdaemon.org 37 | SIGNAL_LEVEL_UPLOAD="noise" 38 | SIGNAL_LEVEL_UPLOAD_ID="AC0G_BEE1" 39 | SIGNAL_LEVEL_UPLOAD_GRAPHS="yes" 40 | 41 | # 4. Receiver definitions -- REQUIRED 42 | # two radiod receivers -- one for wspr and one for wwv 43 | declare RECEIVER_LIST=( 44 | "KA9Q_0_WSPR wspr-pcm.local AI6VN CM88mc NULL" 45 | "KA9Q_0_WWV_IQ wwv-iq.local AI6VN CM88mc NULL" 46 | ) 47 | 48 | # 5. Schedule definitions -- REQUIRED 49 | # SCHEDULE 50 | declare WSPR_SCHEDULE=( 51 | "00:00 KA9Q_0_WSPR,2200,W2:F2:F5:F15:F30 KA9Q_0_WSPR,630,W2:F2:F5 KA9Q_0_WSPR,160,W2:F2:F5 52 | KA9Q_0_WSPR,80,W2:F2:F5 KA9Q_0_WSPR,80eu,W2:F2:F5 KA9Q_0_WSPR,60,W2:F2:F5 53 | KA9Q_0_WSPR,60eu,W2:F2:F5 KA9Q_0_WSPR,40,W2:F2:F5 KA9Q_0_WSPR,30,W2:F2:F5 54 | KA9Q_0_WSPR,22,W2 KA9Q_0_WSPR,20,W2:F2:F5 KA9Q_0_WSPR,17,W2:F2:F5 55 | KA9Q_0_WSPR,15,W2:F2:F5 KA9Q_0_WSPR,12,W2:F2:F5 KA9Q_0_WSPR,10,W2:F2:F5 56 | 57 | KA9Q_0_WWV_IQ,WWV_2_5,I1 KA9Q_0_WWV_IQ,WWV_5,I1 KA9Q_0_WWV_IQ,WWV_10,I1 58 | KA9Q_0_WWV_IQ,WWV_15,I1 KA9Q_0_WWV_IQ,WWV_20,I1 KA9Q_0_WWV_IQ,WWV_25,I1 59 | KA9Q_0_WWV_IQ,CHU_3,I1 KA9Q_0_WWV_IQ,CHU_7,I1 KA9Q_0_WWV_IQ,CHU_14,I1" 60 | ) 61 | ``` -------------------------------------------------------------------------------- /docs/source/contributors.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | ## Creator and Mastermind: 4 | 5 | - Rob Robinett AI6VN 6 | 7 | ## Others lending a hand: 8 | 9 | - Phil Karn KA9Q 10 | - Gwyn Griffiths 11 | - Christoph Mayer 12 | - Scott Newell N5TNL 13 | - Franco Venturi K4VZ 14 | - Philip Barnard 15 | - Michael Hauan AC0G 16 | -------------------------------------------------------------------------------- /docs/source/description/collaborations.md: -------------------------------------------------------------------------------- 1 | # Collaborations 2 | 3 | ## wsprnet.org 4 | 5 | ## HamSCI 6 | 7 | ## SuperDARN 8 | 9 | ## WSPR.rocks 10 | 11 | ## WSPR.live 12 | 13 | ## PSKReporter 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/source/description/history.md: -------------------------------------------------------------------------------- 1 | # History 2 | 3 | WD 4 | -------------------------------------------------------------------------------- /docs/source/description/how_it_works.md: -------------------------------------------------------------------------------- 1 | # How wsprdaemon Works 2 | 3 | Wsprdaemon (WD) runs as a Linux service to decode WSPR and FST4W spots from one or more Kiwis and/or RX888 SDRs and reliably posts them to wsprnet.org. It includes many features not found in WSJT-x, including multiple band and/or multiple receiver support. WD also records additional information about spots like doppler shift and background noise level which permit much deeper understanding of propagation conditions. For systems like the KiwiSDR which have a limited number of receive channels, schedules can be configured to switch between bands at different hours of the day or at sunrise/sunset-relative times. Spots obtained from multiple receivers on the same band ( e.g a 40M vertical and 500' Beverage ) can be merged together with only the best SNR posted to wsprnet.org. WD can be configured to create graphs of the background noise level for display locally and/or at graphs.wsprdaemon.org. 4 | 5 | After configuration, WD runs like a home appliance: it recovers on its own from power and internet outages and caches all spots and other data it gathers until wsprnet.org and/or wsprdaemon.net confirm delivery. Most of the 20+ 'top spotting' sites at http://wspr.rocks/topspotters/ are running WD, and in aggregate they report about 33% of the 7+M spots recorded each day at wsprnet.org. 6 | 7 | WD runs on almost any Debian Linux system running Ubuntu 22.04 LTS on x86. Although WD on a Pi 4 can decode 10+ bands, most sites run WD on a x86 CPU. 8 | 9 | ## Basic components 10 | 11 | - receiver integration (KiwiSDR, ka9q-radio) 12 | - wspr decoding and reporting to wsprnet 13 | - grape recording, conversion to digital_rf, and reporting to HamSCI 14 | - pskreporter 15 | 16 | ![](../_images/wd-file-structure.png) 17 | 18 | ## A running instance of wsprdaemon performs several tasks: 19 | 20 | - configuration and installation checks 21 | - preparation of recording and posting directories 22 | - "listening" functions for defined KIWI and KA9Q receivers 23 | - decoding and posting wspr and fst4w spots 24 | - recording 16 kHz I/Q around WWV and CHU broadcasts for upload to HamSCI 25 | - monitoring and logging results and errors 26 | 27 | ![](../_images/wd-functions.png) 28 | 29 | ## What will make it not start or then stop working? 30 | 31 | - data stream not defined -- wd requires at least one receiver (KIWI or KA9Q) 32 | - schedule not defined -- wd requires a schedule definition 33 | - wd requires a working hardware radio (KiwiSDR or RX888) with proper configuration 34 | - [See Troubleshooting](../troubleshooting/overview.md) 35 | 36 | ## What happens to the data? 37 | 38 | WD sends the collected data to several upstream servers (depending on configuration): 39 | - 1. wsprdaemon.org (wspr spots and other information) 40 | - 2. wspr.net (wspr spots only) 41 | - 3. pskreporter.info (FT4 and FT8 spots) 42 | - 4. pswsnetwork.caps.ua.edu (WWV/H and CHU monitoring) 43 | 44 | A diagram of the WSPR reports: 45 | 46 | ![](../_images/WD-data-architecture.png) -------------------------------------------------------------------------------- /docs/source/description/validity.md: -------------------------------------------------------------------------------- 1 | # Validity of data path and results 2 | 3 | describe the data path and validity checks 4 | -------------------------------------------------------------------------------- /docs/source/external_links.md: -------------------------------------------------------------------------------- 1 | # External Links 2 | 3 | ## [Technical Descriptions](https://wsprdaemon.org/technical.html) 4 | ## [wsprdaemon](https://github.com/rrobinett/wsprdaemon.git) 5 | ## [ka9q-radio](https://ka9q-radio.org) 6 | ## [Personal Space Weather Station Data](https://pswsnetwork.caps.ua.edu/home) 7 | ## [HamSCI](https://hamsci.org/grape) 8 | ## [WSPR Rocks!](https://wspr.rocks) 9 | ## [WSPR.net](https://wsprnet.org) 10 | ## [PSKReporter](https://pskreporter.info) 11 | ## [SuperDARN](https://superdarn.ca/) -------------------------------------------------------------------------------- /docs/source/index.md: -------------------------------------------------------------------------------- 1 | 2 | # WSPRDAEMON Documentation 3 | 4 | ```{toctree} 5 | :maxdepth: 2 6 | :caption: Description 7 | description/how_it_works.md 8 | description/validity.md 9 | ``` 10 | 11 | ```{toctree} 12 | :maxdepth: 2 13 | :caption: Requirements 14 | 15 | requirements/os.md 16 | requirements/radios.md 17 | requirements/network.md 18 | ``` 19 | 20 | ```{toctree} 21 | :maxdepth: 2 22 | :caption: Software Installation 23 | 24 | installation/preparation.md 25 | installation/git.md 26 | ``` 27 | 28 | ```{toctree} 29 | :maxdepth: 1 30 | :caption: Software Configuration 31 | 32 | configuration/wd_conf.md 33 | configuration/radiod_conf.md 34 | configuration/ka9q-web.md 35 | ``` 36 | 37 | ```{toctree} 38 | :maxdepth: 2 39 | :caption: Working Results 40 | 41 | results/wspr.md 42 | results/grape.md 43 | results/psk.md 44 | ``` 45 | 46 | ```{toctree} 47 | :maxdepth: 2 48 | :caption: Maintenance & Updates 49 | 50 | maintenance/operating.md 51 | maintenance/monitoring.md 52 | maintenance/aliases.md 53 | ``` 54 | 55 | ```{toctree} 56 | :maxdepth: 2 57 | :caption: Troubleshooting 58 | 59 | troubleshooting/overview.md 60 | troubleshooting/typicals.md 61 | ``` 62 | 63 | ```{toctree} 64 | :maxdepth: 1 65 | :caption: FAQ 66 | 67 | FAQ.md 68 | ``` 69 | 70 | ```{toctree} 71 | :maxdepth: 1 72 | :caption: External Links 73 | 74 | external_links.md 75 | ``` 76 | 77 | ```{toctree} 78 | :maxdepth: 1 79 | :caption: Contributors 80 | 81 | contributors.md 82 | ``` 83 | -------------------------------------------------------------------------------- /docs/source/installation/git.md: -------------------------------------------------------------------------------- 1 | # Download the software with GIT 2 | 3 | GitHub hosts the repository for all versions of WD. Presently, the current master provides version 3.2.3. The latest development version, 3.3.1, remains in a branch. 4 | 5 | ## Clone wsprdaemon from github.com 6 | 7 | From /home/wsprdaemon (or the installing user's home directory) [See Preparing the Installation](./preparation.md) 8 | ``` 9 | git clone https://github.com/rrobinett/wsprdaemon.git 10 | cd wsprdaemon 11 | ``` 12 | Execute all further git commands in the /home/wsprdaemon/wsprdaemon directory. 13 | 14 | Ensure you have the latest stable version: 15 | ``` 16 | git checkout master 17 | git status 18 | git log 19 | ``` 20 | 21 | Subsequently, to apply any updates of the latest version, use: 22 | ``` 23 | git pull 24 | ``` 25 | 26 | "master" generally refers to the latest stable version of the code. As development of the code proceeds, 27 | you may elect to switch to a development branch, e.g., 3.3.1. To do this, use: 28 | ``` 29 | git checkout 3.3.1 30 | git pull 31 | ``` 32 | 33 | 34 | WD provides lots of shell "aliases" to important and otherwise useful functions. To have immediate access to these, run: 35 | ``` 36 | source bash-aliases ../.bash_aliases 37 | ``` 38 | 39 | Having prepared and cloned the wsprdaemon software, now you can run it: 40 | ``` 41 | wd 42 | ``` 43 | 44 | This sets the stage and prompts you to configure your setup: 45 | - [wsprdaemon configuration](../configuration/wd_conf.md) 46 | - [radiod configuration](../configuration/radiod_conf.md) 47 | - KiwiSDR 48 | 49 | Once you have defined a new wsprdaemon.conf (or restored your previous one) then invoke a command like: 50 | ``` 51 | wdv 52 | ``` 53 | Nominally, this reports the current version of wsprdaemon but it will use wsprdaemon.conf to proceed with setting up wsprdaemon to run -- downloading and compiling any required software (e.g., ka9q-radio and ka9q-web), setting up the proper directories, etc. 54 | 55 | Finally, you can start wsprdaemon with: 56 | ``` 57 | wd -A 58 | ``` 59 | which will pipe all messages to stdout (the screen from which you invoked the command) which can help if something doesn't function properly. 60 | 61 | Or simply run: 62 | ``` 63 | wda 64 | ``` 65 | which starts wsprdaemon "quietly" piping all messages to log files. 66 | 67 | # To install ka9q-radio independently: 68 | 69 | ka9q-radio has many uses outside its integrated role with WD. You can install and run it without WD. 70 | Keep in mind that Rob checks out a particular version of ka9q-radio that he knows works with WD. 71 | So, if you use another version, you may find its interaction with WD problematic. YMMV. 72 | 73 | For details of ka9q-radio installation, consult the docs sub-directory in the ka9q-radio created after performing a `git clone`. 74 | 75 | - [KA9Q_RADIO_GIT_URL](https://github.com/ka9q/ka9q-radio.git) 76 | - [KA9Q_FT8_GIT_URL](https://github.com/ka9q/ft8_lib.git) 77 | - [PSK_UPLOADER_GIT_URL](https://github.com/pjsg/ftlib-pskreporter.git) 78 | 79 | ## To install ka9q-web: 80 | 81 | ka9q-web requires ka9q-radio of course, but also the web server package, onion, produced by David Moreno, so install it first. 82 | 83 | - [ONION_GIT_URL](https://github.com/davidmoreno/onion) 84 | 85 | Use the following bash scripts as scripts or just as a guide to installation: 86 | 87 | ``` 88 | declare ONION_LIBS_NEEDED="libgnutls28-dev libgcrypt20-dev cmake" 89 | if [[ ${OS_RELEASE} =~ 24.04 ]]; then 90 | ONION_LIBS_NEEDED="${ONION_LIBS_NEEDED} libgnutls30t64 libgcrypt20" 91 | fi 92 | 93 | function build_onion() { 94 | local project_subdir=$1 95 | local project_logfile="${project_subdir}-build.log" 96 | 97 | wd_logger 2 "Building ${project_subdir}" 98 | ( 99 | cd ${project_subdir} 100 | mkdir -p build 101 | cd build 102 | cmake -DONION_USE_PAM=false -DONION_USE_PNG=false -DONION_USE_JPEG=false -DONION_USE_XML2=false -DONION_USE_SYSTEMD=false -DONION_USE_SQLITE3=false -DONION_USE_REDIS=false -DONION_USE_GC=false -DONION_USE_TESTS=false -DONION_EXAMPLES=false -DONION_USE_BINDINGS_CPP=false .. 103 | make 104 | sudo make install 105 | sudo ldconfig 106 | ) >& ${project_logfile} 107 | rc=$? 108 | if [[ ${rc} -ne 0 ]]; then 109 | wd_logger 1 "ERROR: compile of '${project_subdir}' returned ${rc}:\n$( < ${project_logfile} )" 110 | exit 1 111 | fi 112 | wd_logger 2 "Done" 113 | return 0 114 | } 115 | ``` 116 | 117 | - [KA9Q_WEB_GIT_URL](https://github.com/scottnewell/ka9q-web) 118 | 119 | ``` 120 | function build_ka9q_web() { 121 | local project_subdir=$1 122 | local project_logfile="${project_subdir}_build.log" 123 | 124 | wd_logger 2 "Building ${project_subdir}" 125 | ( 126 | cd ${project_subdir} 127 | make 128 | sudo make install 129 | ) >& ${project_logfile} 130 | rc=$? ; if (( rc )); then 131 | wd_logger 1 "ERROR: compile of 'ka9q-web' returned ${rc}:\n$(< ${project_logfile})" 132 | exit 1 133 | fi 134 | wd_logger 2 "Done" 135 | return 0 136 | } 137 | ``` -------------------------------------------------------------------------------- /docs/source/maintenance/monitoring.md: -------------------------------------------------------------------------------- 1 | # Monitoring WD operation 2 | 3 | ## recordings 4 | 5 | ## postings 6 | 7 | ## logs 8 | 9 | ## using BTOP 10 | 11 | ![](../_images/btop.png) 12 | 13 | ## Useful wd aliases 14 | 15 | - *wd-query* -- usage: wd-query {-r | -t} ID [HOURS_TO_SEARCH] -r => search for ID of reporter -t => search for ID of transmit beacon" 16 | - *wd-wsprlog-check* 17 | - watch "ls -lt `find -name pcmrecord-errors.log`" 18 | - *wdrl* -- show the syslog entries for the radiod service. add -f to watch new log lines appear 19 | - *wdln* -- watch the upload to wsprnet log 20 | - *wdle* -- search all log files for ERROR lines -------------------------------------------------------------------------------- /docs/source/maintenance/operating.md: -------------------------------------------------------------------------------- 1 | # Operating WD 2 | 3 | ## Start and Stop 4 | 5 | After installation, there are two different ways to run WD. Each of these is invoked from the command line. 6 | 7 | The first method is the usual one where WD is run as a 'systemctl' service. In this mode it automatically starts when Linux boots or reboots after a power cycle. The command line (aliased) commands associated with this mode are 'wd -a' and 'wd -z'. 'wd -a' starts this mode while 'wd -z' terminates it. However, this method does not post system errors back to the command line. For this reason, when first verifying at startup or for verifying operation after wsprdaemon.conf has been modified, it can be useful to temporarily use the second mode. 8 | 9 | This second mode is invoked with 'wd -A' and terminated with 'wd -Z'. Note the capitalization differences. 'wd -A' invokes WD from the command line rather than automatically from the systemctl environment. This means that it does post errors back to the command line where they can be viewed. This mode is exited by typing 'wd -Z'. 10 | 11 | Once an installation and wsprdaemon.conf changes are verified, 'wd -Z' followed by 'wd -a' will make operation automatic. Any future changes should be made by first stopping this automatic operation and then temporarily using 'wd -A' to re-verify them followed by 'wd -Z' and 'wd -a' once they are acceptable. 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/source/network/basics.md: -------------------------------------------------------------------------------- 1 | # Basic Network Requirements 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/source/network/multicast.md: -------------------------------------------------------------------------------- 1 | # Multicast Requirements 2 | 3 | ![](../_images/IGMP_switch.png) 4 | 5 | -------------------------------------------------------------------------------- /docs/source/requirements/network.md: -------------------------------------------------------------------------------- 1 | # Networking 2 | 3 | WD using ka9q-radio typically runs on a stand-alone computer. In this scenario, a standard ethernet or WiFi connection to the computer will suffice for remote management and for reporting. 4 | 5 | However, ka9q-radio uses RTP (multicast) streams to manage interprocess communications. This means one can run ka9q-radio with a hardware radio on one computer and process the output on another. However, multicast streams produce significant network traffic -- enough to bring a WiFi network to a standstill. 6 | 7 | When running on a stand-alone computer, one should set the parameter ttl = 0 in radiod@.conf. This directs radiod to put no RTP streams on the LAN. When distributing the functions between computers, however, one sets ttl = 1 in radiod@.conf and, especially with a connected WiFi LAN, interposes an IGMP-aware ethernet switch (with IGMP snooping ON) between the computers using RTP and the rest of your network. This will confine the RTP streams to the connections between your radiod and WD computers so they don't flood the rest of your LAN. 8 | 9 | ![](../_images/IGMP_switch.png) -------------------------------------------------------------------------------- /docs/source/requirements/os.md: -------------------------------------------------------------------------------- 1 | # Operating Sytems 2 | 3 | I currently do most of my installation and run-time testing on the recently released Ubuntu 24.04 LTS server OS. Desktop Ubuntu includes a lot of software auto-upgrade features which can disrupt the operation of WD and other applications, so I suggest you avoid using the desktop version. If you do use it, then at least follow [Clint KA7OEI's "de-snap" instructions](http://www.sdrutah.org/info/websdr_Ubuntu_2204_install_notes.html#snapd) 4 | 5 | [Download Ubuntu server](https://ubuntu.com/download/server) and run the 'Raspberry Pi Imager' program on your PC to copy the Ubuntu.iso file to a 8 GB or larger USB thumb drive. 6 | 7 | In Imager select: 8 | 9 | Rasperry Pi Device => "NO FILTERING" Operating System => Use custom => browse to the Ubuntu image file you have downloaded to your PC Storage => the thumb drive 10 | 11 | You will then insert the USB thumb drive into your host and boot from that drive. Frequently you will need to press the DEL key or a function key (e.g. F7) immediately after power-up in order to instruct the BIOS of the server to boot from the thumb drive. 12 | 13 | I suggest that you create a user 'wsprdaemon' with sudo privileges after installation is complete. 14 | 15 | While WD can run on many different x86 and ARM CPU's, the RX888 is best run on a i5-6500T-class or newer x86 server. 16 | 17 | For new installations I have found the Beelink brand SER 5 Ryzen 5800U offers excellent price, performance and low power consumption for a WD system. Today Sept 9, 2024 Amazon offers it for $270 (after $18 discount) at [Amazon](https://www.amazon.com/Beelink-SER5-Computer-Graphics-Support/dp/B0D6G965BC), but the same Beelink may be offered at several different prices on Amazon, so search for price including 'discount coupons'. The Beelink Ser 5 5560U is another excellent choice which until today I was able to purchase for $219. Also consider the Ryzen 5800 series chips, (5800U, 5800H, 5825H) but avoid the 5700 series as these have a divided L3 cache which may introduce gaps in the USB stream as processing switches from one set of cores to another. 18 | 19 | Whatever server you choose, WD runs a little better on 16 GB of ram. 20 | 21 | The Beelink comes with Windows installed but WD runs on Linux, so I usually first install Windows and associate the Beelink with my Microsoft account to be sure the server hardware and software are functional, and in case I want to restore Windows on that server. 22 | 23 | Maximizing CPU performance on the Beelink requires that the 'always slow' default fan setting be changed in the BIOS to 'fan on a 40C, fan max at 60C, speed ramp 8'. The 'btop' program which I run to monitor the CPU usage displays CPU temperature among many other things. If it shows the CPU at much more than 60C during the 100% busy periods which start every even 2 minutes, then your fan is not running fast enough. 24 | 25 | I also find the 'power always on' setting deeply buried in the ACH sub menu. 26 | -------------------------------------------------------------------------------- /docs/source/requirements/radios.md: -------------------------------------------------------------------------------- 1 | # Compatible Radios 2 | 3 | Please note, for the present and in collaboration with HamSCI efforts, WD supports kiwiSDR and RX888. The documentation here also primarily addresses the configuration and use of WD with those two radios. 4 | 5 | ## KiwiSDR 6 | 7 | ## Radios that work with ka9q-radio 8 | 9 | ### RX888 10 | 11 | The RX888 connects to your computer via a USB 3 -- SuperSpeed connection. These typically have a BLUE tab on the computer socket as distinct from the white or black for USB 2. 12 | 13 | As delivered the RX888 has sub-optimal thermal protection and for radio science applications it needs an external GPSDO @ 27 MHz clock (although you can alter this). 14 | 15 | Paul Elliott WB6CXC created a screwdriver-only kit which enhances the thermal protection and adds a ground-isolated external clock SMA input port. Paul describes the installation and use of his kit on [his website](https://turnislandsystems.com/wp-content/uploads/2024/05/RX888-Kit-2.pdf) 16 | 17 | The kit is available at [the TAPR web store](https://tapr.org/product/rx888-clock-kit-and-thermal-pad/). 18 | 19 | ### Airspy variants 20 | 21 | - Airspy R2 22 | - Airspy HF+ 23 | 24 | ### RTL-SDR 25 | 26 | ### SDRPLAY variants 27 | 28 | Not directly suppported by radiod. 29 | - RSPduo 30 | - RSPdx 31 | 32 | ### FobosSDR 33 | 34 | ### Others... 35 | 36 | - Funcube Dongle 37 | - OpenHPSDR variants 38 | -------------------------------------------------------------------------------- /docs/source/results/grape.md: -------------------------------------------------------------------------------- 1 | # HamSCI WWV/H and CHU Monitoring 2 | 3 | WD software captures data in support of the [HamSCI Personal Space Weather Station project](https://hamsci.org/psws-overview). 4 | It makes simultaneous I/Q stream recordings of WWV, WWVH, and CHU broadcasts, converts these into digital_rf data repositories and uploads them to the [PSWS server at the University of Alabama](https://pswsnetwork.caps.ua.edu/). 5 | 6 | ![](../_images/psws.png) 7 | 8 | ![](../_images/wwv_obs.png) 9 | -------------------------------------------------------------------------------- /docs/source/results/psk.md: -------------------------------------------------------------------------------- 1 | # PSKReporter 2 | 3 | Managed by Philip Gladstone, the [website](https://pskreporter.info) aggregates lots of information. A WD/radiod installation uploads spots to this site. 4 | 5 | ![](../_images/pskreporter.png) -------------------------------------------------------------------------------- /docs/source/results/wspr.md: -------------------------------------------------------------------------------- 1 | # WSPR spots 2 | 3 | WSPR, which stands for Weak Signal Propagation Reporter, defines a protocol used in amateur radio for assessing radio signal propagation. It allows users to send and receive very low-power transmissions over long distances, that a receiving station then decodes and reports. Here are some key points about WSPR: 4 | 5 | **Purpose**: WSPR is primarily used for scientific and testing purposes, helping amateur radio operators analyze radio wave propagation conditions. 6 | 7 | **Operation**: WSPR operates on specific frequencies and uses a special encoding method for its transmissions. Unlike traditional radio communication, WSPR does not support two-way conversations; it focuses on sending short, beacon-like signals. 8 | 9 | **Implementation**: The WSPR protocol can be implemented in software, and it is compatible with various hardware setups, making it accessible for many amateur radio operators. 10 | 11 | **Community and Reporting**: WSPR signals are often received by automated software-defined radio (SDR) receivers, which can report back the signal's strength and other details via the Internet, contributing to a global database of propagation conditions. 12 | 13 | For more detailed information, you may refer to the [WSPR Wikipedia page](https://en.wikipedia.org/wiki/WSPR_(amateur_radio_software)). 14 | 15 | ## WSPR.Rocks 16 | 17 | Managed by Philip Barnard VK7JJ. 18 | Presents an interface to a Clickhouse DB courtesy of Arne at wspr.live and hosted at wsprdaemon.org. 19 | 20 | [wspr.rocks](https://wspr.rocks) 21 | 22 | ## WSPR.Live 23 | 24 | WSPR.live allows you to do analysis on the real-time wspr spot data. The database contains all spots ever reported to wsprnet.org and allows public access to the data. 25 | 26 | [wspr.live](https://wspr.live) 27 | 28 | ## WSPRnet 29 | 30 | Amateur radio operators using K1JT's MEPT_JT digital mode collaborate via the Weak Signal Propagation Reporter Network to probe radio frequency propagation conditions using very low power (QRP/QRPp) transmissions. The software is open source, and the data collected are available to the public through [this site](https://wsprnet.org/). 31 | 32 | 33 | -------------------------------------------------------------------------------- /docs/source/troubleshooting/overview.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting wsprdaemon 2 | 3 | Nothing ever goes wrong! 4 | 5 | ## Use tmux or screen 6 | 7 | Especially useful in managing a computer remotely. This will enable you to maintain a session between logins or should your network connection drop. 8 | 9 | ## Use btop! 10 | 11 | ![](../_images/btop.png) 12 | 13 | ## Use wdln 14 | 15 | This displays the wsprnet upload log. 16 | 17 | ## Use wdle 18 | 19 | ## Check recordings 20 | 21 | Change to the temporary directory. 22 | 23 | ``` 24 | cdt 25 | ``` 26 | Then enter the sub-directory for a receiver and channel, for instance, the following moves to receiver KA9Q_0 and a 20m wspr channel: 27 | ``` 28 | cd recording.d/KA9Q_0/20/ 29 | ``` 30 | Here, you can invoke the wdww alias that dynamically lists the wav files for that channel. A problem exists if you see no files or don't see regular incrementing of the latest file size. 31 | 32 | ``` 33 | wdww 34 | ``` 35 | 36 | ## Check the logs 37 | 38 | You can locate logs using something like the following command: 39 | ``` 40 | find . -type f -name "*.log" ! -name "*sox.log" ! -name "*error.log" 41 | ``` 42 | where: 43 | - "." indicates starting the search from the current directory 44 | - "-type" directs the search only for files 45 | - "-name" specifies the filename of interest 46 | - "! -name" specifies names to exclude 47 | 48 | ### logs in /var/log 49 | 50 | - /var/log/wspr.log 51 | - /var/log/ft8.log 52 | - /var/log/ft4.log 53 | 54 | ### logs in ~/wsprdaemon 55 | 56 | - ./wav-archive.d/grape_upload_daemon.log 57 | - ./ka9q-radio_build.log 58 | - ./grep.log 59 | - ./ps.log 60 | - ./diff.log 61 | - ./git.log 62 | - ./uploads.d/wsprdaemon.d/upload_to_wsprdaemon_daemon.log 63 | - ./uploads.d/wsprnet.d/spots.d/upload_to_wsprnet_daemon.log 64 | - ./ft8_lib_build.log 65 | - ./onion-build.log 66 | - ./noise_plot.log 67 | - ./ka9q_web_daemon.log 68 | - ./ka9q-web_build.log 69 | - ./watchdog_daemon.log 70 | - ./ka9q_web_service_8081.log 71 | 72 | ### logs in /dev/shm/wsprdaemon 73 | 74 | Note: the 2nd and 3rd subdirectories will vary according to your receivers and channels. 75 | 76 | - ./recording.d/KA9Q_LONGWIRE/80eu/posting_daemon.log 77 | - ./recording.d/KA9Q_LONGWIRE/80eu/decoding_daemon.log 78 | - ./recording.d/KA9Q_LONGWIRE/80eu/find.log 79 | - ./recording.d/KA9Q_LONGWIRE/80eu/metadump.log 80 | - ./recording.d/KA9Q_LONGWIRE/80eu/ka9q_status.log 81 | - ./recording.d/KA9Q_LONGWIRE/80eu/sox-stats.log 82 | - ./recording.d/KA9Q_LONGWIRE/80eu/adc_overloads.log 83 | - ./recording.d/KA9Q_LONGWIRE/80eu/sox.log 84 | - ./recording.d/KA9Q_LONGWIRE/80eu/get-peak-wav-sample.log 85 | - ./recording.d/KA9Q_LONGWIRE/80eu/W_120/decoding_daemon.log 86 | - ./recording.d/KA9Q_LONGWIRE/80eu/wav_status.log 87 | - ./recording.d/KA9Q_LONGWIRE/80eu/add_derived.log 88 | - ./recording.d/KA9Q_LONGWIRE/80eu/printf.log 89 | 90 | - ./recording.d/KA9Q_LONGWIRE_WWV/wav-record-daemon-all.log 91 | - ./recording.d/KA9Q_LONGWIRE_WWV/pcmrecord-errors.log 92 | - ./recording.d/KA9Q_LONGWIRE_WWV/pcmrecord-outs.log 93 | 94 | - ./uploads.d/wsprdaemon.d/grep.log 95 | - ./uploads.d/wsprnet.d/curl.log 96 | -------------------------------------------------------------------------------- /docs/source/troubleshooting/typicals.md: -------------------------------------------------------------------------------- 1 | # Some typical problems 2 | 3 | ## Consider usb loading issues. 4 | - is the RX888 plugged into a USB3 superspeed socket? 5 | - has radiod successfully loading the firmware to the RX888? 6 | 7 | ## Consider system service issues 8 | - systemctl service enabled? 9 | - configured to always restart on boot? 10 | - does your computer or OS support what you want the RX888 or wsprdaemon to do? 11 | - for Beelink, set the fan to 'auto' with the fan turning on at 40C and getting to max speed at 60C with at ramp of '8' 12 | - have you configured the computer to resume function after loss/restoration of power? 13 | 14 | ## Consider avahi address translation issues 15 | - avahi installed (see pre-requisite libraries) 16 | 17 | ## Consider ethernet hubs/architecture 18 | 19 | - ttl = 0 by default (assuming a stand-alone setup) so as not to flood the LAN or WLAN with multicast packets 20 | - if using multicast between computers, ttl = 1 required on sending computer and on the right NIC 21 | - multicast will require an IGMP-capable switch with snooping ON to isolate the computers using radiod multicast from the rest of your LAN or WLAN, particularly if your LAN uses WiFi. 22 | - have you defined and enabled a device in radiod@rx888-XXX.conf? 23 | 24 | ## Brute force recovery 25 | 26 | One method of recovery involves, in effect, starting from (almost) scratch. 27 | 28 | The important "almost" refers to preserving your configuration. DON'T FORGET THIS! 29 | 30 | First, critically, copy your ~/wsprdaemon/wsprdaemon.conf file to somewhere safe, e.g., the home directory: 31 | ``` 32 | cp ~/wsprdaemon/wsprdaemon.conf ~ 33 | ``` 34 | Then delete the wsprdaemon subdirectory: 35 | ``` 36 | rm -rf ~/wsprdaemon/ 37 | ``` 38 | 39 | Clean out /shm/wsprdaemon: 40 | ``` 41 | rm -rf /shm/wsprdaemon/* 42 | ``` 43 | 44 | Clone the WD repository (while in /home/wsprdaemon/): 45 | ``` 46 | git clone https://github.com/rrobinett/wsprdaemon.git 47 | ``` 48 | 49 | Copy the wsprdaemon.conf back into the new ~/wsprdaemon: 50 | ``` 51 | cp ~/wsprdaemon.conf ~/wsrdaemon 52 | ``` 53 | 54 | Run wd to get everything built and installed: 55 | ``` 56 | wd 57 | ``` 58 | 59 | Restart WD: 60 | ``` 61 | wda 62 | ``` 63 | 64 | Check the usual places (see above) to ensure things are functioning as expected. -------------------------------------------------------------------------------- /get-peak-wav-sample.py: -------------------------------------------------------------------------------- 1 | import soundfile as sf 2 | import numpy as np 3 | import argparse 4 | 5 | # Set up argument parser 6 | parser = argparse.ArgumentParser(description="Find the maximum sample value and its dBFS value in a list of audio files.") 7 | parser.add_argument("files", nargs='+', help="Paths to the input WAV files") 8 | args = parser.parse_args() 9 | 10 | max_sample_value = -np.inf # Initialize to the smallest possible value 11 | 12 | # Process each file 13 | for file_path in args.files: 14 | try: 15 | # Read the audio file 16 | data, samplerate = sf.read(file_path) 17 | 18 | # Flatten the data if it's multi-channel 19 | if data.ndim > 1: 20 | data = data.flatten() 21 | 22 | # Update the maximum sample value 23 | file_max = np.max(np.abs(data)) 24 | max_sample_value = max(max_sample_value, file_max) 25 | 26 | # print(f"File: {file_path}, Max Sample: {file_max:.12f}") 27 | 28 | except Exception as e: 29 | print(f"Error processing {file_path}: {e}") 30 | 31 | # Compute dBFS for the overall maximum sample 32 | if max_sample_value > -np.inf: 33 | overall_dbfs = 20 * np.log10(max_sample_value) if max_sample_value > 0 else -float('inf') 34 | # print(f"\nOverall Max Sample Value (Linear): {max_sample_value:.12f}") 35 | # print(f"Overall Max Sample Value (dBFS): {overall_dbfs:.12f} dBFS") 36 | print(f"{max_sample_value:.12f}") 37 | print(f"{overall_dbfs:.12f}") 38 | else: 39 | print("\nNo valid files processed.") 40 | 41 | -------------------------------------------------------------------------------- /ka9q-ft-cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script is run by cron ever 10 minutes to cleanup old wav files 4 | # and truncate ftX.log files which have grown too large 5 | 6 | declare -r GET_FILE_SIZE_CMD="stat --format=%s" 7 | declare -r LOG_FILE_MAX_SIZE_BYTES=1000000 8 | function truncate_file() { 9 | local file_path=$1 ### Must be a text format file 10 | local file_max_size=$2 ### In bytes 11 | local file_size=$( ${GET_FILE_SIZE_CMD} ${file_path} ) 12 | 13 | [[ $verbosity -ge 3 ]] && echo "$(date): truncate_file() '${file_path}' of size ${file_size} bytes to max size of ${file_max_size} bytes" 14 | 15 | if [[ ${file_size} -gt ${file_max_size} ]]; then 16 | local file_lines=$( cat ${file_path} | wc -l ) 17 | local truncated_file_lines=$(( ${file_lines} / 2)) 18 | local tmp_file_path="${file_path%.*}.tmp" 19 | tail -n ${truncated_file_lines} ${file_path} > ${tmp_file_path} 20 | mv ${tmp_file_path} ${file_path} 21 | local truncated_file_size=$( ${GET_FILE_SIZE_CMD} ${file_path} ) 22 | [[ $verbosity -ge 1 ]] && echo "$(date): truncate_file() '${file_path}' of original size ${file_size} bytes / ${file_lines} lines now is ${truncated_file_size} bytes" 23 | fi 24 | } 25 | 26 | cd /dev/shm/ka9q-radio 27 | 28 | declare old_wav_file_name_list=( $(find . -type f -name '*wav' -mmin +30) ) 29 | for old_wav_file_name in ${old_wav_file_name_list[@]}; do 30 | [[ $verbosity -ge 1 ]] && echo "$(date): deleting old_wav_file_name=${old_wav_file_name}" 31 | rm -f ${old_wav_file_name} 32 | done 33 | 34 | declare log_file_name_list=( $(find -type f -name '*.log') ) 35 | for log_file_name in ${log_file_name_list[@]}; do 36 | truncate_file ${log_file_name} ${LOG_FILE_MAX_SIZE_BYTES} 37 | done 38 | cd - > /dev/null 39 | 40 | 41 | -------------------------------------------------------------------------------- /noise_graphs_reporter_index_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Noise Analysis 4 | 5 | 6 | 7 | Noise Graphics 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /noise_graphs_root_index_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Wsprdaemon Noise Graphs 4 | 5 | 6 | 7 | Noise Graphics 8 | Noise Graphics 9 | Noise Graphics 10 | Noise Graphics 11 | Noise Graphics 12 | Noise Graphics 13 | 14 | 15 | -------------------------------------------------------------------------------- /one-minute-silent-float.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/one-minute-silent-float.wv -------------------------------------------------------------------------------- /radiod@rx888-wsprdaemon-template.conf: -------------------------------------------------------------------------------- 1 | # Generic minimal RX888-wsprdaemon config 2 | 3 | [global] 4 | hardware = rx888 # use built-in rx888 driver, configured in [rx888] 5 | status = hf-status.local # DNS name for receiver status and commands 6 | data = hf-data.local 7 | samprate = 12000 # default output sample rate 8 | mode = usb # default receive mode 9 | # rest are defaults 10 | #ttl = 1 11 | ttl = 0 # Too many WD sites don't have IGMP aware ethernet swtiches, so don't send radiod multicast packets out the ethernet port. 12 | fft-threads = 1 13 | #iface = enp1s0 14 | 15 | [rx888] 16 | device = "rx888" # required so it won't be seen as a demod section 17 | description = "rx888 wsprdaemon" # good to put callsign and antenna description in here 18 | gain = 0 # dB 19 | # rest are defaults 20 | #samprate = 129600000 # Hz 21 | samprate = 64800000 # 128 Msps will eventual burn out the stock RX888 Mk II, and this 64 Msps frees much CPU on older CPUs 22 | 23 | [WSPR] 24 | # Bottom of 200 Hz WSPR segments on each band. Center is 1500 Hz higher 25 | # sample rate must be 12 kHz as required by wsprd 26 | disable = no 27 | encoding = float 28 | data = wspr-pcm.local 29 | agc = 0 30 | gain = 0 31 | samprate = 12000 32 | mode = usb 33 | low = 1300 34 | high = 1700 35 | freq = "136k000 474k200 1m836600 3m568600 3m592600 5m287200 5m364700 7m038600 10m138700 13m553900 14m095600 18m104600 21m094600 24m924600 28m124600 50m293000" 36 | 37 | [FT8] 38 | disable = no 39 | data = ft8-pcm.local 40 | mode = usb 41 | freq = "1m840000 3m573000 5m357000 7m074000 10m136000 14m074000 18m100000 21m074000 24m915000 28m074000 50m313000" 42 | 43 | [FT4] 44 | disable = no 45 | data = ft4-pcm.local 46 | mode = usb 47 | freq = "3m575000 7m047500 10m140000 14m080000 18m104000 21m140000 24m919000 28m180000 50m318000" 48 | 49 | [WWV-IQ] 50 | disable = no 51 | encoding = float 52 | data = wwv-iq.local 53 | agc = 0 54 | gain = 0 55 | samprate = 16k 56 | mode = iq 57 | freq = "60k000 2500000 5000000 10000000 15000000 20000000 25000000 3330000 7850000 14670000" ### Added the three CHU frequencies 58 | 59 | [HF MANUAL] 60 | data = hf-pcm.local 61 | freq = 0 62 | -------------------------------------------------------------------------------- /show-memory-usage.sh: -------------------------------------------------------------------------------- 1 | declare pid_file_list=() 2 | declare pid_last_values_file="show-memory-usage.last" 3 | declare pid_new_values_file="show-memory-usage.new" 4 | 5 | touch ${pid_last_values_file} 6 | 7 | function get_daemon_pid_list() 8 | { 9 | pid_file_list=($(find ~/wsprdaemon /tmp/wsprdaemon -name '*.pid') ) 10 | } 11 | 12 | function get_mem_usage() 13 | { 14 | local mem_total=0 15 | > ${pid_new_values_file} 16 | get_daemon_pid_list 17 | for pid_file in ${pid_file_list[@]} ; do 18 | local pid_val=$(< ${pid_file} ) 19 | if ps ${pid_val} > /dev/null ; then 20 | local pid_rss_val=$(awk -v pid_file=${pid_file} '/VmRSS/{printf "%s\n", $2}' /proc/${pid_val}/status) 21 | # printf "PID %6s VmRSS %6s from pid file %s\n" ${pid_val} ${pid_rss_val} ${pid_file} 22 | local old_rss_val=$( grep "${pid_file}" ${pid_last_values_file} | awk '{print $1}') 23 | if [[ "${pid_rss_val}" -ne "${old_rss_val}" ]]; then 24 | printf "For PID %8s old=%8s new=%8s from file %s\n" "${pid_val}" "${old_rss_val}" "${pid_rss_val}" "${pid_file}" 25 | fi 26 | printf "%8d %s\n" ${pid_rss_val} ${pid_file} >> ${pid_new_values_file} 27 | mem_total=$(( mem_total + pid_rss_val)) 28 | else 29 | echo "pid file ${pid_file} contains pid # ${pid_val} which isn't active" 30 | rm ${pid_file} 31 | fi 32 | done 33 | local output_str="$(date): Found ${#pid_file_list[@]} pid files with a VmRSS total of ${mem_total}" 34 | echo "${output_str}" >> show-memory-usage.txt 35 | echo "${output_str}" 36 | } 37 | while true; do 38 | get_mem_usage 39 | sleep 10 40 | done 41 | -------------------------------------------------------------------------------- /silent_iq.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrobinett/wsprdaemon/71d90d1563fcb2c9f07d2aa53f2150326229aa63/silent_iq.flac -------------------------------------------------------------------------------- /suntimes.py: -------------------------------------------------------------------------------- 1 | from math import cos,sin,acos,asin,tan, floor 2 | from math import degrees as deg, radians as rad , pi as pi 3 | from datetime import date,datetime,time,timezone,timedelta 4 | import calendar 5 | import sys 6 | from numpy import deg2rad 7 | import subprocess 8 | 9 | # Gwyn Griffiths G3ZIL 2 September 2023 V2 10 | # Basic principles sunrise and sunset calculator needs lat and lon as the two input arguments 11 | # Extracts timezone for the local computer using timedatectl as an operating system command 12 | # Equations from NOAA at https://gml.noaa.gov/grad/solcalc/solareqns.PDF 13 | # Watch out here - the trig functions have mix of degrees and radian inputs so explicit conversion used where needed 14 | # Error check for perpetual day or night and times are outputin the 'except' block 15 | # V2 has no timezone argument, calculates from call to operating system executable timedatectl 16 | # G3ZIL checking code 30 April 2025 for any numerical error 17 | 18 | lat=float(sys.argv[1]) 19 | lon=float(sys.argv[2]) 20 | 21 | date_offset=0 22 | 23 | # calculate day of year 24 | today=datetime.now(timezone.utc)+timedelta(days=date_offset) # get today's date UTC timezone 25 | day_of_year=int(today.strftime('%j')) # day of year as integer 26 | year=int(today.strftime('%Y')) # year as integer 27 | 28 | # get number of days in year 29 | if(calendar.isleap(year)): 30 | n_days=366 31 | else: 32 | n_days=365 33 | 34 | # get hour 35 | hour=int(today.strftime('%H')) # hour as zero padded integer 36 | 37 | # calculate fractional year gamma where whole year is two pi, so gamma is in radians, fine for trig functions below 38 | gamma=((2*pi)/n_days)*(day_of_year-1+(hour-12)/24) 39 | # calculate equation of time in minutes 40 | eqtime=229.18*(0.000075+0.001868*cos(gamma)-0.032077*sin(gamma)-0.014615*cos(2*gamma)-0.040849*sin(2*gamma)) 41 | 42 | # calculate solar declination angle in radians 43 | decl=0.006918-0.399912*cos(gamma)+0.070257*sin(gamma)-0.006758*cos(2*gamma)+0.000907*sin(2*gamma)-0.002697*cos(3*gamma)+0.00148*sin(3*gamma) 44 | 45 | # calculate timezone offset in integer hours from longitude 46 | tz_offset = float(subprocess.check_output("timedatectl | awk '/Time/{print substr($5,1,3)}'", shell=True, universal_newlines=True)) 47 | 48 | #print ("time zone offset from timedatectl ", tz_offset) 49 | 50 | # use the try feature as error trap for polar night and day 51 | try: 52 | # Sunrise/Sunset Calculations 53 | # For the special case of sunrise or sunset, the zenith is set to 90.833 (the approximate correction for 54 | # atmospheric refraction at sunrise and sunset, and the size of the solar disk), and the hour angle 55 | # becomes: 56 | deg2rad=360/(2*pi) 57 | ha_sunrise=acos((cos(90.833/deg2rad)/(cos(lat/deg2rad)*cos(decl)))-tan(lat/deg2rad)*tan(decl))*deg2rad 58 | ha_sunset=-acos((cos(90.833/deg2rad)/(cos(lat/deg2rad)*cos(decl)))-tan(lat/deg2rad)*tan(decl))*deg2rad 59 | 60 | #Then the UTC time of sunrise (or sunset) in minutes is: 61 | sunrise = 720-4*(lon+ha_sunrise)-eqtime 62 | sunset = 720-4*(lon+ha_sunset)-eqtime # was +, an error, corrected 30 April 2025, the error was about 2 minutes, but see major bug below 63 | hour_sunrise=int((floor(sunrise/60)+tz_offset) % 24) 64 | hour_sunset=int((floor(sunset/60)+tz_offset) % 24) 65 | min_sunrise=int(sunrise % 60) 66 | min_sunset=int(sunset % 60) 67 | print("{:02d}".format(hour_sunrise),":", "{:02d}".format(min_sunrise)," ", "{:02d}".format(hour_sunset),":", "{:02d}".format(min_sunset), sep='') 68 | # above print line had an error in that the print for min_sunset was printing the variable min_sunrise. Corrected 30 April 2025 G3ZIL 69 | 70 | except ValueError as e: 71 | if (('math domain error') in str (e)): # exception raised 72 | if (lat> 60) and (100 <= day_of_year <=260): # Northern hemisphere summer 73 | print('00:00 23:59') # it is light all day 74 | elif (lat >60) and (1 <= day_of_year <=80 or 280 <= day_of_year <366): # Northern hemisphere winter 75 | print('00:00 00:01') # it is dark all day 76 | elif (lat< -60) and (100 <= day_of_year <=260): # Southern hemisphere winter 77 | print('00:00 00:01') # it is dark all day 78 | elif (lat <-60) and (1 <= day_of_year <=80 or 280 <= day_of_year <366): # Southern hemisphere summer 79 | print('00:00 23:59') # it is light all day 80 | else: 81 | print ('Should never get here given latitude and day of year') 82 | -------------------------------------------------------------------------------- /ts_batch_upload.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #!/usr/bin/python 3 | # March-May 2020 Gwyn Griffiths 4 | # ts_batch_upload.py a program to read in a spots file scraped from wsprnet.org by scraper.sh and upload to a TimescaleDB 5 | # Version 1.2 May 2020 batch upload from a parsed file. Takes about 1.7s compared with 124s for line by line 6 | # that has been pre-formatted with an awk line to be in the right order and have single quotes around the time and character fields 7 | # Added additional diagnostics to identify which part of the upload fails (12 in 1936 times) 8 | import psycopg2 # This is the main connection tool, believed to be written in C 9 | import psycopg2.extras # This is needed for the batch upload functionality 10 | import csv # To import the csv file 11 | import sys # to get at command line argument with argv 12 | import argparse 13 | import logging 14 | 15 | # initially set the connection flag to be None 16 | conn=None 17 | connected="Not connected" 18 | cursor="No cursor" 19 | execute="Not executed" 20 | commit="Not committed" 21 | ret_code=0 22 | 23 | def ts_batch_upload(batch_file, sql, connect_info): 24 | global conn, connected, cursor, execute, commit, ret_code 25 | try: 26 | with batch_file as csv_file: 27 | csv_data = csv.reader(csv_file, delimiter=',') 28 | # connect to the PostgreSQL database 29 | logging.debug("Trying to connect") 30 | conn = psycopg2.connect(connect_info) 31 | connected = "Connected" 32 | logging.debug("Appear to have connected") 33 | # create a new cursor 34 | cur = conn.cursor() 35 | cursor = "Got cursor" 36 | # execute the INSERT statement 37 | psycopg2.extras.execute_batch(cur, sql, csv_data) 38 | execute = "Executed" 39 | logging.debug("After the execute") 40 | # commit the changes to the database 41 | conn.commit() 42 | commit = "Committed" 43 | # close communication with the database 44 | cur.close() 45 | logging.debug("%s %s %s %s" % (connected, cursor, execute, commit) ) 46 | except: 47 | logging.error("Unable to record spot file to the database: %s %s %s %s" % (connected, cursor, execute, commit)) 48 | ret_code=1 49 | finally: 50 | if conn is not None: 51 | conn.close() 52 | sys.exit(ret_code) 53 | 54 | if __name__ == "__main__": 55 | parser = argparse.ArgumentParser(description='Upload WSPRNET spots to Timescale DB') 56 | parser.add_argument("-i", "--input", dest="spotsFile", help="FILE is a CSV containing WSPRNET spots", metavar="FILE", required=True, nargs='?', type=argparse.FileType('r'), default=sys.stdin) 57 | parser.add_argument("-s", "--sql", dest="sqlFile", help="FILE is a SQL file containing an INSERT query", metavar="FILE", required=True, type=argparse.FileType('r'), default="insert-spots.sql") 58 | parser.add_argument("-a", "--address", dest="address", help="ADDRESS is the hostname of the Timescale DB", metavar="ADDRESS", required=False, default="localhost") 59 | parser.add_argument("-o", "--ip_port", dest="ip_port", help="The IP port of the Timescale DB", metavar="IPPORT", required=False, default="5432") 60 | parser.add_argument("-d", "--database", dest="database", help="DATABASE is the database name in Timescale DB", metavar="DATABASE", required=True, default="wsprnet") 61 | parser.add_argument("-u", "--username", dest="username", help="USERNAME is the username to use with Timescale DB", metavar="USERNAME", required=True, default="wsprnet") 62 | parser.add_argument("-p", "--password", dest="password", help="PASSWORD is the password to use with Timescale DB", metavar="PASSWORD", required=True, default="secret") 63 | parser.add_argument("--log", dest="log", help="The Python logging module's log level to use", type=lambda x: getattr(logging, x), required=False, default=logging.INFO) 64 | args = parser.parse_args() 65 | 66 | logging.basicConfig(level=args.log) 67 | 68 | with args.sqlFile as sql_file: 69 | sql = sql_file.read().strip() 70 | 71 | connect_info="dbname='%s' user='%s' host='%s' port='%s' password='%s'" % (args.database, args.username, args.address, args.ip_port, args.password) 72 | logging.debug(connect_info) 73 | ts_batch_upload(batch_file=args.spotsFile, sql=sql, connect_info=connect_info) 74 | -------------------------------------------------------------------------------- /ts_insert_wd_noise.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO wsprdaemon_noise_s (time, site, receiver, rx_grid, band, rms_level, c2_level, ov) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) 2 | -------------------------------------------------------------------------------- /ts_insert_wd_spots.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO wsprdaemon_spots_s 2 | (time, sync_quality, "SNR", dt, freq, tx_call, tx_grid, "tx_dBm", drift, decode_cycles, jitter, blocksize, metric, osd_decode, ipass, nhardmin, mode, rms_noise, c2_noise, band, rx_grid, rx_id, km, rx_az, rx_lat, rx_lon, tx_az, tx_lat, tx_lon, v_lat, v_lon, ov_count, wsprnet_info, receiver ) 3 | VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) 4 | 5 | -------------------------------------------------------------------------------- /ts_insert_wn_spots.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO spots (wd_time, "Spotnum", "Date", "Reporter", "ReporterGrid", "dB", "MHz", "CallSign", "Grid", "Power", "Drift", distance, azimuth, "Band", version, code, 2 | wd_band, wd_c2_noise, wd_rms_noise, wd_rx_az, wd_rx_lat, wd_rx_lon, wd_tx_az, wd_tx_lat, wd_tx_lon, wd_v_lat, wd_v_lon ) 3 | VALUES( %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s ) -------------------------------------------------------------------------------- /ts_noise.awk: -------------------------------------------------------------------------------- 1 | NF == 15 { 2 | no_head=FILENAME 3 | 4 | n = split (FILENAME, path_array, /\//) 5 | call_grid=path_array[n-3] 6 | 7 | split (call_grid, call_grid_array, "_") 8 | site=call_grid_array[1] 9 | gsub(/=/,"/",site) 10 | rx_grid=call_grid_array[2] 11 | 12 | receiver=path_array[4] 13 | band=path_array[5] 14 | time_freq=path_array[6] 15 | 16 | split (time_freq, time_freq_array, "_") 17 | date=time_freq_array[1] 18 | split (date,date_array,"") 19 | date_ts="20"date_array[1]date_array[2]"-"date_array[3]date_array[4]"-"date_array[5]date_array[6] 20 | time=time_freq_array[2] 21 | split (time, time_array,"") 22 | time_ts=time_array[1]time_array[2]":"time_array[3]time_array[4] 23 | 24 | rms_level=$13 25 | c2_level=$14 26 | ov=$15 27 | #printf "time='%s:%s' \nsite='%s' \nreceiver='%s' \nrx_grid='%s' \nband='%s' \nrms_level:'%s' \nc2_level:'%s' \nov='%s'\n", date_ts, time_ts, site, receiver, rx_grid, band, rms_level, c2_level, ov 28 | printf "%s:%s,%s,%s,%s,%s,%s,%s,%s\n", date_ts, time_ts, site, receiver, rx_grid, band, rms_level, c2_level, ov 29 | } 30 | -------------------------------------------------------------------------------- /wav2grape.conf: -------------------------------------------------------------------------------- 1 | # config file for wav2grape 2 | 3 | [global] 4 | channel name = ch0 5 | subdir cadence secs = 86400 6 | file cadence millisecs = 3600000 7 | compression level = 9 8 | 9 | [subchannels] 10 | WWVB = 0.06 11 | WWV_2_5 = 2.5 12 | WWV_5 = 5 13 | WWV_10 = 10 14 | WWV_15 = 15 15 | WWV_20 = 20 16 | WWV_25 = 25 17 | CHU_3 = 3.33 18 | CHU_7 = 7.85 19 | CHU_14 = 14.67 20 | K_BEACON_5 = 5.1543 21 | K_BEACON_7_3 = 7.0393 22 | K_BEACON_7_4 = 7.0394 23 | 24 | # you can have additional metadata for a specific site and receiver 25 | #[AC0G_EM38ww] 26 | #site_property = abc 27 | # 28 | #[AC0G_EM38ww KA9Q_0_WWV_IQ] 29 | #receiver_property = def 30 | 31 | [KFS_SW] 32 | gpsdo = yes 33 | 34 | -------------------------------------------------------------------------------- /wav2grape.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # go from wav files to Grape: 3 | # - convert from wav to Digital RF dataset 4 | # - upload Digital RF dataset directory to PSWS network 5 | # 6 | # Franco Venturi - K4VZ - Mon 22 Jan 2024 01:20:14 PM UTC 7 | # Rob Robinett - AI6VN - Mon Jan 30 2024 modified to be part of a Wsprdaemon client 8 | 9 | set -euo pipefail 10 | 11 | WSPRDAEMON_ROOT_DIR=${WSPRDAEMONM_ROOT_DIR-~/wsprdaemon} 12 | 13 | declare -r PSWS_SERVER_URL='pswsnetwork.caps.ua.edu' 14 | declare -r UPLOAD_TO_PSWS_SERVER_COMPLETED_FILE_NAME='pswsnetwork_upload_completed' 15 | declare -r GRAPE_TMP_DIR="/run/user/$(id -u)/grape_drf_cache" 16 | declare -r WAV2GRAPE_PYTHON_CMD="${WSPRDAEMON_ROOT_DIR}/wav2grape.py" 17 | 18 | 19 | ### Given: the path to the .../wav-archive.d//_ directory under which there may be one or more receivers with 24 hour wav files which have not 20 | ### been converted to DRF and uploaded to the GRAPE server 21 | ### Returns: 0 on nothing to do or success on uploading 22 | 23 | declare WD_TEST_RX_DIR=~/wsprdaemon/wav-archive.d/20240128/KFS=Q_CM87tj/KA9Q_Omni_WWV_IQ@S000199_999 24 | 25 | function upload_24hour_wavs_to_grape_drf_server() { 26 | local reporter_wav_root_dir=$( realpath $1 ) 27 | # reporter_wav_root_dir=${WD_TEST_RX_DIR} 28 | 29 | local reporter_upload_complete_file_name="${reporter_wav_root_dir}/${UPLOAD_TO_PSWS_SERVER_COMPLETED_FILE_NAME}" 30 | 31 | if [[ -f ${reporter_upload_complete_file_name} ]]; then 32 | echo "File ${reporter_upload_complete_file_name} exists, so upload of wav files has already been successful" 33 | return 0 34 | fi 35 | ### On the WD client the flac and 24hour.wav files are cached in the non-volitile file system which has the format: 36 | ### ...../wsprdaemon/wav-archive.d//_/@_/ 37 | ### WSPR_REPORTER_ID, WSPR_REPORTER_GRID and WD_RECEIVER and WD_RECEIVER_NAME are assigned by the WD client and entered into the wsprdaemon.conf file 38 | ### Each WD client can support multiple WSPR_REPORTER_IDs, each of which can have the same or a unique WSPR_REPORTER_GRID 39 | ### Each WSPR_REPORTER_ID+WSPR_REPORTER_GRID is associated with one or more WSPR_RECIEVER_NAMEs, and each of those will support one or more BANDS 40 | ### 41 | local dir_path_list=( ${reporter_wav_root_dir//\// } ) 42 | local wav_date=${dir_path_list[-2]} 43 | local reporter_info=${dir_path_list[-1]} 44 | local reporter_id=${reporter_info%_*} ### Chop off the _GRID to get the WSPR reporter id 45 | local reporter_grid=${reporter_info#*_} ### Chop off the REPROTER_ID to get the grid 46 | 47 | ### Search each receiver for wav files 48 | local receiver_dir 49 | local receiver_dir_list=( $(find "${reporter_wav_root_dir}" -mindepth 1 -maxdepth 1 -type d -not -name '*mutex.lock' | sort ) ) 50 | if [[ ${#receiver_dir_list[@]} -eq 0 ]]; then 51 | echo "There are no receiver dirs under ${reporter_wav_root_dir}" 52 | return 1 53 | fi 54 | for receiver_dir in ${receiver_dir_list[@]} ; do 55 | local receiver_info="${receiver_dir##*/}" 56 | local receiver_name="${receiver_info%@*}" 57 | local pswsnetwork_info="${receiver_info#*@}" 58 | local psws_station_id="${pswsnetwork_info%_*}" 59 | local psws_instrument_id="${pswsnetwork_info#*_}" 60 | echo "Processing ${receiver_dir}: 61 | date: ${wav_date}- site: ${reporter_id} - receiver_name: $receiver_name - psws_station_id: $psws_station_id - psws_instrument_id: $psws_instrument_id" 1>&2 62 | rm -rf ${GRAPE_TMP_DIR}/* 63 | umask 022 64 | local receiver_tmp_dir="$("$WAV2GRAPE_PYTHON_CMD" -i "$receiver_dir" -o "$GRAPE_TMP_DIR")" 65 | echo "DRF files can be found in ${receiver_tmp_dir}. Now upload them" 66 | # upload to PSWS network 67 | ( 68 | cd "$(dirname "$receiver_tmp_dir")" 69 | { 70 | echo "put -r ."; 71 | echo "mkdir c$(basename "$receiver_tmp_dir")\#$psws_instrument_id\#$(date -u +%Y-%m-%dT%H-%M)"; 72 | } | sftp -b - "$psws_station_id"@"$PSWS_SERVER_URL" 73 | ) 74 | rm -r "$receiver_tmp_dir" 75 | done 76 | echo touch "${reporter_upload_complete_file_name}" 77 | } 78 | 79 | upload_24hour_wavs_to_grape_drf_server $1 80 | -------------------------------------------------------------------------------- /wav_window.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Filename: wav_window_v1.py 4 | # January 2020 Gwyn Griffiths 5 | # Program to apply a Hann window to a wsprdaemon wav file for subsequent processing by sox stat -freq (initially at least) 6 | 7 | from __future__ import print_function 8 | import math 9 | import scipy 10 | import scipy.io.wavfile as wavfile 11 | import numpy as np 12 | import wave 13 | import sys 14 | 15 | WAV_INPUT_FILENAME=sys.argv[1] 16 | WAV_OUTPUT_FILENAME=sys.argv[2] 17 | 18 | # Set up the audio file parameters for windowing 19 | # fs_rate is passed to the output file 20 | fs_rate, signal = wavfile.read(WAV_INPUT_FILENAME) # returns sample rate as int and data as numpy array 21 | # set some constants 22 | N_FFT=352 # this being the number expected 23 | N_FFT_POINTS=4096 # number of input samples in each sox stat -freq FFT (fixed) 24 | # so N_FFT * N_FFT_POINTS = 1441792 samples, which at 12000 samples per second is 120.15 seconds 25 | # while we have only 120 seconds, so for now operate with N_FFT-1 to have all filled 26 | # may decide all 352 are overkill anyway 27 | N=N_FFT*N_FFT_POINTS 28 | w=np.zeros(N_FFT_POINTS) 29 | 30 | output=np.zeros(N, dtype=np.int16) # declaring as dtype=np.int16 is critical as the wav file needs to be 16 bit integers 31 | 32 | # create a N_FFT_POINTS array with the Hann weighting function 33 | for i in range (0, N_FFT_POINTS): 34 | x=(math.pi*float(i))/float(N_FFT_POINTS) 35 | w[i]=np.sin(x)**2 36 | 37 | for j in range (0, N_FFT-1): 38 | offset=j*N_FFT_POINTS 39 | for i in range (0, N_FFT_POINTS): 40 | output[i+offset]=int(w[i]*signal[i+offset]) 41 | wavfile.write(WAV_OUTPUT_FILENAME, fs_rate, output) 42 | -------------------------------------------------------------------------------- /wd-grape-statistics.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare WD_PSWS_SITES=( S000042 S000108 S000109 S000110 S000111 S000113 S000114 S000115 S000116 S000117 S000119 S000120 S000121 S000123) 4 | 5 | function print-header() { 6 | if [[ "$1" == "header" ]]; then 7 | printf "Found ${#date_list[@]} dates in ${#WD_PSWS_SITES[@]} WD GRAPE Sites\n" 8 | fi 9 | printf "Date/Site#:" 10 | local site_id 11 | for site_id in ${WD_PSWS_SITES[@]}; do 12 | local site_num="${site_id: -3}" 13 | printf " %s" "${site_num}" 14 | done 15 | printf "\n" 16 | if [[ "$1" != "header" ]]; then 17 | printf "Found ${#date_list[@]} dates in ${#WD_PSWS_SITES[@]} WD GRAPE Sites\n" 18 | fi 19 | 20 | } 21 | 22 | function wd-statistics() { 23 | local site_home_list=( ${WD_PSWS_SITES[@]/#/..\/} ) 24 | local date_list=( $(find ${site_home_list[@]} -mindepth 1 -maxdepth 1 -type d -name 'OBS*' -printf "%f\n" 2> /dev/null | sort -u ) ) 25 | 26 | print-header "header" 27 | for obs_date in ${date_list[@]}; do 28 | printf "${obs_date:3:10}: " 29 | local site_home 30 | for site_home in ${site_home_list[@]}; do 31 | local dir_size=" " 32 | local obs_date_dir=${site_home}/${obs_date} 33 | if [[ -d ${obs_date_dir} ]]; then 34 | dir_size="$(du -sh ${obs_date_dir} | cut -f 1)" 35 | fi 36 | printf "%4s " "${dir_size}" 37 | #exit 1 38 | done 39 | printf "\n" 40 | done 41 | print-header "foot" 42 | } 43 | 44 | wd-statistics 45 | -------------------------------------------------------------------------------- /wd_remote_access_daemon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | ### This function is executed by systemctl to start and stop a remote access connection in the ~/wsprdsaemon/bin directory 4 | 5 | declare FRPC_CMD=./frpc 6 | declare FRPC_INI_FILE=${FRPC_CMD}_wd.ini 7 | declare FRPC_ERROR_SLEEP_SECS=10 ### Wait this long after a connection fails before retrying the connection 8 | 9 | function wd_remote_access_daemon() { 10 | echo "Starting with args '$@'" 11 | if [[ "${1-}" == "-A" ]] ; then 12 | echo "Starting" 13 | local rc 14 | ${FRPC_CMD} -c ${FRPC_INI_FILE} & 15 | rc=$? 16 | if [[ ${rc} -ne 0 ]]; then 17 | echo "ERROR: '${FRPC_CMD} -c ${FRPC_INI_FILE} &' => ${rc}. Sleep ${FRPC_ERROR_SLEEP_SECS} and try to connect once again" 18 | fi 19 | else 20 | echo "Stopping" 21 | fi 22 | return 0 23 | } 24 | 25 | wd_remote_access_daemon $@ 26 | -------------------------------------------------------------------------------- /wd_spots_to_ts.awk: -------------------------------------------------------------------------------- 1 | #!/bin/awk 2 | 3 | BEGIN { 4 | ts_2_10_fields_count = split ( "date time sync_quality snr dt freq call grid pwr drift decode_cycles jitter blocksize metric osd_decode ipass nhardmin for_wsprnet rms_noise c2_noise band my_grid my_call_sign km rx_az rx_lat rx_lon tx_az tx_lat tx_lon v_lat v_lon", ts_2_10_field_names, " ") 5 | ts_3_0_fields_count = split ( "date time sync_quality snr dt freq call grid pwr drift decode_cycles jitter blocksize metric osd_decode ipass nhardmin for_wsprnet rms_noise c2_noise band my_grid my_call_sign km rx_az rx_lat rx_lon tx_az tx_lat tx_lon v_lat v_lon overload_counts pkt_mode", ts_3_0_field_names, " ") 6 | } 7 | 8 | function print_spot_line_fields (spot_line) { 9 | spot_line_count = split( spot_line, spot_line_array, " ") 10 | if ( spot_line_count == ts_2_10_fields_count ) { 11 | for ( i = 1; i <= ts_2_10_fields_count; ++ i ) { 12 | printf ( "#%2d: %15s: %s\n", i, ts_2_10_field_names[i], spot_line_array[i] ) 13 | } 14 | return 15 | } 16 | if ( spot_line_count == ts_3_0_fields_count ) { 17 | for ( i = 1; i <= ts_3_0_fields_count; ++ i ) { 18 | printf ( "#%2d: %15s: %s\n", i, ts_3_0_field_names[i], spot_line_array[i] ) 19 | } 20 | return 21 | } 22 | printf ( "ERROR: spot line has %d fields, not the expected %d fields: '%s'\n", spot_line_count, ts_2_10_fields_count, spot_line) 23 | return 24 | } 25 | 26 | ### Process 32 filed WD 2.x spot lines and 34 field WD 3.0 spot lines into the CSV line format expected by TS 27 | 28 | NF != 32 && NF != 34 { 29 | printf( "ERROR: file %s spot line has %d fields, not the expected 32 or 34 fields: '%s\n'", FILENAME, NF, $0) 30 | } 31 | 32 | NF == 32 || NF == 34 { 33 | field_count = split($0, fields, " ") 34 | if ( NF == 32 ) { 35 | if ( fields[18] == 0 ) { 36 | fields[18] = 2 ### In WD 2.x this field was a placeholder and always set to zero. Set is to the value for 'WSPR-2' pkt mode, since that is the only pkt mode decoded by WD 2.x 37 | } else { 38 | ### In 2.10 some or all of the type 2 spots were incorrectly formatted where tx 'grid' was 'none', the next field 'tpwr' held that grid, and the following fields were similarly off by one 39 | ### This code attempts to detect and clean up such malformed lines 40 | if ( verbosity > 1 ) print_spot_line_fields($0) 41 | if ( verbosity > 0 ) printf ("ERROR: found unexpected 'pkt_mode' value '%s' instead of the expected value '0' in spot file %s spot line:\n%s\n", fields[18], FILENAME, $0 ) 42 | for ( i = 8; i <= 19; ++i ) { 43 | if ( verbosity > 1 ) printf ("Move field #%2d (%s) to field #%2d\n", i+1, fields[i+1], i) 44 | fields[i] = fields[i+1] 45 | } 46 | ### In 2.10 the RMS noise field is lost and C2_noise may contain RMS or C2 data. So make both noise levels the same 47 | fields[18] = 2 ### In 2.10 this field was 'for_wsprnet' but in 3.0 is is 'pkt_mode' and all 2.10 spots are WSPR-2 48 | } 49 | fields[++field_count] = 0 ### 'ov_count' : There is no overload counts information in WD 2.x spot lines 50 | fields[++field_count] = 0 ### 'wsprnet_info' : This field may be used in WD 3.0 to signal that the server should 'proxy upload' this spot to wsprnet.org 51 | 52 | if ( fields[9] != int(fields[9]) ) { 53 | ### Some older versions of WD produce corrupt lines. Don't record them 54 | printf ("ERROR: ") 55 | } 56 | } 57 | ### Create the spot time in the TS format: "20YY-MM-DD:HH:MM" 58 | wd_year = substr(fields[1], 1, 2) 59 | wd_month = substr(fields[1], 3, 2) 60 | wd_day = substr(fields[1], 5, 2) 61 | wd_hour = substr(fields[2], 1, 2) 62 | wd_min = substr(fields[2], 3, 2) 63 | ts_time = ( "20" wd_year "-" wd_month "-" wd_day ":" wd_hour ":" wd_min ) 64 | 65 | fields[3] = int ( fields[3] * 100 ) ### ALL_WSPR.TXT reports sync_quality as a float (0.NN), but we have defined that sync field as a int in TS 66 | 67 | printf( "\"%s\"", ts_time ) 68 | 69 | fields[7] = toupper(fields[7]) 70 | fields[8] = ( toupper(substr(fields[8], 1, 2)) substr(fields[8], 3, 2) tolower(substr(fields[8], 5, 2)) ) 71 | fields[23] = toupper(fields[23]) 72 | fields[22] = ( toupper(substr(fields[22], 1, 2)) substr(fields[22], 3, 2) tolower(substr(fields[22], 5, 2)) ) 73 | 74 | file_path_count = split ( FILENAME, file_path_array, "/" ) 75 | rx_name = file_path_array[ file_path_count - 2] 76 | fields[++field_count] = rx_name ### Taken from path to the file which contains this spot line 77 | 78 | for ( i = 3; i <= field_count; ++ i) { 79 | printf ( ",%s", fields[i]) 80 | } 81 | printf "\n" 82 | } 83 | -------------------------------------------------------------------------------- /wd_version.txt: -------------------------------------------------------------------------------- 1 | 3.3.2 2 | -------------------------------------------------------------------------------- /wn_from_wd_spot_file.awk: -------------------------------------------------------------------------------- 1 | #!/bin/awk 2 | 3 | ### This awk script takes a file of 34 field WD extended spot lines and output 11 field wsprnet batch upload spot lines 4 | ### Doing that requries moving the 'sync_quality' to field 3 and transforming the 'pkt_mode' field in field $18 of extended spots to a subset in field 11 of the WN spot line 5 | 6 | NF != 34 { 7 | printf ("ERROR: WD spot file %s has %d fields instead of the expected 34 fields\n", FILENAME, NF ) 8 | } 9 | NF == 34 { 10 | if ( $8 == "none" ) { 11 | $8 = " " 12 | } 13 | wd_pkt_mode = $18 14 | if ( wd_pkt_mode == 2 ) ### produced by 'wsprd' 15 | wn_pkt_mode = 2 ### WSPR-2 16 | else if ( wd_pkt_mode == 15 ) ### produced by 'wsprd' 17 | wn_pkt_mode = 15 ### WSPR-15 18 | else if ( wd_pkt_mode == 3 ) ### added by WD to the lines produced by 'jt9' 19 | wn_pkt_mode = 3 ### FST4W-120 20 | else if ( wd_pkt_mode == 6 ) ### added by WD to the lines produced by 'jt9' 21 | wn_pkt_mode = 5 ### FST4W-300 22 | else if ( wd_pkt_mode == 16 ) ### added by WD to the lines produced by 'jt9' 23 | wn_pkt_mode = 15 ### FST4W-900 24 | else if ( wd_pkt_mode == 31 ) ### added by WD to the lines produced by 'jt9' 25 | wn_pkt_mode = 30 ### FST4W-1800 26 | else { 27 | wn_pkt_mod= 2 28 | printf ("ERROR: WD spot line has pkt_mode = '%s', not one of the expected 2/3/5/15/16/30 values: ", wd_pkt_mode) 29 | } 30 | printf ( "%6s %4s %5.2f %3d %5.2f %12.7f %-14s %-6s %2d %2d %4d\n", $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, wn_pkt_mode) 31 | } 32 | -------------------------------------------------------------------------------- /wspr.rotate: -------------------------------------------------------------------------------- 1 | /var/log/wspr.log 2 | { 3 | rotate 10 4 | daily 5 | missingok 6 | notifempty 7 | compress 8 | delaycompress 9 | copytruncate 10 | } 11 | -------------------------------------------------------------------------------- /wsprnet-scraper.awk: -------------------------------------------------------------------------------- 1 | #!/bin/awk 2 | 3 | ### Filter and convert spots repored by the wsprnet.org API into a csv file which will be recorded in the TS and CH databases 4 | ### 5 | ### The calling command line is expected to define the awk variable spot_epoch: 6 | ### awk -v spot_epoch=${spot_epoch} -f ${WSPRDAEMON_ROOT_DIR}/wsprnet-scraper.awk <<< "${sorted_lines}" > ${WSPRNET_SCRAPER_TMP_PATH}/filtered_spots.csv 7 | 8 | BEGIN { 9 | FS = "," 10 | OFS = "," 11 | spot_minute = ( ( spot_epoch % 3600 ) / 60 ) 12 | spot_date = strftime( "%Y-%m-%d:%H:%M", spot_epoch ) 13 | 14 | is_odd_minute = ( spot_minute % 2 ) 15 | if ( is_odd_minute == 0 ) { 16 | fixed_spot_epoch = spot_epoch 17 | } else { 18 | fixed_spot_epoch = spot_epoch + 60 19 | } 20 | fixed_date = strftime( "%Y-%m-%d:%H:%M", fixed_spot_epoch ) 21 | 22 | print_diags = 0 23 | if ( print_diags == 1 ) { 24 | if ( is_odd_minute == 0 ) { 25 | printf ( "Finding spots with epoch %d which is at even minute %d == '%s'\n", spot_epoch, spot_minute, fixed_date) 26 | } else { 27 | printf ( "Finding spots with epoch %d which is at odd minute %d and if mode is invalid change the spot to the next even minute epoch %d == '%s'\n", spot_epoch, spot_minute, fixed_spot_epoch, fixed_date) 28 | } 29 | } 30 | 31 | for ( i = 0; i < 60; i += 2 ) { valid_mode_1_minute[i] = 1 } ## WSPR-2 valid minutes 32 | for ( i = 0; i < 60; i += 2 ) { valid_mode_3_minute[i] = 1 } ## FST4W-120 valid minutes 33 | for ( i = 0; i < 60; i += 5 ) { valid_mode_4_minute[i] = 1 } ## FST4W-300 valid minutes 34 | for ( i = 0; i < 60; i += 15 ) { valid_mode_2_minute[i] = 1 } ## FST4W-900 valid minutes 35 | for ( i = 0; i < 60; i += 30 ) { valid_mode_8_minute[i] = 1 } ## FST4W-1800 valid minutes 36 | 37 | if ( valid_mode_1_minute[spot_minute] == 1 ) { is_valid_mode_1_minute = 1 } 38 | if ( valid_mode_3_minute[spot_minute] == 1 ) { is_valid_mode_3_minute = 1 } 39 | if ( valid_mode_4_minute[spot_minute] == 1 ) { is_valid_mode_4_minute = 1 } 40 | if ( valid_mode_2_minute[spot_minute] == 1 ) { is_valid_mode_2_minute = 1 } 41 | if ( valid_mode_8_minute[spot_minute] == 1 ) { is_valid_mode_8_minute = 1 } 42 | } 43 | 44 | $2 == spot_epoch { 45 | found_valid_mode = 1 46 | found_valid_minute = 1 47 | if ( $15 == 1 ) { 48 | spot_length = 2 ### Mode 1 spots (2 minute long WSPR) can happen only on even minutes 49 | if ( is_valid_mode_1_minute == 0 ) { 50 | found_valid_minute = 0 51 | } 52 | } else if ( $15 == 3 ) { 53 | spot_length = 2 ### Mode 1 spots (2 minute long FST4W-120) can happen only on even minutes 54 | if ( is_valid_mode_3_minute == 0 ) { 55 | found_valid_minute = 0 56 | } 57 | } else if ( $15 == 2 ) { 58 | spot_length = 15 ### Mode 2 spots (15 minute long WSPR and FST4W) can happen on both even and odd minutes 59 | if ( is_valid_mode_2_minute == 0 ) { 60 | found_valid_minute = 0 61 | } 62 | } else if ( $15 == 4 ) { 63 | spot_length = 5 ### Mode 4 spots (5 minute long FST4W) can happen on both even and odd minutes 64 | if ( is_valid_mode_4_minute == 0 ) { 65 | found_valid_minute = 0 66 | } 67 | } else if ( $15 == 8 ) { 68 | spot_length = 30 ### Mode 8 spots (30 minute long FST4W) can happen only on even minutess 69 | if ( is_valid_mode_8_minute == 0 ) { 70 | found_valid_minute = 0 71 | } 72 | } else { 73 | found_valid_mode = 0 74 | } 75 | 76 | if ( found_valid_mode == 1) { 77 | if ( found_valid_minute == 1) { 78 | printf ( "%s,%s\n", spot_date, $0 ) ### This should be the case for the vast majority of spots 79 | } else { 80 | ### Mode is valid, but minute is not valid 81 | if ( is_odd_minute == 0 ) { 82 | ### Leave unchanged time of valid modes at invalid even minutes 83 | printf ("Found valid mode %2d == %2d minute long spot at invalid even minute %2d: %s\n", $15, spot_length, spot_minute, $0 ) 84 | printf ( "%s,%s\n", spot_date, $0 ) 85 | } else { 86 | $2 = fixed_spot_epoch 87 | printf ("Found valid mode %2d == %2d minute long spot at invalid odd minute %2d and fixed it to %d '%s': %s\n", $15, spot_length, spot_minute, fixed_spot_epoch, fixed_date, $0 ) 88 | printf ( "%s,%s\n", fixed_date, $0 ) 89 | } 90 | } 91 | } else { 92 | ### Mode is invalid, so always force to even minute 93 | if ( is_odd_minute == 0 ) { 94 | printf ("Found invalid mode %2d == %2d minute long spot at even minute %2d: %s\n", $15, spot_length, spot_minute, $0 ) 95 | printf ( "%s,%s\n", spot_date, $0 ) ### Leave unchanged even minute bad mode spots 96 | } else { 97 | $2 = fixed_spot_epoch 98 | printf ("Found invalid mode %2d spot at odd minute %2d and fixed it to %d '%s': %s\n", $15, spot_minute, fixed_spot_epoch, fixed_date, $0 ) 99 | printf ( "%s,%s\n", fixed_date, $0 ) 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /wwv_start.py: -------------------------------------------------------------------------------- 1 | #!/home/wsprdaemon/wsprdaemon/venv/bin/python3 2 | 3 | import numpy as np 4 | import os 5 | import soundfile as sf 6 | import sys 7 | from pathlib import Path 8 | from scipy import signal 9 | import re 10 | 11 | def cross_file(filename): 12 | # look for 0.8 seconds of 1 kHz (eventually, also 1.5 kHz at top of hour) 13 | 14 | # determine tone burst frequency from filename, if possible 15 | # expects a filename such as 20250405T044300Z_5000000_iq.wav 16 | tone = 1000 17 | regex_pattern = r'(\d{8})T(\d{2})(\d{2})(\d{2})Z_(\d+)_([a-z]+).wav' 18 | match = re.search(regex_pattern, filename) 19 | if match: 20 | if int(match.group(3)) == 0: 21 | # top of hour, 1500 Hz instead of 1000 Hz 22 | tone = 1500 23 | 24 | # read first 3 seconds from wav file 25 | wav_sample_rate = sf.info(filename).samplerate 26 | samples, wav_sample_rate = sf.read(filename, frames = (3 * wav_sample_rate)) 27 | 28 | # Convert to a 1d array of complex values 29 | samples_c = samples.view(dtype = np.complex128) 30 | 31 | # demod, remove DC offset, convert to 1d array 32 | wav_amp = np.abs(samples_c) 33 | wav_demod = wav_amp - np.mean(wav_amp); 34 | wav_demod = wav_demod.squeeze() 35 | 36 | # generate demod wav file for testing 37 | # sf.write('demod.wav', wav_demod, wav_sample_rate, subtype="FLOAT") 38 | 39 | # create 0.8 seconds of sine wav at 1 kHz 40 | x = np.linspace(0, 0.8, int(0.8 * wav_sample_rate)) 41 | beep = 0.05 * np.sin(2 * x * np.pi * tone) 42 | 43 | # normalize amplitudes 44 | beep = (beep - np.mean(beep)) / np.std(beep) 45 | wav_demod = (wav_demod - np.mean(wav_demod)) / np.std(wav_demod) 46 | 47 | # cross corr 48 | corr = signal.correlate(wav_demod, beep, mode='full', method='fft') 49 | lags = signal.correlation_lags(len(wav_demod), len(beep), mode='full') 50 | 51 | peak = np.argmax(corr) 52 | wav_peak = lags[peak]; 53 | peak_value = corr[peak] 54 | 55 | print(f'{1000.0 * (wav_peak / wav_sample_rate):.2f} ms') 56 | return 57 | 58 | def main(): 59 | cross_file(sys.argv[1]) 60 | 61 | if __name__ == '__main__': 62 | main() 63 | --------------------------------------------------------------------------------