Skip to content

Commit 55ad151

Browse files
authored
Merge pull request #128 from MrAlders0n/copilot/update-status-messages-guidelines
Implement passive RX log API integration with parallel batch aggregation
2 parents fe28924 + fdf5211 commit 55ad151

1 file changed

Lines changed: 214 additions & 1 deletion

File tree

content/wardrive.js

Lines changed: 214 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ const OTTAWA_GEOFENCE_RADIUS_M = 150000; // 150 km in meters
7373
// Distance-Based Ping Filtering
7474
const MIN_PING_DISTANCE_M = 25; // Minimum distance (25m) between pings
7575

76+
// Passive RX Log Batch Configuration
77+
const RX_BATCH_DISTANCE_M = 25; // Distance trigger for flushing batch (separate from MIN_PING_DISTANCE_M for independent tuning)
78+
const RX_BATCH_TIMEOUT_MS = 30000; // Max hold time per repeater (30 sec)
79+
const RX_BATCH_MIN_WAIT_MS = 2000; // Min wait to collect burst RX events
80+
7681
// MeshMapper API Configuration
7782
const MESHMAPPER_API_URL = "https://yow.meshmapper.net/wardriving-api.php";
7883
const MESHMAPPER_CAPACITY_CHECK_URL = "https://yow.meshmapper.net/capacitycheck.php";
@@ -194,7 +199,8 @@ const state = {
194199
isListening: false, // Whether we're currently listening passively
195200
rxLogHandler: null, // Handler function for passive rx_log events
196201
entries: [] // Array of { repeaterId, snr, lat, lon, timestamp }
197-
}
202+
},
203+
rxBatchBuffer: new Map() // Map<repeaterId, {firstLocation, firstTimestamp, samples, timeoutId}>
198204
};
199205

200206
// Status message management with minimum visibility duration
@@ -548,6 +554,17 @@ function cleanupAllTimers() {
548554

549555
// Clear wardrive session ID
550556
state.wardriveSessionId = null;
557+
558+
// Clear RX batch buffer and cancel any pending timeouts
559+
if (state.rxBatchBuffer && state.rxBatchBuffer.size > 0) {
560+
for (const [repeaterId, batch] of state.rxBatchBuffer.entries()) {
561+
if (batch.timeoutId) {
562+
clearTimeout(batch.timeoutId);
563+
}
564+
}
565+
state.rxBatchBuffer.clear();
566+
debugLog("RX batch buffer cleared");
567+
}
551568
}
552569

