Compare commits
	
		
			10 Commits
		
	
	
		
			acdee84d3e
			...
			50085de7db
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					50085de7db | ||
| 
						 | 
					e7002c712c | ||
| 
						 | 
					307c1fcd86 | ||
| 
						 | 
					dc7d4ed8a3 | ||
| 
						 | 
					e1211fe108 | ||
| 
						 | 
					6686cba26d | ||
| 
						 | 
					1b0494c45a | ||
| 
						 | 
					f694e65c2a | ||
| 
						 | 
					78641d0eef | ||
| 
						 | 
					50e8324786 | 
							
								
								
									
										46
									
								
								api_app.py
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								api_app.py
									
									
									
									
									
								
							@@ -4,6 +4,7 @@ from datetime import date, timedelta
 | 
			
		||||
import configparser
 | 
			
		||||
import csv
 | 
			
		||||
import datetime
 | 
			
		||||
import timeago
 | 
			
		||||
import ast
 | 
			
		||||
import glob
 | 
			
		||||
import json, operator
 | 
			
		||||
@@ -20,34 +21,6 @@ def read_config():
 | 
			
		||||
    config.read('config.ini')
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
def read_logs(log_folder):
 | 
			
		||||
    # Read some log files
 | 
			
		||||
    # UTC time, so let's look at tomorrow, today, and yesterday.
 | 
			
		||||
    today = date.today()
 | 
			
		||||
    yesterday = today - timedelta(days = 1)
 | 
			
		||||
    tomorrow = today + timedelta(days = 1)
 | 
			
		||||
    file_list = glob.glob(log_folder+str(yesterday)+"*") + \
 | 
			
		||||
                glob.glob(log_folder+str(today)+"*") + \
 | 
			
		||||
                glob.glob(log_folder+str(tomorrow)+"*")
 | 
			
		||||
 | 
			
		||||
    # https://stackoverflow.com/a/66071962
 | 
			
		||||
    json_array = []
 | 
			
		||||
    for file in file_list:
 | 
			
		||||
        with open(file, encoding='utf-8') as csvf:
 | 
			
		||||
            csvReader = csv.DictReader(csvf)
 | 
			
		||||
            for row in csvReader:
 | 
			
		||||
                #add this python dict to json array
 | 
			
		||||
                json_array.append(row)
 | 
			
		||||
 | 
			
		||||
    # Add the call and location of this station to the packet info
 | 
			
		||||
    config = read_config()
 | 
			
		||||
    for item in json_array:
 | 
			
		||||
        item['station_name'] = config['Settings']['station_call']
 | 
			
		||||
        item['station_lat'] = config['Settings']['station_lat']
 | 
			
		||||
        item['station_lon'] = config['Settings']['station_lon']
 | 
			
		||||
 | 
			
		||||
    return(json_array)
 | 
			
		||||
 | 
			
		||||
def dict_factory(cursor, row):
 | 
			
		||||
    d = {}
 | 
			
		||||
    for idx, col in enumerate(cursor.description):
 | 
			
		||||
@@ -118,23 +91,33 @@ def index():
 | 
			
		||||
 | 
			
		||||
    # Get list of recent packets using API
 | 
			
		||||
    # TODO use relative path
 | 
			
		||||
    response = json.loads(requests.get("https://digi.w1cdn.net/aprs_api/packets").text)['data']
 | 
			
		||||
    #frames = json.loads(requests.get(url_for("packets", _external=True)).text)['data']
 | 
			
		||||
    #frames = json.loads(requests.get("https://digi.w1cdn.net/aprs_api/packets").text)['data']
 | 
			
		||||
    frames = json.loads(requests.get(config['Settings']['base_url']+"/packets").text)['data']
 | 
			
		||||
    for frame in frames:
 | 
			
		||||
        if frame['created'] != None:
 | 
			
		||||
            frame['time_ago'] = timeago.format(frame['created_unix'], datetime.datetime.now())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    # Play with function to create station list
 | 
			
		||||
    #stations = select_all_stations(get_db_connection())
 | 
			
		||||
    #print(url_for("static", filename="test.txt", _external=True))
 | 
			
		||||
    # this should work: stations = json.loads(requests.get(url_for("stations", _external=True)).text)['data']
 | 
			
		||||
    stations = json.loads(requests.get("https://digi.w1cdn.net/aprs_api/stations").text)['data']
 | 
			
		||||
    #stations = json.loads(requests.get(url_for("stations", _external=True)).text)['data']
 | 
			
		||||
    #stations = json.loads(requests.get("https://digi.w1cdn.net/aprs_api/stations").text)['data']
 | 
			
		||||
    stations = json.loads(requests.get(config['Settings']['base_url']+"/stations").text)['data']
 | 
			
		||||
    # Convert unix time to datetime on the fly because I'm lazy right now
 | 
			
		||||
    for station in stations:
 | 
			
		||||
        if station['last_heard_unix'] != None:
 | 
			
		||||
            station['last_heard'] = datetime.datetime.utcfromtimestamp(station['last_heard_unix'])
 | 
			
		||||
            station['time_ago'] = timeago.format(station['last_heard_unix'], datetime.datetime.now())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    return render_template('index.html',
 | 
			
		||||
                            station_call = config['Settings']['station_call'],
 | 
			
		||||
                            station_lat = config['Settings']['station_lat'],
 | 
			
		||||
                            station_lon = config['Settings']['station_lon'],
 | 
			
		||||
                            frames = response,
 | 
			
		||||
                            frames = frames,
 | 
			
		||||
                            stations = stations)
 | 
			
		||||
 | 
			
		||||
