From f70fc267076d03296ad6c7e80d8527d0dddd37b7 Mon Sep 17 00:00:00 2001 From: Daniele Marra <39775345+iu7igu@users.noreply.github.com> Date: Fri, 18 Sep 2020 21:03:51 +0200 Subject: [PATCH 01/11] APRS IMPLEMENTATION --- config.py | 10 + hblink-SAMPLE.cfg | 495 ++++++++++++++++++++++++---------------------- hblink.py | 149 +++++++++++++- 3 files changed, 408 insertions(+), 246 deletions(-) diff --git a/config.py b/config.py index f86628c..efec699 100755 --- a/config.py +++ b/config.py @@ -104,6 +104,7 @@ def build_config(_config_file): CONFIG = {} CONFIG['GLOBAL'] = {} + CONFIG['APRS'] = {} CONFIG['REPORTS'] = {} CONFIG['LOGGER'] = {} CONFIG['ALIASES'] = {} @@ -122,6 +123,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({ diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg index a3b616c..3e05f6b 100755 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -1,241 +1,254 @@ -# PROGRAM-WIDE PARAMETERS GO HERE -# PATH - working path for files, leave it alone unless you NEED to change it -# PING_TIME - the interval that peers will ping the master, and re-try registraion -# - how often the Master maintenance loop runs -# MAX_MISSED - how many pings are missed before we give up and re-register -# - number of times the master maintenance loop runs before de-registering a peer -# -# ACLs: -# -# Access Control Lists are a very powerful tool for administering your system. -# But they consume packet processing time. Disable them if you are not using them. -# But be aware that, as of now, the configuration stanzas still need the ACL -# sections configured even if you're not using them. -# -# REGISTRATION ACLS ARE ALWAYS USED, ONLY SUBSCRIBER AND TGID MAY BE DISABLED!!! -# -# The 'action' May be PERMIT|DENY -# Each entry may be a single radio id, or a hypenated range (e.g. 1-2999) -# Format: -# ACL = 'action:id|start-end|,id|start-end,....' -# --for example-- -# SUB_ACL: DENY:1,1000-2000,4500-60000,17 -# -# ACL Types: -# REG_ACL: peer radio IDs for registration (only used on HBP master systems) -# SUB_ACL: subscriber IDs for end-users -# TGID_TS1_ACL: destination talkgroup IDs on Timeslot 1 -# TGID_TS2_ACL: destination talkgroup IDs on Timeslot 2 -# -# ACLs may be repeated for individual systems if needed for granularity -# Global ACLs will be processed BEFORE the system level ACLs -# Packets will be matched against all ACLs, GLOBAL first. If a packet 'passes' -# All elements, processing continues. Packets are discarded at the first -# negative match, or 'reject' from an ACL element. -# -# If you do not wish to use ACLs, set them to 'PERMIT:ALL' -# TGID_TS1_ACL in the global stanza is used for OPENBRIDGE systems, since all -# traffic is passed as TS 1 between OpenBridges -[GLOBAL] -PATH: ./ -PING_TIME: 5 -MAX_MISSED: 3 -USE_ACL: True -REG_ACL: PERMIT:ALL -SUB_ACL: DENY:1 -TGID_TS1_ACL: PERMIT:ALL -TGID_TS2_ACL: PERMIT:ALL - - -# NOT YET WORKING: NETWORK REPORTING CONFIGURATION -# Enabling "REPORT" will configure a socket-based reporting -# system that will send the configuration and other items -# to a another process (local or remote) that may process -# the information for some useful purpose, like a web dashboard. -# -# REPORT - True to enable, False to disable -# REPORT_INTERVAL - Seconds between reports -# REPORT_PORT - TCP port to listen on if "REPORT_NETWORKS" = NETWORK -# REPORT_CLIENTS - comma separated list of IPs you will allow clients -# to connect on. Entering a * will allow all. -# -# ****FOR NOW MUST BE TRUE - USE THE LOOPBACK IF YOU DON'T USE THIS!!!**** -[REPORTS] -REPORT: True -REPORT_INTERVAL: 60 -REPORT_PORT: 4321 -REPORT_CLIENTS: 127.0.0.1 - - -# SYSTEM LOGGER CONFIGURAITON -# This allows the logger to be configured without chaning the individual -# python logger stuff. LOG_FILE should be a complete path/filename for *your* -# system -- use /dev/null for non-file handlers. -# LOG_HANDLERS may be any of the following, please, no spaces in the -# list if you use several: -# null -# console -# console-timed -# file -# file-timed -# syslog -# LOG_LEVEL may be any of the standard syslog logging levels, though -# as of now, DEBUG, INFO, WARNING and CRITICAL are the only ones -# used. -# -[LOGGER] -LOG_FILE: /tmp/hblink.log -LOG_HANDLERS: console-timed -LOG_LEVEL: DEBUG -LOG_NAME: HBlink - -# DOWNLOAD AND IMPORT SUBSCRIBER, PEER and TGID ALIASES -# Ok, not the TGID, there's no master list I know of to download -# This is intended as a facility for other applcations built on top of -# HBlink to use, and will NOT be used in HBlink directly. -# STALE_DAYS is the number of days since the last download before we -# download again. Don't be an ass and change this to less than a few days. -[ALIASES] -TRY_DOWNLOAD: True -PATH: ./ -PEER_FILE: peer_ids.json -SUBSCRIBER_FILE: subscriber_ids.json -TGID_FILE: talkgroup_ids.json -PEER_URL: https://www.radioid.net/static/rptrs.json -SUBSCRIBER_URL: https://www.radioid.net/static/users.json -STALE_DAYS: 7 - -# OPENBRIDGE INSTANCES - DUPLICATE SECTION FOR MULTIPLE CONNECTIONS -# OpenBridge is a protocol originall created by DMR+ for connection between an -# IPSC2 server and Brandmeister. It has been implemented here at the suggestion -# of the Brandmeister team as a way to legitimately connect HBlink to the -# Brandemiester network. -# It is recommended to name the system the ID of the Brandmeister server that -# it connects to, but is not necessary. TARGET_IP and TARGET_PORT are of the -# Brandmeister or IPSC2 server you are connecting to. PASSPHRASE is the password -# that must be agreed upon between you and the operator of the server you are -# connecting to. NETWORK_ID is a number in the format of a DMR Radio ID that -# will be sent to the other server to identify this connection. -# other parameters follow the other system types. -# -# ACLs: -# OpenBridge does not 'register', so registration ACL is meaningless. -# OpenBridge passes all traffic on TS1, so there is only 1 TGID ACL. -# Otherwise ACLs work as described in the global stanza -[OBP-1] -MODE: OPENBRIDGE -ENABLED: True -IP: -PORT: 62035 -NETWORK_ID: 3129100 -PASSPHRASE: password -TARGET_IP: 1.2.3.4 -TARGET_PORT: 62035 -USE_ACL: True -SUB_ACL: DENY:1 -TGID_ACL: PERMIT:ALL - -# MASTER INSTANCES - DUPLICATE SECTION FOR MULTIPLE MASTERS -# 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. -# Repeat - if True, the master repeats traffic to peers, False, it does nothing. -# -# MAX_PEERS -- maximun number of peers that may be connect to this master -# at any given time. This is very handy if you're allowing hotspots to -# connect, or using a limited computer like a Raspberry Pi. -# -# ACLs: -# See comments in the GLOBAL stanza -[MASTER-1] -MODE: MASTER -ENABLED: True -REPEAT: True -MAX_PEERS: 10 -EXPORT_AMBE: False -IP: -PORT: 54000 -PASSPHRASE: s3cr37w0rd -GROUP_HANGTIME: 5 -USE_ACL: True -REG_ACL: DENY:1 -SUB_ACL: DENY:1 -TGID_TS1_ACL: PERMIT:ALL -TGID_TS2_ACL: PERMIT:ALL - -# PEER INSTANCES - DUPLICATE SECTION FOR MULTIPLE PEERS -# There are a LOT of errors in the HB Protocol specifications on this one! -# MOST of these items are just strings and will be properly dealt with by the program -# The TX & RX Frequencies are 9-digit numbers, and are the frequency in Hz. -# Latitude is an 8-digit unsigned floating point number. -# Longitude is a 9-digit signed floating point number. -# Height is in meters -# Setting Loose to True relaxes the validation on packets received from the master. -# This will allow HBlink to connect to a non-compliant system such as XLXD, DMR+ etc. -# -# ACLs: -# See comments in the GLOBAL stanza -[REPEATER-1] -MODE: PEER -ENABLED: True -LOOSE: False -EXPORT_AMBE: False -IP: -PORT: 54001 -MASTER_IP: 172.16.1.1 -MASTER_PORT: 54000 -PASSPHRASE: homebrew -CALLSIGN: W1ABC -RADIO_ID: 312000 -RX_FREQ: 449000000 -TX_FREQ: 444000000 -TX_POWER: 25 -COLORCODE: 1 -SLOTS: 1 -LATITUDE: 38.0000 -LONGITUDE: -095.0000 -HEIGHT: 75 -LOCATION: Anywhere, USA -DESCRIPTION: This is a cool repeater -URL: www.w1abc.org -SOFTWARE_ID: 20170620 -PACKAGE_ID: MMDVM_HBlink -GROUP_HANGTIME: 5 -OPTIONS: -USE_ACL: True -SUB_ACL: DENY:1 -TGID_TS1_ACL: PERMIT:ALL -TGID_TS2_ACL: PERMIT:ALL - -[XLX-1] -MODE: XLXPEER -ENABLED: True -LOOSE: True -EXPORT_AMBE: False -IP: -PORT: 54002 -MASTER_IP: 172.16.1.1 -MASTER_PORT: 62030 -PASSPHRASE: passw0rd -CALLSIGN: W1ABC -RADIO_ID: 312000 -RX_FREQ: 449000000 -TX_FREQ: 444000000 -TX_POWER: 25 -COLORCODE: 1 -SLOTS: 1 -LATITUDE: 38.0000 -LONGITUDE: -095.0000 -HEIGHT: 75 -LOCATION: Anywhere, USA -DESCRIPTION: This is a cool repeater -URL: www.w1abc.org -SOFTWARE_ID: 20170620 -PACKAGE_ID: MMDVM_HBlink -GROUP_HANGTIME: 5 -XLXMODULE: 4004 -USE_ACL: True -SUB_ACL: DENY:1 -TGID_TS1_ACL: PERMIT:ALL -TGID_TS2_ACL: PERMIT:ALL +# PROGRAM-WIDE PARAMETERS GO HERE +# PATH - working path for files, leave it alone unless you NEED to change it +# PING_TIME - the interval that peers will ping the master, and re-try registraion +# - how often the Master maintenance loop runs +# MAX_MISSED - how many pings are missed before we give up and re-register +# - number of times the master maintenance loop runs before de-registering a peer +# +# ACLs: +# +# Access Control Lists are a very powerful tool for administering your system. +# But they consume packet processing time. Disable them if you are not using them. +# But be aware that, as of now, the configuration stanzas still need the ACL +# sections configured even if you're not using them. +# +# REGISTRATION ACLS ARE ALWAYS USED, ONLY SUBSCRIBER AND TGID MAY BE DISABLED!!! +# +# The 'action' May be PERMIT|DENY +# Each entry may be a single radio id, or a hypenated range (e.g. 1-2999) +# Format: +# ACL = 'action:id|start-end|,id|start-end,....' +# --for example-- +# SUB_ACL: DENY:1,1000-2000,4500-60000,17 +# +# ACL Types: +# REG_ACL: peer radio IDs for registration (only used on HBP master systems) +# SUB_ACL: subscriber IDs for end-users +# TGID_TS1_ACL: destination talkgroup IDs on Timeslot 1 +# TGID_TS2_ACL: destination talkgroup IDs on Timeslot 2 +# +# ACLs may be repeated for individual systems if needed for granularity +# Global ACLs will be processed BEFORE the system level ACLs +# Packets will be matched against all ACLs, GLOBAL first. If a packet 'passes' +# All elements, processing continues. Packets are discarded at the first +# negative match, or 'reject' from an ACL element. +# +# If you do not wish to use ACLs, set them to 'PERMIT:ALL' +# TGID_TS1_ACL in the global stanza is used for OPENBRIDGE systems, since all +# traffic is passed as TS 1 between OpenBridges +[GLOBAL] +PATH: ./ +PING_TIME: 5 +MAX_MISSED: 3 +USE_ACL: True +REG_ACL: PERMIT:ALL +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: True +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 +# system that will send the configuration and other items +# to a another process (local or remote) that may process +# the information for some useful purpose, like a web dashboard. +# +# REPORT - True to enable, False to disable +# REPORT_INTERVAL - Seconds between reports +# REPORT_PORT - TCP port to listen on if "REPORT_NETWORKS" = NETWORK +# REPORT_CLIENTS - comma separated list of IPs you will allow clients +# to connect on. Entering a * will allow all. +# +# ****FOR NOW MUST BE TRUE - USE THE LOOPBACK IF YOU DON'T USE THIS!!!**** +[REPORTS] +REPORT: True +REPORT_INTERVAL: 60 +REPORT_PORT: 4321 +REPORT_CLIENTS: 127.0.0.1 + + +# SYSTEM LOGGER CONFIGURAITON +# This allows the logger to be configured without chaning the individual +# python logger stuff. LOG_FILE should be a complete path/filename for *your* +# system -- use /dev/null for non-file handlers. +# LOG_HANDLERS may be any of the following, please, no spaces in the +# list if you use several: +# null +# console +# console-timed +# file +# file-timed +# syslog +# LOG_LEVEL may be any of the standard syslog logging levels, though +# as of now, DEBUG, INFO, WARNING and CRITICAL are the only ones +# used. +# +[LOGGER] +LOG_FILE: /tmp/hblink.log +LOG_HANDLERS: console-timed +LOG_LEVEL: DEBUG +LOG_NAME: HBlink + +# DOWNLOAD AND IMPORT SUBSCRIBER, PEER and TGID ALIASES +# Ok, not the TGID, there's no master list I know of to download +# This is intended as a facility for other applcations built on top of +# HBlink to use, and will NOT be used in HBlink directly. +# STALE_DAYS is the number of days since the last download before we +# download again. Don't be an ass and change this to less than a few days. +[ALIASES] +TRY_DOWNLOAD: True +PATH: ./ +PEER_FILE: peer_ids.json +SUBSCRIBER_FILE: subscriber_ids.json +TGID_FILE: talkgroup_ids.json +PEER_URL: https://www.radioid.net/static/rptrs.json +SUBSCRIBER_URL: https://www.radioid.net/static/users.json +STALE_DAYS: 7 + +# OPENBRIDGE INSTANCES - DUPLICATE SECTION FOR MULTIPLE CONNECTIONS +# OpenBridge is a protocol originall created by DMR+ for connection between an +# IPSC2 server and Brandmeister. It has been implemented here at the suggestion +# of the Brandmeister team as a way to legitimately connect HBlink to the +# Brandemiester network. +# It is recommended to name the system the ID of the Brandmeister server that +# it connects to, but is not necessary. TARGET_IP and TARGET_PORT are of the +# Brandmeister or IPSC2 server you are connecting to. PASSPHRASE is the password +# that must be agreed upon between you and the operator of the server you are +# connecting to. NETWORK_ID is a number in the format of a DMR Radio ID that +# will be sent to the other server to identify this connection. +# other parameters follow the other system types. +# +# ACLs: +# OpenBridge does not 'register', so registration ACL is meaningless. +# OpenBridge passes all traffic on TS1, so there is only 1 TGID ACL. +# Otherwise ACLs work as described in the global stanza +[OBP-1] +MODE: OPENBRIDGE +ENABLED: True +IP: +PORT: 62035 +NETWORK_ID: 3129100 +PASSPHRASE: password +TARGET_IP: 1.2.3.4 +TARGET_PORT: 62035 +USE_ACL: True +SUB_ACL: DENY:1 +TGID_ACL: PERMIT:ALL + +# MASTER INSTANCES - DUPLICATE SECTION FOR MULTIPLE MASTERS +# 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. +# Repeat - if True, the master repeats traffic to peers, False, it does nothing. +# +# MAX_PEERS -- maximun number of peers that may be connect to this master +# at any given time. This is very handy if you're allowing hotspots to +# connect, or using a limited computer like a Raspberry Pi. +# +# ACLs: +# See comments in the GLOBAL stanza +[MASTER-1] +MODE: MASTER +ENABLED: True +REPEAT: True +MAX_PEERS: 10 +EXPORT_AMBE: False +IP: +PORT: 54000 +PASSPHRASE: s3cr37w0rd +GROUP_HANGTIME: 5 +USE_ACL: True +REG_ACL: DENY:1 +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL + +# PEER INSTANCES - DUPLICATE SECTION FOR MULTIPLE PEERS +# There are a LOT of errors in the HB Protocol specifications on this one! +# MOST of these items are just strings and will be properly dealt with by the program +# The TX & RX Frequencies are 9-digit numbers, and are the frequency in Hz. +# Latitude is an 8-digit unsigned floating point number. +# Longitude is a 9-digit signed floating point number. +# Height is in meters +# Setting Loose to True relaxes the validation on packets received from the master. +# This will allow HBlink to connect to a non-compliant system such as XLXD, DMR+ etc. +# +# ACLs: +# See comments in the GLOBAL stanza +[REPEATER-1] +MODE: PEER +ENABLED: True +LOOSE: False +EXPORT_AMBE: False +IP: +PORT: 54001 +MASTER_IP: 172.16.1.1 +MASTER_PORT: 54000 +PASSPHRASE: homebrew +CALLSIGN: W1ABC +RADIO_ID: 312000 +RX_FREQ: 449000000 +TX_FREQ: 444000000 +TX_POWER: 25 +COLORCODE: 1 +SLOTS: 1 +LATITUDE: 38.0000 +LONGITUDE: -095.0000 +HEIGHT: 75 +LOCATION: Anywhere, USA +DESCRIPTION: This is a cool repeater +URL: www.w1abc.org +SOFTWARE_ID: 20170620 +PACKAGE_ID: MMDVM_HBlink +GROUP_HANGTIME: 5 +OPTIONS: +USE_ACL: True +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL + +[XLX-1] +MODE: XLXPEER +ENABLED: True +LOOSE: True +EXPORT_AMBE: False +IP: +PORT: 54002 +MASTER_IP: 172.16.1.1 +MASTER_PORT: 62030 +PASSPHRASE: passw0rd +CALLSIGN: W1ABC +RADIO_ID: 312000 +RX_FREQ: 449000000 +TX_FREQ: 444000000 +TX_POWER: 25 +COLORCODE: 1 +SLOTS: 1 +LATITUDE: 38.0000 +LONGITUDE: -095.0000 +HEIGHT: 75 +LOCATION: Anywhere, USA +DESCRIPTION: This is a cool repeater +URL: www.w1abc.org +SOFTWARE_ID: 20170620 +PACKAGE_ID: MMDVM_HBlink +GROUP_HANGTIME: 5 +XLXMODULE: 4004 +USE_ACL: True +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL diff --git a/hblink.py b/hblink.py index 83b67ad..909d169 100755 --- a/hblink.py +++ b/hblink.py @@ -35,6 +35,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 +69,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 +85,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 +109,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 +118,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 +482,18 @@ 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']: + 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 +520,124 @@ 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['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:] + + + AIS.sendall(str(dati[0])+">APRS,TCPIP*,qAC,"+str(self._CONFIG['APRS']['CALLSIGN'])+":!"+str(lat_utile)[:-2]+lat_verso+"/"+str(lon_utile)[:-1]+lon_verso+"r"+str(self._CONFIG['APRS']['MESSAGE'])+' RX: '+str(rx_utile)+' TX: '+str(tx_utile)) + logging.info('APRS INVIATO') + + 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)) @@ -768,6 +904,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]))) @@ -789,12 +927,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\n\tThe Regents of the K0USY Group. All rights reserved.\n') + logger.info('APRS IMPLEMENTATION BY IU7IGU email: iu7igu@yahoo.com \n\nCopyright (c) 2013, 2014, 2015, 2016, 2018, 2019\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() @@ -824,3 +962,4 @@ if __name__ == '__main__': logger.debug('(GLOBAL) %s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) reactor.run() + From 5a1414d5d601c414e2f7fcaaadc953d5005587f1 Mon Sep 17 00:00:00 2001 From: Daniele Marra <39775345+iu7igu@users.noreply.github.com> Date: Fri, 18 Sep 2020 21:05:07 +0200 Subject: [PATCH 02/11] APRSlib --- requirements.txt | 1 + 1 file changed, 1 insertion(+) 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 From 091f8bd7d1028f00092aa1881c649ef3165c7528 Mon Sep 17 00:00:00 2001 From: Daniele Marra <39775345+iu7igu@users.noreply.github.com> Date: Fri, 18 Sep 2020 21:05:42 +0200 Subject: [PATCH 03/11] update repo --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 842c902..a58dcf8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ RUN apt update && \ rm -rf /var/lib/apt/lists/* && \ cd /opt && \ rm -rf /usr/src/dmr_utils3 && \ - git clone https://github.com/n0mjs710/hblink3 + git clone https://github.com/iu7igu/hblink3-aprs ENV AAA BBBB RUN cd /opt/hblink3/ && \ sed -i s/.*python.*//g requirements.txt && \ From 1ba5db5f3cd1a95d6a46ae743b0725b843e98f56 Mon Sep 17 00:00:00 2001 From: Waldek Date: Tue, 13 Oct 2020 21:07:31 +0200 Subject: [PATCH 04/11] changed default APRS enabled from True to False --- hblink-SAMPLE.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg index 3e05f6b..3d0040e 100755 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -54,7 +54,7 @@ TGID_TS2_ACL: PERMIT:ALL # MESSAGE: This message will print on APRS description together RX and TX Frequency [APRS] -ENABLED: True +ENABLED: False REPORT_INTERVAL: 5 CALLSIGN:HB1LNK-11 SERVER:euro.aprs2.net From 3912657bb688891cbc086247ea338571d1361243 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 15 Oct 2020 15:10:04 -0700 Subject: [PATCH 05/11] moved APRS to a per master config --- config.py | 5 +++++ hblink-SAMPLE.cfg | 1 + hblink.py | 19 ++++++++++++++----- 3 files changed, 20 insertions(+), 5 deletions(-) mode change 100755 => 100644 config.py mode change 100755 => 100644 hblink.py diff --git a/config.py b/config.py old mode 100755 new mode 100644 index efec699..281ed23 --- 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 @@ -259,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')), @@ -331,3 +335,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 index 3d0040e..dd673d9 100755 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -164,6 +164,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 909d169..9e1edf1 --- 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 @@ -482,7 +485,8 @@ 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']: + if self._config['APRS_ENABLED'] == True: fn = 'nom_aprs' f = open(fn) output = [] @@ -523,7 +527,9 @@ class HBSYSTEM(DatagramProtocol): #APRS IMPLEMENTATION conta = 0 lista_blocco=['ysf', 'xlx', 'nxdn', 'dstar', 'echolink','p25', 'svx'] - if self._CONFIG['APRS']['ENABLED'] and not str(_this_peer['CALLSIGN'].decode('UTF-8')).replace(' ', '').isalpha() : + #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() @@ -624,9 +630,10 @@ class HBSYSTEM(DatagramProtocol): rx_utile = dati[2][0:3]+'.'+dati[2][3:] tx_utile = dati[3][0:3]+'.'+dati[3][3:] - - AIS.sendall(str(dati[0])+">APRS,TCPIP*,qAC,"+str(self._CONFIG['APRS']['CALLSIGN'])+":!"+str(lat_utile)[:-2]+lat_verso+"/"+str(lon_utile)[:-1]+lon_verso+"r"+str(self._CONFIG['APRS']['MESSAGE'])+' RX: '+str(rx_utile)+' TX: '+str(tx_utile)) - logging.info('APRS INVIATO') + # 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: @@ -958,8 +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() + From 431639e17abbb5c931bb576a647721d773191b14 Mon Sep 17 00:00:00 2001 From: KF7EEL Date: Thu, 15 Oct 2020 15:42:31 -0700 Subject: [PATCH 06/11] Update README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 1735783..1efae52 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. + +This is my "flavor" of HBLink3 that I use in production. + --- ### FOR SUPPORT, DISCUSSION, GETTING INVOLVED ### From 9a57531e71031a137081aed2b96258c6b2b6b98f Mon Sep 17 00:00:00 2001 From: KF7EEL Date: Thu, 15 Oct 2020 15:53:41 -0700 Subject: [PATCH 07/11] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1efae52..a9acfe7 100755 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ --- -# In this fork, APRS beaconing of peers is set on a per master configuration. +# In this fork, APRS beaconing of peers is set on a per master configuration. Also, private-call-dev has been merged with master. -This is my "flavor" of HBLink3 that I use in production. +This is my "flavor" of HBLink3 that I use in production. --- ### FOR SUPPORT, DISCUSSION, GETTING INVOLVED ### From 8271f1bd33e5df8cd4f3ea443f8f3bdb11676298 Mon Sep 17 00:00:00 2001 From: KF7EEL <57960556+kf7eel@users.noreply.github.com> Date: Sat, 21 Nov 2020 00:11:54 -0800 Subject: [PATCH 08/11] Add GPS decoding for APRS Added support to decode GPS packets and upload APRS location reports. Work with Anytone D878, probably D868 and D578. May even work with some BTech radios. --- gps_data.py | 265 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 gps_data.py diff --git a/gps_data.py b/gps_data.py new file mode 100644 index 0000000..d0153fa --- /dev/null +++ b/gps_data.py @@ -0,0 +1,265 @@ +#!/usr/bin/env python +# +############################################################################### +# Copyright (C) 2020 Cortney T. Buffington, N0MJS +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +############################################################################### + +''' +This is a GPS and Data application. It decodes and reassambles DMR GPS packets and +uploads them th APRS-IS. +''' + +# Python modules we need +import sys +from bitarray import bitarray +from time import time +from importlib import import_module +from types import ModuleType + +# Twisted is pretty important, so I keep it separate +from twisted.internet.protocol import Factory, Protocol +from twisted.protocols.basic import NetstringReceiver +from twisted.internet import reactor, task + +# Things we import from the main hblink module +from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, config_reports, mk_aliases, acl_check +from dmr_utils3.utils import bytes_3, int_id, get_alias +from dmr_utils3 import decode, bptc, const +import config +import log +import const + +# The module needs logging logging, but handlers, etc. are controlled by the parent +import logging +logger = logging.getLogger(__name__) + +# Other modules we need for data and GPS +from bitarray import bitarray +from binascii import b2a_hex as ahex +import re +##from binascii import a2b_hex as bhex +import aprslib +import datetime +from bitarray.util import ba2int as ba2num + +# Does anybody read this stuff? There's a PEP somewhere that says I should do this. +__author__ = 'Cortney T. Buffington, N0MJS; Eric Craw, KF7EEL' +__copyright__ = 'Copyright (c) 2020 Cortney T. Buffington' +__credits__ = 'Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT' +__license__ = 'GNU GPLv3' +__maintainer__ = 'Eric Craw, KF7EEL' +__email__ = 'kf7eel@qsl.net' +__status__ = 'pre-alpha' + +# Known to work with: AT-D878 + +############################# Configuration Variables - Change these ############################# +# DMR ID for GPS and data +data_id = 310999 + +# Group call or Unit (private) call +data_type = 'unit' +# APRS-IS login information +aprs_callsign = 'CALLSIGN' +aprs_passcode = ***** +aprs_server = 'rotate.aprs2.net' +aprs_port = 14580 +user_ssid = '15' +aprs_comment = 'HBLink GPS decode - ' + + +################################################################################################## + +# Headers for GPS by model of radio: +# AT-D878 - Compressed UDP +# MD-380 - Unified Data Transport + + +# From dmr_utils3, modified to decode entire packet. Works for 1/2 rate coded data. +def decode_full(_data): + binlc = bitarray(endian='big') + binlc.extend([_data[136],_data[121],_data[106],_data[91], _data[76], _data[61], _data[46], _data[31]]) + binlc.extend([_data[152],_data[137],_data[122],_data[107],_data[92], _data[77], _data[62], _data[47], _data[32], _data[17], _data[2] ]) + binlc.extend([_data[123],_data[108],_data[93], _data[78], _data[63], _data[48], _data[33], _data[18], _data[3], _data[184],_data[169]]) + binlc.extend([_data[94], _data[79], _data[64], _data[49], _data[34], _data[19], _data[4], _data[185],_data[170],_data[155],_data[140]]) + binlc.extend([_data[65], _data[50], _data[35], _data[20], _data[5], _data[186],_data[171],_data[156],_data[141],_data[126],_data[111]]) + binlc.extend([_data[36], _data[21], _data[6], _data[187],_data[172],_data[157],_data[142],_data[127],_data[112],_data[97], _data[82] ]) + binlc.extend([_data[7], _data[188],_data[173],_data[158],_data[143],_data[128],_data[113],_data[98], _data[83]]) + #This is the rest of the Full LC data -- the RS1293 FEC that we don't need + binlc.extend([_data[68],_data[53],_data[174],_data[159],_data[144],_data[129],_data[114],_data[99],_data[84],_data[69],_data[54],_data[39]]) + binlc.extend([_data[24],_data[145],_data[130],_data[115],_data[100],_data[85],_data[70],_data[55],_data[40],_data[25],_data[10],_data[191]]) + return binlc + + +n_packet_assembly = 0 + +packet_assembly = '' + +#Convert DMR packet to binary from MMDVM packet and remove Slot Type and EMB Sync stuff to allow for BPTC 196,96 decoding +def bptc_decode(_data): + binary_packet = bitarray(decode.to_bits(_data[20:])) + del binary_packet[98:166] + return decode_full(binary_packet) + +def header_info(_data): + bptc_decode(_data) + # Work in progress, used to determine data format + pass + +########### + + +class DATA_SYSTEM(HBSYSTEM): +## global n_packet_assembly, packet_assembly + + def __init__(self, _name, _config, _report): + HBSYSTEM.__init__(self, _name, _config, _report) + + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): + # Capture data headers + global n_packet_assembly + #logger.info(_dtype_vseq) + if int_id(_dst_id) == data_id: + if _call_type == data_type: + if _dtype_vseq == 6: + global btf + logger.info('Header from ' + str(int_id(_rf_src))) + logger.info(ahex(bptc_decode(_data))) + logger.info('Blocks to follow: ' + str(ba2num(bptc_decode(_data)[65:72]))) + btf = ba2num(bptc_decode(_data)[65:72]) + # Data blocks at 1/2 rate, see https://github.com/g4klx/MMDVM/blob/master/DMRDefines.h for data types. _dtype_seq defined here also + if _dtype_vseq == 7: + btf = btf - 1 + logger.info('Block #: ' + str(btf)) + #logger.info(_seq) + global packet_assembly + logger.info('Data block from ' + str(int_id(_rf_src))) + logger.info(ahex(bptc_decode(_data))) + if _seq == 0: + n_packet_assembly = 0 + packet_assembly = '' + + if btf < btf + 1: + n_packet_assembly = n_packet_assembly + 1 + packet_assembly = packet_assembly + str(bptc_decode(_data)) #str((decode_full_lc(b_packet)).strip('bitarray(')) + # Use block 0 as trigger. $GPRMC must also be in string to indicate NMEA. + # This triggers the APRS upload + if btf == 0: #_seq == 12: + final_packet = bitarray(re.sub("\)|\(|bitarray|'", '', packet_assembly)).tobytes().decode('utf-8', 'ignore') + if '$GPRMC' in final_packet: + logger.info(final_packet + '\n') + logger.info('Latitude: ' + re.sub(',', '', final_packet[29:40]) + ' Longitude: ' + re.sub(',', '', final_packet[41:53]) + ' Direction: ' + re.sub(',', '', final_packet[58:62]) + ' Speed: ' + re.sub(',', '', final_packet[53:58])) + # Begin APRS format and upload + aprs_loc_packet = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + str(user_ssid) + '>APRS,TCPIP*:/' + str(datetime.datetime.utcnow().strftime("%H%M%Sh")) + str(final_packet[29:36]) + str(final_packet[39]) + '/' + str(re.sub(',', '', final_packet[41:49])) + str(final_packet[52]) + '[/' + aprs_comment + ' DMR ID: ' + str(int_id(_rf_src)) + logger.info(aprs_loc_packet) + try: + # Try parse of APRS packet. If it fails, it will not upload to APRS-IS + aprslib.parse(aprs_loc_packet) + AIS = aprslib.IS(aprs_callsign, passwd=aprs_passcode,host=aprs_server, port=aprs_port) + AIS.connect() + AIS.sendall(aprs_loc_packet) + AIS.close() + except: + logger.info('Failed to parse packet. Packet may be deformed. Not uploaded.') + # Get callsign based on DMR ID + #logger.info(get_alias(int_id(_rf_src), subscriber_ids)) + # End APRS-IS upload + else: + logger.info(final_packet) + packet_assembly = '' + logger.info(_seq) + #logger.info(_dtype_vseq) + logger.info(ahex(bptc_decode(_data)).decode('utf-8', 'ignore')) + #logger.info(bitarray(re.sub("\)|\(|bitarray|'", '', str(bptc_decode(_data)).tobytes().decode('utf-8', 'ignore')))) + + else: + pass + + +#************************************************ +# MAIN PROGRAM LOOP STARTS HERE +#************************************************ + +if __name__ == '__main__': + import argparse + import sys + import os + import signal + from dmr_utils3.utils import try_download, mk_id_dict + + # Change the current directory to the location of the application + os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) + + # CLI argument parser - handles picking up the config file from the command line, and sending a "help" message + parser = argparse.ArgumentParser() + parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (usually hblink.cfg)') + parser.add_argument('-l', '--logging', action='store', dest='LOG_LEVEL', help='Override config file logging level.') + cli_args = parser.parse_args() + + # Ensure we have a path for the config file, if one wasn't specified, then use the default (top of file) + if not cli_args.CONFIG_FILE: + cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/hblink.cfg' + + # Call the external routine to build the configuration dictionary + CONFIG = config.build_config(cli_args.CONFIG_FILE) + + # Start the system logger + 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\n\tThe Regents of the K0USY Group. All rights reserved.\n GPS and Data decoding by Eric, KF7EEL') + logger.debug('Logging system started, anything from here on gets logged') + + # Set up the signal handler + def sig_handler(_signal, _frame): + logger.info('SHUTDOWN: >>>GPS and Data Decoder<<< IS TERMINATING WITH SIGNAL %s', str(_signal)) + hblink_handler(_signal, _frame) + logger.info('SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR') + reactor.stop() + + # Set signal handers so that we can gracefully exit if need be + for sig in [signal.SIGTERM, signal.SIGINT]: + signal.signal(sig, sig_handler) + + # Create the name-number mapping dictionaries + peer_ids, subscriber_ids, talkgroup_ids = mk_aliases(CONFIG) + + + # INITIALIZE THE REPORTING LOOP + if CONFIG['REPORTS']['REPORT']: + report_server = config_reports(CONFIG, reportFactory) + else: + report_server = None + logger.info('(REPORT) TCP Socket reporting not configured') + + # HBlink instance creation + logger.info('HBlink \'gps_data.py\' -- SYSTEM STARTING...') + for system in CONFIG['SYSTEMS']: + if CONFIG['SYSTEMS'][system]['ENABLED']: + if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': + systems[system] = OPENBRIDGE(system, CONFIG, report_server) + else: + systems[system] = DATA_SYSTEM(system, CONFIG, report_server) + + reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) + logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) + + reactor.run() + + +# John 3:16 - For God so loved the world, that he gave his only Son, +# that whoever believes in him should not perish but have eternal life. From 7ac8c40ac0046c03835b098f55b75cf9b90c9279 Mon Sep 17 00:00:00 2001 From: KF7EEL <57960556+kf7eel@users.noreply.github.com> Date: Sat, 21 Nov 2020 00:13:21 -0800 Subject: [PATCH 09/11] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a9acfe7..6f1ea50 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ --- -# In this fork, APRS beaconing of peers is set on a per master configuration. Also, private-call-dev has been merged with master. +# 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. From 9f79b8c69ca5009d66528c1dc488f5af8b0288c9 Mon Sep 17 00:00:00 2001 From: KF7EEL <57960556+kf7eel@users.noreply.github.com> Date: Sat, 21 Nov 2020 22:22:49 -0800 Subject: [PATCH 10/11] Improve ability to utilize group calls Improve ability to utilize group calls acting as a master or peer. --- gps_data.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/gps_data.py b/gps_data.py index d0153fa..9453718 100644 --- a/gps_data.py +++ b/gps_data.py @@ -67,19 +67,22 @@ __status__ = 'pre-alpha' # Known to work with: AT-D878 +# Must have the following at line 1054 in bridge.py to forward group vcsbk, also there is a typo there: +# self.group_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) + ############################# Configuration Variables - Change these ############################# # DMR ID for GPS and data -data_id = 310999 +data_id = 9099 # Group call or Unit (private) call -data_type = 'unit' +call_type = 'unit' # APRS-IS login information -aprs_callsign = 'CALLSIGN' -aprs_passcode = ***** +aprs_callsign = 'N0CALL' +aprs_passcode = 12345 aprs_server = 'rotate.aprs2.net' aprs_port = 14580 user_ssid = '15' -aprs_comment = 'HBLink GPS decode - ' +aprs_comment = 'HBLink3 GPS decode - ' ################################################################################################## @@ -134,8 +137,13 @@ class DATA_SYSTEM(HBSYSTEM): global n_packet_assembly #logger.info(_dtype_vseq) if int_id(_dst_id) == data_id: - if _call_type == data_type: - if _dtype_vseq == 6: + #logger.info(type(_seq)) + if type(_seq) is bytes: + pckt_seq = int.from_bytes(_seq, 'big') + else: + pckt_seq = _seq + if _call_type == call_type or (_call_type == 'vcsbk' and pckt_seq > 3): #int.from_bytes(_seq, 'big') > 3 ): + if _dtype_vseq == 6 or _dtype_vseq == 'group': global btf logger.info('Header from ' + str(int_id(_rf_src))) logger.info(ahex(bptc_decode(_data))) From 64d8c16e2e0f62e0b9b69c1379e62d15f5dc6444 Mon Sep 17 00:00:00 2001 From: KF7EEL Date: Sat, 21 Nov 2020 22:38:46 -0800 Subject: [PATCH 11/11] fix typo and set CSBK handling in bridge.py --- bridge.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bridge.py b/bridge.py index fc40c19..9d1f129 100755 --- a/bridge.py +++ b/bridge.py @@ -1050,8 +1050,9 @@ class routerHBP(HBSYSTEM): logger.error('(%s) *UNIT CALL NOT FORWARDED* UNIT calling is disabled for this system (INGRESS)', self._system) else: self.unit_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) - elif _call_type == 'vscsbk': - logger.debug('CSBK recieved, but HBlink does not process them currently') + elif _call_type == 'vcsbk': + logger.debug('CSBK recieved, but HBlink does not process them currently. By default, CSBK packet will be forwarded to the talkgroup.') + self.group_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) else: logger.error('Unknown call type recieved -- not processed')