Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
HamSCI Sunrisefest recording scripts for Airspy HF+ Discovery

HamSCI Sunrisefest recording scripts for Airspy HF+ Discovery

Prerequisites

How to use

  • Specify the working directory with the variable recorddir of sunrisefest-recording.py
  • Change other initial settings
  • Put record-airspyhf.sh in the working directory
  • Put sunrisefest-recording.py in the working directory
  • Chdir to the working directory
  • Run ./sunrisefest-recording.py
    • The script runs the recording jobs on every 8 and 48 minutes of the hour
    • Running this under a tmux window or a typescript process is convinient for the logging
  • You will find a list of recorded files in the working directory

Output of the sunrisefest-recording.py

UTC: 2022-04-14 07:08:00.005203
Receive WWVH for JJ1BDX_WWV_10MHz_2022-04-14_07-08.wav
Device Serial Number: 0x3652A980E9C746F7
Stop with Ctrl-C

User cancel, exiting...
Total time: 60.2846 s
Average speed 0.1920 MS/s IQ
done
UTC: 2022-04-14 07:48:00.004328
Receive WWVH for JJ1BDX_WWVH_10MHz_2022-04-14_07-48.wav
Device Serial Number: 0x3652A980E9C746F7
Stop with Ctrl-C

User cancel, exiting...
Total time: 60.2848 s
Average speed 0.1920 MS/s IQ
done

Recorded result format

  • 24ksamples/sec (24kHz) stereo (IQ) 32-bit float WAV
  • The WAV files can be directly monitored with various audio recording software including AudaCity and Fission
    • Normalize the sound file before listening to it with your ears
  • You can use airspy-fmradion to listen to the files too
    • A command example: airspy-fmradion -t filesource -m am -c freq=10000000,filename=JJ1BDX_WWVH_10MHz_2022-04-14_07-48.wav -P -

Why Advanced Python Scheduler?

Because it's accurate and the jobs are turned on in within <10msec of the scheduled time. Cron is not this accurate.

License

MIT

# Generate a shell script from input IQ WAV file listing (one per line)
{
print "echo \"processing " $1 "\"";
# this output path can be something else
print "papermill sunrisefest.ipynb ./output/test.ipynb --no-progress-bar -y \""
print "input_filename: " $1;
print "input_requires_demodulation: True"
print "lat: your_latitude"
print "lon: your_longitude"
print "radio: your_radio_model"
print "antenna: your_antenna_info"
print "\""
}

JJ1BDX HamSCI Sunrisefest April 2022 station info

(As of 29-APR-2022)

Multiple SDR receiver units are connected to the Intel NUC for processing the data. For the 10MHz reception of Sunrise Festival, an Airspy HF+ Discovery (in a third-party metal enclosure labeled “JJ1BDX OCT 2020” of Shaft Corporation) is connected to a whip antenna with a counterpoise outdoor at the balcony. The Airspy HF+ Dual Port is for FM broadcast reception. There's also an Instock Wireless GPS410, which is a 1:4 splitter of the outdoor GPS signal fed into the GPS receiver connected to a Raspberry Pi 4.

The antenna for receiving 10MHz is a 2.2m-length center-loaded whip antenna (Diamond Antenna HF30CL for 10.1MHz) with counterpoise (5 x 5m-length wires) at a corner of the North balcony of our house, up 9 meters above the ground. This antenna works well for the 10MHz WWV/WWVH reception, as well as for transmitting FT8/FT4/WSPR/CW.

There are a few computers and other equipments in the station rack. The black Intel NUC called "gluonite" with a 32GB USB memory inserted is the computer receiving and analyzing the WWV/WWVH signals for the Sunrise Festival. Another Intel NUC called "gravitas" besides "gluonite" mostly works for receiving ADS-B, and was used several times for the HamSCI Solar Eclipse Festivals to record the audio signals from Grape 1 receiver. An uninterrupted power supply (OMRON BY35S) lies down at the bottom of the rack, connected to the computers and network equipments to prevent damages from unexpected power outages. The rack also has a Raspberry Pi 4 working as a GNSS-synchronized NTP server, a GbE switch, and a 13.8V DC power supply for the ham radio transceivers.

[End of document]

