Compare commits

...

56 Commits
termux ... gps

Author SHA1 Message Date
KF7EEL be1c306af8
Update README.md 2021-09-05 19:24:34 -07:00
KF7EEL 421a0ddc49
Update README.md 2021-04-29 11:55:38 -07:00
KF7EEL e97e5cff32 fix dashboard logging bug 2021-03-11 17:54:38 -08:00
KF7EEL 7f46a19749 improve PIN write to user_settings 2021-03-11 08:40:55 -08:00
KF7EEL e6ace9e72d fix missing code from bridge_gps_data.py 2021-03-11 08:02:24 -08:00
KF7EEL 1fb22d01c6 Merge branch 'gps' of https://github.com/kf7eel/hblink3 into gps 2021-03-11 07:52:43 -08:00
KF7EEL 4e0c87dac9 fix config.py, add editing of user settings to dashboard, limit number of stored locations per user 2021-03-11 07:52:06 -08:00
KF7EEL ef850596d1
cap positions on map at 15 for station view 2021-03-05 08:15:42 -08:00
KF7EEL 247168d567
fix typo 2021-03-05 07:27:25 -08:00
KF7EEL 6a6fdf367a
correct condition 2021-03-05 07:20:07 -08:00
KF7EEL b860dc75ba
pass on blank addrersse messages 2021-03-05 07:15:58 -08:00
KF7EEL 6cc6f7fc81
fix exception 2021-03-04 17:15:37 -08:00
KF7EEL 873bbb6ce7
fix bug again 2021-03-04 16:55:17 -08:00
KF7EEL 2e5dec5cac fix bugs 2021-03-04 16:31:54 -08:00
KF7EEL 239c0730c0 fix incorrect config 2021-03-04 15:17:51 -08:00
KF7EEL fcff610fd3 fix bugs, add user settings to config 2021-03-04 15:16:03 -08:00
KF7EEL 6a1476af51
Update gps_data-SAMPLE.cfg 2021-03-04 13:57:10 -08:00
KF7EEL 295dfbda1f rewrite dahboard config, update APRS receive 2021-03-04 12:36:45 -08:00
KF7EEL e61f90fcfd initial untested receive script 2021-03-03 16:42:01 -08:00
KF7EEL 599f549ab9 add dashboard APRS message hook 2021-03-03 16:36:25 -08:00
KF7EEL 65ab256b7f fix dashboard SOS bug 2021-03-02 14:04:42 -08:00
KF7EEL 104cab1c29 fix typo 2021-03-02 13:46:12 -08:00
KF7EEL eded346f91 fix typo 2021-03-02 13:41:40 -08:00
KF7EEL a91eb65273 make mailbox file persistent 2021-03-02 13:40:03 -08:00
KF7EEL 5b1e250d57 fix exception in map view 2021-03-02 12:56:24 -08:00
KF7EEL d332604f26 add comment to map plots 2021-03-02 12:41:03 -08:00
KF7EEL 888d8c19b7 fix typos 2021-03-01 20:24:37 -08:00
KF7EEL cb47556205 switch to UNIX time 2021-03-01 20:11:32 -08:00
KF7EEL f8cdf8fead minor improvement, remove user_settings.html 2021-03-01 18:16:49 -08:00
KF7EEL d0e90531c6 fix mailbox parsing 2021-03-01 16:50:55 -08:00
KF7EEL dccf561e3d add generic template 2021-03-01 16:41:36 -08:00
KF7EEL 751af20357 update help 2021-03-01 16:39:48 -08:00
KF7EEL 12dc612c2e add mailbox feature 2021-03-01 15:58:09 -08:00
KF7EEL dfedf9eade add generic template to dashboard 2021-03-01 14:18:51 -08:00
KF7EEL ee7c7eccf1 add user setting query, improve dashboard 2021-03-01 13:52:22 -08:00
KF7EEL f4010939dc increase number of BB entries in RSS 2021-02-28 20:01:29 -08:00
KF7EEL 9b19a09eac limit map size on track screen 2021-02-28 19:48:42 -08:00
KF7EEL 89176e922c add option for map theme 2021-02-28 17:27:08 -08:00
KF7EEL 7febe66a76 add notice function as an alternative to emergency 2021-02-28 17:01:32 -08:00
KF7EEL ce36850621 use internal map rather than aprs.fi 2021-02-28 16:17:58 -08:00
KF7EEL 3368683a1f update emergency function 2021-02-28 14:37:23 -08:00
KF7EEL d84e937c1f add emergency feature 2021-02-28 13:47:02 -08:00
KF7EEL ec82ce8b24 add bulletin board rss, correct spelling of bulletin 2021-02-28 12:25:19 -08:00
KF7EEL 40a3030694 update map look and feel 2021-02-28 10:51:39 -08:00
KF7EEL f68c5d0691 improve map 2021-02-27 15:44:14 -08:00
KF7EEL bf9b45d44f fix map template 2021-02-27 15:11:09 -08:00
KF7EEL fb9226e04f add tracking to map 2021-02-27 14:57:48 -08:00
KF7EEL b00ba9ea62 improve map plotting 2021-02-27 06:55:48 -08:00
KF7EEL 9b14982abe improve map and dashboard 2021-02-26 23:30:49 -08:00
KF7EEL 61fd13514b increase positions logged 2021-02-26 21:51:42 -08:00
KF7EEL 77309d5ca6 Merge branch 'gps' of https://github.com/kf7eel/hblink3 into gps 2021-02-26 21:17:33 -08:00
KF7EEL db77a165b4 add map to dashboard 2021-02-26 21:17:06 -08:00
KF7EEL 83274856a3
Update README.md 2021-02-26 18:50:02 -08:00
KF7EEL 72ed9f2d2b
remove aprs_upload calls
Accidentally added aprs calls from aprs_features branch
2021-02-26 13:45:06 -08:00
KF7EEL 1b904e9af7 improve regexp for APRS messages 2021-02-26 13:40:44 -08:00
KF7EEL db068d859e add exception logging 2021-02-26 10:49:54 -08:00
16 changed files with 1337 additions and 174 deletions

View File

