diff --git a/station/Makefile b/station/Makefile index b7fd3f7..e928f55 100644 --- a/station/Makefile +++ b/station/Makefile @@ -1,3 +1,6 @@ install: cp tools/baseband_spectogram.py /usr/local/bin/ - chmod +x /usr/local/bin/baseband_spectogram.py \ No newline at end of file + chmod +x /usr/local/bin/baseband_spectogram.py + + cp tools/cw_morse.py /usr/local/bin/ + chmod +x /usr/local/bin/cw_morse.py \ No newline at end of file diff --git a/station/recorder.py b/station/recorder.py index 7756cc3..3a74e6d 100644 --- a/station/recorder.py +++ b/station/recorder.py @@ -50,7 +50,11 @@ class recorder(threading.Thread): realStart = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp()) - os.system(f"satdump record {baseband} --source {self.job['receiver']['params']['radio']} --samplerate {fs} --frequency {self.job['transmitter']['centerFrequency']} --gain {self.job['receiver']['params']['gain']} --baseband_format s8 --timeout {recordTime}") + ret = os.system(f"satdump record {baseband} --source {self.job['receiver']['params']['radio']} --samplerate {fs} --frequency {self.job['transmitter']['centerFrequency']} --gain {self.job['receiver']['params']['gain']} --baseband_format s8 --timeout {recordTime}") + + if ret != 0: # fail to open sdr + puller.setFail(self.job["id"]) + return realEnd = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp()) diff --git a/station/tools/cw_morse.py b/station/tools/cw_morse.py new file mode 100644 index 0000000..704ae2b --- /dev/null +++ b/station/tools/cw_morse.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python +# Simple CW morse decoder + +import argparse +import numpy as np + +if __name__ == '__main__': + cliParser = argparse.ArgumentParser(description='Simple CW morse decoder') + + cliParser.add_argument('input_file', type=str, help='input filename baseband record') + cliParser.add_argument('output_file', type=str, help='output filename decoded text') + cliParser.add_argument('-fs', '--sampleRate', type=float, help='sets the sample rate [hz]') + cliParser.add_argument('-mfs', '--morseFS', type=float, help='sets the sample rate for CW (Downsample) [hz]', default=125000) + cliParser.add_argument('-of', '--offsetFreq', type=float, help='sets carrier offset [hz]', default=0) + cliParser.add_argument('-bw', '--bandWidth', type=float, help='sets carrier bandwidth [hz]', default=60) + cliParser.add_argument('-fc', '--failChar', type=str, help='sets char what is used when morse decode fail', default='') + + + cliParser.add_argument('-f', '--format', type=str, + help='Output format', + choices=["int8"], + default='int8') + + args = cliParser.parse_args() + + data = np.memmap(args.input_file, dtype=args.format, mode="r") + + fft_size = 1024 + + sample_rate = args.sampleRate + down_rate = args.morseFS + offset_freq = args.offsetFreq + bandwidth = args.bandWidth + failChar = args.failChar + + downSample = int(sample_rate // down_rate) + + data = data[1::2] + 1j * data[0::2] + data = data[1::downSample] # downsample + data = data - np.mean(data) # normalize + + num_rows = len(data) // fft_size + + beep_trashold = [] + + for i in range(num_rows): + fft = np.abs(np.fft.fftshift(np.fft.fft(data[i*fft_size:(i+1)*fft_size]))) + frame_max = np.max(fft[len(fft)//2 - bandwidth//2 + offset_freq : len(fft)//2 + bandwidth//2 + offset_freq]) + beep_trashold.append(frame_max) + + beep_trashold = np.mean(beep_trashold) + + beep = np.zeros(num_rows) + + for i in range(num_rows): + fft = np.abs(np.fft.fftshift(np.fft.fft(data[i*fft_size:(i+1)*fft_size]))) + frame_max = np.max(fft[len(fft)//2 - bandwidth//2 + offset_freq : len(fft)//2 + bandwidth//2 + offset_freq]) + if (frame_max >= beep_trashold): + beep[i] = 1 + + is_one = False + mean_one_len = [] + mean_zero_len = [] + sig_len = 0 + + for signal in beep: + if signal == 1 and not(is_one): + mean_zero_len.append(sig_len) + sig_len = 0 + is_one = True + + if signal == 0 and is_one: + mean_one_len.append(sig_len) + sig_len = 0 + is_one = False + + sig_len += 1 + + mean_one_len = np.mean(mean_one_len) + mean_zero_len = np.mean(mean_zero_len) + + is_one = False + morse = "" + sig_len = 0 + + for signal in beep: + if signal == 1 and not(is_one): + is_one = True + if sig_len >= mean_zero_len * 2: + morse += "\n" + if sig_len >= mean_zero_len: + morse += " " + + sig_len = 0 + + if signal == 0 and is_one: + is_one = False + if sig_len > mean_one_len: + morse += "-" + else: + morse += "." + sig_len = 0 + + sig_len += 1 + + + # extra space added at the end to access the + # last morse code + MORSE_CODE_DICT = { 'A':'.-', 'B':'-...', + 'C':'-.-.', 'D':'-..', 'E':'.', + 'F':'..-.', 'G':'--.', 'H':'....', + 'I':'..', 'J':'.---', 'K':'-.-', + 'L':'.-..', 'M':'--', 'N':'-.', + 'O':'---', 'P':'.--.', 'Q':'--.-', + 'R':'.-.', 'S':'...', 'T':'-', + 'U':'..-', 'V':'...-', 'W':'.--', + 'X':'-..-', 'Y':'-.--', 'Z':'--..', + '1':'.----', '2':'..---', '3':'...--', + '4':'....-', '5':'.....', '6':'-....', + '7':'--...', '8':'---..', '9':'----.', + '0':'-----', ', ':'--..--', '.':'.-.-.-', + '?':'..--..', '/':'-..-.', '-':'-....-', + '(':'-.--.', ')':'-.--.-'} + + message = morse + ' ' + + decipher = '' + citext = '' + for letter in message: + if (not(letter in [' ', '\n'])): + citext += letter + else: + if (citext in MORSE_CODE_DICT.values()): + decipher += list(MORSE_CODE_DICT.keys())[list(MORSE_CODE_DICT.values()).index(citext)] + elif citext != '': + decipher += failChar + + if (letter == '\n'): + decipher += ' ' + + citext = '' + + f = open(args.output_file, "w+") + f.write(decipher) + f.close() \ No newline at end of file diff --git a/web/seeds.php b/web/seeds.php index 28f18e5..2e22d5e 100644 --- a/web/seeds.php +++ b/web/seeds.php @@ -146,6 +146,16 @@ $spectogramPipe->commit(); + $cwPipe = new \DAL\processPipe(); + $cwPipe->name->set("CW Morse"); + $cwPipe->pipe->set([ + "baseband_spectogram.py {baseband} {artefactDir}/spectogram.png -fs {fs} -fc {freq}", + "cw_morse.py {baseband} {artefactDir}/morse.txt -fs {fs} -fc \"[?]\"", + "cp {baseband} {artefactDir}/{freq}_{fs}.s8" + ]); + + $cwPipe->commit(); + /** * NOAA 19 @@ -346,7 +356,7 @@ $maxvalierCW->modulation->set($cw); $maxvalierCW->antenna->set($yagi); $maxvalierCW->priority->set(0); - $maxvalierCW->processPipe->set($spectogramPipe); + $maxvalierCW->processPipe->set($cwPipe); $maxvalierCW->commit(); // add autoplas