def new_kiwiworker(o, band_hops_str, idx): options = copy(o) def _extract_band(band_hops_str): local = str(band_hops_str).split('|') # ['FT8', 'FT4', 'FT8', 'FT8'] mode_hops = [ config.MODES[b[-1]] if b[-1] in config.MODES else "FT8" for b in local ] # ['20', '30', '40', '60'] band_hops = [ b[:-1] if b[-1] not in [str(n) for n in range(0, 9)] else b for b in local ] # [14074, 10140, 7074, 5357] in KHz freq_hops = [ config.BANDS[mode_hops[i]][b] * 1000 for i, b in enumerate(band_hops) ] return mode_hops, band_hops, freq_hops options.band_hops_str = band_hops_str options.mode_hops, options.band_hops, options.freq_hops = _extract_band( band_hops_str) options.idx = idx options.timestamp = int(time.time() + os.getpid() + idx) & 0xffffffff # tmp dirs preparation for i, mode in enumerate(options.mode_hops): dir = os.path.join(Config.tmpdir(), options.station, mode, options.band_hops[i]) if not os.path.isdir(dir): os.makedirs(dir, exist_ok=True) else: os.popen("rm -f %s/*.wav" % dir) worker = KiwiWorker(target=WsjtSoundRecorder(options), name="%s-%s" % (options.station, options.band_hops_str)) return worker
def main(): # extend the OptionParser so that we can print multiple paragraphs in # the help text class MyParser(OptionParser): def format_description(self, formatter): result = [] for paragraph in self.description: result.append(formatter.format_description(paragraph)) return "\n".join(result[:-1]) # drop last \n def format_epilog(self, formatter): result = [] for paragraph in self.epilog: result.append(formatter.format_epilog(paragraph)) return "".join(result) usage = "%prog -s SERVER -p PORT -f FREQ -m MODE [other options]" description = [ "kiwirecorder.py records data from one or more KiwiSDRs to your disk." " It takes a number of options as inputs, the most basic of which" " are shown above.", "To record data from multiple Kiwis at once, use the same syntax," " but pass a list of values (where applicable) instead of a single value." " Each list of values should be comma-separated and without spaces." " For instance, to record one Kiwi at localhost on port 80, and another Kiwi" " at example.com port 8073, run the following:", " kiwirecorder.py -s localhost,example.com -p 80,8073 -f 10000,10000 -m am", "In this example, both Kiwis will record on 10,000 kHz (10 MHz) in AM mode." " Any option that states \"can be a comma-separated list\" also means a single" " value will be duplicated across multiple connection. In the above example" " the simpler \"-f 10000\" can been used.", "" ] epilog = [] # text here would go after the options list parser = MyParser(usage=usage, description=description, epilog=epilog) parser.add_option('-s', '--server-host', dest='server_host', type='string', default='localhost', help='Server host (can be a comma-separated list)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option( '-p', '--server-port', dest='server_port', type='string', default=8073, help='Server port, default 8073 (can be a comma-separated list)', action='callback', callback_args=(int, ), callback=get_comma_separated_args) parser.add_option( '--pw', '--password', dest='password', type='string', default='', help='Kiwi login password (if required, can be a comma-separated list)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option( '--tlimit-pw', '--tlimit-password', dest='tlimit_password', type='string', default='', help= 'Connect time limit exemption password (if required, can be a comma-separated list)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option( '-u', '--user', dest='user', type='string', default='kiwirecorder.py', help='Kiwi connection user name (can be a comma-separated list)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option( '--station', dest='station', type='string', default=None, help= 'Station ID to be appended to filename (can be a comma-separated list)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option( '--log', '--log-level', '--log_level', type='choice', dest='log_level', default='warn', choices=['debug', 'info', 'warn', 'error', 'critical'], help='Log level: debug|info|warn(default)|error|critical') parser.add_option('-q', '--quiet', dest='quiet', default=False, action='store_true', help='Don\'t print progress messages') parser.add_option('-d', '--dir', dest='dir', type='string', default=None, help='Optional destination directory for files') parser.add_option( '--fn', '--filename', dest='filename', type='string', default='', help= 'Use fixed filename instead of generated filenames (optional station ID(s) will apply, can be a comma-separated list)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option( '--tlimit', '--time-limit', dest='tlimit', type='float', default=None, help='Record time limit in seconds. Ignored when --dt-sec used.') parser.add_option('--dt-sec', dest='dt', type='int', default=0, help='Start a new file when mod(sec_of_day,dt) == 0') parser.add_option('--launch-delay', '--launch_delay', dest='launch_delay', type='int', default=0, help='Delay (secs) in launching multiple connections') parser.add_option( '--connect-retries', '--connect_retries', dest='connect_retries', type='int', default=0, help= 'Number of retries when connecting to host (retries forever by default)' ) parser.add_option('--connect-timeout', '--connect_timeout', dest='connect_timeout', type='int', default=15, help='Retry timeout(sec) connecting to host') parser.add_option('-k', '--socket-timeout', '--socket_timeout', dest='socket_timeout', type='int', default=10, help='Socket timeout(sec) during data transfers') parser.add_option( '--OV', dest='ADC_OV', default=False, action='store_true', help='Print "ADC OV" message when Kiwi ADC is overloaded') parser.add_option( '--ts', '--tstamp', '--timestamp', dest='tstamp', default=False, action='store_true', help='Add timestamps to output. Applies only to S-meter mode currently.' ) parser.add_option( '--stats', dest='stats', default=False, action='store_true', help= 'Print additional statistics. Applies only to S-meter mode currently.') parser.add_option( '--no-api', dest='no_api', default=False, action='store_true', help='Simulate connection to Kiwi using improper/incomplete API') parser.add_option('-v', '-V', '--version', dest='krec_version', default=False, action='store_true', help='Print version number and exit') group = OptionGroup(parser, "Audio connection options", "") group.add_option( '-f', '--freq', dest='frequency', type='string', default=1000, help='Frequency to tune to, in kHz (can be a comma-separated list)', action='callback', callback_args=(float, ), callback=get_comma_separated_args) group.add_option( '-m', '--modulation', dest='modulation', type='string', default='am', help= 'Modulation; one of am, lsb, usb, cw, nbfm, iq (default passband if -L/-H not specified)' ) group.add_option('--ncomp', '--no_compression', dest='compression', default=True, action='store_false', help='Don\'t use audio compression') group.add_option('-L', '--lp-cutoff', dest='lp_cut', type='float', default=None, help='Low-pass cutoff frequency, in Hz') group.add_option('-H', '--hp-cutoff', dest='hp_cut', type='float', default=None, help='High-pass cutoff frequency, in Hz') group.add_option( '-r', '--resample', dest='resample', type='int', default=0, help= 'Resample output file to new sample rate in Hz. The resampling ratio has to be in the range [1/256,256]' ) group.add_option('-T', '--squelch-threshold', dest='thresh', type='float', default=None, help='Squelch threshold, in dB.') group.add_option( '--squelch-tail', dest='squelch_tail', type='float', default=1, help= 'Time for which the squelch remains open after the signal is below threshold.' ) group.add_option( '-g', '--agc-gain', dest='agc_gain', type='string', default=None, help= 'AGC gain; if set, AGC is turned off (can be a comma-separated list)', action='callback', callback_args=(float, ), callback=get_comma_separated_args) group.add_option('--agc-yaml', dest='agc_yaml_file', type='string', default=None, help='AGC options provided in a YAML-formatted file') group.add_option('--nb', dest='nb', action='store_true', default=False, help='Enable noise blanker with default parameters.') group.add_option( '--nb-gate', dest='nb_gate', type='int', default=100, help='Noise blanker gate time in usec (100 to 5000, default 100)') group.add_option( '--nb-th', '--nb-thresh', dest='nb_thresh', type='int', default=50, help='Noise blanker threshold in percent (0 to 100, default 50)') group.add_option( '-w', '--kiwi-wav', dest='is_kiwi_wav', default=False, action='store_true', help= 'In the wav file include KIWI header containing GPS time-stamps (only for IQ mode)' ) group.add_option('--kiwi-tdoa', dest='is_kiwi_tdoa', default=False, action='store_true', help='Used when called by Kiwi TDoA extension') group.add_option( '--test-mode', dest='test_mode', default=False, action='store_true', help='Write wav data to /dev/null (Linux) or NUL (Windows)') group.add_option( '--snd', '--sound', dest='sound', default=False, action='store_true', help= 'Also process sound data when in waterfall or S-meter mode (sound connection options above apply)' ) parser.add_option_group(group) group = OptionGroup(parser, "S-meter mode options", "") group.add_option( '--S-meter', '--s-meter', dest='S_meter', type='int', default=-1, help= 'Report S-meter (RSSI) value after S_METER number of averages. S_METER=0 does no averaging and reports each RSSI value received. Options --ts and --stats apply.' ) group.add_option('--sdt-sec', dest='sdt', type='int', default=0, help='S-meter measurement interval') parser.add_option_group(group) group = OptionGroup(parser, "Waterfall connection options", "") group.add_option('--wf', dest='waterfall', default=False, action='store_true', help='Process waterfall data instead of audio') group.add_option('-z', '--zoom', dest='zoom', type='int', default=0, help='Zoom level 0-14') parser.add_option_group(group) (options, unused_args) = parser.parse_args() ## clean up OptionParser which has cyclic references parser.destroy() if options.krec_version: print('kiwirecorder v1.0') sys.exit() FORMAT = '%(asctime)-15s pid %(process)5d %(message)s' logging.basicConfig(level=logging.getLevelName(options.log_level.upper()), format=FORMAT) if options.log_level.upper() == 'DEBUG': gc.set_debug(gc.DEBUG_SAVEALL | gc.DEBUG_LEAK | gc.DEBUG_UNCOLLECTABLE) run_event = threading.Event() run_event.set() if options.S_meter >= 0: if options.S_meter > 0 and options.sdt != 0: raise Exception( 'Options --S-meter > 0 and --sdt-sec != 0 are incompatible. Did you mean to use --S-meter=0 ?' ) options.quiet = True if options.tlimit is not None and options.dt != 0: print('Warning: --tlimit ignored when --dt-sec option used') ### decode AGC YAML file options options.agc_yaml = None if options.agc_yaml_file: try: if not HAS_PyYAML: raise Exception( 'PyYAML not installed: sudo apt install python-yaml / sudo apt install python3-yaml / pip install pyyaml / pip3 install pyyaml' ) with open(options.agc_yaml_file) as yaml_file: documents = yaml.full_load(yaml_file) logging.debug('AGC file %s: %s' % (options.agc_yaml_file, documents)) logging.debug('Got AGC paramteres from file %s: %s' % (options.agc_yaml_file, documents['AGC'])) options.agc_yaml = documents['AGC'] except KeyError: logging.fatal('The YAML file does not contain AGC options') return except Exception as e: logging.fatal(e) return options.raw = False options.rigctl_enabled = False gopt = options multiple_connections, options = options_cross_product(options) snd_recorders = [] if not gopt.waterfall or (gopt.waterfall and gopt.sound): for i, opt in enumerate(options): opt.multiple_connections = multiple_connections opt.idx = i snd_recorders.append( KiwiWorker(args=(KiwiSoundRecorder(opt), opt, run_event))) wf_recorders = [] if gopt.waterfall: for i, opt in enumerate(options): opt.multiple_connections = multiple_connections opt.idx = i wf_recorders.append( KiwiWorker(args=(KiwiWaterfallRecorder(opt), opt, run_event))) try: for i, r in enumerate(snd_recorders): if opt.launch_delay != 0 and i != 0 and options[ i - 1].server_host == options[i].server_host: time.sleep(opt.launch_delay) r.start() #logging.info("started sound recorder %d, timestamp=%d" % (i, options[i].timestamp)) logging.info("started sound recorder %d" % i) for i, r in enumerate(wf_recorders): if i != 0 and options[i - 1].server_host == options[i].server_host: time.sleep(opt.launch_delay) r.start() logging.info("started waterfall recorder %d" % i) while run_event.is_set(): time.sleep(.1) except KeyboardInterrupt: run_event.clear() join_threads(snd_recorders, wf_recorders) print("KeyboardInterrupt: threads successfully closed") except Exception as e: print_exc() run_event.clear() join_threads(snd_recorders, wf_recorders) print("Exception: threads successfully closed") if gopt.is_kiwi_tdoa: for i, opt in enumerate(options): # NB: MUST be a print (i.e. not a logging.info) print("status=%d,%d" % (i, opt.status)) logging.debug('gc %s' % gc.garbage)
def main(): # extend the OptionParser so that we can print multiple paragraphs in # the help text class MyParser(OptionParser): def format_description(self, formatter): result = [] for paragraph in self.description: result.append(formatter.format_description(paragraph)) return "\n".join(result[:-1]) # drop last \n def format_epilog(self, formatter): result = [] for paragraph in self.epilog: result.append(formatter.format_epilog(paragraph)) return "".join(result) usage = "%prog -s SERVER -p PORT -f FREQ -m MODE [other options]" description = [ "kiwiclientd.py receives audio from a KiwiSDR and plays" " it to a (virtual) sound device. This can be used to" " send KiwiSDR audio to various programs to decode the" " received signals." " This program also accepts hamlib rigctl commands over" " a network socket to change the kiwisdr frequency" " To stream multiple KiwiSDR channels at once, use the" " same syntax, but pass a list of values (where applicable)" " instead of single values. For example, to stream" " two KiwiSDR channels in USB to the virtual sound cards" " kiwisdr0 & kiwisdr1, with the rigctl ports 6400 &" " 6401 respectively, run the following:", "$ kiwiclientd.py -s kiwisdr.example.com -p 8073 -f 10000 -m usb --snddev kiwisnd0,kiwisnd1 --rigctl-port 6400,6401", "" ] epilog = [] # text here would go after the options list parser = MyParser(usage=usage, description=description, epilog=epilog) parser.add_option('-s', '--server-host', dest='server_host', type='string', default='localhost', help='Server host (can be a comma-separated list)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option( '-p', '--server-port', dest='server_port', type='string', default=8073, help='Server port, default 8073 (can be a comma-separated list)', action='callback', callback_args=(int, ), callback=get_comma_separated_args) parser.add_option( '--pw', '--password', dest='password', type='string', default='', help='Kiwi login password (if required, can be a comma-separated list)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option( '--tlimit-pw', '--tlimit-password', dest='tlimit_password', type='string', default='', help= 'Connect time limit exemption password (if required, can be a comma-separated list)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option( '-u', '--user', dest='user', type='string', default='kiwiclientd', help='Kiwi connection user name (can be a comma-separated list)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option( '--log', '--log-level', '--log_level', type='choice', dest='log_level', default='warn', choices=['debug', 'info', 'warn', 'error', 'critical'], help='Log level: debug|info|warn(default)|error|critical') parser.add_option('-q', '--quiet', dest='quiet', default=False, action='store_true', help='Don\'t print progress messages') parser.add_option( '--tlimit', '--time-limit', dest='tlimit', type='float', default=None, help='Record time limit in seconds. Ignored when --dt-sec used.') parser.add_option('--launch-delay', '--launch_delay', dest='launch_delay', type='int', default=0, help='Delay (secs) in launching multiple connections') parser.add_option( '--connect-retries', '--connect_retries', dest='connect_retries', type='int', default=0, help= 'Number of retries when connecting to host (retries forever by default)' ) parser.add_option('--connect-timeout', '--connect_timeout', dest='connect_timeout', type='int', default=15, help='Retry timeout(sec) connecting to host') parser.add_option('-k', '--socket-timeout', '--socket_timeout', dest='socket_timeout', type='int', default=10, help='Socket timeout(sec) during data transfers') parser.add_option( '--OV', dest='ADC_OV', default=False, action='store_true', help='Print "ADC OV" message when Kiwi ADC is overloaded') parser.add_option('-v', '-V', '--version', dest='krec_version', default=False, action='store_true', help='Print version number and exit') group = OptionGroup(parser, "Audio connection options", "") group.add_option( '-f', '--freq', dest='frequency', type='string', default=1000, help='Frequency to tune to, in kHz (can be a comma-separated list)', action='callback', callback_args=(float, ), callback=get_comma_separated_args) group.add_option( '-m', '--modulation', dest='modulation', type='string', default='am', help= 'Modulation; one of am, lsb, usb, cw, nbfm, iq (default passband if -L/-H not specified)' ) group.add_option('--ncomp', '--no_compression', dest='compression', default=True, action='store_false', help='Don\'t use audio compression') group.add_option('-L', '--lp-cutoff', dest='lp_cut', type='float', default=None, help='Low-pass cutoff frequency, in Hz') group.add_option('-H', '--hp-cutoff', dest='hp_cut', type='float', default=None, help='High-pass cutoff frequency, in Hz') group.add_option( '-r', '--resample', dest='resample', type='int', default=0, help= 'Resample output file to new sample rate in Hz. The resampling ratio has to be in the range [1/256,256]' ) group.add_option('-T', '--squelch-threshold', dest='thresh', type='float', default=None, help='Squelch threshold, in dB.') group.add_option( '--squelch-tail', dest='squelch_tail', type='float', default=1, help= 'Time for which the squelch remains open after the signal is below threshold.' ) group.add_option( '-g', '--agc-gain', dest='agc_gain', type='string', default=None, help= 'AGC gain; if set, AGC is turned off (can be a comma-separated list)', action='callback', callback_args=(float, ), callback=get_comma_separated_args) group.add_option('--nb', dest='nb', action='store_true', default=False, help='Enable noise blanker with default parameters.') group.add_option( '--nb-gate', dest='nb_gate', type='int', default=100, help='Noise blanker gate time in usec (100 to 5000, default 100)') group.add_option( '--nb-th', '--nb-thresh', dest='nb_thresh', type='int', default=50, help='Noise blanker threshold in percent (0 to 100, default 50)') parser.add_option_group(group) group = OptionGroup(parser, "Sound device options", "") group.add_option( '--snddev', '--sound-device', dest='sounddevice', type='string', default='', action='callback', help='Sound device to play kiwi audio on (can be comma separated list)', callback_args=(str, ), callback=get_comma_separated_args) group.add_option('--ls-snd', '--list-sound-devices', dest='list_sound_devices', default=False, action='store_true', help='List available sound devices and exit') parser.add_option_group(group) group = OptionGroup(parser, "Rig control options", "") group.add_option('--rigctl', '--enable-rigctl', dest='rigctl_enabled', default=True, action='store_true', help='Enable rigctld backend for frequency changes.') group.add_option( '--rigctl-port', '--rigctl-port', dest='rigctl_port', type='string', default='6400', help= 'Port listening for rigctl commands (default 6400, can be comma separated list', action='callback', callback_args=(int, ), callback=get_comma_separated_args) group.add_option('--rigctl-addr', '--rigctl-address', dest='rigctl_address', type='string', default=None, help='Address to listen on (default 127.0.0.1)') parser.add_option_group(group) (options, unused_args) = parser.parse_args() ## clean up OptionParser which has cyclic references parser.destroy() if options.krec_version: print('kiwiclientd v1.0') sys.exit() if options.list_sound_devices: print(sc.all_speakers()) sys.exit() FORMAT = '%(asctime)-15s pid %(process)5d %(message)s' logging.basicConfig(level=logging.getLevelName(options.log_level.upper()), format=FORMAT) if options.log_level.upper() == 'DEBUG': gc.set_debug(gc.DEBUG_SAVEALL | gc.DEBUG_LEAK | gc.DEBUG_UNCOLLECTABLE) run_event = threading.Event() run_event.set() if options.tlimit is not None and options.dt != 0: print('Warning: --tlimit ignored when --dt-sec option used') options.sdt = 0 options.dir = None options.raw = False options.sound = True options.no_api = False options.tstamp = False options.station = None options.filename = None options.test_mode = False options.is_kiwi_wav = False options.is_kiwi_tdoa = False gopt = options multiple_connections, options = options_cross_product(options) snd_recorders = [] for i, opt in enumerate(options): opt.multiple_connections = multiple_connections opt.idx = i snd_recorders.append( KiwiWorker(args=(KiwiSoundRecorder(opt), opt, run_event))) try: for i, r in enumerate(snd_recorders): if opt.launch_delay != 0 and i != 0 and options[ i - 1].server_host == options[i].server_host: time.sleep(opt.launch_delay) r.start() #logging.info("started kiwi client %d, timestamp=%d" % (i, options[i].timestamp)) logging.info("started kiwi client %d" % i) while run_event.is_set(): time.sleep(.1) except KeyboardInterrupt: run_event.clear() join_threads(snd_recorders) print("KeyboardInterrupt: threads successfully closed") except Exception as e: print_exc() run_event.clear() join_threads(snd_recorders) print("Exception: threads successfully closed") logging.debug('gc %s' % gc.garbage)
def main(): parser = OptionParser() parser.add_option('-s', '--server-host', dest='server_host', type='string', default='localhost', help='Server host') parser.add_option('-p', '--server-port', dest='server_port', type='string', default="8073", help='Server port, default 8073') parser.add_option('--pw', '--password', dest='password', type='string', default='', help='Kiwi login password') parser.add_option('-u', '--user', dest='user', type='string', default='kiwirecorder.py', help='Kiwi connection user name') parser.add_option('-f', '--freq', dest='frequency', type='float', default=1000, help='Frequency to tune to, in kHz') parser.add_option('-z', '--zoom', dest='zoom', type='int', default=0, help='Zoom level 0-14') parser.add_option( '--station', dest='station', type='string', default=None, help='Station ID to be appended to filename', ) parser.add_option( '--log', '--log-level', '--log_level', type='choice', dest='log_level', default='warn', choices=['debug', 'info', 'warn', 'error', 'critical'], help='Log level: debug|info|warn(default)|error|critical') (opt, unused_args) = parser.parse_args() ## clean up OptionParser which has cyclic references parser.destroy() opt.is_kiwi_tdoa = False opt.socket_timeout = 10 opt.tlimit = None opt.no_api = True opt.raw = False opt.S_meter = -1 opt.ADC_OV = None FORMAT = '%(asctime)-15s pid %(process)5d %(message)s' logging.basicConfig(level=logging.getLevelName(opt.log_level.upper()), format=FORMAT) if opt.log_level.upper() == 'DEBUG': gc.set_debug(gc.DEBUG_SAVEALL | gc.DEBUG_LEAK | gc.DEBUG_UNCOLLECTABLE) run_event = threading.Event() run_event.set() snd_queue, wf_queue = [Queue(), Queue()] snd_recorder = KiwiWorker(args=(KiwiSoundRecorder(opt, snd_queue), opt, run_event)) wf_recorder = KiwiWorker(args=(KiwiWaterfallRecorder(opt, wf_queue), opt, run_event)) consumer = Consumer(args=(opt, snd_queue, wf_queue, run_event)) threads = [snd_recorder, wf_recorder, consumer] try: opt.start_time = time.time() opt.tstamp = int(time.time() + os.getpid()) & 0xffffffff opt.idx = 0 snd_recorder.start() opt.start_time = time.time() opt.tstamp = int(time.time() + os.getpid() + 1) & 0xffffffff opt.idx = 1 wf_recorder.start() consumer.start() while run_event.is_set(): time.sleep(.5) except KeyboardInterrupt: run_event.clear() join_threads(threads) print("KeyboardInterrupt: threads successfully closed") except Exception: print_exc() run_event.clear() join_threads(threads) print("Exception: threads successfully closed") logging.debug('gc %s' % gc.garbage)
def main(): parser = OptionParser() parser.add_option('-s', '--server-host', dest='server_host', type='string', default='localhost', help='Server host (can be a comma-delimited list)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option( '-p', '--server-port', dest='server_port', type='string', default=8073, help='Server port, default 8073 (can be a comma delimited list)', action='callback', callback_args=(int, ), callback=get_comma_separated_args) parser.add_option( '--pw', '--password', dest='password', type='string', default='', help='Kiwi login password (if required, can be a comma delimited list)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option( '--tlimit-pw', '--tlimit-password', dest='tlimit_password', type='string', default='', help= 'Connect time limit exemption password (if required, can be a comma delimited list)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option('-u', '--user', dest='user', type='string', default='kiwirecorder.py', help='Kiwi connection user name', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option( '--station', dest='station', type='string', default=None, help= 'Station ID to be appended to filename (can be a comma-separated list)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option( '--log', '--log-level', '--log_level', type='choice', dest='log_level', default='warn', choices=['debug', 'info', 'warn', 'error', 'critical'], help='Log level: debug|info|warn(default)|error|critical') parser.add_option('-q', '--quiet', dest='quiet', default=False, action='store_true', help='Don\'t print progress messages') parser.add_option('-d', '--dir', dest='dir', type='string', default=None, help='Optional destination directory for files') parser.add_option( '--fn', '--filename', dest='filename', type='string', default='', help= 'Use fixed filename instead of generated filenames (optional station ID(s) will apply)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option( '--tlimit', '--time-limit', dest='tlimit', type='float', default=None, help='Record time limit in seconds. Ignored when --dt-sec used.') parser.add_option('--dt-sec', dest='dt', type='int', default=0, help='Start a new file when mod(sec_of_day,dt) == 0') parser.add_option('--launch-delay', '--launch_delay', dest='launch_delay', type='int', default=0, help='Delay (secs) in launching multiple connections') parser.add_option( '--connect-retries', '--connect_retries', dest='connect_retries', type='int', default=0, help= 'Number of retries when connecting to host (retries forever by default)' ) parser.add_option('--connect-timeout', '--connect_timeout', dest='connect_timeout', type='int', default=15, help='Retry timeout(sec) connecting to host') parser.add_option('-k', '--socket-timeout', '--socket_timeout', dest='socket_timeout', type='int', default=10, help='Socket timeout(sec) during data transfers') parser.add_option( '--OV', dest='ADC_OV', default=False, action='store_true', help='Print "ADC OV" message when Kiwi ADC is overloaded') parser.add_option( '--ts', '--tstamp', '--timestamp', dest='tstamp', default=False, action='store_true', help='Add timestamps to output. Applies only to S-meter mode currently.' ) parser.add_option( '--stats', dest='stats', default=False, action='store_true', help= 'Print additional statistics. Applies only to S-meter mode currently.') parser.add_option( '--no-api', dest='no_api', default=False, action='store_true', help='Simulate connection to Kiwi using improper/incomplete API') group = OptionGroup(parser, "Audio connection options", "") group.add_option( '-f', '--freq', dest='frequency', type='string', default=1000, help='Frequency to tune to, in kHz (can be a comma-separated list)', action='callback', callback_args=(float, ), callback=get_comma_separated_args) group.add_option( '-m', '--modulation', dest='modulation', type='string', default='am', help= 'Modulation; one of am, lsb, usb, cw, nbfm, iq (default passband if -L/-H not specified)' ) group.add_option('--ncomp', '--no_compression', dest='compression', default=True, action='store_false', help='Don\'t use audio compression') group.add_option('-L', '--lp-cutoff', dest='lp_cut', type='float', default=None, help='Low-pass cutoff frequency, in Hz') group.add_option('-H', '--hp-cutoff', dest='hp_cut', type='float', default=None, help='High-pass cutoff frequency, in Hz') group.add_option( '-r', '--resample', dest='resample', type='int', default=0, help= 'Resample output file to new sample rate in Hz. The resampling ratio has to be in the range [1/256,256]' ) group.add_option('-T', '--squelch-threshold', dest='thresh', type='float', default=None, help='Squelch threshold, in dB.') group.add_option( '--squelch-tail', dest='squelch_tail', type='float', default=1, help= 'Time for which the squelch remains open after the signal is below threshold.' ) group.add_option( '-g', '--agc-gain', dest='agc_gain', type='string', default=None, help= 'AGC gain; if set, AGC is turned off (can be a comma-separated list)', action='callback', callback_args=(float, ), callback=get_comma_separated_args) group.add_option('--nb', dest='nb', action='store_true', default=False, help='Enable noise blanker with default parameters.') group.add_option( '--nb-gate', dest='nb_gate', type='int', default=100, help='Noise blanker gate time in usec (100 to 5000, default 100)') group.add_option( '--nb-th', '--nb-thresh', dest='nb_thresh', type='int', default=50, help='Noise blanker threshold in percent (0 to 100, default 50)') group.add_option( '-w', '--kiwi-wav', dest='is_kiwi_wav', default=False, action='store_true', help= 'In the wav file include KIWI header containing GPS time-stamps (only for IQ mode)' ) group.add_option('--kiwi-tdoa', dest='is_kiwi_tdoa', default=False, action='store_true', help='Used when called by Kiwi TDoA extension') group.add_option( '--test-mode', dest='test_mode', default=False, action='store_true', help='Write wav data to /dev/null (Linux) or NUL (Windows)') group.add_option( '--snd', '--sound', dest='sound', default=False, action='store_true', help= 'Also process sound data when in waterfall or S-meter mode (sound connection options above apply)' ) group.add_option( '-a', '--audio', dest='audio', default=False, action='store_true', help='Get audio output instead of writing to disk (mod by linkz)') parser.add_option_group(group) group = OptionGroup(parser, "S-meter mode options", "") group.add_option( '--S-meter', '--s-meter', dest='S_meter', type='int', default=-1, help= 'Report S-meter (RSSI) value after S_METER number of averages. S_METER=0 does no averaging and reports each RSSI value received. Options --ts and --stats apply.' ) parser.add_option('--sdt-sec', dest='sdt', type='int', default=0, help='S-meter measurement interval') parser.add_option_group(group) group = OptionGroup(parser, "Waterfall connection options", "") group.add_option('--wf', dest='waterfall', default=False, action='store_true', help='Process waterfall data instead of audio') group.add_option('-z', '--zoom', dest='zoom', type='int', default=0, help='Zoom level 0-14') parser.add_option_group(group) (options, unused_args) = parser.parse_args() ## clean up OptionParser which has cyclic references parser.destroy() FORMAT = '%(asctime)-15s pid %(process)5d %(message)s' logging.basicConfig(level=logging.getLevelName(options.log_level.upper()), format=FORMAT) if options.log_level.upper() == 'DEBUG': gc.set_debug(gc.DEBUG_SAVEALL | gc.DEBUG_LEAK | gc.DEBUG_UNCOLLECTABLE) run_event = threading.Event() run_event.set() if options.S_meter >= 0: if options.S_meter > 0 and options.sdt != 0: raise Exception( 'Options --S-meter > 0 and --sdt-sec != 0 are incompatible. Did you mean to use --S-meter=0 ?' ) options.quiet = True if options.tlimit is not None and options.dt != 0: print('Warning: --tlimit ignored when --dt-sec option used') options.raw = False gopt = options multiple_connections, options = options_cross_product(options) snd_recorders = [] if not gopt.waterfall or (gopt.waterfall and gopt.sound): for i, opt in enumerate(options): opt.multiple_connections = multiple_connections opt.idx = i snd_recorders.append( KiwiWorker(args=(KiwiSoundRecorder(opt), opt, run_event))) wf_recorders = [] if gopt.waterfall: for i, opt in enumerate(options): opt.multiple_connections = multiple_connections opt.idx = i wf_recorders.append( KiwiWorker(args=(KiwiWaterfallRecorder(opt), opt, run_event))) try: for i, r in enumerate(snd_recorders): if opt.launch_delay != 0 and i != 0 and options[ i - 1].server_host == options[i].server_host: time.sleep(opt.launch_delay) r.start() #logging.info("started sound recorder %d, timestamp=%d" % (i, options[i].timestamp)) logging.info("started sound recorder %d" % i) for i, r in enumerate(wf_recorders): if i != 0 and options[i - 1].server_host == options[i].server_host: time.sleep(opt.launch_delay) r.start() logging.info("started waterfall recorder %d" % i) while run_event.is_set(): time.sleep(.1) except KeyboardInterrupt: run_event.clear() join_threads(snd_recorders, wf_recorders) print("KeyboardInterrupt: threads successfully closed") except Exception as e: print_exc() run_event.clear() join_threads(snd_recorders, wf_recorders) print("Exception: threads successfully closed") if gopt.is_kiwi_tdoa: for i, opt in enumerate(options): # NB: MUST be a print (i.e. not a logging.info) print("status=%d,%d" % (i, opt.status)) logging.debug('gc %s' % gc.garbage)
def main(): parser = OptionParser() parser.add_option( '--log', '--log-level', '--log_level', type='choice', dest='log_level', default='warn', choices=['debug', 'info', 'warn', 'error', 'critical'], help='Log level: debug|info|warn(default)|error|critical') parser.add_option( '--progress', dest='progress', default=False, action='store_true', help='Print progress messages instead of output of binary data') parser.add_option('-k', '--socket-timeout', '--socket_timeout', dest='socket_timeout', type='int', default=10, help='Timeout(sec) for sockets') parser.add_option('-s', '--server-host', dest='server_host', type='string', default='localhost', help='Server host (can be a comma-delimited list)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option( '-p', '--server-port', dest='server_port', type='string', default=8073, help='Server port, default 8073 (can be a comma delimited list)', action='callback', callback_args=(int, ), callback=get_comma_separated_args) parser.add_option( '--pw', '--password', dest='password', type='string', default='', help='Kiwi login password (if required, can be a comma delimited list)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option('-u', '--user', dest='user', type='string', default='kiwirecorder.py', help='Kiwi connection user name', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option('--launch-delay', '--launch_delay', dest='launch_delay', type='int', default=0, help='Delay (secs) in launching multiple connections') parser.add_option( '-f', '--freq', dest='frequency', type='string', default=1000, help='Frequency to tune to, in kHz (can be a comma-separated list)', action='callback', callback_args=(float, ), callback=get_comma_separated_args) parser.add_option('-m', '--modulation', dest='modulation', type='string', default='am', help='Modulation; one of am, lsb, usb, cw, nbfm, iq') parser.add_option('--ncomp', '--no_compression', dest='compression', default=True, action='store_false', help='Don\'t use audio compression') parser.add_option('-L', '--lp-cutoff', dest='lp_cut', type='float', default=100, help='Low-pass cutoff frequency, in Hz') parser.add_option('-H', '--hp-cutoff', dest='hp_cut', type='float', default=2600, help='Low-pass cutoff frequency, in Hz') parser.add_option('--tlimit', '--time-limit', dest='tlimit', type='float', default=None, help='Record time limit in seconds') parser.add_option('-T', '--squelch-threshold', dest='thresh', type='float', default=None, help='Squelch threshold, in dB.') parser.add_option( '--squelch-tail', dest='squelch_tail', type='float', default=1, help= 'Time for which the squelch remains open after the signal is below threshold.' ) parser.add_option( '-g', '--agc-gain', dest='agc_gain', type='string', default=None, help= 'AGC gain; if set, AGC is turned off (can be a comma-separated list)', action='callback', callback_args=(float, ), callback=get_comma_separated_args) parser.add_option('--wf', '--waterfall', dest='waterfall', default=False, action='store_true', help='Process waterfall data instead of audio') parser.add_option( '--admin', dest='admin', default=False, action='store_true', help='Kiwi connection: admin instead of default audio stream.') (options, unused_args) = parser.parse_args() ## clean up OptionParser which has cyclic references parser.destroy() FORMAT = '%(asctime)-15s pid %(process)5d %(message)s' logging.basicConfig(level=logging.getLevelName(options.log_level.upper()), format=FORMAT) if options.log_level.upper() == 'DEBUG': gc.set_debug(gc.DEBUG_SAVEALL | gc.DEBUG_LEAK | gc.DEBUG_UNCOLLECTABLE) run_event = threading.Event() run_event.set() options.raw = True options.S_meter = -1 options.ADC_OV = None options.is_kiwi_tdoa = False options.no_api = False options.connect_retries = 0 options.connect_timeout = 3 gopt = options multiple_connections, options = options_cross_product(options) nc_inst = [] for i, opt in enumerate(options): opt.multiple_connections = multiple_connections opt.idx = 0 nc_inst.append(KiwiWorker(args=(KiwiNetcat(opt, True), opt, run_event))) opt.writer_init = False opt.idx = 1 nc_inst.append( KiwiWorker(args=(KiwiNetcat(opt, False), opt, run_event))) try: for i, r in enumerate(nc_inst): if opt.launch_delay != 0 and i != 0 and options[ i - 1].server_host == options[i].server_host: time.sleep(opt.launch_delay) r.start() #logging.info("started sound recorder %d, timestamp=%d" % (i, options[i].timestamp)) logging.info("started sound recorder %d" % i) while run_event.is_set(): time.sleep(.1) except KeyboardInterrupt: run_event.clear() join_threads(nc_inst) print("KeyboardInterrupt: threads successfully closed") except Exception as e: print_exc() run_event.clear() join_threads(nc_inst) print("Exception: threads successfully closed") logging.debug('gc %s' % gc.garbage)
def main(): parser = OptionParser() parser.add_option( '--log', '--log-level', '--log_level', type='choice', dest='log_level', default='warn', choices=['debug', 'info', 'warn', 'error', 'critical'], help='Log level: debug|info|warn(default)|error|critical') parser.add_option( '--progress', dest='progress', default=False, action='store_true', help='Print progress messages instead of output of binary data') parser.add_option('-k', '--socket-timeout', '--socket_timeout', dest='socket_timeout', type='int', default=10, help='Timeout(sec) for sockets') parser.add_option('-s', '--server-host', dest='server_host', type='string', default='localhost', help='Server host (can be a comma-delimited list)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option( '-p', '--server-port', dest='server_port', type='string', default=8073, help='Server port, default 8073 (can be a comma delimited list)', action='callback', callback_args=(int, ), callback=get_comma_separated_args) parser.add_option( '--pw', '--password', dest='password', type='string', default='', help='Kiwi login password (if required, can be a comma delimited list)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option( '--tlimit-pw', '--tlimit-password', dest='tlimit_password', type='string', default='', help= 'Connect time limit exemption password (if required, can be a comma-separated list)', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option('-u', '--user', dest='user', type='string', default='kiwi_nc.py', help='Kiwi connection user name', action='callback', callback_args=(str, ), callback=get_comma_separated_args) parser.add_option('--launch-delay', '--launch_delay', dest='launch_delay', type='int', default=0, help='Delay (secs) in launching multiple connections') parser.add_option( '-f', '--freq', dest='frequency', type='string', default=1000, help='Frequency to tune to, in kHz (can be a comma-separated list). ' 'For sideband modes (lsb/lsn/usb/usn/cw/cwn) this is the carrier frequency. See --pbc option below.', action='callback', callback_args=(float, ), callback=get_comma_separated_args) parser.add_option( '--pbc', '--freq-pbc', dest='freq_pbc', action='store_true', default=False, help= 'For sideband modes (lsb/lsn/usb/usn/cw/cwn) interpret -f/--freq frequency as the passband center frequency.' ) parser.add_option( '-m', '--modulation', dest='modulation', type='string', default='am', help= 'Modulation; one of am/amn, sam/sau/sal/sas/qam, lsb/lsn, usb/usn, cw/cwn, nbfm, iq (default passband if -L/-H not specified)' ) parser.add_option( '--ncomp', '--no_compression', dest='compression', default=True, action='store_false', help='Don\'t use audio compression (IQ mode never uses compression)') parser.add_option('-L', '--lp-cutoff', dest='lp_cut', type='float', default=100, help='Low-pass cutoff frequency, in Hz') parser.add_option('-H', '--hp-cutoff', dest='hp_cut', type='float', default=2600, help='Low-pass cutoff frequency, in Hz') parser.add_option('--tlimit', '--time-limit', dest='tlimit', type='float', default=None, help='Record time limit in seconds') parser.add_option('-T', '--squelch-threshold', dest='thresh', type='float', default=None, help='Squelch threshold, in dB.') parser.add_option( '--squelch-tail', dest='squelch_tail', type='float', default=1, help= 'Time for which the squelch remains open after the signal is below threshold.' ) parser.add_option( '-g', '--agc-gain', dest='agc_gain', type='string', default=None, help= 'AGC gain; if set, AGC is turned off (can be a comma-separated list)', action='callback', callback_args=(float, ), callback=get_comma_separated_args) parser.add_option('--agc-decay', dest='agc_decay', type='int', default=1000, help='AGC decay (msec); if set, AGC is turned on') parser.add_option('--agc-yaml', dest='agc_yaml_file', type='string', default=None, help='AGC options provided in a YAML-formatted file') parser.add_option('--de-emp', dest='de_emp', action='store_true', default=False, help='Enable de-emphasis.') parser.add_option('--wf', '--waterfall', dest='waterfall', default=False, action='store_true', help='Process waterfall data instead of audio') group = OptionGroup(parser, "KiwiSDR development options", "") group.add_option('--gc-stats', dest='gc_stats', default=False, action='store_true', help='Print garbage collection stats') group.add_option( '--admin', dest='admin', default=False, action='store_true', help='Kiwi connection: admin instead of default audio stream.') parser.add_option_group(group) (options, unused_args) = parser.parse_args() ## clean up OptionParser which has cyclic references parser.destroy() FORMAT = '%(asctime)-15s pid %(process)5d %(message)s' logging.basicConfig(level=logging.getLevelName(options.log_level.upper()), format=FORMAT) if options.gc_stats: gc.set_debug(gc.DEBUG_SAVEALL | gc.DEBUG_LEAK | gc.DEBUG_UNCOLLECTABLE) run_event = threading.Event() run_event.set() ### decode AGC YAML file options options.agc_yaml = None if options.agc_yaml_file: try: if not HAS_PyYAML: raise Exception( 'PyYAML not installed: sudo apt install python-yaml / sudo apt install python3-yaml / pip install pyyaml / pip3 install pyyaml' ) with open(options.agc_yaml_file) as yaml_file: documents = yaml.full_load(yaml_file) logging.debug('AGC file %s: %s' % (options.agc_yaml_file, documents)) logging.debug('Got AGC parameters from file %s: %s' % (options.agc_yaml_file, documents['AGC'])) options.agc_yaml = documents['AGC'] except KeyError: logging.fatal('The YAML file does not contain AGC options') return except Exception as e: logging.fatal(e) return options.raw = True options.S_meter = -1 options.ADC_OV = None options.is_kiwi_tdoa = False options.no_api = False options.connect_retries = 0 options.connect_timeout = 3 options.rigctl_enabled = False gopt = options multiple_connections, options = options_cross_product(options) nc_inst = [] for i, opt in enumerate(options): opt.multiple_connections = multiple_connections opt.idx = 0 nc_inst.append(KiwiWorker(args=(KiwiNetcat(opt, True), opt, run_event))) if gopt.admin: opt.writer_init = False opt.idx = 1 nc_inst.append( KiwiWorker(args=(KiwiNetcat(opt, False), opt, run_event))) try: for i, r in enumerate(nc_inst): if opt.launch_delay != 0 and i != 0 and options[ i - 1].server_host == options[i].server_host: time.sleep(opt.launch_delay) r.start() #logging.info("started sound recorder %d, timestamp=%d" % (i, options[i].timestamp)) logging.info("started sound recorder %d" % i) while run_event.is_set(): time.sleep(.1) except KeyboardInterrupt: run_event.clear() join_threads(nc_inst) print("KeyboardInterrupt: threads successfully closed") except Exception as e: print_exc() run_event.clear() join_threads(nc_inst) print("Exception: threads successfully closed") if gopt.gc_stats: logging.debug('gc %s' % gc.garbage)