first commit

This commit is contained in:
Xavier 2022-12-02 13:27:53 +01:00
commit ddc8194bd6
18 changed files with 2115 additions and 0 deletions

27
README.md Executable file
View File

@ -0,0 +1,27 @@
DAPRS 1.0
============================
## Version en production 1.0
## 1.0
placer:<br>
le répertoire dashboard dans FreeDMR/script<br>
puis les fichiers:<br>
tout les fichier gps_* dans FreeDMR
puis éditer fichier pour le service hbdaprs.service
changer
/opt/FreeDMR/gps_data.py par votre chemin
puis copier le fichier hbdaprs.service
dans
/lib/systemd/system
puis sudo systemctl enable hbdaprs.service
73
Xavier

131
dashboard/dashboard.py Normal file
View File

@ -0,0 +1,131 @@
###############################################################################
# HBLink - Copyright (C) 2020 Cortney T. Buffington, N0MJS <n0mjs@me.com>
# 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
###############################################################################
'''
This is a web dashboard for the GPS/Data application.
'''
from flask import Flask, render_template
import ast, os
from dashboard_settings import *
app = Flask(__name__)
tbl_hdr = '''
<fieldset style="border-radius: 8px; background-color:#e0e0e0e0; text-algin: lef; margin-left:15px;margin-right:15px;font-size:14px;border-top-left-radius: 10px; border-top-right-radius: 10px;border-bottom-left-radius: 10px; border-bottom-right-radius: 10px;">
<table style="width:100%; font: 10pt arial, sans-serif">
'''
tbl_ftr = '''
</table></fieldset>
'''
def get_loc_data():
try:
dash_loc = ast.literal_eval(os.popen('cat /tmp/gps_data_user_loc.txt').read())
tmp_loc = ''
loc_hdr = '''
<TR style=" height: 32px;font: 10pt arial, sans-serif; background-color:#CDCDCD; color:black">
<TH>Indicatif</TH><TH>Latitude</TH><TH>Longitude</TH><TH>Date et heure</TH></TR>
'''
cpt=0
for e in dash_loc:
cpt = cpt + 1
tmp_color="#f9f9f9f9"
if cpt >= 2:
tmp_color="#d9d9d9d9"
cpt = 0
tmp_loc = tmp_loc + '''
<TR style="background-color:''' + str(tmp_color) + ''';">
<TD><strong>''' + e['call'] + '''</strong></TD>
<TD><strong>&nbsp;''' + str(e['lat']) + '''&nbsp;</strong></TD>
<TD><strong>&nbsp;''' + str(e['lon']) + '''&nbsp;</strong></TD>
<TD>&nbsp;''' + e['time'] + '''&nbsp;</TD>
</TR>
'''
return str(str('<h1 style="text-align: center;">Positions Reçues</h1>') + tbl_hdr + loc_hdr + tmp_loc + tbl_ftr)
except:
return str('<h1 style="text-align: center;">Pas de données</h1>')
def get_log_data():
#try:
dash_log = (os.popen('cat /tmp/gps_data.log | nl | sort -n -r | cut -f2').read())
dash_log = dash_log.replace('\n', '<br>')
return str('<font color="#fff">'+dash_log+'</font>')
#except:
# return str('<font color="#fff">Pas de données</font>')
def get_bb_data():
try:
dash_bb = ast.literal_eval(os.popen('cat /tmp/gps_data_user_bb.txt').read())
tmp_bb = ''
bb_hdr = '''
<TR style=" height: 32px;font: 10pt arial, sans-serif; background-color:#CDCDCD; color:black">
<TH>Indicatif</TH><TH>DMR id</TH><TH>Bulletin</TH><TH>Date et heure</TH></TR>
'''
for e in dash_bb:
tmp_bb = tmp_bb + '''
<TR style="background-color:#f9f9f9f9;">
<TD><strong>&nbsp;''' + e['call'] + '''&nbsp;</strong></TD>
<TD>''' + str(e['dmr_id']) + '''</TD>
<TD><strong>&nbsp;''' + e['bulliten'] + '''&nbsp;</strong></TD>
<TD>&nbsp;''' + e['time'] + '''&nbsp;</TD>
</TR>
'''
return str('<h1 style="text-align: center;">Bulletins</h1>' + tbl_hdr + bb_hdr + tmp_bb + tbl_ftr)
except:
return str('<h1 style="text-align: center;">No data</h1>')
@app.route('/')
def index():
#return get_data()
return render_template('index.html', title = dashboard_title, logo = logo)
@app.route('/bulletin_board')
def dash_bb():
return get_bb_data()
#return render_template('index.html', data = str(get_data()))
@app.route('/positions')
def dash_loc():
return get_loc_data()
#return render_template('index.html', data = str(get_data()))
@app.route('/logdaprs')
def dash_log():
return get_log_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()
return render_template('help.html', title = dashboard_title, logo = logo, description = description, data_call_type = data_call_type, data_call_id = data_call_id, aprs_ssid = aprs_ssid)
@app.route('/about/')
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)
if __name__ == '__main__':
app.run(debug = True, port=dash_port, host=dash_host)

View File