553570
function enableControls(connected) {
@@ -1861,6 +1878,9 @@ async function handlePassiveRxLogging(packet, data) {
18611878

18621879
debugLog(`[PASSIVE RX] ✅ Observation logged: repeater=${repeaterId}, snr=${data.lastSnr}, location=${lat.toFixed(5)},${lon.toFixed(5)}`);
18631880

1881+
// Handle batch tracking for API (parallel batch per repeater)
1882+
handlePassiveRxForAPI(repeaterId, data.lastSnr, { lat, lon });
1883+
18641884
} catch (error) {
18651885
debugError(`[PASSIVE RX] Error processing passive RX: ${error.message}`, error);
18661886
}
@@ -2023,6 +2043,196 @@ async function postRxLogToMeshMapperAPI(entries) {
20232043
debugLog(`[PASSIVE RX] Would post ${entries.length} RX log entries to API (not implemented yet)`);
20242044
}
20252045

2046+
// ---- Passive RX Batch API Integration ----
2047+
2048+
/**
2049+
* Handle passive RX event for API batching
2050+
* Each repeater is tracked independently with its own batch and timer
2051+
* @param {string} repeaterId - Repeater ID (hex string)
2052+
* @param {number} snr - Signal to noise ratio
2053+
* @param {Object} currentLocation - Current GPS location {lat, lon}
2054+
*/
2055+
function handlePassiveRxForAPI(repeaterId, snr, currentLocation) {
2056+
debugLog(`[RX BATCH] Processing RX event: repeater=${repeaterId}, snr=${snr}`);
2057+
2058+
// Get or create batch for this repeater
2059+
let batch = state.rxBatchBuffer.get(repeaterId);
2060+
2061+
if (!batch) {
2062+
// First RX from this repeater - create new batch
2063+
debugLog(`[RX BATCH] Creating new batch for repeater ${repeaterId}`);
2064+
batch = {
2065+
firstLocation: { lat: currentLocation.lat, lng: currentLocation.lon },
2066+
firstTimestamp: Date.now(),
2067+
samples: [],
2068+
timeoutId: null
2069+
};
2070+
state.rxBatchBuffer.set(repeaterId, batch);
2071+
2072+
// Set timeout for this repeater (independent timer)
2073+
batch.timeoutId = setTimeout(() => {
2074+
debugLog(`[RX BATCH] Timeout triggered for repeater ${repeaterId} after ${RX_BATCH_TIMEOUT_MS}ms`);
2075+
flushBatch(repeaterId, 'timeout');
2076+
}, RX_BATCH_TIMEOUT_MS);
2077+
2078+
debugLog(`[RX BATCH] Timeout set for repeater ${repeaterId}: ${RX_BATCH_TIMEOUT_MS}ms`);
2079+
}
2080+
2081+
// Add sample to batch
2082+
const sample = {
2083+
snr,
2084+
location: { lat: currentLocation.lat, lng: currentLocation.lon },
2085+
timestamp: Date.now()
2086+
};
2087+
batch.samples.push(sample);
2088+
2089+
debugLog(`[RX BATCH] Sample added to batch for repeater ${repeaterId}: sample_count=${batch.samples.length}`);
2090+
2091+
// Check distance trigger (has user moved >= RX_BATCH_DISTANCE_M from first location?)
2092+
const distance = calculateHaversineDistance(
2093+
currentLocation.lat,
2094+
currentLocation.lon,
2095+
batch.firstLocation.lat,
2096+
batch.firstLocation.lng
2097+
);
2098+
2099+
debugLog(`[RX BATCH] Distance check for repeater ${repeaterId}: ${distance.toFixed(2)}m from first location (threshold=${RX_BATCH_DISTANCE_M}m)`);
2100+
2101+
if (distance >= RX_BATCH_DISTANCE_M) {
2102+
debugLog(`[RX BATCH] Distance threshold met for repeater ${repeaterId}, flushing batch`);
2103+
flushBatch(repeaterId, 'distance');
2104+
}
2105+
}
2106+
2107+
/**
2108+
* Flush a single repeater's batch - aggregate and post to API
2109+
* @param {string} repeaterId - Repeater ID to flush
2110+
* @param {string} trigger - What caused the flush: 'distance' | 'timeout' | 'session_end'
2111+
*/
2112+
function flushBatch(repeaterId, trigger) {
2113+
debugLog(`[RX BATCH] Flushing batch for repeater ${repeaterId}, trigger=${trigger}`);
2114+
2115+
const batch = state.rxBatchBuffer.get(repeaterId);
2116+
if (!batch || batch.samples.length === 0) {
2117+
debugLog(`[RX BATCH] No batch to flush for repeater ${repeaterId}`);
2118+
return;
2119+
}
2120+
2121+
// Clear timeout if it exists
2122+
if (batch.timeoutId) {
2123+
clearTimeout(batch.timeoutId);
2124+
debugLog(`[RX BATCH] Cleared timeout for repeater ${repeaterId}`);
2125+
}
2126+
2127+
// Calculate aggregations
2128+
const snrValues = batch.samples.map(s => s.snr);
2129+
const snrAvg = snrValues.reduce((sum, val) => sum + val, 0) / snrValues.length;
2130+
const snrMax = Math.max(...snrValues);
2131+
const snrMin = Math.min(...snrValues);
2132+
const sampleCount = batch.samples.length;
2133+
const timestampStart = batch.firstTimestamp;
2134+
const timestampEnd = batch.samples[batch.samples.length - 1].timestamp;
2135+
2136+
// Build API entry
2137+
const entry = {
2138+
repeater_id: repeaterId,
2139+
location: batch.firstLocation,
2140+
snr_avg: parseFloat(snrAvg.toFixed(3)),
2141+
snr_max: parseFloat(snrMax.toFixed(3)),
2142+
snr_min: parseFloat(snrMin.toFixed(3)),
2143+
sample_count: sampleCount,
2144+
timestamp_start: timestampStart,
2145+
timestamp_end: timestampEnd,
2146+
trigger: trigger
2147+
};
2148+
2149+
debugLog(`[RX BATCH] Aggregated entry for repeater ${repeaterId}:`, entry);
2150+
debugLog(`[RX BATCH] snr_avg=${snrAvg.toFixed(3)}, snr_max=${snrMax.toFixed(3)}, snr_min=${snrMin.toFixed(3)}`);
2151+
debugLog(`[RX BATCH] sample_count=${sampleCount}, duration=${((timestampEnd - timestampStart) / 1000).toFixed(1)}s`);
2152+
2153+
// Queue for API posting
2154+
queueApiPost(entry);
2155+
2156+
// Remove batch from buffer (cleanup)
2157+
state.rxBatchBuffer.delete(repeaterId);
2158+
debugLog(`[RX BATCH] Batch removed from buffer for repeater ${repeaterId}`);
2159+
}
2160+
2161+
/**
2162+
* Flush all active batches (called on session end, disconnect, etc.)
2163+
* @param {string} trigger - What caused the flush: 'session_end' | 'disconnect' | etc.
2164+
*/
2165+
function flushAllBatches(trigger = 'session_end') {
2166+
debugLog(`[RX BATCH] Flushing all batches, trigger=${trigger}, active_batches=${state.rxBatchBuffer.size}`);
2167+
2168+
if (state.rxBatchBuffer.size === 0) {
2169+
debugLog(`[RX BATCH] No batches to flush`);
2170+
return;
2171+
}
2172+
2173+
// Iterate all repeater batches and flush each one
2174+
const repeaterIds = Array.from(state.rxBatchBuffer.keys());
2175+
for (const repeaterId of repeaterIds) {
2176+
flushBatch(repeaterId, trigger);
2177+
}
2178+
2179+
debugLog(`[RX BATCH] All batches flushed: ${repeaterIds.length} repeaters`);
2180+
}
2181+
2182+
/**
2183+
* Queue an entry for API posting
2184+
* For now: console.log in debug mode
2185+
* Future: Actual HTTP POST to MESHMAPPER_RX_LOG_API_URL
2186+
* @param {Object} entry - The aggregated entry to post
2187+
*/
2188+
function queueApiPost(entry) {
2189+
// Validate session_id exists
2190+
if (!state.wardriveSessionId) {
2191+
debugWarn(`[RX BATCH API] Cannot post: no session_id available`);
2192+
return;
2193+
}
2194+
2195+
// Build API payload
2196+
const payload = {
2197+
session_id: state.wardriveSessionId,
2198+
entries: [entry]
2199+
};
2200+
2201+
// DEBUG MODE: Console log the payload
2202+
debugLog(`[RX BATCH API] ===== API PAYLOAD (DEBUG MODE) =====`);
2203+
console.log('[RX BATCH API] Would POST to:', MESHMAPPER_RX_LOG_API_URL || '(URL not configured)');
2204+
console.log('[RX BATCH API] Payload:', JSON.stringify(payload, null, 2));
2205+
debugLog(`[RX BATCH API] =====================================`);
2206+
2207+
// PRODUCTION CODE (commented out until ready)
2208+
/*
2209+
if (!MESHMAPPER_RX_LOG_API_URL) {
2210+
debugWarn('[RX BATCH API] API URL not configured, skipping POST');
2211+
return;
2212+
}
2213+
2214+
// Post to API
2215+
fetch(MESHMAPPER_RX_LOG_API_URL, {
2216+
method: 'POST',
2217+
headers: { 'Content-Type': 'application/json' },
2218+
body: JSON.stringify(payload)
2219+
})
2220+
.then(response => {
2221+
if (!response.ok) {
2222+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
2223+
}
2224+
return response.json();
2225+
})
2226+
.then(data => {
2227+
debugLog(`[RX BATCH API] ✅ Successfully posted entry for repeater ${entry.repeater_id}`);
2228+
debugLog(`[RX BATCH API] Response:`, data);
2229+
})
2230+
.catch(error => {
2231+
debugError(`[RX BATCH API] ❌ Failed to post entry for repeater ${entry.repeater_id}: ${error.message}`);
2232+
});
2233+
*/
2234+
}
2235+
20262236
// ---- Mobile Session Log Bottom Sheet ----
20272237

20282238
/**
@@ -3126,6 +3336,9 @@ async function connect() {
31263336
stopRepeaterTracking(); // Stop repeater echo tracking
31273337
stopPassiveRxListening(); // Stop passive RX listening
31283338

3339+
// Flush all pending RX batch data before cleanup
3340+
flushAllBatches('disconnect');
3341+
31293342
// Clean up all timers
31303343
cleanupAllTimers();
31313344

0 commit comments

Comments
 (0)