class Packets(Resource):
 | 
			
		||||
@@ -169,7 +152,6 @@ class Stations(Resource):
 | 
			
		||||
 | 
			
		||||
# Read config
 | 
			
		||||
config = read_config()
 | 
			
		||||
log_folder = config['Settings']['log_folder']
 | 
			
		||||
 | 
			
		||||
# Start subprocess to watch KISS connection
 | 
			
		||||
import subprocess
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,12 @@
 | 
			
		||||
[Settings]
 | 
			
		||||
# Path to direwolf log folder, include trailing slash
 | 
			
		||||
log_folder = logs/
 | 
			
		||||
#log_folder = /home/pi/logs/direwolf/
 | 
			
		||||
 | 
			
		||||
# Name and location of this station, for inclusion in the API
 | 
			
		||||
station_call = W1CDN-1
 | 
			
		||||
station_lat = 47.941500
 | 
			
		||||
station_lon = -97.027000
 | 
			
		||||
 | 
			
		||||
# Base URL for application (no trailing slash)
 | 
			
		||||
base_url = https://digi.w1cdn.net/aprs_api
 | 
			
		||||
 | 
			
		||||
# How long to keep packets (frames) e.g., "2 days", "5 minutes"
 | 
			
		||||
keep_time = "2 days"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
"id",
 | 
			
		||||
"latitude",
 | 
			
		||||
"longitude",
 | 
			
		||||
"mbits",
 | 
			
		||||
@@ -44,6 +46,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,45 @@ 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:
 | 
			
		||||
                a = aprslib.parse(str(frame))
 | 
			
		||||
                try:
 | 
			
		||||
                    a = aprslib.parse(str(frame))
 | 
			
		||||
                except Exception as error:
 | 
			
		||||
                    logging.error("Error with aprslib:", error)
 | 
			
		||||
                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)
 | 
			
		||||
 | 
			
		||||
                # Make this a string and deal with it later (probably a mistake)
 | 
			
		||||
                a['path'] = str(a['path'])
 | 
			
		||||
                if 'subpacket' in a:
 | 
			
		||||
                    a['subpacket'] = str(a['subpacket'])
 | 
			
		||||
                #logging.debug(a['path'])
 | 
			
		||||
                # Store true/false as 1/0
 | 
			
		||||
                if 'alive' in a:
 | 
			
		||||
                    if a['alive'] == True:
 | 
			
		||||
@@ -65,14 +87,15 @@ 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())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                logging.debug("Inserting into database")
 | 
			
		||||
                try:
 | 
			
		||||
                    # Insert data
 | 
			
		||||
                    sql = "INSERT INTO frames ("+attrib_names+") VALUES ("+attrib_values+")"
 | 
			
		||||
                    logging.debug(sql)
 | 
			
		||||
                    conn.execute(sql, list(a.values()))
 | 
			
		||||
 | 
			
		||||
                    logging.debug("Frames table updated")
 | 
			
		||||
                    # TODO update stations table here
 | 
			
		||||
                    # Original intent was to include the id from the frames table,
 | 
			
		||||
                    # but that would mean making another query.
 | 
			
		||||
