mirror of
https://github.com/smittix/intercept.git
synced 2026-06-20 19:28:26 -07:00
Harden APRS station plotting across payload variants
This commit is contained in:
@@ -469,6 +469,49 @@ def parse_position(data: str) -> Optional[dict]:
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
# Legacy/no-decimal variant occasionally seen in degraded decodes:
|
||||||
|
# DDMMN/DDDMMW (symbol chars still present between/after coords).
|
||||||
|
nodot_match = re.match(
|
||||||
|
r'^(\d{2})(\d{2})([NS])(.)(\d{3})(\d{2})([EW])(.)?',
|
||||||
|
data
|
||||||
|
)
|
||||||
|
if nodot_match:
|
||||||
|
lat_deg = int(nodot_match.group(1))
|
||||||
|
lat_min = float(nodot_match.group(2))
|
||||||
|
lat_dir = nodot_match.group(3)
|
||||||
|
symbol_table = nodot_match.group(4)
|
||||||
|
lon_deg = int(nodot_match.group(5))
|
||||||
|
lon_min = float(nodot_match.group(6))
|
||||||
|
lon_dir = nodot_match.group(7)
|
||||||
|
symbol_code = nodot_match.group(8) or ''
|
||||||
|
|
||||||
|
lat = lat_deg + lat_min / 60.0
|
||||||
|
if lat_dir == 'S':
|
||||||
|
lat = -lat
|
||||||
|
|
||||||
|
lon = lon_deg + lon_min / 60.0
|
||||||
|
if lon_dir == 'W':
|
||||||
|
lon = -lon
|
||||||
|
|
||||||
|
result = {
|
||||||
|
'lat': round(lat, 6),
|
||||||
|
'lon': round(lon, 6),
|
||||||
|
'symbol': symbol_table + symbol_code,
|
||||||
|
}
|
||||||
|
|
||||||
|
remaining = data[13:] if len(data) > 13 else ''
|
||||||
|
|
||||||
|
cs_match = re.search(r'(\d{3})/(\d{3})', remaining)
|
||||||
|
if cs_match:
|
||||||
|
result['course'] = int(cs_match.group(1))
|
||||||
|
result['speed'] = int(cs_match.group(2))
|
||||||
|
|
||||||
|
alt_match = re.search(r'/A=(-?\d+)', remaining)
|
||||||
|
if alt_match:
|
||||||
|
result['altitude'] = int(alt_match.group(1))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
# Fallback: tolerate APRS ambiguity spaces in minute fields.
|
# Fallback: tolerate APRS ambiguity spaces in minute fields.
|
||||||
# Example: 4903. N/07201. W
|
# Example: 4903. N/07201. W
|
||||||
if len(data) >= 18:
|
if len(data) >= 18:
|
||||||
|
|||||||
@@ -9488,8 +9488,13 @@
|
|||||||
if (Array.isArray(payload)) return payload;
|
if (Array.isArray(payload)) return payload;
|
||||||
if (Array.isArray(payload.stations)) return payload.stations;
|
if (Array.isArray(payload.stations)) return payload.stations;
|
||||||
if (Array.isArray(payload.data)) return payload.data;
|
if (Array.isArray(payload.data)) return payload.data;
|
||||||
|
if (payload.data && Array.isArray(payload.data.stations)) return payload.data.stations;
|
||||||
if (payload.data && Array.isArray(payload.data.data)) return payload.data.data;
|
if (payload.data && Array.isArray(payload.data.data)) return payload.data.data;
|
||||||
|
if (payload.result && Array.isArray(payload.result.stations)) return payload.result.stations;
|
||||||
if (payload.result && Array.isArray(payload.result.data)) return payload.result.data;
|
if (payload.result && Array.isArray(payload.result.data)) return payload.result.data;
|
||||||
|
if (payload.data && payload.data.result && Array.isArray(payload.data.result.stations)) {
|
||||||
|
return payload.data.result.stations;
|
||||||
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9508,6 +9513,8 @@
|
|||||||
stations.forEach((station) => {
|
stations.forEach((station) => {
|
||||||
const callsign = String(station && station.callsign ? station.callsign : '').trim();
|
const callsign = String(station && station.callsign ? station.callsign : '').trim();
|
||||||
if (!callsign) return;
|
if (!callsign) return;
|
||||||
|
const lat = station.lat ?? station.latitude ?? null;
|
||||||
|
const lon = station.lon ?? station.longitude ?? null;
|
||||||
|
|
||||||
const signature = getAprsStationSignature(station);
|
const signature = getAprsStationSignature(station);
|
||||||
if (aprsAgentStationSignatures.get(callsign) === signature) return;
|
if (aprsAgentStationSignatures.get(callsign) === signature) return;
|
||||||
@@ -9525,12 +9532,50 @@
|
|||||||
processAprsPacket({
|
processAprsPacket({
|
||||||
type: 'aprs',
|
type: 'aprs',
|
||||||
...station,
|
...station,
|
||||||
|
lat,
|
||||||
|
lon,
|
||||||
callsign,
|
callsign,
|
||||||
agent_name: station.agent_name || agentName || 'Remote Agent'
|
agent_name: station.agent_name || agentName || 'Remote Agent'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadAprsStationSnapshot(isAgentMode = false) {
|
||||||
|
try {
|
||||||
|
const endpoint = (isAgentMode && aprsCurrentAgent)
|
||||||
|
? `/controller/agents/${aprsCurrentAgent}/aprs/data`
|
||||||
|
: '/aprs/stations';
|
||||||
|
const response = await fetch(endpoint);
|
||||||
|
if (!response.ok) return;
|
||||||
|
const payload = await response.json();
|
||||||
|
const stations = extractAprsStationsFromPayload(payload);
|
||||||
|
if (!Array.isArray(stations) || stations.length === 0) return;
|
||||||
|
if (isAgentMode) {
|
||||||
|
processAprsAgentStations(stations, payload.agent_name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stations.forEach((station) => {
|
||||||
|
const callsign = String(station && station.callsign ? station.callsign : '').trim();
|
||||||
|
if (!callsign) return;
|
||||||
|
const packet = {
|
||||||
|
type: 'aprs',
|
||||||
|
...station,
|
||||||
|
callsign,
|
||||||
|
lat: station.lat ?? station.latitude ?? null,
|
||||||
|
lon: station.lon ?? station.longitude ?? null,
|
||||||
|
packet_type: station.packet_type || 'position',
|
||||||
|
};
|
||||||
|
if (aprsHasValidCoordinates(packet.lat, packet.lon) && aprsMap) {
|
||||||
|
updateAprsMarker(packet);
|
||||||
|
}
|
||||||
|
updateAprsStationList(packet);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.debug('APRS snapshot load failed:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function startAprs() {
|
function startAprs() {
|
||||||
// Get values from function bar controls
|
// Get values from function bar controls
|
||||||
const region = document.getElementById('aprsStripRegion').value;
|
const region = document.getElementById('aprsStripRegion').value;
|
||||||
@@ -9627,6 +9672,9 @@
|
|||||||
if (customFreqInput) customFreqInput.disabled = true;
|
if (customFreqInput) customFreqInput.disabled = true;
|
||||||
startAprsMeterCheck();
|
startAprsMeterCheck();
|
||||||
startAprsStream(isAgentMode);
|
startAprsStream(isAgentMode);
|
||||||
|
// Backfill current stations in case position packets arrived before
|
||||||
|
// map initialization or SSE attachment.
|
||||||
|
loadAprsStationSnapshot(isAgentMode);
|
||||||
} else {
|
} else {
|
||||||
alert('APRS Error: ' + (scanResult.message || scanResult.error || 'Failed to start'));
|
alert('APRS Error: ' + (scanResult.message || scanResult.error || 'Failed to start'));
|
||||||
updateAprsStatus('error');
|
updateAprsStatus('error');
|
||||||
|
|||||||
@@ -41,3 +41,11 @@ def test_parse_aprs_packet_handles_ambiguous_uncompressed_position() -> None:
|
|||||||
assert packet["packet_type"] == "position"
|
assert packet["packet_type"] == "position"
|
||||||
assert packet["lat"] == pytest.approx(49.05, rel=0, abs=1e-6)
|
assert packet["lat"] == pytest.approx(49.05, rel=0, abs=1e-6)
|
||||||
assert packet["lon"] == pytest.approx(-72.016667, rel=0, abs=1e-6)
|
assert packet["lon"] == pytest.approx(-72.016667, rel=0, abs=1e-6)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_aprs_packet_handles_no_decimal_position_variant() -> None:
|
||||||
|
packet = parse_aprs_packet("KJ7ABC-7>APRS,WIDE1-1:!4903N/07201W-Test")
|
||||||
|
assert packet is not None
|
||||||
|
assert packet["packet_type"] == "position"
|
||||||
|
assert packet["lat"] == pytest.approx(49.05, rel=0, abs=1e-6)
|
||||||
|
assert packet["lon"] == pytest.approx(-72.016667, rel=0, abs=1e-6)
|
||||||
|
|||||||
Reference in New Issue
Block a user