├── .gitignore ├── LICENSE ├── Makefile ├── README.rst ├── dirus.json ├── dirus ├── __init__.py ├── classes.py ├── cmd.py └── constants.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.deb 2 | *.egg 3 | *.egg-info/ 4 | *.egg/ 5 | *.ignore 6 | *.py[co] 7 | *.py[oc] 8 | *.spl 9 | *.vagrant 10 | .DS_Store 11 | .coverage 12 | .eggs/ 13 | .eggs/* 14 | .idea 15 | .idea/ 16 | .pt 17 | .vagrant/ 18 | RELEASE-VERSION.txt 19 | build/ 20 | cover/ 21 | dist/ 22 | dump.rdb 23 | flake8.log 24 | local/ 25 | local_* 26 | metadata/ 27 | nosetests.xml 28 | output.xml 29 | pylint.log 30 | redis-server.log 31 | redis-server/ 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 Orion Labs, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Dirus. 2 | # 3 | # Source:: https://github.com/ampledata/dirus 4 | # Author:: Greg Albrecht W2GMD 5 | # Copyright:: Copyright 2016 Orion Labs, Inc. 6 | # License:: Apache License, Version 2.0 7 | # 8 | 9 | 10 | .DEFAULT_GOAL := all 11 | 12 | 13 | all: develop 14 | 15 | develop: remember 16 | python setup.py develop 17 | 18 | install_requirements: 19 | pip install -r requirements.txt 20 | 21 | install: remember 22 | python setup.py install 23 | 24 | uninstall: 25 | pip uninstall -y dirus 26 | 27 | reinstall: uninstall install 28 | 29 | remember: 30 | @echo 31 | @echo "Hello from the Makefile..." 32 | @echo "Don't forget to run: 'make install_requirements'" 33 | @echo 34 | 35 | clean: 36 | @rm -rf *.egg* build dist *.py[oc] */*.py[co] cover doctest_pypi.cfg \ 37 | nosetests.xml pylint.log output.xml flake8.log tests.log \ 38 | test-result.xml htmlcov fab.log .coverage 39 | 40 | publish: 41 | python setup.py register sdist upload 42 | 43 | nosetests: remember 44 | python setup.py nosetests 45 | 46 | pep8: remember 47 | flake8 --max-complexity 12 --exit-zero dirus/*.py tests/*.py 48 | 49 | flake8: pep8 50 | 51 | lint: remember 52 | pylint --msg-template="{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}" \ 53 | -r n dirus/*.py tests/*.py || exit 0 54 | 55 | pylint: lint 56 | 57 | test: lint pep8 nosetests 58 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Dirus - SDR to Direwolf Gateway Daemon 2 | ************************************** 3 | 4 | Dirus is a daemon for managing an SDR to Direwolf gateway, the purpose of which 5 | is to allow an SDR (e.g. RTL-SDR, HackRF, etc.) to present as a KISS device 6 | to other software (e.g. APRS Decoders). 7 | 8 | This can be accomplished with Direwolf alone, but Dirus provides an easy way 9 | to configure, manage and daemonize this process. 10 | 11 | Requirements 12 | ============ 13 | 14 | Dirus' requirements are relatively minimal. You'll need at least one FM decoder 15 | tool, and Dire Wolf. 16 | 17 | Either one of: 18 | 19 | * @rxseger's rx_tools https://github.com/rxseger/rx_tools 20 | Good for non-RTL-SDR devices, such as HackRF (tested with Dirus). 21 | 22 | * @steve-m's librtlsdr http://sdr.osmocom.org/trac/wiki/rtl-sdr 23 | Good for RTL-SDR devices. 24 | 25 | Plus: 26 | 27 | * @wb2osz's Dire Wolf https://github.com/wb2osz/direwolf 28 | 29 | That's it! Install one FM Decoder, Dire Wolf, and Dirus and you're off to the races! 30 | 31 | Usage 32 | ===== 33 | 34 | Your best bet is to use dirus with supervisor, or another daemon management tool: 35 | 36 | :: 37 | 38 | # /etc/supervisor.d/dirus.conf 39 | [program:dirus] 40 | command=dirus -c /etc/dirus.json 41 | process_name=%(program_name)s 42 | numprocs=1 43 | numprocs_start=0 44 | priority=999 45 | autostart=true 46 | autorestart=true 47 | startsecs=1 48 | startretries=3 49 | exitcodes=0,2 50 | stopsignal=TERM 51 | stopwaitsecs=10 52 | redirect_stderr=false 53 | stdout_logfile=AUTO 54 | stdout_logfile_maxbytes=50MB 55 | stdout_logfile_backups=10 56 | stdout_capture_maxbytes=0 57 | stdout_events_enabled=false 58 | stderr_logfile=AUTO 59 | stderr_logfile_maxbytes=50MB 60 | stderr_logfile_backups=10 61 | stderr_capture_maxbytes=0 62 | stderr_events_enabled=false 63 | serverurl=AUTO 64 | 65 | 66 | Source 67 | ====== 68 | Github: https://github.com/ampledata/dirus 69 | 70 | Author 71 | ====== 72 | Greg Albrecht W2GMD 73 | 74 | Copyright 75 | ========= 76 | Copyright 2016 Orion Labs, Inc. 77 | 78 | License 79 | ======= 80 | Apache License, Version 2.0 81 | -------------------------------------------------------------------------------- /dirus.json: -------------------------------------------------------------------------------- 1 | { 2 | "rtl": { 3 | "frequency": 144.390, 4 | "ppm": 4.384, 5 | "gain": 0, 6 | "offset_tuning": false, 7 | "device_index": 0, 8 | "sample_rate": 44100 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /dirus/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """Dirus Package.""" 5 | 6 | from .constants import LOG_FORMAT, LOG_LEVEL, SAMPLE_RATE # NOQA 7 | from .classes import Dirus # NOQA 8 | 9 | __author__ = 'Greg Albrecht W2GMD ' 10 | __license__ = 'Apache License, Version 2.0' 11 | __copyright__ = 'Copyright 2016 Orion Labs, Inc.' 12 | -------------------------------------------------------------------------------- /dirus/classes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """Dirus Classes.""" 5 | 6 | import os 7 | import logging 8 | import logging.handlers 9 | import subprocess 10 | import tempfile 11 | import threading 12 | import time 13 | 14 | import dirus 15 | 16 | __author__ = 'Greg Albrecht W2GMD ' 17 | __license__ = 'Apache License, Version 2.0' 18 | __copyright__ = 'Copyright 2016 Orion Labs, Inc.' 19 | 20 | 21 | class Dirus(threading.Thread): 22 | 23 | """Dirus Class.""" 24 | 25 | _logger = logging.getLogger(__name__) 26 | if not _logger.handlers: 27 | _logger.setLevel(dirus.LOG_LEVEL) 28 | _console_handler = logging.StreamHandler() 29 | _console_handler.setLevel(dirus.LOG_LEVEL) 30 | _console_handler.setFormatter(dirus.LOG_FORMAT) 31 | _logger.addHandler(_console_handler) 32 | _logger.propagate = False 33 | 34 | def __init__(self, config): 35 | threading.Thread.__init__(self) 36 | self.config = config 37 | self.direwolf_conf = None 38 | self.processes = {} 39 | self.daemon = True 40 | self._stop = threading.Event() 41 | 42 | def __del__(self): 43 | self.stop() 44 | 45 | def _write_direwolf_conf(self): 46 | tmp_fd, self.direwolf_conf = tempfile.mkstemp( 47 | prefix='dirus_', suffix='.conf') 48 | os.write(tmp_fd, "ADEVICE null null\n") 49 | os.close(tmp_fd) 50 | 51 | def run(self): 52 | self._write_direwolf_conf() 53 | 54 | # Allow use of 'rx_fm' for Soapy/HackRF 55 | rtl_cmd = self.config['rtl'].get('command', 'rtl_fm') 56 | 57 | frequency = "%sM" % self.config['rtl']['frequency'] 58 | sample_rate = self.config['rtl'].get('sample_rate', dirus.SAMPLE_RATE) 59 | ppm = self.config['rtl'].get('ppm') 60 | gain = self.config['rtl'].get('gain') 61 | device_index = self.config['rtl'].get('device_index', '0') 62 | 63 | if bool(self.config['rtl'].get('offset_tuning')): 64 | enable_option = 'offset' 65 | else: 66 | enable_option = 'none' 67 | 68 | src_cmd = [rtl_cmd] 69 | src_cmd.extend(('-f', frequency)) 70 | src_cmd.extend(('-s', sample_rate)) 71 | src_cmd.extend(('-E', enable_option)) 72 | src_cmd.extend(('-d', device_index)) 73 | 74 | if ppm is not None: 75 | src_cmd.extend(('-p', ppm)) 76 | 77 | if gain is not None: 78 | src_cmd.extend(('-g', gain)) 79 | 80 | src_cmd.append('-') 81 | 82 | src_cmd = map(str, src_cmd) 83 | 84 | self._logger.debug('src_cmd="%s"', ' '.join(src_cmd)) 85 | 86 | src_proc = subprocess.Popen( 87 | src_cmd, 88 | stdout=subprocess.PIPE 89 | ) 90 | 91 | self.processes['src'] = src_proc 92 | 93 | direwolf_cmd = ['direwolf'] 94 | 95 | # Configuration file name. 96 | direwolf_cmd.extend(('-c', self.direwolf_conf)) 97 | # Text colors. 1=normal, 0=disabled. 98 | direwolf_cmd.extend(('-t', 0)) 99 | # Number of audio channels, 1 or 2. 100 | direwolf_cmd.extend(('-n', 1)) 101 | # Bits per audio sample, 8 or 16. 102 | direwolf_cmd.extend(('-b', 16)) 103 | # Read from STDIN. 104 | direwolf_cmd.append('-') 105 | 106 | direwolf_cmd = map(str, direwolf_cmd) 107 | 108 | self._logger.debug('direwolf_cmd="%s"', ' '.join(direwolf_cmd)) 109 | 110 | direwolf_proc = subprocess.Popen( 111 | direwolf_cmd, 112 | stdin=self.processes['src'].stdout, 113 | stdout=subprocess.PIPE 114 | ) 115 | 116 | self.processes['direwolf'] = direwolf_proc 117 | 118 | while not self.stopped(): 119 | time.sleep(0.01) 120 | 121 | def stop(self): 122 | """ 123 | Stop the thread at the next opportunity. 124 | """ 125 | for name in ['direwolf', 'src']: 126 | try: 127 | proc = self.processes[name] 128 | proc.terminate() 129 | except Exception as ex: 130 | self._logger.exception( 131 | 'Raised Exception while trying to terminate %s: %s', 132 | name, ex) 133 | self._stop.set() 134 | 135 | def stopped(self): 136 | """ 137 | Checks if the thread is stopped. 138 | """ 139 | return self._stop.isSet() 140 | -------------------------------------------------------------------------------- /dirus/cmd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """Dirus Commands""" 5 | 6 | import argparse 7 | import json 8 | import time 9 | 10 | import dirus 11 | 12 | __author__ = 'Greg Albrecht W2GMD ' 13 | __license__ = 'Apache License, Version 2.0' 14 | __copyright__ = 'Copyright 2016 Orion Labs, Inc.' 15 | 16 | 17 | def cli(): 18 | parser = argparse.ArgumentParser(description='Dirus') 19 | 20 | parser.add_argument( 21 | '-c', dest='config', 22 | default='dirus.json', 23 | help='Use this config file') 24 | args = parser.parse_args() 25 | 26 | with open(args.config) as config_file: 27 | config = json.load(config_file) 28 | 29 | print 'Starting Dirus...' 30 | 31 | dgate = dirus.Dirus(config) 32 | 33 | try: 34 | dgate.start() 35 | while dgate.is_alive(): 36 | time.sleep(0.01) 37 | except KeyboardInterrupt: 38 | dgate.stop() 39 | finally: 40 | dgate.stop() 41 | 42 | 43 | if __name__ == '__main__': 44 | cli() 45 | -------------------------------------------------------------------------------- /dirus/constants.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """Dirus Constants.""" 5 | 6 | import logging 7 | 8 | __author__ = 'Greg Albrecht W2GMD ' 9 | __license__ = 'Apache License, Version 2.0' 10 | __copyright__ = 'Copyright 2016 Orion Labs, Inc.' 11 | 12 | 13 | LOG_LEVEL = logging.DEBUG 14 | LOG_FORMAT = logging.Formatter( 15 | '%(asctime)s dirus %(levelname)s %(name)s.%(funcName)s:%(lineno)d' 16 | ' - %(message)s') 17 | 18 | SAMPLE_RATE = 44100 19 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Python Distribution Package Requirements for Dirus. 2 | # 3 | # Source:: https://github.com/ampledata/dirus 4 | # Author:: Greg Albrecht W2GMD 5 | # Copyright:: Copyright 2016 Orion Labs, Inc. 6 | # License:: Apache License, Version 2.0 7 | # 8 | 9 | 10 | flake8 11 | pylint 12 | twine 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Setup for Dirus. 6 | 7 | Source:: https://github.com/ampledata/dirus 8 | """ 9 | 10 | import setuptools 11 | import sys 12 | 13 | __title__ = 'dirus' 14 | __version__ = '1.0.0' 15 | __author__ = 'Greg Albrecht W2GMD ' 16 | __license__ = 'Apache License, Version 2.0' 17 | __copyright__ = 'Copyright 2016 Orion Labs, Inc.' 18 | 19 | 20 | def publish(): 21 | """Function for publishing package to pypi.""" 22 | if sys.argv[-1] == 'publish': 23 | os.system('python setup.py sdist') 24 | os.system('twine upload dist/*') 25 | sys.exit() 26 | 27 | 28 | publish() 29 | 30 | 31 | setuptools.setup( 32 | author='Greg Albrecht', 33 | author_email='oss@undef.net', 34 | description='Dirus', 35 | entry_points={ 36 | 'console_scripts': [ 37 | 'dirus = dirus.cmd:cli' 38 | ] 39 | }, 40 | include_package_data=True, 41 | install_requires=['aprs'], 42 | license=open('LICENSE').read(), 43 | long_description=open('README.rst').read(), 44 | name='dirus', 45 | package_data={'': ['LICENSE']}, 46 | package_dir={'dirus': 'dirus'}, 47 | packages=['dirus'], 48 | url='http://github.com/ampledata/dirus', 49 | version=__version__, 50 | zip_safe=False 51 | ) 52 | --------------------------------------------------------------------------------