#!/usr/bin/env python3
# Converting file name convention of JJ1BDX_WWV_10MHz_2022-04-16_16-08.wav
# into requested file data submission format of CSV
# (to be converted to Google Sheets or even Excel)
# Use filelist.txt as the filename list (one file in a line)
# The output: filelist.csv
# MIT License
# -----------
#
# Copyright (c) 2022 Kenji Rikitake, JJ1BDX
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
import datetime
import csv
firstrow = ['\ufeffTimestamp',
"What is your callsign? (If you don't have one, use your ORCID)",
'What frequency did you record?',
'What day did you make the recording?',
'What time did you make the recording? ',
'Which signal did you record?',
'What type of recording did you make?',
'Upload your recording here. (Use correct filename convention!) ',
'Did you analyze your data using the Jupyter Notebook provided?',
'What time of flight was observed?',
'How many simultaneous propagation modes did you observe?',
'Any other notes about this file?']
with open("filelist.csv", "w") as csvfile:
csvfilewriter = csv.writer(csvfile)
# Write down CSV row
csvfilewriter.writerow(firstrow)
with open("filelist.txt", "r") as filelist:
for line in filelist:
# get necessary info from the filename
filename = line.rstrip()
basename = filename.split(".")[0]
namelist = basename.split("_")
callsign = namelist[0]
station = namelist[1]
freq = namelist[2]
filetimestamp = datetime.datetime.strptime(
namelist[3] + "_" + namelist[4], '%Y-%m-%d_%H-%M')
nowtime = datetime.datetime.now(datetime.timezone.utc)
signaldescription = ""
if station == "WWV":
signaldescription = "WWV (8 minutes past the hour)"
elif station == "WWVH":
signaldescription = "WWVH (48 minutes past the hour)"
csvfilewriter.writerow([
nowtime.strftime("%m/%d/%Y %H:%M:%S"),
callsign,
freq,
filetimestamp.strftime("%m/%d/%Y"),
filetimestamp.strftime("%H:%M:%S"),
signaldescription,
"I/Q",
filename,
"No",
"",
"",
""])
#!/bin/sh
FREQ=$1
FILENAME=$2
OPTIONS="-s serial_number_of_Airspy_HF+"
#
# Record the 192ksps IQ file for 60 seconds at the specified frequency in MHz,
# then convert it into a 24ksps IQ file and save it with a given filename
#
airspyhf_rx ${OPTIONS} -f ${FREQ} -r stdout -z -a 192000 -n 11520000 | \
sox -t raw -r 192000 -e floating-point -b 32 -c 2 - -t wav -r 24k ${FILENAME}
#
# MIT License
# -----------
#
# Copyright (c) 2022 Kenji Rikitake, JJ1BDX
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#!/usr/bin/env python3
# Sunrisefest reception event loop by Kenji Rikitake, JJ1BDX
# MIT License
# -----------
#
# Copyright (c) 2022 Kenji Rikitake, JJ1BDX
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
from apscheduler.schedulers.blocking import BlockingScheduler
import subprocess
import os
import signal
import sys
import datetime
# my callsign
mycallsign="JJ1BDX"
# target frequency in MHz
myfreq=10.0
# target frequency as a part of wav filename
mymhz="10MHz"
# target file directory for recorded wav files
recorddir="/work/kenji/sunrisefest-202204"
def signal_handler(signal, frame):
print('Terminated by CTRL/C')
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
def record_file(wavfilename):
global recorddir, myfreq
os.chdir(recorddir)
subprocess.run(("./record-airspyhf.sh" + " " +
str(myfreq) + " " + str(wavfilename)),
cwd=recorddir, shell=True)
def rx_record(station):
nowutc = datetime.datetime.utcnow()
nowdate = nowutc.strftime("%Y-%m-%d")
nowhm = nowutc.strftime("%H-%M")
wavfile = mycallsign + "_" + str(station) + "_" + mymhz + \
"_" + nowdate + "_" + nowhm + "_" + "iq" + ".wav"
print("UTC: " + str(nowutc))
print("Receive WWVH for " + wavfile, flush=True)
record_file(wavfile)
def rx_wwvh():
rx_record("WWVH")
def rx_wwv():
rx_record("WWV")
scheduler = BlockingScheduler()
scheduler.add_job(rx_wwv, 'cron', minute=8, second=0)
scheduler.add_job(rx_wwvh, 'cron', minute=48, second=0)
#scheduler.print_jobs()
scheduler.start()
# End of code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment