Various stuff

This commit is contained in:
Ian Renton
2025-09-30 20:51:56 +01:00
parent 280749919d
commit 37692f41a8
9 changed files with 100 additions and 56 deletions

View File

@@ -7,28 +7,28 @@ SOFTWARE_VERSION = "0.1"
# Modes
CW_MODES = ["CW"]
PHONE_MODES = ["PHONE", "SSB", "USB", "LSB", "AM", "FM", "DV", "DMR", "DSTAR", "C4FM", "M17"]
DATA_MODES = ["DIGI", "DATA", "FT8", "FT4", "RTTY", "SSTV", "JS8", "HELL", "BPSK", "PSK", "BPSK31", "OLIVIA"]
DATA_MODES = ["DIGI", "DATA", "DIGITAL", "FT8", "FT4", "RTTY", "SSTV", "JS8", "HELL", "BPSK", "PSK", "BPSK31", "OLIVIA"]
ALL_MODES = CW_MODES + PHONE_MODES + DATA_MODES
# Band definitions
BANDS = [
Band(name="160m", start_freq=1800, end_freq=2000),
Band(name="80m", start_freq=3500, end_freq=4000),
Band(name="60m", start_freq=5250, end_freq=5410),
Band(name="40m", start_freq=7000, end_freq=7300),
Band(name="30m", start_freq=10100, end_freq=10150),
Band(name="20m", start_freq=14000, end_freq=14350),
Band(name="17m", start_freq=18068, end_freq=18168),
Band(name="15m", start_freq=21000, end_freq=21450),
Band(name="12m", start_freq=24890, end_freq=24990),
Band(name="10m", start_freq=28000, end_freq=29700),
Band(name="6m", start_freq=50000, end_freq=54000),
Band(name="4m", start_freq=70000, end_freq=70500),
Band(name="2m", start_freq=144000, end_freq=148000),
Band(name="70cm", start_freq=420000, end_freq=450000),
Band(name="23cm", start_freq=1240000, end_freq=1325000),
Band(name="13cm", start_freq=2300000, end_freq=2450000)]
UNKNOWN_BAND = Band(name="Unknown", start_freq=0, end_freq=0)
Band(name="160m", start_freq=1800, end_freq=2000, color="#7cfc00", contrast_color="black"),
Band(name="80m", start_freq=3500, end_freq=4000, color="#e550e5", contrast_color="black"),
Band(name="60m", start_freq=5250, end_freq=5410, color="#00008b", contrast_color="white"),
Band(name="40m", start_freq=7000, end_freq=7300, color="#5959ff", contrast_color="white"),
Band(name="30m", start_freq=10100, end_freq=10150, color="#62d962", contrast_color="black"),
Band(name="20m", start_freq=14000, end_freq=14350, color="#f2c40c", contrast_color="black"),
Band(name="17m", start_freq=18068, end_freq=18168, color="#f2f261", contrast_color="black"),
Band(name="15m", start_freq=21000, end_freq=21450, color="#cca166", contrast_color="black"),
Band(name="12m", start_freq=24890, end_freq=24990, color="#b22222", contrast_color="white"),
Band(name="10m", start_freq=28000, end_freq=29700, color="#ff69b4", contrast_color="black"),
Band(name="6m", start_freq=50000, end_freq=54000, color="#FF0000", contrast_color="white"),
Band(name="4m", start_freq=70000, end_freq=70500, color="#cc0044", contrast_color="white"),
Band(name="2m", start_freq=144000, end_freq=148000, color="#FF1493", contrast_color="black"),
Band(name="70cm", start_freq=420000, end_freq=450000, color="#999900", contrast_color="white"),
Band(name="23cm", start_freq=1240000, end_freq=1325000, color="#5AB8C7", contrast_color="black"),
Band(name="13cm", start_freq=2300000, end_freq=2450000, color="#FF7F50", contrast_color="black")]
UNKNOWN_BAND = Band(name="Unknown", start_freq=0, end_freq=0, color="black", contrast_color="white")
# DXCC flags (borrowed from https:#github.com/wavelog/wavelog/blob/master/application/libraries/DxccFlag.php)
DXCC_FLAGS = {

View File

@@ -3,6 +3,7 @@ from datetime import datetime
from diskcache import Cache
from pyhamtools import LookupLib, Callinfo
from pyhamtools.frequency import freq_to_band
from pyhamtools.locator import latlong_to_locator
from core.config import config
@@ -25,7 +26,7 @@ def infer_mode_from_comment(comment):
return None
# Infer a "mode family" from a mode.
def infer_mode_family_from_mode(mode):
def infer_mode_type_from_mode(mode):
if mode.upper() in CW_MODES:
return "CW"
elif mode.upper() in PHONE_MODES:
@@ -137,6 +138,11 @@ def infer_grid_from_callsign_dxcc(call):
return latlong_to_locator(latlon[0], latlon[1], 8)
# Infer a mode from the frequency according to the band plan. Just a guess really.
def infer_mode_from_frequency(freq):
return freq_to_band(freq)["mode"]
# Convert objects to serialisable things. Used by JSON serialiser as a default when it encounters unserializable things.
# Converts datetimes to ISO.
# Anything else it tries to convert to a dict.

View File

@@ -9,3 +9,7 @@ class Band:
start_freq: float
# Stop frequency, in kHz
end_freq: float
# Colour to use for this band, as per PSK Reporter
color: str
# Contrast colour to use for text against a background of the band colour
contrast_color: str

View File

@@ -7,10 +7,10 @@ import pytz
from pyhamtools.locator import locator_to_latlong, latlong_to_locator
from core.constants import DXCC_FLAGS
from core.utils import infer_mode_family_from_mode, infer_band_from_freq, infer_continent_from_callsign, \
from core.utils import infer_mode_type_from_mode, infer_band_from_freq, infer_continent_from_callsign, \
infer_country_from_callsign, infer_cq_zone_from_callsign, infer_itu_zone_from_callsign, infer_dxcc_id_from_callsign, \
infer_mode_from_comment, infer_name_from_callsign, infer_latlon_from_callsign_dxcc, infer_grid_from_callsign_dxcc, \
infer_latlon_from_callsign_qrz, infer_grid_from_callsign_qrz
infer_latlon_from_callsign_qrz, infer_grid_from_callsign_qrz, infer_mode_from_frequency
# Data class that defines a spot.
@@ -50,7 +50,9 @@ class Spot:
# Reported mode, such as SSB, PHONE, CW, FT8...
mode: str = None
# Inferred mode "family". One of "CW", "PHONE" or "DIGI".
mode_family: str = None
mode_type: str = None
# Source of the mode information. "SPOT", "COMMENT", "BANDPLAN" or "NONE"
mode_source: str = "NONE"
# Frequency, in kHz
freq: float = None
# Band, defined by the frequency, e.g. "40m" or "70cm"
@@ -77,11 +79,11 @@ class Spot:
latitude: float = None
longitude: float = None
# Location source. Indicates how accurate the location might be. Values: "SPOT", "QRZ, "DXCC", "NONE"
location_source: str = None
location_source: str = "NONE"
# Location good. Indicates that the software thinks the location data is good enough to plot on a map.
location_good: bool = None
location_good: bool = False
# QRT state. Some APIs return spots marked as QRT. Otherwise we can check the comments.
qrt: bool = None
qrt: bool = False
# Where we got the spot from, e.g. "POTA", "Cluster"...
source: str = None
# The ID the source gave it, if any.
@@ -118,11 +120,24 @@ class Spot:
band = infer_band_from_freq(self.freq)
self.band = band.name
# Mode from comments, mode family from mode
# Mode from comments or bandplan
if self.mode:
self.mode_source = "SPOT"
if self.comment and not self.mode:
self.mode = infer_mode_from_comment(self.comment)
if self.mode and not self.mode_family:
self.mode_family=infer_mode_family_from_mode(self.mode)
self.mode_source = "COMMENT"
if self.freq and not self.mode:
self.mode = infer_mode_from_frequency(self.freq)
self.mode_source = "BANDPLAN"
# Normalise "generic digital" modes. "DIGITAL", "DIGI" and "DATA" are just the same thing with no extra
# information, so standardise on "DATA"
if self.mode == "DIGI" or self.mode == "DIGITAL":
self.mode = "DATA"
# Mode type from mode
if self.mode and not self.mode_type:
self.mode_type = infer_mode_type_from_mode(self.mode)
# Grid to lat/lon and vice versa
if self.grid and not self.latitude:

View File

@@ -105,9 +105,9 @@ class WebServer:
case "mode":
modes = query.get(k).split(",")
spots = [s for s in spots if s.mode in modes]
case "mode_family":
case "mode_type":
mode_families = query.get(k).split(",")
spots = [s for s in spots if s.mode_family in mode_families]
spots = [s for s in spots if s.mode_type in mode_families]
case "dx_continent":
dxconts = query.get(k).split(",")
spots = [s for s in spots if s.dx_continent in dxconts]

View File

@@ -122,7 +122,7 @@ paths:
- PSK
- BPSK31
- OLIVIA
- name: mode_family
- name: mode_type
in: query
description: "Limit the spots to only ones from one or more mode families. To select more than one mode family, supply a comma-separated list."
required: false
@@ -338,7 +338,7 @@ components:
- BPSK31
- OLIVIA
example: SSB
mode_family:
mode_type:
type: string
description: Inferred mode "family".
enum:
@@ -346,6 +346,14 @@ components:
- PHONE
- DATA
example: PHONE
mode_source:
type: string
description: Where we got the mode from. If this was from the spot itself, it's likely quite accurate, but if we had to fall back to the bandplan, it might not be correct.
enum:
- SPOT
- COMMENT
- BANDPLAN
- NONE
freq:
type: number
description: Frequency, in kHz

View File

@@ -3,16 +3,3 @@ div#table-container {
margin: 0 auto;
overflow: scroll;
}
div#table-container table, div#table-container th, div#table-container td {
border: 1px solid;
border-collapse: collapse
}
div#table-container th, div#table-container td {
padding: 10px;
}
div#table-container th {
background-color: dodgerblue;
}