@ -1,3 +1,5 @@
### This project has morphed into HBNet. It is still under heavy development and not ready for production. You can see it [here](https://github.com/kf7eel/hblink3/tree/hbnet). The example/development server can be found at [https://demo.hbnet.xyz](https://demo.hbnet.xyz).
# GPS/Data Application
This repository contains everything needed to decode DMR GPS packets and SMS for HBLink3. This application can act as a master or peer and receive data as a group call or private call. It is designed to work in a multi system/user network.
@ -15,8 +17,10 @@ Files modified from original master branch of HBLink3:
#### Optional Modules
* Flask - Required for dashboard
* smtplib - Required for sending email. If pip fails to install module, it may already be installed as most Linux distrobutions have this module by default.
* smtplib - Required for sending email. If pip fails to install module, it may already be installed as most Linux distributions have this module by default.
* traceback - If pip fails to install module, it may already be installed as most Linux distrobutions have this module by default.
* slixmpp - Required for upcoming XMPP gateway.
* folium - Required for mapping on dashboard.
This should work for DMR radios that send location data as a UTF-8 NMEA sentence. I am hopping to add support for more radios in the future.
@ -115,6 +119,14 @@ The web dashboard is completely optional. Python module flask is required for th
**At this time, only sending of messages from DMR SMS to APRS-IS is supported.** I find this feature very pointless because it will only go one way, but someone else may find it important. **Messages from sent from APRS-IS to DMR SMS will not work.** I have not written the code for this yet. It will likley be a long time before this is a possibility.
## APRS TOCALL
The project was granted a [tocall](http://www.aprs.org/aprs11/tocalls.txt) of **APHBLx** by Bob Bruniga, WB4APR. This will identify that your APRS position came from HBLink. The x on the end can be any letter/number. Here are the current designations of APHBLx:
* **APHBL3** - HBlink3 D-APRS gateway
* **APHBLD** - DMRlink D-APRS gateway (the IPSC version of the project)
* **_APHBLS_** - Planned, but not in use: HBLink3 via KISS TNC
## Configuration
See hblink_SAMPLE.cfg, rules_SAMPLE.py, and gps_data_SAMPLE.cfg for examples.

View File

@ -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: 15
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
@ -135,6 +148,14 @@ IGATE_BEACON_ICON = /I
IGATE_LATITUDE = 0000.00N
IGATE_LONGITUDE = 00000.00W
# User settings file, MUST configure using absolute path.
USER_SETTINGS_FILE: /path/to/user_settings.txt
# The APRS filter below is used for the message received script. See http://www.aprs-is.net/javAPRSFilter.aspx for details
# about APRS filters.
APRS_RECEIVE_LOGIN_CALL: N0CALL-1
APRS_FILTER: r/47/-120/500 t/m
# The email gateway settingns below are OPTIONAL. They are NOT REQUIRED if you don't want
# to enable the email gateway. Leave as is to disable.
EMAIL_SENDER:
@ -142,6 +163,51 @@ EMAIL_PASSWORD:
SMTP_SERVER: smtp.gmail.com
SMTP_PORT: 465
# The options below are required for operation of the dashboard and will cause errors in gps_data.py
# if configured wrong. Leave them as default unless you know what you are doing.
# If you do change, you must use absolute paths.
LOCATION_FILE: /tmp/gps_data_user_loc.txt
BULLETIN_BOARD_FILE: /tmp/gps_data_user_bb.txt
MAILBOX_FILE: /tmp/gps_data_user_mailbox.txt
EMERGENCY_SOS_FILE: /tmp/gps_data_user_sos.txt
# The following options are used for the dashboard. The dashboard is optional.
# Title of the Dashboard
DASHBOARD_TITLE: HBLink3 D-APRS Dashboard
# Logo used on dashboard page
LOGO: https://raw.githubusercontent.com/kf7eel/hblink3/gps/HBlink.png
# Port to run server
DASH_PORT: 8092
# IP to run server on
DASH_HOST: 127.0.0.1
#Description of dashboard to show on main page
DESCRIPTION: Welcome to the dashboard.
# Gateway contact info displayed on about page.
CONTACT_NAME: your name
CONTACT_CALL: N0CALL
CONTACT_EMAIL: email@example.org
CONTACT_WEBSITE: https://hbl.ink
# Time format for display
TIME_FORMAT: %%H:%%M:%%S - %%m/%%d/%%y
# Center dashboard map over these coordinates
MAP_CENTER_LAT: 47.00
MAP_CENTER_LON: -120.00
ZOOM_LEVEL: 7
# List and preview of some map themes at http://leaflet-extras.github.io/leaflet-providers/preview/
# The following are options for map themes and just work, you should use one of these: “OpenStreetMap”, “Stamen” (Terrain, Toner, and Watercolor),
MAP_THEME: Stamen Toner
# RSS feed link, shows in the link section of each RSS item.
RSS_LINK: http://localhost:8092
# 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
@ -191,6 +257,9 @@ TGID_ACL: PERMIT:ALL
[MASTER-1]
MODE: MASTER
ENABLED: True
# The APRS setting below has to do with the beaconing of PEER conections, and not GPS positions
# from radios. This setting will not affect gps_data.py
APRS: False
REPEAT: True
MAX_PEERS: 10
EXPORT_AMBE: False

View File

@ -2,6 +2,8 @@
#
###############################################################################
# Copyright (C) 2016-2019 Cortney T. Buffington, N0MJS <n0mjs@me.com>
# GPS/Data - Copyright (C) 2020 Eric Craw, KF7EEL <kf7eel@qsl.net>
# Annotated modifications Copyright (C) 2021 Xavier FRS2013
#
# 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
@ -55,6 +57,7 @@ import pickle
# The module needs logging, but handlers, etc. are controlled by the parent
import logging
logger = logging.getLogger(__name__)
import traceback
# Import UNIT time from rules.py
from rules import UNIT_TIME, STATIC_UNIT
@ -108,7 +111,7 @@ __email__ = 'n0mjs@me.com'
# AT-D878 - Compressed UDP
# MD-380 - Unified Data Transport
hdr_type = ''
btf = ''
btf = -1
ssid = ''
# From dmr_utils3, modified to decode entire packet. Works for 1/2 rate coded data.
@ -157,30 +160,80 @@ def aprs_send(packet):
AIS.close()
logger.info('Packet sent to APRS-IS.')
def dashboard_loc_write(call, lat, lon, time):
#try:
def dashboard_loc_write(call, lat, lon, time, comment):
dash_entries = ast.literal_eval(os.popen('cat /tmp/gps_data_user_loc.txt').read())
# except:
# dash_entries = []
dash_entries.insert(0, {'call': call, 'lat': lat, 'lon': lon, 'time':time})
with open("/tmp/gps_data_user_loc.txt", 'w') as user_loc_file:
user_loc_file.write(str(dash_entries[:15]))
dash_entries.insert(0, {'call': call, 'lat': lat, 'lon': lon, 'time':time, 'comment':comment})
# Clear old entries
list_index = 0
call_count = 0
new_dash_entries = []
for i in dash_entries:
if i['call'] == call:
if call_count >= 25:
pass
else:
new_dash_entries.append(i)
call_count = call_count + 1
if call != i['call']:
new_dash_entries.append(i)
pass
list_index = list_index + 1
with open(loc_file, 'w') as user_loc_file:
user_loc_file.write(str(new_dash_entries[:500]))
user_loc_file.close()
logger.info('User location saved for dashboard')
#logger.info(dash_entries)
def dashboard_bb_write(call, dmr_id, time, bulletin):
#try:
dash_bb = ast.literal_eval(os.popen('cat /tmp/gps_data_user_bb.txt').read())
dash_bb = ast.literal_eval(os.popen('cat ' + bb_file).read())
# except:
# dash_entries = []
dash_bb.insert(0, {'call': call, 'dmr_id': dmr_id, 'time': time, 'bulliten':bulletin})
with open("/tmp/gps_data_user_bb.txt", 'w') as user_bb_file:
user_bb_file.write(str(dash_bb[:10]))
dash_bb.insert(0, {'call': call, 'dmr_id': dmr_id, 'time': time, 'bulletin':bulletin})
with open(bb_file, 'w') as user_bb_file:
user_bb_file.write(str(dash_bb[:20]))
user_bb_file.close()
logger.info('User bulletin entry saved.')
#logger.info(dash_bb)
def mailbox_write(call, dmr_id, time, message, recipient):
#try:
mail_file = ast.literal_eval(os.popen('cat ' + the_mailbox_file).read())
mail_file.insert(0, {'call': call, 'dmr_id': dmr_id, 'time': time, 'message':message, 'recipient': recipient})
with open(the_mailbox_file, 'w') as mailbox_file:
mailbox_file.write(str(mail_file[:100]))
mailbox_file.close()
logger.info('User mail saved.')
def mailbox_delete(dmr_id):
mail_file = ast.literal_eval(os.popen('cat ' + the_mailbox_file).read())
call = str(get_alias((dmr_id), subscriber_ids))
new_data = []
for message in mail_file:
if message['recipient'] != call:
new_data.append(message)
with open(the_mailbox_file, 'w') as mailbox_file:
mailbox_file.write(str(new_data[:100]))
mailbox_file.close()
logger.info('Mailbox updated. Delete occurred.')
def sos_write(dmr_id, time, message):
user_settings = ast.literal_eval(os.popen('cat ' + user_settings_file).read())
try:
if user_settings[dmr_id][1]['ssid'] == '':
sos_call = user_settings[dmr_id][0]['call'] + '-' + user_ssid
else:
sos_call = user_settings[dmr_id][0]['call'] + '-' + user_settings[dmr_id][1]['ssid']
except:
sos_call = str(get_alias((dmr_id), subscriber_ids))
sos_info = {'call': sos_call, 'dmr_id': dmr_id, 'time': time, 'message':message}
with open(emergency_sos_file, 'w') as sos_file:
sos_file.write(str(sos_info))
sos_file.close()
logger.info('Saved SOS.')
# Send email via SMTP function
def send_email(to_email, email_subject, email_message):
global smtp_server
@ -205,34 +258,39 @@ def decdeg2dms(dd):
def user_setting_write(dmr_id, setting, value):
## try:
# Open file and load as dict for modification
with open("./user_settings.txt", 'r') as f:
with open(user_settings_file, 'r') as f:
## if f.read() == '{}':
## user_dict = {}
user_dict = ast.literal_eval(f.read())
logger.info('Current settings: ' + str(user_dict))
if dmr_id not in user_dict:
user_dict[dmr_id] = [{'call': str(get_alias((dmr_id), subscriber_ids))}, {'ssid': ''}, {'icon': ''}, {'comment': ''}]
if setting.upper() == 'ICON':
user_dict[dmr_id][2]['icon'] = value
if setting.upper() == 'SSID':
user_dict[dmr_id][1]['ssid'] = value
if setting.upper() == 'COM':
user_comment = user_dict[dmr_id][3]['comment'] = value[0:35]
if setting.upper() == 'APRS':
user_dict[dmr_id] = [{'call': str(get_alias((dmr_id), subscriber_ids))}, {'ssid': ''}, {'icon': ''}, {'comment': ''}]
if setting.upper() == 'PIN':
try:
if user_dict[dmr_id]:
user_dict[dmr_id][4]['pin'] = value
if not user_dict[dmr_id]:
user_dict[dmr_id] = [{'call': str(get_alias((dmr_id), subscriber_ids))}, {'ssid': ''}, {'icon': ''}, {'comment': ''}, {'pin': pin}]
except:
user_dict[dmr_id].append({'pin': value})
f.close()
logger.info('Loaded user settings. Preparing to write...')
# Write modified dict to file
with open("./user_settings.txt", 'w') as user_dict_file:
with open(user_settings_file, 'w') as user_dict_file:
user_dict_file.write(str(user_dict))
user_dict_file.close()
logger.info('User setting saved')
f.close()
packet_assembly = ''
## except:
## logger.info('No data file found, creating one.')
## #Path('./user_settings.txt').mkdir(parents=True, exist_ok=True)
## Path('./user_settings.txt').touch()
# Process SMS, do something bases on message
def process_sms(_rf_src, sms):
@ -246,19 +304,37 @@ def process_sms(_rf_src, sms):
user_setting_write(int_id(_rf_src), re.sub(' .*|@','',sms), re.sub('@SSID| ','',sms))
elif '@COM' in sms:
user_setting_write(int_id(_rf_src), re.sub(' .*|@','',sms), re.sub('@COM |@COM','',sms))
elif '@PIN' in sms:
user_setting_write(int_id(_rf_src), re.sub(' .*|@','',sms), int(re.sub('@PIN |@PIN','',sms)))
# Write blank entry to cause APRS receive to look for packets for this station.
elif '@APRS' in sms:
user_setting_write(int_id(_rf_src), 'APRS', '')
elif '@BB' in sms:
dashboard_bb_write(get_alias(int_id(_rf_src), subscriber_ids), int_id(_rf_src), strftime('%H:%M:%S - %m/%d/%y'), re.sub('@BB|@BB ','',sms))
dashboard_bb_write(get_alias(int_id(_rf_src), subscriber_ids), int_id(_rf_src), time(), re.sub('@BB|@BB ','',sms))
elif '@' and ' E-' in sms:
email_message = re.sub('.*@|.* E-', '', sms)
to_email = re.sub(' E-.*', '', sms)
email_message = str(re.sub('.*@|.* E-', '', sms))
to_email = str(re.sub(' E-.*', '', sms))
email_subject = 'New message from ' + str(get_alias(int_id(_rf_src), subscriber_ids))
logger.info('Email to: ' + to_email)
logger.info('Message: ' + email_message)
try:
send_email(to_email, email_subject, email_message)
logger.info('Email sent.')
except:
except Exception as error_exception:
logger.info('Failed to send email.')
logger.info(error_exception)
logger.info(str(traceback.extract_tb(error_exception.__traceback__)))
elif '@SOS' in sms or '@NOTICE' in sms:
sos_write(int_id(_rf_src), time(), sms)
elif '@REM SOS' == sms:
os.remove(emergency_sos_file)
logger.info('Removing SOS')
elif '@' and 'M-' in sms:
message = re.sub('^@|.* M-|','',sms)
recipient = re.sub('@| M-.*','',sms)
mailbox_write(get_alias(int_id(_rf_src), subscriber_ids), int_id(_rf_src), time(), message, str(recipient).upper())
elif '@REM MAIL' == sms:
mailbox_delete(_rf_src)
elif '@MH' in sms:
grid_square = re.sub('@MH ', '', sms)
if len(grid_square) < 6:
@ -282,7 +358,7 @@ def process_sms(_rf_src, sms):
logger.info('Latitude: ' + str(aprs_lat))
logger.info('Longitude: ' + str(aprs_lon))
# 14FRS2013 simplified and moved settings retrieval
user_settings = ast.literal_eval(os.popen('cat ./user_settings.txt').read())
user_settings = ast.literal_eval(os.popen('cat ' + user_settings_file).read())
if int_id(_rf_src) not in user_settings:
ssid = str(user_ssid)
icon_table = '/'
@ -303,7 +379,7 @@ def process_sms(_rf_src, sms):
ssid = user_settings[int_id(_rf_src)][1]['ssid']
if user_settings[int_id(_rf_src)][3]['comment'] != '':
comment = user_settings[int_id(_rf_src)][3]['comment']
aprs_loc_packet = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid + '>APHBL3,TCPIP*:/' + str(datetime.datetime.utcnow().strftime("%H%M%Sh")) + str(aprs_lat) + icon_table + str(aprs_lon) + icon_icon + '/' + str(comment)
aprs_loc_packet = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid + '>APHBL3,TCPIP*:@' + str(datetime.datetime.utcnow().strftime("%H%M%Sh")) + str(aprs_lat) + icon_table + str(aprs_lon) + icon_icon + '/' + str(comment)
logger.info(aprs_loc_packet)
logger.info('User comment: ' + comment)
logger.info('User SSID: ' + ssid)
@ -311,19 +387,21 @@ def process_sms(_rf_src, sms):
try:
aprslib.parse(aprs_loc_packet)
aprs_send(aprs_loc_packet)
dashboard_loc_write(str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid, aprs_lat, aprs_lon, strftime('%H:%M:%S - %m/%d/%y'))
dashboard_loc_write(str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid, aprs_lat, aprs_lon, time(), comment)
#logger.info('Sent manual position to APRS')
except:
except Exception as error_exception:
logger.info('Exception. Not uploaded')
logger.info(error_exception)
logger.info(str(traceback.extract_tb(error_exception.__traceback__)))
packet_assembly = ''
elif 'A-' in sms and '@' in sms:
#Example SMS text: @ARMDS A-This is a test.
aprs_dest = re.sub('@| A-.*','',sms)
aprs_msg = re.sub('@.* A-|','',sms)
aprs_msg = re.sub('^@|.* A-|','',sms)
logger.info('APRS message to ' + aprs_dest.upper() + '. Message: ' + aprs_msg)
user_settings = ast.literal_eval(os.popen('cat ./user_settings.txt').read())
user_settings = ast.literal_eval(os.popen('cat ' + user_settings_file).read())
if int_id(_rf_src) in user_settings and user_settings[int_id(_rf_src)][1]['ssid'] != '':
ssid = user_settings[int_id(_rf_src)][1]['ssid']
else:
@ -334,15 +412,19 @@ def process_sms(_rf_src, sms):
aprslib.parse(aprs_msg_pkt)
aprs_send(aprs_msg_pkt)
#logger.info('Packet sent.')
except:
except Exception as error_exception:
logger.info('Error uploading MSG packet.')
logger.info(error_exception)
logger.info(str(traceback.extract_tb(error_exception.__traceback__)))
try:
if sms in cmd_list:
logger.info('Executing command/script.')
os.popen(cmd_list[sms]).read()
packet_assembly = ''
except:
except Exception as error_exception:
logger.info('Exception. Command possibly not in list, or other error.')
logger.info(error_exception)
logger.info(str(traceback.extract_tb(error_exception.__traceback__)))
packet_assembly = ''
else:
pass
@ -1446,13 +1528,18 @@ class routerHBP(HBSYSTEM):
lon_min = ba2num(bptc_decode(_data)[46:52])
lat_min_dec = str(ba2num(bptc_decode(_data)[24:38])).zfill(4)
lon_min_dec = str(ba2num(bptc_decode(_data)[52:66])).zfill(4)
aprs_lat = str(str(lat_deg) + str(lat_min) + '.' + str(lat_min_dec)[0:2]).zfill(7) + lat_dir
aprs_lon = str(str(lon_deg) + str(lon_min) + '.' + str(lon_min_dec)[0:2]).zfill(8) + lon_dir
# Old MD-380 coordinate format, keep here until new is confirmed working.
#aprs_lat = str(str(lat_deg) + str(lat_min) + '.' + str(lat_min_dec)[0:2]).zfill(7) + lat_dir
#aprs_lon = str(str(lon_deg) + str(lon_min) + '.' + str(lon_min_dec)[0:2]).zfill(8) + lon_dir
# Fix for MD-380 by G7HIF
aprs_lat = str(str(lat_deg) + str(lat_min).zfill(2) + '.' + str(lat_min_dec)[0:2]).zfill(7) + lat_dir
aprs_lon = str(str(lon_deg) + str(lon_min).zfill(2) + '.' + str(lon_min_dec)[0:2]).zfill(8) + lon_dir
# Form APRS packet
#logger.info(aprs_loc_packet)
logger.info('Lat: ' + str(aprs_lat) + ' Lon: ' + str(aprs_lon))
# 14FRS2013 simplified and moved settings retrieval
user_settings = ast.literal_eval(os.popen('cat ./user_settings.txt').read())
user_settings = ast.literal_eval(os.popen('cat ' + user_settings_file).read())
if int_id(_rf_src) not in user_settings:
ssid = str(user_ssid)
icon_table = '/'
@ -1473,7 +1560,7 @@ class routerHBP(HBSYSTEM):
ssid = user_settings[int_id(_rf_src)][1]['ssid']
if user_settings[int_id(_rf_src)][3]['comment'] != '':
comment = user_settings[int_id(_rf_src)][3]['comment']
aprs_loc_packet = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid + '>APHBL3,TCPIP*:/' + str(datetime.datetime.utcnow().strftime("%H%M%Sh")) + str(aprs_lat) + icon_table + str(aprs_lon) + icon_icon + '/' + str(comment)
aprs_loc_packet = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid + '>APHBL3,TCPIP*:@' + str(datetime.datetime.utcnow().strftime("%H%M%Sh")) + str(aprs_lat) + icon_table + str(aprs_lon) + icon_icon + '/' + str(comment)
logger.info(aprs_loc_packet)
logger.info('User comment: ' + comment)
logger.info('User SSID: ' + ssid)
@ -1484,10 +1571,12 @@ class routerHBP(HBSYSTEM):
float(lat_deg) < 91
float(lon_deg) < 121
aprs_send(aprs_loc_packet)
dashboard_loc_write(str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid, aprs_lat, aprs_lon, strftime('%H:%M:%S - %m/%d/%y'))
dashboard_loc_write(str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid, aprs_lat, aprs_lon, time(), comment)
#logger.info('Sent APRS packet')
except:
except Exception as error_exception:
logger.info('Error. Failed to send packet. Packet may be malformed.')
logger.info(error_exception)
logger.info(str(traceback.extract_tb(error_exception.__traceback__)))
udt_block = 1
hdr_type = ''
else:
@ -1546,7 +1635,7 @@ class routerHBP(HBSYSTEM):
# Begin APRS format and upload
# Disable opening file for reading to reduce "collision" or reading and writing at same time.
# 14FRS2013 simplified and moved settings retrieval
user_settings = ast.literal_eval(os.popen('cat ./user_settings.txt').read())
user_settings = ast.literal_eval(os.popen('cat ' + user_settings_file).read())
if int_id(_rf_src) not in user_settings:
ssid = str(user_ssid)
icon_table = '/'
@ -1567,14 +1656,16 @@ class routerHBP(HBSYSTEM):
ssid = user_settings[int_id(_rf_src)][1]['ssid']
if user_settings[int_id(_rf_src)][3]['comment'] != '':
comment = user_settings[int_id(_rf_src)][3]['comment']
aprs_loc_packet = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid + '>APHBL3,TCPIP*:/' + str(datetime.datetime.utcnow().strftime("%H%M%Sh")) + str(loc.lat[0:7]) + str(loc.lat_dir) + icon_table + str(loc.lon[0:8]) + str(loc.lon_dir) + icon_icon + str(round(loc.true_course)).zfill(3) + '/' + str(round(loc.spd_over_grnd)).zfill(3) + '/' + str(comment)
aprs_loc_packet = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid + '>APHBL3,TCPIP*:@' + str(datetime.datetime.utcnow().strftime("%H%M%Sh")) + str(loc.lat[0:7]) + str(loc.lat_dir) + icon_table + str(loc.lon[0:8]) + str(loc.lon_dir) + icon_icon + str(round(loc.true_course)).zfill(3) + '/' + str(round(loc.spd_over_grnd)).zfill(3) + '/' + str(comment)
logger.info(aprs_loc_packet)
logger.info('User comment: ' + comment)
logger.info('User SSID: ' + ssid)
logger.info('User icon: ' + icon_table + icon_icon)
except:
except Exception as error_exception:
logger.info('Error or user settings file not found, proceeding with default settings.')
aprs_loc_packet = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + str(user_ssid) + '>APHBL3,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) + '[' + str(round(loc.true_course)).zfill(3) + '/' + str(round(loc.spd_over_grnd)).zfill(3) + '/' + aprs_comment + ' DMR ID: ' + str(int_id(_rf_src))
aprs_loc_packet = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + str(user_ssid) + '>APHBL3,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) + '[' + str(round(loc.true_course)).zfill(3) + '/' + str(round(loc.spd_over_grnd)).zfill(3) + '/' + aprs_comment + ' DMR ID: ' + str(int_id(_rf_src))
logger.info(error_exception)
logger.info(str(traceback.extract_tb(error_exception.__traceback__)))
try:
# Try parse of APRS packet. If it fails, it will not upload to APRS-IS
aprslib.parse(aprs_loc_packet)
@ -1582,9 +1673,11 @@ class routerHBP(HBSYSTEM):
float(loc.lat)
float(loc.lon)
aprs_send(aprs_loc_packet)
dashboard_loc_write(str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid, str(loc.lat[0:7]) + str(loc.lat_dir), str(loc.lon[0:8]) + str(loc.lon_dir), strftime('%H:%M:%S - %m/%d/%y'))
except:
dashboard_loc_write(str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid, str(loc.lat[0:7]) + str(loc.lat_dir), str(loc.lon[0:8]) + str(loc.lon_dir), time(), comment)
except Exception as error_exception:
logger.info('Failed to parse packet. Packet may be deformed. Not uploaded.')
logger.info(error_exception)
logger.info(str(traceback.extract_tb(error_exception.__traceback__)))
#final_packet = ''
# Get callsign based on DMR ID
# End APRS-IS upload
@ -1697,32 +1790,6 @@ if __name__ == '__main__':
# Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
# Check if user_settings (for APRS settings of users) exists. Creat it if not.
if Path('./user_settings.txt').is_file():
pass
else:
Path('./user_settings.txt').touch()
with open("./user_settings.txt", 'w') as user_dict_file:
user_dict_file.write("{1: [{'call': 'N0CALL'}, {'ssid': ''}, {'icon': ''}, {'comment': ''}]}")
user_dict_file.close()
# Check to see if dashboard files exist
if Path('/tmp/gps_data_user_loc.txt').is_file():
pass
else:
Path('/tmp/gps_data_user_loc.txt').touch()
with open("/tmp/gps_data_user_loc.txt", 'w') as user_loc_file:
user_loc_file.write("[]")
user_loc_file.close()
if Path('/tmp/gps_data_user_bb.txt').is_file():
pass
else:
Path('/tmp/gps_data_user_bb.txt').touch()
with open("/tmp/gps_data_user_bb.txt", 'w') as user_bb_file:
user_bb_file.write("[]")
user_bb_file.close()
# 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)')
@ -1755,6 +1822,43 @@ if __name__ == '__main__':
smtp_server = CONFIG['GPS_DATA']['SMTP_SERVER']
smtp_port = CONFIG['GPS_DATA']['SMTP_PORT']
# Dashboard files
bb_file = CONFIG['GPS_DATA']['BULLETIN_BOARD_FILE']
loc_file = CONFIG['GPS_DATA']['LOCATION_FILE']
the_mailbox_file = CONFIG['GPS_DATA']['MAILBOX_FILE']
emergency_sos_file = CONFIG['GPS_DATA']['EMERGENCY_SOS_FILE']
# Check if user_settings (for APRS settings of users) exists. Creat it if not.
if Path(user_settings_file).is_file():
pass
else:
Path(user_settings_file).touch()
with open(user_settings_file, 'w') as user_dict_file:
user_dict_file.write("{1: [{'call': 'N0CALL'}, {'ssid': ''}, {'icon': ''}, {'comment': ''}]}")
user_dict_file.close()
# Check to see if dashboard files exist
if Path(loc_file).is_file():
pass
else:
Path(loc_file).touch()
with open(loc_file, 'w') as user_loc_file:
user_loc_file.write("[]")
user_loc_file.close()
if Path(bb_file).is_file():
pass
else:
Path(bb_file).touch()
with open(bb_file, 'w') as user_bb_file:
user_bb_file.write("[]")
user_bb_file.close()
if Path(the_mailbox_file).is_file():
pass
else:
Path(the_mailbox_file).touch()
with open(the_mailbox_file, 'w') as user_loc_file:
user_loc_file.write("[]")
user_loc_file.close()
# Ensure we have a path for the rules file, if one wasn't specified, then use the default (top of file)
if not cli_args.RULES_FILE:
cli_args.RULES_FILE = os.path.dirname(os.path.abspath(__file__))+'/rules.py'
@ -1812,6 +1916,7 @@ if __name__ == '__main__':
systems[system] = routerHBP(system, CONFIG, report_server)
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])
#aprs_upload(CONFIG)
def loopingErrHandle(failure):
logger.error('(GLOBAL) STOPPING REACTOR TO AVOID MEMORY LEAK: Unhandled error in timed loop.\n %s', failure)

View File

@ -161,6 +161,11 @@ def build_config(_config_file):
'EMAIL_PASSWORD': config.get(section, 'EMAIL_PASSWORD'),
'SMTP_SERVER': config.get(section, 'SMTP_SERVER'),
'SMTP_PORT': config.get(section, 'SMTP_PORT'),
'LOCATION_FILE': config.get(section, 'LOCATION_FILE'),
'BULLETIN_BOARD_FILE': config.get(section, 'BULLETIN_BOARD_FILE'),
'MAILBOX_FILE': config.get(section, 'MAILBOX_FILE'),
'EMERGENCY_SOS_FILE': config.get(section, 'EMERGENCY_SOS_FILE'),
'USER_SETTINGS_FILE': config.get(section, 'USER_SETTINGS_FILE'),
})
if not CONFIG['LOGGER']['LOG_FILE']:

