Compare commits

...

39 Commits
v1.10 ... v1.13

Author SHA1 Message Date
Roman Zeyde
e637e701df bump version 2015-09-19 12:13:19 +03:00
Roman Zeyde
5be6684fa6 travis: upgrade pytest version for Python 3.5 2015-09-19 12:05:58 +03:00
Roman Zeyde
0876be18e4 Merge branch 'py35' 2015-09-19 12:02:24 +03:00
Roman Zeyde
3b8f913fcb setup: Python 3.5 is supported 2015-09-19 12:01:52 +03:00
Roman Zeyde
45d4ccae76 use --assert=plain on Travis 2015-09-19 11:58:30 +03:00
Roman Zeyde
7cb05aaaf7 travis: test under Python 3.5 2015-09-19 11:45:50 +03:00
Roman Zeyde
3911f16bd7 tox: fix indent to tabs 2015-09-18 21:07:43 +03:00
Roman Zeyde
b5f8e07ae2 recv: define integration gain as member variable 2015-08-16 18:30:30 +03:00
Roman Zeyde
835841bf2e test_calib: test frequency change case 2015-08-16 18:21:40 +03:00
Roman Zeyde
c70b3c9dc7 calib: remove AttributeHolder 2015-08-16 17:55:40 +03:00
Roman Zeyde
544dd28ddd common: add docstrings 2015-08-16 17:55:40 +03:00
Roman Zeyde
c887dbf4e6 README: use screencasts instead of videos 2015-08-15 08:35:22 +03:00
Roman Zeyde
bf6282127c docs: link to readthedocs.org 2015-08-14 13:07:04 +03:00
Roman Zeyde
65f2559a19 README: fix whitespace 2015-08-13 10:07:07 +03:00
Roman Zeyde
1c6f8894a5 setup.py: fix pytest invocation 2015-08-11 10:43:28 +03:00
Roman Zeyde
c19d11744f remove requirements.txt 2015-08-11 10:39:00 +03:00
Roman Zeyde
4d60cac7ed version: meta-bump due to PyPI problems 2015-08-07 17:44:45 +03:00
Roman Zeyde
a3adda625b gitignore: ignore backup files 2015-07-29 18:03:56 +03:00
Roman Zeyde
a44f55a608 travis: fix tests runner 2015-07-29 17:59:09 +03:00
Roman Zeyde
e0a38bf5d7 README: simplify description 2015-07-29 17:44:47 +03:00
Roman Zeyde
6b67721374 tox: update to run on updated tests 2015-07-29 17:44:46 +03:00
Roman Zeyde
20efa6a688 tests: remove unused code 2015-07-29 17:44:46 +03:00
Roman Zeyde
327a7f9d0f tests: move into amodem package 2015-07-29 17:44:46 +03:00
Roman Zeyde
7b0ba1714f Merge pull request #18 from anduck/patch-1
changed input and output vice versa
2015-07-28 08:58:01 +03:00
anduck
ec76a1394c changed input and output vice versa 2015-07-27 22:45:34 +03:00
Roman Zeyde
bf7b59db11 bump version 2015-07-17 08:27:30 +03:00
Roman Zeyde
f51cf8c4db config: shorten start & stop silence periods 2015-07-17 08:22:49 +03:00
Roman Zeyde
aec1648ae7 __main__: fix PEP8 2015-07-01 14:23:16 +03:00
Roman Zeyde
2ad3ffced4 alsa: fix string format for Python 2.6 2015-07-01 14:20:16 +03:00
Roman Zeyde
318081fca4 __main__: fix dummy interface case 2015-07-01 14:17:49 +03:00
Roman Zeyde
f82a4f4a39 audio: add simple ALSA interface 2015-07-01 13:55:08 +03:00
Roman Zeyde
8d72621b9b __main__: fix format string 2015-06-29 12:23:52 +03:00
Roman Zeyde
2e6196416b __main__: add logging about zlib usage 2015-06-23 11:38:09 +03:00
Roman Zeyde
01c78bae8f __main__: fix imports 2015-06-23 11:37:52 +03:00
Roman Zeyde
c56f696e9e version: use separate file for versioning 2015-06-23 11:37:43 +03:00
Roman Zeyde
3fe515ea59 __main__: should not be executable 2015-06-23 11:15:41 +03:00
Roman Zeyde
55e7152da6 README: add downloads badge 2015-06-13 14:23:03 +03:00
Roman Zeyde
66c639b597 README: use shields.io for badges 2015-06-13 08:26:23 +03:00
Roman Zeyde
a4ebf68223 bump package version 2015-04-23 11:56:33 +03:00
27 changed files with 255 additions and 117 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
*~
\#*\#
*.out
*.pyc
*.sublime-*