View File

@@ -1,16 +1,38 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Unnamed spot tool</title>
<link rel="stylesheet" href="css/style.css" 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">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"
crossorigin="anonymous"></script>
</head>
<body>
<h1>Unnamed spot tool</h1>
<div id="filters">Some filters here</div>
<div class="container">
<header class="d-flex flex-wrap justify-content-center py-3 mb-4 border-bottom">
<a href="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none">
<span class="fs-4">Unnamed Spot Tool</span>
</a>
<ul class="nav nav-pills">
<li class="nav-item"><a href="#" class="nav-link">About</a></li>
<li class="nav-item"><a href="/apidocs" class="nav-link">API</a></li>
<li class="nav-item"><a href="#" class="nav-link">Status</a></li>
<li class="nav-item"><a href="#" class="nav-link">Filters</a></li>
</ul>
</header>
</div>
<div id="table-container"></div>
<script src="js/code.js"></script>

View File

@@ -1,17 +1,19 @@
$.getJSON('/api/spots', function(jsonData) {
let headers = Object.keys(jsonData[0]);
let table = $('<table>').append('<thead><tr></tr></thead><tbody></tbody>');
let table = $('<table class="table table-striped table-hover">').append('<thead><tr class="table-primary"></tr></thead><tbody></tbody>');
["Time", "DX", "Frequency", "Mode", "Comment", "Source", "DE"].forEach(header => table.find('thead tr').append(`<th>${header}</th>`));
jsonData.forEach(row => {
let $tr = $('<tr>');
$tr.append(`<td>${row["time"]}</td>`);
$tr.append(`<td>${row["dx_call"]}</td>`);
var time = moment(row["time"], moment.ISO_8601);
var time_formatted = time.format("HH:mm")
$tr.append(`<td>${time_formatted}</td>`);
$tr.append(`<td>${row["dx_flag"]}&nbsp;${row["dx_call"]}</td>`);
$tr.append(`<td>${row["freq"]}</td>`);
$tr.append(`<td>${row["mode"]}</td>`);
$tr.append('<td>').append(escapeHtml(`${row["comment"]}`)).append('</td>');
$tr.append('<td>' + escapeHtml(`${row["comment"]}`) + '</td>');
$tr.append(`<td>${row["source"]}</td>`);
$tr.append(`<td>${row["de_call"]}</td>`);
$tr.append(`<td>${row["de_flag"]}&nbsp;${row["de_call"]}</td>`);
table.find('tbody').append($tr);
});