View File

@ -105,6 +105,7 @@ 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.
#
@ -126,6 +127,11 @@ APRS_LOGIN_PASSCODE: 12345
APRS_SERVER: rotate.aprs2.net
APRS_PORT: 14580
# The APRS filter below is used for the message received script. See http://www.aprs-is.net/javAPRSFilter.aspx for details
# about APRS filters.
APRS_RECEIVE_LOGIN_CALL: N0CALL-1
APRS_FILTER: r/47/-120/500 t/m
# The following settings are only applicable if you are using the gps_data_beacon_igate script.
# They do not affect the operation gps_data itself.
# Time in minutes.
@ -135,13 +141,62 @@ IGATE_BEACON_ICON = /I
IGATE_LATITUDE = 0000.00N
IGATE_LONGITUDE = 00000.00W
# User settings file, MUST configure using absolute path.
USER_SETTINGS_FILE: /path/to/user_settings.txt
# The email gateway settingns below are OPTIONAL. They are NOT REQUIRED if you don't want
# to enable the email gateway. Leave as is to disable.
http://www.aprs-is.net/javAPRSFilter.aspx# to enable the email gateway. Leave as is to disable.
EMAIL_SENDER: test@example.org
EMAIL_PASSWORD: letmein
SMTP_SERVER: smtp.gmail.com
SMTP_PORT: 465
# The options below are required for operation of the dashboard and will cause errors in gps_data.py
# if configured wrong. Leave them as default unless you know what you are doing.
# If you do change, you must use absolute paths.
LOCATION_FILE: /tmp/gps_data_user_loc.txt
BULLETIN_BOARD_FILE: /tmp/gps_data_user_bb.txt
MAILBOX_FILE: /tmp/gps_data_user_mailbox.txt
EMERGENCY_SOS_FILE: /tmp/gps_data_user_sos.txt
# The following options are used for the dashboard. The dashboard is optional.
# Title of the Dashboard
DASHBOARD_TITLE: HBLink3 D-APRS Dashboard
# Logo used on dashboard page
LOGO: https://raw.githubusercontent.com/kf7eel/hblink3/gps/HBlink.png
# Port to run server
DASH_PORT: 8092
# IP to run server on
DASH_HOST: 127.0.0.1
#Description of dashboard to show on main page
DESCRIPTION: Welcome to the dashboard.
# Gateway contact info displayed on about page.
CONTACT_NAME: your name
CONTACT_CALL: N0CALL
CONTACT_EMAIL: email@example.org
CONTACT_WEBSITE: https://hbl.ink
# Time format for display
TIME_FORMAT: %%H:%%M:%%S - %%m/%%d/%%y
# Center dashboard map over these coordinates
MAP_CENTER_LAT: 47.00
MAP_CENTER_LON: -120.00
ZOOM_LEVEL: 7
# List and preview of some map themes at http://leaflet-extras.github.io/leaflet-providers/preview/
# The following are options for map themes and just work, you should use one of these: “OpenStreetMap”, “Stamen” (Terrain, Toner, and Watercolor),
MAP_THEME: Stamen Toner
# RSS feed link, shows in the link section of each RSS item.
RSS_LINK: http://localhost:8092
#####################################################################
# 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

View File

