Compare commits

...

35 Commits

Author SHA1 Message Date
Roman Zeyde
9dbb3826c7 Bump version: 1.13.1 → 1.14.0 2018-03-11 08:50:09 +02:00
Roman Zeyde
c991b2264e setup: update package status to stable :) 2018-03-11 08:48:21 +02:00
Roman Zeyde
f30d28e39a Bump version: 1.13.0 → 1.13.1 2018-02-18 11:24:05 +02:00
Roman Zeyde
fff10853a9 version: use bumpversion for bumping 2018-02-18 11:23:41 +02:00
Roman Zeyde
147404645c pep8 -> pycodestyle 2018-02-18 11:05:44 +02:00
Roman Zeyde
0e29f9a606 pylint: fix warnings (mostly import-related) 2018-02-18 11:05:43 +02:00
Roman Zeyde
93a174142b Update supported Python version 2018-02-18 10:04:23 +02:00
Roman Zeyde
3210638003 framing: use '%08x' for CRC-32 logging 2018-02-18 10:04:23 +02:00
Roman Zeyde
ceaf893675 config: use '//' for Python 3 (instead of '/') 2018-02-18 10:04:23 +02:00
Roman Zeyde
6629cb6762 Merge pull request #27 from babetoduarte/master
Added basic comment descriptions to the scripts and to some of the so…
2017-10-15 12:05:53 -07:00
Jorge A. Duarte
555186c2d8 Fixed PEP-8 trailing whitespaces on doctrings. 2017-10-15 13:58:15 -05:00
Jorge A. Duarte
66acac3e35 Made PEP8 changes to several scripts and files. 2017-10-15 13:52:22 -05:00
Jorge A. Duarte
e1bdae2069 Made documentation changes as requested, according to PEP-257. 2017-10-15 13:23:57 -05:00
babetoduarte
1ff777d226 Added basic comment descriptions to the scripts and to some of the source files. There's still much to be done, but it's a start. 2017-10-14 17:03:27 -05:00
Roman Zeyde
40460e0291 README: remove unused badges 2017-10-05 17:53:48 +03:00
Roman Zeyde
43b68779a4 travis: add Python 3.6 2017-10-05 17:28:39 +03:00
Roman Zeyde
90aae24600 update LICENSE 2016-05-26 19:20:54 +03:00
Roman Zeyde
4c6315daf2 record: remove print statement 2016-05-26 19:20:08 +03:00
Roman Zeyde
f7a151534f travis: remove Python 3.2 due to broken coverage support 2016-02-06 17:55:27 +02:00
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
32 changed files with 214 additions and 138 deletions

7
.bumpversion.cfg Normal file
View File

@@ -0,0 +1,7 @@
[bumpversion]
commit = True
tag = True
current_version = 1.14.0
[bumpversion:file:setup.py]

View File

@@ -1,18 +1,19 @@
sudo: false
language: python
python:
- "2.6"
- "2.7"
- "3.2"
- "3.3"
- "3.4"
- "3.5"
- "3.6"
install:
- pip install .
- pip install coveralls pep8 mock
- pip install pytest>=2.7.3 --upgrade
- pip install coveralls pycodestyle mock
script:
- pep8 amodem/ scripts/
- pycodestyle 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

View File

@@ -1,6 +1,6 @@
amodem -- Audio Modem Communication Library
Copyright (C) 2014, Roman Zeyde.
Copyright (C) 2014, Roman Zeyde, Google Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -8,8 +8,8 @@ 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
:target: https://landscape.io/github/romanz/amodem/master
:alt: Code Health
.. image:: https://img.shields.io/pypi/pyversions/amodem.svg
:target: https://pypi.python.org/pypi/amodem/
@@ -23,9 +23,6 @@ Audio Modem Communication Library
.. 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
@@ -55,7 +52,7 @@ 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).
@@ -142,7 +139,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
-----
@@ -226,7 +223,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
-------------

View File

@@ -1,14 +1,19 @@
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
from . import main, calib, audio, async
from .config import bitrates
from . import version
import argparse
import logging
import os
import sys
import zlib
import logging
import argparse
import pkg_resources
from . import async
from . import audio
from . import calib
from . import main
from .config import bitrates
# Python 3 has `buffer` attribute for byte-based I/O
_stdin = getattr(sys.stdin, 'buffer', sys.stdin)
@@ -97,6 +102,7 @@ def get_volume_cmd(args):
for c in volume_controllers:
if os.system(c['test']) == 0:
return c[args.command]
return None
def wrap(cls, stream, enable):
@@ -188,11 +194,15 @@ class _Dummy(object):
pass
def _version():
return pkg_resources.require('amodem')[0].version
def _main():
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__,
description = fmt.format(_version(),
config.modem_bps / 1e3, len(config.symbols),
config.Nfreq, config.Fs / 1e3)
interface = None

View File

@@ -1,3 +1,8 @@
"""Code which adds Linux ALSA support for interfaces,
recording and playing.
"""
import subprocess
import logging

View File

