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
	