Compare commits
23 Commits
6686cba26d
...
map-1
Author | SHA1 | Date | |
---|---|---|---|
c8f8f28a4a | |||
ab595ed3cb | |||
3758ac21cb | |||
260e946ab6 | |||
5fb589507e | |||
bcb0624786 | |||
4642f45adc | |||
31fb381063 | |||
8f2425290c | |||
9797f29bf2 | |||
bfa7b91446 | |||
0592f45af8 | |||
c1f58966a6 | |||
4439ae680e | |||
25bffd9b61 | |||
1894de5d86 | |||
79da9de6f7 | |||
26b4081720 | |||
50085de7db | |||
e7002c712c | |||
307c1fcd86 | |||
dc7d4ed8a3 | |||
e1211fe108 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,3 +2,5 @@
|
||||
config.ini
|
||||
*.db
|
||||
*.log
|
||||
/temp/*
|
||||
*.pyc
|
||||
|
87
api_app.py
87
api_app.py
@ -86,6 +86,19 @@ def select_frames(conn, n, url_params):
|
||||
rows = cur.fetchall()
|
||||
return rows
|
||||
|
||||
def select_stations(conn, n):
|
||||
"""
|
||||
Query rows in the stations table
|
||||
:param conn: the Connection object
|
||||
:return:
|
||||
"""
|
||||
cur = conn.cursor()
|
||||
sql = 'SELECT * FROM stations ORDER BY last_heard_unix DESC LIMIT {n}'.format(n=n)
|
||||
print(sql)
|
||||
cur.execute(sql)
|
||||
rows = cur.fetchall()
|
||||
return rows
|
||||
|
||||
@api_app.route('/')
|
||||
def index():
|
||||
|
||||
@ -113,12 +126,82 @@ def index():
|
||||
station['time_ago'] = timeago.format(station['last_heard_unix'], datetime.datetime.now())
|
||||
|
||||
|
||||
# Map stuff
|
||||
frames_locs = list(filter(lambda x: x['latitude'] != None, frames))
|
||||
# Make a GeoJSON
|
||||
geojs = json.dumps({
|
||||
"type": "FeatureCollection",
|
||||
"features":[
|
||||
{
|
||||
"type":"Feature",
|
||||
"geometry": {
|
||||
"type":"Point",
|
||||
"coordinates":[frame['longitude'], frame['latitude']],
|
||||
},
|
||||
"properties":frame,
|
||||
|
||||
} for frame in frames_locs
|
||||
]
|
||||
})
|
||||
|
||||
return render_template('index.html',
|
||||
station_call = config['Settings']['station_call'],
|
||||
station_lat = config['Settings']['station_lat'],
|
||||
station_lon = config['Settings']['station_lon'],
|
||||
frames = frames,
|
||||
stations = stations)
|
||||
stations = stations,
|
||||
geojs = geojs)
|
||||
|
||||
@api_app.route('/map')
|
||||
def map():
|
||||
|
||||
# Get the default list of frames from the API
|
||||
frames = json.loads(requests.get(config['Settings']['base_url']+"/packets").text)['data']
|
||||
|
||||
frames_locs = list(filter(lambda x: x['latitude'] != None, frames))
|
||||
|
||||
# Make a GeoJSON
|
||||
geojs = json.dumps({
|
||||
"type": "FeatureCollection",
|
||||
"features":[
|
||||
{
|
||||
"type":"Feature",
|
||||
"geometry": {
|
||||
"type":"Point",
|
||||
"coordinates":[frame['longitude'], frame['latitude']],
|
||||
},
|
||||
"properties":frame,
|
||||
|
||||
} for frame in frames_locs
|
||||
]
|
||||
})
|
||||
|
||||
# Make markers for all the frames
|
||||
# id_counter = 0
|
||||
# markers = ''
|
||||
# marker_ids = []
|
||||
# for frame in frames:
|
||||
# if frame['latitude'] != None:
|
||||
# # Create unique ID for each marker
|
||||
# idd = 'frame' + str(id_counter)
|
||||
# id_counter += 1
|
||||
|
||||
# # Create each marker
|
||||
# markers += "var {idd} = L.marker([{latitude}, {longitude}]);\
|
||||
# {idd}.addTo(map).bindTooltip('{from_ssid}', permanent=true).openTooltip();".format(idd=idd, latitude=frame['latitude'],\
|
||||
# longitude=frame['longitude'],
|
||||
# from_ssid=frame['from'],
|
||||
# created=frame['created'])
|
||||
# # Try to make a list of markers for Leaflet, but not working
|
||||
# marker_ids.append(idd)
|
||||
|
||||
|
||||
return render_template('map.html',
|
||||
station_lat = config['Settings']['station_lat'],
|
||||
station_lon = config['Settings']['station_lon'],
|
||||
station_call = config['Settings']['station_call'],
|
||||
#markers = markers,
|
||||
geojs = geojs)
|
||||
|
||||
class Packets(Resource):
|
||||
def get(self):
|
||||
@ -145,7 +228,7 @@ class Stations(Resource):
|
||||
|
||||
conn = get_db_connection()
|
||||
# Limit to number of records requested
|
||||
data = select_all_stations(conn)
|
||||
data = select_stations(conn, n = n)
|
||||
# Sort by created date, descending (https://stackoverflow.com/a/45266808)
|
||||
#data.sort(key=operator.itemgetter('created'), reverse=True)
|
||||
return {'data':data}, 200 # return data and 200 OK code
|
||||
|
7
aprs_tool.code-workspace
Normal file
7
aprs_tool.code-workspace
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
]
|
||||
}
|
@ -3,6 +3,7 @@ db_frames_fields = ("id",
|
||||
"addresse",
|
||||
"alive",
|
||||
"altitude",
|
||||
"body",
|
||||
"comment",
|
||||
"course",
|
||||
"created",
|
||||
@ -11,6 +12,7 @@ db_frames_fields = ("id",
|
||||
"frame",
|
||||
"from",
|
||||
"gpsfixstatus",
|
||||
"header_raw",
|
||||
"latitude",
|
||||
"longitude",
|
||||
"mbits",
|
||||
@ -20,6 +22,7 @@ db_frames_fields = ("id",
|
||||
"mtype",
|
||||
"object_format",
|
||||
"object_name",
|
||||
"party",
|
||||
"path",
|
||||
"phg",
|
||||
"phg_dir",
|
||||
@ -30,6 +33,7 @@ db_frames_fields = ("id",
|
||||
"posambiguity",
|
||||
"raw",
|
||||
"raw_timestamp",
|
||||
"rng",
|
||||
"speed",
|
||||
"station_call",
|
||||
"station_lat",
|
||||
@ -44,6 +48,7 @@ db_frames_fields = ("id",
|
||||
"tEQNS",
|
||||
"tPARM",
|
||||
"tUNIT",
|
||||
"type",
|
||||
"via",
|
||||
"weather",
|
||||
"wx_raw_timestamp")
|
||||
|
@ -7,6 +7,8 @@ import aprslib
|
||||
import configparser
|
||||
import time
|
||||
import logging
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
import time
|
||||
|
||||
def read_config():
|
||||
config = configparser.ConfigParser()
|
||||
@ -18,6 +20,15 @@ def get_db_connection():
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
def refresh_kiss_connection(kiss_conn):
|
||||
logging.debug("Restarting KISS connection on schedule")
|
||||
logging.debug("Stopping current connection")
|
||||
kiss_conn.stop()
|
||||
#logging.debug("Waiting 5 seconds")
|
||||
#time.sleep(5)
|
||||
logging.debug("Starting new connection")
|
||||
kiss_conn.start()
|
||||
|
||||
def main():
|
||||
|
||||
# Add the call and location of this station to the packet info
|
||||
@ -28,34 +39,56 @@ def main():
|
||||
|
||||
logging.basicConfig(filename=config['Settings']['log_path'], level=logging.DEBUG, \
|
||||
format='%(asctime)s - %(message)s')
|
||||
logging.debug('kiss_and_db.py running')
|
||||
logging.debug('============= kiss_and_db.py running =============')
|
||||
|
||||
ki = aprs.TCPKISS(host=config['Settings']['kiss_host'], port=int(config['Settings']['kiss_port']))
|
||||
ki.start()
|
||||
|
||||
#scheduler = AsyncIOScheduler()
|
||||
#scheduler.add_job(refresh_kiss_connection, 'interval', hours = 1, args = [ki])
|
||||
#scheduler.start()
|
||||
|
||||
# Make a simple frame and send it
|
||||
frame = aprs.APRSFrame.ui(
|
||||
destination="APZ001",
|
||||
source=config['Settings']['mycall'],
|
||||
path=["WIDE1-1"],
|
||||
info=b">Hello World!",
|
||||
)
|
||||
# frame = aprs.APRSFrame.ui(
|
||||
# destination="APZ001",
|
||||
# source=config['Settings']['mycall'],
|
||||
# path=["WIDE1-1"],
|
||||
# info=b">Hello World!",
|
||||
# )
|
||||
#ki.write(frame)
|
||||
|
||||
# Watch for new packets to come in
|
||||
while True:
|
||||
conn = get_db_connection()
|
||||
for frame in ki.read(min_frames=1):
|
||||
logging.debug("New packet, trying to parse")
|
||||
logging.debug(str(frame))
|
||||
try:
|
||||
try:
|
||||
a = aprslib.parse(str(frame))
|
||||
except Exception as error:
|
||||
logging.error("Error with aprslib:", exc_info = error)
|
||||
else:
|
||||
a['station_call'] = config['Settings']['station_call']
|
||||
a['station_lat'] = config['Settings']['station_lat']
|
||||
a['station_lon'] = config['Settings']['station_lon']
|
||||
a['created_unix'] = int(time.time())
|
||||
print(a)
|
||||
a['party'] = 1
|
||||
|
||||
# Make this a string and deal with it later (probably a mistake)
|
||||
a['path'] = str(a['path'])
|
||||
# Process 3rd-party data
|
||||
if 'subpacket' in a:
|
||||
b = a['subpacket'] # make a copy
|
||||
a['subpacket'] = str(a['subpacket']) # turn the original to a string
|
||||
b['party'] = 3
|
||||
b['path'] = str(b['path'])
|
||||
b['header_raw'] = a['raw'].split("}", 1)[0] # just the first part
|
||||
b['station_call'] = config['Settings']['station_call']
|
||||
b['station_lat'] = config['Settings']['station_lat']
|
||||
b['station_lon'] = config['Settings']['station_lon']
|
||||
b['created_unix'] = int(time.time())
|
||||
#logging.debug(a['path'])
|
||||
# Store true/false as 1/0
|
||||
if 'alive' in a:
|
||||
if a['alive'] == True:
|
||||
@ -65,12 +98,23 @@ def main():
|
||||
# Build an INSERT statement based on the fields we have from the frame
|
||||
attrib_names = ', '.join('"%s"' % w for w in a.keys())
|
||||
attrib_values = ", ".join("?" * len(a.keys()))
|
||||
|
||||
logging.debug(attrib_names)
|
||||
logging.debug(a.values())
|
||||
if 'subpacket' in a:
|
||||
# 3rd-party
|
||||
b_attrib_names = ', '.join('"%s"' % w for w in b.keys())
|
||||
b_attrib_values = ", ".join("?" * len(b.keys()))
|
||||
logging.debug(b_attrib_names)
|
||||
logging.debug(b.values())
|
||||
|
||||
try:
|
||||
logging.debug("Inserting into database")
|
||||
# Insert data
|
||||
sql = "INSERT INTO frames ("+attrib_names+") VALUES ("+attrib_values+")"
|
||||
conn.execute(sql, list(a.values()))
|
||||
if 'subpacket' in a:
|
||||
b_sql = "INSERT INTO frames ("+b_attrib_names+") VALUES ("+b_attrib_values+")"
|
||||
conn.execute(b_sql, list(b.values()))
|
||||
logging.debug("Frames table updated")
|
||||
# TODO update stations table here
|
||||
# Original intent was to include the id from the frames table,
|
||||
@ -88,6 +132,15 @@ def main():
|
||||
last_heard_unix = excluded.last_heard_unix;"
|
||||
# Insert/update data
|
||||
conn.execute(query3)
|
||||
if 'subpacket' in a:
|
||||
b_station_update = "'"+b['from'] +"', '"+ str(b['created_unix']) +"', '1'"
|
||||
b_query3 = "INSERT INTO stations ([from], last_heard_unix, count) \
|
||||
VALUES("+b_station_update+") \
|
||||
ON CONFLICT([from]) \
|
||||
DO UPDATE SET count = count + 1,\
|
||||
last_heard_unix = excluded.last_heard_unix;"
|
||||
# Insert/update data
|
||||
conn.execute(b_query3)
|
||||
logging.debug("Station table updated")
|
||||
conn.commit()
|
||||
#except:
|
||||
@ -97,12 +150,12 @@ def main():
|
||||
# "5 minutes" also works
|
||||
#conn.execute("DELETE FROM frames WHERE created < DATETIME('now', '"+config['Settings']['keep_time']+"')")
|
||||
#conn.commit()
|
||||
except:
|
||||
except Exception as error:
|
||||
#print("Error with SQLite!")
|
||||
logging.error("Error with SQLite!")
|
||||
except:
|
||||
logging.error("Error with SQLite!", exc_info = error)
|
||||
except Exception as error:
|
||||
#print("Frame could not be parsed.")
|
||||
logging.error("Frame could not be parsed.")
|
||||
logging.error("Frame could not be parsed:", exc_info = error)
|
||||
|
||||
|
||||
conn.close()
|
||||
|
@ -5,6 +5,7 @@ CREATE TABLE frames (
|
||||
addresse TEXT,
|
||||
alive INT,
|
||||
altitude REAL,
|
||||
body TEXT,
|
||||
comment TEXT,
|
||||
course REAL,
|
||||
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
@ -13,6 +14,7 @@ CREATE TABLE frames (
|
||||
frame TEXT,
|
||||
"from" TEXT,
|
||||
gpsfixstatus TEXT,
|
||||
header_raw TEXT,
|
||||
latitude REAL,
|
||||
longitude REAL,
|
||||
mbits INT,
|
||||
@ -22,6 +24,7 @@ CREATE TABLE frames (
|
||||
mtype TEXT,
|
||||
object_format TEXT,
|
||||
object_name TEXT,
|
||||
party INT,
|
||||
path TEXT,
|
||||
phg REAL,
|
||||
phg_dir TEXT,
|
||||
@ -32,6 +35,7 @@ CREATE TABLE frames (
|
||||
posambiguity INT,
|
||||
raw TEXT,
|
||||
raw_timestamp TEXT,
|
||||
rng REAL,
|
||||
speed REAL,
|
||||
station_call TEXT,
|
||||
station_lat REAL,
|
||||
@ -46,6 +50,7 @@ CREATE TABLE frames (
|
||||
tEQNS TEXT,
|
||||
tPARM TEXT,
|
||||
tUNIT TEXT,
|
||||
type TEXT,
|
||||
via TEXT,
|
||||
weather TEXT,
|
||||
wx_raw_timestamp TIMESTAMP
|
||||
|
@ -4,18 +4,79 @@
|
||||
<meta charset="UTF-8">
|
||||
<title>{{station_call}} Status</title>
|
||||
|
||||
<!-- Leaflet's CSS -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||||
crossorigin=""/>
|
||||
<!-- Make sure you put this AFTER Leaflet's CSS -->
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
|
||||
crossorigin=""></script>
|
||||
|
||||
<style>
|
||||
table, th, td {
|
||||
border: 1px solid black;
|
||||
}
|
||||
#map { height: 250px; }
|
||||
.leaflet-tooltip.my-labels {
|
||||
background-color: transparent;
|
||||
border: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="width: 100%; overflow: hidden;">
|
||||
<div style="width: 50%; float: left;">
|
||||
|
||||
<h1>{{station_call}} Status</h1>
|
||||
Station location: {{station_lat}}, {{station_lon}}
|
||||
|
||||
<h2> About </h2>
|
||||
This is a work in progress. See <a href="https://amiok.net/gitea/W1CDN/aprs_tool">https://amiok.net/gitea/W1CDN/aprs_tool</a> for usage.
|
||||
</div>
|
||||
<div style="margin-left: 50%;">
|
||||
<div id="map"></div>
|
||||
<script>
|
||||
var map = L.map('map').setView([{{station_lat}}, {{station_lon}}], 10);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {attribution: '© <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors'}).addTo(map);
|
||||
|
||||
//{{markers|safe}}
|
||||
|
||||
// Show station location
|
||||
var station = L.marker([{{station_lat}}, {{station_lon}}]).addTo(map).bindTooltip('{{station_call}}', {permanent: true}).openTooltip();
|
||||
|
||||
// Show GeoJSON of markers
|
||||
var group = L.geoJSON({{geojs|safe}},
|
||||
{
|
||||
style: function (feature) {
|
||||
return {color: feature.properties.color};
|
||||
}
|
||||
});
|
||||
|
||||
// group.bindTooltip(function (layer) {
|
||||
// return 'Object '+layer.feature.properties.object_name+' from '+layer.feature.properties.from;
|
||||
// }, {permanent: false}).openTooltip().addTo(map);
|
||||
// Hacked together from https://gis.stackexchange.com/a/246919
|
||||
var pointLayer = L.geoJSON(null, {
|
||||
pointToLayer: function(feature,latlng){
|
||||
//(true condition) ? "true" : "false"
|
||||
label = (feature.properties.object_name === null) ? String(feature.properties.from) : String(feature.properties.object_name)
|
||||
//label = String('Object '+feature.properties.object_name+' from '+feature.properties.from) // Must convert to string, .bindTooltip can't use straight 'feature.properties.attribute'
|
||||
return new L.CircleMarker(latlng, {
|
||||
radius: 1,
|
||||
}).bindTooltip(label, {permanent: true, opacity: 0.7, className: "my-labels"}).openTooltip();
|
||||
}
|
||||
});
|
||||
pointLayer.addData({{geojs|safe}});
|
||||
map.addLayer(pointLayer);
|
||||
|
||||
|
||||
// Zoom to show all
|
||||
map.fitBounds(group.getBounds().pad(0.3));
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2> Recent RF Packets </h2>
|
||||
<table>
|
||||
@ -40,6 +101,8 @@ This is a work in progress. See <a href="https://amiok.net/gitea/W1CDN/aprs_tool
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
<h2> Recent Stations </h2>
|
||||
<table>
|
||||
<tr>
|
||||
|
42
templates/map.html
Normal file
42
templates/map.html
Normal file
@ -0,0 +1,42 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Leaflet's CSS -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||||
crossorigin=""/>
|
||||
<!-- Make sure you put this AFTER Leaflet's CSS -->
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
|
||||
crossorigin=""></script>
|
||||
<style>
|
||||
#map { height: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map"></div>
|
||||
|
||||
<script>
|
||||
var map = L.map('map').setView([{{station_lat}}, {{station_lon}}], 10);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {attribution: '© <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors'}).addTo(map);
|
||||
|
||||
//{{markers|safe}}
|
||||
|
||||
// Show station location
|
||||
var station = L.marker([{{station_lat}}, {{station_lon}}]).addTo(map).bindTooltip('{{station_call}}', permanent=true).openTooltip();
|
||||
|
||||
// Show GeoJSON of markers
|
||||
var group = L.geoJSON({{geojs|safe}},
|
||||
{
|
||||
style: function (feature) {
|
||||
return {color: feature.properties.color};
|
||||
}
|
||||
}).bindTooltip(function (layer) {
|
||||
return 'Object '+layer.feature.properties.object_name+' from '+layer.feature.properties.from;
|
||||
}, permanent=true).addTo(map);
|
||||
|
||||
// Zoom to show all
|
||||
map.fitBounds(group.getBounds().pad(0.2));
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
11
test_async.py
Normal file
11
test_async.py
Normal file
@ -0,0 +1,11 @@
|
||||
import asyncio
|
||||
import aprs
|
||||
|
||||
async def main():
|
||||
transport, protocol = await aprs.create_tcp_connection("192.168.0.30", 8001)
|
||||
|
||||
async for frame in protocol.read():
|
||||
print(frame)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
36
test_db.py
36
test_db.py
@ -1,36 +0,0 @@
|
||||
|
||||
# Learn how to update database
|
||||
|
||||
import sqlite3
|
||||
|
||||
def get_db_connection():
|
||||
conn = sqlite3.connect('database.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
conn = get_db_connection()
|
||||
|
||||
# Grab a random row from frames table and pretend it is new
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT [from], id, created_unix FROM frames ORDER BY RANDOM() LIMIT 1;")
|
||||
rows = cur.fetchall()
|
||||
results = dict(rows[0])
|
||||
values = ', '.join('"%s"' % w for w in results.values())
|
||||
|
||||
|
||||
# Build query
|
||||
# "from" is wrappedin [] because it is a reserved word and using '' doesn't work.
|
||||
query3 = "INSERT INTO stations ([from], frames_id, last_heard_unix, count) \
|
||||
VALUES("+values+", 1) \
|
||||
ON CONFLICT([from]) \
|
||||
DO UPDATE SET count = count + 1;"
|
||||
|
||||
# example https://stackoverflow.com/a/50718957/2152245
|
||||
# query2 = "INSERT INTO stations ([from], frames_id, last_heard_unix, count) \
|
||||
# VALUES('KC9TZN-8', 4068, 1687623864, 1) \
|
||||
# ON CONFLICT([from]) \
|
||||
# DO UPDATE SET count = count + 1;"
|
||||
|
||||
conn.execute(query3)
|
||||
conn.commit()
|
||||
conn.close()
|
Reference in New Issue
Block a user