From 37692f41a8d1b9709257c637364e9c4b878da6eb Mon Sep 17 00:00:00 2001 From: Ian Renton Date: Tue, 30 Sep 2025 20:51:56 +0100 Subject: [PATCH] Various stuff --- core/constants.py | 36 +++++++++++++++++------------------ core/utils.py | 8 +++++++- data/band.py | 6 +++++- data/spot.py | 35 ++++++++++++++++++++++++---------- server/webserver.py | 4 ++-- webassets/apidocs/openapi.yml | 12 ++++++++++-- webassets/css/style.css | 13 ------------- webassets/index.html | 30 +++++++++++++++++++++++++---- webassets/js/code.js | 12 +++++++----- 9 files changed, 100 insertions(+), 56 deletions(-) diff --git a/core/constants.py b/core/constants.py index 523060a..8293751 100644 --- a/core/constants.py +++ b/core/constants.py @@ -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 = { diff --git a/core/utils.py b/core/utils.py index 51e75e0..033333e 100644 --- a/core/utils.py +++ b/core/utils.py @@ -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. diff --git a/data/band.py b/data/band.py index df4b432..e57a790 100644 --- a/data/band.py +++ b/data/band.py @@ -8,4 +8,8 @@ class Band: # Start frequency, in kHz start_freq: float # Stop frequency, in kHz - end_freq: float \ No newline at end of file + 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 \ No newline at end of file diff --git a/data/spot.py b/data/spot.py index ee89290..9fd47b7 100644 --- a/data/spot.py +++ b/data/spot.py @@ -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 = infer_mode_from_comment(self.comment) + 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: diff --git a/server/webserver.py b/server/webserver.py index a0b77cf..eb8b416 100644 --- a/server/webserver.py +++ b/server/webserver.py @@ -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] diff --git a/webassets/apidocs/openapi.yml b/webassets/apidocs/openapi.yml index 46bf756..257de5f 100644 --- a/webassets/apidocs/openapi.yml +++ b/webassets/apidocs/openapi.yml @@ -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 diff --git a/webassets/css/style.css b/webassets/css/style.css index 0e6b2ce..c1ad48e 100644 --- a/webassets/css/style.css +++ b/webassets/css/style.css @@ -2,17 +2,4 @@ div#table-container { width: 80%; 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; } \ No newline at end of file diff --git a/webassets/index.html b/webassets/index.html index 2024fe1..40e5e25 100644 --- a/webassets/index.html +++ b/webassets/index.html @@ -1,18 +1,40 @@ + + + Unnamed spot tool + + + -

Unnamed spot tool

-
Some filters here
-
+
+
+ + Unnamed Spot Tool + - + +
+
+ +
+ + diff --git a/webassets/js/code.js b/webassets/js/code.js index df2b347..298b949 100644 --- a/webassets/js/code.js +++ b/webassets/js/code.js @@ -1,17 +1,19 @@ $.getJSON('/api/spots', function(jsonData) { let headers = Object.keys(jsonData[0]); - let table = $('').append(''); + let table = $('
').append(''); ["Time", "DX", "Frequency", "Mode", "Comment", "Source", "DE"].forEach(header => table.find('thead tr').append(``)); jsonData.forEach(row => { let $tr = $(''); - $tr.append(``); - $tr.append(``); + var time = moment(row["time"], moment.ISO_8601); + var time_formatted = time.format("HH:mm") + $tr.append(``); + $tr.append(``); $tr.append(``); $tr.append(``); - $tr.append(''); + $tr.append(''); $tr.append(``); - $tr.append(``); + $tr.append(``); table.find('tbody').append($tr); });
${header}
${row["time"]}${row["dx_call"]}${time_formatted}${row["dx_flag"]} ${row["dx_call"]}${row["freq"]}${row["mode"]}').append(escapeHtml(`${row["comment"]}`)).append('' + escapeHtml(`${row["comment"]}`) + '${row["source"]}${row["de_call"]}${row["de_flag"]} ${row["de_call"]}