commit c566ee76ef5fd4b043b55f211aadf8b1bfc2334b Author: Cort Buffington Date: Wed Jul 20 16:16:27 2016 -0500 Initial Upload diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d9cd9f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +.DS_Store +.dropbox +*.out +Icon +*.pyc +*.bak +*.lcl +*.conf +*.config +*.json +*.pickle +*.c +*.cpp +*.o +*.h diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..686ff21 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group. n0mjs@me.com + +This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike +3.0 Unported License.To view a copy of this license, visit +http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to +Creative Commons, 444 Castro Street, Suite 900, Mountain View, +California, 94041, USA. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..bc93bc4 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +##PROJECT: Open Source HomeBrew Repeater Proctol Client/Master. +**PURPOSE:** Thanks to the work of Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT we have an open protocol for internetworking DMR repeaters. Unfortunately, there's no generic client and/or master stacks. This project is to build an open-source, python-based implementation. This is a non-commercial license. Atribution is *required* if you use it. + +For those who will ask: This is a piece of software that implements an open-source, amateur radio networking protocol. It is not a network. It is not indended to be a network. It is not intended to replace or circumvent a network. People do those things, code doesn't. + +**PROPERTY:** +This work represents the author's interpretation of the HomeBrew Repeater Protocol, based on the 2015-07-26 documents from DMRplus, "IPSC Protocol Specs for homebrew DMR repeater" as written by Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT, also licenced under Creative Commons BY-NC-SA license. + +**WARRANTY** +None. The owners of this work make absolutley no warranty, express or implied. Use this software at your own risk. + +**PRE-REQUISITE KNOWLEDGE:** +This document assumes the reader is familiar with the Python programming language and DMR. + + +**MORE DOCUMENTATION TO COME** + + +**PREFERRED ATTRIBUTION** + +Attribution is requested to be given with the following information: + + Title: "DMRlink" + Source: "http://github.com/n0mjs710/HBlink" + + Author: "K0USY Group" + Link: "http://k0usy.strikingly.com" + + License: "CC BY-NC-SA 3.0" + Link: "http://creativecommons.org/licenses/by-nc-sa/3.0/" + +An example where Title, Author and License are hyperlinked: + ["HBlink"](http://github.com/n0mjs710/HBlink) by [K0USY Group](http://k0usy.strikingly.com is licensed under [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/) + +An example where hyperlinks are not possible: + "HBlink" (http://github.com/n0mjs710/HBlink) by K0USY Group (http://k0usy.strikingly.com) is licensed under CC BY-NC-SA 3.0 (http://creativecommons.org/licenses/by-nc-sa/3.0/) + +Attribution is requested to be made in any official public or private documentation about the use or derivative use of HBlink in conjunction with whatever function(s), operation(s) or feature(s) employ HBlink in whole or in part. Attribution need only be made once per any such "official" documentation and may be placed wherever deemed appropriate by the author of such documentation. + +***73 DE N0MJS*** + +Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group. n0mjs@me.com + +This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike +3.0 Unported License.To view a copy of this license, visit +http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to +Creative Commons, 444 Castro Street, Suite 900, Mountain View, +California, 94041, USA. diff --git a/hb_message_types.py b/hb_message_types.py new file mode 100644 index 0000000..91f3087 --- /dev/null +++ b/hb_message_types.py @@ -0,0 +1,21 @@ +# Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group. n0mjs@me.com +# +# This work is licensed under the Creative Attribution-NonCommercial-ShareAlike +# 3.0 Unported License.To view a copy of this license, visit +# http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to +# Creative Commons, 444 Castro Street, Suite 900, Mountain View, +# California, 94041, USA. + +# Known HomeBrew Repeater Message Types +# In message below, "ID" is taken to mean the 4-byte HEX repeater ID string of the repaeter(DMR Radio ID) +RPTL = 'RPTL' # Initial LOGIN, RPTL+ID +MSTNAK = 'MSTNAK' # Master negatvive ack, MSTNAK+ID +MSTACK = 'MSTACK' # Master acknowledgement, MSTACK+ID + # if in response to a login, MSTACK+ID+(random 32-bit integer (as a string)) +RPTK = 'RPTK' # See explantation elsewhere about passphrase, ID and SHA-256 hash! +MSTPING = 'MSTPING' # From the repeater, MSTPING+ID +RPTPONG = 'MSTPONG' # From the master, MSTPONG+ID +MSTCL = 'MSTCL' # From the master, MSTCL+ID indicates close-down of the master +RPTCL = 'RPTCL' # From the repeater, RPTCL+ID indicates close-down of the repeater +RPTC = 'RPTC' # From the repeater, information packet about the repeater +DMRD = 'DMRD' # DMR data, format documented elsewhere \ No newline at end of file diff --git a/hblink.cfg b/hblink.cfg new file mode 100644 index 0000000..53bdaae --- /dev/null +++ b/hblink.cfg @@ -0,0 +1,58 @@ +[GLOBAL] +PATH: ./ + +[LOGGER] +LOG_FILE: /tmp/hblink.log +LOG_HANDLERS: console-timed +LOG_LEVEL: INFO +LOG_NAME: HBlink + +[MASTER] +ENABLED: True +IP: +PORT: 54000 +PASSPHRASE: + +[REPEATER-1] +ENABLED: True +IP: +PORT: 54001 +MASTER_IP: 172.16.1.1 +MASTER_PORT: 54000 +PASSPHRASE: homebrew +CALLSIGN: W1ABC +RADIO_ID: 312000 +RX_FREQ: 449.000000 +TX_FREQ: 444.000000 +TX_POWER: 25 +ColorCode: 1 +LATITUDE: 38.00000 +LONGITUDE: -95.00000 +HEIGHT: 75 +LOCATION: Anywhere, USA +DESCRIPTION: This is a cool repeater +URL: www.w1abc.org +SOFTWARE_ID: HBlink v1.0 +PACKAGE_ID: HBlink v1.0 + +[REPEATER-2] +ENABLED: True +IP: +PORT: 54002 +MASTER_IP: 172.16.1.1 +MASTER_PORT: 54000 +PASSPHRASE: homebrew +CALLSIGN: K9ABC +RADIO_ID: 312001 +RX_FREQ: 448.000000 +TX_FREQ: 443.000000 +TX_POWER: 40 +ColorCode: 1 +LATITUDE: 38.10000 +LONGITUDE: -95.10000 +HEIGHT: 50 +LOCATION: Somewhere, USA +DESCRIPTION: This is a cooler repeater +URL: www.k9abc.org +SOFTWARE_ID: HBlink v1.0 +PACKAGE_ID: HBlink v1.0 \ No newline at end of file diff --git a/hblink.py b/hblink.py new file mode 100755 index 0000000..f5ead7e --- /dev/null +++ b/hblink.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python +# +# This work is licensed under the Creative Attribution-NonCommercial-ShareAlike +# 3.0 Unported License.To view a copy of this license, visit +# http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to +# Creative Commons, 444 Castro Street, Suite 900, Mountain View, +# California, 94041, USA. + +from __future__ import print_function + +import ConfigParser +import argparse +import sys +import os +import logging + +from logging.config import dictConfig +from binascii import b2a_hex as h +from socket import gethostbyname + +from twisted.internet.protocol import DatagramProtocol +from twisted.internet import reactor + +__author__ = 'Cortney T. Buffington, N0MJS' +__copyright__ = 'Copyright (c) 2013 - 2016 Cortney T. Buffington, N0MJS and the K0USY Group' +__credits__ = 'Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT' +__license__ = 'Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported' +__maintainer__ = 'Cort Buffington, N0MJS' +__email__ = 'n0mjs@me.com' +__status__ = 'pre-alpha' + +# Change the current directory to the location of the application +os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) + +parser = argparse.ArgumentParser() +parser.add_argument('-c', '--config', action='store', dest='CFG_FILE', help='/full/path/to/config.file (usually hblink.cfg)') + +cli_args = parser.parse_args() + +#************************************************ +# PARSE THE CONFIG FILE AND BUILD STRUCTURE +#************************************************ + +CLIENTS = {} +config = ConfigParser.ConfigParser() + +if not cli_args.CFG_FILE: + cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/hblink.cfg' +try: + if not config.read(cli_args.CFG_FILE): + sys.exit('Configuration file \''+cli_args.CFG_FILE+'\' is not a valid configuration file! Exiting...') +except: + sys.exit('Configuration file \''+cli_args.CFG_FILE+'\' is not a valid configuration file! Exiting...') + +try: + for section in config.sections(): + if section == 'GLOBAL': + # Process GLOBAL items in the configuration + PATH = config.get(section, 'PATH') + + elif section == 'LOGGER': + # Process LOGGER items in the configuration + LOGGER = { + 'LOG_FILE': config.get(section, 'LOG_FILE'), + 'LOG_HANDLERS': config.get(section, 'LOG_HANDLERS'), + 'LOG_LEVEL': config.get(section, 'LOG_LEVEL'), + 'LOG_NAME': config.get(section, 'LOG_NAME') + } + + elif section == 'MASTER': + # HomeBrew Master Configuration + MASTER = { + 'ENABLED': config.getboolean(section, 'ENABLED'), + 'IP': gethostbyname(config.get(section, 'IP')), + 'PORT': config.getint(section, 'PORT'), + 'PASSPHRASE': config.get(section, 'PASSPHRASE') + } + + elif config.getboolean(section, 'ENABLED'): + # HomeBrew Client (Repeater) Configuration(s) + CLIENTS.update({section: { + 'ENABLED': config.getboolean(section, 'ENABLED'), + 'IP': gethostbyname(config.get(section, 'IP')), + 'PORT': config.getint(section, 'PORT'), + 'MASTER_IP': gethostbyname(config.get(section, 'MASTER_IP')), + 'MASTER_PORT': config.getint(section, 'MASTER_PORT'), + 'PASSPHRASE': config.get(section, 'PASSPHRASE'), + 'CALLSIGN': config.get(section, 'CALLSIGN'), + 'RADIO_ID': hex(int(config.get(section, 'RADIO_ID')))[2:].rjust(8,'0').decode('hex'), + 'RX_FREQ': config.get(section, 'RX_FREQ'), + 'TX_FREQ': config.get(section, 'TX_FREQ'), + 'TX_POWER': config.get(section, 'TX_POWER'), + 'COLORCODE': config.get(section, 'COLORCODE'), + 'LATITUDE': config.get(section, 'LATITUDE'), + 'LONGITUDE': config.get(section, 'LONGITUDE'), + 'HEIGHT': config.get(section, 'HEIGHT'), + 'LOCATION': config.get(section, 'LOCATION'), + 'DESCRIPTION': config.get(section, 'DESCRIPTION'), + 'URL': config.get(section, 'URL'), + 'SOFTWARE_ID': config.get(section, 'SOFTWARE_ID'), + 'PACKAGE_ID': config.get(section, 'PACKAGE_ID') + }}) + +except: + sys.exit('Could not parse configuration file, exiting...') + + +#************************************************ +# CONFIGURE THE SYSTEM LOGGER +#************************************************ + +dictConfig({ + 'version': 1, + 'disable_existing_loggers': False, + 'filters': { + }, + 'formatters': { + 'verbose': { + 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' + }, + 'timed': { + 'format': '%(levelname)s %(asctime)s %(message)s' + }, + 'simple': { + 'format': '%(levelname)s %(message)s' + }, + 'syslog': { + 'format': '%(name)s (%(process)d): %(levelname)s %(message)s' + } + }, + 'handlers': { + 'null': { + 'class': 'logging.NullHandler' + }, + 'console': { + 'class': 'logging.StreamHandler', + 'formatter': 'simple' + }, + 'console-timed': { + 'class': 'logging.StreamHandler', + 'formatter': 'timed' + }, + 'file': { + 'class': 'logging.FileHandler', + 'formatter': 'simple', + 'filename': LOGGER['LOG_FILE'], + }, + 'file-timed': { + 'class': 'logging.FileHandler', + 'formatter': 'timed', + 'filename': LOGGER['LOG_FILE'], + }, + 'syslog': { + 'class': 'logging.handlers.SysLogHandler', + 'formatter': 'syslog', + } + }, + 'loggers': { + LOGGER['LOG_NAME']: { + 'handlers': LOGGER['LOG_HANDLERS'].split(','), + 'level': LOGGER['LOG_LEVEL'], + 'propagate': True, + } + } +}) +logger = logging.getLogger(LOGGER['LOG_NAME']) + +#************************************************ +# HERE ARE THE IMPORTANT PARTS +#************************************************ + +class HBMASTER(DatagramProtocol): + def __init__(self, *args, **kwargs): + pass + +class HBCLIENT(DatagramProtocol): + def __init__(self, *args, **kwargs): + pass + + +#************************************************ +# MAIN PROGRAM LOOP STARTS HERE +#************************************************ + +if __name__ == '__main__': + logger.info('HBlink \'HBlink.py\' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING...') + + # HBlink Master + if MASTER: + hbmaster = HBMASTER() + reactor.listenUDP(MASTER['PORT'], hbmaster, interface=MASTER['IP']) + + clients = {} + for client in CLIENTS: + if CLIENTS[client]['ENABLED']: + clients[client] = HBCLIENT(client) + reactor.listenUDP(CLIENTS[client]['PORT'], clients[client], interface=CLIENTS[client]['IP']) + + reactor.run() \ No newline at end of file