merged into branch

This commit is contained in:
KF7EEL 2020-11-22 11:24:02 -08:00
commit d55a23a42f
6 changed files with 190 additions and 19 deletions

View File

@ -1,3 +1,9 @@
---
# In this fork, APRS beaconing of peers is set on a per master configuration. Also, private-call-dev has been merged with master. Now with GPS decoding and APRS location reports for Anytone radios.
This is my "flavor" of HBLink3 that I use in production.
--- ---
### FOR SUPPORT, DISCUSSION, GETTING INVOLVED ### ### FOR SUPPORT, DISCUSSION, GETTING INVOLVED ###

0
bridge.py Executable file → Normal file
View File

17
config.py Executable file → Normal file
View File

@ -26,6 +26,9 @@ updated if the items in the main configuraiton file (usually hblink.cfg)
change. change.
''' '''
# Added config option for APRS in the master config section
# Modified by KF7EEL - 10-15-2020
import configparser import configparser
import sys import sys
import const import const
@ -104,6 +107,7 @@ def build_config(_config_file):
CONFIG = {} CONFIG = {}
CONFIG['GLOBAL'] = {} CONFIG['GLOBAL'] = {}
CONFIG['APRS'] = {}
CONFIG['REPORTS'] = {} CONFIG['REPORTS'] = {}
CONFIG['LOGGER'] = {} CONFIG['LOGGER'] = {}
CONFIG['ALIASES'] = {} CONFIG['ALIASES'] = {}
@ -123,6 +127,15 @@ def build_config(_config_file):
'TG2_ACL': config.get(section, 'TGID_TS2_ACL') 'TG2_ACL': config.get(section, 'TGID_TS2_ACL')
}) })
elif section == 'APRS':
CONFIG['APRS'].update({
'ENABLED': config.getboolean(section, 'ENABLED'),
'CALLSIGN': config.get(section, 'CALLSIGN'),
'REPORT_INTERVAL': config.getint(section, 'REPORT_INTERVAL'),
'SERVER': config.get(section, 'SERVER'),
'MESSAGE': config.get(section, 'MESSAGE')
})
elif section == 'REPORTS': elif section == 'REPORTS':
CONFIG['REPORTS'].update({ CONFIG['REPORTS'].update({
'REPORT': config.getboolean(section, 'REPORT'), 'REPORT': config.getboolean(section, 'REPORT'),
@ -182,7 +195,7 @@ def build_config(_config_file):
'SOFTWARE_ID': bytes(config.get(section, 'SOFTWARE_ID').ljust(40)[:40], 'utf-8'), 'SOFTWARE_ID': bytes(config.get(section, 'SOFTWARE_ID').ljust(40)[:40], 'utf-8'),
'PACKAGE_ID': bytes(config.get(section, 'PACKAGE_ID').ljust(40)[:40], 'utf-8'), 'PACKAGE_ID': bytes(config.get(section, 'PACKAGE_ID').ljust(40)[:40], 'utf-8'),
'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME'), 'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME'),
'OPTIONS': b''.join([b'Type=HBlink;', bytes(config.get(section, 'OPTIONS'), 'utf-8')]), 'OPTIONS': bytes(config.get(section, 'OPTIONS'), 'utf-8'),
'USE_ACL': config.getboolean(section, 'USE_ACL'), 'USE_ACL': config.getboolean(section, 'USE_ACL'),
'SUB_ACL': config.get(section, 'SUB_ACL'), 'SUB_ACL': config.get(section, 'SUB_ACL'),
'TG1_ACL': config.get(section, 'TGID_TS1_ACL'), 'TG1_ACL': config.get(section, 'TGID_TS1_ACL'),
@ -249,6 +262,7 @@ def build_config(_config_file):
CONFIG['SYSTEMS'].update({section: { CONFIG['SYSTEMS'].update({section: {
'MODE': config.get(section, 'MODE'), 'MODE': config.get(section, 'MODE'),
'ENABLED': config.getboolean(section, 'ENABLED'), 'ENABLED': config.getboolean(section, 'ENABLED'),
'APRS_ENABLED': config.getboolean(section, 'APRS_ENABLED'),
'REPEAT': config.getboolean(section, 'REPEAT'), 'REPEAT': config.getboolean(section, 'REPEAT'),
'MAX_PEERS': config.getint(section, 'MAX_PEERS'), 'MAX_PEERS': config.getint(section, 'MAX_PEERS'),
'IP': gethostbyname(config.get(section, 'IP')), 'IP': gethostbyname(config.get(section, 'IP')),
@ -322,3 +336,4 @@ if __name__ == '__main__':
return not _acl[0] return not _acl[0]
print(acl_check(b'\x00\x01\x37', CONFIG['GLOBAL']['TG1_ACL'])) print(acl_check(b'\x00\x01\x37', CONFIG['GLOBAL']['TG1_ACL']))

14
hblink-SAMPLE.cfg Executable file → Normal file
View File

@ -46,6 +46,19 @@ SUB_ACL: DENY:1
TGID_TS1_ACL: PERMIT:ALL TGID_TS1_ACL: PERMIT:ALL
TGID_TS2_ACL: PERMIT:ALL TGID_TS2_ACL: PERMIT:ALL
# APRS - BY IU7IGU
# Enabling "APRS" will configure APRS-Beaconing of Master's connection
# like repeater and hotspots.
# REPORT_INTERVAL in Minute (ALLOW only > 3 Minutes)
# CALLSIGN: Callsign that will pubblish data on aprs server
# MESSAGE: This message will print on APRS description together RX and TX Frequency
[APRS]
ENABLED: False
REPORT_INTERVAL: 5
CALLSIGN:HB1LNK-11
SERVER:euro.aprs2.net
MESSAGE:Connesso ad HBLINK
# NOT YET WORKING: NETWORK REPORTING CONFIGURATION # NOT YET WORKING: NETWORK REPORTING CONFIGURATION
# Enabling "REPORT" will configure a socket-based reporting # Enabling "REPORT" will configure a socket-based reporting
@ -154,6 +167,7 @@ TGID_ACL: PERMIT:ALL
[MASTER-1] [MASTER-1]
MODE: MASTER MODE: MASTER
ENABLED: True ENABLED: True
APRS_ENABLED: False
REPEAT: True REPEAT: True
MAX_PEERS: 10 MAX_PEERS: 10
EXPORT_AMBE: False EXPORT_AMBE: False

167
hblink.py Executable file → Normal file
View File

@ -27,6 +27,9 @@ works stand-alone before troubleshooting any applications that use it. It has
sufficient logging to be used standalone as a troubleshooting application. sufficient logging to be used standalone as a troubleshooting application.
''' '''
# Added config option for APRS in the master config section. Will only send packets to APRS-IS if each master is enabled.
# Modified by KF7EEL - 10-15-2020
# Specifig functions from modules we need # Specifig functions from modules we need
from binascii import b2a_hex as ahex from binascii import b2a_hex as ahex
from binascii import a2b_hex as bhex from binascii import a2b_hex as bhex
@ -35,6 +38,9 @@ from hashlib import sha256, sha1
from hmac import new as hmac_new, compare_digest from hmac import new as hmac_new, compare_digest
from time import time from time import time
from collections import deque from collections import deque
import aprslib
import os
# Twisted is pretty important, so I keep it separate # Twisted is pretty important, so I keep it separate
from twisted.internet.protocol import DatagramProtocol, Factory, Protocol from twisted.internet.protocol import DatagramProtocol, Factory, Protocol
@ -66,6 +72,8 @@ __email__ = 'n0mjs@me.com'
# Global variables used whether we are a module or __main__ # Global variables used whether we are a module or __main__
systems = {} systems = {}
open("nom_aprs","w").close
# Timed loop used for reporting HBP status # Timed loop used for reporting HBP status
def config_reports(_config, _factory): def config_reports(_config, _factory):
def reporting_loop(_logger, _server): def reporting_loop(_logger, _server):
@ -80,10 +88,10 @@ def config_reports(_config, _factory):
reporting = task.LoopingCall(reporting_loop, logger, report_server) reporting = task.LoopingCall(reporting_loop, logger, report_server)
reporting.start(_config['REPORTS']['REPORT_INTERVAL']) reporting.start(_config['REPORTS']['REPORT_INTERVAL'])
return report_server return report_server
# Shut ourselves down gracefully by disconnecting from the masters and peers. # Shut ourselves down gracefully by disconnecting from the masters and peers.
def hblink_handler(_signal, _frame): def hblink_handler(_signal, _frame):
for system in systems: for system in systems:
@ -104,6 +112,7 @@ def acl_check(_id, _acl):
# OPENBRIDGE CLASS # OPENBRIDGE CLASS
#************************************************ #************************************************
class OPENBRIDGE(DatagramProtocol): class OPENBRIDGE(DatagramProtocol):
def __init__(self, _name, _config, _report): def __init__(self, _name, _config, _report):
# Define a few shortcuts to make the rest of the class more readable # Define a few shortcuts to make the rest of the class more readable
@ -113,6 +122,8 @@ class OPENBRIDGE(DatagramProtocol):
self._config = self._CONFIG['SYSTEMS'][self._system] self._config = self._CONFIG['SYSTEMS'][self._system]
self._laststrid = deque([], 20) self._laststrid = deque([], 20)
def dereg(self): def dereg(self):
logger.info('(%s) is mode OPENBRIDGE. No De-Registration required, continuing shutdown', self._system) logger.info('(%s) is mode OPENBRIDGE. No De-Registration required, continuing shutdown', self._system)
@ -474,8 +485,19 @@ class HBSYSTEM(DatagramProtocol):
and self._peers[_peer_id]['SOCKADDR'] == _sockaddr: and self._peers[_peer_id]['SOCKADDR'] == _sockaddr:
logger.info('(%s) Peer is closing down: %s (%s)', self._system, self._peers[_peer_id]['CALLSIGN'], int_id(_peer_id)) logger.info('(%s) Peer is closing down: %s (%s)', self._system, self._peers[_peer_id]['CALLSIGN'], int_id(_peer_id))
self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr) self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr)
#if self._CONFIG['APRS']['ENABLED']:
if self._config['APRS_ENABLED'] == True:
fn = 'nom_aprs'
f = open(fn)
output = []
for line in f:
if not str(int_id(_peer_id)) in line:
output.append(line)
f.close()
f = open(fn, 'w')
f.writelines(output)
f.close()
del self._peers[_peer_id] del self._peers[_peer_id]
else: else:
_peer_id = _data[4:8] # Configure Command _peer_id = _data[4:8] # Configure Command
if _peer_id in self._peers \ if _peer_id in self._peers \
@ -502,6 +524,127 @@ class HBSYSTEM(DatagramProtocol):
self.send_peer(_peer_id, b''.join([RPTACK, _peer_id])) self.send_peer(_peer_id, b''.join([RPTACK, _peer_id]))
logger.info('(%s) Peer %s (%s) has sent repeater configuration', self._system, _this_peer['CALLSIGN'], _this_peer['RADIO_ID']) logger.info('(%s) Peer %s (%s) has sent repeater configuration', self._system, _this_peer['CALLSIGN'], _this_peer['RADIO_ID'])
#APRS IMPLEMENTATION
conta = 0
lista_blocco=['ysf', 'xlx', 'nxdn', 'dstar', 'echolink','p25', 'svx']
#if self._CONFIG['SYSTEMS']['APRS_ENABLED']['ENABLED'] and self._CONFIG['APRS']['ENABLED'] and not str(_this_peer['CALLSIGN'].decode('UTF-8')).replace(' ', '').isalpha() :
# Check if master has APRS enabled instead of global.
if self._config['APRS_ENABLED'] and not str(_this_peer['CALLSIGN'].decode('UTF-8')).replace(' ', '').isalpha() :
file = open("nom_aprs","r")
linee = file.readlines()
file.close()
for link in lista_blocco:
if int(str(_this_peer['CALLSIGN'].decode('UTF-8')).replace(' ', '').find(link.upper())) == 0:
conta = conta + 1
if len(linee) > 0:
logging.info('Leggo')
for linea in linee:
dati_l = linea.split(':')
if str(_this_peer['RADIO_ID']) == str(dati_l[1]):
conta = conta + 1
if conta == 0:
file=open("nom_aprs",'a')
if len(str(_this_peer['RADIO_ID'])) > 7:
id_pr=int(str(_this_peer['RADIO_ID'])[-2:])
callsign_u=str(_this_peer['CALLSIGN'].decode('UTF-8'))+"-"+str(id_pr)
file.write(callsign_u.replace(' ', '')+ ":"+ str(_this_peer['RADIO_ID']) +":"+ str(_this_peer['RX_FREQ'].decode('UTF-8')) + ":" + str(_this_peer['TX_FREQ'].decode('UTF-8'))+ ":" + str(_this_peer['LATITUDE'].decode('UTF-8')) + ":" + str(_this_peer['LONGITUDE'].decode('UTF-8')) + "\n")
file.close()
else:
file.write(str(_this_peer['CALLSIGN'].decode('UTF-8')).replace(' ', '')+ ":"+ str(_this_peer['RADIO_ID']) +":"+ str(_this_peer['RX_FREQ'].decode('UTF-8')) + ":" + str(_this_peer['TX_FREQ'].decode('UTF-8'))+ ":" + str(_this_peer['LATITUDE'].decode('UTF-8')) + ":" + str(_this_peer['LONGITUDE'].decode('UTF-8')) + "\n")
file.close()
else:
if conta == 0:
file=open("nom_aprs",'a')
if len(str(_this_peer['RADIO_ID'])) > 7:
id_pr=int(str(_this_peer['RADIO_ID'])[-2:])
callsign_u=str(_this_peer['CALLSIGN'].decode('UTF-8'))+"-"+str(id_pr)
file.write(callsign_u.replace(' ', '')+ ":"+ str(_this_peer['RADIO_ID']) +":"+ str(_this_peer['RX_FREQ'].decode('UTF-8')) + ":" + str(_this_peer['TX_FREQ'].decode('UTF-8'))+ ":" + str(_this_peer['LATITUDE'].decode('UTF-8')) + ":" + str(_this_peer['LONGITUDE'].decode('UTF-8')) + "\n")
file.close()
else:
file.write(str(_this_peer['CALLSIGN'].decode('UTF-8')).replace(' ', '')+ ":"+ str(_this_peer['RADIO_ID']) +":"+ str(_this_peer['RX_FREQ'].decode('UTF-8')) + ":" + str(_this_peer['TX_FREQ'].decode('UTF-8'))+ ":" + str(_this_peer['LATITUDE'].decode('UTF-8')) + ":" + str(_this_peer['LONGITUDE'].decode('UTF-8')) + "\n")
file.close()
def sendAprs():
AIS = aprslib.IS(str(self._CONFIG['APRS']['CALLSIGN']), passwd=aprslib.passcode(str(self._CONFIG['APRS']['CALLSIGN'])), host=str(self._CONFIG['APRS']['SERVER']), port=14580)
AIS.connect()
f = open('nom_aprs', 'r')
lines = f.readlines()
if lines:
for line in lines:
if line != ' ':
lat_verso = ''
lon_verso = ''
dati = line.split(":")
d1_c = int(float(dati[4]))
d2_c = int(float(dati[5]))
if d1_c < 0:
d1 = abs(d1_c)
dm1=abs(float(dati[4])) - d1
dm1_s= float(dm1) * 60
dm1_u="{:.4f}".format(dm1_s)
if d1 < 10 and d1 > -10:
lat_utile='0'+str(d1)+str(dm1_u)
else:
lat_utile = str(d1)+str(dm1_u)
lat_verso = 'S'
else:
d1 = int(float(dati[4]))
dm1=float(dati[4]) - d1
dm1_s= float(dm1) * 60
dm1_u="{:.4f}".format(dm1_s)
if d1 < 10 and d1 > -10:
lat_utile='0'+str(d1)+str(dm1_u)
else:
lat_utile = str(d1)+str(dm1_u)
lat_verso = 'N'
if d2_c < 0:
d2=abs(d2_c)
dm2=abs(float(dati[5])) - d2
dm2_s= float(dm2) * 60
dm2_u="{:.3f}".format(dm2_s)
if d2 < 10 and d2 > -10:
lon_utile = '00'+str(d2)+str(dm2_u)
elif d2 < 100:
lon_utile = '0'+str(d2)+str(dm2_u)
else:
lon_utile = str(d2)+str(dm2_s)
lon_verso = 'W'
else:
d2=int(float(dati[5]))
dm2=float(dati[5]) - d2
dm2_s= float(dm2) * 60
dm2_u="{:.3f}".format(dm2_s)
if d2 < 10 and d2 > -10:
lon_utile = '00'+str(d2)+str(dm2_u)
elif d2 < 100:
lon_utile = '0'+str(d2)+str(dm2_u)
else:
lon_utile = str(d2)+str(dm2_u)
lon_verso = 'E'
rx_utile = dati[2][0:3]+'.'+dati[2][3:]
tx_utile = dati[3][0:3]+'.'+dati[3][3:]
# Modified latitude and longitude strings from original, kept getting "uncompressed location" error from aprs.fi. Also will add Color Code to status, not yet working.
AIS.sendall(str(dati[0])+">APRS,TCPIP*,qAC,"+str(self._CONFIG['APRS']['CALLSIGN'])+":!"+str(lat_utile)[:7]+lat_verso+"/"+str(lon_utile)[:8]+lon_verso+"r"+str(self._CONFIG['APRS']['MESSAGE'])+' RX: '+str(rx_utile)[:8]+' TX: '+str(tx_utile)[:8]) # + ' CC: ' + str(_this_peer['COLORCODE']).decode('UTF-8'))
#logging.info(str(dati[0])+">APRS,TCPIP*,qAC,"+str(self._CONFIG['APRS']['CALLSIGN'])+":!"+str(lat_utile)[:7]+lat_verso+"/"+str(lon_utile)[:8]+lon_verso+"r"+str(self._CONFIG['APRS']['MESSAGE'])+' RX: '+str(rx_utile)[:8]+' TX: '+str(tx_utile)[:8] + ' CC:' + str(_this_peer['COLORCODE']))
logging.info('APRS INVIATO / APRS Packet Sent')
if conta == 0:
if self._CONFIG['APRS']['REPORT_INTERVAL'] > 3:
l=task.LoopingCall(sendAprs)
l.start(float(int(self._CONFIG['APRS']['REPORT_INTERVAL']*60)))
else:
l=task.LoopingCall(sendAprs)
l.start(5*60)
logger.info('Report Time APRS to short')
else: else:
self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr) self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr)
logger.warning('(%s) Peer info from Radio ID that has not logged in: %s', self._system, int_id(_peer_id)) logger.warning('(%s) Peer info from Radio ID that has not logged in: %s', self._system, int_id(_peer_id))
@ -519,18 +662,6 @@ class HBSYSTEM(DatagramProtocol):
self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr) self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr)
logger.warning('(%s) Ping from Radio ID that is not logged in: %s', self._system, int_id(_peer_id)) logger.warning('(%s) Ping from Radio ID that is not logged in: %s', self._system, int_id(_peer_id))
elif _command == RPTO:
_peer_id = _data[4:8]
if _peer_id in self._peers \
and self._peers[_peer_id]['CONNECTION'] == 'YES' \
and self._peers[_peer_id]['SOCKADDR'] == _sockaddr:
logger.info('(%s) Peer %s (%s) has send options: %s', self._system, self._peers[_peer_id]['CALLSIGN'], int_id(_peer_id), _data[8:])
self.transport.write(b''.join([RPTACK, _peer_id]), _sockaddr)
elif _command == DMRA:
_peer_id = _data[4:8]
logger.info('(%s) Recieved DMR Talker Alias from peer %s, subscriber %s', self._system, self._peers[_peer_id]['CALLSIGN'], int_id(_rf_src))
else: else:
logger.error('(%s) Unrecognized command. Raw HBP PDU: %s', self._system, ahex(_data)) logger.error('(%s) Unrecognized command. Raw HBP PDU: %s', self._system, ahex(_data))
@ -660,7 +791,6 @@ class HBSYSTEM(DatagramProtocol):
self._stats['CONNECTION'] = 'YES' self._stats['CONNECTION'] = 'YES'
self._stats['CONNECTED'] = time() self._stats['CONNECTED'] = time()
logger.info('(%s) Connection to Master Completed', self._system) logger.info('(%s) Connection to Master Completed', self._system)
# If we are an XLX, send the XLX module request here. # If we are an XLX, send the XLX module request here.
if self._config['MODE'] == 'XLXPEER': if self._config['MODE'] == 'XLXPEER':
self.send_xlxmaster(self._config['RADIO_ID'], int(4000), self._config['MASTER_SOCKADDR']) self.send_xlxmaster(self._config['RADIO_ID'], int(4000), self._config['MASTER_SOCKADDR'])
@ -781,6 +911,8 @@ if __name__ == '__main__':
import sys import sys
import os import os
import signal import signal
import aprslib
import threading
# Change the current directory to the location of the application # Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
@ -802,7 +934,7 @@ if __name__ == '__main__':
if cli_args.LOG_LEVEL: if cli_args.LOG_LEVEL:
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
logger = log.config_logging(CONFIG['LOGGER']) logger = log.config_logging(CONFIG['LOGGER'])
logger.info('\n\nCopyright (c) 2013, 2014, 2015, 2016, 2018, 2019, 2020\n\tThe Regents of the K0USY Group. All rights reserved.\n') logger.info('APRS IMPLEMENTATION BY IU7IGU email: iu7igu@yahoo.com \n APRS per master config by KF7EEL - KF7EEL@qsl.net \n\nCopyright (c) 2013, 2014, 2015, 2016, 2018, 2019, 2020\n\tThe Regents of the K0USY Group. All rights reserved.')
logger.debug('(GLOBAL) Logging system started, anything from here on gets logged') logger.debug('(GLOBAL) Logging system started, anything from here on gets logged')
# Set up the signal handler # Set up the signal handler
@ -833,7 +965,10 @@ if __name__ == '__main__':
systems[system] = OPENBRIDGE(system, CONFIG, report_server) systems[system] = OPENBRIDGE(system, CONFIG, report_server)
else: else:
systems[system] = HBSYSTEM(system, CONFIG, report_server) systems[system] = HBSYSTEM(system, CONFIG, report_server)
logger.info(CONFIG['SYSTEMS'][system]['APRS_ENABLED'])
reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP'])
logger.debug('(GLOBAL) %s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) logger.debug('(GLOBAL) %s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system])
reactor.run() reactor.run()

View File

@ -3,3 +3,4 @@ bitarray>=0.8.1
Twisted>=16.3.0 Twisted>=16.3.0
dmr_utils3>=0.1.19 dmr_utils3>=0.1.19
configparser>=3.0.0 configparser>=3.0.0
aprslib>=0.6.42