View File

@@ -6,15 +6,17 @@ python:
- "3.2"
- "3.3"
- "3.4"
- "3.5"
install:
- pip install .
- pip install pytest>=2.7.3 --upgrade
- pip install coveralls pep8 mock
script:
- pep8 amodem/ scripts/ tests/
- pep8 amodem/ scripts/
- echo "Hello World!" | amodem send -vv -l- -o- | amodem recv -vv -l- -i-
- coverage run --source=amodem --omit="*/__main__.py" -m py.test -vvs tests/
- coverage run --source=amodem --omit="*/__main__.py" -m py.test -vvs
after_success:
- coverage report

View File

@@ -8,20 +8,28 @@ Audio Modem Communication Library
:target: https://coveralls.io/r/romanz/amodem?branch=master
:alt: Code Coverage
.. image:: https://landscape.io/github/romanz/amodem/master/landscape.svg?style=flat
:target: https://landscape.io/github/romanz/amodem/master
:alt: Code Health
.. image:: https://pypip.in/py_versions/amodem/badge.svg?style=flat
:target: https://landscape.io/github/romanz/amodem/master
:alt: Code Health
.. image:: https://readthedocs.org/projects/amodem/badge/?version=latest
:target: http://amodem.readthedocs.org/en/latest/
:alt: Documentation
.. image:: https://img.shields.io/pypi/pyversions/amodem.svg
:target: https://pypi.python.org/pypi/amodem/
:alt: Python Versions
.. image:: https://pypip.in/license/amodem/badge.svg?style=flat
.. image:: https://img.shields.io/pypi/l/amodem.svg
:target: https://pypi.python.org/pypi/amodem/
:alt: License
.. image:: https://pypip.in/version/amodem/badge.svg?style=flat
.. image:: https://img.shields.io/pypi/v/amodem.svg
:target: https://pypi.python.org/pypi/amodem/
:alt: Package Version
.. image:: https://pypip.in/status/amodem/badge.svg?style=flat
.. image:: https://img.shields.io/pypi/status/amodem.svg
:target: https://pypi.python.org/pypi/amodem/
:alt: Development Status
.. image:: https://img.shields.io/pypi/dm/amodem.svg
:target: https://pypi.python.org/pypi/amodem/
:alt: Downloads
.. image:: https://badge.waffle.io/romanz/amodem.svg?label=ready&title=ready
:target: https://waffle.io/romanz/amodem
:alt: 'Ready'
@@ -29,25 +37,28 @@ Audio Modem Communication Library
Description
-----------
This program can be used to transmit a specified file between 2 computers, using
a simple audio cable (for better SNR and higher speeds) or a simple headset,
allowing true air-gapped communication (via a speaker and a microphone).
This program can transmit a file between 2 computers, using a simple headset,
allowing true air-gapped communication (via a speaker and a microphone),
or an audio cable (for higher transmission speed).
The sender modulates an input binary data file into an audio signal,
The sender modulates the input data into an audio signal,
which is played to the sound card.
The receiver side records the transmitted audio,
which is demodulated concurrently into an output binary data file.
The receiver records the audio, and demodulates it back to the original data.
The process requires a single manual calibration step: the transmitter has to
find maximal output volume for its sound card, which will not saturate the
receiving microphone.
find the optimal output volume for its sound card, which will not saturate the
receiving microphone and provide good enough Signal-to-Noise ratio
for the demodulation to succeed.
Technical Details
-----------------
The modem is using OFDM over an audio cable with the following parameters:
- Sampling rate: 8/16/32 kHz
- Baud rate: 1 kHz
- Symbol modulation: BPSK, 4-PSK, 16-QAM ,64-QAM, 256-QAM
- Symbol modulation: BPSK, 4-PSK, 16-QAM, 64-QAM, 256-QAM
- Carriers: 2-11 kHz (up to ten carriers)
This way, modem may achieve 80kbps bitrate = 10 kB/s (for best SNR).
@@ -134,7 +145,7 @@ and send me the resulting ``audio.raw`` file for debugging::
~/receiver $ arecord --format=S16_LE --channels=1 --rate=32000 audio.raw
You can see a video of the `calibration process <http://www.youtube.com/watch?v=jRUj2Ifk-Po>`_.
You can see a screencast of the `calibration process <https://asciinema.org/a/25065?autoplay=1>`_.
Usage
-----
@@ -147,11 +158,11 @@ Prepare the sender (generate a random binary data file to be sent)::
Start the receiver (will wait for the sender to start)::
~/receiver $ amodem recv -vv -i data.rx
~/receiver $ amodem recv -vv -o data.rx
Start the sender (will modulate the data and start the transmission)::
~/sender $ amodem send -vv -o data.tx
~/sender $ amodem send -vv -i data.tx
A similar log should be emitted by the sender::
@@ -218,7 +229,7 @@ After the receiver has finished, verify the received file's hash::
~/receiver $ sha256sum data.rx
008df57d4f3ed6e7a25d25afd57d04fc73140e8df604685bd34fcab58f5ddc01 data.rx
You can see a video of the `data transfer process <http://www.youtube.com/watch?v=GZQUtHB8so4>`_.
You can see a screencast of the `data transfer process <https://asciinema.org/a/25066?autoplay=1>`_.
Visualization
-------------