@ -48,6 +48,7 @@ import const
# The module needs logging logging, but handlers, etc. are controlled by the parent
import logging
logger = logging.getLogger(__name__)
import traceback
# Other modules we need for data and GPS
from bitarray import bitarray
@ -70,12 +71,12 @@ from gps_functions import cmd_list
# Module for maidenhead grids
try:
import maidenhead as mh
except:
except Exception as error_exception:
logger.info('Error importing maidenhead module, make sure it is installed.')
# Module for sending email
try:
import smtplib
except:
except Exception as error_exception:
logger.info('Error importing smtplib module, make sure it is installed.')
#Modules for APRS settings
@ -92,6 +93,8 @@ __maintainer__ = 'Eric Craw, KF7EEL'
__email__ = 'kf7eel@qsl.net'
__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)
@ -101,7 +104,7 @@ __status__ = 'pre-alpha'
# AT-D878 - Compressed UDP
# MD-380 - Unified Data Transport
hdr_type = ''
btf = ''
btf = -1
ssid = ''
# From dmr_utils3, modified to decode entire packet. Works for 1/2 rate coded data.
@ -149,31 +152,88 @@ def aprs_send(packet):
AIS.sendall(packet)
AIS.close()
logger.info('Packet sent to APRS-IS.')
# For future use
##def position_timer(aprs_call):
## dash_entries = ast.literal_eval(os.popen('cat ' + loc_file).read())
## for i in dash_entries:
## if aprs_call == i['call']:
## if time.time()
def dashboard_loc_write(call, lat, lon, time):
#try:
def dashboard_loc_write(call, lat, lon, time, comment):
dash_entries = ast.literal_eval(os.popen('cat /tmp/gps_data_user_loc.txt').read())
# except:
# dash_entries = []
dash_entries.insert(0, {'call': call, 'lat': lat, 'lon': lon, 'time':time})
with open("/tmp/gps_data_user_loc.txt", 'w') as user_loc_file:
user_loc_file.write(str(dash_entries[:15]))
dash_entries.insert(0, {'call': call, 'lat': lat, 'lon': lon, 'time':time, 'comment':comment})
# Clear old entries
list_index = 0
call_count = 0
new_dash_entries = []
for i in dash_entries:
if i['call'] == call:
if call_count >= 25:
pass
else:
new_dash_entries.append(i)
call_count = call_count + 1
if call != i['call']:
new_dash_entries.append(i)
pass
list_index = list_index + 1
with open(loc_file, 'w') as user_loc_file:
user_loc_file.write(str(new_dash_entries[:500]))
user_loc_file.close()
logger.info('User location saved for dashboard')
#logger.info(dash_entries)
def dashboard_bb_write(call, dmr_id, time, bulletin):
#try:
dash_bb = ast.literal_eval(os.popen('cat /tmp/gps_data_user_bb.txt').read())
dash_bb = ast.literal_eval(os.popen('cat ' + bb_file).read())
# except:
# dash_entries = []
dash_bb.insert(0, {'call': call, 'dmr_id': dmr_id, 'time': time, 'bulliten':bulletin})
with open("/tmp/gps_data_user_bb.txt", 'w') as user_bb_file:
user_bb_file.write(str(dash_bb[:10]))
dash_bb.insert(0, {'call': call, 'dmr_id': dmr_id, 'time': time, 'bulletin':bulletin})
with open(bb_file, 'w') as user_bb_file:
user_bb_file.write(str(dash_bb[:20]))
user_bb_file.close()
logger.info('User bulletin entry saved.')
#logger.info(dash_bb)
def mailbox_write(call, dmr_id, time, message, recipient):
#try:
mail_file = ast.literal_eval(os.popen('cat ' + the_mailbox_file).read())
mail_file.insert(0, {'call': call, 'dmr_id': dmr_id, 'time': time, 'message':message, 'recipient': recipient})
with open(the_mailbox_file, 'w') as mailbox_file:
mailbox_file.write(str(mail_file[:100]))
mailbox_file.close()
logger.info('User mail saved.')
def mailbox_delete(dmr_id):
mail_file = ast.literal_eval(os.popen('cat ' + the_mailbox_file).read())
call = str(get_alias((dmr_id), subscriber_ids))
new_data = []
for message in mail_file:
if message['recipient'] != call:
new_data.append(message)
with open(the_mailbox_file, 'w') as mailbox_file:
mailbox_file.write(str(new_data[:100]))
mailbox_file.close()
logger.info('Mailbox updated. Delete occurred.')
def sos_write(dmr_id, time, message):
user_settings = ast.literal_eval(os.popen('cat ' + user_settings_file).read())
try:
if user_settings[dmr_id][1]['ssid'] == '':
sos_call = user_settings[dmr_id][0]['call'] + '-' + user_ssid
else:
sos_call = user_settings[dmr_id][0]['call'] + '-' + user_settings[dmr_id][1]['ssid']
except:
sos_call = str(get_alias((dmr_id), subscriber_ids))
sos_info = {'call': sos_call, 'dmr_id': dmr_id, 'time': time, 'message':message}
with open(emergency_sos_file, 'w') as sos_file:
sos_file.write(str(sos_info))
sos_file.close()
logger.info('Saved SOS.')
# Send email via SMTP function
def send_email(to_email, email_subject, email_message):
global smtp_server
@ -198,39 +258,44 @@ def decdeg2dms(dd):
def user_setting_write(dmr_id, setting, value):
## try:
# Open file and load as dict for modification
with open("./user_settings.txt", 'r') as f:
with open(user_settings_file, 'r') as f:
## if f.read() == '{}':
## user_dict = {}
user_dict = ast.literal_eval(f.read())
logger.info('Current settings: ' + str(user_dict))
if dmr_id not in user_dict:
user_dict[dmr_id] = [{'call': str(get_alias((dmr_id), subscriber_ids))}, {'ssid': ''}, {'icon': ''}, {'comment': ''}]
if setting.upper() == 'ICON':
user_dict[dmr_id][2]['icon'] = value
if setting.upper() == 'SSID':
user_dict[dmr_id][1]['ssid'] = value
if setting.upper() == 'COM':
user_comment = user_dict[dmr_id][3]['comment'] = value[0:35]
if setting.upper() == 'APRS':
user_dict[dmr_id] = [{'call': str(get_alias((dmr_id), subscriber_ids))}, {'ssid': ''}, {'icon': ''}, {'comment': ''}]
if setting.upper() == 'PIN':
try:
if user_dict[dmr_id]:
user_dict[dmr_id][4]['pin'] = value
if not user_dict[dmr_id]:
user_dict[dmr_id] = [{'call': str(get_alias((dmr_id), subscriber_ids))}, {'ssid': ''}, {'icon': ''}, {'comment': ''}, {'pin': pin}]
except:
user_dict[dmr_id].append({'pin': value})
f.close()
logger.info('Loaded user settings. Preparing to write...')
# Write modified dict to file
with open("./user_settings.txt", 'w') as user_dict_file:
with open(user_settings_file, 'w') as user_dict_file:
user_dict_file.write(str(user_dict))
user_dict_file.close()
logger.info('User setting saved')
f.close()
packet_assembly = ''
## except:
## logger.info('No data file found, creating one.')
## #Path('./user_settings.txt').mkdir(parents=True, exist_ok=True)
## Path('./user_settings.txt').touch()
# Process SMS, do something bases on message
def process_sms(_rf_src, sms):
if sms == 'ID':
logger.info(str(get_alias(int_id(from_id), subscriber_ids)) + ' - ' + str(int_id(from_id)))
logger.info(str(get_alias(int_id(_rf_src), subscriber_ids)) + ' - ' + str(int_id(_rf_src)))
elif sms == 'TEST':
logger.info('It works!')
elif '@ICON' in sms:
@ -239,19 +304,37 @@ def process_sms(_rf_src, sms):
user_setting_write(int_id(_rf_src), re.sub(' .*|@','',sms), re.sub('@SSID| ','',sms))
elif '@COM' in sms:
user_setting_write(int_id(_rf_src), re.sub(' .*|@','',sms), re.sub('@COM |@COM','',sms))
elif '@PIN' in sms:
user_setting_write(int_id(_rf_src), re.sub(' .*|@','',sms), int(re.sub('@PIN |@PIN','',sms)))
# Write blank entry to cause APRS receive to look for packets for this station.
elif '@APRS' in sms:
user_setting_write(int_id(_rf_src), 'APRS', '')
elif '@BB' in sms:
dashboard_bb_write(get_alias(int_id(_rf_src), subscriber_ids), int_id(_rf_src), time.strftime('%H:%M:%S - %m/%d/%y'), re.sub('@BB|@BB ','',sms))
dashboard_bb_write(get_alias(int_id(_rf_src), subscriber_ids), int_id(_rf_src), time.time(), re.sub('@BB|@BB ','',sms))
elif '@' and ' E-' in sms:
email_message = re.sub('.*@|.* E-', '', sms)
to_email = re.sub(' E-.*', '', sms)
email_message = str(re.sub('.*@|.* E-', '', sms))
to_email = str(re.sub(' E-.*', '', sms))
email_subject = 'New message from ' + str(get_alias(int_id(_rf_src), subscriber_ids))
logger.info('Email to: ' + to_email)
logger.info('Message: ' + email_message)
try:
send_email(to_email, email_subject, email_message)
logger.info('Email sent.')
except:
except Exception as error_exception:
logger.info('Failed to send email.')
logger.info(error_exception)
logger.info(str(traceback.extract_tb(error_exception.__traceback__)))
elif '@SOS' in sms or '@NOTICE' in sms:
sos_write(int_id(_rf_src), time.time(), sms)
elif '@REM SOS' == sms:
os.remove(emergency_sos_file)
logger.info('Removing SOS or Notice')
elif '@' and 'M-' in sms:
message = re.sub('^@|.* M-|','',sms)
recipient = re.sub('@| M-.*','',sms)
mailbox_write(get_alias(int_id(_rf_src), subscriber_ids), int_id(_rf_src), time.time(), message, str(recipient).upper())
elif '@REM MAIL' == sms:
mailbox_delete(_rf_src)
elif '@MH' in sms:
grid_square = re.sub('@MH ', '', sms)
if len(grid_square) < 6:
@ -275,7 +358,7 @@ def process_sms(_rf_src, sms):
logger.info('Latitude: ' + str(aprs_lat))
logger.info('Longitude: ' + str(aprs_lon))
# 14FRS2013 simplified and moved settings retrieval
user_settings = ast.literal_eval(os.popen('cat ./user_settings.txt').read())
user_settings = ast.literal_eval(os.popen('cat ' + user_settings_file).read())
if int_id(_rf_src) not in user_settings:
ssid = str(user_ssid)
icon_table = '/'
@ -296,7 +379,7 @@ def process_sms(_rf_src, sms):
ssid = user_settings[int_id(_rf_src)][1]['ssid']
if user_settings[int_id(_rf_src)][3]['comment'] != '':
comment = user_settings[int_id(_rf_src)][3]['comment']
aprs_loc_packet = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid + '>APHBL3,TCPIP*:/' + str(datetime.datetime.utcnow().strftime("%H%M%Sh")) + str(aprs_lat) + icon_table + str(aprs_lon) + icon_icon + '/' + str(comment)
aprs_loc_packet = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid + '>APHBL3,TCPIP*:@' + str(datetime.datetime.utcnow().strftime("%H%M%Sh")) + str(aprs_lat) + icon_table + str(aprs_lon) + icon_icon + '/' + str(comment)
logger.info(aprs_loc_packet)
logger.info('User comment: ' + comment)
logger.info('User SSID: ' + ssid)
@ -304,19 +387,21 @@ def process_sms(_rf_src, sms):
try:
aprslib.parse(aprs_loc_packet)
aprs_send(aprs_loc_packet)
dashboard_loc_write(str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid, aprs_lat, aprs_lon, time.strftime('%H:%M:%S - %m/%d/%y'))
dashboard_loc_write(str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid, aprs_lat, aprs_lon, time.time(), comment)
#logger.info('Sent manual position to APRS')
except:
except Exception as error_exception:
logger.info('Exception. Not uploaded')
logger.info(error_exception)
logger.info(str(traceback.extract_tb(error_exception.__traceback__)))
packet_assembly = ''
elif 'A-' in sms and '@' in sms:
#Example SMS text: @ARMDS A-This is a test.
aprs_dest = re.sub('@| A-.*','',sms)
aprs_msg = re.sub('@.* A-|','',sms)
aprs_msg = re.sub('^@|.* A-|','',sms)
logger.info('APRS message to ' + aprs_dest.upper() + '. Message: ' + aprs_msg)
user_settings = ast.literal_eval(os.popen('cat ./user_settings.txt').read())
user_settings = ast.literal_eval(os.popen('cat ' + user_settings_file).read())
if int_id(_rf_src) in user_settings and user_settings[int_id(_rf_src)][1]['ssid'] != '':
ssid = user_settings[int_id(_rf_src)][1]['ssid']
else:
@ -327,16 +412,20 @@ def process_sms(_rf_src, sms):
aprslib.parse(aprs_msg_pkt)
aprs_send(aprs_msg_pkt)
#logger.info('Packet sent.')
except:
except Exception as error_exception:
logger.info('Error uploading MSG packet.')
logger.info(error_exception)
logger.info(str(traceback.extract_tb(error_exception.__traceback__)))
try:
if sms in cmd_list:
logger.info('Executing command/script.')
os.popen(cmd_list[sms]).read()
packet_assembly = ''
except:
except Exception as error_exception:
logger.info('Exception. Command possibly not in list, or other error.')
packet_assembly = ''
logger.info(error_exception)
logger.info(str(traceback.extract_tb(error_exception.__traceback__)))
else:
pass
@ -394,13 +483,18 @@ class DATA_SYSTEM(HBSYSTEM):
lon_min = ba2num(bptc_decode(_data)[46:52])
lat_min_dec = str(ba2num(bptc_decode(_data)[24:38])).zfill(4)
lon_min_dec = str(ba2num(bptc_decode(_data)[52:66])).zfill(4)
aprs_lat = str(str(lat_deg) + str(lat_min) + '.' + str(lat_min_dec)[0:2]).zfill(7) + lat_dir
aprs_lon = str(str(lon_deg) + str(lon_min) + '.' + str(lon_min_dec)[0:2]).zfill(8) + lon_dir
# Old MD-380 coordinate format, keep here until new is confirmed working.
#aprs_lat = str(str(lat_deg) + str(lat_min) + '.' + str(lat_min_dec)[0:2]).zfill(7) + lat_dir
#aprs_lon = str(str(lon_deg) + str(lon_min) + '.' + str(lon_min_dec)[0:2]).zfill(8) + lon_dir
# Fix for MD-380 by G7HIF
aprs_lat = str(str(lat_deg) + str(lat_min).zfill(2) + '.' + str(lat_min_dec)[0:2]).zfill(7) + lat_dir
aprs_lon = str(str(lon_deg) + str(lon_min).zfill(2) + '.' + str(lon_min_dec)[0:2]).zfill(8) + lon_dir
# Form APRS packet
#logger.info(aprs_loc_packet)
logger.info('Lat: ' + str(aprs_lat) + ' Lon: ' + str(aprs_lon))
# 14FRS2013 simplified and moved settings retrieval
user_settings = ast.literal_eval(os.popen('cat ./user_settings.txt').read())
user_settings = ast.literal_eval(os.popen('cat ' + user_settings_file).read())
if int_id(_rf_src) not in user_settings:
ssid = str(user_ssid)
icon_table = '/'
@ -421,7 +515,7 @@ class DATA_SYSTEM(HBSYSTEM):
ssid = user_settings[int_id(_rf_src)][1]['ssid']
if user_settings[int_id(_rf_src)][3]['comment'] != '':
comment = user_settings[int_id(_rf_src)][3]['comment']
aprs_loc_packet = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid + '>APHBL3,TCPIP*:/' + str(datetime.datetime.utcnow().strftime("%H%M%Sh")) + str(aprs_lat) + icon_table + str(aprs_lon) + icon_icon + '/' + str(comment)
aprs_loc_packet = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid + '>APHBL3,TCPIP*:@' + str(datetime.datetime.utcnow().strftime("%H%M%Sh")) + str(aprs_lat) + icon_table + str(aprs_lon) + icon_icon + '/' + str(comment)
logger.info(aprs_loc_packet)
logger.info('User comment: ' + comment)
logger.info('User SSID: ' + ssid)
@ -432,10 +526,12 @@ class DATA_SYSTEM(HBSYSTEM):
float(lat_deg) < 91
float(lon_deg) < 121
aprs_send(aprs_loc_packet)
dashboard_loc_write(str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid, aprs_lat, aprs_lon, time.strftime('%H:%M:%S - %m/%d/%y'))
dashboard_loc_write(str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid, aprs_lat, aprs_lon, time.time(), comment)
#logger.info('Sent APRS packet')
except:
except Exception as error_exception:
logger.info('Error. Failed to send packet. Packet may be malformed.')
logger.info(error_exception)
logger.info(str(traceback.extract_tb(error_exception.__traceback__)))
udt_block = 1
hdr_type = ''
else:
@ -459,7 +555,7 @@ class DATA_SYSTEM(HBSYSTEM):
btf = btf - 1
logger.info('Block #: ' + str(btf))
#logger.info(_seq)
logger.info('Data block from ' + str(get_alias(int_id(_rf_src), subscriber_ids)) + '. DMR ID: ' + str(int_id(_rf_src)) + '. Destination: ' + str(int_id(_dst_id)))
logger.info('Data block from ' + str(get_alias(int_id(_rf_src), subscriber_ids)) + '. DMR ID: ' + str(int_id(_rf_src)))
logger.info(ahex(bptc_decode(_data)))
if _seq == 0:
n_packet_assembly = 0
@ -474,7 +570,6 @@ class DATA_SYSTEM(HBSYSTEM):
if btf == 0:
final_packet = str(bitarray(re.sub("\)|\(|bitarray|'", '', packet_assembly)).tobytes().decode('utf-8', 'ignore'))
sms_hex = str(ba2hx(bitarray(re.sub("\)|\(|bitarray|'", '', packet_assembly))))
sms_hex_string = re.sub("b'|'", '', str(sms_hex))
#NMEA GPS sentence
if '$GPRMC' in final_packet or '$GNRMC' in final_packet:
@ -495,7 +590,7 @@ class DATA_SYSTEM(HBSYSTEM):
# Begin APRS format and upload
# Disable opening file for reading to reduce "collision" or reading and writing at same time.
# 14FRS2013 simplified and moved settings retrieval
user_settings = ast.literal_eval(os.popen('cat ./user_settings.txt').read())
user_settings = ast.literal_eval(os.popen('cat ' + user_settings_file).read())
if int_id(_rf_src) not in user_settings:
ssid = str(user_ssid)
icon_table = '/'
@ -516,14 +611,16 @@ class DATA_SYSTEM(HBSYSTEM):
ssid = user_settings[int_id(_rf_src)][1]['ssid']
if user_settings[int_id(_rf_src)][3]['comment'] != '':
comment = user_settings[int_id(_rf_src)][3]['comment']
aprs_loc_packet = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid + '>APHBL3,TCPIP*:/' + str(datetime.datetime.utcnow().strftime("%H%M%Sh")) + str(loc.lat[0:7]) + str(loc.lat_dir) + icon_table + str(loc.lon[0:8]) + str(loc.lon_dir) + icon_icon + str(round(loc.true_course)).zfill(3) + '/' + str(round(loc.spd_over_grnd)).zfill(3) + '/' + str(comment)
aprs_loc_packet = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid + '>APHBL3,TCPIP*:@' + str(datetime.datetime.utcnow().strftime("%H%M%Sh")) + str(loc.lat[0:7]) + str(loc.lat_dir) + icon_table + str(loc.lon[0:8]) + str(loc.lon_dir) + icon_icon + str(round(loc.true_course)).zfill(3) + '/' + str(round(loc.spd_over_grnd)).zfill(3) + '/' + str(comment)
logger.info(aprs_loc_packet)
logger.info('User comment: ' + comment)
logger.info('User SSID: ' + ssid)
logger.info('User icon: ' + icon_table + icon_icon)
except:
except Exception as error_exception:
logger.info('Error or user settings file not found, proceeding with default settings.')
aprs_loc_packet = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + str(user_ssid) + '>APHBL3,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) + '[' + str(round(loc.true_course)).zfill(3) + '/' + str(round(loc.spd_over_grnd)).zfill(3) + '/' + aprs_comment + ' DMR ID: ' + str(int_id(_rf_src))
aprs_loc_packet = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + str(user_ssid) + '>APHBL3,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) + '[' + str(round(loc.true_course)).zfill(3) + '/' + str(round(loc.spd_over_grnd)).zfill(3) + '/' + aprs_comment + ' DMR ID: ' + str(int_id(_rf_src))
logger.info(error_exception)
logger.info(str(traceback.extract_tb(error_exception.__traceback__)))
try:
# Try parse of APRS packet. If it fails, it will not upload to APRS-IS
aprslib.parse(aprs_loc_packet)
@ -531,9 +628,11 @@ class DATA_SYSTEM(HBSYSTEM):
float(loc.lat)
float(loc.lon)
aprs_send(aprs_loc_packet)
dashboard_loc_write(str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid, str(loc.lat[0:7]) + str(loc.lat_dir), str(loc.lon[0:8]) + str(loc.lon_dir), time.strftime('%H:%M:%S - %m/%d/%y'))
except:
dashboard_loc_write(str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + ssid, str(loc.lat[0:7]) + str(loc.lat_dir), str(loc.lon[0:8]) + str(loc.lon_dir), time.time(), comment)
except Exception as error_exception:
logger.info('Failed to parse packet. Packet may be deformed. Not uploaded.')
logger.info(error_exception)
logger.info(str(traceback.extract_tb(error_exception.__traceback__)))
#final_packet = ''
# Get callsign based on DMR ID
# End APRS-IS upload
@ -602,29 +701,7 @@ if __name__ == '__main__':
# Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
# Check if user_settings (for APRS settings of users) exists. Creat it if not.
if Path('./user_settings.txt').is_file():
pass
else:
Path('./user_settings.txt').touch()
with open("./user_settings.txt", 'w') as user_dict_file:
user_dict_file.write("{1: [{'call': 'N0CALL'}, {'ssid': ''}, {'icon': ''}, {'comment': ''}]}")
user_dict_file.close()
# Check to see if dashboard files exist
if Path('/tmp/gps_data_user_loc.txt').is_file():
pass
else:
Path('/tmp/gps_data_user_loc.txt').touch()
with open("/tmp/gps_data_user_loc.txt", 'w') as user_loc_file:
user_loc_file.write("[]")
user_loc_file.close()
if Path('/tmp/gps_data_user_bb.txt').is_file():
pass
else:
Path('/tmp/gps_data_user_bb.txt').touch()
with open("/tmp/gps_data_user_bb.txt", 'w') as user_bb_file:
user_bb_file.write("[]")
user_bb_file.close()
# 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 gps_data.cfg)')
@ -654,6 +731,46 @@ if __name__ == '__main__':
email_password = CONFIG['GPS_DATA']['EMAIL_PASSWORD']
smtp_server = CONFIG['GPS_DATA']['SMTP_SERVER']
smtp_port = CONFIG['GPS_DATA']['SMTP_PORT']
# Dashboard files
bb_file = CONFIG['GPS_DATA']['BULLETIN_BOARD_FILE']
loc_file = CONFIG['GPS_DATA']['LOCATION_FILE']
the_mailbox_file = CONFIG['GPS_DATA']['MAILBOX_FILE']
emergency_sos_file = CONFIG['GPS_DATA']['EMERGENCY_SOS_FILE']
# User APRS settings
user_settings_file = CONFIG['GPS_DATA']['USER_SETTINGS_FILE']
# Check if user_settings (for APRS settings of users) exists. Creat it if not.
if Path(user_settings_file).is_file():
pass
else:
Path(user_settings_file).touch()
with open(user_settings_file, 'w') as user_dict_file:
user_dict_file.write("{1: [{'call': 'N0CALL'}, {'ssid': ''}, {'icon': ''}, {'comment': ''}]}")
user_dict_file.close()
# Check to see if dashboard files exist
if Path(loc_file).is_file():
pass
else:
Path(loc_file).touch()
with open(loc_file, 'w') as user_loc_file:
user_loc_file.write("[]")
user_loc_file.close()
if Path(bb_file).is_file():
pass
else:
Path(bb_file).touch()
with open(bb_file, 'w') as user_bb_file:
user_bb_file.write("[]")
user_bb_file.close()
if Path(the_mailbox_file).is_file():
pass
else:
Path(the_mailbox_file).touch()
with open(the_mailbox_file, 'w') as user_mail_file:
user_mail_file.write("[]")
user_mail_file.close()
# Start the system logger
if cli_args.LOG_LEVEL:

View File

@ -3,8 +3,8 @@ bitarray>=0.8.1
Twisted>=16.3.0
dmr_utils3>=0.1.19
configparser>=3.0.0
aprslib
aprslib>=0.6.42
pynmea2
maidenhead
flask
smtplib
folium

View File

@ -0,0 +1,85 @@
###############################################################################
# GPS/Data - Copyright (C) 2020 Eric Craw, KF7EEL <kf7eel@qsl.net>
#
# 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
###############################################################################
import aprslib
import ast, os
import re
from configparser import ConfigParser
import time
import argparse
def mailbox_write(call, dmr_id, time, message, recipient):
global mailbox_file
mail_file = ast.literal_eval(os.popen('cat ' + parser.get('GPS_DATA', 'MAILBOX_FILE')).read())
mail_file.insert(0, {'call': call, 'dmr_id': dmr_id, 'time': time, 'message':message, 'recipient': recipient})
with open(parser.get('GPS_DATA', 'MAILBOX_FILE'), 'w') as mailbox_file:
mailbox_file.write(str(mail_file[:100]))
mailbox_file.close()
print('User mail saved.')
def aprs_filter(packet):
user_settings = ast.literal_eval(os.popen('cat ' + user_settings_file).read())
if 'addresse' in aprslib.parse(packet):
#print(aprslib.parse(packet))
recipient = re.sub('-.*','', aprslib.parse(packet)['addresse'])
recipient_ssid = re.sub('.*-','', aprslib.parse(packet)['addresse'])
if recipient == '':
pass
else:
for i in user_settings.items():
ssid = i[1][1]['ssid']
if i[1][1]['ssid'] == '':
ssid = user_aprs_ssid
if recipient in i[1][0]['call'] and recipient_ssid in ssid:
mailbox_write(re.sub('-.*','', aprslib.parse(packet)['addresse']), aprslib.parse(packet)['from'], time.time(), aprslib.parse(packet)['message_text'], recipient)
if 'msgNo' in aprslib.parse(packet):
time.sleep(1)
AIS.sendall(aprslib.parse(packet)['addresse'] + '>APHBL3,TCPIP*:' + ':' + aprslib.parse(packet)['from'].ljust(9) +':ack'+aprslib.parse(packet)['msgNo'])
print('Send ACK')
print(aprslib.parse(packet)['addresse'] + '>APHBL3,TCPIP*:' + ':' + aprslib.parse(packet)['from'].ljust(9) +':ack'+aprslib.parse(packet)['msgNo'])
## else:
## print(aprslib.parse(packet)['from'])
if __name__ == '__main__':
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (usually gps_data.cfg)')
cli_args = arg_parser.parse_args()
parser = ConfigParser()
if not cli_args.CONFIG_FILE:
print('\n\nMust specify a config file with -c argument.\n\n')
parser.read(cli_args.CONFIG_FILE)
aprs_server = parser.get('GPS_DATA', 'APRS_SERVER')
aprs_port = parser.get('GPS_DATA', 'APRS_PORT')
aprs_login = parser.get('GPS_DATA', 'APRS_RECEIVE_LOGIN_CALL')
aprs_passcode = parser.get('GPS_DATA', 'APRS_LOGIN_PASSCODE')
mailbox_file = parser.get('GPS_DATA', 'MAILBOX_FILE')
user_settings_file = parser.get('GPS_DATA', 'USER_SETTINGS_FILE')
user_aprs_ssid = parser.get('GPS_DATA', 'USER_APRS_SSID')
AIS = aprslib.IS(aprs_login, passwd=int(aprs_passcode), host=aprs_server, port=int(aprs_port))
user_settings = ast.literal_eval(os.popen('cat ' + user_settings_file).read())
print('APRS message receive script for GPS/Data Application.\nAuthor: Eric, KF7EEL - kf7eel@qsl.net')
AIS.set_filter(parser.get('GPS_DATA', 'APRS_FILTER'))
AIS.connect()
print('Connecting to APRS-IS')
AIS.consumer(aprs_filter, raw=True)

View File

@ -21,9 +21,17 @@
This is a web dashboard for the GPS/Data application.
'''
from flask import Flask, render_template
from flask import Flask, render_template, request, Response, Markup
import ast, os
from dashboard_settings import *
#from dashboard_settings import *
import folium
from folium.plugins import MarkerCluster
import re
from datetime import datetime
import argparse
from configparser import ConfigParser
app = Flask(__name__)
@ -38,7 +46,7 @@ tbl_ftr = '''
def get_loc_data():
try:
dash_loc = ast.literal_eval(os.popen('cat /tmp/gps_data_user_loc.txt').read())
dash_loc = ast.literal_eval(os.popen('cat ' + loc_file).read())
tmp_loc = ''
loc_hdr = '''
<tr>
@ -52,25 +60,39 @@ def get_loc_data():
<h2>&nbsp;<strong>Longitude</strong>&nbsp;</h2>
</td>
<td style="text-align: center;">
<h2>&nbsp;<strong>Local Time</strong>&nbsp;</h2>
<h2>&nbsp;<strong>Time</strong>&nbsp;</h2>
</td>
</tr>
'''
last_known_loc_list = []
display_number = 15
for e in dash_loc:
tmp_loc = tmp_loc + '''<tr>
<td style="text-align: center;"><a href="https://aprs.fi/''' + e['call'] + '''"><strong>''' + e['call'] + '''</strong></a></td>
if display_number == 0:
break
else:
if e['call'] in last_known_loc_list:
pass
if e['call'] not in last_known_loc_list:
if type(e['time']) == str:
loc_time = str(e['time'])
if type(e['time']) == int or type(e['time']) == float:
loc_time = datetime.fromtimestamp(e['time']).strftime(time_format)
last_known_loc_list.append(e['call'])
display_number = display_number - 1
tmp_loc = tmp_loc + '''<tr>
<td style="text-align: center;"><a href="view_map?track=''' + e['call'] + '''"target="_blank"><strong>''' + e['call'] + '''</strong></a></td>
<td style="text-align: center;"><strong>&nbsp;''' + str(e['lat']) + '''&nbsp;</strong></td>
<td style="text-align: center;"><strong>&nbsp;''' + str(e['lon']) + '''&nbsp;</strong></td>
<td style="text-align: center;">&nbsp;''' + e['time'] + '''&nbsp;</td>
<td style="text-align: center;">&nbsp;''' + loc_time + '''&nbsp;</td>
</tr>'''
return str(str('<h1 style="text-align: center;">Positions Received</h1>') + tbl_hdr + loc_hdr + tmp_loc + tbl_ftr)
return str(str('<h1 style="text-align: center;">Last Known Location</h1>') + tbl_hdr + loc_hdr + tmp_loc + tbl_ftr)
except:
return str('<h1 style="text-align: center;">No data</h1>')
return str('<h1 style="text-align: center;">No data</h1>')
def get_bb_data():
try:
dash_bb = ast.literal_eval(os.popen('cat /tmp/gps_data_user_bb.txt').read())
dash_bb = ast.literal_eval(os.popen('cat ' + bb_file).read())
tmp_bb = ''
bb_hdr = '''
@ -79,33 +101,104 @@ def get_bb_data():
<h2><strong>&nbsp;Callsign&nbsp;</strong></h2>
</td>
<td style="text-align: center;">
<h2>&nbsp;<strong>DMR ID</strong>&nbsp; </h2>
<h2>&nbsp;<strong>ID</strong>&nbsp; </h2>
</td>
<td style="text-align: center;">
<h2>&nbsp;<strong>Bulletin</strong>&nbsp;</h2>
</td>
<td style="text-align: center;">
<h2>&nbsp;<strong>Local Time</strong>&nbsp;</h2>
<h2>&nbsp;<strong>Time</strong>&nbsp;</h2>
</td>
</tr>
'''
display_number = 10
for e in dash_bb:
tmp_bb = tmp_bb + '''<tr>
<td style="text-align: center;"><strong>&nbsp;''' + e['call'] + '''&nbsp;</strong></td>
<td style="text-align: center;">''' + str(e['dmr_id']) + '''</td>
<td style="text-align: center;"><strong>&nbsp;''' + e['bulliten'] + '''&nbsp;</strong></td>
<td style="text-align: center;">&nbsp;''' + e['time'] + '''&nbsp;</td>
</tr>'''
if display_number == 0:
break
else:
if type(e['time']) == str:
loc_time = str(e['time'])
if type(e['time']) == int or type(e['time']) == float:
loc_time = datetime.fromtimestamp(e['time']).strftime(time_format)
display_number = display_number - 1
tmp_bb = tmp_bb + '''<tr>
<td style="text-align: center;"><strong>&nbsp;''' + e['call'] + '''&nbsp;</strong></td>
<td style="text-align: center;">''' + str(e['dmr_id']) + '''</td>
<td style="text-align: center;"><strong>&nbsp;''' + e['bulletin'] + '''&nbsp;</strong></td>
<td style="text-align: center;">&nbsp;''' + loc_time + '''&nbsp;</td>
</tr>'''
return str('<h1 style="text-align: center;">Bulletin Board</h1>' + tbl_hdr + bb_hdr + tmp_bb + tbl_ftr)
except:
return str('<h1 style="text-align: center;">No data</h1>')
def check_emergency():
# open emergency txt
try:
sos_file = ast.literal_eval(os.popen('cat ' + emergency_sos_file).read())
if type(sos_file['time']) == str:
loc_time = str(sos_file['time'])
if type(sos_file['time']) == int or type(sos_file['time']) == float:
loc_time = datetime.fromtimestamp(sos_file['time']).strftime(time_format)
if '@NOTICE' in sos_file['message'] and '@SOS' not in sos_file['message']:
notice_header = '<span style="background-color: #ffffff; color: #008000;">NOTICE:</span>'
else:
notice_header = '<span style="background-color: #ff0000; color: #ffffff;">EMERGENCY ACTIVATION</span>'
value = Markup("""
<h1 style="text-align: center;">""" + notice_header + """</h1>
<table style="width: 441px; margin-left: auto; margin-right: auto;" border="3">
<tbody>
<tr>
<td style="width: 78.3667px;"><span style="text-decoration: underline;"><strong>From:</strong></span></td>
<td style="width: 345.633px; text-align: center;"><strong>""" + sos_file['call'] + """</strong> - """ + str(sos_file['dmr_id']) + """</td>
</tr>
<tr>
<td style="width: 78.3667px;"><span style="text-decoration: underline;"><strong>Message:</strong></span></td>
<td style="width: 345.633px; text-align: center;">""" + sos_file['message'] + """</td>
</tr>
<tr>
<td style="width: 78.3667px;"><span style="text-decoration: underline;"><strong>Time:</strong></span></td>
<td style="width: 345.633px; text-align: center;">""" + loc_time + """</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<button onclick="window.open('view_map?track=""" + sos_file['call'] + """&reload=30','_blank' );" type="button" class="emergency_button"><h1>View Station on Map</h1></button>
<p style="text-align: center;"><a href="https://aprs.fi/""" + sos_file['call'] + """"><strong>View on aprs.fi</strong></a></p>
<hr />
""")
return value
except Exception as e:
return ''
def aprs_to_latlon(x):
degrees = int(x) // 100
minutes = x - 100*degrees
return degrees + minutes/60
def user_setting_write(dmr_id, input_ssid, input_icon, input_comment):
dmr_id = int(dmr_id)
user_settings = ast.literal_eval(os.popen('cat ' + user_settings_file).read())
new_dict = user_settings
new_dict[dmr_id][1]['ssid'] = input_ssid
new_dict[dmr_id][2]['icon'] = input_icon
new_dict[dmr_id][3]['comment'] = input_comment
print(input_comment)
print(new_dict[dmr_id])
# Write modified dict to file
with open(user_settings_file, 'w') as user_dict_file:
user_dict_file.write(str(new_dict))
user_dict_file.close()
@app.route('/')
def index():
value = Markup('<strong>The HTML String</strong>')
#return get_data()
return render_template('index.html', title = dashboard_title, logo = logo)
return render_template('index.html', title = dashboard_title, logo = logo, emergency = check_emergency())
@app.route('/bulletin_board')
def dash_bb():
return get_bb_data()
@ -113,10 +206,7 @@ def dash_bb():
@app.route('/positions')
def dash_loc():
return get_loc_data()
#return render_template('index.html', data = str(get_data()))
##@app.route('/<string:page_name>/')
##def render_static(page_name):
## return render_template('%s.html' % page_name, title = dashboard_title, logo = logo, description = description)
@app.route('/help/')
def help():
#return get_data()
@ -125,6 +215,550 @@ def help():
def about():
#return get_data()
return render_template('about.html', title = dashboard_title, logo = logo, contact_name = contact_name, contact_call = contact_call, contact_email = contact_email, contact_website = contact_website)
@app.route('/view_map')
def view_map():
reload_time = request.args.get('reload')
track_call = request.args.get('track')
map_size = request.args.get('map_size')
user_loc = ast.literal_eval(os.popen('cat ' + loc_file).read())
last_known_list = []
coord_list = []
try:
if track_call:
#folium_map = folium.Map(location=map_center, zoom_start=int(zoom_level))
#marker_cluster = MarkerCluster().add_to(folium_map)
for user_coord in user_loc:
user_lat = aprs_to_latlon(float(re.sub('[A-Za-z]','', user_coord['lat'])))
user_lon = aprs_to_latlon(float(re.sub('[A-Za-z]','', user_coord['lon'])))
if type(user_coord['time']) == str:
loc_time = str(user_coord['time'])
if type(user_coord['time']) == int or type(user_coord['time']) == float:
loc_time = datetime.fromtimestamp(user_coord['time']).strftime(time_format)
if 'S' in user_coord['lat']:
user_lat = -user_lat
if 'W' in user_coord['lon']:
user_lon = -user_lon
loc_comment = ''
if 'comment' in user_coord:
loc_comment = """
<tr>
<td style="text-align: center;">Comment:</td>
</tr>
<tr>
<td style="text-align: center;"><strong>"""+ str(user_coord['comment']) +"""</strong></td>
</tr>"""
if user_coord['call'] not in last_known_list and user_coord['call'] == track_call:
folium_map = folium.Map(location=[user_lat, user_lon], tiles=map_theme, zoom_start=15)
marker_cluster = MarkerCluster().add_to(folium_map)
folium.Marker([user_lat, user_lon], popup="""<i>
<table style="width: 150px;">
<tbody>
<tr>
<td style="text-align: center;">Last Location:</td>
</tr>
<tr>
<td style="text-align: center;"><strong>"""+ str(user_coord['call']) +"""</strong></td>
</tr>
<tr>
<td style="text-align: center;"><em>"""+ loc_time +"""</em></td>
""" + loc_comment + """
</tr>
</tbody>
</table>
</i>
""", icon=folium.Icon(color="red", icon="record"), tooltip=str(user_coord['call'])).add_to(folium_map)
last_known_list.append(user_coord['call'])
if user_coord['call'] in last_known_list and user_coord['call'] == track_call:
folium.CircleMarker([user_lat, user_lon], popup="""
<table style="width: 150px;">
<tbody>
<tr>
<td style="text-align: center;"><strong>""" + user_coord['call'] + """</strong></td>
</tr>
<tr>
<td style="text-align: center;"><em>""" + loc_time + """</em></td>
</tr>
</tbody>
</table>
""", tooltip=str(user_coord['call']), fill=True, fill_color="#3186cc", radius=4).add_to(marker_cluster)
#return folium_map._repr_html_()
if not reload_time:
reload_time = 120
if not map_size:
map_view = '''<table style="width: 1000px; height: 600px; margin-left: auto; margin-right: auto;" border="1">
<tbody>
<tr>
<td>
''' + folium_map._repr_html_() + '''</td>
</tr>
</tbody>
</table>'''
if map_size == 'full':
map_view = folium_map._repr_html_()
content = '''
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="''' + str(reload_time) + """" >
<title>""" + dashboard_title + """ - Tracking """+ track_call + """</title>
</head>
<p style="text-align: center;"><strong>""" + dashboard_title + """ - Tracking """ + track_call + """</strong></p>
<p style="text-align: center;"><em>Page automatically reloads every """ + str(reload_time) + """ seconds.</em></p>
<p style="text-align: center;">
<select name="sample" onchange="location = this.value;">
<option value="view_map?track=""" + track_call + """&reload=120">2 Minutes</option>
<option value="view_map?track=""" + track_call + """&reload=">Don't Reload</option>
<option value="view_map?track=""" + track_call + """&reload=30">30 Seconds</option>
<option value="view_map?track=""" + track_call + """&reload=5">5 Minutes</option>
<option value="view_map?track=""" + track_call + """&reload=600">10 Minutes</option>
</select>
<p style="text-align: center;"><button onclick="self.close()">Close</button><!--<button onclick="history.back()">Back</button>-->
</p>
""" + map_view
return render_template('generic.html', title = dashboard_title, logo = logo, content = Markup(content))
except Exception as e:
content = """<h1 style="text-align: center;">Station not found.</h1>
#<p style="text-align: center;"><button onclick="self.close()">Close Window</button>
#</p>"""
return render_template('generic.html', title = dashboard_title, logo = logo, content = Markup(content))
if not track_call:
folium_map = folium.Map(location=(map_center_lat, map_center_lon), tiles=map_theme, zoom_start=int(zoom_level))
marker_cluster = MarkerCluster().add_to(folium_map)
for user_coord in user_loc:
user_lat = aprs_to_latlon(float(re.sub('[A-Za-z]','', user_coord['lat'])))
user_lon = aprs_to_latlon(float(re.sub('[A-Za-z]','', user_coord['lon'])))
if type(user_coord['time']) == str:
loc_time = str(user_coord['time'])
if type(user_coord['time']) == int or type(user_coord['time']) == float:
loc_time = datetime.fromtimestamp(user_coord['time']).strftime(time_format)
if 'S' in user_coord['lat']:
user_lat = -user_lat
if 'W' in user_coord['lon']:
user_lon = -user_lon
loc_comment = ''
coord_list.append([user_lat, user_lon])
if 'comment' in user_coord:
loc_comment = """
<tr>
<td style="text-align: center;">Comment:</td>
</tr>
<tr>
<td style="text-align: center;"><strong>"""+ str(user_coord['comment']) +"""</strong></td>
</tr>"""
if user_coord['call'] not in last_known_list:
folium.Marker([user_lat, user_lon], popup="""<i>
<table style="width: 150px;">
<tbody>
<tr>
<td style="text-align: center;">Last Location:</td>
</tr>
<tr>
<td style="text-align: center;"><strong>""" + user_coord['call'] + """</strong></td>
</tr>
<tr>
<td style="text-align: center;"><em>""" + loc_time + """</em></td>
</tr>
""" + loc_comment + """
<tr>
<td style="text-align: center;"><strong><A href="view_map?track=""" + user_coord['call'] + """" target="_blank">Track Station</A></strong></td>
</tr>
</tbody>
</table>
</i>""", icon=folium.Icon(color="red", icon="record"), tooltip=str(user_coord['call'])).add_to(folium_map)
last_known_list.append(user_coord['call'])
if user_coord['call'] in last_known_list:
if coord_list.count([user_lat, user_lon]) > 15:
pass
else:
folium.CircleMarker([user_lat, user_lon], popup="""
<table style="width: 150px;">
<tbody>
<tr>
<td style="text-align: center;"><strong>""" + user_coord['call'] + """</strong></td>
</tr>
<tr>
<td style="text-align: center;"><em>""" + loc_time + """</em></td>
</tr>
</tbody>
</table>
""", tooltip=str(user_coord['call']), fill=True, fill_color="#3186cc", radius=4).add_to(marker_cluster)
return folium_map._repr_html_()
@app.route('/map/')
def map():
return render_template('map.html', title = dashboard_title, logo = logo)
@app.route('/user', methods = ['GET', 'POST'])
def user_settings():
user_settings = ast.literal_eval(os.popen('cat ' + user_settings_file).read())
user_id = request.args.get('user_id')
if request.method == 'POST' and request.form.get('dmr_id'):
if int(request.form.get('dmr_id')) in user_settings:
user_id = request.form.get('dmr_id')
ssid = user_settings[int(request.form.get('dmr_id'))][1]['ssid']
icon = user_settings[int(request.form.get('dmr_id'))][2]['icon']
comment = user_settings[int(request.form.get('dmr_id'))][3]['comment']
try:
pin = user_settings[int(request.form.get('dmr_id'))][4]['pin']
if ssid == '':
ssid = aprs_ssid
if icon == '':
icon = '\['
if comment == '':
comment = default_comment + ' ' + user_id
user_result = """
Use this tool to change the stored APRS settings for your DMR ID. When a position is sent, the stored settings will be used to format the APRS packet. Leave field(s) blank for default value.
<h2 style="text-align: center;">&nbsp;Modify Settings for ID: """ + user_id + """</h2>
<form action="user" method="post">
<table style="margin-left: auto; margin-right: auto; width: 419.367px;" border="1">
<tbody>
<tr>
<td style="width: 82px;"><strong>Callsign:</strong></td>
<td style="width: 319.367px; text-align: center;"><strong>""" + str(user_settings[int(user_id)][0]['call']) + """</strong></td>
</tr>
<tr>
<td style="width: 82px;"><strong>SSID:</strong></td>
<td style="width: 319.367px; text-align: center;"><input id="ssid" name="ssid" type="text" placeholder='""" + ssid + """' /></td>
</tr>
<tr>
<td style="width: 82px;"><strong>Icon:</strong></td>
<td style="width: 319.367px; text-align: center;"><input id="icon" name="icon" type="text" placeholder='""" + icon + """' /></td>
</tr>
<tr>
<td style="width: 82px;"><strong>Comment:</strong></td>
<td style="width: 319.367px; text-align: center;"><input id="comment" name="comment" type="text" placeholder='""" + comment + """'/></td>
</tr>
<tr>
<td style="width: 82px;"><strong>DMR ID:</strong></td>
<td style="width: 319.367px; text-align: center;"><input id="dmr_id" name="dmr_id" type="text" value='""" + user_id + """'/></td>
</tr>
<tr>
<td style="width: 82px;"><strong>PIN:</strong></td>
<td style="width: 319.367px; text-align: center;"><input id="pin" name="pin" type="password" /></td>
</tr>
</tbody>
</table>
<p style="text-align: center;"><input type="submit" value="Submit" /></p>
</form>
<p>&nbsp;</p>
"""
except:
user_result = """<h2 style="text-align: center;">No PIN set for """ + str(user_settings[int(user_id)][0]['call']) + """ - """ + request.form.get('dmr_id') + """</h2>
<p style="text-align: center;"><button onclick="history.back()">Back</button>
</p>"""
if int(request.form.get('dmr_id')) not in user_settings:
user_result = """<h2 style="text-align: center;">DMR ID not found.</h2>
<p style="text-align: center;"><button onclick="history.back()">Back</button>
</p>"""
#if edit_user:
if request.method == 'POST' and request.form.get('dmr_id') and request.form.get('pin'):
if int(request.form.get('pin')) == pin:
ssid = request.form.get('ssid')
icon = request.form.get('icon')
comment = request.form.get('comment')
user_setting_write(request.form.get('dmr_id'), request.form.get('ssid'), request.form.get('icon'), request.form.get('comment'))
user_result = """<h2 style="text-align: center;">Changed settings for """ + str(user_settings[int(user_id)][0]['call']) + """ - """ + request.form.get('dmr_id') + """</h2>
<p style="text-align: center;"><button onclick="history.back()">Back</button>
</p>"""
if int(request.form.get('pin')) != pin:
user_result = """<h2 style="text-align: center;">Incorrect PIN.</h2>
<p style="text-align: center;"><button onclick="history.back()">Back</button>
</p>"""
if request.method == 'GET' and not request.args.get('user_id'):
user_result = """
Use this tool to find, check, and change the stored APRS settings for your DMR ID. When a position is sent, the stored settings will be used to format the APRS packet.
<table style="width: 600px; margin-left: auto; margin-right: auto;" border="3">
<tbody>
<tr>
<td><form action="user" method="get">
<table style="margin-left: auto; margin-right: auto;">
<tbody>
<tr style="height: 62px;">
<td style="text-align: center; height: 62px;">
<h2><strong><label for="user_id">Look up DMR ID:</label></strong></h2>
</td>
</tr>
<tr style="height: 51.1667px;">
<td style="height: 51.1667px;"><input id="user_id" name="user_id" type="text" /></td>
</tr>
<tr style="height: 27px;">
<td style="text-align: center; height: 27px;"><input type="submit" value="Submit" /></td>
</tr>
</tbody>
</table>
</form></td>
<td><form action="user" method="post">
<table style="margin-left: auto; margin-right: auto;">
<tbody>
<tr style="height: 62px;">
<td style="text-align: center; height: 62px;">
<h2><strong><label for="dmr_id">Edit DMR ID:</label></strong></h2>
</td>
</tr>
<tr style="height: 51.1667px;">
<td style="height: 51.1667px;"><input id="dmr_id" name="dmr_id" type="text" /></td>
</tr>
<tr style="height: 27px;">
<td style="text-align: center; height: 27px;"><input type="submit" value="Submit" /></td>
</tr>
</tbody>
</table>
</form></td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
"""
#else:
if request.method == 'GET' and request.args.get('user_id'):
try:
call = user_settings[int(user_id)][0]['call']
ssid = user_settings[int(user_id)][1]['ssid']
icon = user_settings[int(user_id)][2]['icon']
comment = user_settings[int(user_id)][3]['comment']
if ssid == '':
ssid = aprs_ssid
if icon == '':
icon = '\['
if comment == '':
comment = default_comment + ' ' + user_id
#for result in user_settings:
#return user_settings[int(user_id)][0]
#return user_id
#return user_settings
user_result = """<h2 style="text-align: center;">&nbsp;Settings for ID: """ + user_id + """</h2>
<table style="margin-left: auto; margin-right: auto; width: 419.367px;" border="1">
<tbody>
<tr>
<td style="width: 82px;"><strong>Callsign:</strong></td>
<td style="width: 319.367px; text-align: center;">""" + user_settings[int(user_id)][0]['call'] + """</td>
</tr>
<tr>
<td style="width: 82px;"><strong>SSID:</strong></td>
<td style="width: 319.367px; text-align: center;">""" + ssid + """</td>
</tr>
<tr>
<td style="width: 82px;"><strong>Icon:</strong></td>
<td style="width: 319.367px; text-align: center;">""" + icon + """</td>
</tr>
<tr>
<td style="width: 82px;"><strong>Comment:</strong></td>
<td style="width: 319.367px; text-align: center;">""" + comment + """</td>
</tr>
</tbody>
</table>
<p style="text-align: center;"><button onclick="history.back()">Back</button>
</p>
"""
except:
user_result = '''<h2 style="text-align: center;">User ID not found.</h2>
<p style="text-align: center;"><button onclick="history.back()">Back</button>
</p>'''
return render_template('generic.html', title = dashboard_title, logo = logo, content = Markup(user_result))
@app.route('/mailbox')
def mailbox():
recipient = request.args.get('recipient')
if not recipient:
mail_content = """
<p>The Mailbox is a place where users can leave messages via DMR SMS. A user can leave a message for someone else by sending a specially formatted SMS to <strong>""" + data_call_id + """</strong>.
The message recipient can then use the mailbox to check for messages. You can also check for APRS mesages addressed to your DMR radio. Enter your call sign (without APRS SSID) below to check for messages. See the <a href="help">help</a> page for more information.</p>
<form action="mailbox" method="get">
<table style="margin-left: auto; margin-right: auto;">
<tbody>
<tr style="height: 62px;">
<td style="text-align: center; height: 62px;">
<h2><strong><label for="recipient">Callsign:</label></strong></h2>
</td>
</tr>
<tr style="height: 51.1667px;">
<td style="height: 51.1667px;"><input id="recipient" name="recipient" type="text" /></td>
</tr>
<tr style="height: 27px;">
<td style="text-align: center; height: 27px;"><input type="submit" value="Submit" /></td>
</tr>
</tbody>
</table>
</form>
<p>&nbsp;</p>
"""
else:
mailbox_file = ast.literal_eval(os.popen('cat ' + the_mailbox_file).read())
mail_content = '<h2 style="text-align: center;">Messages for: ' + recipient.upper() + '''
</h2>\n<p style="text-align: center;"><button onclick="history.back()">Back</button></p>\n
<h4 style="text-align: center;"><a href="mailbox_rss?recipient=''' + recipient.upper() + '''"><em>Mailbox RSS Feed for ''' + recipient.upper() + '''</em></a><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH5QIcFBAOXAevLAAAAZZJREFUSMftlbtKA0EUhj8jWhi8gaIEC29oxEoRFESLgIXYiWVSKoj6CCrBBwj6CBHNE1hEWy21ETQqiIW1wXhPo81ZOBw2apbdVPvDsDPnP8M/5zKzECJEQKivYO8DFoAYEAGKtTpQEvhW4w3IA+tAVy2F9fgEskA8COHUL8LOKAMZoMmLQF0FewcwImmNAzPANBB18b0BFoGroNLfBiyLgI2+BMwF3XgNwCrwYsQ//BBPSRPdAoeybjE+A8ClS+Sjfnf1E5A2dW4FzoxfwWvD/XWd7oAxI24jz3gVnpS7eiEpt+KvQEL5D5qal/245zFgU+pnXzMd+Zrh9/3q5l7g3CXtTs0bgWvFffn5vDa7iKcVv2K4DS8i3cAOsAuMm8h12ovqqrVL/R3upFrRKPBgHgctvm0iSynuWNnf5bf6byy5dPKe4nukhg6XU9yW2TfsJlDpNCUX27OaP8pD4WBCzQtmX381EUeAI3Xqe6m5xoHpYAezJuJkNb9Fh0tI4+SlXhpTwJBaZ+XbCcwr+6kcPESI2uAHmAijFaMnEmYAAAAASUVORK5CYII=" /></h4>
'''
for messages in mailbox_file:
if messages['recipient'] == recipient.upper():
sender = """
<tr>
<td style="width: 63px;"><strong>DMR ID:</strong></td>
<td style="width: 292.55px; text-align: center;">""" + str(messages['dmr_id']) + """</td>
</tr>
"""
if type(messages['time']) == str:
loc_time = str(messages['time'])
if type(messages['time']) == int or type(messages['time']) == float:
loc_time = datetime.fromtimestamp(messages['time']).strftime(time_format)
if type(messages['dmr_id']) == str:
sender = """
<tr>
<td style="width: 63px;"><strong>APRS Call:</strong></td>
<td style="width: 292.55px; text-align: center;">""" + str(messages['dmr_id']) + """</td>
</tr>
"""
mail_content = mail_content + """
<table style="margin-left: auto; margin-right: auto; width: 372.55px;" border="1">
<tbody>
<tr>
<td style="width: 63px;"><strong>From:</strong></td>
<td style="text-align: center; width: 292.55px;"><strong>""" + messages['call'] + """</strong></td>
</tr>
""" + sender + """
<tr>
<td style="width: 63px;"><strong>Time:</strong></td>
<td style="width: 292.55px; text-align: center;">""" + loc_time + """</td>
</tr>
<tr>
<td style="width: 63px;"><strong>Message:</strong></td>
<td style="width: 292.55px; text-align: center;"><strong>""" + messages['message'] + """</strong></td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
"""
return render_template('generic.html', title = dashboard_title, logo = logo, content = Markup(mail_content))
@app.route('/bulletin_rss.xml')
def bb_rss():
try:
dash_bb = ast.literal_eval(os.popen('cat ' + bb_file).read())
post_data = ''
rss_header = """<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>""" + dashboard_title + """ - Bulletin Board Feed</title>
<link>""" + rss_link + """</link>
<description>This is the Bulletin Board feed from """ + dashboard_title + """</description>"""
for entry in dash_bb:
if type(entry['time']) == str:
loc_time = str(entry['time'])
if type(entry['time']) == int or type(entry['time']) == float:
loc_time = datetime.fromtimestamp(entry['time']).strftime(time_format)
post_data = post_data + """
<item>
<title>""" + entry['call'] + ' - ' + str(entry['dmr_id']) + """</title>
<link>""" + rss_link + """</link>
<description>""" + entry['bulletin'] + """ - """ + loc_time + """</description>
<pubDate>""" + datetime.fromtimestamp(entry['time']).strftime('%a, %d %b %y') +"""</pubDate>
</item>
"""
return Response(rss_header + post_data + "\n</channel>\n</rss>", mimetype='text/xml')
except Exception as e:
#return str('<h1 style="text-align: center;">No data</h1>')
return str(e)
@app.route('/mailbox_rss')
def mail_rss():
mailbox_file = ast.literal_eval(os.popen('cat ' + the_mailbox_file).read())
post_data = ''
recipient = request.args.get('recipient').upper()
rss_header = """<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>""" + dashboard_title + """ - Mailbox Feed for """ + recipient + """</title>
<link>""" + rss_link + """</link>
<description>This is a Mailbox feed from """ + dashboard_title + """ for """ + recipient + """.</description>"""
for entry in mailbox_file:
if type(entry['time']) == str:
loc_time = str(entry['time'])
if type(entry['time']) == int or type(entry['time']) == float:
loc_time = datetime.fromtimestamp(entry['time']).strftime(time_format)
if entry['recipient'] == recipient:
post_data = post_data + """
<item>
<title>""" + entry['call'] + ' - ' + str(entry['dmr_id']) + """</title>
<link>""" + rss_link + """</link>
<description>""" + entry['message'] + """ - """ + loc_time + """</description>
<pubDate>""" + datetime.fromtimestamp(entry['time']).strftime('%a, %d %b %y') +"""</pubDate>
</item>
"""
return Response(rss_header + post_data + "\n</channel>\n</rss>", mimetype='text/xml')
if __name__ == '__main__':
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (usually gps_data.cfg)')
cli_args = arg_parser.parse_args()
parser = ConfigParser()
if not cli_args.CONFIG_FILE:
print('\n\nMust specify a config file with -c argument.\n\n')
parser.read(cli_args.CONFIG_FILE)
###### Definitions #####
# Title of the Dashboard
dashboard_title = parser.get('GPS_DATA', 'DASHBOARD_TITLE')
# Logo used on dashboard page
logo = parser.get('GPS_DATA', 'LOGO')
dash_port = int(parser.get('GPS_DATA', 'DASH_PORT'))
# IP to run server on
dash_host = parser.get('GPS_DATA', 'DASH_HOST')
#Description of dashboard to show on main page
description = parser.get('GPS_DATA', 'DESCRIPTION')
# The following will generate a help page for your users.
# Data call type
if parser.get('GPS_DATA', 'CALL_TYPE') == 'unit':
data_call_type = 'Private Call'
if parser.get('GPS_DATA', 'CALL_TYPE') == 'group':
data_call_type = 'Group Call'
if parser.get('GPS_DATA', 'CALL_TYPE') == 'both':
data_call_type = 'Private or Group Call'
# DMR ID of GPS/Data application
data_call_id = parser.get('GPS_DATA', 'DATA_DMR_ID')
# Default APRS ssid
aprs_ssid = parser.get('GPS_DATA', 'USER_APRS_SSID')
# Gateway contact info displayed on about page.
contact_name = parser.get('GPS_DATA', 'CONTACT_NAME')
contact_call = parser.get('GPS_DATA', 'CONTACT_CALL')
contact_email = parser.get('GPS_DATA', 'CONTACT_EMAIL')
contact_website = parser.get('GPS_DATA', 'CONTACT_WEBSITE')
# Center dashboard map over these coordinates
map_center_lat = float(parser.get('GPS_DATA', 'MAP_CENTER_LAT'))
map_center_lon = float(parser.get('GPS_DATA', 'MAP_CENTER_LON'))
zoom_level = int(parser.get('GPS_DATA', 'ZOOM_LEVEL'))
map_theme = parser.get('GPS_DATA', 'MAP_THEME')
# Time format for display
time_format = parser.get('GPS_DATA', 'TIME_FORMAT')
# RSS feed link, shows in the link section of each RSS item.
rss_link = parser.get('GPS_DATA', 'RSS_LINK')
# Default APRS comment for users.
default_comment = parser.get('GPS_DATA', 'USER_APRS_COMMENT')
# DO NOT MODIFY BELOW HERE.
bb_file = parser.get('GPS_DATA', 'BULLETIN_BOARD_FILE')
loc_file = parser.get('GPS_DATA', 'LOCATION_FILE')
emergency_sos_file = parser.get('GPS_DATA', 'EMERGENCY_SOS_FILE')
the_mailbox_file = parser.get('GPS_DATA', 'MAILBOX_FILE')
user_settings_file = parser.get('GPS_DATA', 'USER_SETTINGS_FILE')
########################
app.run(debug = True, port=dash_port, host=dash_host)

View File

@ -47,3 +47,17 @@ contact_name = 'your name'
contact_call = 'N0CALL'
contact_email = 'email@example.org'
contact_website = 'https://hbl.ink'
# Time format for display
time_format = '%H:%M:%S - %m/%d/%y'
# Center dashboard map over these coordinates
map_center = (47.00, -120.00)
zoom_level = 7
# List and preview of some map themes at http://leaflet-extras.github.io/leaflet-providers/preview/
# The following are options for map themes and just work, you should use one of these: “OpenStreetMap”, “Stamen” (Terrain, Toner, and Watercolor),
map_theme = 'Stamen Toner'
# RSS feed link, shows in the link section of each RSS item.
rss_link = 'http://localhost:8092'

View File

@ -1,5 +1,5 @@
<div>
<hr />
<div style="text-align: center;">Dashboard created by KF7EEL - <a href="https://github.com/kf7eel/hblink3">https://github.com/kf7eel/hblink3</a><br />HBLink created by N0MJS.<br /><a href="https://github.com/HBLink-org">HBLink GitHub page</a><br />This is branch: <strong>gps<strong></div>
<div style="text-align: center;">Dashboard created by KF7EEL - <a href="https://kf7eel.github.io/hblink3/">https://github.com/kf7eel/hblink3</a><br />HBLink created by N0MJS.<br /><a href="https://github.com/HBLink-org">HBLink GitHub page</a><br />This is branch: <strong>gps<strong></div>
</body>
</html>

View File

@ -0,0 +1,5 @@
{% include 'page.html' %}
{% include 'header.html' %}
{{content}}
{% include 'footer.html' %}

View File

@ -4,10 +4,14 @@
<table style="width: 500px; margin-left: auto; margin-right: auto;" border="black" cellspacing="3" cellpadding="3">
<tbody>
<tr>
<td style="text-align: center;"><button onclick="window.location.href='/';"> D-APRS Dashboard </button></td>
<td style="text-align: center;"><button onclick="window.location.href='/help';"> D-APRS Help </button></td>
<td style="text-align: center;"><button onclick="window.location.href='/';"> Dashboard Home </button></td>
<td style="text-align: center;"><button onclick="window.location.href='/map';"> Station Map </button></td>
<td style="text-align: center;"><button onclick="window.location.href='/mailbox';"> The Mailbox </button></td>
<td style="text-align: center;"><button onclick="window.location.href='/user';"> User Settings </button></td>
<td style="text-align: center;"><button onclick="window.location.href='/help';"> Dashboard Help </button></td>
<td style="text-align: center;"><button onclick="window.location.href='/about';"> Gateway Contact </button></td>
</tr>
</tbody>
</table>
<hr />

View File

@ -51,6 +51,11 @@
<td><em><code>@SSID 7</code></em></td>
</tr>
<tr>
<td><strong>@PIN</strong></td>
<td>Set a PIN. This is used for changing your APRS settings ia the dashboard. You must set this for each DMR ID you wish to change via the dashboard.</td>
<td><em><code>@PIN 1234</code></em></td>
</tr>
<tr>
<td><strong>@MH</strong></td>
<td>Set you location by maidenhead grid square. Designed for radios with no GPS or that are not compatable yet.</td>
<td><em><code>@MH DN97uk</code></em></td>
@ -69,11 +74,44 @@
<td><strong>[EMAIL ADDRESS] E-[MESSAGE]</strong></td>
<td>Send an email to an email address.</td>
<td><em><code>test@example.org E-This is a test.</code></em></td>
</tr>
</tr>
<tr>
<td><strong>@[RECIPIENT CALLSIGN] M-[MESSAGE]</strong></td>
<td>Post a message to the Mailbox.</td>
<td><em>@N0CALL M-This is a test.</em></td>
</tr>
<tr>
<td><strong>@REM MAIL</strong></td>
<td>Remove all mail addressed to you.</td>
<td><em>@REM MAIL</em></td>
</tr>
<tr>
<td><strong>@SOS</strong></td>
<td>EMERGENCY activation displays a banner on the main dashboard page.</td>
<td><em>THIS IS AN EMERGENCY. @SOS</em></td>
</tr>
<tr>
<td><strong>@NOTICE</strong></td>
<td>Used to prominantly display a notice, similar to EMERGENCY activation.</td>
<td><em>@NOTICE Server going down at 1800</em></td>
</tr>
<tr>
<td><strong>@REM SOS</strong></td>
<td>Removes an EMERGENCY or notice.</td>
<td><em>@REM SOS</em></td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p><strong>Commands should be in upper case.</strong> Send an SMS to <strong>{{data_call_id}}</strong> as a <strong>{{data_call_type}}</strong> with the desired command followed by the value. For example, to change your icon to a dog, the command would be <code>@ICON /p</code> (see the icon table for values). Changing your SSID is as simple as <code>@SSID 7</code>, and <code>@COM Testing 123</code> will change the comment.</p>
<p>Sending <code>@BB Test</code> will result in a post to the bulletin board with the messaage of "Test".</p>
<p><strong>To remove any of the stored values, just send the appropriate command without any input.</strong> <code>@COM</code> will remove the stored comment, <code>@ICON</code> will remove the stored icon, and <code>@ICON</code> will remove the strored icon. Any position reports sent will have the default settings.</p>
<h3 style="text-align: center;">EMERGENCY Activation</h3>
<p>The EMERGENCY Activation function will display a prominant banner accross the main dashboard page. The banner contains a message, time, callsign of station, and a link to the last known location of the station. This feature is intended to be used in situations where a user is in distress. To trigger an EMERGENCY activation, simply send an SMS to <strong>{{data_call_id}}</strong> with <strong>@SOS</strong> in the message. The <strong>@SOS</strong> command can be anywhere in the message. A less intimidating banner can be displayed for testing, or other purposes, by using <strong>@NOTICE</strong>. To remove the EMERGENCY activation, simply send the <strong>@REM SOS</strong> command.</p>
<h3 style="text-align: center;">The Map</h3>
<p>This dashboard now has a built in map. The map will display the last know location of stations and some previous plots from them. You can track a station by clicking on the red pin, and selecting the <strong>Track Station</strong> link. When tracking a station, you have a few options for a refresh rate, located in a drop down menu.</p>
<h3 style="text-align: center;">The Mailbox</h3>
<p>The Mailbox is a place where users can leave messages via DMR SMS. A user can leave a message for someone else by sending a formatted SMS to <strong>{{data_call_id}}</strong>. See the table above for a syntax example of posting a message to the Mailbox. The message recipient can then use the mailbox to check for messages. Message recipients can delete all of their messages by sending the command <strong>@REM MAIL</strong>. There is no way to remove a single message, it's all or none.</p>
{% include 'footer.html' %}

View File

@ -16,13 +16,25 @@ p {
max-width: 1200px;
min-width: 1200px;
margin: auto;
}
.emergency_button {
display: block;
width: 100%;
border: none;
background-color: ##ff0000;
padding: 14px 28px;
font-size: 16px;
cursor: pointer;
text-align: center;
}
</style>
</head>
<div class="content">
<body>
{% include 'header.html' %}
{{emergency}}
<p style="text-align: center;"><em>Page automatically reloads every 2 minutes.</em></p>
<h4 style="text-align: center;"><a href="bulletin_rss.xml"><em>Bulletin Board RSS Feed</em></a><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH5QIcFBAOXAevLAAAAZZJREFUSMftlbtKA0EUhj8jWhi8gaIEC29oxEoRFESLgIXYiWVSKoj6CCrBBwj6CBHNE1hEWy21ETQqiIW1wXhPo81ZOBw2apbdVPvDsDPnP8M/5zKzECJEQKivYO8DFoAYEAGKtTpQEvhW4w3IA+tAVy2F9fgEskA8COHUL8LOKAMZoMmLQF0FewcwImmNAzPANBB18b0BFoGroNLfBiyLgI2+BMwF3XgNwCrwYsQ//BBPSRPdAoeybjE+A8ClS+Sjfnf1E5A2dW4FzoxfwWvD/XWd7oAxI24jz3gVnpS7eiEpt+KvQEL5D5qal/245zFgU+pnXzMd+Zrh9/3q5l7g3CXtTs0bgWvFffn5vDa7iKcVv2K4DS8i3cAOsAuMm8h12ovqqrVL/R3upFrRKPBgHgctvm0iSynuWNnf5bf6byy5dPKe4nukhg6XU9yW2TfsJlDpNCUX27OaP8pD4WBCzQtmX381EUeAI3Xqe6m5xoHpYAezJuJkNb9Fh0tI4+SlXhpTwJBaZ+XbCcwr+6kcPESI2uAHmAijFaMnEmYAAAAASUVORK5CYII=" /></h4>
<table>
<tbody>
<tr>

View File

@ -0,0 +1,8 @@
{% include 'page.html' %}
{% include 'header.html' %}
<p style="text-align: center;"><span style="color: #ff0000;"><strong>Red</strong></span>: Last location received. <span style="color: #0000ff;"><strong>Blue</strong></span>: Location history.</p>
<p style="text-align: center;">&nbsp;</p>
<div><p align="center"><iframe style="border: none;" title="Map" src="/view_map?track=" width="1100" height="700"></iframe></p></div>
<p>&nbsp;</p>
{% include 'footer.html' %}