@@ -87,11 +110,9 @@ def main():
 | 
			
		||||
                    ON CONFLICT([from]) \
 | 
			
		||||
                    DO UPDATE SET count = count + 1,\
 | 
			
		||||
                                  last_heard_unix = excluded.last_heard_unix;"
 | 
			
		||||
                    #print(query3)
 | 
			
		||||
                    logging.debug(query3)
 | 
			
		||||
                    # Insert/update data
 | 
			
		||||
                    conn.execute(query3)
 | 
			
		||||
 | 
			
		||||
                    logging.debug("Station table updated")
 | 
			
		||||
                    conn.commit()
 | 
			
		||||
                    #except:
 | 
			
		||||
                     #   print("Stations table couldn't be updated.")
 | 
			
		||||
@@ -103,9 +124,9 @@ def main():
 | 
			
		||||
                except:
 | 
			
		||||
                    #print("Error with SQLite!")
 | 
			
		||||
                    logging.error("Error with SQLite!")
 | 
			
		||||
            except:
 | 
			
		||||
            except Exception as error:
 | 
			
		||||
                #print("Frame could not be parsed.")
 | 
			
		||||
                logging.error("Frame could not be parsed.")
 | 
			
		||||
                logging.error("Frame could not be parsed:", error)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        conn.close()
 | 
			
		||||
 
 | 
			
		||||
@@ -6,3 +6,4 @@ kiss
 | 
			
		||||
aprslib
 | 
			
		||||
sqlite3
 | 
			
		||||
json
 | 
			
		||||
timeago
 | 
			
		||||
@@ -12,7 +12,10 @@
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
<h1>{{station_call}} Status</h1>
 | 
			
		||||
{{station_lat}}, {{station_lon}}
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
<h2> Recent RF Packets </h2>
 | 
			
		||||
   <table>
 | 
			
		||||
@@ -21,6 +24,7 @@
 | 
			
		||||
      <th> object_name </th>
 | 
			
		||||
      <th> raw </th>
 | 
			
		||||
      <th> created (utc) </th>
 | 
			
		||||
      <th> relative </th>
 | 
			
		||||
      <th> more </th>
 | 
			
		||||
    </tr>
 | 
			
		||||
        {% for i in frames %}
 | 
			
		||||
@@ -29,6 +33,7 @@
 | 
			
		||||
       <td> {{ i['object_name'] }} </td>
 | 
			
		||||
       <td> {{ i['raw'] }} </td>
 | 
			
		||||
       <td> {{ i['created'] }} </td>
 | 
			
		||||
       <td> {{ i['time_ago'] }} </td>
 | 
			
		||||
       <td> <a href="https://digi.w1cdn.net/aprs_api/packets?id={{ i['id'] }}">query</a>,
 | 
			
		||||
            <a href="https://aprs.fi/#!mt=roadmap&z=12&call=a%2F{{ i['from'] }}">aprs.fi</a></td>
 | 
			
		||||
     </tr>
 | 
			
		||||
@@ -40,6 +45,7 @@
 | 
			
		||||
  <tr>
 | 
			
		||||
   <th> from </th>
 | 
			
		||||
   <th> last heard (utc) </th>
 | 
			
		||||
   <th> relative </th>
 | 
			
		||||
   <th> count </th>
 | 
			
		||||
   <th> more </th>
 | 
			
		||||
 </tr>
 | 
			
		||||
@@ -47,13 +53,12 @@
 | 
			
		||||
  <tr>
 | 
			
		||||
    <td> <a href="https://digi.w1cdn.net/aprs_api/packets?from={{ i['from'] }}">{{ i['from'] }}</a> </td>
 | 
			
		||||
    <td> {{ i['last_heard'] }} </td>
 | 
			
		||||
    <td> {{ i['time_ago'] }} </td>
 | 
			
		||||
    <td> {{ i['count']}} </td>
 | 
			
		||||
    <td> <a href="https://aprs.fi/#!mt=roadmap&z=12&call=a%2F{{ i['from'] }}">aprs.fi</a></td>
 | 
			
		||||
  </tr>
 | 
			
		||||
   {% endfor %}
 | 
			
		||||
</table>
 | 
			
		||||
 | 
			
		||||
<h2> Help </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.
 | 
			
		||||
</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())
 | 
			
		||||
		Reference in New Issue
	
	Block a user