60
amodem/__main__.py Executable file → Normal file
View File

@@ -1,7 +1,8 @@
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
from amodem import main, calib, audio, async
from amodem.config import bitrates
from . import main, calib, audio, async
from .config import bitrates
from . import version
import os
import sys
@@ -28,6 +29,7 @@ config = bitrates.get(int(bitrate))
class Compressor(object):
def __init__(self, stream):
self.obj = zlib.compressobj()
log.info('Using zlib compressor')
self.stream = stream
def read(self, size):
@@ -48,6 +50,7 @@ class Compressor(object):
class Decompressor(object):
def __init__(self, stream):
self.obj = zlib.decompressobj()
log.info('Using zlib decompressor')
self.stream = stream
def write(self, data):
@@ -57,8 +60,10 @@ class Decompressor(object):
self.stream.write(self.obj.flush())
def FileType(mode, audio_interface=None):
def FileType(mode, interface_factory=None):
def opener(fname):
audio_interface = interface_factory() if interface_factory else None
assert 'r' in mode or 'w' in mode
if audio_interface is None and fname is None:
fname = '-'
@@ -94,20 +99,11 @@ def get_volume_cmd(args):
return c[args.command]
class _DummyContextManager(object):
def __enter__(self):
pass
def __exit__(self, *args):
pass
def wrap(cls, stream, enable):
return cls(stream) if enable else stream
def create_parser(description, interface):
def create_parser(description, interface_factory):
p = argparse.ArgumentParser(description=description)
subparsers = p.add_subparsers()
@@ -132,7 +128,7 @@ def create_parser(description, interface):
volume_cmd=get_volume_cmd(args)
),
input_type=FileType('rb'),
output_type=FileType('wb', interface),
output_type=FileType('wb', interface_factory),
command='send'
)
@@ -159,7 +155,7 @@ def create_parser(description, interface):
config=config, src=args.src, verbose=args.verbose,
volume_cmd=get_volume_cmd(args)
),
input_type=FileType('rb', interface),
input_type=FileType('rb', interface_factory),
output_type=FileType('wb'),
command='recv'
)
@@ -184,13 +180,27 @@ def create_parser(description, interface):
return p
class _Dummy(object):
def __enter__(self):
return self
def __exit__(self, *args):
pass
def _main():
fmt = ('Audio OFDM MODEM: {0:.1f} kb/s ({1:d}-QAM x {2:d} carriers) '
'Fs={3:.1f} kHz')
description = fmt.format(config.modem_bps / 1e3, len(config.symbols),
fmt = ('Audio OFDM MODEM v{0:s}: '
'{1:.1f} kb/s ({2:d}-QAM x {3:d} carriers) '
'Fs={4:.1f} kHz')
description = fmt.format(version.__doc__,
config.modem_bps / 1e3, len(config.symbols),
config.Nfreq, config.Fs / 1e3)
interface = audio.Interface(config=config)
p = create_parser(description, interface)
interface = None
def interface_factory():
return interface
p = create_parser(description, interface_factory)
args = p.parse_args()
if args.verbose == 0:
@@ -213,9 +223,13 @@ def _main():
import pylab # pylint: disable=import-error
args.pylab = pylab
if args.audio_library == '-':
interface = _DummyContextManager()
if args.audio_library == 'ALSA':
from . import alsa
interface = alsa.Interface(config)
elif args.audio_library == '-':
interface = _Dummy()
else:
interface = audio.Interface(config)
interface.load(args.audio_library)
with interface:
@@ -227,9 +241,9 @@ def _main():
else:
args.calib(config=config, args=args)
finally:
log.debug('Closing input and output')
args.src.close()
args.dst.close()
log.debug('Finished I/O')
if __name__ == '__main__':

65
amodem/alsa.py Normal file
View File

@@ -0,0 +1,65 @@
import subprocess
import logging
log = logging.getLogger(__name__)
class Interface(object):
RECORDER = 'arecord'
PLAYER = 'aplay'
def __init__(self, config):
self.config = config
rate = int(config.Fs)
bits_per_sample = config.bits_per_sample
assert bits_per_sample == 16
args = '-f S{0:d}_LE -c 1 -r {1:d} -T 100 -q -'
args = args.format(bits_per_sample, rate).split()
self.record_cmd = [self.RECORDER] + args
self.play_cmd = [self.PLAYER] + args
self.processes = []
def __enter__(self):
return self
def __exit__(self, *args):
for p in self.processes:
try:
p.wait()
except OSError:
log.warning('%s failed', p)
def launch(self, **kwargs):
log.debug('Launching subprocess: %s', kwargs)
p = subprocess.Popen(**kwargs)
self.processes.append(p)
return p
def recorder(self):
return Recorder(self)
def player(self):
return Player(self)
class Recorder(object):
def __init__(self, lib):
self.p = lib.launch(args=lib.record_cmd, stdout=subprocess.PIPE)
self.read = self.p.stdout.read
self.bufsize = 4096
def close(self):
self.p.kill()
class Player(object):
def __init__(self, lib):
self.p = lib.launch(args=lib.play_cmd, stdin=subprocess.PIPE)
self.write = self.p.stdin.write
def close(self):
self.p.stdin.close()
self.p.wait()

View File

@@ -72,10 +72,10 @@ def detector(config, src, frame_length=200):
else:
msg = 'too {0} signal'.format(errors[flags.index(False)])
yield common.AttributeHolder(dict(
yield dict(
freq=freq, rms=rms, peak=peak, coherency=coherency,
total=total, success=success, msg=msg
))
)
def volume_calibration(result_iterator, volume_ctl):
@@ -90,7 +90,7 @@ def volume_calibration(result_iterator, volume_ctl):
for index, result in enumerate(itertools.chain([None], result_iterator)):
if index % iters_per_update == 0:
if index > 0: # skip dummy (first result)
sign = 1 if (result.total < target_level) else -1
sign = 1 if (result['total'] < target_level) else -1
level = level + step * sign
level = min(max(level, min_level), max_level)
step = step * 0.5
@@ -111,12 +111,7 @@ def iter_window(iterable, size):
yield block
def recv(config, src, verbose=False, volume_cmd=None, dump_audio=None):
fmt = '{0.freq:6.0f} Hz: {0.msg:20s}'
if verbose:
fields = ['total', 'rms', 'coherency', 'peak']
fmt += ', '.join('{0}={{0.{0}:.4f}}'.format(f) for f in fields)
def recv_iter(config, src, volume_cmd=None, dump_audio=None):
volume_ctl = volume_controller(volume_cmd)
if dump_audio:
@@ -125,6 +120,19 @@ def recv(config, src, verbose=False, volume_cmd=None, dump_audio=None):
result_iterator = volume_calibration(result_iterator, volume_ctl)
for _prev, curr, _next in iter_window(result_iterator, size=3):
# don't log errors during frequency changes
if _prev.success and _next.success and _prev.freq != _next.freq:
curr.msg = curr.msg if curr.success else 'frequency change'
log.info(fmt.format(curr))
if _prev['success'] and _next['success']:
if _prev['freq'] != _next['freq']:
if not curr['success']:
curr['msg'] = 'frequency change'
yield curr
def recv(config, src, verbose=False, volume_cmd=None, dump_audio=None):
fmt = '{freq:6.0f} Hz: {msg:20s}'
log.info('verbose: %s', verbose)
if verbose:
fields = ['total', 'rms', 'coherency', 'peak']
fmt += ', '.join('{0}={{{0}:.4f}}'.format(f) for f in fields)
for state in recv_iter(config, src, volume_cmd, dump_audio):
log.info(fmt.format(**state))

View File

@@ -1,3 +1,6 @@
''' Common package functionality.
'''
import itertools
import numpy as np
@@ -8,21 +11,25 @@ scaling = 32000.0 # out of 2**15
def load(fileobj):
''' Load signal from file object. '''
return loads(fileobj.read())
def loads(data):
''' Load signal from memory buffer. '''
x = np.frombuffer(data, dtype='int16')
x = x / scaling
return x
def dumps(sym):
''' Dump signal to memory buffer. '''
sym = sym.real * scaling
return sym.astype('int16').tostring()
def iterate(data, size, func=None, truncate=True, index=False):
''' Iterate over a signal, taking each time *size* elements. '''
offset = 0
data = iter(data)
@@ -40,6 +47,9 @@ def iterate(data, size, func=None, truncate=True, index=False):
def split(iterable, n):
''' Split an iterable of n-tuples into n iterables of scalars.
The k-th iterable will be equivalent to (i[k] for i in iter).
'''
def _gen(it, index):
for item in it:
yield item[index]
@@ -49,37 +59,29 @@ def split(iterable, n):
def icapture(iterable, result):
''' Appends each yielded item to result. '''
for i in iter(iterable):
result.append(i)
yield i
def take(iterable, n):
''' Take n elements from iterable, and return them as a numpy array. '''
return np.array(list(itertools.islice(iterable, n)))
# "Python 3" zip re-implementation for Python 2
def izip(iterables):
''' "Python 3" zip re-implementation for Python 2. '''
iterables = [iter(iterable) for iterable in iterables]
while True:
yield tuple([next(iterable) for iterable in iterables])
class Dummy(object):
''' Dummy placeholder object for testing and mocking. '''
def __getattr__(self, name):
return self
def __call__(self, *args, **kwargs):
return self
class AttributeHolder(object):
def __init__(self, d):
self.__dict__.update(d)
def __repr__(self):
items = sorted(self.__dict__.items())
args = ', '.join('{0}={1}'.format(k, v) for k, v in items)
return '{0}({1})'.format(self.__class__.__name__, args)

View File

@@ -12,8 +12,8 @@ class Configuration(object):
latency = 0.1
# sender config
silence_start = 1.0
silence_stop = 1.0
silence_start = 0.25
silence_stop = 0.25
# receiver config
skip_start = 0.1

View File

@@ -28,6 +28,7 @@ class Receiver(object):
self.equalizer = equalizer.Equalizer(config)
self.carrier_index = config.carrier_index
self.output_size = 0 # number of bytes written to output stream
self.freq_err_gain = 0.01 * self.Tsym # integration feedback gain
def _prefix(self, symbols, gain=1.0):
S = common.take(symbols, len(equalizer.prefix))
@@ -140,7 +141,7 @@ class Receiver(object):
err = np.mean(np.angle(err))/(2*np.pi) if len(err) else 0
errors.clear()
sampler.freq -= 0.01 * err * self.Tsym
sampler.freq -= self.freq_err_gain * err
sampler.offset -= err
def _report_progress(self, noise, sampler):

40
amodem/tests/test_alsa.py Normal file
View File

@@ -0,0 +1,40 @@
from amodem import alsa, config
import mock
def test_alsa():
interface = alsa.Interface(config=config.fastest())
interface.launch = mock.Mock()
with interface:
r = interface.recorder()
r.read(2)
r.close()
p = mock.call(
args='arecord -f S16_LE -c 1 -r 32000 -T 100 -q -'.split(),
stdout=-1)
assert interface.launch.mock_calls == [p, p.stdout.read(2), p.kill()]
interface.launch = mock.Mock()
with interface:
p = interface.player()
p.write('\x00\x00')
p.close()
p = mock.call(
args='aplay -f S16_LE -c 1 -r 32000 -T 100 -q -'.split(),
stdin=-1)
assert interface.launch.mock_calls == [
p, p.stdin.write('\x00\x00'), p.stdin.close(), p.wait()
]
def test_alsa_subprocess():
interface = alsa.Interface(config=config.fastest())
with mock.patch('subprocess.Popen') as popen:
with interface:
p = interface.launch(args=['foobar'])
p.wait.side_effect = OSError('invalid command')
assert interface.processes == [p]
assert popen.mock_calls == [mock.call(args=['foobar'])]

View File

@@ -19,14 +19,6 @@ class ProcessMock(object):
self.stdout = self
self.bytes_per_sample = 2
def launch(self, *args, **kwargs):
return self
__call__ = launch
def kill(self):
pass
def write(self, data):
assert self.buf.tell() < 10e6
self.buf.write(data)
@@ -47,8 +39,8 @@ def test_too_strong():
calib.send(config, p, gain=1.001, limit=32)
p.buf.seek(0)
for r in calib.detector(config, src=p):
assert not r.success
assert r.msg == 'too strong signal'
assert not r['success']
assert r['msg'] == 'too strong signal'
def test_too_weak():
@@ -56,8 +48,8 @@ def test_too_weak():
calib.send(config, p, gain=0.01, limit=32)
p.buf.seek(0)
for r in calib.detector(config, src=p):
assert not r.success
assert r.msg == 'too weak signal'
assert not r['success']
assert r['msg'] == 'too weak signal'
def test_too_noisy():
@@ -65,8 +57,8 @@ def test_too_noisy():
signal = np.array([r.choice([-1, 1]) for i in range(int(config.Fs))])
src = BytesIO(common.dumps(signal * 0.5))
for r in calib.detector(config, src=src):
assert not r.success
assert r.msg == 'too noisy signal'
assert not r['success']
assert r['msg'] == 'too noisy signal'
def test_errors():
@@ -102,9 +94,9 @@ def test_drift(freq_err):
src = BytesIO(common.dumps(signal))
iters = 0
for r in calib.detector(config, src, frame_length=frame_length):
assert r.success is True
assert abs(r.rms - rms) < 1e-3
assert abs(r.total - rms) < 1e-3
assert r['success'] is True
assert abs(r['rms'] - rms) < 1e-3
assert abs(r['total'] - rms) < 1e-3
iters += 1
assert iters > 0
@@ -154,3 +146,15 @@ def test_recv_binary_search():
fmt = 'ctl {0:.0f}%'
expected = [mock.call(shell=True, args=fmt.format(100 * g)) for g in gains]
assert check_call.mock_calls == expected
def test_recv_freq_change():
p = ProcessMock()
calib.send(config, p, gain=0.5, limit=2)
offset = p.buf.tell() // 16
p.buf.seek(offset)
messages = [state['msg'] for state in calib.recv_iter(config, p)]
assert messages == [
'good signal', 'good signal', 'good signal',
'frequency change',
'good signal', 'good signal', 'good signal']

View File

@@ -54,14 +54,6 @@ def test_izip():
assert list(common.izip([x, y])) == list(zip(x, y))
def test_holder():
d = {'x': 1, 'y': 2.3}
a = common.AttributeHolder(d)
assert a.x == d['x']
assert a.y == d['y']
assert repr(a) == 'AttributeHolder(x=1, y=2.3)'
def test_configs():
default = config.Configuration()
fastest = config.fastest()

View File

@@ -29,7 +29,6 @@ def test_read():
j += 1
try:
for buf in f:
pass
next(f)
except IOError as e:
assert e.args == ('timeout',)

View File

@@ -14,15 +14,7 @@ logging.basicConfig(level=logging.DEBUG, # useful for debugging
format='%(asctime)s %(levelname)-12s %(message)s')
class Args(object):
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def __getattr__(self, name):
return None
def run(size, chan=None, df=0, success=True, reader=None, cfg=None):
def run(size, chan=None, df=0, success=True, cfg=None):
if cfg is None:
cfg = config.fastest()
tx_data = os.urandom(size)
@@ -43,8 +35,6 @@ def run(size, chan=None, df=0, success=True, reader=None, cfg=None):
rx_data = BytesIO()
dump = BytesIO()
if reader:
rx_audio = reader(rx_audio)
try:
result = main.recv(config=cfg, src=rx_audio, dst=rx_data,
dump_audio=dump, pylab=None)

1
amodem/version.py Normal file
View File

@@ -0,0 +1 @@
'1.13'

View File

@@ -1,3 +0,0 @@
numpy
six
argcomplete

View File

@@ -2,6 +2,16 @@
from setuptools import setup
from setuptools.command.test import test as TestCommand
import os
import ast
def parse_vesrion():
cwd = os.path.dirname(__name__)
version_file = os.path.join(cwd, 'amodem', 'version.py')
tree = ast.parse(open(version_file).read())
expr, = tree.body
return expr.value.s
class PyTest(TestCommand):
@@ -12,11 +22,11 @@ class PyTest(TestCommand):
def run_tests(self):
import sys
import pytest
sys.exit(pytest.main(['tests']))
sys.exit(pytest.main(['.']))
setup(
name='amodem',
version='1.10',
version=parse_vesrion(),
description='Audio Modem Communication Library',
author='Roman Zeyde',
author_email='roman.zeyde@gmail.com',
@@ -38,6 +48,7 @@ setup(
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: System :: Networking',
'Topic :: Communications',

View File

@@ -7,9 +7,9 @@ deps=
pep8
coverage
pylint
six
six
commands=
pep8 amodem/ scripts/ tests/
pep8 amodem/ scripts/
pylint --extension-pkg-whitelist=numpy --report=no amodem --rcfile .pylintrc
coverage run --source amodem/ --omit="*/__main__.py" -m py.test -v tests/
coverage run --source amodem/ --omit="*/__main__.py" -m py.test -v
coverage report