mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-06-23 21:25:12 +00:00
Implement spotting to Tiles on the Air. #95
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import threading
|
||||
from datetime import datetime
|
||||
|
||||
import pytz
|
||||
@@ -175,8 +176,8 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
try:
|
||||
# Submit spot to the upstream provider
|
||||
provider.submit_spot(spot, upstream_credentials)
|
||||
# Trigger an immediate re-poll so the spot appears quickly
|
||||
provider.force_poll()
|
||||
# Trigger a re-poll after 1 second so the spot appears quickly
|
||||
threading.Timer(1.0, lambda: provider.force_poll()).start()
|
||||
except NotImplementedError as e:
|
||||
upstream_warning = str(e)
|
||||
except Exception as e:
|
||||
|
||||
@@ -21,7 +21,6 @@ class SOTA(HTTPSpotProvider):
|
||||
SUMMIT_URL_ROOT = "https://api-db2.sota.org.uk/api/summits/"
|
||||
|
||||
SUBMIT_URL = "https://api-db2.sota.org.uk/api/spots"
|
||||
|
||||
VALID_MODES = ["AM", "CW", "Data", "DV", "FM", "SSB"]
|
||||
|
||||
def __init__(self, provider_config):
|
||||
@@ -77,23 +76,24 @@ class SOTA(HTTPSpotProvider):
|
||||
|
||||
# Figure out a valid mode. Borrowed this from PoLo :)
|
||||
# https://github.com/ham2k/app-polo/blob/main/src/extensions/activities/sota/SOTAPostSelfSpot.js
|
||||
if spot.mode and spot.mode not in self.VALID_MODES:
|
||||
if spot.mode in SSB_SUB_MODES:
|
||||
spot.mode = "SSB"
|
||||
elif spot.mode in DV_SUB_MODES:
|
||||
spot.mode = "DV"
|
||||
mode = spot.mode
|
||||
if mode and mode not in self.VALID_MODES:
|
||||
if mode in SSB_SUB_MODES:
|
||||
mode = "SSB"
|
||||
elif mode in DV_SUB_MODES:
|
||||
mode = "DV"
|
||||
else:
|
||||
spot.mode = "Data"
|
||||
mode = "Data"
|
||||
|
||||
body = {
|
||||
"activatorCallsign": spot.dx_call,
|
||||
"associationCode": ref_split[0],
|
||||
"summitCode": ref_split[1],
|
||||
"frequency": str(spot.freq / 1000000.0),
|
||||
"mode": spot.mode or "",
|
||||
"posterCallsign": spot.de_call,
|
||||
"frequency": spot.freq / 1000000.0,
|
||||
"mode": mode or "",
|
||||
"callsign": spot.de_call,
|
||||
"comments": spot.comment or "",
|
||||
"type": "TEST" # todo remove once testing complete
|
||||
"type": "TEST" # todo replatce with NORMAL/QRT once testing complete
|
||||
}
|
||||
headers = {**HTTP_HEADERS, "Authorization": "bearer " + access_token, "id_token": id_token, "Content-Type": "application/json"}
|
||||
response = requests.post(self.SUBMIT_URL, json=body, headers=headers, timeout=(5, 30))
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from datetime import datetime
|
||||
|
||||
import requests
|
||||
|
||||
from core.constants import HTTP_HEADERS, SSB_SUB_MODES
|
||||
from data.sig_ref import SIGRef
|
||||
from data.spot import Spot
|
||||
from spotproviders.http_spot_provider import HTTPSpotProvider
|
||||
@@ -10,6 +13,8 @@ class Tiles(HTTPSpotProvider):
|
||||
|
||||
POLL_INTERVAL_SEC = 120
|
||||
SPOTS_URL = "https://icneuzxitdqtofutxbla.supabase.co/functions/v1/spots?active_hours=24"
|
||||
SUBMIT_URL = "https://icneuzxitdqtofutxbla.supabase.co/functions/v1/self-spot"
|
||||
VALID_MODES = ["SSB", "CW", "FT8", "FT4", "FM", "DMR", "D-STAR", "M17", "AX.25", "JS8Call", "PSK31", "Olivia", "VarAC", "Other"]
|
||||
|
||||
def __init__(self, provider_config):
|
||||
super().__init__(provider_config, self.SPOTS_URL, self.POLL_INTERVAL_SEC)
|
||||
@@ -41,6 +46,48 @@ class Tiles(HTTPSpotProvider):
|
||||
new_spots.append(spot)
|
||||
return new_spots
|
||||
|
||||
def can_submit_spot(self, sig):
|
||||
return sig == "Tiles"
|
||||
|
||||
def submit_spot(self, spot, credentials):
|
||||
# Tiles on the air currently only supports *self* spots
|
||||
if spot.dx_call == spot.de_call:
|
||||
|
||||
# Figure out a valid mode. Borrowed this from PoLo :)
|
||||
# https://github.com/ham2k/app-polo/blob/main/src/extensions/activities/sota/SOTAPostSelfSpot.js
|
||||
if spot.mode:
|
||||
mode = spot.mode
|
||||
if mode not in self.VALID_MODES:
|
||||
if mode in SSB_SUB_MODES:
|
||||
mode = "SSB"
|
||||
elif mode == "OLIVIA":
|
||||
mode = "Olivia"
|
||||
elif mode == "JS8":
|
||||
mode = "JS8Call"
|
||||
else:
|
||||
mode = "Other"
|
||||
|
||||
body = {
|
||||
"call_sign": spot.dx_call,
|
||||
"frequency": str(spot.freq / 1000000.0),
|
||||
"mode": mode or "",
|
||||
"grid": spot.dx_grid or "",
|
||||
"comment": spot.comment or "",
|
||||
"lat": spot.dx_latitude or None,
|
||||
"lon": spot.dx_longitude or None,
|
||||
"qrt": spot.qrt or False,
|
||||
"pin": credentials.get("offline_spot_gateway_pin", "")
|
||||
}
|
||||
headers = {**HTTP_HEADERS, "Content-Type": "application/json"}
|
||||
response = requests.post(self.SUBMIT_URL, json=body, headers=headers, timeout=(5, 30))
|
||||
if not response.ok:
|
||||
raise RuntimeError("Tiles on the Air API returned " + str(response.status_code) + ": " + response.text)
|
||||
else:
|
||||
raise RuntimeError("The Tiles on the Air API requires a mode to be set.")
|
||||
else:
|
||||
raise RuntimeError("The Tiles on the Air API only supports self-spots, the DX call and spotter call must match.")
|
||||
|
||||
|
||||
# Utility function to keep the first decimal point in a given string but remove any others. Used to parse Tiles'
|
||||
# strange frequency format where we can sometimes have e.g. "14.123.5".
|
||||
def strip_extra_decimal_points(s):
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<p>This software is dedicated to the memory of Tom G1PJB, SK, a friend and colleague who sadly passed away around the time I started writing it in Autumn 2025. I was looking forward to showing it to you when it was done.</p>
|
||||
</div>
|
||||
|
||||
<script src="/js/common.js?v=1781252061"></script>
|
||||
<script src="/js/common.js?v=1781335058"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-about").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -109,8 +109,8 @@
|
||||
|
||||
<script>window._recaptchaSiteKey = {% raw json_encode(web_ui_options.get('recaptcha-site-key', '')) %};
|
||||
window._allowUpstreamSpotting = {% raw json_encode(web_ui_options.get('allow-upstream-spotting', True)) %};</script>
|
||||
<script src="/js/common.js?v=1781252061"></script>
|
||||
<script src="/js/add-spot.js?v=1781252061"></script>
|
||||
<script src="/js/common.js?v=1781335058"></script>
|
||||
<script src="/js/add-spot.js?v=1781335058"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-add-spot").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
|
||||
@@ -70,8 +70,8 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/js/common.js?v=1781252061"></script>
|
||||
<script src="/js/alerts.js?v=1781252061"></script>
|
||||
<script src="/js/common.js?v=1781335058"></script>
|
||||
<script src="/js/alerts.js?v=1781335058"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-alerts").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -76,9 +76,9 @@
|
||||
<script>
|
||||
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
||||
</script>
|
||||
<script src="/js/common.js?v=1781252061"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=1781252061"></script>
|
||||
<script src="/js/bands.js?v=1781252061"></script>
|
||||
<script src="/js/common.js?v=1781335058"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=1781335058"></script>
|
||||
<script src="/js/bands.js?v=1781335058"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-bands").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "skeleton.html" %}
|
||||
{% block head_extra %}
|
||||
<link rel="stylesheet" href="/css/style.css?v=1781252061" type="text/css">
|
||||
<link rel="stylesheet" href="/css/style.css?v=1781335058" type="text/css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
||||
<link href="/fa/css/fontawesome.min.css" rel="stylesheet" />
|
||||
@@ -19,9 +19,9 @@
|
||||
integrity="sha384-L1eE4eD41kpBIWe2I0eHy+GnEUC4RIpcvibVW2JCminuPlTl+2Bc528iPdVMg5Dn"
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<script src="https://misc.ianrenton.com/jsutils/utils.js?v=1781252061"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/ui-ham.js?v=1781252061"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/geo.js?v=1781252061"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/utils.js?v=1781335058"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/ui-ham.js?v=1781335058"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/geo.js?v=1781335058"></script>
|
||||
{% end %}
|
||||
{% block body %}
|
||||
<div class="container">
|
||||
|
||||
@@ -284,8 +284,8 @@
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.9/dist/chart.umd.min.js"></script>
|
||||
<script src="/js/common.js?v=1781252061"></script>
|
||||
<script src="/js/conditions.js?v=1781252061"></script>
|
||||
<script src="/js/common.js?v=1781335058"></script>
|
||||
<script src="/js/conditions.js?v=1781335058"></script>
|
||||
<script>$(document).ready(function () {
|
||||
$("#nav-link-conditions").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
@@ -94,9 +94,9 @@
|
||||
<script>
|
||||
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
||||
</script>
|
||||
<script src="/js/common.js?v=1781252061"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=1781252061"></script>
|
||||
<script src="/js/map.js?v=1781252061"></script>
|
||||
<script src="/js/common.js?v=1781335058"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=1781335058"></script>
|
||||
<script src="/js/map.js?v=1781335058"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-map").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -104,9 +104,9 @@
|
||||
<script>
|
||||
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
||||
</script>
|
||||
<script src="/js/common.js?v=1781252061"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=1781252061"></script>
|
||||
<script src="/js/spots.js?v=1781252061"></script>
|
||||
<script src="/js/common.js?v=1781335058"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=1781335058"></script>
|
||||
<script src="/js/spots.js?v=1781335058"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-spots").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -59,8 +59,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/common.js?v=1781252061"></script>
|
||||
<script src="/js/status.js?v=1781252061"></script>
|
||||
<script src="/js/common.js?v=1781335058"></script>
|
||||
<script src="/js/status.js?v=1781335058"></script>
|
||||
<script>
|
||||
$(document).ready(function() { $("#nav-link-status").addClass("active"); }); <!-- highlight active page in nav -->
|
||||
</script>
|
||||
|
||||
@@ -15,6 +15,9 @@ var PROVIDER_CREDENTIAL_SCHEMAS = {
|
||||
"ZLOTA": [
|
||||
{ key: "user_id", label: "ZLOTA User ID", help: "" },
|
||||
{ key: "api_key", label: "ZLOTA User PIN", help: "Get your PIN from your ZLOTA account." }
|
||||
],
|
||||
"Tiles": [
|
||||
{ key: "offline_spot_gateway_pin", label: "Offline Spot Gateway PIN", help: "Get your PIN from your Tiles on the Air account profile." }
|
||||
]
|
||||
};
|
||||
|
||||
@@ -239,10 +242,18 @@ function addSpot() {
|
||||
showAddSpotError("A SIG must be selected to submit upstream.");
|
||||
return;
|
||||
}
|
||||
if (!sigRef) {
|
||||
if (!sigRef && upstreamProviderName !== "Tiles") {
|
||||
showAddSpotError("A SIG reference is required to submit upstream.");
|
||||
return;
|
||||
}
|
||||
if (!dxGrid && upstreamProviderName === "Tiles") {
|
||||
showAddSpotError("A grid reference is required to submit upstream to Tiles on the Air.");
|
||||
return;
|
||||
}
|
||||
if (!mode && upstreamProviderName === "Tiles") {
|
||||
showAddSpotError("A mode is required to submit upstream to Tiles on the Air.");
|
||||
return;
|
||||
}
|
||||
|
||||
var creds = loadCredentials(upstreamProviderName);
|
||||
spot["submit_upstream"] = true;
|
||||
|
||||
Reference in New Issue
Block a user