@@ -1,6 +1,9 @@
import threading
import six # since `Queue` module was renamed to `queue` (in Python 3)
"""Asynchronous Reading capabilities for amodem."""
import logging
import threading
import six # since `Queue` module was renamed to `queue` (in Python 3)
log = logging.getLogger()

View File

@@ -1,3 +1,5 @@
"""Audio capabilities for amodem."""
import ctypes
import logging
import time

View File

@@ -1,13 +1,16 @@
"""Calibration capabilities for amodem."""
import itertools
import logging
import subprocess
import numpy as np
from . import common
from . import dsp
from . import sampling
from . import stream
import numpy as np
import itertools
import logging
import subprocess
log = logging.getLogger(__name__)
@@ -72,10 +75,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 +93,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
@@ -102,6 +105,7 @@ def volume_calibration(result_iterator, volume_ctl):
def iter_window(iterable, size):
# pylint: disable=stop-iteration-return
block = []
while True:
item = next(iterable)
@@ -111,12 +115,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 +124,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,28 +1,38 @@
""" Common package functionality.
Commom utilities and procedures for amodem.
"""
import itertools
import logging
import numpy as np
import logging
log = logging.getLogger(__name__)
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 +50,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 +62,30 @@ 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. """
# pylint: disable=stop-iteration-return
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

@@ -1,3 +1,5 @@
"""Configuration class."""
import numpy as np
@@ -49,7 +51,7 @@ class Configuration(object):
])
# QAM constellation
Nx = 2 ** int(np.ceil(bits_per_symbol / 2))
Nx = 2 ** int(np.ceil(bits_per_symbol // 2))
Ny = self.Npoints // Nx
symbols = [complex(x, y) for x in range(Nx) for y in range(Ny)]
symbols = np.array(symbols)

View File

@@ -1,12 +1,15 @@
"""Signal detection capabilities for amodem."""
import collections
import itertools
import logging
import numpy as np
from . import dsp
from . import equalizer
from . import common
import numpy as np
import logging
import itertools
import collections
log = logging.getLogger(__name__)

View File

@@ -1,3 +1,5 @@
"""Digital Signal Processing capabilities for amodem."""
import numpy as np
from . import common
@@ -53,14 +55,13 @@ def coherence(x, omega):
n = len(x)
Hc = exp_iwt(-omega, n) / np.sqrt(0.5*n)
norm_x = norm(x)
if norm_x:
return np.dot(Hc, x) / norm_x
else:
if not norm_x:
return 0.0
return np.dot(Hc, x) / norm_x
def linear_regression(x, y):
''' Find (a,b) such that y = a*x + b. '''
""" Find (a,b) such that y = a*x + b. """
x = np.array(x)
y = np.array(y)
mean_x = np.mean(x)
@@ -98,7 +99,7 @@ class MODEM(object):
yield self.encode_map[bits_tuple]
def decode(self, symbols, error_handler=None):
''' Maximum-likelihood decoding, using naive nearest-neighbour. '''
""" Maximum-likelihood decoding, using naive nearest-neighbour. """
symbols_vec = self.symbols
_dec = self.decode_list
for received in symbols:
@@ -111,7 +112,7 @@ class MODEM(object):
def prbs(reg, poly, bits):
''' Simple pseudo-random number generator. '''
""" Simple pseudo-random number generator. """
mask = (1 << bits) - 1
size = 0 # effective register size (in bits)

View File

@@ -1,10 +1,13 @@
"""Audio equalizing capabilities for amodem."""
import itertools
import numpy as np
from . import dsp
from . import sampling
from . import levinson
import numpy as np
import itertools
class Equalizer(object):

View File

@@ -1,10 +1,11 @@
from . import common
import binascii
import functools
import itertools
import binascii
import struct
import logging
import struct
from . import common
log = logging.getLogger(__name__)
@@ -26,8 +27,10 @@ class Checksum(object):
payload = data[self.size:]
expected = _checksum_func(payload)
if received != expected:
log.warning('Invalid checksum: %04x != %04x', received, expected)
log.warning('Invalid checksum: %08x != %08x', received, expected)
raise ValueError('Invalid checksum')
else:
log.debug('Good checksum: %08x', received)
return payload

View File

@@ -2,9 +2,9 @@ import numpy as np
def solver(t, y):
''' Solve Mx = y for x, where M[i,j] = t[|i-j|], in O(N^2) steps.
""" Solve Mx = y for x, where M[i,j] = t[|i-j|], in O(N^2) steps.
See http://en.wikipedia.org/wiki/Levinson_recursion for details.
'''
"""
N = len(t)
assert len(y) == N

View File

@@ -1,6 +1,8 @@
import numpy as np
import logging
import itertools
import logging
import numpy as np
from . import send as _send
from . import recv as _recv
from . import framing, common, stream, detect, sampling

View File

@@ -1,14 +1,15 @@
import functools
import itertools
import logging
import time
import numpy as np
from . import dsp
from . import common
from . import framing
from . import equalizer
import numpy as np
import logging
import itertools
import functools
import time
log = logging.getLogger(__name__)
@@ -28,6 +29,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))
@@ -48,10 +50,10 @@ class Receiver(object):
log.debug('Prefix OK')
def _train(self, sampler, order, lookahead):
Nfreq = len(self.frequencies)
equalizer_length = equalizer.equalizer_length
train_symbols = self.equalizer.train_symbols(equalizer_length)
train_signal = self.equalizer.modulator(train_symbols) * Nfreq
train_signal = (self.equalizer.modulator(train_symbols) *
len(self.frequencies))
prefix = postfix = equalizer.silence_length * self.Nsym
signal_length = equalizer_length * self.Nsym + prefix + postfix
@@ -137,10 +139,10 @@ class Receiver(object):
def _update_sampler(self, errors, sampler):
err = np.array([e for v in errors.values() for e in v])
err = np.mean(np.angle(err))/(2*np.pi) if len(err) else 0
err = np.mean(np.angle(err))/(2*np.pi) if err.size 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):

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env python
import numpy as np
import itertools
from amodem import common
import numpy as np
from . import common
class Interpolator(object):

View File

@@ -1,11 +1,12 @@
import itertools
import logging
import numpy as np
from . import common
from . import equalizer
from . import dsp
import numpy as np
import logging
import itertools
log = logging.getLogger(__name__)

View File

@@ -39,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():
@@ -48,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():
@@ -57,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():
@@ -94,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
@@ -146,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

@@ -4,3 +4,9 @@ from amodem import config
def test_bitrates():
for rate, cfg in sorted(config.bitrates.items()):
assert rate * 1000 == cfg.modem_bps
def test_slowest():
c = config.slowest()
assert c.Npoints == 2
assert list(c.symbols) == [-1j, 1j]

View File

@@ -8,6 +8,7 @@ import pytest
def concat(iterable):
return bytearray(itertools.chain.from_iterable(iterable))
r = random.Random(0)
blob = bytearray(r.randrange(0, 256) for i in range(64 * 1024))

View File

@@ -20,6 +20,6 @@ def test_resample():
def test_coeffs():
I = sampling.Interpolator(width=4, resolution=16)
err = I.filt[0] - [0, 0, 0, 1, 0, 0, 0, 0]
interp = sampling.Interpolator(width=4, resolution=16)
err = interp.filt[0] - [0, 0, 0, 1, 0, 0, 0, 0]
assert np.max(np.abs(err)) < 1e-10

View File

@@ -1 +0,0 @@
'1.12.0'

View File

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

View File

@@ -1,4 +1,11 @@
#!/usr/bin/env python
"""Script that exposes pylab's spectogram plotting
capabilities to the command line. It implements this
for amodem.config Configurations.
"""
import pylab
import numpy as np
from amodem import common
@@ -27,5 +34,6 @@ def main():
pylab.show()
if __name__ == '__main__':
main()

View File

@@ -1,4 +1,9 @@
#!/usr/bin/env python
"""Script that records audio through an interface
and stores it into an amodem.config Configuration.
"""
import argparse
from amodem import audio
from amodem.config import Configuration
@@ -7,7 +12,6 @@ from amodem.config import Configuration
def run(args):
config = Configuration()
with open(args.filename, 'wb') as dst:
print dst
interface = audio.Interface(config=config)
with interface.load(args.audio_library):
src = interface.recorder()

View File

@@ -1,4 +1,10 @@
#!/usr/bin/env python
"""Script that exposes the amodem.resample() function
to the command line, taking parameters via standard
inputs and returning results via standard outputs.
"""
from amodem.sampling import resample
import argparse
import sys
@@ -11,5 +17,6 @@ def main():
resample(src=sys.stdin, dst=sys.stdout, df=args.df)
if __name__ == '__main__':
main()

View File

@@ -2,17 +2,6 @@
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):
def finalize_options(self):
@@ -22,14 +11,14 @@ class PyTest(TestCommand):
def run_tests(self):
import sys
import pytest
sys.exit(pytest.main(['tests']))
sys.exit(pytest.main(['.']))
setup(
name='amodem',
version=parse_vesrion(),
version='1.14.0',
description='Audio Modem Communication Library',
author='Roman Zeyde',
author_email='roman.zeyde@gmail.com',
author_email='dev@romanzey.de',
license='MIT',
url='http://github.com/romanz/amodem',
packages=['amodem'],
@@ -38,16 +27,17 @@ setup(
install_requires=['numpy', 'six'],
platforms=['POSIX'],
classifiers=[
'Development Status :: 4 - Beta',
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Intended Audience :: Information Technology',
'License :: OSI Approved :: MIT License',
'Operating System :: POSIX',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: System :: Networking',
'Topic :: Communications',

10
tox.ini
View File

@@ -1,15 +1,15 @@
[tox]
envlist = py27,py34
envlist = py27,py3
[testenv]
deps=
pytest
mock
pep8
pycodestyle
coverage
pylint
six
six
commands=
pep8 amodem/ scripts/
pylint --extension-pkg-whitelist=numpy --report=no amodem --rcfile .pylintrc
pycodestyle amodem/ scripts/
pylint --extension-pkg-whitelist=numpy --reports=no amodem --rcfile .pylintrc
coverage run --source amodem/ --omit="*/__main__.py" -m py.test -v
coverage report