def set_spacing(s): """Sets the Spacing (for Farnsworth timing) to None (disabled) `Spacing.none`, Character `Spacing.char` or Word `Spacing.word` When set to `Spacing.none` Farnsworth spacing will not be added. When set to `Spacing.char` Farnsworth spacing will be added between characters. When set to `Spacing.word` Farnsworth spacing will be added between words. Parameters ---------- s : str The value `N|NONE` will set the spacing to `Spacing.none` (disabled). The value `C|CHAR` will set the spacing to `Spacing.char`. The value `W|WORD` will set the spacing to `Spacing.word`. """ global spacing s = s.upper() if s == "N" or s == "NONE": spacing = Spacing.none elif s == "C" or s == "CHAR" or s == "CHARACTER": spacing = Spacing.char elif s == "W" or s == "WORD": spacing = Spacing.word else: msg = "SPACING value '{}' is not a valid `Spacing` value of 'NONE', 'CHAR' or 'WORD'.".format( s) log.err(msg) raise ValueError(msg) user_config.set(__CONFIG_SECTION, __SPACING_KEY, spacing.name.upper())
def set_min_char_speed(s): """Sets the minimum character speed in words per minute A difference between character speed (in WPM) and text speed (in WPM) is used to calulate a Farnsworth timing value. This is the minimum character speed. If the text speed is higher, then the character speed will be bumped up to the text speed. Parameters ---------- s : str The speed in words-per-minute as an interger string value """ global min_char_speed try: _speed = int(s) min_char_speed = _speed user_config.set(__CONFIG_SECTION, __MIN_CHAR_SPEED_KEY, str(min_char_speed)) except ValueError as ex: log.err( "CHARS value '{}' is not a valid integer value. Not setting CWPM value." .format(ex.args[0])) raise
def getArticles(url): if url[:7] == 'file://': s = open(os.getcwd() + '/' + url[7:]).read() else: retry = True while retry: try: s = urlopen(url).read().decode('utf-8') retry = False except: log.err('Can\'t open {}.'.format(url)) time.sleep(5) articles = re.findall('<item>.*?</item>', s, re.IGNORECASE + re.DOTALL) for i in range(0, len(articles)): title, description, pubDate = None, None, None m = re.search('<title>(<!\[CDATA\[)?(.*?)(\]\]>)?</title>', articles[i], re.IGNORECASE + re.DOTALL) if m: title = m.group(2) m = re.search('<description>(<!\[CDATA\[)?(.*?)(\]\]>)?</description>', articles[i], re.IGNORECASE + re.DOTALL) if m: description = m.group(2) flags = re.IGNORECASE + re.DOTALL description = re.sub('<.*?>', '', description, 0, flags) description = re.sub('&#039;', "'", description, 0, flags) description = re.sub('&.*?;', ' ', description, 0, flags) m = re.search('<pubDate>(.*?)</pubDate>', articles[i], re.IGNORECASE + re.DOTALL) if m: pubDate = m.group(1) articles[i] = title, description, pubDate return articles
def set_interface_type(s): """Sets the Interface Type (for Key-Sounder, Loop or Keyer) Parameters ---------- s : str The value `KS|KEY_SOUNDER` will set the interface type to 'InterfaceType.key_sounder'. The value `L|LOOP` will set the interface type to 'InterfaceType.loop'. The value `K|KEYER` will set the interface type to 'InterfaceType.keyer'. """ global interface_type s = s.upper() if s == "KS" or s == "KEY_SOUNDER": interface_type = InterfaceType.key_sounder elif s == "L" or s == "LOOP": interface_type = InterfaceType.loop elif s == "K" or s == "KEYER": interface_type = InterfaceType.keyer else: msg = "TYPE value '{}' is not a valid `Interface Type` value of 'KEY_SOUNDER', 'LOOP' or 'KEYER'.".format( s) log.err(msg) raise ValueError(msg) user_config.set(__CONFIG_SECTION, __INTERFACE_TYPE_KEY, interface_type.name.upper())
def set_auto_connect(s): """Sets the Auto Connect to wire enable state When set to `True` via a value of "TRUE"/"ON"/"YES" the application should automatically connect to the configured wire. Note that this is a 'suggestion'. It isn't used by the base pykob modules. It should be used by applications (like MKOB) to initiate a connection to the configured wire. Parameters ---------- s : str The enable/disable state to set as a string. Values of `YES`|`ON`|`TRUE` will enable auto-connect. Values of `NO`|`OFF`|`FALSE` will disable auto-connect. """ global auto_connect try: auto_connect = strtobool(str(s)) user_config.set(__CONFIG_SECTION, __AUTO_CONNECT_KEY, onOffFromBool(auto_connect)) except ValueError as ex: log.err( "Auto Connect value '{}' is not a valid boolean value. Not setting value." .format(ex.args[0])) raise
def callback(in_data, frame_count, time_info, status_flags): if frame_count != BUFFERSIZE: log.err('Unexpected frame count request from PyAudio:', frame_count) if iFrame[sound] + frame_count < nFrames[sound]: startByte = iFrame[sound] * frameWidth endByte = (iFrame[sound] + frame_count) * frameWidth outData = frames[sound][startByte:endByte] iFrame[sound] += frame_count return (outData, pyaudio.paContinue) else: return (nullFrames, pyaudio.paContinue)
def setSounder(self, state): try: if state != self.sdrState: self.sdrState = state if state: if self.port: self.port.rts = True if self.audio: audio.play(1) # click else: if self.port: self.port.rts = False if self.audio: audio.play(0) # clack except(OSError): log.err("Port not available.") self.port = None
def set_wire(w: str): """Sets the wire to connect to Parameters ---------- w : str The Wire number """ global wire try: _wire = int(w) wire = _wire user_config.set(__CONFIG_SECTION, __WIRE_KEY, str(wire)) except ValueError as ex: log.err("Wire number value '{}' is not a valid integer value.".format( ex.args[0])) raise
def set_text_speed(s): """Sets the Text (code) speed in words per minute Parameters ---------- s : str The text speed in words-per-minute as an interger string value """ global text_speed try: _speed = int(s) text_speed = _speed user_config.set(__CONFIG_SECTION, __TEXT_SPEED_KEY, str(text_speed)) except ValueError as ex: log.err("Text speed value '{}' is not a valid integer value.".format( ex.args[0])) raise
def set_local(l): """Enable/disable local copy When local copy is enabled, the local sound/sounder configuration is used to locally sound the content being sent to the wire. Parameters ---------- l : str The enable/disable state to set as a string. Values of `YES`|`ON`|`TRUE` will enable local copy. Values of `NO`|`OFF`|`FALSE` will disable local copy. """ global local try: local = strtobool(str(l)) user_config.set(__CONFIG_SECTION, __LOCAL_KEY, onOffFromBool(local)) except ValueError as ex: log.err( "LOCAL value '{}' is not a valid boolean value. Not setting value." .format(ex.args[0])) raise
def set_code_type(s): """Sets the Code Type (for American or International) Parameters ---------- s : str The value `A|AMERICAN` will set the code type to 'American'. The value `I|INTERNATIONAL` will set the code type to 'International'. """ global code_type s = s.upper() if s == "A" or s == "AMERICAN": code_type = CodeType.american elif s == "I" or s == "INTERNATIONAL": code_type = CodeType.international else: msg = "TYPE value '{}' is not a valid `Code Type` value of 'AMERICAN' or 'INTERNATIONAL'.".format( s) log.err(msg) raise ValueError(msg) user_config.set(__CONFIG_SECTION, __CODE_TYPE_KEY, code_type.name.upper())
def set_sound(s): """Sets the Sound/Audio enable state When set to `True` via a value of "TRUE"/"ON"/"YES" the computer audio will be used to produce sounder output. Parameters ---------- s : str The enable/disable state to set as a string. Values of `YES`|`ON`|`TRUE` will enable sound. Values of `NO`|`OFF`|`FALSE` will disable sound. """ global sound try: sound = strtobool(str(s)) user_config.set(__CONFIG_SECTION, __SOUND_KEY, onOffFromBool(sound)) except ValueError as ex: log.err( "SOUND value '{}' is not a valid boolean value. Not setting value." .format(ex.args[0])) raise
def set_remote(r): """Enable/disable remote send When remote send is enabled, the content will be sent to the wire configured. Parameters ---------- r : str The enable/disable state to set as a string. Values of `YES`|`ON`|`TRUE` will enable remote send. Values of `NO`|`OFF`|`FALSE` will disable remote send. """ global remote try: remote = strtobool(str(r)) user_config.set(__CONFIG_SECTION, __REMOTE_KEY, onOffFromBool(remote)) except ValueError as ex: log.err( "REMOTE value '{}' is not a valid boolean value. Not setting value." .format(ex.args[0])) raise
def key(self): code = () while self.port: try: s = self.port.dsr if not config.invert_key_input else not self.port.dsr # invert for RS-323 modem signal except(OSError): log.err("Port not available.") self.port = None return "" t = time.time() if s != self.keyState: self.keyState = s dt = int((t - self.tLastKey) * 1000) self.tLastKey = t if self.interfaceType == config.interface_type.key_sounder: self.setSounder(s) time.sleep(DEBOUNCE) if s: code += (-dt,) elif self.cktClose: code += (-dt, +2) # unlatch closed circuit self.cktClose = False return code else: code += (dt,) if not s and code and \ t > self.tLastKey + CODESPACE: return code if s and not self.cktClose and \ t > self.tLastKey + CKTCLOSE: code += (+1,) # latch circuit closed self.cktClose = True return code if len(code) >= 50: # code sequences can't have more than 50 elements return code time.sleep(0.001) return ""
def __init__( self, port=None, interfaceType=config.interface_type.loop, audio=False, callback=None): self.t0 = -1.0 ### ZZZ Keep track of when the playback started self.callback = callback if port and serialAvailable: try: self.port = serial.Serial(port) self.port.dtr = True except: log.info("Interface for key and/or sounder on serial port '{}' not available. Key and sounder will not be used.".format(port)) self.port = None self.callback = None else: self.port = None self.callback = None self.audio = audio self.interfaceType = interfaceType self.sdrState = False # True: mark, False: space self.tLastSdr = time.time() # time of last sounder transition self.setSounder(True) time.sleep(0.5) # ZZZ Why is this here? if self.port: try: self.keyState = self.port.dsr if not config.invert_key_input else not self.port.dsr # True: closed, False: open self.tLastKey = time.time() # time of last key transition self.cktClose = self.keyState # True: circuit latched closed if self.interfaceType == config.interface_type.key_sounder: self.setSounder(self.keyState) except(OSError): log.err("Port not available.") self.port = None self.recorder = None if self.callback: keyreadThread = threading.Thread(name='KOB-KeyRead', daemon=True, target=self.callbackRead) keyreadThread.start()
def set_invert_key_input(b): """ Enable/disable key input signal (DSR) invert. When key-invert is enabled, the key input (DSR on the serial interface) is inverted (because the RS-232 logic is inverted). This is primarily used when the input is from a modem (in dial-up connection). Parameters ---------- b : string 'true/false' The enable/disable state to set as a string. Values of `YES`|`ON`|`TRUE` will enable key invert. Values of `NO`|`OFF`|`FALSE` will disable key invert. """ global invert_key_input try: invert_key_input = strtobool(str(b)) user_config.set(__CONFIG_SECTION, __INVERT_KEY_INPUT_KEY, onOffFromBool(invert_key_input)) except ValueError as ex: log.err( "INVERT KEY INPUT value '{}' is not a valid boolean value. Not setting value." .format(ex.args[0])) raise
def set_sounder(s): """Sets the Sounder enable state When set to `True` via a value of "TRUE"/"ON"/"YES" the sounder will be driven if the `port` value is configured. Parameters ---------- s : str The enable/disable state to set as a string. Values of `YES`|`ON`|`TRUE` will enable sounder output. Values of `NO`|`OFF`|`FALSE` will disable sounder output. """ global sounder try: sounder = strtobool(str(s)) user_config.set(__CONFIG_SECTION, __SOUNDER_KEY, onOffFromBool(sounder)) except ValueError as ex: log.err( "SOUNDER value '{}' is not a valid boolean value. Not setting value." .format(ex.args[0])) raise
# The code speed for the feed: wpm = args.speed # The cwpm: cwpm = args.min_char_speed # Code type: American or International args_code_type = args.code_type.upper() if args_code_type == "A" or args_code_type == "AMERICAN": code_type = config.CodeType.american elif args_code_type == "I" or args_code_type == "INTERNATIONAL": code_type = config.CodeType.international else: msg = "TYPE value '{}' is not a valid `Code Type` value of 'AMERICAN' or 'INTERNATIONAL'.".format( s) log.err(msg) raise ValueError(msg) # Pause between articles (in seconds): artPause = args.artPause # Default group pause to article pause value if no group pause value is supplied grpPause = args.grpPause if args.grpPause > 0.0 else args.artPause # Number of days (from today) of articles to read before repeating: days = args.days # The wait time (in seconds) after someone else transmits before resuming feed: wait = args.wait playback_finished = threading.Event()
def sendForecast(msg): m = re.search(r'WX(.{4,5})\+', msg) if not m: send('~ ? ' + msg[1:-1] + ' ? +') log.log('Weather.py: Invalid request') return station = m.group(1) # Station data url = 'https://api.weather.gov/stations/' + station req = Request(url) req.add_header('User-Agent', 'MorseKOB/[email protected]') req.add_header('Accept', 'application/geo+json') try: s = urlopen(req).read().decode('utf-8') except: send('~ WX ' + station + ' UNKNOWN +') log.err('Weather.py: Can\'t open ' + url) return if DEBUG: print('Station data:', s) m = re.search( r'"coordinates":\s*\[\s*(.+?),\s+(.+?)\s*\].*?"name":\s*"(.*?)[,"]', s, FLAGS) if not m: send('~ WX ' + station + ' UNAVAILABLE +') log.log('Weather.py: Can\'t find forecast for ' + station) return lon = m.group(1) lat = m.group(2) name = m.group(3) if DEBUG: print('lon, lat, name:', lon, lat, name) send('~ WX FOR ' + name + ' = ') # Current conditions url = 'https://api.weather.gov/stations/' + station + '/observations/current' req = Request(url) req.add_header('User-Agent', 'MorseKOB/[email protected]') req.add_header('Accept', 'application/geo+json') try: s = urlopen(req).read().decode('utf-8') except: send('CURRENT WX UNAVAILABLE +') log.err('Weather.py: Can\'t open ' + url) return if DEBUG: print('Current wx data:', s) m = re.search( r'"timestamp":\s*"(.*?)".*?"textDescription":\s*"(.*?)".*?"temperature":.*?"value":\s*(.*?),', s, FLAGS) if not m: send('CURRENT WX MISSING +') log.log('Weather.py: Can\'t parse forecast ' + station) return timestamp = m.group(1) cdx = m.group(2) temp = m.group(3) if DEBUG: print('time, cdx, temp:', timestamp, cdx, temp) try: t = int(float(temp) * 1.8 + 32.5) send('NOW {} AND {} DEG = '.format(cdx, t)) except: send('NOW {} = '.format(cdx)) log.log('Weather.py: Current temp error: ' + temp) # Forecast url = 'https://api.weather.gov/points/{},{}/forecast'.format(lat, lon) req = Request(url) req.add_header('User-Agent', 'MorseKOB/[email protected]') req.add_header('Accept', 'application/geo+json') try: s = urlopen(req).read().decode('utf-8') except: send('FORECAST UNAVAILABLE +') log.err('Weather.py: Can\'t open ' + url) return if DEBUG: print('Forecast data:', s) m = re.search( r'"name":\s*"(.*?)".*?"detailedForecast":\s*"(.*?)".*?"name":\s*"(.*?)".*?"detailedForecast":\s*"(.*?)"', s, FLAGS) if not m: send('FORECAST MISSING +') log.log('Weather.py: Can\'t parse forecast ' + station) return time1 = m.group(1) forecast1 = m.group(2) time2 = m.group(3) forecast2 = m.group(4) if DEBUG: print('time1, forecast1:', time1, forecast1) print('time2, forecast2:', time2, forecast2) send(' {}. {} = {}. {} = 30 +'.format(time1, forecast1, time2, forecast2))
myInternet = internet.Internet(IDTEXT) myInternet.connect(WIRE) myReader = morse.Reader(callback=readerCallback) mySender = morse.Sender(WPM) myKOB = kob.KOB(port=None, audio=False) myReader.setWPM(WPM) code = [] bracket = False msg = '' while True: try: code += myInternet.read() if code[-1] == 1: log.log('Weather.py: {}'.format(code)) myReader.decode(code) myReader.flush() code = [] bracket = False msg = '' except: log.err('Weather.py: Recovering from fatal error.') time.sleep(30) code = [] bracket = False msg = '' except KeyboardInterrupt: print() sys.exit( 0 ) # Since the main program is an infinite loop, ^C is a normal, successful exit.
def read_config(): """Read the configuration values from the user and machine config files. """ global hostname global platform_name global os_name global pykob_version global python_version global pyaudio_version global pyserial_version global system_name global system_version global app_config global app_config_file_path global user_config global user_config_file_path global user_home global user_name # global serial_port # global auto_connect global code_type global interface_type global invert_key_input global local global min_char_speed global remote global server_url global sound global sounder global spacing global station global wire global text_speed # Get the system data try: user_name = getpass.getuser() user_home = os.path.expanduser('~') os_name = os.name system_name = platform.system() system_version = platform.release() platform_name = sys.platform pykob_version = pykob.VERSION python_version = "{}.{}.{}".format(sys.version_info.major, sys.version_info.minor, sys.version_info.micro) try: import pyaudio pyaudio_version = pyaudio.__version__ # NOTE: Using '__" property - not recommended, but only way to get version except: pyaudio_version = "PyAudio is not installed or the version information is not available (check installation)" try: import serial pyserial_version = serial.VERSION except: pyserial_version = "PySerial is not installed or the version information is not available (check installation)" hostname = socket.gethostname() # User configuration file name userConfigFileName = "config-{}.ini".format(user_name) app_configFileName = "config_app.ini" # Create the user and application configuration paths if system_name == "Windows": user_config_file_path = os.path.join( os.environ["LOCALAPPDATA"], os.path.normcase(os.path.join(__APP_NAME, userConfigFileName))) app_config_file_path = os.path.join( os.environ["ProgramData"], os.path.normcase(os.path.join(__APP_NAME, app_configFileName))) elif system_name == "Linux" or system_name == "Darwin": # Linux or Mac user_config_file_path = os.path.join( user_home, os.path.normcase( os.path.join(".{}".format(__APP_NAME), userConfigFileName))) app_config_file_path = os.path.join( user_home, os.path.normcase( os.path.join(".{}".format(__APP_NAME), app_configFileName))) else: log.err("Unknown System name") exit except KeyError as ex: log.err("Key '{}' not found in environment.".format(ex.args[0])) exit create_config_files_if_needed() user_config_defaults = {\ __AUTO_CONNECT_KEY:"OFF", \ __CODE_TYPE_KEY:"AMERICAN", \ __INTERFACE_TYPE_KEY:"LOOP", \ __INVERT_KEY_INPUT_KEY:"OFF", \ __LOCAL_KEY:"ON", \ __MIN_CHAR_SPEED_KEY:"18", \ __REMOTE_KEY:"ON", \ __SERVER_URL_KEY:"NONE", \ __SOUND_KEY:"ON", \ __SOUNDER_KEY:"OFF", \ __SPACING_KEY:"NONE", \ __STATION_KEY:"", \ __WIRE_KEY:"", \ __TEXT_SPEED_KEY:"18"} app_config_defaults = {"PORT": ""} user_config = configparser.ConfigParser(defaults=user_config_defaults, allow_no_value=True, default_section=__CONFIG_SECTION) app_config = configparser.ConfigParser(defaults=app_config_defaults, allow_no_value=True, default_section=__CONFIG_SECTION) user_config.read(user_config_file_path) app_config.read(app_config_file_path) try: # Get the System (App) config values serial_port = app_config.get(__CONFIG_SECTION, __SERIAL_PORT_KEY) # If there isn't a PORT value set PORT to None if not serial_port: serial_port = None # Get the User config values __option = "Auto Connect to Wire" __key = __AUTO_CONNECT_KEY auto_connect = user_config.getboolean(__CONFIG_SECTION, __key) __option = "Code type" __key = __CODE_TYPE_KEY _code_type = (user_config.get(__CONFIG_SECTION, __key)).upper() if _code_type == "AMERICAN": code_type = CodeType.american elif _code_type == "INTERNATIONAL": code_type = CodeType.international else: raise ValueError(_code_type) __option = "Interface type" __key = __INTERFACE_TYPE_KEY _interface_type = (user_config.get(__CONFIG_SECTION, __key)).upper() if _interface_type == "KEY_SOUNDER": interface_type = InterfaceType.key_sounder elif _interface_type == "LOOP": interface_type = InterfaceType.loop elif _interface_type == "KEYER": interface_type = InterfaceType.keyer else: raise ValueError(_interface_type) __option = "Invert key input" __key = __INVERT_KEY_INPUT_KEY invert_key_input = user_config.getboolean(__CONFIG_SECTION, __key) __option = "Local copy" __key = __LOCAL_KEY local = user_config.getboolean(__CONFIG_SECTION, __key) __option = "Minimum character speed" __key = __MIN_CHAR_SPEED_KEY min_char_speed = user_config.getint(__CONFIG_SECTION, __key) __option = "Remote send" __key = __REMOTE_KEY remote = user_config.getboolean(__CONFIG_SECTION, __key) __option = "Text speed" __key = __TEXT_SPEED_KEY text_speed = user_config.getint(__CONFIG_SECTION, __key) __option = "Server URL" __key = __SERVER_URL_KEY _server_url = user_config.get(__CONFIG_SECTION, __key) if (not _server_url) or (_server_url.upper() != "NONE"): server_url = _server_url __option = "Sound" __key = __SOUND_KEY sound = user_config.getboolean(__CONFIG_SECTION, __key) __option = "Sounder" __key = __SOUNDER_KEY sounder = user_config.getboolean(__CONFIG_SECTION, __key) __option = "Spacing" __key = __SPACING_KEY _spacing = (user_config.get(__CONFIG_SECTION, __key)).upper() if _spacing == "NONE": spacing = Spacing.none elif _spacing == "CHAR": spacing = Spacing.char elif _spacing == "WORD": spacing = Spacing.word else: raise ValueError(_spacing) __option = "Station" __key = __STATION_KEY _station = user_config.get(__CONFIG_SECTION, __key) if (not _station) or (_station.upper() != "NONE"): station = _station __option = "Wire" __key = __WIRE_KEY _wire = user_config.get(__CONFIG_SECTION, __key) if (_wire) or (_wire.upper() != "NONE"): try: wire = int(_wire) except ValueError as ex: # log.err("Wire number value '{}' is not a valid integer value.".format(_wire)) wire = 1 except KeyError as ex: log.err("Key '{}' not found in configuration file.".format(ex.args[0])) raise except ValueError as ex: log.err("{} option value '{}' is not a valid value. INI file key: {}.". format(__option, ex.args[0], __key)) raise
def playback_start(self, list_data=False, max_silence=0, speed_factor=100): """ Play a recording to the configured sounder. """ self.playback_stop() self.__playback_resume_flag.clear() self.__playback_stop_flag.clear() self.__p_fts = -1 self.__p_lts = 0 self.__p_stations.clear() self.__p_fpts_index = [] self.__p_line_no = 0 self.__p_lines = 0 self.__recorder_station_id = None self.__recorder_wire = None self.__list_data = list_data self.__max_silence = max_silence self.__speed_factor = speed_factor # # Get information from the current playback recording file. with open(self.__source_file_path, "r") as fp: self.__p_fpts_index.append( (0, 0, False)) # Store line 0 as Time=0, Pos=0, Sender-Change=False previous_station = None # NOTE: Can't iterate over the file lines as it disables `tell()` and `seek()`. line = fp.readline() while line: try: fpos = fp.tell() data = json.loads(line) ts = data['ts'] wire = data['w'] station = data['s'] ts = data['ts'] station = data['s'] # Store the file position and timestamp in the index to use # for seeking to a line based on time or line number self.__p_fpts_index.append( (ts, fpos, station != previous_station)) previous_station = station # Get the first and last timestamps from the recording if self.__p_fts == -1 or ts < self.__p_fts: self.__p_fts = ts # Set the 'first' timestamp if self.__p_lts < ts: self.__p_lts = ts # Update the number of lines self.__p_lines += 1 # Generate the station list from the recording self.__p_stations.add(station) # Read the next line line = fp.readline() except Exception as ex: log.err( "Error processing recording file: '{}' Line: {} Error: {}" .format(self.__source_file_path, self.__p_line_no, ex)) return # Calculate recording file values to aid playback functions self.__playback_thread = threading.Thread( name='Recorder-Playback-Play', daemon=True, target=self.callbackPlay) self.__playback_thread.start() if self.__play_station_list_callback: self.__p_stations_thread = threading.Thread( name='Recorder-Playback-StationList', daemon=True, target=self.callbackPlayStationList) self.__p_stations_thread.start() if self.__list_data: # Print some values about the recording print(" Lines: {} Start: {} End: {} Duration: {}".format( self.__p_lines, date_time_from_ts(self.__p_fts), date_time_from_ts(self.__p_lts), hms_from_ts(self.__p_lts, self.__p_fts)))
help='file (in MorseKOB recorder format) to be played back.') arg_parser.add_argument("--list", action="store_true", default=False, help="Display the recorded data as it is played.", dest="listData") arg_parser.add_argument("--speedfactor", type=int, metavar="n", default=100, help="Factor (percentage) to adjust playback speed by (Default 100).", dest="speedFactor") arg_parser.add_argument("--maxsilence", type=int, metavar="n", default=5, help="Longest silence duration to play, in seconds. A value of '0' will reproduce all silence as recorded (Defalut 5).", dest="maxSilence") args = arg_parser.parse_args() interface_type = args.interface_type port = args.serial_port # serial port for KOB/sounder interface sound = strtobool(args.sound) sounder = strtobool(args.sounder) playback_file = args.playback_file # Validate that the file can be opened try: fp = open(playback_file, 'r') fp.close() except FileNotFoundError: log.err("Recording file not found: {}".format(playback_file)) myKOB = kob.KOB(port=port, audio=sound, interfaceType=interface_type) myRecorder = recorder.Recorder(None, playback_file, play_code_callback=callbackPlay, play_finished_callback=callbackPlayFinished, station_id="Player") myRecorder.playback_start(list_data=args.listData, max_silence=args.maxSilence, speed_factor=args.speedFactor) # Wait until playback is finished while not playback_finished.is_set(): time.sleep(0.5) except KeyboardInterrupt: print("\nEarly exit.") myRecorder.playback_stop() sys.exit(0) # ^C is considered a normal exit.