Client Fully Connects with BrandMeister
Thanks to Colin Durbridge G4EML for figuring out MANY of the BrandMeister changes to HB Protocol (some not documented).
This commit is contained in:
parent
a8dc160bb4
commit
addc9cb52c
12
hb_config.py
12
hb_config.py
@ -23,7 +23,8 @@ def build_config(_config_file):
|
|||||||
|
|
||||||
# Process GLOBAL items in the configuration
|
# Process GLOBAL items in the configuration
|
||||||
CONFIG['GLOBAL'].update({
|
CONFIG['GLOBAL'].update({
|
||||||
'PATH': config.get(section, 'PATH')
|
'PATH': config.get(section, 'PATH'),
|
||||||
|
'PING_TIME': config.getint(section, 'PING_TIME')
|
||||||
})
|
})
|
||||||
|
|
||||||
elif section == 'LOGGER':
|
elif section == 'LOGGER':
|
||||||
@ -49,13 +50,14 @@ def build_config(_config_file):
|
|||||||
'RADIO_ID': hex(int(config.get(section, 'RADIO_ID')))[2:].rjust(8,'0').decode('hex'),
|
'RADIO_ID': hex(int(config.get(section, 'RADIO_ID')))[2:].rjust(8,'0').decode('hex'),
|
||||||
'RX_FREQ': config.get(section, 'RX_FREQ').rjust(9),
|
'RX_FREQ': config.get(section, 'RX_FREQ').rjust(9),
|
||||||
'TX_FREQ': config.get(section, 'TX_FREQ').rjust(9),
|
'TX_FREQ': config.get(section, 'TX_FREQ').rjust(9),
|
||||||
'TX_POWER': config.get(section, 'TX_POWER').rjust(2),
|
'TX_POWER': config.get(section, 'TX_POWER').rjust(2,'0'),
|
||||||
'COLORCODE': config.get(section, 'COLORCODE').rjust(2),
|
'COLORCODE': config.get(section, 'COLORCODE').rjust(2,'0'),
|
||||||
'LATITUDE': config.get(section, 'LATITUDE').rjust(8),
|
'LATITUDE': config.get(section, 'LATITUDE').rjust(8),
|
||||||
'LONGITUDE': config.get(section, 'LONGITUDE').rjust(9),
|
'LONGITUDE': config.get(section, 'LONGITUDE').rjust(9),
|
||||||
'HEIGHT': config.get(section, 'HEIGHT').rjust(3),
|
'HEIGHT': config.get(section, 'HEIGHT').rjust(3,'0'),
|
||||||
'LOCATION': config.get(section, 'LOCATION').rjust(20),
|
'LOCATION': config.get(section, 'LOCATION').rjust(20),
|
||||||
'DESCRIPTION': config.get(section, 'DESCRIPTION').rjust(20),
|
'DESCRIPTION': config.get(section, 'DESCRIPTION').rjust(19),
|
||||||
|
'SLOTS': config.get(section, 'SLOTS'),
|
||||||
'URL': config.get(section, 'URL').rjust(124),
|
'URL': config.get(section, 'URL').rjust(124),
|
||||||
'SOFTWARE_ID': config.get(section, 'SOFTWARE_ID').rjust(40),
|
'SOFTWARE_ID': config.get(section, 'SOFTWARE_ID').rjust(40),
|
||||||
'PACKAGE_ID': config.get(section, 'PACKAGE_ID').rjust(40)
|
'PACKAGE_ID': config.get(section, 'PACKAGE_ID').rjust(40)
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
# 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 = 'RPTPONG' # 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
|
|
@ -1,12 +1,25 @@
|
|||||||
|
# PROGRAM-WIDE PARAMETERS GO HERE
|
||||||
|
# PATH - working path for files, leave it alone unless you NEED to change it
|
||||||
|
# PING_TIME - the interval that clients will ping the master, and re-try registraion
|
||||||
[GLOBAL]
|
[GLOBAL]
|
||||||
PATH: ./
|
PATH: ./
|
||||||
|
PING_TIME
|
||||||
|
|
||||||
|
# LOGGING CONFIGURATION
|
||||||
|
# Should be self explanatory. See Python logging module for more information
|
||||||
|
# Several convenient handlers have been pre-configured, check out the module
|
||||||
|
# hb_log.py to see them
|
||||||
[LOGGER]
|
[LOGGER]
|
||||||
LOG_FILE: /tmp/hblink.log
|
LOG_FILE: /tmp/hblink.log
|
||||||
LOG_HANDLERS: console-timed
|
LOG_HANDLERS: console-timed
|
||||||
LOG_LEVEL: DEBUG
|
LOG_LEVEL: DEBUG
|
||||||
LOG_NAME: HBlink
|
LOG_NAME: HBlink
|
||||||
|
|
||||||
|
# MASTER INSTANCES - DUPLICATE SECTION FOR MULTIPLE CLIENTS
|
||||||
|
# HomeBrew Protocol Master instances go here.
|
||||||
|
# IP may be left blank if there's one interface on your system.
|
||||||
|
# Port should be the port you want this master to listen on. It must be unique
|
||||||
|
# and unused by anything else.
|
||||||
[MASTER-1]
|
[MASTER-1]
|
||||||
MODE: MASTER
|
MODE: MASTER
|
||||||
ENABLED: True
|
ENABLED: True
|
||||||
@ -14,13 +27,13 @@ IP:
|
|||||||
PORT: 54000
|
PORT: 54000
|
||||||
PASSPHRASE: s3cr37w0rd
|
PASSPHRASE: s3cr37w0rd
|
||||||
|
|
||||||
[MASTER-2]
|
# CLIENT INSTANCES - DUPLICATE SECTION FOR MULTIPLE CLIENTS
|
||||||
MODE: MASTER
|
# There are a LOT of errors in the HB Protocol specifications on this one!
|
||||||
ENABLED: False
|
# MOST of these items are just strings and will be properly dealt with by the program
|
||||||
IP:
|
# The TX & RX Frequencies are 9-digit numbers, and are the frequency in Hz.
|
||||||
PORT: 55000
|
# Latitude is an 8-digit unsigned floating point number.
|
||||||
PASSPHRASE: 13370p3r470r
|
# Longitude is a 9-digit signed floating point number.
|
||||||
|
# Height is in meters
|
||||||
[REPEATER-1]
|
[REPEATER-1]
|
||||||
MODE: CLIENT
|
MODE: CLIENT
|
||||||
ENABLED: True
|
ENABLED: True
|
||||||
@ -31,38 +44,15 @@ MASTER_PORT: 54000
|
|||||||
PASSPHRASE: homebrew
|
PASSPHRASE: homebrew
|
||||||
CALLSIGN: W1ABC
|
CALLSIGN: W1ABC
|
||||||
RADIO_ID: 312000
|
RADIO_ID: 312000
|
||||||
RX_FREQ: 449.000000
|
RX_FREQ: 449000000
|
||||||
TX_FREQ: 444.000000
|
TX_FREQ: 444000000
|
||||||
TX_POWER: 25
|
TX_POWER: 25
|
||||||
ColorCode: 1
|
ColorCode: 1
|
||||||
LATITUDE: 38.00000
|
LATITUDE: 038.0000
|
||||||
LONGITUDE: -95.00000
|
LONGITUDE: -095.0000
|
||||||
HEIGHT: 75
|
HEIGHT: 75
|
||||||
LOCATION: Anywhere, USA
|
LOCATION: Anywhere, USA
|
||||||
DESCRIPTION: This is a cool repeater
|
DESCRIPTION: This is a cool repeater
|
||||||
URL: www.w1abc.org
|
URL: www.w1abc.org
|
||||||
SOFTWARE_ID: HBlink v1.0
|
SOFTWARE_ID: HBlink
|
||||||
PACKAGE_ID: HBlink v1.0
|
PACKAGE_ID: v0.1
|
||||||
|
|
||||||
[REPEATER-2]
|
|
||||||
MODE: CLIENT
|
|
||||||
ENABLED: False
|
|
||||||
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
|
|
55
hblink.py
55
hblink.py
@ -12,6 +12,7 @@ from __future__ import print_function
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import signal
|
||||||
|
|
||||||
# Specifig functions from modules we need
|
# Specifig functions from modules we need
|
||||||
from binascii import b2a_hex as h
|
from binascii import b2a_hex as h
|
||||||
@ -31,12 +32,11 @@ from twisted.internet import task
|
|||||||
# Other files we pull from -- this is mostly for readability and segmentation
|
# Other files we pull from -- this is mostly for readability and segmentation
|
||||||
import hb_log
|
import hb_log
|
||||||
import hb_config
|
import hb_config
|
||||||
from hb_message_types import *
|
|
||||||
|
|
||||||
# Does anybody read this stuff? There's a PEP somewhere that says I should do this.
|
# Does anybody read this stuff? There's a PEP somewhere that says I should do this.
|
||||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||||
__copyright__ = 'Copyright (c) 2013 - 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
|
__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'
|
__credits__ = 'Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT'
|
||||||
__license__ = 'Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported'
|
__license__ = 'Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported'
|
||||||
__maintainer__ = 'Cort Buffington, N0MJS'
|
__maintainer__ = 'Cort Buffington, N0MJS'
|
||||||
__email__ = 'n0mjs@me.com'
|
__email__ = 'n0mjs@me.com'
|
||||||
@ -69,7 +69,20 @@ if cli_args.LOG_LEVEL:
|
|||||||
logger = hb_log.config_logging(CONFIG['LOGGER'])
|
logger = hb_log.config_logging(CONFIG['LOGGER'])
|
||||||
logger.debug('Logging system started, anything from here on gets logged')
|
logger.debug('Logging system started, anything from here on gets logged')
|
||||||
|
|
||||||
|
# Shut ourselves down gracefully by disconnecting from the masters and clients.
|
||||||
|
def handler(_signal, _frame):
|
||||||
|
logger.info('*** HBLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
|
||||||
|
|
||||||
|
for client in clients:
|
||||||
|
this_client = clients[client]
|
||||||
|
this_client.send_packet('RPTCL'+CONFIG['CLIENTS'][client]['RADIO_ID'])
|
||||||
|
logger.info('(%s) De-Registering From the Master', client)
|
||||||
|
|
||||||
|
reactor.stop()
|
||||||
|
|
||||||
|
# Set signal handers so that we can gracefully exit if need be
|
||||||
|
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
|
||||||
|
signal.signal(sig, handler)
|
||||||
|
|
||||||
#************************************************
|
#************************************************
|
||||||
# HERE ARE THE IMPORTANT PARTS
|
# HERE ARE THE IMPORTANT PARTS
|
||||||
@ -95,39 +108,37 @@ class HBCLIENT(DatagramProtocol):
|
|||||||
logger.error('(%s) HBCLIENT was not called with an argument. Terminating', self._client)
|
logger.error('(%s) HBCLIENT was not called with an argument. Terminating', self._client)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
def send_packet(self, _packet):
|
|
||||||
print('did this')
|
|
||||||
self.transport.write(_packet, (self._config['MASTER_IP'], self._config['MASTER_PORT']))
|
|
||||||
# KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!!
|
|
||||||
logger.debug('(%s) TX Packet to %s on port %s: %s', self._client, self._config['MASTER_IP'], self._config['MASTER_PORT'], h(_packet))
|
|
||||||
|
|
||||||
def startProtocol(self):
|
def startProtocol(self):
|
||||||
# Set up periodic loop for sending pings to the master. Run every minute
|
# Set up periodic loop for sending pings to the master. Run every minute
|
||||||
self._peer_maintenance = task.LoopingCall(self.peer_maintenance_loop)
|
self._peer_maintenance = task.LoopingCall(self.peer_maintenance_loop)
|
||||||
self._peer_maintenance_loop = self._peer_maintenance.start(10)
|
self._peer_maintenance_loop = self._peer_maintenance.start(CONFIG['GLOBAL']['PING_TIME'])
|
||||||
|
|
||||||
def peer_maintenance_loop(self):
|
def peer_maintenance_loop(self):
|
||||||
if self._stats['CONNECTION'] == 'NO':
|
if self._stats['CONNECTION'] == 'NO':
|
||||||
self.send_packet(RPTL+self._config['RADIO_ID'])
|
self._stats['PINGS_SENT'] = 0
|
||||||
|
self._stats['PINGS_ACKD'] = 0
|
||||||
self._stats['CONNECTION'] = 'RTPL_SENT'
|
self._stats['CONNECTION'] = 'RTPL_SENT'
|
||||||
|
self.send_packet('RPTL'+self._config['RADIO_ID'])
|
||||||
logger.debug('(%s) Sending login request to master', self._client)
|
logger.debug('(%s) Sending login request to master', self._client)
|
||||||
if self._stats['CONNECTION'] == 'YES':
|
if self._stats['CONNECTION'] == 'YES':
|
||||||
self.send_packet('RPTPING'+self._config['RADIO_ID'])
|
self.send_packet('RPTPING'+self._config['RADIO_ID'])
|
||||||
logger.debug('(%s) Ping Sent to Master', self._client)
|
self._stats['PINGS_SENT'] += 1
|
||||||
|
logger.info('(%s) RPTPING Sent to Master. Total Pings Since Connected: %s', self._client, self._stats['PINGS_SENT'])
|
||||||
|
|
||||||
def send_packet(self, _packet):
|
def send_packet(self, _packet):
|
||||||
self.transport.write(_packet, (self._config['MASTER_IP'], self._config['MASTER_PORT']))
|
self.transport.write(_packet, (self._config['MASTER_IP'], self._config['MASTER_PORT']))
|
||||||
# KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!!
|
# KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!!
|
||||||
logger.debug('(%s) TX Packet to %s on port %s: %s', self._client, self._config['MASTER_IP'], self._config['MASTER_PORT'], h(_packet))
|
#logger.debug('(%s) TX Packet to %s on port %s: %s', self._client, self._config['MASTER_IP'], self._config['MASTER_PORT'], h(_packet))
|
||||||
|
|
||||||
def datagramReceived(self, _data, (_host, _port)):
|
def datagramReceived(self, _data, (_host, _port)):
|
||||||
|
|
||||||
_command = _data[:4]
|
_command = _data[:4]
|
||||||
if _command == 'DMRD': # DMRData -- encapsulated DMR data frame
|
if _command == 'DMRD': # DMRData -- encapsulated DMR data frame
|
||||||
print('DMRD Received')
|
logger.debug('(%s) DMRD Received', self._client)
|
||||||
|
|
||||||
elif _command == 'MSTN': # Actually MSTNAK -- a NACK from the master
|
elif _command == 'MSTN': # Actually MSTNAK -- a NACK from the master
|
||||||
print('MSTNAC Received')
|
print('(%s) MSTNAC Received', self._client)
|
||||||
|
self._stats['CONNECTION'] = 'NO'
|
||||||
|
|
||||||
elif _command == 'RPTA': # Actually RPTACK -- an ACK from the master
|
elif _command == 'RPTA': # Actually RPTACK -- an ACK from the master
|
||||||
if self._stats['CONNECTION'] == 'RTPL_SENT':
|
if self._stats['CONNECTION'] == 'RTPL_SENT':
|
||||||
@ -141,7 +152,7 @@ class HBCLIENT(DatagramProtocol):
|
|||||||
elif self._stats['CONNECTION'] == 'AUTHENTICATED':
|
elif self._stats['CONNECTION'] == 'AUTHENTICATED':
|
||||||
if _data[6:10] == self._config['RADIO_ID']:
|
if _data[6:10] == self._config['RADIO_ID']:
|
||||||
logger.info('(%s) Repeater Authentication Accepted', self._client)
|
logger.info('(%s) Repeater Authentication Accepted', self._client)
|
||||||
_config_packet = str(int(h(self._config['RADIO_ID']), 16)).rjust(8)+\
|
_config_packet = self._config['RADIO_ID']+\
|
||||||
self._config['CALLSIGN']+\
|
self._config['CALLSIGN']+\
|
||||||
self._config['RX_FREQ']+\
|
self._config['RX_FREQ']+\
|
||||||
self._config['TX_FREQ']+\
|
self._config['TX_FREQ']+\
|
||||||
@ -152,30 +163,34 @@ class HBCLIENT(DatagramProtocol):
|
|||||||
self._config['HEIGHT']+\
|
self._config['HEIGHT']+\
|
||||||
self._config['LOCATION']+\
|
self._config['LOCATION']+\
|
||||||
self._config['DESCRIPTION']+\
|
self._config['DESCRIPTION']+\
|
||||||
|
self._config['SLOTS']+\
|
||||||
self._config['URL']+\
|
self._config['URL']+\
|
||||||
self._config['SOFTWARE_ID']+\
|
self._config['SOFTWARE_ID']+\
|
||||||
self._config['PACKAGE_ID']
|
self._config['PACKAGE_ID']
|
||||||
|
|
||||||
self.send_packet('RPTC'+_config_packet)
|
self.send_packet('RPTC'+_config_packet)
|
||||||
print(len('RPTC'+_config_packet))
|
|
||||||
self._stats['CONNECTION'] = 'CONFIG-SENT'
|
self._stats['CONNECTION'] = 'CONFIG-SENT'
|
||||||
|
logger.info('(%s) Repeater Configuration Sent', self._client)
|
||||||
|
|
||||||
elif self._stats['CONNECTION'] == 'CONFIG-SENT':
|
elif self._stats['CONNECTION'] == 'CONFIG-SENT':
|
||||||
if _data[6:10] == self._config['RADIO_ID']:
|
if _data[6:10] == self._config['RADIO_ID']:
|
||||||
logger.info('(%s) Repeater Configuration Accepted', self._client)
|
logger.info('(%s) Repeater Configuration Accepted', self._client)
|
||||||
self._stats['CONNECTION'] = 'YES'
|
self._stats['CONNECTION'] = 'YES'
|
||||||
|
logger.info('(%s) Connection to Master Completed', self._client)
|
||||||
|
|
||||||
elif _command == 'MSTP': # Actually MSTPONG -- a reply to RPTPING (send by client)
|
elif _command == 'MSTP': # Actually MSTPONG -- a reply to RPTPING (send by client)
|
||||||
print('MSTPONG Received')
|
self._stats['PINGS_ACKD'] += 1
|
||||||
|
logger.info('(%s) MSTPONG Received. Total Pongs Since Connected: %s', self._client, self._stats['PINGS_ACKD'])
|
||||||
|
|
||||||
elif _command == 'MSTC': # Actually MSTCL -- notify the master this client is closing
|
elif _command == 'MSTC': # Actually MSTCL -- notify us the master is closing down
|
||||||
print('MSTCL Recieved')
|
self._stats['CONNECTION'] = 'NO'
|
||||||
|
logger.info('(%s) MSTCL Recieved', self._client)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.error('(%s) Received an invalid command in packet: %s', self._client, h(_data))
|
logger.error('(%s) Received an invalid command in packet: %s', self._client, h(_data))
|
||||||
|
|
||||||
|
|
||||||
print('Received Packet:', h(_data))
|
#print('Received Packet:', h(_data))
|
||||||
|
|
||||||
|
|
||||||
#************************************************
|
#************************************************
|
||||||
|
Loading…
x
Reference in New Issue
Block a user