From c9fd4915b812015836eec60e5b75d03d1f272e9f Mon Sep 17 00:00:00 2001 From: Adam Wisher <37659188+mrwish7@users.noreply.github.com> Date: Sat, 31 May 2025 09:12:26 +0100 Subject: [PATCH 1/9] Multi TX search tweaks Order the match list by score, clean up of matches panel HTML and show score in DOM as data element --- server/datahandler.js | 3 ++- server/tx_search.js | 7 +++++-- web/index.ejs | 2 +- web/js/main.js | 15 ++++++++------- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/server/datahandler.js b/server/datahandler.js index d0e2291..1f4ef74 100644 --- a/server/datahandler.js +++ b/server/datahandler.js @@ -421,7 +421,8 @@ function handleData(wss, receivedData, rdsWss) { id: currentTx.id, pi: currentTx.pi, reg: currentTx.reg, - otherMatches: currentTx.others + otherMatches: currentTx.others, + score: currentTx.score, }; } }) diff --git a/server/tx_search.js b/server/tx_search.js index 2380cce..33c8eef 100644 --- a/server/tx_search.js +++ b/server/tx_search.js @@ -191,10 +191,12 @@ async function fetchTx(freq, piCode, rdsPs) { for (loc of filteredLocations) { loc.score = evaluateStation(loc); } - match = filteredLocations.reduce((max, obj) => obj.score > max.score ? obj : max, filteredLocations[0]); - multiMatches = filteredLocations.filter(obj => obj !== match); + filteredLocations.sort((a, b) => b.score - a.score); + match = filteredLocations[0]; + multiMatches = filteredLocations.slice(1); } else if (filteredLocations.length === 1) { match = filteredLocations[0]; + match.score = 1; } if (match) { @@ -218,6 +220,7 @@ async function fetchTx(freq, piCode, rdsPs) { pi: match.pi, foundStation: true, reg: match.detectedByPireg, + score: match.score, others: multiMatches, }; } else { diff --git a/web/index.ejs b/web/index.ejs index 044d2ed..286c8b0 100644 --- a/web/index.ejs +++ b/web/index.ejs @@ -290,7 +290,7 @@
-
+

diff --git a/web/js/main.js b/web/js/main.js index 532bce2..0645d06 100644 --- a/web/js/main.js +++ b/web/js/main.js @@ -918,13 +918,11 @@ function throttle(fn, wait) { } function buildAltTxList(txList) { - const wrapper = '
'; let outString = ''; - outString += wrapper; for (let i = 0; i < txList.length; i++) { const tx = txList[i]; outString += `
-
+

${tx.station.replace("R.", "Radio ").replace(/%/g, '%25')}

@@ -935,11 +933,7 @@ function buildAltTxList(txList) {
`; - if (i % 2 !== 0) { - outString += `
${wrapper}`; - } } - outString += '
'; return outString; } @@ -955,6 +949,12 @@ function updateHtmlIfChanged($element, newHtml) { } } +function updateDatasetValIfChanged($element, dataLabel, newVal) { + if ($element.attr(dataLabel) !== newVal) { + $element.attr(dataLabel, newVal); + } +} + // Main function to update data elements, optimized const updateDataElements = throttle(function(parsedData) { updateTextIfChanged($dataFrequency, parsedData.freq); @@ -1015,6 +1015,7 @@ const updateDataElements = throttle(function(parsedData) { updateTextIfChanged($('#data-station-pol'), parsedData.txInfo.pol); updateHtmlIfChanged($('#data-station-azimuth'), parsedData.txInfo.azi + '°'); updateHtmlIfChanged($('#data-station-others'), parsedData.txInfo.otherMatches.length > 0 ? ('+' + parsedData.txInfo.otherMatches.length +'') : ''); + updateDatasetValIfChanged($('#data-station-container'), "data-score", parsedData.txInfo.score); const txDistance = localStorage.getItem('imperialUnits') == "true" ? (Number(parsedData.txInfo.dist) * 0.621371192).toFixed(0) + " mi" : parsedData.txInfo.dist + " km"; const altTxInfo = buildAltTxList(parsedData.txInfo.otherMatches); updateHtmlIfChanged($('#alternative-txes'), altTxInfo); From dae705ccbc10d3015cc3b67054c5eee032b4ead7 Mon Sep 17 00:00:00 2001 From: Adam Wisher <37659188+mrwish7@users.noreply.github.com> Date: Sat, 31 May 2025 14:38:07 +0100 Subject: [PATCH 2/9] Limit multiple matches Only return a max of 10 extra TX matches and filter any with a score less than 1/10 of the winner --- server/tx_search.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/tx_search.js b/server/tx_search.js index 33c8eef..e17c850 100644 --- a/server/tx_search.js +++ b/server/tx_search.js @@ -191,9 +191,13 @@ async function fetchTx(freq, piCode, rdsPs) { for (loc of filteredLocations) { loc.score = evaluateStation(loc); } + // Sort by score in descending order filteredLocations.sort((a, b) => b.score - a.score); match = filteredLocations[0]; - multiMatches = filteredLocations.slice(1); + // Have a maximum of 10 extra matches and remove any with less than 1/10 of the winning score + multiMatches = filteredLocations + .slice(1, 11) + .filter(obj => obj.score >= (match.score/10)); } else if (filteredLocations.length === 1) { match = filteredLocations[0]; match.score = 1; From bf011acfb41fb7253f4ae4628b4f88d98e2f661c Mon Sep 17 00:00:00 2001 From: Adam Wisher <37659188+mrwish7@users.noreply.github.com> Date: Mon, 9 Jun 2025 12:30:47 +0100 Subject: [PATCH 3/9] TX Search improvements Fix local signal IDed as distant sporadic E bug and allow PI-only match when possible --- server/tx_search.js | 50 +++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/server/tx_search.js b/server/tx_search.js index e17c850..f00a04a 100644 --- a/server/tx_search.js +++ b/server/tx_search.js @@ -5,7 +5,7 @@ const consoleCmd = require('./console'); let localDb = {}; let lastFetchTime = 0; const fetchInterval = 1000; -const esSwitchCache = {"lastCheck":0, "esSwitch":false}; +const esSwitchCache = {"lastCheck": null, "esSwitch": false}; const esFetchInterval = 300000; var currentPiCode = ''; var currentRdsPs = ''; @@ -135,7 +135,7 @@ function evaluateStation(station) { let esMode = checkEs(); let weightDistance = station.distanceKm; if (esMode && station.distanceKm > 500) { - weightDistance = Math.abs(station.distanceKm - 1500); + weightDistance = Math.abs(station.distanceKm - 1500) + 200; } let erp = station.erp && station.erp > 0 ? station.erp : 1; let extraWeight = erp > 30 && station.distanceKm <= 500 ? 0.3 : 0; @@ -161,25 +161,34 @@ async function fetchTx(freq, piCode, rdsPs) { || serverConfig.identification.lat.length < 2 || freq < 87 || Object.keys(localDb).length === 0 - || (currentPiCode == piCode && currentRdsPs == rdsPs)) { + || (currentPiCode === piCode && currentRdsPs === rdsPs)) { return Promise.resolve(); } lastFetchTime = now; + currentPiCode = piCode; + currentRdsPs = rdsPs; if (serverConfig.webserver.rdsMode === true) await loadUsStatesGeoJson(); - const filteredLocations = Object.values(localDb.locations) - .map(locData => ({ - ...locData, - stations: locData.stations.filter(station => - station.freq === freq && - (station.pi === piCode.toUpperCase() || station.pireg === piCode.toUpperCase() ) && - validPsCompare(rdsPs, station.ps) - ) - })) - .filter(locData => locData.stations.length > 0); // Ensure locations with at least one matching station remain + let filteredLocations = Object.values(localDb.locations || {}) + .map(locData => ({ + ...locData, + stations: locData.stations.filter(station => + station.freq === freq && + (station.pi === piCode.toUpperCase() || station.pireg === piCode.toUpperCase() ) + ) + })) + .filter(locData => locData.stations.length > 0); // Ensure locations with at least one matching station remain + + // Only check PS if we have more than one match. + if (filteredLocations.length > 1) { + filteredLocations = filteredLocations.map(locData => ({ + ...locData, + stations: locData.stations.filter(station => validPsCompare(rdsPs, station.ps)) + })).filter(locData => locData.stations.length > 0); + } - for (loc of filteredLocations) { + for (let loc of filteredLocations) { loc = Object.assign(loc, loc.stations[0]); delete loc.stations; const dist = haversine(serverConfig.identification.lat, serverConfig.identification.lon, loc.lat, loc.lon); @@ -188,7 +197,7 @@ async function fetchTx(freq, piCode, rdsPs) { } if (filteredLocations.length > 1) { - for (loc of filteredLocations) { + for (let loc of filteredLocations) { loc.score = evaluateStation(loc); } // Sort by score in descending order @@ -210,7 +219,7 @@ async function fetchTx(freq, piCode, rdsPs) { match.state = state; // Add state to matchingCity } } - return { + const result = { station: match.detectedByPireg ? `${match.station.replace("R.", "Radio ")}${match.regname ? ' ' + match.regname : ''}` : match.station.replace("R.", "Radio "), @@ -225,9 +234,14 @@ async function fetchTx(freq, piCode, rdsPs) { foundStation: true, reg: match.detectedByPireg, score: match.score, - others: multiMatches, + others: multiMatches.slice(), }; + filteredLocations.length = 0; + multiMatches.length = 0; + return result; } else { + filteredLocations.length = 0; + multiMatches.length = 0; return Promise.resolve(); } } @@ -236,7 +250,7 @@ function checkEs() { const now = Date.now(); const url = "https://fmdx.org/includes/tools/get_muf.php"; - if (now - esSwitchCache.lastCheck < esFetchInterval) { + if (esSwitchCache.lastCheck && now - esSwitchCache.lastCheck < esFetchInterval) { return esSwitchCache.esSwitch; } From fb68f26d829aaf98304c29532d1ce77d2c3923f5 Mon Sep 17 00:00:00 2001 From: Adam Wisher <37659188+mrwish7@users.noreply.github.com> Date: Thu, 12 Jun 2025 09:18:59 +0100 Subject: [PATCH 4/9] Local db optimisation and GPS support DB indexing to improve tx search performance with PI and reg PI, re-download db on failure, add websocket for GPS location update support Co-Authored-By: Amateur Audio Dude <168192910+AmateurAudioDude@users.noreply.github.com> Co-Authored-By: Highpoint2000 <168109804+Highpoint2000@users.noreply.github.com> --- server/server_config.js | 1 + server/tx_search.js | 129 +++++++++++++++++++++++++++++++++------- 2 files changed, 107 insertions(+), 23 deletions(-) diff --git a/server/server_config.js b/server/server_config.js index fcf4290..f03419f 100644 --- a/server/server_config.js +++ b/server/server_config.js @@ -58,6 +58,7 @@ let serverConfig = { lat: "", lon: "", broadcastTuner: false, + gpsMode: false, proxyIp: "", contact: null, }, diff --git a/server/tx_search.js b/server/tx_search.js index f00a04a..2958ba5 100644 --- a/server/tx_search.js +++ b/server/tx_search.js @@ -3,14 +3,24 @@ const { serverConfig } = require('./server_config'); const consoleCmd = require('./console'); let localDb = {}; +let lastDownloadTime = 0; // Last DB download attempt time. let lastFetchTime = 0; +let piFreqIndex = {}; // Indexing for speedier PI+Freq combinations const fetchInterval = 1000; +const downloadInterval = 300000; const esSwitchCache = {"lastCheck": null, "esSwitch": false}; const esFetchInterval = 300000; var currentPiCode = ''; var currentRdsPs = ''; const usStatesGeoJsonUrl = "https://raw.githubusercontent.com/PublicaMundi/MappingAPI/master/data/geojson/us-states.json"; let usStatesGeoJson = null; // To cache the GeoJSON data for US states +let Latitude = serverConfig.identification.lat; +let Longitude = serverConfig.identification.lon; + +// Create WebSocket URL for GPS lat/lon update. +const webserverPort = serverConfig.webserver.webserverPort || 8080; // Fallback to port 8080 +const externalWsUrl = `ws://127.0.0.1:${webserverPort}/data_plugins`; +const WebSocket = require('ws'); // Get weighting values based on algorithm setting. // Defaults = algorithm 1 @@ -30,16 +40,76 @@ if (typeof algorithms[algoSetting] !== 'undefined') { // IIFE to build the local TX DB cache from the endpoint. (async () => { + const now = Date.now(); + lastDownloadTime = now; + await buildTxDatabase(); +})(); + +if (serverConfig.identification.gpsMode) { + // 5-second delay before activation of GPS lat/lon websocket + setTimeout(() => { + const websocket = new WebSocket(externalWsUrl); + consoleCmd.logInfo('Set up GPS websocket for lat/lon'); + // Event listener to receive data + websocket.on('message', (data) => { + try { + // Parse the received data + const parsedData = JSON.parse(data); + + // Check if the dataset is of type GPS + if (parsedData.type === "GPS" && parsedData.value) { + const gpsData = parsedData.value; + const { status, time, lat, lon, alt, mode } = gpsData; + + if (status === "active") { + Latitude = parseFloat(lat); + Longitude = parseFloat(lon); + } + } + } catch (error) { + consoleCmd.logError("Error processing WebSocket data:", error); + } + }); + + }, 5000); +} + +// Function to build local TX database from FMDX Maps endpoint. +async function buildTxDatabase() { try { consoleCmd.logInfo('Fetching transmitter database...'); const response = await fetch(`https://maps.fmdx.org/api?qth=${serverConfig.identification.lat},${serverConfig.identification.lon}`); if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); localDb = await response.json(); + buildPiFreqIndex(); consoleCmd.logInfo('Transmitter database successfully loaded.'); } catch (error) { consoleCmd.logError("Failed to fetch transmitter database:", error); } -})(); +} + +// Function to build index map of PI+Freq combinations +function buildPiFreqIndex() { + piFreqIndex = {}; // reset + for (const locData of Object.values(localDb.locations || {})) { + for (const station of locData.stations || []) { + if (!station.freq) continue; + const freq = station.freq; + const pi = station.pi?.toUpperCase(); + const pireg = station.pireg?.toUpperCase(); + if (pi) { + const key = `${freq}|${pi}`; + if (!piFreqIndex[key]) piFreqIndex[key] = []; + piFreqIndex[key].push({ ...locData, station }); + } + if (pireg) { + const regKey = `${freq}|${pireg}`; + if (!piFreqIndex[regKey]) piFreqIndex[regKey] = []; + piFreqIndex[regKey].push({ ...locData, station }); + } + } + } +} // Load the US states GeoJSON data async function loadUsStatesGeoJson() { @@ -105,11 +175,16 @@ function getStateForCoordinates(lat, lon) { * If at least three valid matches are found for any token, the function returns true. */ function validPsCompare(rdsPs, stationPs) { + if (typeof stationPs !== 'string' || typeof rdsPs !== 'string') { + consoleCmd.logError(`Invalid TX values. stationPs: ${stationPs}, rdsPs: ${rdsPs}`); + return false; + } + // Standardize the rdsPs string: replace spaces with underscores and convert to lowercase. const standardizedRdsPs = rdsPs.replace(/ /g, '_').toLowerCase(); // Split stationPs into tokens (e.g., "__mdr___ _kultur_" -> ["__mdr___", "_kultur_"]) - const psTokens = stationPs.split(/\s+/).filter(token => token.length > 0).map(token => token.toLowerCase()); + const psTokens = stationPs.split(/\s+/).filter(token => token.length > 0).map(token => { const lower = token.toLowerCase(); return lower.length < 8 ? lower.padEnd(8, '_') : lower; }); // Iterate through all tokens and check if any token yields at least three valid (non "_" ) matches. for (let token of psTokens) { @@ -156,29 +231,37 @@ async function fetchTx(freq, piCode, rdsPs) { const now = Date.now(); freq = parseFloat(freq); - if (isNaN(freq)) return; - if (now - lastFetchTime < fetchInterval - || serverConfig.identification.lat.length < 2 - || freq < 87 - || Object.keys(localDb).length === 0 - || (currentPiCode === piCode && currentRdsPs === rdsPs)) { - return Promise.resolve(); + // If we don't have a local database and the interval has passed, re-try download. + if ( + Object.keys(localDb).length === 0 && + now - lastDownloadTime > downloadInterval + ) { + lastDownloadTime = now; + await buildTxDatabase(); } + if ( + isNaN(freq) || + now - lastFetchTime < fetchInterval || + Latitude.length < 2 || + freq < 87 || + Object.keys(localDb).length === 0 || + (currentPiCode === piCode && currentRdsPs === rdsPs) + ) return Promise.resolve(); + lastFetchTime = now; currentPiCode = piCode; currentRdsPs = rdsPs; if (serverConfig.webserver.rdsMode === true) await loadUsStatesGeoJson(); - let filteredLocations = Object.values(localDb.locations || {}) - .map(locData => ({ - ...locData, - stations: locData.stations.filter(station => - station.freq === freq && - (station.pi === piCode.toUpperCase() || station.pireg === piCode.toUpperCase() ) - ) - })) - .filter(locData => locData.stations.length > 0); // Ensure locations with at least one matching station remain + const key = `${freq}|${piCode.toUpperCase()}`; + let rawMatches = piFreqIndex[key] || []; + + // Format the results into the same structure as before + let filteredLocations = rawMatches.map(({ station, ...locData }) => ({ + ...locData, + stations: [station] + })); // Only check PS if we have more than one match. if (filteredLocations.length > 1) { @@ -191,7 +274,7 @@ async function fetchTx(freq, piCode, rdsPs) { for (let loc of filteredLocations) { loc = Object.assign(loc, loc.stations[0]); delete loc.stations; - const dist = haversine(serverConfig.identification.lat, serverConfig.identification.lon, loc.lat, loc.lon); + const dist = haversine(Latitude, Longitude, loc.lat, loc.lon); loc = Object.assign(loc, dist); loc.detectedByPireg = (loc.pireg === piCode.toUpperCase()); } @@ -206,7 +289,7 @@ async function fetchTx(freq, piCode, rdsPs) { // Have a maximum of 10 extra matches and remove any with less than 1/10 of the winning score multiMatches = filteredLocations .slice(1, 11) - .filter(obj => obj.score >= (match.score/10)); + .filter(obj => obj.score >= (match.score / 10)); } else if (filteredLocations.length === 1) { match = filteredLocations[0]; match.score = 1; @@ -254,7 +337,7 @@ function checkEs() { return esSwitchCache.esSwitch; } - if (serverConfig.identification.lat > 20) { + if (Latitude > 20) { esSwitchCache.lastCheck = now; fetch(url) .then(response => { @@ -262,8 +345,8 @@ function checkEs() { return response.json(); }) .then(data => { - if ((serverConfig.identification.lon < -32 && data.north_america.max_frequency !== "No data") || - (serverConfig.identification.lon >= -32 && data.europe.max_frequency !== "No data")) { + if ((Longitude < -32 && data.north_america.max_frequency !== "No data") || + (Longitude >= -32 && data.europe.max_frequency !== "No data")) { esSwitchCache.esSwitch = true; } }) From 72d48b0c2e1458350f6aef16f7cf5d7471e3d071 Mon Sep 17 00:00:00 2001 From: Adam Wisher <37659188+mrwish7@users.noreply.github.com> Date: Thu, 12 Jun 2025 13:58:25 +0100 Subject: [PATCH 5/9] Fix algorithm choice and change Es mode distance Fix hard-coded algorithm weighting values back to config values and increase distance for Es weighting to be applied --- server/tx_search.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/tx_search.js b/server/tx_search.js index 2958ba5..b79c766 100644 --- a/server/tx_search.js +++ b/server/tx_search.js @@ -209,11 +209,11 @@ function validPsCompare(rdsPs, stationPs) { function evaluateStation(station) { let esMode = checkEs(); let weightDistance = station.distanceKm; - if (esMode && station.distanceKm > 500) { + if (esMode && station.distanceKm > 700) { weightDistance = Math.abs(station.distanceKm - 1500) + 200; } let erp = station.erp && station.erp > 0 ? station.erp : 1; - let extraWeight = erp > 30 && station.distanceKm <= 500 ? 0.3 : 0; + let extraWeight = erp > weightedErp && station.distanceKm <= weightDistance ? 0.3 : 0; let score = 0; // If ERP is 1W, use a simpler formula to avoid zero-scoring. if (erp === 0.001) { From eb8dbf8fd530c26a20a9cd1c65d927a1a70c77a7 Mon Sep 17 00:00:00 2001 From: Adam Wisher <37659188+mrwish7@users.noreply.github.com> Date: Fri, 27 Jun 2025 14:08:47 +0100 Subject: [PATCH 6/9] Adjust spE weighting behaviour Don't weight options for spE if a powerful station is found under the threshold --- server/tx_search.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/server/tx_search.js b/server/tx_search.js index b79c766..cdb029c 100644 --- a/server/tx_search.js +++ b/server/tx_search.js @@ -206,8 +206,7 @@ function validPsCompare(rdsPs, stationPs) { return false; } -function evaluateStation(station) { - let esMode = checkEs(); +function evaluateStation(station, esMode) { let weightDistance = station.distanceKm; if (esMode && station.distanceKm > 700) { weightDistance = Math.abs(station.distanceKm - 1500) + 200; @@ -280,8 +279,16 @@ async function fetchTx(freq, piCode, rdsPs) { } if (filteredLocations.length > 1) { + // Check for any 10kW+ stations within 700km, and don't Es weight if any found. + const tropoPriority = filteredLocations.some( + loc => loc.distanceKm < 700 && loc.erp >= 10 + ); + let esMode = false; + if (!tropoPriority) { + esMode = checkEs(); + } for (let loc of filteredLocations) { - loc.score = evaluateStation(loc); + loc.score = evaluateStation(loc, esMode); } // Sort by score in descending order filteredLocations.sort((a, b) => b.score - a.score); From 3cedab524892b2085e5021f4df467e6de70ade4d Mon Sep 17 00:00:00 2001 From: Adam Wisher <37659188+mrwish7@users.noreply.github.com> Date: Sat, 5 Jul 2025 16:23:40 +0100 Subject: [PATCH 7/9] Improve TX DB fetch retry process Improve process for retrying when TX DB download fails --- server/tx_search.js | 54 ++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/server/tx_search.js b/server/tx_search.js index cdb029c..2fc0147 100644 --- a/server/tx_search.js +++ b/server/tx_search.js @@ -3,11 +3,9 @@ const { serverConfig } = require('./server_config'); const consoleCmd = require('./console'); let localDb = {}; -let lastDownloadTime = 0; // Last DB download attempt time. let lastFetchTime = 0; let piFreqIndex = {}; // Indexing for speedier PI+Freq combinations const fetchInterval = 1000; -const downloadInterval = 300000; const esSwitchCache = {"lastCheck": null, "esSwitch": false}; const esFetchInterval = 300000; var currentPiCode = ''; @@ -38,12 +36,8 @@ if (typeof algorithms[algoSetting] !== 'undefined') { weightedDist = algorithms[algoSetting][1]; } -// IIFE to build the local TX DB cache from the endpoint. -(async () => { - const now = Date.now(); - lastDownloadTime = now; - await buildTxDatabase(); -})(); +// Build the TX database. +setTimeout(buildTxDatabase, 3000); if (serverConfig.identification.gpsMode) { // 5-second delay before activation of GPS lat/lon websocket @@ -76,15 +70,30 @@ if (serverConfig.identification.gpsMode) { // Function to build local TX database from FMDX Maps endpoint. async function buildTxDatabase() { - try { - consoleCmd.logInfo('Fetching transmitter database...'); - const response = await fetch(`https://maps.fmdx.org/api?qth=${serverConfig.identification.lat},${serverConfig.identification.lon}`); - if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); - localDb = await response.json(); - buildPiFreqIndex(); - consoleCmd.logInfo('Transmitter database successfully loaded.'); - } catch (error) { - consoleCmd.logError("Failed to fetch transmitter database:", error); + if (Latitude.length > 0 && Longitude.length > 0) { + let awaitingTxInfo = true; + while (awaitingTxInfo) { + try { + consoleCmd.logInfo('Fetching transmitter database...'); + const response = await fetch(`https://maps.fmdx.org/api?qth=${serverConfig.identification.lat},${serverConfig.identification.lon}`, { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }); + if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); + localDb = await response.json(); + buildPiFreqIndex(); + consoleCmd.logInfo('Transmitter database successfully loaded.'); + awaitingTxInfo = false; + } catch (error) { + consoleCmd.logError("Failed to fetch transmitter database:", error); + await new Promise(res => setTimeout(res, 30000)); + consoleCmd.logInfo('Retrying transmitter database download...'); + } + } + } else { + consoleCmd.logInfo('Server latitude and longitude must be set before transmitter database can be built'); } } @@ -230,21 +239,12 @@ async function fetchTx(freq, piCode, rdsPs) { const now = Date.now(); freq = parseFloat(freq); - // If we don't have a local database and the interval has passed, re-try download. - if ( - Object.keys(localDb).length === 0 && - now - lastDownloadTime > downloadInterval - ) { - lastDownloadTime = now; - await buildTxDatabase(); - } - if ( isNaN(freq) || now - lastFetchTime < fetchInterval || Latitude.length < 2 || freq < 87 || - Object.keys(localDb).length === 0 || + Object.keys(piFreqIndex).length === 0 || (currentPiCode === piCode && currentRdsPs === rdsPs) ) return Promise.resolve(); From cd9bbaee87db04ec64f5e49e2386dae98300586c Mon Sep 17 00:00:00 2001 From: Adam Wisher <37659188+mrwish7@users.noreply.github.com> Date: Sat, 5 Jul 2025 17:03:27 +0100 Subject: [PATCH 8/9] Bugfix to avoid CPU spike of checking big object length Don't check the length of the generated indexed database as it's big and causes a lag --- server/tx_search.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/tx_search.js b/server/tx_search.js index 2fc0147..fce7019 100644 --- a/server/tx_search.js +++ b/server/tx_search.js @@ -244,7 +244,7 @@ async function fetchTx(freq, piCode, rdsPs) { now - lastFetchTime < fetchInterval || Latitude.length < 2 || freq < 87 || - Object.keys(piFreqIndex).length === 0 || + Object.keys(localDb).length === 0 || (currentPiCode === piCode && currentRdsPs === rdsPs) ) return Promise.resolve(); From 9b3593bc0213162fa9204d65f6bd6275337424b2 Mon Sep 17 00:00:00 2001 From: Adam Wisher <37659188+mrwish7@users.noreply.github.com> Date: Tue, 22 Jul 2025 17:51:07 +0100 Subject: [PATCH 9/9] Valid PS compare fix for PS under 3 chars Fix to allow stations with a PS less than 3 characters to be IDed when PS validity checked --- server/tx_search.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server/tx_search.js b/server/tx_search.js index fce7019..d4c9b59 100644 --- a/server/tx_search.js +++ b/server/tx_search.js @@ -197,6 +197,9 @@ function validPsCompare(rdsPs, stationPs) { // Iterate through all tokens and check if any token yields at least three valid (non "_" ) matches. for (let token of psTokens) { + // If total non "_" length of token is less than 3, allow match based on that length instead + const tokenLength = token.replace(/_/g, "").length; + const minMatchLen = tokenLength > 2 ? 3 : tokenLength; // If the token's length does not match the standardized rdsPs length, skip this token. if (token.length !== standardizedRdsPs.length) continue; @@ -208,7 +211,7 @@ function validPsCompare(rdsPs, stationPs) { matchCount++; } } - if (matchCount >= 3) { + if (matchCount >= minMatchLen) { return true; } } @@ -393,4 +396,4 @@ function deg2rad(deg) { module.exports = { fetchTx -}; \ No newline at end of file +};