@ -0,0 +1,49 @@
###############################################################################
# HBLink - Copyright (C) 2020 Cortney T. Buffington, N0MJS <n0mjs@me.com>
# 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
###############################################################################
'''
Settings for web dashboard.
'''
# 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_title + '''.
'''
# The following will generate a help page for your users.
# Data call type
data_call_type = 'Private Call'
# DMR ID of GPS/Data application
data_call_id = '9099'
# Default APRS ssid
aprs_ssid = '15'
# Gateway contact info displayed on about page.
contact_name = 'your name'
contact_call = 'N0CALL'
contact_email = 'email@example.org'
contact_website = 'https://hbl.ink'

View File

@ -0,0 +1,49 @@
###############################################################################
# HBLink - Copyright (C) 2020 Cortney T. Buffington, N0MJS <n0mjs@me.com>
# 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
###############################################################################
'''
Settings for web dashboard.
'''
# Title of the Dashboard
dashboard_title = 'D-APRS Dashboard'
# Logo used on dashboard page
logo = 'https://monsite.com/logo.png'
# Port to run server
dash_port = 8086
# IP to run server on
dash_host = '192.168.0.1'
#Description of dashboard to show on main page
description = '''
Welcome to the ''' + dashboard_title + '''.
'''
# The following will generate a help page for your users.
# Data call type
data_call_type = 'Private Call'
# DMR ID of GPS/Data application
data_call_id = '9099'
# Default APRS ssid
aprs_ssid = '15'
# Gateway contact info displayed on about page.
contact_name = 'MYCALL'
contact_call = 'MYCALL'
contact_email = 'mail@free.fr'
contact_website = 'http://www.monsite.com'

View File

@ -0,0 +1,29 @@
{% include 'page.html' %}
{% include 'header.html' %}
<fieldset style="background-color:#e0e0e0e0;text-algin: lef; margin-left:15px;margin-right:15px;font-size:14px;border-top-left-radius: 10px; border-top-right-radius: 10px;border-bottom-left-radius: 10px; border-bottom-right-radius: 10px;">
<p>&nbsp;</p>
<p>Nous contacter.</p>
<center>
<fieldset style="border-radius: 8px; background-color:#e0e0e0e0; text-algin: lef; margin-left:15px;margin-right:15px;font-size:14px;border-top-left-radius: 10px; border-top-right-radius: 10px;border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; width:50%;">
<table style="width:80%; font: 10pt arial, sans-serif">
<tr>
<td><strong>Indicatif</strong></td>
<td>&nbsp;{{contact_call}}</td>
</tr>
<tr>
<td><strong>E-Mail</strong></td>
<td>&nbsp;{{contact_email}}</td>
</tr>
<tr>
<td><strong>Website</strong></td>
<td>&nbsp;{{contact_website}}</td>
</tr>
</table></fieldset>
</center>
<p>&nbsp;</p>
</fieldset>
{% include 'footer.html' %}

View File

@ -0,0 +1,5 @@
<div>
<hr />
<div style="text-align: center;">Copyright (c) 2016 - 2021. All rights reserved.</div>
</body>
</html>

View File

@ -0,0 +1,14 @@
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="{{logo}}" alt="Logo" width="300" height="144" /></p>
<h1 style="text-align: center;">{{title}}</h1>
<hr />
<table style="width: 1200px; margin-left: auto; margin-right: auto;" border="black" cellspacing="3" cellpadding="3">
<tbody>
<tr>
<td style="text-align: center;"><input class="button link" type="button" value="APRS DashBoard" onclick="javascript:window.location.href='http://fra1od.freeboxos.fr:85';"></td>
<td style="text-align: center;"><input class="button link" type="button" value="D-APRS DashBoard" onclick="javascript:window.location.href='/';"></td>
<td style="text-align: center;"><input class="button link" type="button" value="D-APRS Help" onclick="javascript:window.location.href='/help';"></td>
<td style="text-align: center;"><input class="button link" type="button" value="Contact" onclick="javascript:window.location.href='/about';"></td>
</tr>
</tbody>
</table>
<hr />

View File

@ -0,0 +1,96 @@
{% include 'page.html' %}
{% include 'header.html' %}
<fieldset style="border-radius: 8px; background-color:#e0e0e0e0; text-algin: lef; margin-left:15px;margin-right:15px;font-size:14px;border-top-left-radius: 10px; border-top-right-radius: 10px;border-bottom-left-radius: 10px; border-bottom-right-radius: 10px;">
<h2 style="text-align: center;">Attention:</h2>
<p><span style="color: #ff0000;"><strong>Veuillez noter que de nombreuses radios DMR (sinon toutes) NE transmettent PAS votre indicatif lors de l'envoi d'une position GPS. Il est de votre responsabilité d'identifier votre station conformément aux réglementations de votre pays.</strong></span></p>
<p>&nbsp;</p>
<p>Configurez votre radio pour envoyer des positions GPS avec les paramètres suivants:</p>
<center>
<fieldset style="border-radius: 8px; background-color:#e0e0e0e0; text-algin: lef; margin-left:15px;margin-right:15px;font-size:14px;border-top-left-radius: 10px; border-top-right-radius: 10px;border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; width:30%;">
<table style="width:100%; font: 10pt arial, sans-serif">
<TR style=" height: 32px;font: 10pt arial, sans-serif; background-color:#CDCDCD; color:black">
<TH><strong>Data Call Type</strong></TH><TH><strong>SLOT</strong></TH><TH><strong>TG ID</strong></TH></TR>
<tr>
<td style="text-align: center;">&nbsp;{{data_call_type}}</td>
<td style="text-align: center;">&nbsp;SL1 et SL2</td>
<td style="text-align: center;">&nbsp;{{data_call_id}}</td>
</tr>
</table></fieldset>
</center>
<p>&nbsp;</p>
<p>Lorsqu'une position est reçue par cette passerelle, elle extrait les coordonnées et crée un paquet de position APRS. Cette passerelle trouvera votre indicatif en fonction de l'identifiant DMR de votre radio. Il est essentiel d'avoir vos informations à jour avec Le réseaux DMR FRA et DMRNET>. Un SSID APRS prédéfini à <strong>{{aprs_ssid}}</strong> est ajouté à votre indicatif (il peut être personalisé). Le paquet de localisation APRS est ensuite téléchargé vers APRS-IS (la carte APRS). Aucune configuration ou création de compte n'est requise au préalable, il faut juste être enregistré sur le réseaux. C'est à peu près "plug and play"."</p>
<p>Par exemple, N0CALL a un ID DMR de 1234567. La radio de N0CALL envoie une position à cette passerelle avec les paramètres ci-dessus. Cette passerelle interrogera le réseaux, et la base de données pour l'ID DMR 1234567. Le résultat sera un paquet d'emplacement APRS avec la position sur la carte APRS INDICATIF-SSID&nbsp;{{aprs_ssid}}. </p>
<p>Vous pouvez modifier les paramètres APRS par défaut de votre radio par SMS.</p>
<h2 style="text-align: center;">Les commandes</h2>
<center>
<fieldset style="border-radius: 8px; background-color:#e0e0e0e0; text-algin: lef; margin-left:15px;margin-right:15px;font-size:14px;border-top-left-radius: 10px; border-top-right-radius: 10px;border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; width:70%;">
<table style="width:100%; font: 10pt arial, sans-serif">
<TR style=" height: 32px;font: 10pt arial, sans-serif; background-color:#CDCDCD; color:black">
<TH><strong>Commande</strong></TH><TH><strong>Description</strong></TH><TH><strong>Exemple</strong></TH></TR>
<tr>
<td><strong>TEST</strong></td>
<td>Tester la reception de SMS sur le dashboard.</td>
<td><em><code>TEST</code></em></td>
</tr>
<tr>
<td><strong>MAILTEST</strong></td>
<td>Tester l'envoie de mail. Attention il faut activer la fonction voir @MAIL</td>
<td><em><code>MAILTEST</code></em></td>
</tr>
<tr>
<td><strong>SOS</strong></td>
<td>Envoyer à tous un mail de SOS</td>
<td><em><code>SOS</code></em></td>
</tr>
<tr>
<td><strong>@COM</strong></td>
<td>Changer le commentaire de l'APRS.</td>
<td><em><code>@COM c'est un test.</code></em></td>
</tr>
<tr>
<td><strong>@ICON</strong></td>
<td>Changer l'icon de la position APRS. La table est par défaut /. Voir <a href="http://fra1od.fr.to:85/aprs/aide.php" rel="nofollow">http://fra1od.fr.to:85/aprs/aide.php</a> pour la liste des icon APRS.</td>
<td><em><code>@icon p</code></em></td>
</tr>
<tr>
<td><strong>@SSID</strong></td>
<td>Changer votre SSID à votre indicatif idem voir lien dans la section @ICON.</td>
<td><em><code>@SSID 7</code></em></td>
</tr>
<tr>
<td><strong>@MAIL</strong></td>
<td>Activer et transmettre son mail..</td>
<td><em><code>@MAIL mail@fai.extention</code></em></td>
</tr>
<tr>
<td><strong>@MH</strong></td>
<td>Définissez votre emplacement par carré de grille <a href="https://k7fry.com/grid/">QTH Locator</a> Attention que 6 cararctères, et respecter MAJ MIN. Conçu pour les radios sans GPS ou qui ne sont pas encore compatibles.</td>
<td><em><code>@MH JN33no</code></em></td>
</tr>
<tr>
<td><strong>@BB</strong></td>
<td>Publier un message sur le tableau de bord Web.</td>
<td><em><code>@BB c'est un bulletin de test.</code></em></td>
</tr>
<td><strong>@[CALLSIGN-SSID] A-[MESSAGE]</strong></td>
<td>Envoyer un message à une autre station via APRS</td>
<td><em><code>@N0CALL-15 A-C est un test.</code></em></td>
</tr>
</table></fieldset>
</center>
<p>&nbsp;</p>
<p>Envoyez un SMS à <strong>{{data_call_id}}</strong> comme un <strong>{{data_call_type}}</strong> avec la commande souhaitée suivie de la valeur. Par exemple, pour changer votre icône en chien, la commande serait <code>@ICON p</code> (voir le tableau des icônes pour les valeurs). Changer votre SSID est aussi simple que <code>@SSID 7</code>, et <code>@COM Testing 123</code> changera le commentaire.</p>
<p>Envoyer <code>@BB Test</code> entraînera un message sur le buletin avec le message "Test".</p>
<p><strong>Pour supprimer l'une des valeurs stockées, envoyez simplement la commande appropriée sans aucune entrée.</strong> <code>@COM</code> supprimera le commentaire stocké, <code>@ICON</code>
supprimera l'icône stockée, et <code>@ICON</code> supprimera l'icône en chaîne. Tous les rapports de position envoyés auront les paramètres par défaut.
<strong>Pour activer la notification mail: <code>@MAIL mail@fai.extention</code>. pour supprimer la notification de mail: <code>@MAIL</code>.
</p>
</fieldset>
{% include 'footer.html' %}

View File

@ -0,0 +1,160 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="120" >
<title>{{title}}</title>
<style>
body {background-color: D3D3D3;}
h1 {color: green;}
p {
padding: 10px;
margin: 20px;
}
.content {
max-width: 1200px;
min-width: 1200px;
margin: auto;
}
a:link {
color: #0066ff;
text-decoration: none;
}
/* visited link */
a:visited {
color: #0066ff;
text-decoration: none;
}
/* mouse over link */
a:hover {
color: hotpink;
text-decoration: underline;
}
/* selected link */
a:active {
color: #0066ff;
text-decoration: none;
}
.tooltip {
position: relative;
opacity: 1;
display: inline-block;
border-bottom: 1px dotted black;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 280px;
background-color: #6E6E6E;
box-shadow: 4px 4px 6px #800000;
color: #FFFFFF;
text-align: left;
border-radius: 6px;
padding: 8px 0;
left: 100%
opacity: 1;
/* Position the tooltip */
position: absolute;
z-index: 1;
}
.tooltip:hover .tooltiptext {
right: 100%
opacity: 1;
visibility: visible;
}
.button {
background-color: #356244;
border: none;
color: white;
padding: 8px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 14px;
font-weight: 500;
margin: 4px 2px;
border-radius: 8px;
box-shadow: 0px 8px 10px rgba(0,0,0,0.1);
}
.link {background-color: #356244;}
.link:hover {background-color: #3e8e41;}
.dropbtn {
background-color: #356244;
border: none;
color: white;
padding: 8px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 14px;
font-weight: 500;
margin: 4px 2px;
border-radius: 8px;
box-shadow: 0px 8px 10px rgba(0,0,0,0.1);
}
/* The container <div> - needed to position the dropdown content */
.dropdown {
position: relative;
display: inline-block;
}
/* Dropdown Content (Hidden by Default) */
.dropdown-content {
display: none;
position: absolute;
background-color: #f1f1f1;
min-width: 140px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
}
/* Links inside the dropdown */
.dropdown-content a {
color: black;
padding: 6px 16px;
text-decoration: none;
display: block;
}
/* Change color of dropdown links on hover */
.dropdown-content a:hover {background-color: #ddd;}
/* Show the dropdown menu on hover */
.dropdown:hover .dropdown-content {display: block;}
/* Change the background color of the dropdown button when the dropdown content is shown */
.dropdown:hover .dropbtn {background-color: #3e8e41;}
table, td, th {border: .5px solid #d0d0d0; padding: 2px; border-collapse: collapse; text-align:center;}
</style>
</head>
<div class="content">
<body background="https://fra1od.freeboxos.fr:4443/paper.png">
{% include 'header.html' %}
<p style="text-align: center;"><b><em>Actualisation automatique toutes les 2min.</em></b></p>
<fieldset style="background-color:#e0e0e0e0;text-algin: lef; margin-left:15px;margin-right:15px;font-size:14px;border-top-left-radius: 10px; border-top-right-radius: 10px;border-bottom-left-radius: 10px; border-bottom-right-radius: 10px;">
<table style="width:100%; font: 10pt arial, sans-serif">
<tbody>
<tr>
<td><p align="center"><iframe style="border: none;" title="Bulletin" src="bulletin_board" width="620" height="550"></iframe></p></td>
<td><p align="center"><iframe style="border: none;" title="Positions Reçues" src="positions" width="580" height="550"></iframe></p></td>
</tr>
<tr>
<td colspan="2" align="center">
<fieldset style="width: 1200px; margin-left:0px;margin-right:0px;font-size:14px;border-top-left-radius: 10px; border-top-right-radius: 10px;border-bottom-left-radius: 10px; border-bottom-right-radius: 10px;"><legend><b><font color="#000">&nbsp;.: Log DMR Data :.&nbsp;</font></b></legend>
<div style="height: 20em; text-align: left; font-size:13px; background-color: #000000; color:#b1eee9;" >
<iframe style="border: none;" title="log" src="logdaprs" width="1200" height="250">
</div>
</fieldset>
</td>
</tr>
</tbody>
</table>
</fieldset>
{% include 'footer.html' %}

View File

@ -0,0 +1,137 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{title}}</title>
<style>
body {background-color: D3D3D3;}
h1 {color: green;}
p {
padding: 10px;
margin: 20px;
}
.content {
max-width: 1200px;
min-width: 1200px;
margin: auto;
}
a:link {
color: #0066ff;
text-decoration: none;
}
/* visited link */
a:visited {
color: #0066ff;
text-decoration: none;
}
/* mouse over link */
a:hover {
color: hotpink;
text-decoration: underline;
}
/* selected link */
a:active {
color: #0066ff;
text-decoration: none;
}
.tooltip {
position: relative;
opacity: 1;
display: inline-block;
border-bottom: 1px dotted black;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 280px;
background-color: #6E6E6E;
box-shadow: 4px 4px 6px #800000;
color: #FFFFFF;
text-align: left;
border-radius: 6px;
padding: 8px 0;
left: 100%
opacity: 1;
/* Position the tooltip */
position: absolute;
z-index: 1;
}
.tooltip:hover .tooltiptext {
right: 100%
opacity: 1;
visibility: visible;
}
.button {
background-color: #356244;
border: none;
color: white;
padding: 8px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 14px;
font-weight: 500;
margin: 4px 2px;
border-radius: 8px;
box-shadow: 0px 8px 10px rgba(0,0,0,0.1);
}
.link {background-color: #356244;}
.link:hover {background-color: #3e8e41;}
.dropbtn {
background-color: #356244;
border: none;
color: white;
padding: 8px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 14px;
font-weight: 500;
margin: 4px 2px;
border-radius: 8px;
box-shadow: 0px 8px 10px rgba(0,0,0,0.1);
}
/* The container <div> - needed to position the dropdown content */
.dropdown {
position: relative;
display: inline-block;
}
/* Dropdown Content (Hidden by Default) */
.dropdown-content {
display: none;
position: absolute;
background-color: #f1f1f1;
min-width: 140px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
}
/* Links inside the dropdown */
.dropdown-content a {
color: black;
padding: 6px 16px;
text-decoration: none;
display: block;
}
/* Change color of dropdown links on hover */
.dropdown-content a:hover {background-color: #ddd;}
/* Show the dropdown menu on hover */
.dropdown:hover .dropdown-content {display: block;}
/* Change the background color of the dropdown button when the dropdown content is shown */
.dropdown:hover .dropbtn {background-color: #3e8e41;}
table, td, th {border: .5px solid #d0d0d0; padding: 2px; border-collapse: collapse; text-align:center;}
</style>
</head>
<div class="content">
<body background="https://fra1od.fr.to:4443/paper.png">

242
gps_data-SAMPLE.cfg Normal file
View File

@ -0,0 +1,242 @@
# PROGRAM-WIDE PARAMETERS GO HERE
# PATH - working path for files, leave it alone unless you NEED to change it
# PING_TIME - the interval that peers will ping the master, and re-try registraion
# - how often the Master maintenance loop runs
# MAX_MISSED - how many pings are missed before we give up and re-register
# - number of times the master maintenance loop runs before de-registering a peer
#
# ACLs:
#
# Access Control Lists are a very powerful tool for administering your system.
# But they consume packet processing time. Disable them if you are not using them.
# But be aware that, as of now, the configuration stanzas still need the ACL
# sections configured even if you're not using them.
#
# REGISTRATION ACLS ARE ALWAYS USED, ONLY SUBSCRIBER AND TGID MAY BE DISABLED!!!
#
# The 'action' May be PERMIT|DENY
# Each entry may be a single radio id, or a hypenated range (e.g. 1-2999)
# Format:
# ACL = 'action:id|start-end|,id|start-end,....'
# --for example--
# SUB_ACL: DENY:1,1000-2000,4500-60000,17
#
# ACL Types:
# REG_ACL: peer radio IDs for registration (only used on HBP master systems)
# SUB_ACL: subscriber IDs for end-users
# TGID_TS1_ACL: destination talkgroup IDs on Timeslot 1
# TGID_TS2_ACL: destination talkgroup IDs on Timeslot 2
#
# ACLs may be repeated for individual systems if needed for granularity
# Global ACLs will be processed BEFORE the system level ACLs
# Packets will be matched against all ACLs, GLOBAL first. If a packet 'passes'
# All elements, processing continues. Packets are discarded at the first
# negative match, or 'reject' from an ACL element.
#
# If you do not wish to use ACLs, set them to 'PERMIT:ALL'
# TGID_TS1_ACL in the global stanza is used for OPENBRIDGE systems, since all
# traffic is passed as TS 1 between OpenBridges
[GLOBAL]
PATH: ./
PING_TIME: 5
MAX_MISSED: 3
USE_ACL: True
REG_ACL: PERMIT:ALL
SUB_ACL: DENY:1
TGID_TS1_ACL: PERMIT:ALL
TGID_TS2_ACL: PERMIT:ALL
# NOT YET WORKING: NETWORK REPORTING CONFIGURATION
# Enabling "REPORT" will configure a socket-based reporting
# system that will send the configuration and other items
# to a another process (local or remote) that may process
# the information for some useful purpose, like a web dashboard.
#
# REPORT - True to enable, False to disable
# REPORT_INTERVAL - Seconds between reports
# REPORT_PORT - TCP port to listen on if "REPORT_NETWORKS" = NETWORK
# REPORT_CLIENTS - comma separated list of IPs you will allow clients
# to connect on. Entering a * will allow all.
#
# ****FOR NOW MUST BE TRUE - USE THE LOOPBACK IF YOU DON'T USE THIS!!!****
[REPORTS]
REPORT: True
REPORT_INTERVAL: 60
REPORT_PORT: 4323
REPORT_CLIENTS: 127.0.0.1
# SYSTEM LOGGER CONFIGURAITON
# This allows the logger to be configured without chaning the individual
# python logger stuff. LOG_FILE should be a complete path/filename for *your*
# system -- use /dev/null for non-file handlers.
# LOG_HANDLERS may be any of the following, please, no spaces in the
# list if you use several:
# null
# console
# console-timed
# file
# file-timed
# syslog
# LOG_LEVEL may be any of the standard syslog logging levels, though
# as of now, DEBUG, INFO, WARNING and CRITICAL are the only ones
# used.
#
[LOGGER]
LOG_FILE: /tmp/gps_data.log
LOG_HANDLERS: console-timed
LOG_LEVEL: DEBUG
LOG_NAME: HBlink3 GPS/Data
# DOWNLOAD AND IMPORT SUBSCRIBER, PEER and TGID ALIASES
# Ok, not the TGID, there's no master list I know of to download
# This is intended as a facility for other applcations built on top of
# HBlink to use, and will NOT be used in HBlink directly.
# STALE_DAYS is the number of days since the last download before we
# download again. Don't be an ass and change this to less than a few days.
[ALIASES]
TRY_DOWNLOAD: True
PATH: ./
PEER_FILE: peer_ids.json
SUBSCRIBER_FILE: subscriber_ids.json
TGID_FILE: talkgroup_ids.json
PEER_URL: https://www.radioid.net/static/rptrs.json
SUBSCRIBER_URL: https://www.radioid.net/static/users.json
STALE_DAYS: 1
# GPS/Data Application - by KF7EEL
# Configure the settings for the DMR GPS to APRS position application here.
#
# DATA_DMR_ID - This is the DMR ID that users send DMR GPS data.
# CALL_TYPE - gorup or unit. Group if you want users to send data to a talk group,
# unit if you want users to send data as a private call.
# USER_APRS_SSID - Default APRS SSID assigned to user APRS positions.
# USER_APRS_COMMENT - Default Comment attached to user APRS positions.
# APRS_LOGIN_CALL, PASSCODE, SERVER, and PORT - Login settings for APRS-IS.
[GPS_DATA]
DATA_DMR_ID: 9099
CALL_TYPE: unit
USER_APRS_SSID: 15
USER_APRS_COMMENT: HBLink3 D-APRS -
APRS_LOGIN_CALL: N0CALL
APRS_LOGIN_PASSCODE: 12345
APRS_SERVER: rotate.aprs2.net
APRS_PORT: 14580
# 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.
IGATE_BEACON_TIME = 45
IGATE_BEACON_COMMENT = HBLink3 D-APRS Gateway
IGATE_BEACON_ICON = /I
IGATE_LATITUDE = 0000.00N
IGATE_LONGITUDE = 00000.00W
# OPENBRIDGE INSTANCES - DUPLICATE SECTION FOR MULTIPLE CONNECTIONS
# OpenBridge is a protocol originall created by DMR+ for connection between an
# IPSC2 server and Brandmeister. It has been implemented here at the suggestion
# of the Brandmeister team as a way to legitimately connect HBlink to the
# Brandemiester network.
# It is recommended to name the system the ID of the Brandmeister server that
# it connects to, but is not necessary. TARGET_IP and TARGET_PORT are of the
# Brandmeister or IPSC2 server you are connecting to. PASSPHRASE is the password
# that must be agreed upon between you and the operator of the server you are
# connecting to. NETWORK_ID is a number in the format of a DMR Radio ID that
# will be sent to the other server to identify this connection.
# other parameters follow the other system types.
#
# ACLs:
# OpenBridge does not 'register', so registration ACL is meaningless.
# Proper OpenBridge passes all traffic on TS1.
# HBlink can extend OPB to use both slots for unit calls only.
# Setting "BOTH_SLOTS" True ONLY affects unit traffic!
# Otherwise ACLs work as described in the global stanza
##[OBP-1]
##MODE: OPENBRIDGE
##ENABLED: True
##IP:
##PORT: 62035
##NETWORK_ID: 3129100
##PASSPHRASE: password
##TARGET_IP: 1.2.3.4
##TARGET_PORT: 62035
##BOTH_SLOTS: True
##USE_ACL: True
##SUB_ACL: DENY:1
##TGID_ACL: PERMIT:ALL
# MASTER INSTANCES - DUPLICATE SECTION FOR MULTIPLE MASTERS
# HomeBrew Protocol Master instances go here.
# IP may be left blank if there's one interface on your system.
# Port should be the port you want this master to listen on. It must be unique
# and unused by anything else.
# Repeat - if True, the master repeats traffic to peers, False, it does nothing.
#
# MAX_PEERS -- maximun number of peers that may be connect to this master
# at any given time. This is very handy if you're allowing hotspots to
# connect, or using a limited computer like a Raspberry Pi.
#
# ACLs:
# See comments in the GLOBAL stanza
[MASTER-1]
MODE: MASTER
ENABLED: False
REPEAT: True
MAX_PEERS: 10
EXPORT_AMBE: False
IP:
PORT: 54000
PASSPHRASE: password
GROUP_HANGTIME: 5
USE_ACL: True
REG_ACL: DENY:1
SUB_ACL: DENY:1
TGID_TS1_ACL: PERMIT:ALL
TGID_TS2_ACL: PERMIT:ALL
# PEER INSTANCES - DUPLICATE SECTION FOR MULTIPLE PEERS
# There are a LOT of errors in the HB Protocol specifications on this one!
# MOST of these items are just strings and will be properly dealt with by the program
# The TX & RX Frequencies are 9-digit numbers, and are the frequency in Hz.
# Latitude is an 8-digit unsigned floating point number.
# Longitude is a 9-digit signed floating point number.
# Height is in meters
# Setting Loose to True relaxes the validation on packets received from the master.
# This will allow HBlink to connect to a non-compliant system such as XLXD, DMR+ etc.
#
# ACLs:
# See comments in the GLOBAL stanza
[D-APRS]
MODE: PEER
ENABLED: True
LOOSE: True
EXPORT_AMBE: False
IP:
PORT: 54071
MASTER_IP: localhost
MASTER_PORT: 54070
PASSPHRASE: passw0rd
CALLSIGN: D-APRS
RADIO_ID: 9099
RX_FREQ: 000000000
TX_FREQ: 000000000
TX_POWER: 0
COLORCODE: 1
SLOTS: 1
LATITUDE: 00.0000
LONGITUDE: 000.0000
HEIGHT: 0
LOCATION: This Server
DESCRIPTION: GPS to APRS
URL: www.github.com/kf7eel/hblink3
SOFTWARE_ID: 20170620
PACKAGE_ID: MMDVM_HBlink
GROUP_HANGTIME: 5
OPTIONS:
USE_ACL: True
SUB_ACL: DENY:1
TGID_TS1_ACL: PERMIT:ALL
TGID_TS2_ACL: PERMIT:ALL

287
gps_data.cfg Normal file
View File

@ -0,0 +1,287 @@
# PROGRAM-WIDE PARAMETERS GO HERE
# PATH - working path for files, leave it alone unless you NEED to change it
# PING_TIME - the interval that peers will ping the master, and re-try registraion
# - how often the Master maintenance loop runs
# MAX_MISSED - how many pings are missed before we give up and re-register
# - number of times the master maintenance loop runs before de-registering a peer
#
# ACLs:
#
# Access Control Lists are a very powerful tool for administering your system.
# But they consume packet processing time. Disable them if you are not using them.
# But be aware that, as of now, the configuration stanzas still need the ACL
# sections configured even if you're not using them.
#
# REGISTRATION ACLS ARE ALWAYS USED, ONLY SUBSCRIBER AND TGID MAY BE DISABLED!!!
#
# The 'action' May be PERMIT|DENY
# Each entry may be a single radio id, or a hypenated range (e.g. 1-2999)
# Format:
# ACL = 'action:id|start-end|,id|start-end,....'
# --for example--
# SUB_ACL: DENY:1,1000-2000,4500-60000,17
#
# ACL Types:
# REG_ACL: peer radio IDs for registration (only used on HBP master systems)
# SUB_ACL: subscriber IDs for end-users
# TGID_TS1_ACL: destination talkgroup IDs on Timeslot 1
# TGID_TS2_ACL: destination talkgroup IDs on Timeslot 2
#
# ACLs may be repeated for individual systems if needed for granularity
# Global ACLs will be processed BEFORE the system level ACLs
# Packets will be matched against all ACLs, GLOBAL first. If a packet 'passes'
# All elements, processing continues. Packets are discarded at the first
# negative match, or 'reject' from an ACL element.
#
# If you do not wish to use ACLs, set them to 'PERMIT:ALL'
# TGID_TS1_ACL in the global stanza is used for OPENBRIDGE systems, since all
# traffic is passed as TS 1 between OpenBridges
[GLOBAL]
PATH: ./
PING_TIME: 5
MAX_MISSED: 3
USE_ACL: True
REG_ACL: PERMIT:ALL
SUB_ACL: DENY:1
TGID_TS1_ACL: PERMIT:ALL
TGID_TS2_ACL: PERMIT:ALL
# NOT YET WORKING: NETWORK REPORTING CONFIGURATION
# Enabling "REPORT" will configure a socket-based reporting
# system that will send the configuration and other items
# to a another process (local or remote) that may process
# the information for some useful purpose, like a web dashboard.
#
# REPORT - True to enable, False to disable
# REPORT_INTERVAL - Seconds between reports
# REPORT_PORT - TCP port to listen on if "REPORT_NETWORKS" = NETWORK
# REPORT_CLIENTS - comma separated list of IPs you will allow clients
# to connect on. Entering a * will allow all.
#
# ****FOR NOW MUST BE TRUE - USE THE LOOPBACK IF YOU DON'T USE THIS!!!****
[REPORTS]
REPORT: True
REPORT_INTERVAL: 60
REPORT_PORT: 4323
REPORT_CLIENTS: 127.0.0.1
# SYSTEM LOGGER CONFIGURAITON
# This allows the logger to be configured without chaning the individual
# python logger stuff. LOG_FILE should be a complete path/filename for *your*
# system -- use /dev/null for non-file handlers.
# LOG_HANDLERS may be any of the following, please, no spaces in the
# list if you use several:
# null
# console
# console-timed
# file
# file-timed
# syslog
# LOG_LEVEL may be any of the standard syslog logging levels, though
# as of now, DEBUG, INFO, WARNING and CRITICAL are the only ones
# used.
#
[LOGGER]
LOG_FILE: /tmp/gps_data.log
LOG_HANDLERS: console-timed
#LOG_LEVEL: DEBUG
LOG_LEVEL: INFO
LOG_NAME: GPS/Data
# DOWNLOAD AND IMPORT SUBSCRIBER, PEER and TGID ALIASES
# Ok, not the TGID, there's no master list I know of to download
# This is intended as a facility for other applcations built on top of
# HBlink to use, and will NOT be used in HBlink directly.
# STALE_DAYS is the number of days since the last download before we
# download again. Don't be an ass and change this to less than a few days.
[ALIASES]
TRY_DOWNLOAD: True
PATH: ./
PEER_FILE: peer_ids.json
SUBSCRIBER_FILE: subscriber_ids_aprs.json
TGID_FILE: talkgroup_ids.json
PEER_URL:
SUBSCRIBER_URL:
STALE_DAYS: 365
# GPS/Data Application - by KF7EEL
# Configure the settings for the DMR GPS to APRS position application here.
#
# DATA_DMR_ID - This is the DMR ID that users send DMR GPS data.
# CALL_TYPE - gorup or unit. Group if you want users to send data to a talk group,
# unit if you want users to send data as a private call.
# USER_APRS_SSID - Default APRS SSID assigned to user APRS positions.
# USER_APRS_COMMENT - Default Comment attached to user APRS positions.
# APRS_LOGIN_CALL, PASSCODE, SERVER, and PORT - Login settings for APRS-IS.
[GPS_DATA]
DATA_DMR_ID: 9099
CALL_TYPE: unit
USER_APRS_SSID: 15
USER_APRS_COMMENT: D-APRS -
APRS_LOGIN_CALL: FRA1OD
APRS_LOGIN_PASSCODE: 15301
APRS_SERVER: 192.168.0.129
APRS_PORT: 14580
# 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.
IGATE_BEACON_TIME = 45
IGATE_BEACON_COMMENT = D-APRS Gateway
IGATE_BEACON_ICON = /I
IGATE_LATITUDE = 0047.53N
IGATE_LONGITUDE = 00007.11E
# OPENBRIDGE INSTANCES - DUPLICATE SECTION FOR MULTIPLE CONNECTIONS
# OpenBridge is a protocol originall created by DMR+ for connection between an
# IPSC2 server and Brandmeister. It has been implemented here at the suggestion
# of the Brandmeister team as a way to legitimately connect HBlink to the
# Brandemiester network.
# It is recommended to name the system the ID of the Brandmeister server that
# it connects to, but is not necessary. TARGET_IP and TARGET_PORT are of the
# Brandmeister or IPSC2 server you are connecting to. PASSPHRASE is the password
# that must be agreed upon between you and the operator of the server you are
# connecting to. NETWORK_ID is a number in the format of a DMR Radio ID that
# will be sent to the other server to identify this connection.
# other parameters follow the other system types.
#
# ACLs:
# OpenBridge does not 'register', so registration ACL is meaningless.
# Proper OpenBridge passes all traffic on TS1.
# HBlink can extend OPB to use both slots for unit calls only.
# Setting "BOTH_SLOTS" True ONLY affects unit traffic!
# Otherwise ACLs work as described in the global stanza
##[OBP-1]
##MODE: OPENBRIDGE
##ENABLED: True
##IP:
##PORT: 62035
##NETWORK_ID: 3129100
##PASSPHRASE: password
##TARGET_IP: 1.2.3.4
##TARGET_PORT: 62035
##BOTH_SLOTS: True
##USE_ACL: True
##SUB_ACL: DENY:1
##TGID_ACL: PERMIT:ALL
# MASTER INSTANCES - DUPLICATE SECTION FOR MULTIPLE MASTERS
# HomeBrew Protocol Master instances go here.
# IP may be left blank if there's one interface on your system.
# Port should be the port you want this master to listen on. It must be unique
# and unused by anything else.
# Repeat - if True, the master repeats traffic to peers, False, it does nothing.
#
# MAX_PEERS -- maximun number of peers that may be connect to this master
# at any given time. This is very handy if you're allowing hotspots to
# connect, or using a limited computer like a Raspberry Pi.
#
# ACLs:
# See comments in the GLOBAL stanza
[MASTER-1]
MODE: MASTER
ENABLED: False
REPEAT: True
MAX_PEERS: 10
EXPORT_AMBE: False
IP:
PORT: 54000
PASSPHRASE: password
GROUP_HANGTIME: 5
USE_ACL: True
REG_ACL: DENY:1
SUB_ACL: DENY:1
TGID_TS1_ACL: PERMIT:ALL
TGID_TS2_ACL: PERMIT:ALL
# PEER INSTANCES - DUPLICATE SECTION FOR MULTIPLE PEERS
# There are a LOT of errors in the HB Protocol specifications on this one!
# MOST of these items are just strings and will be properly dealt with by the program
# The TX & RX Frequencies are 9-digit numbers, and are the frequency in Hz.
# Latitude is an 8-digit unsigned floating point number.
# Longitude is a 9-digit signed floating point number.
# Height is in meters
# Setting Loose to True relaxes the validation on packets received from the master.
# This will allow HBlink to connect to a non-compliant system such as XLXD, DMR+ etc.
#
# ACLs:
# See comments in the GLOBAL stanza
[D-APRS]
MODE: PEER
ENABLED: True
LOOSE: True
EXPORT_AMBE: False
IP:
PORT: 54071
MASTER_IP: localhost
MASTER_PORT: 54070
PASSPHRASE: passw0rd
CALLSIGN: D-APRS
RADIO_ID: 9099
RX_FREQ: 000000000
TX_FREQ: 000000000
TX_POWER: 0
COLORCODE: 1
SLOTS: 1
LATITUDE: 43.5300
LONGITUDE: 007.1100
HEIGHT: 0
LOCATION: FRANCE
DESCRIPTION: GPS to APRS
URL: www.openpmr.c.la
SOFTWARE_ID: 20210116
PACKAGE_ID: MMDVM_HBlink
GROUP_HANGTIME: 5
OPTIONS:
USE_ACL: True
SUB_ACL: DENY:1
TGID_TS1_ACL: PERMIT:ALL
TGID_TS2_ACL: PERMIT:ALL
DEFAULT_UA_TIMER: 10
SINGLE_MODE: True
VOICE_IDENT: True
TS1_STATIC:
TS2_STATIC:
DEFAULT_REFLECTOR: 0
[D-APRS-NODEDMR]
MODE: PEER
ENABLED: True
LOOSE: True
EXPORT_AMBE: False
IP:
PORT: 54074
MASTER_IP: 192.168.0.1
MASTER_PORT: 62030
PASSPHRASE: password
CALLSIGN: D-APRS
RADIO_ID: 9099
RX_FREQ: 000000000
TX_FREQ: 000000000
TX_POWER: 0
COLORCODE: 1
SLOTS: 1
LATITUDE: 43.5300
LONGITUDE: 007.1100
HEIGHT: 0
LOCATION: FRANCE
DESCRIPTION: GPS to APRS
URL: www.openpmr.c.la
SOFTWARE_ID: 20210116
PACKAGE_ID: MMDVM_HBlink
GROUP_HANGTIME: 5
OPTIONS:
USE_ACL: True
SUB_ACL: DENY:1
TGID_TS1_ACL: PERMIT:9099
TGID_TS2_ACL: PERMIT:9099
DEFAULT_UA_TIMER: 10
SINGLE_MODE: True
VOICE_IDENT: True
TS1_STATIC:
TS2_STATIC:
DEFAULT_REFLECTOR: 0

804
gps_data.py Normal file
View File

@ -0,0 +1,804 @@
#!/usr/bin/env python3
#
###############################################################################
# HBLink - Copyright (C) 2020 Cortney T. Buffington, N0MJS <n0mjs@me.com>
# GPS/Data - Copyright (C) 2020 Eric Craw, KF7EEL <kf7eel@qsl.net>
# Modification 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
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
###############################################################################
'''
This is a GPS and Data application. It decodes and reassambles DMR GPS packets and
uploads them th APRS-IS.
'''
# Python modules we need
import sys
from bitarray import bitarray
from time import time
from importlib import import_module
from types import ModuleType
# Twisted is pretty important, so I keep it separate
from twisted.internet.protocol import Factory, Protocol
from twisted.protocols.basic import NetstringReceiver
from twisted.internet import reactor, task
# Things we import from the main hblink module
from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, config_reports, mk_aliases, acl_check
from dmr_utils3.utils import bytes_3, int_id, get_alias
from dmr_utils3 import decode, bptc, const
import config
import log
import const
# The module needs logging logging, but handlers, etc. are controlled by the parent
import logging
logger = logging.getLogger(__name__)
# Other modules we need for data and GPS
from bitarray import bitarray
from binascii import b2a_hex as ahex
import re
##from binascii import a2b_hex as bhex
import aprslib
import datetime
from bitarray.util import ba2int as ba2num
from bitarray.util import ba2hex as ba2hx
import codecs
import time
#Needed for working with NMEA
import pynmea2
# Modules for executing commands/scripts
import os
from gps_functions import cmd_list
# Module for maidenhead grids
import maidenhead as mh
#Modules for APRS settings
import ast
from pathlib import Path
#For Mail
import smtplib, ssl
# Does anybody read this stuff? There's a PEP somewhere that says I should do this.
__author__ = 'Cortney T. Buffington, N0MJS; Eric Craw, KF7EEL'
__copyright__ = 'Copyright (c) 2020 Cortney T. Buffington'
__credits__ = 'Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT'
__license__ = 'GNU GPLv3'
__maintainer__ = 'Eric Craw, KF7EEL'
__email__ = 'kf7eel@qsl.net'
__status__ = 'pre-alpha'
# Known to work with: AT-D878
# 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)
##################################################################################################
# Headers for GPS by model of radio:
# AT-D878 - Compressed UDP
# MD-380 - Unified Data Transport
hdr_type = ''
btf = ''
ssid = ''
# From dmr_utils3, modified to decode entire packet. Works for 1/2 rate coded data.
def decode_full(_data):
binlc = bitarray(endian='big')
binlc.extend([_data[136],_data[121],_data[106],_data[91], _data[76], _data[61], _data[46], _data[31]])
binlc.extend([_data[152],_data[137],_data[122],_data[107],_data[92], _data[77], _data[62], _data[47], _data[32], _data[17], _data[2] ])
binlc.extend([_data[123],_data[108],_data[93], _data[78], _data[63], _data[48], _data[33], _data[18], _data[3], _data[184],_data[169]])
binlc.extend([_data[94], _data[79], _data[64], _data[49], _data[34], _data[19], _data[4], _data[185],_data[170],_data[155],_data[140]])
binlc.extend([_data[65], _data[50], _data[35], _data[20], _data[5], _data[186],_data[171],_data[156],_data[141],_data[126],_data[111]])
binlc.extend([_data[36], _data[21], _data[6], _data[187],_data[172],_data[157],_data[142],_data[127],_data[112],_data[97], _data[82] ])
binlc.extend([_data[7], _data[188],_data[173],_data[158],_data[143],_data[128],_data[113],_data[98], _data[83]])
#This is the rest of the Full LC data -- the RS1293 FEC that we don't need
# This is extremely important for SMS and GPS though.
binlc.extend([_data[68],_data[53],_data[174],_data[159],_data[144],_data[129],_data[114],_data[99],_data[84],_data[69],_data[54],_data[39]])
binlc.extend([_data[24],_data[145],_data[130],_data[115],_data[100],_data[85],_data[70],_data[55],_data[40],_data[25],_data[10],_data[191]])
return binlc
n_packet_assembly = 0
packet_assembly = ''
final_packet = ''
#Convert DMR packet to binary from MMDVM packet and remove Slot Type and EMB Sync stuff to allow for BPTC 196,96 decoding
def bptc_decode(_data):
binary_packet = bitarray(decode.to_bits(_data[20:]))
del binary_packet[98:166]
return decode_full(binary_packet)
# Placeholder for future header id
def header_ID(_data):
hex_hdr = str(ahex(bptc_decode(_data)))
return hex_hdr[2:6]
# Work in progress, used to determine data format
## pass
def aprs_send(packet):
AIS = aprslib.IS(aprs_callsign, passwd=aprs_passcode,host=aprs_server, port=aprs_port)
AIS.connect()
AIS.sendall(packet)
AIS.close()
def dashboard_loc_write(call, lat, lon, time):
#try:
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[:20]))
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())
# 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[:20]))
user_bb_file.close()
logger.info('User bulletin entry saved.')
#logger.info(dash_bb)
# Thanks for this forum post for this - https://stackoverflow.com/questions/2579535/convert-dd-decimal-degrees-to-dms-degrees-minutes-seconds-in-python
def decdeg2dms(dd):
is_positive = dd >= 0
dd = abs(dd)
minutes,seconds = divmod(dd*3600,60)
degrees,minutes = divmod(minutes,60)
degrees = degrees if is_positive else -degrees
return (degrees,minutes,seconds)
def user_setting_write(dmr_id, setting, value):
# Open file and load as dict for modification
with open("./user_settings.txt", 'r') as f:
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': ''},{'mail': ''}]
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() == 'MAIL':
user_dict[dmr_id][4]['mail'] = 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:
user_dict_file.write(str(user_dict))
user_dict_file.close()
logger.info('User setting saved')
f.close()
packet_assembly = ''
##def retrieve_aprs_settings(_rf_src):
## user_settings = ast.literal_eval(os.popen('cat ./user_settings.txt').read())
## if int_id(_rf_src) not in user_settings:
## aprs_loc_packet = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + str(user_ssid) + '>APRS,TCPIP*:/' + str(datetime.datetime.utcnow().strftime("%H%M%Sh")) + str(loc.lat[0:7]) + str(loc.lat_dir) + '/' + str(loc.lon[0:8]) + str(loc.lon_dir) + '[' + str(round(loc.true_course)).zfill(3) + '/' + str(round(loc.spd_over_grnd)).zfill(3) + '/' + aprs_comment + ' DMR ID: ' + str(int_id(_rf_src))
## else:
## if user_settings[int_id(_rf_src)][1]['ssid'] == '':
## ssid = user_ssid
## if user_settings[int_id(_rf_src)][3]['comment'] == '':
## comment = aprs_comment + ' DMR ID: ' + str(int_id(_rf_src))
## if user_settings[int_id(_rf_src)][2]['icon'] == '':
## icon_table = '/'
## icon_icon = '['
## if user_settings[int_id(_rf_src)][2]['icon'] != '':
## icon_table = user_settings[int_id(_rf_src)][2]['icon'][0]
## icon_icon = user_settings[int_id(_rf_src)][2]['icon'][1]
## if user_settings[int_id(_rf_src)][1]['ssid'] != '':
## 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']
## return ssid, icon, comment
##
#Process Mail
def process_mail(mailtoto,msg_mail):
logger.debug('Mailto:'+str(mailtoto))
logger.debug('MsgMail:'+str(msg_mail))
try:
os.system('echo "' + msg_mail + '" > fichier.txt')
os.system('mutt -s "FRA1OD DMRD:" ' + str(mailtoto) +' < fichier.txt')
os.system('rm fichier.txt')
return (str(' Mail send'))
except:
return (str(' Mail not send'))
# 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)))
elif sms == 'TEST':
logger.info('It works!')
dashboard_bb_write(get_alias(int_id(_rf_src), subscriber_ids), int_id(_rf_src), time.strftime('%H:%M:%S - %d/%m/%y'),sms)
elif sms == 'MAILTEST':
logger.info('Mail It works!')
user_settings = ast.literal_eval(os.popen('cat ./user_settings.txt').read())
if int_id(_rf_src) in user_settings:
if user_settings[int_id(_rf_src)][4]['mail'] != '':
mailto = user_settings[int_id(_rf_src)][4]['mail']
#Send mail at int_id(_rf_src) alias get_alias(int_id(_rf_src)
error_mail = process_mail(mailto,'Vous recevez ce message du Réseau FRA1OD\nTest MAIL')
dashboard_bb_write(get_alias(int_id(_rf_src), subscriber_ids), int_id(_rf_src), time.strftime('%H:%M:%S - %d/%m/%y'),sms + str(error_mail))
else:
dashboard_bb_write(get_alias(int_id(_rf_src), subscriber_ids), int_id(_rf_src), time.strftime('%H:%M:%S - %d/%m/%y'),sms + "not active")
elif 'SOS' in sms:
with open("./user_settings.txt", 'r') as f:
user_dict = ast.literal_eval(f.read())
for x in user_dict:
logger.info('ID: ' + str(x))
logger.info('Mail to: ' + str(user_dict[x][4]['mail']))
mailto = user_dict[x][4]['mail']
if (user_dict[x][4]['mail'] !=''):
error_mail = process_mail(mailto,'Vous recevez ce message du Réseau FRA1OD\nSOS:')
logger.info('BB Mail : ' + str(error_mail))
f.close()
dashboard_bb_write(get_alias(int_id(_rf_src), subscriber_ids), int_id(_rf_src), time.strftime('%H:%M:%S - %d/%m/%y'), re.sub('@BB|@BB ','BB:',sms))
elif '@ICON' in sms:
user_setting_write(int_id(_rf_src), re.sub(' .*|@','',sms), '/' + re.sub('@ICON| ','',sms))
elif '@SSID' in 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 '@MAIL' in sms:
user_setting_write(int_id(_rf_src), re.sub(' .*|@','',sms), re.sub('@MAIL |@MAIL','',sms))
elif '@BB' in sms:
with open("./user_settings.txt", 'r') as f:
user_dict = ast.literal_eval(f.read())
for x in user_dict:
logger.info('ID: ' + str(x))
logger.info('Mail to: ' + str(user_dict[x][4]['mail']))
mailto = user_dict[x][4]['mail']
if (user_dict[x][4]['mail'] !=''):
error_mail = process_mail(mailto,re.sub('@BB|@BB ','Vous recevez ce message du Réseau FRA1OD\nBB:',sms))
logger.info('BB Mail : ' + str(error_mail))
f.close()
dashboard_bb_write(get_alias(int_id(_rf_src), subscriber_ids), int_id(_rf_src), time.strftime('%H:%M:%S - %d/%m/%y'), re.sub('@BB|@BB ','BB:',sms))
elif '@MH' in sms:
grid_square = re.sub('@MH ', '', sms)
grid_square = re.sub(' |\n|\r|\t', '', grid_square)
dashboard_bb_write(get_alias(int_id(_rf_src), subscriber_ids), int_id(_rf_src), time.strftime('%H:%M:%S - %d/%m/%y'), re.sub('@BB|@BB ','','QTH Locator : ' + grid_square))
#logger.info('grid_square='+str(grid_square))
#logger.info('len grid_square='+str(len(grid_square)))
if len(grid_square) == 6:
logger.info('Decode QTH Locator'+str(grid_square))
positionmh = mh.to_location(grid_square)
#logger.debug('positionmh='+ str(positionmh))
#logger.debug('lat='+str(mh.to_location(grid_square)[0]))
#logger.debug('lon='+str(mh.to_location(grid_square)[1]))
lat = decdeg2dms(mh.to_location(grid_square)[0])
lon = decdeg2dms(mh.to_location(grid_square)[1])
logger.debug('Latitude: ' + str(lat))
logger.debug('Longitude: ' + str(lon))
lon_dir = 'E'
lat_dir = 'N'
if lon[0] < 1:
lon_dir = 'W'
if lon[0] > 0:
lon_dir = 'E'
if lat[0] < 1:
lat_dir = 'S'
if lat[0] > 0:
lat_dir = 'N'
aprs_lat = (lat[0] * 100) + lat[1]
if (lat[2] > 1):
aprs_lat = aprs_lat + (lat[2]/100)
aprs_lat = str("%4.2f" % aprs_lat) + lat_dir
logger.debug('Latitude: ' + str(aprs_lat))
aprs_lon = (lon[0] * 100) + lon[1]
if (lon[2] > 1):
aprs_lon = aprs_lon + (lon[2]/100)
aprs_lon = str("%5.2f" % aprs_lon) + lon_dir
if ((lon[0] > -1) and (lon[0] < 1)):
aprs_lon = "0" + str(aprs_lon)
if ((lon[0] * 100) < 1000):
aprs_lon = "00" + str(aprs_lon)
if ( (lon[0] * 100) > 1000):
aprs_lon = "0" + str(aprs_lon)
logger.debug('Longitude: ' + str(aprs_lon))
logger.info(mh.to_location(grid_square))
logger.info(str(lat) + ', ' + str(lon))
logger.info('Latitude: ' + str(aprs_lat))
logger.info('Longitude: ' + str(aprs_lon))
user_settings = ast.literal_eval(os.popen('cat ./user_settings.txt').read())
if int_id(_rf_src) not in user_settings:
ssid = str(user_ssid)
icon_table = '/'
icon_icon = '['
comment = aprs_comment + ' DMR ID: ' + str(int_id(_rf_src))
else:
if user_settings[int_id(_rf_src)][1]['ssid'] == '':
ssid = user_ssid
if user_settings[int_id(_rf_src)][3]['comment'] == '':
comment = aprs_comment + ' DMR ID: ' + str(int_id(_rf_src))
if user_settings[int_id(_rf_src)][2]['icon'] == '':
icon_table = '/'
icon_icon = '['
if user_settings[int_id(_rf_src)][2]['icon'] != '':
icon_table = user_settings[int_id(_rf_src)][2]['icon'][0]
icon_icon = user_settings[int_id(_rf_src)][2]['icon'][1]
if user_settings[int_id(_rf_src)][1]['ssid'] != '':
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']
try:
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)
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 - %d/%m/%y'))
logger.info('Sent manual position QTH locator to APRS')
except:
logger.info('Exception. Not uploaded')
packet_assembly = ''
else :
pass
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)
logger.info('APRS message to ' + aprs_dest.upper() + '. Message: ' + aprs_msg)
user_settings = ast.literal_eval(os.popen('cat ./user_settings.txt').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:
ssid = user_ssid
try:
aprs_msg_pkt = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + str(ssid) + '>APHBL3,TCPIP*::' + str(aprs_dest).ljust(9).upper() + ':' + aprs_msg[0:73]
logger.info(aprs_msg_pkt)
aprslib.parse(aprs_msg_pkt)
aprs_send(aprs_msg_pkt)
logger.info('Packet sent.')
except:
logger.info('Error uploading MSG packet.')
dashboard_bb_write(get_alias(int_id(_rf_src), subscriber_ids), int_id(_rf_src), time.strftime('%H:%M:%S - %d/%m/%y'), re.sub('@|@A- ','','Send DAPRS for: ' + sms))
'''
try:
if sms in cmd_list:
logger.info('Executing command /script.')
os.popen(cmd_list[sms]).read()
packet_assembly = ''
except:
logger.info('Exception. Command possibly not in list, or other error.')
packet_assembly = ''
else:
pass
'''
################
## CLASS DATA ##
################
class DATA_SYSTEM(HBSYSTEM):
def __init__(self, _name, _config, _report):
HBSYSTEM.__init__(self, _name, _config, _report)
def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data):
# Capture data headers
#global n_packet_assembly, hdr_type, btf_6_stop
global n_packet_assembly, hdr_type
logger.info('Start:'+time.strftime('%H:%M:%S - %d/%m/%y'))
#logger.debug('Special debug for developement:')
#logger.debug('data_hex:'+ str(ahex(bptc_decode(_data))))
#logger.debug('Type: '+str(hdr_type))
#logger.debug('data8:12: '+str(ba2num(bptc_decode(_data)[8:12])))
#logger.debug('dtype_vseq=: '+str(_dtype_vseq))
#logger.debug('DST_ID=' + str(int_id(_dst_id)))
#logger.debug('CALL_TYPE=' + str(_call_type))
if type(_seq) is bytes:
pckt_seq = int.from_bytes(_seq, 'big')
else:
pckt_seq = _seq
#logger.debug('PCKT_SEQ=' + str(pckt_seq))
if int_id(_dst_id) == data_id:
#logger.info(type(_seq))
if type(_seq) is bytes:
pckt_seq = int.from_bytes(_seq, 'big')
else:
pckt_seq = _seq
# Try to classify header
# UDT header has DPF of 0101, which is 5.
# If 5 is at position 3, then this should be a UDT header for MD-380 type radios.
# Coordinates are usually in the very next block after the header, we will discard the rest.
#logger.info(ahex(bptc_decode(_data)[0:10]))
if _call_type == call_type and header_ID(_data)[3] == '5' and ba2num(bptc_decode(_data)[69:72]) == 0 and ba2num(bptc_decode(_data)[8:12]) == 0 or (_call_type == 'vcsbk' and header_ID(_data)[3] == '5' and ba2num(bptc_decode(_data)[69:72]) == 0 and ba2num(bptc_decode(_data)[8:12]) == 0):
global udt_block
logger.info('MD-380 type UDT header detected. Very next packet should be location.')
hdr_type = '380'
if _dtype_vseq == 6 and hdr_type == '380' or _dtype_vseq == 'group' and hdr_type == '380':
udt_block = 1
if _dtype_vseq == 7 and hdr_type == '380':
udt_block = udt_block - 1
if udt_block == 0:
logger.info('MD-380 type packet. This should contain the GPS location.')
logger.info('Packet: ' + str(ahex(bptc_decode(_data))))
if ba2num(bptc_decode(_data)[1:2]) == 1:
lat_dir = 'N'
if ba2num(bptc_decode(_data)[1:2]) == 0:
lat_dir = 'S'
if ba2num(bptc_decode(_data)[2:3]) == 1:
lon_dir = 'E'
if ba2num(bptc_decode(_data)[2:3]) == 0:
lon_dir = 'W'
lat_deg = ba2num(bptc_decode(_data)[11:18])
lon_deg = ba2num(bptc_decode(_data)[38:46])
lat_min = ba2num(bptc_decode(_data)[18:24])
lon_min = ba2num(bptc_decode(_data)[46:52])
lat_min_dec = ba2num(bptc_decode(_data)[24:38])
lon_min_dec = ba2num(bptc_decode(_data)[52:66])
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
# Form APRS packet
# For future use below
#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('Lat: ' + str(aprs_lat) + ' Lon: ' + str(aprs_lon))
user_settings = ast.literal_eval(os.popen('cat ./user_settings.txt').read())
if int_id(_rf_src) not in user_settings:
ssid = str(user_ssid)
icon_table = '/'
icon_icon = '['
comment = aprs_comment + ' DMR ID: ' + str(int_id(_rf_src))
user_setting_write(int_id(_rf_src), 'SSID', ssid)
user_setting_write(int_id(_rf_src), 'ICON', icon_table + icon_icon)
user_setting_write(int_id(_rf_src), 'COM', comment)
else:
if user_settings[int_id(_rf_src)][1]['ssid'] == '':
ssid = user_ssid
if user_settings[int_id(_rf_src)][3]['comment'] == '':
comment = aprs_comment + ' DMR ID: ' + str(int_id(_rf_src))
if user_settings[int_id(_rf_src)][2]['icon'] == '':
icon_table = '/'
icon_icon = '['
if user_settings[int_id(_rf_src)][2]['icon'] != '':
icon_table = user_settings[int_id(_rf_src)][2]['icon'][0]
icon_icon = user_settings[int_id(_rf_src)][2]['icon'][1]
if user_settings[int_id(_rf_src)][1]['ssid'] != '':
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']
# Attempt to prevent malformed packets from being uploaded.
try:
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)
aprslib.parse(aprs_loc_packet)
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, str(loc.lat[0:7]) + str(loc.lat_dir), str(loc.lon[0:8]) + str(loc.lon_dir), time.strftime('%H:%M:%S - %d/%m/%y'))
logger.info('Sent APRS packet')
logger.info(aprs_loc_packet)
logger.info('comment: ' + comment)
logger.info('SSID: ' + ssid)
logger.info('icon: ' + icon_table + icon_icon)
except:
logger.info('Error. Failed to send packet. Packet may be malformed.')
udt_block = 1
hdr_type = ''
else:
pass
#NMEA type packets for Anytone like radios.
elif _call_type == call_type or (_call_type == 'vcsbk' and pckt_seq > 3) or (_call_type == 'group' and pckt_seq > 3): #int.from_bytes(_seq, 'big') > 3 ):
global packet_assembly, btf
if _dtype_vseq == 6 or _dtype_vseq == 'group':
#global btf, hdr_start
global hdr_start
hdr_start = str(header_ID(_data))
logger.info('Header from ' + str(get_alias(int_id(_rf_src), subscriber_ids)) + '. DMR ID: ' + str(int_id(_rf_src)))
logger.info(ahex(bptc_decode(_data)))
logger.info('Blocks to follow: ' + str(ba2num(bptc_decode(_data)[65:72])))
btf = ba2num(bptc_decode(_data)[65:72])
# Try resetting packet_assembly
packet_assembly = ''
# Data blocks at 1/2 rate, see https://github.com/g4klx/MMDVM/blob/master/DMRDefines.h for data types. _dtype_seq defined here also
if _dtype_vseq == 7 :
#try:
btf = btf - 1
#except:
# btf = 5
#logger.info('Block#: ' + str(btf))
#logger.info('SEQ='+str(_seq))
#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
packet_assembly = ''
#if btf < btf + 1:
n_packet_assembly = n_packet_assembly + 1
packet_assembly = packet_assembly + str(bptc_decode(_data)) #str((decode_full_lc(b_packet)).strip('bitarray('))
#logger.info('packet_assembly'+str(packet_assembly))
# Use block 0 as trigger. $GPRMC must also be in string to indicate NMEA.
# This triggers the APRS upload
if btf == 0 : # _seq ==12:
final_packet = 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
#logger.info('Final packet:'+final_packet + '\n')
if '$GPRMC' in final_packet or '$GNRMC' in final_packet:
#logger.info(final_packet + '\n')
nmea_parse = re.sub('A\*.*|.*\$', '', str(final_packet))
nmea_parse = nmea_parse.replace('*','')
#logger.debug('NMEA=' +str(nmea_parse))
loc = pynmea2.parse(nmea_parse, check=False)
#logger.debug('LOC='+str(loc))
logger.info('Latitude: ' + str(loc.lat[0:7]) + str(loc.lat_dir) + ' Longitude: ' + str(loc.lon[0:8]) + str(loc.lon_dir) + ' Direction: ' + str(loc.true_course) + ' Speed: ' + str(loc.spd_over_grnd) + '\n')
# Begin APRS format and upload
##aprs_loc_packet = str(get_alias(int_id(_rf_src), subscriber_ids)) + '-' + str(user_ssid) + '>APRS,TCPIP*:/' + str(datetime.datetime.utcnow().strftime("%H%M%Sh")) + str(final_packet[29:36]) + str(final_packet[39]) + '/' + str(re.sub(',', '', final_packet[41:49])) + str(final_packet[52]) + '[/' + aprs_comment + ' DMR ID: ' + str(int_id(_rf_src))
##try:
# Disable opening file for reading to reduce "collision" or reading and writing at same time.
with open("./user_settings.txt", 'r') as f:
user_settings = ast.literal_eval(f.read())
user_settings = ast.literal_eval(os.popen('cat ./user_settings.txt').read())
if int_id(_rf_src) not in user_settings:
ssid = str(user_ssid)
icon_table = '/'
icon_icon = '['
comment = aprs_comment + ' DMR ID: ' + str(int_id(_rf_src))
user_setting_write(int_id(_rf_src), 'SSID', ssid)
user_setting_write(int_id(_rf_src), 'ICON', icon_table + icon_icon)
user_setting_write(int_id(_rf_src), 'COM', comment)
else:
if user_settings[int_id(_rf_src)][1]['ssid'] == '':
ssid = user_ssid
if user_settings[int_id(_rf_src)][3]['comment'] == '':
comment = aprs_comment + ' DMR ID: ' + str(int_id(_rf_src))
if user_settings[int_id(_rf_src)][2]['icon'] == '':
icon_table = '/'
icon_icon = '['
if user_settings[int_id(_rf_src)][2]['icon'] != '':
icon_table = user_settings[int_id(_rf_src)][2]['icon'][0]
icon_icon = user_settings[int_id(_rf_src)][2]['icon'][1]
if user_settings[int_id(_rf_src)][1]['ssid'] != '':
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']
#logger.info(retrieve_aprs_settings(_rf_src))
try:
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)
# Try parse of APRS packet. If it fails, it will not upload to APRS-IS
aprslib.parse(aprs_loc_packet)
# Float values of lat and lon. Anything that is not a number will cause it to fail.
float(loc.lat)
float(loc.lon)
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 - %d/%m/%y'))
logger.info(aprs_loc_packet)
logger.info('Sent APRS packet')
logger.info('comment: ' + comment)
logger.info('SSID: ' + ssid)
logger.info('icon: ' + icon_table + icon_icon)
except:
logger.info('Error or user settings file not found, proceeding with default settings.')
# End APRS-IS upload
# Assume this is an SMS message
elif '$GPRMC' not in final_packet or '$GNRMC' not in final_packet:
#### # Motorola type SMS header
## if '824a' in hdr_start or '024a' in hdr_start:
## logger.info('\nMotorola type SMS')
## sms = codecs.decode(bytes.fromhex(''.join(sms_hex[74:-8].split('00'))), 'utf-8')
## logger.info('\n\n' + 'Received SMS from ' + str(get_alias(int_id(_rf_src), subscriber_ids)) + ', DMR ID: ' + str(int_id(_rf_src)) + ': ' + str(sms) + '\n')
## process_sms(_rf_src, sms)
## packet_assembly = ''
## # ETSI? type SMS header
## elif '0244' in hdr_start or '8244' in hdr_start:
## logger.info('ETSI? type SMS')
## sms = codecs.decode(bytes.fromhex(''.join(sms_hex[64:-8].split('00'))), 'utf-8')
## logger.info('\n\n' + 'Received SMS from ' + str(get_alias(int_id(_rf_src), subscriber_ids)) + ', DMR ID: ' + str(int_id(_rf_src)) + ': ' + str(sms) + '\n')
## #logger.info(final_packet)
## #logger.info(sms_hex[64:-8])
## process_sms(_rf_src, sms)
## packet_assembly = ''
####
## else:
logger.info('\nSMS detected. Attempting to parse.')
#logger.info(final_packet)
#logger.info('SMS HEX='+sms_hex)
#logger.info(type(sms_hex))
logger.info('Attempting to find command...')
sms = codecs.decode(bytes.fromhex(''.join(sms_hex_string[:-8].split('00'))), 'utf-8', 'ignore')
logger.info('SMS='+ str(sms) + '\n')
msg_found = re.sub('.*\n', '', sms)
logger.info('\n\n' + 'Received SMS from ' + str(get_alias(int_id(_rf_src), subscriber_ids)) + ', DMR ID: ' + str(int_id(_rf_src)) + ': ' + str(msg_found) + '\n')
process_sms(_rf_src, msg_found)
#logger.info(bitarray(re.sub("\)|\(|bitarray|'", '', str(bptc_decode(_data)).tobytes().decode('utf-8', 'ignore'))))
#logger.info('\n\n' + 'Received SMS from ' + str(get_alias(int_id(_rf_src), subscriber_ids)) + ', DMR ID: ' + str(int_id(_rf_src)) + ': ' + str(sms) + '\n')
# Reset the packet assembly to prevent old data from returning.
hdr_start = ''
n_packet_assembly = 0
packet_assembly = ''
btf = 0
#packet_assembly = '' #logger.info(_dtype_vseq)
#logger.info(ahex(bptc_decode(_data)).decode('utf-8', 'ignore'))
#logger.info(bitarray(re.sub("\)|\(|bitarray|'", '', str(bptc_decode(_data)).tobytes().decode('utf-8', 'ignore'))))
else:
pass
#logger.debug('nbpacket:'+ str(n_packet_assembly))
#************************************************
# MAIN PROGRAM LOOP STARTS HERE
#************************************************
if __name__ == '__main__':
#global aprs_callsign, aprs_passcode, aprs_server, aprs_port, user_ssid, aprs_comment, call_type, data_id
import argparse
import sys
import os
import signal
from dmr_utils3.utils import try_download, mk_id_dict
# Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
# 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)')
parser.add_argument('-l', '--logging', action='store', dest='LOG_LEVEL', help='Override config file logging level.')
cli_args = parser.parse_args()
# Ensure we have a path for the config file, if one wasn't specified, then use the default (top of file)
if not cli_args.CONFIG_FILE:
cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/gps_data.cfg'
# Call the external routine to build the configuration dictionary
CONFIG = config.build_config(cli_args.CONFIG_FILE)
data_id = int(CONFIG['GPS_DATA']['DATA_DMR_ID'])
# Group call or Unit (private) call
call_type = CONFIG['GPS_DATA']['CALL_TYPE']
# APRS-IS login information
aprs_callsign = CONFIG['GPS_DATA']['APRS_LOGIN_CALL']
aprs_passcode = int(CONFIG['GPS_DATA']['APRS_LOGIN_PASSCODE'])
aprs_server = CONFIG['GPS_DATA']['APRS_SERVER']
aprs_port = int(CONFIG['GPS_DATA']['APRS_PORT'])
user_ssid = CONFIG['GPS_DATA']['USER_APRS_SSID']
aprs_comment = CONFIG['GPS_DATA']['USER_APRS_COMMENT']
# Start the system logger
if cli_args.LOG_LEVEL:
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
logger = log.config_logging(CONFIG['LOGGER'])
btf_6_stop = 0
logger.setLevel(logging.DEBUG)
# create file handler which logs even debug messages
fh = logging.FileHandler('/tmp/gps_data.log')
fh.setLevel(logging.DEBUG)
# create console handler with a higher log level
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
# create formatter and add it to the handlers
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# add the handlers to the logger
logger.addHandler(fh)
logger.addHandler(ch)
logger.info('\n\nCopyright (c) 2013, 2014, 2015, 2016, 2018, 2019, 2021\n\tThe Regents of the K0USY Group. All rights reserved.\n GPS and Data decoding by Eric, KF7EEL Modified by Xavier FRS2013')
logger.debug('Logging system started, anything from here on gets logged')
# Set up the signal handler
def sig_handler(_signal, _frame):
logger.info('SHUTDOWN: >>>GPS and Data Decoder<<< IS TERMINATING WITH SIGNAL %s', str(_signal))
hblink_handler(_signal, _frame)
logger.info('SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR')
reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT]:
signal.signal(sig, sig_handler)
# Create the name-number mapping dictionaries
peer_ids, subscriber_ids, talkgroup_ids = mk_aliases(CONFIG)
# INITIALIZE THE REPORTING LOOP
if CONFIG['REPORTS']['REPORT']:
report_server = config_reports(CONFIG, reportFactory)
else:
report_server = None
logger.info('(REPORT) TCP Socket reporting not configured')
# HBlink instance creation
logger.info('HBlink \'gps_data.py\' -- SYSTEM STARTING...')
for system in CONFIG['SYSTEMS']:
if CONFIG['SYSTEMS'][system]['ENABLED']:
if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE':
systems[system] = OPENBRIDGE(system, CONFIG, report_server)
else:
systems[system] = DATA_SYSTEM(system, CONFIG, report_server)
reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP'])
logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system])
reactor.run()
# John 3:16 - For God so loved the world, that he gave his only Son,
# that whoever believes in him should not perish but have eternal life.

60
gps_data_igate_beacon.py Normal file
View File

@ -0,0 +1,60 @@
#!/usr/bin/env python
#
###############################################################################
# HBLink - Copyright (C) 2020 Cortney T. Buffington, N0MJS <n0mjs@me.com>
# 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
###############################################################################
'''
This script uploads a beacon to APRS-IS for the GPS/Data Application itself, cuasing it to appear as an igate
an aprs.fi.
'''
import aprslib
import argparse
import os
import config
import time
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (usually gps_data.cfg)')
cli_args = parser.parse_args()
if not cli_args.CONFIG_FILE:
cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/gps_data.cfg'
CONFIG = config.build_config(cli_args.CONFIG_FILE)
print('''
GPS/Data Application Beacon Script by Eric, KF7EEL. https://github.com/kf7eel/hblink3 \n
This script will upload an APRS position for the GPS/Data Application. This usually causes most APRS-IS clients to see the GPS/Data Application
as an i-gate. \n
Using the following setting to upload beacon.\n
Callsign: ''' + CONFIG['GPS_DATA']['APRS_LOGIN_CALL'] + ''' - Position comment: ''' + CONFIG['GPS_DATA']['IGATE_BEACON_COMMENT'] + '''
Beacon time: ''' + CONFIG['GPS_DATA']['IGATE_BEACON_TIME'] + '''
''')
beacon_packet = CONFIG['GPS_DATA']['APRS_LOGIN_CALL'] + '>APHBL3,TCPIP*:!' + CONFIG['GPS_DATA']['IGATE_LATITUDE'] + str(CONFIG['GPS_DATA']['IGATE_BEACON_ICON'][0]) + CONFIG['GPS_DATA']['IGATE_LONGITUDE'] + str(CONFIG['GPS_DATA']['IGATE_BEACON_ICON'][1]) + '/' + CONFIG['GPS_DATA']['IGATE_BEACON_COMMENT']
#print(beacon_packet)
AIS = aprslib.IS(CONFIG['GPS_DATA']['APRS_LOGIN_CALL'], passwd=CONFIG['GPS_DATA']['APRS_LOGIN_PASSCODE'],host=CONFIG['GPS_DATA']['APRS_SERVER'], port=CONFIG['GPS_DATA']['APRS_PORT'])
while int(CONFIG['GPS_DATA']['IGATE_BEACON_TIME']) > 15:
AIS.connect()
AIS.sendall(beacon_packet)
print(beacon_packet)
AIS.close()
time.sleep(int(CONFIG['GPS_DATA']['IGATE_BEACON_TIME'])*60)

11
gps_functions.py Normal file
View File

@ -0,0 +1,11 @@
# Various configurable options used in the GPS/Data application.
# SMS message that triggers command
cmd_list = {
'COMMAND' : 'echo "command to execute"',
'UPTIME' : 'uptime'
}

14
hbdaprs.service Normal file
View File

@ -0,0 +1,14 @@
[Unit]
Description=Start DAPRS
After=multi-user.target
[Service]
ExecStart=/usr/bin/python3 /opt/FreeDMR/gps_data.py
[Install]
WantedBy=multi-user.target