diff --git a/README.md b/README.md index 1735783..6f1ea50 100755 --- a/README.md +++ b/README.md @@ -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 ### diff --git a/bridge.py b/bridge.py old mode 100755 new mode 100644 diff --git a/config.py b/config.py old mode 100755 new mode 100644 index 7600642..8da7f10 --- a/config.py +++ b/config.py @@ -26,6 +26,9 @@ updated if the items in the main configuraiton file (usually hblink.cfg) change. ''' +# Added config option for APRS in the master config section +# Modified by KF7EEL - 10-15-2020 + import configparser import sys import const @@ -104,6 +107,7 @@ def build_config(_config_file): CONFIG = {} CONFIG['GLOBAL'] = {} + CONFIG['APRS'] = {} CONFIG['REPORTS'] = {} CONFIG['LOGGER'] = {} CONFIG['ALIASES'] = {} @@ -122,6 +126,15 @@ def build_config(_config_file): 'TG1_ACL': config.get(section, 'TGID_TS1_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': CONFIG['REPORTS'].update({ @@ -182,7 +195,7 @@ def build_config(_config_file): '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'), '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'), 'SUB_ACL': config.get(section, 'SUB_ACL'), 'TG1_ACL': config.get(section, 'TGID_TS1_ACL'), @@ -249,6 +262,7 @@ def build_config(_config_file): CONFIG['SYSTEMS'].update({section: { 'MODE': config.get(section, 'MODE'), 'ENABLED': config.getboolean(section, 'ENABLED'), + 'APRS_ENABLED': config.getboolean(section, 'APRS_ENABLED'), 'REPEAT': config.getboolean(section, 'REPEAT'), 'MAX_PEERS': config.getint(section, 'MAX_PEERS'), 'IP': gethostbyname(config.get(section, 'IP')), @@ -322,3 +336,4 @@ if __name__ == '__main__': return not _acl[0] print(acl_check(b'\x00\x01\x37', CONFIG['GLOBAL']['TG1_ACL'])) + diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg old mode 100755 new mode 100644 index c2e789d..8bb4fe8 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -46,6 +46,19 @@ SUB_ACL: DENY:1 TGID_TS1_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 # Enabling "REPORT" will configure a socket-based reporting @@ -154,6 +167,7 @@ TGID_ACL: PERMIT:ALL [MASTER-1] MODE: MASTER ENABLED: True +APRS_ENABLED: False REPEAT: True MAX_PEERS: 10 EXPORT_AMBE: False diff --git a/hblink.py b/hblink.py old mode 100755 new mode 100644 index 7896396..04941b6 --- a/hblink.py +++ b/hblink.py @@ -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. ''' +# 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 from binascii import b2a_hex as ahex 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 time import time from collections import deque +import aprslib +import os + # Twisted is pretty important, so I keep it separate 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__ systems = {} +open("nom_aprs","w").close + # Timed loop used for reporting HBP status def config_reports(_config, _factory): def reporting_loop(_logger, _server): @@ -80,10 +88,10 @@ def config_reports(_config, _factory): reporting = task.LoopingCall(reporting_loop, logger, report_server) reporting.start(_config['REPORTS']['REPORT_INTERVAL']) - return report_server + # Shut ourselves down gracefully by disconnecting from the masters and peers. def hblink_handler(_signal, _frame): for system in systems: @@ -104,6 +112,7 @@ def acl_check(_id, _acl): # OPENBRIDGE CLASS #************************************************ + class OPENBRIDGE(DatagramProtocol): def __init__(self, _name, _config, _report): # Define a few shortcuts to make the rest of the class more readable @@ -112,7 +121,9 @@ class OPENBRIDGE(DatagramProtocol): self._report = _report self._config = self._CONFIG['SYSTEMS'][self._system] self._laststrid = deque([], 20) - + + + def dereg(self): 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: 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) + #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] - else: _peer_id = _data[4:8] # Configure Command if _peer_id in self._peers \ @@ -502,6 +524,127 @@ class HBSYSTEM(DatagramProtocol): 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']) + #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: 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)) @@ -519,18 +662,6 @@ class HBSYSTEM(DatagramProtocol): 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)) - 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: 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['CONNECTED'] = time() logger.info('(%s) Connection to Master Completed', self._system) - # If we are an XLX, send the XLX module request here. if self._config['MODE'] == 'XLXPEER': self.send_xlxmaster(self._config['RADIO_ID'], int(4000), self._config['MASTER_SOCKADDR']) @@ -781,6 +911,8 @@ if __name__ == '__main__': import sys import os import signal + import aprslib + import threading # Change the current directory to the location of the application os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) @@ -802,12 +934,12 @@ if __name__ == '__main__': if cli_args.LOG_LEVEL: CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL 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') # Set up the signal handler def sig_handler(_signal, _frame): - logger.info('(GLOBAL) SHUTDOWN: HBLINK IS TERMINATING WITH SIGNAL %s', str(_signal)) + logger.info('(GLOBAL) SHUTDOWN: HBLINK IS TERMINATING WITH SIGNAL %s', str(_signal)) hblink_handler(_signal, _frame) logger.info('(GLOBAL) SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR') reactor.stop() @@ -833,7 +965,10 @@ if __name__ == '__main__': systems[system] = OPENBRIDGE(system, CONFIG, report_server) else: 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']) logger.debug('(GLOBAL) %s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) reactor.run() + + diff --git a/requirements.txt b/requirements.txt index 3d17f35..5d078c2 100755 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ bitarray>=0.8.1 Twisted>=16.3.0 dmr_utils3>=0.1.19 configparser>=3.0.0 +aprslib>=0.6.42