From 4dc58f7e012f59358809455806a40eba6a8e294a Mon Sep 17 00:00:00 2001 From: KF7EEL Date: Mon, 23 Nov 2020 10:19:58 -0800 Subject: [PATCH 1/3] move gps to separate config --- config.py | 13 +++ gps_data-SAMPLE.cfg | 235 ++++++++++++++++++++++++++++++++++++++++++++ gps_data.py | 38 ++++--- 3 files changed, 266 insertions(+), 20 deletions(-) create mode 100644 gps_data-SAMPLE.cfg diff --git a/config.py b/config.py index 8da7f10..c1cba86 100644 --- a/config.py +++ b/config.py @@ -108,6 +108,7 @@ def build_config(_config_file): CONFIG = {} CONFIG['GLOBAL'] = {} CONFIG['APRS'] = {} + CONFIG['GPS_DATA'] = {} CONFIG['REPORTS'] = {} CONFIG['LOGGER'] = {} CONFIG['ALIASES'] = {} @@ -136,6 +137,18 @@ def build_config(_config_file): 'MESSAGE': config.get(section, 'MESSAGE') }) + elif section == 'GPS_DATA': + CONFIG['GPS_DATA'].update({ + 'DATA_DMR_ID': config.get(section, 'DATA_DMR_ID'), + 'USER_APRS_SSID': config.get(section, 'USER_APRS_SSID'), + 'CALL_TYPE': config.get(section, 'CALL_TYPE'), + 'USER_APRS_COMMENT': config.get(section, 'USER_APRS_COMMENT'), + 'APRS_LOGIN_CALL': config.get(section, 'APRS_LOGIN_CALL'), + 'APRS_LOGIN_PASSCODE': config.get(section, 'APRS_LOGIN_PASSCODE'), + 'APRS_SERVER': config.get(section, 'APRS_SERVER'), + 'APRS_PORT': config.get(section, 'APRS_PORT'), + }) + elif section == 'REPORTS': CONFIG['REPORTS'].update({ 'REPORT': config.getboolean(section, 'REPORT'), diff --git a/gps_data-SAMPLE.cfg b/gps_data-SAMPLE.cfg new file mode 100644 index 0000000..40527ea --- /dev/null +++ b/gps_data-SAMPLE.cfg @@ -0,0 +1,235 @@ +# 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/gps_data.log +LOG_HANDLERS: console-timed +LOG_LEVEL: DEBUG +LOG_NAME: HBlink3 GPS/Data + +# 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: 1 + +# GPS/Data Application - by KF7EEL +# Configure the settings for the DMR GPS to APRS position application here. +# +# DATA_DMR_ID - This is the DMR ID that users send DMR GPS data. +# CALL_TYPE - gorup or unit. Group if you want users to send data to a talk group, +# unit if you want users to send data as a private call. +# USER_APRS_SSID - Default APRS SSID assigned to user APRS positions. +# USER_APRS_COMMENT - Default Comment attached to user APRS positions. +# APRS_LOGIN_CALL, PASSCODE, SERVER, and PORT - Login settings for APRS-IS. +[GPS_DATA] +DATA_DMR_ID: 9099 +CALL_TYPE: unit +USER_APRS_SSID: 15 +USER_APRS_COMMENT: HBLink3 GPS Decode - +APRS_LOGIN_CALL: N0CALL +APRS_LOGIN_PASSCODE: 12345 +APRS_SERVER: rotate.aprs2.net +APRS_PORT: 14580 + + +# 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. +# Proper OpenBridge passes all traffic on TS1. +# HBlink can extend OPB to use both slots for unit calls only. +# Setting "BOTH_SLOTS" True ONLY affects unit traffic! +# 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 +##BOTH_SLOTS: True +##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: False +REPEAT: True +MAX_PEERS: 10 +EXPORT_AMBE: False +IP: +PORT: 54000 +PASSPHRASE: password +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 + +[PEER-1] +MODE: PEER +ENABLED: True +LOOSE: True +EXPORT_AMBE: False +IP: +PORT: 54002 +MASTER_IP: 10.10.10.105 +MASTER_PORT: 54070 +PASSPHRASE: passw0rd +CALLSIGN: GPS +RADIO_ID: 9099 +RX_FREQ: 000000000 +TX_FREQ: 000000000 +TX_POWER: 0 +COLORCODE: 1 +SLOTS: 1 +LATITUDE: 47.0000 +LONGITUDE: -120.0000 +HEIGHT: 0 +LOCATION: Somewhere, Cool +DESCRIPTION: GPS to APRS +URL: www.github.com/kf7eel/hblink3 +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 + diff --git a/gps_data.py b/gps_data.py index 3dacbd8..99b7103 100644 --- a/gps_data.py +++ b/gps_data.py @@ -75,21 +75,6 @@ __status__ = 'pre-alpha' # 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 = 9099 - -# Group call or Unit (private) call -call_type = 'unit' -# APRS-IS login information -aprs_callsign = 'N0CALL' -aprs_passcode = 12345 -aprs_server = 'rotate.aprs2.net' -aprs_port = 14580 -user_ssid = '15' -aprs_comment = 'HBLink3 GPS Decoder - ' - - ################################################################################################## # Headers for GPS by model of radio: @@ -178,10 +163,10 @@ class DATA_SYSTEM(HBSYSTEM): sms_hex = str(ba2hx(bitarray(re.sub("\)|\(|bitarray|'", '', packet_assembly)))) #NMEA GPS sentence if '$GPRMC' in final_packet: - logger.info(final_packet + '\n') + #logger.info(final_packet + '\n') nmea_parse = re.sub('A\*.*|.*\$', '', str(final_packet)) loc = pynmea2.parse(nmea_parse, check=False) - logger.info('Latitude: ' + str(loc.lat) + str(loc.lat_dir) + ' Longitude: ' + str(loc.lon) + str(loc.lon_dir) + ' Direction: ' + str(loc.true_course) + ' Speed: ' + str(loc.spd_over_grnd)) + logger.info('Latitude: ' + str(loc.lat) + str(loc.lat_dir) + ' Longitude: ' + str(loc.lon) + str(loc.lon_dir) + ' Direction: ' + str(loc.true_course) + ' Speed: ' + str(loc.spd_over_grnd) + '\n') # 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)) 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(loc.lat[0:7]) + str(loc.lat_dir) + '/' + str(loc.lon[0:8]) + str(loc.lon_dir) + '[/' + aprs_comment + ' DMR ID: ' + str(int_id(_rf_src)) @@ -200,14 +185,14 @@ class DATA_SYSTEM(HBSYSTEM): # End APRS-IS upload # Assume this is an SMS message if '$GPRMC' not in final_packet: - logger.info(final_packet) + #logger.info(final_packet) sms = codecs.decode(bytes.fromhex(''.join(sms_hex[74:-8].split('00'))), 'utf-8') - logger.info('Received SMS from ' + str(get_alias(int_id(_rf_src), subscriber_ids)) + ', DMR ID: ' + str(int_id(_rf_src)) + ': ' + str(sms)) + logger.info('\n' + 'Received SMS from ' + str(get_alias(int_id(_rf_src), subscriber_ids)) + ', DMR ID: ' + str(int_id(_rf_src)) + ': ' + str(sms) + '\n') # Reset the packet assembly to prevent old data from returning. packet_assembly = '' #logger.info(_seq) #logger.info(_dtype_vseq) - logger.info(ahex(bptc_decode(_data)).decode('utf-8', 'ignore')) + #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: @@ -219,6 +204,7 @@ class DATA_SYSTEM(HBSYSTEM): #************************************************ if __name__ == '__main__': + #global aprs_callsign, aprs_passcode, aprs_server, aprs_port, user_ssid, aprs_comment, call_type, data_id import argparse import sys import os @@ -241,6 +227,18 @@ if __name__ == '__main__': # Call the external routine to build the configuration dictionary CONFIG = config.build_config(cli_args.CONFIG_FILE) + data_id = int(CONFIG['GPS_DATA']['DATA_DMR_ID']) + + # Group call or Unit (private) call + call_type = CONFIG['GPS_DATA']['CALL_TYPE'] + # APRS-IS login information + aprs_callsign = CONFIG['GPS_DATA']['APRS_LOGIN_CALL'] + aprs_passcode = int(CONFIG['GPS_DATA']['APRS_LOGIN_PASSCODE']) + aprs_server = CONFIG['GPS_DATA']['APRS_SERVER'] + aprs_port = int(CONFIG['GPS_DATA']['APRS_PORT']) + user_ssid = CONFIG['GPS_DATA']['USER_APRS_SSID'] + aprs_comment = CONFIG['GPS_DATA']['USER_APRS_COMMENT'] + # Start the system logger if cli_args.LOG_LEVEL: CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL From de6386e4f143505eda58dea942914cdb31db8125 Mon Sep 17 00:00:00 2001 From: KF7EEL Date: Mon, 23 Nov 2020 12:53:59 -0800 Subject: [PATCH 2/3] implement basic SMS processing --- gps_data.py | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/gps_data.py b/gps_data.py index 99b7103..e1c1498 100644 --- a/gps_data.py +++ b/gps_data.py @@ -109,11 +109,22 @@ 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) +# Placeholder for future header id +def header_ID(_data): + hex_hdr = str(ahex(bptc_decode(_data))) + return hex_hdr[2:6] # Work in progress, used to determine data format - pass +## pass + +# Process SMS, do something bases on message + +def process_sms(from_id, sms): + if sms == 'ID': + logger.info(str(get_alias(int_id(from_id), subscriber_ids)) + ' - ' + str(int_id(from_id))) + if sms == 'TEST': + logger.info('This is a really cool function.') + else: + pass ########### @@ -134,9 +145,11 @@ class DATA_SYSTEM(HBSYSTEM): pckt_seq = int.from_bytes(_seq, 'big') else: pckt_seq = _seq + # Try to classify header 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 + global btf, hdr_start + hdr_start = str(header_ID(_data)) logger.info('Header from ' + str(get_alias(int_id(_rf_src), subscriber_ids)) + '. DMR ID: ' + str(int_id(_rf_src))) logger.info(ahex(bptc_decode(_data))) logger.info('Blocks to follow: ' + str(ba2num(bptc_decode(_data)[65:72]))) @@ -185,11 +198,21 @@ class DATA_SYSTEM(HBSYSTEM): # End APRS-IS upload # Assume this is an SMS message if '$GPRMC' not in final_packet: - #logger.info(final_packet) - sms = codecs.decode(bytes.fromhex(''.join(sms_hex[74:-8].split('00'))), 'utf-8') - logger.info('\n' + 'Received SMS from ' + str(get_alias(int_id(_rf_src), subscriber_ids)) + ', DMR ID: ' + str(int_id(_rf_src)) + ': ' + str(sms) + '\n') + # Motorola type SMS header + if '024' in hdr_start: + logger.info('\nMotorola type SMS') + sms = codecs.decode(bytes.fromhex(''.join(sms_hex[74:-8].split('00'))), 'utf-8') + logger.info('\n\n' + 'Received SMS from ' + str(get_alias(int_id(_rf_src), subscriber_ids)) + ', DMR ID: ' + str(int_id(_rf_src)) + ': ' + str(sms) + '\n') + process_sms(_rf_src, sms) + else: + logger.info('Unknown tpye SMS') + logger.info(final_packet) + pass + #logger.info(bitarray(re.sub("\)|\(|bitarray|'", '', str(bptc_decode(_data)).tobytes().decode('utf-8', 'ignore')))) + #logger.info('\n\n' + 'Received SMS from ' + str(get_alias(int_id(_rf_src), subscriber_ids)) + ', DMR ID: ' + str(int_id(_rf_src)) + ': ' + str(sms) + '\n') # Reset the packet assembly to prevent old data from returning. - packet_assembly = '' + packet_assembly = '' + hdr_start = '' #logger.info(_seq) #logger.info(_dtype_vseq) #logger.info(ahex(bptc_decode(_data)).decode('utf-8', 'ignore')) From 7764feb2982e24eddbab9f2b0cbfaee7ec8ae4df Mon Sep 17 00:00:00 2001 From: KF7EEL Date: Mon, 23 Nov 2020 13:49:34 -0800 Subject: [PATCH 3/3] ensure lat and lon are float --- gps_data.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gps_data.py b/gps_data.py index e1c1498..09b67f6 100644 --- a/gps_data.py +++ b/gps_data.py @@ -176,7 +176,7 @@ class DATA_SYSTEM(HBSYSTEM): sms_hex = str(ba2hx(bitarray(re.sub("\)|\(|bitarray|'", '', packet_assembly)))) #NMEA GPS sentence if '$GPRMC' in final_packet: - #logger.info(final_packet + '\n') + logger.info(final_packet + '\n') nmea_parse = re.sub('A\*.*|.*\$', '', str(final_packet)) loc = pynmea2.parse(nmea_parse, check=False) logger.info('Latitude: ' + str(loc.lat) + str(loc.lat_dir) + ' Longitude: ' + str(loc.lon) + str(loc.lon_dir) + ' Direction: ' + str(loc.true_course) + ' Speed: ' + str(loc.spd_over_grnd) + '\n') @@ -187,6 +187,9 @@ class DATA_SYSTEM(HBSYSTEM): try: # Try parse of APRS packet. If it fails, it will not upload to APRS-IS aprslib.parse(aprs_loc_packet) + # Float values of lat and lon. Anything that is not a number will cause it to fail. + float(loc.lat) + float(loc.lon) AIS = aprslib.IS(aprs_callsign, passwd=aprs_passcode,host=aprs_server, port=aprs_port) AIS.connect() AIS.sendall(aprs_loc_packet)