Skip to content

Commit 53a3d3e

Browse files
committed
Parse skimmer spots
1 parent ee01855 commit 53a3d3e

2 files changed

Lines changed: 68 additions & 35 deletions

File tree

‎app.js‎

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const APIv1 = require('./modules/apiv1');
1515
const APIv2 = require('./modules/apiv2');
1616
const Metrics = require('./modules/metrics');
1717
const RateLimiter = require('./modules/rate-limiter');
18-
const { toUcWord, qrg2band, getFreshestSpot, getOldestSpot, normalizeFrequency } = require('./lib/utils');
18+
const { toUcWord, qrg2band, getFreshestSpot, getOldestSpot, normalizeFrequency, parseSkimmerMessage } = require('./lib/utils');
1919
const express = require("express");
2020
const app = express();
2121
const path = require("path");
@@ -1504,6 +1504,7 @@ function validateAndSanitizeSpot(spot) {
15041504
band: String(spot.band || ''),
15051505
mode: spot.mode || null,
15061506
submode: spot.submode || null,
1507+
skimmer: spot.skimmer || null,
15071508
dxcc_spotter: normalizeDXCCObject(spot.dxcc_spotter),
15081509
dxcc_spotted: normalizeDXCCObject(spot.dxcc_spotted)
15091510
};
@@ -1538,10 +1539,13 @@ async function handlespot(spot, spot_source = "cluster") {
15381539
return;
15391540
}
15401541

1542+
// Store original spotter callsign before normalization for skimmer parsing
1543+
const originalSpotter = spot.spotter;
1544+
15411545
// Normalize spotter callsign - strip RBN/cluster suffixes like -#, -15, etc.
15421546
// This simplifies deduplication and improves cache hit rates
15431547
spot.spotter = normalizeSpotterCallsign(spot.spotter);
1544-
1548+
15451549
// Normalize frequency for consistency (based on Wavelog PR #2514)
15461550
// All frequencies should be in kHz with 1 decimal place
15471551
const normalizedFreq = normalizeFrequency(spot.frequency);
@@ -1602,7 +1606,14 @@ async function handlespot(spot, spot_source = "cluster") {
16021606
_sourceData: sourceData // Attach source data for GUI display
16031607
}
16041608

