From f20bb40b99383001bde2343f96caadb4e1aa0c76 Mon Sep 17 00:00:00 2001 From: Laurence Date: Fri, 26 Jul 2024 16:43:48 +0800 Subject: [PATCH 1/3] 1. Apply pyaudio stream on Windows in audio.py 2. Add reedsolo encode&decode with data in framing.py 3. Add stop_event during send&recv in send.py recv.py, which is necessary for building dual data-link --- amodem/async_reader.py | 2 +- amodem/audio.py | 172 ++++++++++++++++++++++++++++++----------- amodem/detect.py | 10 ++- amodem/framing.py | 87 +++++++++++++++++++-- amodem/send.py | 5 +- 5 files changed, 216 insertions(+), 60 deletions(-) diff --git a/amodem/async_reader.py b/amodem/async_reader.py index c8e2998d..b8537a1a 100644 --- a/amodem/async_reader.py +++ b/amodem/async_reader.py @@ -14,7 +14,7 @@ def __init__(self, stream, bufsize): self.queue = Queue() self.stop = threading.Event() args = (stream, bufsize, self.queue, self.stop) - self.thread = threading.Thread(target=AsyncReader._thread, + self.thread = threading.Thread(target=AsyncReader._thread, daemon=True, args=args, name='AsyncReader') self.thread.start() self.buf = b'' diff --git a/amodem/audio.py b/amodem/audio.py index 811b5fc6..6d000ab2 100644 --- a/amodem/audio.py +++ b/amodem/audio.py @@ -2,13 +2,23 @@ import ctypes import logging +import platform +import string import time log = logging.getLogger(__name__) - -class AudioError(Exception): - pass +PLATFORM_IS_WIN = platform.system() == 'Windows' +WIN_ERR_CODES = ['pa.paNoError', 'pa.paNotInitialized', 'pa.paUnanticipatedHostError', 'pa.paInvalidChannelCount', + 'pa.paInvalidSampleRate', 'pa.paInvalidDevice', 'pa.paInvalidFlag', 'pa.paSampleFormatNotSupported', + 'pa.paBadIODeviceCombination', 'pa.paInsufficientMemory', 'pa.paBufferTooBig', 'pa.paBufferTooSmall', + 'pa.paNullCallback', 'pa.paBadStreamPtr', 'pa.paTimedOut', 'pa.paInternalError', + 'pa.paDeviceUnavailable', 'pa.paIncompatibleHostApiSpecificStreamInfo', 'pa.paStreamIsStopped', + 'pa.paStreamIsNotStopped', 'pa.paInputOverflowed', 'pa.paOutputUnderflowed', 'pa.paHostApiNotFound', + 'pa.paInvalidHostApi', 'pa.paCanNotReadFromACallbackStream', 'pa.paCanNotWriteToACallbackStream', + 'pa.paCanNotReadFromAnOutputOnlyStream', 'pa.paCanNotWriteToAnInputOnlyStream', + 'pa.paIncompatibleStreamHostApi'] +WIN_ERR_CODE_MAP = {} class Interface: @@ -18,28 +28,69 @@ def __init__(self, config, debug=False): self.streams = [] self.lib = None + self.win_pyaudio = None + self.win_err_code_map = {} + self.win_func_names = [] + self.win_mapped_func = {} + def load(self, name): - self.lib = ctypes.CDLL(name) + if PLATFORM_IS_WIN: + import pyaudio as pa + self.lib = pa._portaudio + self.win_pyaudio = pa.PyAudio() + self.win_func_names = dir(self.lib) + for code_str in WIN_ERR_CODES: + WIN_ERR_CODE_MAP[eval(code_str)] = code_str[3:] + WIN_ERR_CODE_MAP[0] = b'Success' + else: + self.lib = ctypes.CDLL(name) assert self._error_string(0) == b'Success' version = self.call('GetVersionText', restype=ctypes.c_char_p) log.info('%s loaded', version) return self def _error_string(self, code): - return self.call('GetErrorText', code, restype=ctypes.c_char_p) + if PLATFORM_IS_WIN: + return WIN_ERR_CODE_MAP.get(code, "Unknown Error") + else: + return self.call('GetErrorText', code, restype=ctypes.c_char_p) def call(self, name, *args, **kwargs): assert self.lib is not None - func_name = f'Pa_{name}' - if self.debug: - log.debug('API: %s%s', name, args) - func = getattr(self.lib, func_name) - func.restype = kwargs.get('restype', self._error_check) - return func(*args) + if PLATFORM_IS_WIN: + if name in self.win_mapped_func: + func_name = self.win_mapped_func[name] + else: + func_name = "" + for c, ch in enumerate(name): + if c > 0 and ch in string.ascii_uppercase: + func_name += f"_{ch.lower()}" + else: + func_name += ch.lower() + if self.debug: + log.debug('API: %s%s', name, args) + if func_name in self.win_func_names: + func = getattr(self.lib, func_name) + if hasattr(func, "restype"): + func.restype = kwargs.get('restype', self._error_check) + try: + return func(*args) + except Exception as e: + log.error(f"call [{name}], args [{args}], kwargs [{kwargs}]") + raise e + else: + raise Exception("No such method", func_name) + else: + func_name = f'Pa_{name}' + if self.debug: + log.debug('API: %s%s', name, args) + func = getattr(self.lib, func_name) + func.restype = kwargs.get('restype', self._error_check) + return func(*args) def _error_check(self, res): if res != 0: - raise AudioError(res, self._error_string(res)) + raise Exception(res, self._error_string(res)) def __enter__(self): self.call('Initialize') @@ -58,7 +109,6 @@ def player(self): class Stream: - timer = time.time class Parameters(ctypes.Structure): @@ -77,51 +127,78 @@ def __init__(self, interface, config, read=False, write=False): self.stream_callback = ctypes.c_void_p(None) self.bytes_per_sample = config.sample_size self.latency = float(config.latency) # in seconds - self.bufsize = int(self.latency * config.Fs * self.bytes_per_sample) + self.bufsize = int(2 * self.latency * config.Fs * self.bytes_per_sample) assert config.bits_per_sample == 16 # just to make sure :) read = bool(read) write = bool(write) assert read != write # don't support full duplex - direction = 'Input' if read else 'Output' - api_name = f'GetDefault{direction}Device' - index = interface.call(api_name, restype=ctypes.c_int) - self.params = Stream.Parameters( - device=index, # choose default device - channelCount=1, # mono audio - sampleFormat=0x00000008, # 16-bit samples (paInt16) - suggestedLatency=self.latency, - hostApiSpecificStreamInfo=None) - - self.interface.call( - 'OpenStream', - ctypes.byref(self.stream), - ctypes.byref(self.params) if read else None, - ctypes.byref(self.params) if write else None, - ctypes.c_double(config.Fs), - ctypes.c_ulong(0), # (paFramesPerBufferUnspecified) - ctypes.c_ulong(0), # no flags (paNoFlag) - self.stream_callback, - self.user_data) - - self.interface.streams.append(self) - self.interface.call('StartStream', self.stream) + if PLATFORM_IS_WIN: + import pyaudio as pa + arguments = { + 'rate': int(config.Fs), + 'channels': 1, + 'format': pa.paInt16, + 'input': read, + 'output': write, + 'frames_per_buffer': pa.paFramesPerBufferUnspecified, + } + self.stream = self.interface.win_pyaudio.open(**arguments) + self.interface.streams.append(self) + self.stream.start_stream() + else: + direction = 'Input' if read else 'Output' + api_name = f'GetDefault{direction}Device' + index = interface.call(api_name, restype=ctypes.c_int) + self.params = Stream.Parameters( + device=index, # choose default device + channelCount=1, # mono audio + sampleFormat=0x00000008, # 16-bit samples (paInt16) + suggestedLatency=self.latency, + hostApiSpecificStreamInfo=None) + + self.interface.call( + 'OpenStream', + ctypes.byref(self.stream), + ctypes.byref(self.params) if read else None, + ctypes.byref(self.params) if write else None, + ctypes.c_double(config.Fs), + ctypes.c_ulong(0), # (paFramesPerBufferUnspecified) + ctypes.c_ulong(0), # no flags (paNoFlag) + self.stream_callback, + self.user_data) + + self.interface.streams.append(self) + self.interface.call('StartStream', self.stream) self.start_time = self.timer() self.io_time = 0 def close(self): if self.stream: - self.interface.call('StopStream', self.stream) - self.interface.call('CloseStream', self.stream) + if PLATFORM_IS_WIN: + self.stream.stop_stream() + self.stream.close() + else: + self.interface.call('StopStream', self.stream) + self.interface.call('CloseStream', self.stream) self.stream = None def read(self, size): assert size % self.bytes_per_sample == 0 - buf = ctypes.create_string_buffer(size) - frames = ctypes.c_ulong(size // self.bytes_per_sample) - t0 = self.timer() - self.interface.call('ReadStream', self.stream, buf, frames) + if PLATFORM_IS_WIN: + class _tmp: + def __init__(self): + self.raw = None + + t0 = self.timer() + buf = _tmp() + buf.raw = self.stream.read((size // self.bytes_per_sample)) + else: + buf = ctypes.create_string_buffer(size) + frames = ctypes.c_ulong(size // self.bytes_per_sample) + t0 = self.timer() + self.interface.call('ReadStream', self.stream, buf, frames) t1 = self.timer() self.io_time += (t1 - t0) if self.interface.debug: @@ -132,6 +209,9 @@ def read(self, size): def write(self, data): data = bytes(data) assert len(data) % self.bytes_per_sample == 0 - buf = ctypes.c_char_p(data) - frames = ctypes.c_ulong(len(data) // self.bytes_per_sample) - self.interface.call('WriteStream', self.stream, buf, frames) + if PLATFORM_IS_WIN: + self.stream.write(data, num_frames=(len(data) // self.bytes_per_sample)) + else: + buf = ctypes.c_char_p(data) + frames = ctypes.c_ulong(len(data) // self.bytes_per_sample) + self.interface.call('WriteStream', self.stream, buf, frames) diff --git a/amodem/detect.py b/amodem/detect.py index be47285c..947863ac 100644 --- a/amodem/detect.py +++ b/amodem/detect.py @@ -3,6 +3,7 @@ import collections import itertools import logging +import threading import numpy as np @@ -14,7 +15,6 @@ class Detector: - COHERENCE_THRESHOLD = 0.9 CARRIER_DURATION = sum(equalizer.prefix) @@ -31,10 +31,12 @@ def __init__(self, config, pylab): self.max_offset = config.timeout * config.Fs self.plt = pylab - def _wait(self, samples): + def _wait(self, samples, stop_event: threading.Event = None): counter = 0 bufs = collections.deque([], maxlen=self.maxlen) for offset, buf in common.iterate(samples, self.Nsym, index=True): + if stop_event is not None and stop_event.is_set(): + raise StopIteration('Stop iteration by stop_event') if offset > self.max_offset: raise ValueError('Timeout waiting for carrier') bufs.append(buf) @@ -50,8 +52,8 @@ def _wait(self, samples): raise ValueError('No carrier detected') - def run(self, samples): - offset, bufs = self._wait(samples) + def run(self, samples, stop_event: threading.Event = None): + offset, bufs = self._wait(samples, stop_event=stop_event) length = (self.CARRIER_THRESHOLD - 1) * self.Nsym begin = offset - length diff --git a/amodem/framing.py b/amodem/framing.py index 946e4b93..9205fe3e 100644 --- a/amodem/framing.py +++ b/amodem/framing.py @@ -3,11 +3,67 @@ import itertools import logging import struct +from math import erfc + +import numpy as np +import reedsolo from . import common log = logging.getLogger(__name__) +# 创建RS编码器 +NUM_SYMBOLS = 32 +rs_codec = reedsolo.RSCodec(NUM_SYMBOLS) # number of ecc symbols (you can repair nsym/2 errors and nsym erasures. + + +def ber_mqam(snr_db, M): + # 根据snr重新协商纠错数量 + # Convert SNR from dB to linear scale + snr_linear = 10 ** (snr_db / 10.0) + # Calculate BER for M-QAM + ber = (2 * (np.sqrt(M) - 1) / (np.sqrt(M) * np.log2(M))) * \ + erfc(np.sqrt(3 * snr_linear / (2 * (M - 1)))) + return ber + + +def set_rs_codec(snr_db): + global NUM_SYMBOLS + global rs_codec + + +def encode_with_rs(data): + # 确保数据是bytearray类型 + if not isinstance(data, bytearray): + data = bytearray(data) + # 编码数据 + encoded = rs_codec.encode(data) + return encoded + + +def decode_with_rs(encoded_data): + try: + # 解码数据 + decoded, _, _ = rs_codec.decode(encoded_data) + return decoded + except reedsolo.ReedSolomonError as e: + import traceback + traceback.print_exc() + return None + + +def encode_pack(data): + return encode_with_rs(data) + + +def decode_pack(encoded_data, chunk_size): + chunk = bytearray(itertools.islice(encoded_data, chunk_size)) + if len(chunk) < chunk_size: + raise ValueError(f'Incomplete frame, length {len(chunk)} < {chunk_size}(required)') + + decoded = decode_with_rs(chunk) + return iter(decoded) + def _checksum_func(x): return binascii.crc32(bytes(x)) @@ -19,7 +75,8 @@ class Checksum: def encode(self, payload): checksum = _checksum_func(payload) - return struct.pack(self.fmt, checksum) + payload + encoded = struct.pack(self.fmt, checksum) + payload + return encoded def decode(self, data): received, = struct.unpack(self.fmt, bytes(data[:self.size])) @@ -33,28 +90,41 @@ def decode(self, data): class Framer: - block_size = 250 + chunk_size = 255 + unencrypted_size = chunk_size - NUM_SYMBOLS + block_size = unencrypted_size - 1 - 4 # 1 bytes length, 4 bytes crc prefix_fmt = '>B' prefix_len = struct.calcsize(prefix_fmt) checksum = Checksum() EOF = b'' - def _pack(self, block): + def _pack(self, block, padded_size=None): frame = self.checksum.encode(block) - return bytearray(struct.pack(self.prefix_fmt, len(frame)) + frame) + packed = bytearray(struct.pack(self.prefix_fmt, len(frame)) + frame) + + if padded_size is not None: + current_length = len(packed) + if current_length > padded_size: + raise ValueError(f"Packed data length ({current_length}) exceeds target length ({padded_size})") + + padding_length = padded_size - current_length + packed.extend(b'\x00' * padding_length) + packed = encode_pack(packed) + return packed def encode(self, data): for block in common.iterate(data=data, size=self.block_size, func=bytearray, truncate=False): - yield self._pack(block=block) - yield self._pack(block=self.EOF) + yield self._pack(block=block, padded_size=self.unencrypted_size) + yield self._pack(block=self.EOF, padded_size=self.unencrypted_size) def decode(self, data): data = iter(data) while True: - length, = _take_fmt(data, self.prefix_fmt) - frame = _take_len(data, length) + pack = decode_pack(data, self.chunk_size) + length, = _take_fmt(pack, self.prefix_fmt) + frame = _take_len(pack, length) block = self.checksum.decode(frame) if block == self.EOF: log.debug('EOF frame detected') @@ -83,6 +153,7 @@ def chain_wrapper(func): def wrapped(*args, **kwargs): result = func(*args, **kwargs) return itertools.chain.from_iterable(result) + return wrapped diff --git a/amodem/send.py b/amodem/send.py index 37de2dba..365322bb 100644 --- a/amodem/send.py +++ b/amodem/send.py @@ -1,5 +1,6 @@ import itertools import logging +import threading import numpy as np @@ -39,11 +40,13 @@ def start(self): self.write(signal) self.write(self.silence) - def modulate(self, bits): + def modulate(self, bits, stop_event: threading.Event = None): bits = itertools.chain(bits, self.padding) Nfreq = len(self.carriers) symbols_iter = common.iterate(self.modem.encode(bits), size=Nfreq) for i, symbols in enumerate(symbols_iter, 1): + if stop_event is not None and stop_event.is_set(): + raise StopIteration('Stop iteration by stop_event') self.write(np.dot(symbols, self.carriers)) if i % self.iters_per_report == 0: total_bits = i * Nfreq * self.modem.bits_per_symbol From 926702b8394f1b738b6a0ec4183cc41e7da7f6de Mon Sep 17 00:00:00 2001 From: Laurence Date: Sun, 28 Jul 2024 12:30:54 +0800 Subject: [PATCH 2/3] 1. Optimize speed by removing EOF frame 2. Add configuration with negotiate_frequencies --- amodem/config.py | 2 ++ amodem/detect.py | 4 ++-- amodem/framing.py | 58 ++++++++++++++++++++++++++++++++--------------- amodem/recv.py | 4 ++-- amodem/send.py | 2 +- 5 files changed, 47 insertions(+), 23 deletions(-) diff --git a/amodem/config.py b/amodem/config.py index b08bcab6..5e7e0333 100644 --- a/amodem/config.py +++ b/amodem/config.py @@ -8,6 +8,7 @@ class Configuration: Tsym = 0.001 # symbol duration [seconds] Npoints = 64 frequencies = [1e3, 8e3] # use 1..8 kHz carriers + negotiate_frequencies = [12e3] # negotiate carrier # audio config bits_per_sample = 16 @@ -71,6 +72,7 @@ def __init__(self, **kwargs): 24: Configuration(Fs=16e3, Npoints=16, frequencies=[1e3, 6e3]), 28: Configuration(Fs=32e3, Npoints=16, frequencies=[3e3, 9e3]), 32: Configuration(Fs=32e3, Npoints=16, frequencies=[2e3, 9e3]), + 40: Configuration(Fs=32e3, Npoints=16, frequencies=[2e3, 11e3]), 36: Configuration(Fs=32e3, Npoints=64, frequencies=[4e3, 9e3]), 42: Configuration(Fs=32e3, Npoints=64, frequencies=[4e3, 10e3]), 48: Configuration(Fs=32e3, Npoints=64, frequencies=[3e3, 10e3]), diff --git a/amodem/detect.py b/amodem/detect.py index 947863ac..0fd274be 100644 --- a/amodem/detect.py +++ b/amodem/detect.py @@ -64,7 +64,7 @@ def run(self, samples, stop_event: threading.Event = None): log.debug('Buffered %d ms of audio', len(bufs)) - bufs = list(bufs)[-self.CARRIER_THRESHOLD-self.SEARCH_WINDOW:] + bufs = list(bufs)[-self.CARRIER_THRESHOLD - self.SEARCH_WINDOW:] n = self.SEARCH_WINDOW + self.CARRIER_DURATION - self.CARRIER_THRESHOLD trailing = list(itertools.islice(samples, n * self.Nsym)) bufs.append(np.array(trailing)) @@ -88,7 +88,7 @@ def find_start(self, buf): signal = (2 ** 0.5) * signal / dsp.norm(signal) corr = np.abs(np.correlate(buf, signal)) - norm_b = np.sqrt(np.correlate(np.abs(buf)**2, np.ones(len(signal)))) + norm_b = np.sqrt(np.correlate(np.abs(buf) ** 2, np.ones(len(signal)))) coeffs = np.zeros_like(corr) coeffs[norm_b > 0.0] = corr[norm_b > 0.0] / norm_b[norm_b > 0.0] diff --git a/amodem/framing.py b/amodem/framing.py index 9205fe3e..956ceafe 100644 --- a/amodem/framing.py +++ b/amodem/framing.py @@ -3,6 +3,7 @@ import itertools import logging import struct +import time from math import erfc import numpy as np @@ -73,20 +74,28 @@ class Checksum: fmt = '>L' # unsigned longs (32-bit) size = struct.calcsize(fmt) - def encode(self, payload): + def encode(self, payload, cut_eof=False, is_last_block=False): checksum = _checksum_func(payload) + if cut_eof and is_last_block: + checksum = _checksum_func(struct.pack('>I', checksum)) encoded = struct.pack(self.fmt, checksum) + payload return encoded - def decode(self, data): + def decode(self, data, cut_eof=False): received, = struct.unpack(self.fmt, bytes(data[:self.size])) payload = data[self.size:] expected = _checksum_func(payload) - if received != expected: + if cut_eof: + eof_detected = received == _checksum_func(struct.pack('>I', expected)) + valid_fail = received != expected and not eof_detected + else: + eof_detected = False + valid_fail = received != expected + if valid_fail: log.warning('Invalid checksum: %08x != %08x', received, expected) raise ValueError('Invalid checksum') log.debug('Good checksum: %08x', received) - return payload + return payload, eof_detected class Framer: @@ -99,8 +108,8 @@ class Framer: EOF = b'' - def _pack(self, block, padded_size=None): - frame = self.checksum.encode(block) + def _pack(self, block, padded_size=None, cut_eof=False, is_last_block=False): + frame = self.checksum.encode(block, cut_eof=cut_eof, is_last_block=is_last_block) packed = bytearray(struct.pack(self.prefix_fmt, len(frame)) + frame) if padded_size is not None: @@ -113,25 +122,38 @@ def _pack(self, block, padded_size=None): packed = encode_pack(packed) return packed - def encode(self, data): - for block in common.iterate(data=data, size=self.block_size, - func=bytearray, truncate=False): - yield self._pack(block=block, padded_size=self.unencrypted_size) - yield self._pack(block=self.EOF, padded_size=self.unencrypted_size) - - def decode(self, data): + def encode(self, data, cut_eof=False): + iterator = common.iterate(data=data, size=self.block_size, + func=bytearray, truncate=False) + + prev_block = next(iterator, None) + for current_block in iterator: + yield self._pack(block=prev_block, padded_size=self.unencrypted_size) + prev_block = current_block + if prev_block is not None: + yield self._pack(block=prev_block, padded_size=self.unencrypted_size, cut_eof=cut_eof, + is_last_block=cut_eof) + if not cut_eof: + # 添加EOF块 + yield self._pack(block=self.EOF, padded_size=self.unencrypted_size) + + def decode(self, data, cut_eof=False): data = iter(data) while True: pack = decode_pack(data, self.chunk_size) length, = _take_fmt(pack, self.prefix_fmt) frame = _take_len(pack, length) - block = self.checksum.decode(frame) + block, eof_detected = self.checksum.decode(frame, cut_eof=cut_eof) if block == self.EOF: log.debug('EOF frame detected') return yield block + if eof_detected: + log.debug('End frame detected') + return + def _take_fmt(data, fmt): length = struct.calcsize(fmt) @@ -171,10 +193,10 @@ def __init__(self): @chain_wrapper -def encode(data, framer=None): +def encode(data, framer=None, cut_eof=False): converter = BitPacker() framer = framer or Framer() - for frame in framer.encode(data): + for frame in framer.encode(data, cut_eof=cut_eof): for byte in frame: yield converter.to_bits[byte] @@ -187,7 +209,7 @@ def _to_bytes(bits): yield [converter.to_byte[chunk]] -def decode_frames(bits, framer=None): +def decode_frames(bits, framer=None, cut_eof=False): framer = framer or Framer() - for frame in framer.decode(_to_bytes(bits)): + for frame in framer.decode(_to_bytes(bits), cut_eof=cut_eof): yield bytes(frame) diff --git a/amodem/recv.py b/amodem/recv.py index ff8a3e6c..6cbef90a 100644 --- a/amodem/recv.py +++ b/amodem/recv.py @@ -157,7 +157,7 @@ def _report_progress(self, noise, sampler): (1.0 - sampler.freq) * 1e6 ) - def run(self, sampler, gain, output): + def run(self, sampler, gain, output, cut_eof=False): log.debug('Receiving') symbols = dsp.Demux(sampler, omegas=self.omegas, Nsym=self.Nsym) self._prefix(symbols, gain=gain) @@ -168,7 +168,7 @@ def run(self, sampler, gain, output): bitstream = self._demodulate(sampler, symbols) bitstream = itertools.chain.from_iterable(bitstream) - for frame in framing.decode_frames(bitstream): + for frame in framing.decode_frames(bitstream, cut_eof=cut_eof): output.write(frame) self.output_size += len(frame) diff --git a/amodem/send.py b/amodem/send.py index 365322bb..904d87c5 100644 --- a/amodem/send.py +++ b/amodem/send.py @@ -46,7 +46,7 @@ def modulate(self, bits, stop_event: threading.Event = None): symbols_iter = common.iterate(self.modem.encode(bits), size=Nfreq) for i, symbols in enumerate(symbols_iter, 1): if stop_event is not None and stop_event.is_set(): - raise StopIteration('Stop iteration by stop_event') + raise StopIteration('Modulate stop iteration by stop_event') self.write(np.dot(symbols, self.carriers)) if i % self.iters_per_report == 0: total_bits = i * Nfreq * self.modem.bits_per_symbol From f091001cfcc584a727ac9935bf958bbb7173775b Mon Sep 17 00:00:00 2001 From: Laurence Date: Sun, 28 Jul 2024 12:34:22 +0800 Subject: [PATCH 3/3] Optimze log --- amodem/detect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amodem/detect.py b/amodem/detect.py index 0fd274be..bb3942c2 100644 --- a/amodem/detect.py +++ b/amodem/detect.py @@ -36,7 +36,7 @@ def _wait(self, samples, stop_event: threading.Event = None): bufs = collections.deque([], maxlen=self.maxlen) for offset, buf in common.iterate(samples, self.Nsym, index=True): if stop_event is not None and stop_event.is_set(): - raise StopIteration('Stop iteration by stop_event') + raise StopIteration('Detector stop iteration by stop_event') if offset > self.max_offset: raise ValueError('Timeout waiting for carrier') bufs.append(buf)