mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2025-10-27 08:49:27 +00:00
Add APRS-IS support
This commit is contained in:
@@ -14,10 +14,7 @@ Currently supports:
|
|||||||
* UKBOTA
|
* UKBOTA
|
||||||
* Parks n Peaks
|
* Parks n Peaks
|
||||||
* RBN
|
* RBN
|
||||||
|
* APRS
|
||||||
Future plans:
|
|
||||||
* APRS?
|
|
||||||
* Packet?
|
|
||||||
|
|
||||||
Suggested names so far:
|
Suggested names so far:
|
||||||
* All in 1 Spots
|
* All in 1 Spots
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ providers:
|
|||||||
- type: "GMA"
|
- type: "GMA"
|
||||||
- type: "HEMA"
|
- type: "HEMA"
|
||||||
- type: "ParksNPeaks"
|
- type: "ParksNPeaks"
|
||||||
|
# - type: "APRS-IS"
|
||||||
# Some, like DX Clusters, require extra config. You can add multiple DX clusters if you want!
|
# Some, like DX Clusters, require extra config. You can add multiple DX clusters if you want!
|
||||||
-
|
-
|
||||||
type: "DXCluster"
|
type: "DXCluster"
|
||||||
@@ -31,7 +32,6 @@ providers:
|
|||||||
# -
|
# -
|
||||||
# type: "RBN"
|
# type: "RBN"
|
||||||
# port: 7001
|
# port: 7001
|
||||||
# - type: "APRS-IS"
|
|
||||||
|
|
||||||
# Port to open the local web server on
|
# Port to open the local web server on
|
||||||
web-server-port: 8080
|
web-server-port: 8080
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ class Spot:
|
|||||||
dx_cq_zone: int = None
|
dx_cq_zone: int = None
|
||||||
# ITU zone of the DX operator
|
# ITU zone of the DX operator
|
||||||
dx_itu_zone: int = None
|
dx_itu_zone: int = None
|
||||||
|
# If this is an APRS spot, what SSID was the DX operator using?
|
||||||
|
# This is a string not an int for now, as I often see non-numeric ones somehow
|
||||||
|
dx_aprs_ssid: str = None
|
||||||
# Reported mode, such as SSB, PHONE, CW, FT8...
|
# Reported mode, such as SSB, PHONE, CW, FT8...
|
||||||
mode: str = None
|
mode: str = None
|
||||||
# Inferred mode "family". One of "CW", "PHONE" or "DIGI".
|
# Inferred mode "family". One of "CW", "PHONE" or "DIGI".
|
||||||
|
|||||||
15
main.py
15
main.py
@@ -6,6 +6,7 @@ from time import sleep
|
|||||||
|
|
||||||
from core.cleanup import CleanupTimer
|
from core.cleanup import CleanupTimer
|
||||||
from core.config import config
|
from core.config import config
|
||||||
|
from providers.aprsis import APRSIS
|
||||||
from providers.dxcluster import DXCluster
|
from providers.dxcluster import DXCluster
|
||||||
from providers.gma import GMA
|
from providers.gma import GMA
|
||||||
from providers.hema import HEMA
|
from providers.hema import HEMA
|
||||||
@@ -17,7 +18,11 @@ from providers.wwbota import WWBOTA
|
|||||||
from providers.wwff import WWFF
|
from providers.wwff import WWFF
|
||||||
from server.webserver import WebServer
|
from server.webserver import WebServer
|
||||||
|
|
||||||
# Main control flag, switch False to stop main application thread
|
# Globals
|
||||||
|
spot_list = []
|
||||||
|
status_data = {}
|
||||||
|
providers = []
|
||||||
|
cleanup_timer = None
|
||||||
run = True
|
run = True
|
||||||
|
|
||||||
# Shutdown function
|
# Shutdown function
|
||||||
@@ -51,6 +56,8 @@ def get_provider_from_config(config_providers_entry):
|
|||||||
return DXCluster(config_providers_entry["host"], config_providers_entry["port"])
|
return DXCluster(config_providers_entry["host"], config_providers_entry["port"])
|
||||||
case "RBN":
|
case "RBN":
|
||||||
return RBN(config_providers_entry["port"])
|
return RBN(config_providers_entry["port"])
|
||||||
|
case "APRS-IS":
|
||||||
|
return APRSIS()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -69,12 +76,6 @@ if __name__ == '__main__':
|
|||||||
# Shut down gracefully on SIGINT
|
# Shut down gracefully on SIGINT
|
||||||
signal.signal(signal.SIGINT, shutdown)
|
signal.signal(signal.SIGINT, shutdown)
|
||||||
|
|
||||||
# Set up spot list & status data areas
|
|
||||||
spot_list = []
|
|
||||||
status_data = {}
|
|
||||||
|
|
||||||
# Create data providers
|
|
||||||
providers = []
|
|
||||||
for entry in config["providers"]:
|
for entry in config["providers"]:
|
||||||
providers.append(get_provider_from_config(entry))
|
providers.append(get_provider_from_config(entry))
|
||||||
# Set up data providers
|
# Set up data providers
|
||||||
|
|||||||
62
providers/aprsis.py
Normal file
62
providers/aprsis.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import logging
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
import aprslib
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
from core.config import config
|
||||||
|
from data.spot import Spot
|
||||||
|
from providers.provider import Provider
|
||||||
|
|
||||||
|
|
||||||
|
# Provider for the APRS-IS.
|
||||||
|
class APRSIS(Provider):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.thread = Thread(target=self.connect)
|
||||||
|
self.thread.daemon = True
|
||||||
|
self.aprsis = None
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return "APRS-IS"
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
self.aprsis = aprslib.IS(config["server-owner-callsign"])
|
||||||
|
self.status = "Connecting"
|
||||||
|
logging.info("APRS-IS connecting...")
|
||||||
|
self.aprsis.connect()
|
||||||
|
self.aprsis.consumer(self.handle)
|
||||||
|
logging.info("APRS-IS connected.")
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.status = "Shutting down"
|
||||||
|
self.aprsis.close()
|
||||||
|
self.thread.join()
|
||||||
|
|
||||||
|
def handle(self, data):
|
||||||
|
# Split SSID in "from" call and store separately
|
||||||
|
from_parts = data["from"].split("-")
|
||||||
|
dx_call = from_parts[0]
|
||||||
|
dx_aprs_ssid = from_parts[1] if len(from_parts) > 1 else None
|
||||||
|
spot = Spot(source="APRS-IS",
|
||||||
|
dx_call=dx_call,
|
||||||
|
dx_aprs_ssid=dx_aprs_ssid,
|
||||||
|
de_call=data["via"],
|
||||||
|
comment=data["comment"] if "comment" in data else None,
|
||||||
|
latitude=data["latitude"] if "latitude" in data else None,
|
||||||
|
longitude=data["longitude"] if "longitude" in data else None,
|
||||||
|
time=datetime.now(pytz.UTC)) # APRS-IS spots are live so we can assume spot time is "now"
|
||||||
|
# Fill in any blanks
|
||||||
|
spot.infer_missing()
|
||||||
|
# Add to our list
|
||||||
|
self.submit(spot)
|
||||||
|
print(spot)
|
||||||
|
|
||||||
|
self.status = "OK"
|
||||||
|
self.last_update_time = datetime.now(timezone.utc)
|
||||||
|
logging.debug("Data received from APRS-IS.")
|
||||||
@@ -5,3 +5,4 @@ pyhamtools~=0.12.0
|
|||||||
telnetlib3~=2.0.8
|
telnetlib3~=2.0.8
|
||||||
pytz~=2025.2
|
pytz~=2025.2
|
||||||
requests~=2.32.5
|
requests~=2.32.5
|
||||||
|
aprslib~=0.7.2
|
||||||
Reference in New Issue
Block a user