1605-
//do DXCC lookup (with timeout protection)
1609+
// Parse skimmer messages if this is a skimmer spot
1610+
// Only processes spots with -# suffix in spotter callsign
1611+
const skimmerData = parseSkimmerMessage(spot.message, originalSpotter);
1612+
if (skimmerData) {
1613+
dxSpot.skimmer = skimmerData;
1614+
}
1615+
1616+
//do DXCC lookup (with timeout protection)
16061617
try {
16071618
dxSpot.dxcc_spotter = await dxcc_lookup(spot.spotter);
16081619
dxSpot.dxcc_spotted = await dxcc_lookup(spot.spotted);
@@ -1934,23 +1945,6 @@ function removeFromIndexes(spot) {
19341945
/**
19351946
* Rebuilds all indexes from scratch
19361947
*/
1937-
function rebuildIndexes() {
1938-
bandIndex.clear();
1939-
frequencyIndex.clear();
1940-
sourceIndex.clear();
1941-
spotKeyIndex.clear();
1942-
1943-
// Clear statistics indexes
1944-
modeTypeStats.phone = 0;
1945-
modeTypeStats.digi = 0;
1946-
modeTypeStats.cw = 0;
1947-
modeTypeStats.unknown = 0;
1948-
Object.keys(continentStats).forEach(key => delete continentStats[key]);
1949-
Object.keys(continentDeStats).forEach(key => delete continentDeStats[key]);
1950-
rbnSpotCount = 0;
1951-
1952-
spots.forEach(spot => updateIndexes(spot));
1953-
}
19541948

19551949
/**
19561950
* Removes spots older than the configured maximum age
@@ -2008,17 +2002,11 @@ function logStatistics() {
20082002
const analyticsSummary = analytics.getSummary();
20092003
totalRequests = analyticsSummary.summary?.totalRequests || 0;
20102004

2011-
// Debug: log if analytics seems disabled or has no data
2012-
if (totalRequests === 0 && Object.keys(analyticsSummary.clients || {}).length === 0) {
2013-
console.log(`[Core] [DEBUG] Analytics enabled but no requests tracked yet`);
2014-
}
20152005
} catch (e) {
20162006
console.error(`[Core] [ERROR] Failed to get analytics summary: ${e.message}`);
20172007
}
20182008
} else {
2019-
console.log(`[Core] [DEBUG] Analytics disabled (enabled: ${config.analyticsEnabled})`);
2020-
}
2021-
2009+
}
20222010
console.log(`[Core] ═══════════════════════════════════════════════════════════════════════════`);
20232011
console.log(`[Core] Statistics: Uptime=${uptimeHours}h ${uptimeMinutes}m | Requests=${totalRequests} | Total Spots=${spots.length}`);
20242012
console.log(`[Core] Sources: ${sourceList || 'none'}`);

‎lib/utils.js‎

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -168,12 +168,12 @@ function toKHz(freqMHz) {
168168
* Normalizes frequency to consistent kHz format with 1 decimal place.
169169
* This ensures frequency consistency across all sources (DXCluster, POTA, SOTA, RBN).
170170
* Handles both kHz and Hz inputs automatically.
171-
*
171+
*
172172
* Based on Wavelog PR #2514 fix for inconsistent frequency format.
173-
*
173+
*
174174
* @param {number|string} frequency - Frequency in kHz or Hz (auto-detected).
175175
* @returns {number} - Normalized frequency in kHz with 1 decimal precision, or NaN if invalid.
176-
*
176+
*
177177
* @example
178178
* normalizeFrequency(14074) // 14074.0
179179
* normalizeFrequency(14074.1) // 14074.1
@@ -182,30 +182,74 @@ function toKHz(freqMHz) {
182182
*/
183183
function normalizeFrequency(frequency) {
184184
const freq = Number(frequency);
185-
185+
186186
// Validate input
187187
if (!Number.isFinite(freq) || freq <= 0) {
188188
return NaN;
189189
}
190-
190+
191191
// Auto-detect Hz vs kHz based on magnitude
192192
// If frequency > 1 MHz (1000 kHz), assume it's in Hz
193193
let freqInKHz = freq;
194194
if (freq >= 1000000) {
195195
// Convert Hz to kHz
196196
freqInKHz = freq / 1000;
197197
}
198-
198+
199199
// Validate frequency range (30 kHz to 300 GHz = 300,000,000 kHz)
200200
if (freqInKHz < 30 || freqInKHz > 300000000) {
201201
return NaN;
202202
}
203-
203+
204204
// Round to 1 decimal place for consistency
205205
// This matches DXCluster format (e.g., 14074.1)
206206
return Math.round(freqInKHz * 10) / 10;
207207
}
208208

209+
/**
210+
* Parse skimmer message into structured components.
211+
* Only processes skimmer spots (those with -# suffix in spotter callsign).
212+
* Extracts mode, SNR, speed, and type from skimmer messages.
213+
*
214+
* @param {string} message - Skimmer message (e.g., "CW 34 dB 28 WPM CQ")
215+
* @param {string} spotter - Spotter callsign to check for -# suffix
216+
* @returns {object|null} Parsed skimmer data or null if not a skimmer spot or parsing fails
217+
*
218+
* @example
219+
* parseSkimmerMessage("CW 34 dB 28 WPM CQ", "LZ5DI-#")
220+
* // Returns: { mode: "CW", snr: 34, speed: 28, type: "CQ" }
221+
*
222+
* parseSkimmerMessage("tnx QRP 1W QSO 73 GL", "9Y4M")
223+
* // Returns: null (not a skimmer spot)
224+
*/
225+
function parseSkimmerMessage(message, spotter) {
226+
// Only process skimmer spots (those with -# suffix)
227+
if (!spotter || !spotter.includes('-#')) {
228+
return null;
229+
}
230+
231+
// Validate message is a string
232+
if (typeof message !== 'string') {
233+
return null;
234+
}
235+
236+
// Pattern: MODE SNR dB SPEED WPM TYPE
237+
// Example: "CW 34 dB 28 WPM CQ" or "CW 4 dB 18 WPM BEACON"
238+
const regex = /^(\w+)\s+(\d+)\s+dB\s+(\d+)\s+WPM\s+(\w+).*$/;
239+
const match = message.match(regex);
240+
241+
if (!match) {
242+
return null;
243+
}
244+
245+
return {
246+
mode: match[1], // e.g., "CW", "RTTY"
247+
snr: parseInt(match[2], 10), // e.g., 34 (without "dB")
248+
speed: parseInt(match[3], 10),// e.g., 28 (without "WPM")
249+
type: match[4] // e.g., "CQ", "BEACON"
250+
};
251+
}
252+
209253
module.exports = {
210254
logConnectionState,
211255
toUcWord,
@@ -215,5 +259,6 @@ module.exports = {
215259
sleepNow,
216260
getAllowedDeviation,
217261
toKHz,
218-
normalizeFrequency
262+
normalizeFrequency,
263+
parseSkimmerMessage
219264
};

0 commit comments

Comments
 (0)