Skip to content

Commit 71c94a6

Browse files
authored
Merge pull request #34 from MrAlders0n/copilot/add-session-ping-echo-tracker
Add repeater echo tracking to session ping logs
2 parents efc58d0 + 76b299d commit 71c94a6

1 file changed

Lines changed: 120 additions & 2 deletions

File tree

content/wardrive.js

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
// - Acquire wake lock during auto mode to keep screen awake
77

88
import { WebBleConnection } from "/content/mc/index.js"; // your BLE client
9+
import Constants from "/content/mc/constants.js";
10+
import Packet from "/content/mc/packet.js";
911

1012
// ---- Config ----
1113
const CHANNEL_NAME = "#wardriving"; // change to "#wardrive" if needed
@@ -15,6 +17,7 @@ const GPS_FRESHNESS_BUFFER_MS = 5000; // Buffer time for GPS freshness
1517
const GPS_ACCURACY_THRESHOLD_M = 100; // Maximum acceptable GPS accuracy in meters
1618
const MESHMAPPER_DELAY_MS = 7000; // Delay MeshMapper API call by 7 seconds
1719
const COOLDOWN_MS = 7000; // Cooldown period for manual ping and auto toggle
20+
const REPEATER_LISTEN_MS = 7000; // Listen for repeater echoes for 7 seconds
1821
const STATUS_UPDATE_DELAY_MS = 100; // Brief delay to ensure "Ping sent" status is visible
1922
const MAP_REFRESH_DELAY_MS = 1000; // Delay after API post to ensure backend updated
2023
const WARDROVE_KEY = new Uint8Array([
@@ -64,7 +67,10 @@ const state = {
6467
autoCountdownTimer: null, // Timer for auto-ping countdown display
6568
nextAutoPingTime: null, // Timestamp when next auto-ping will occur
6669
apiCountdownTimer: null, // Timer for API post countdown display
67-
apiPostTime: null // Timestamp when API post will occur
70+
apiPostTime: null, // Timestamp when API post will occur
71+
repeaterListenTimer: null, // Timer for stopping repeater listener
72+
repeaterData: null, // Current repeater collection data {sessionLi, repeaters: Map}
73+
repeaterLogListener: null // LogRxData event listener reference
6874
};
6975

7076
// ---- UI helpers ----
@@ -454,6 +460,113 @@ function buildPayload(lat, lon) {
454460
return `${PING_PREFIX} ${coordsStr} ${suffix}`;
455461
}
456462

463+
// ---- Repeater Tracking ----
464+
function startRepeaterTracking(sessionLi) {
465+
// Clean up any existing tracking
466+
stopRepeaterTracking();
467+
468+
// Initialize repeater data collection
469+
state.repeaterData = {
470+
sessionLi: sessionLi,
471+
repeaters: new Map() // Map of repeaterID -> SNR
472+
};
473+
474+
// Create listener for LogRxData events
475+
state.repeaterLogListener = (logData) => {
476+
try {
477+
// Check if repeater data collection is still active
478+
if (!state.repeaterData) return;
479+
480+
// Validate input data
481+
if (!logData || typeof logData.lastSnr !== 'number' || !logData.raw) return;
482+
483+
// Parse the packet from raw data
484+
let packet;
485+
try {
486+
packet = Packet.fromBytes(logData.raw);
487+
} catch (parseError) {
488+
console.warn("Failed to parse packet from LogRxData:", parseError);
489+
return;
490+
}
491+
492+
// Check if this is a group text message (our ping echo)
493+
// Verify path exists and has at least one byte (repeater ID)
494+
if (packet.getPayloadType() === Packet.PAYLOAD_TYPE_GRP_TXT &&
495+
packet.path && packet.path.length > 0) {
496+
// Extract repeater ID (first byte of path)
497+
const repeaterId = packet.path[0];
498+
499+
// Validate repeater ID is a valid byte value (0-255)
500+
// Note: repeaterId can be 0 (valid byte value), so we check type and range
501+
if (repeaterId === undefined || typeof repeaterId !== 'number' ||
502+
!Number.isInteger(repeaterId) || repeaterId < 0 || repeaterId > 255) {
503+
console.warn(`Invalid repeater ID: ${repeaterId}`);
504+
return;
505+
}
506+
507+
// SNR ranges from -12 to +12 dB
508+
// Note: logData.lastSnr is already processed (readInt8() / 4) by the connection layer
509+
const snr = Math.round(logData.lastSnr);
510+
511+
// Store or update repeater data
512+
// Keep the highest SNR value (closest to +12dB) for duplicate repeaters
513+
if (!state.repeaterData.repeaters.has(repeaterId) ||
514+
state.repeaterData.repeaters.get(repeaterId) < snr) {
515+
state.repeaterData.repeaters.set(repeaterId, snr);
516+
console.log(`Repeater detected: ID=${repeaterId}, SNR=${snr}dB`);
517+
}
518+
}
519+
} catch (e) {
520+
console.warn("Failed to parse repeater data:", e);
521+
}
522+
};
523+
524+
// Start listening for LogRxData events
525+
if (state.connection) {
526+
state.connection.on(Constants.PushCodes.LogRxData, state.repeaterLogListener);
527+
}
528+
529+
// Schedule stop after REPEATER_LISTEN_MS
530+
state.repeaterListenTimer = setTimeout(() => {
531+
stopRepeaterTracking();
532+
}, REPEATER_LISTEN_MS);
533+
}
534+
535+
function stopRepeaterTracking() {
536+
// Stop the timer
537+
if (state.repeaterListenTimer) {
538+
clearTimeout(state.repeaterListenTimer);
539+
state.repeaterListenTimer = null;
540+
}
541+
542+
// Remove the event listener
543+
if (state.repeaterLogListener && state.connection) {
544+
state.connection.off(Constants.PushCodes.LogRxData, state.repeaterLogListener);
545+
state.repeaterLogListener = null;
546+
}
547+
548+
// Update the session log entry with repeater data
549+
if (state.repeaterData && state.repeaterData.sessionLi) {
550+
const repeaters = state.repeaterData.repeaters;
551+
if (repeaters.size > 0) {
552+
// Format repeater data as [ID1(SNR1),ID2(SNR2),...]
553+
// SNR values range from -12 to +12 dB, e.g., [25(-8),21(-5),14(3)]
554+
const repeaterList = Array.from(repeaters.entries())
555+
.sort((a, b) => a[0] - b[0]) // Sort by repeater ID
556+
.map(([id, snr]) => `${id}(${snr})`)
557+
.join(',');
558+
559+
// Append to the existing text in the session log
560+
const currentText = state.repeaterData.sessionLi.textContent;
561+
state.repeaterData.sessionLi.textContent = `${currentText} [${repeaterList}]`;
562+
console.log(`Session ping updated with ${repeaters.size} repeater(s)`);
563+
}
564+
}
565+
566+
// Clear repeater data
567+
state.repeaterData = null;
568+
}
569+
457570
// ---- MeshMapper API ----
458571
async function postToMeshMapperAPI(lat, lon) {
459572
try {
@@ -616,7 +729,8 @@ async function sendPing(manual = false) {
616729
state.meshMapperTimer = null;
617730
}, MESHMAPPER_DELAY_MS);
618731

619-
const nowStr = new Date().toLocaleString();
732+
// Format timestamp as ISO 8601 without milliseconds: YYYY-MM-DDTHH:MM:SSZ
733+
const nowStr = new Date().toISOString().split('.')[0] + 'Z';
620734
if (lastPingEl) lastPingEl.textContent = `${nowStr}${payload}`;
621735

622736
// Session log
@@ -627,6 +741,9 @@ async function sendPing(manual = false) {
627741
sessionPingsEl.appendChild(li);
628742
// Auto-scroll to bottom when a new entry arrives
629743
sessionPingsEl.scrollTop = sessionPingsEl.scrollHeight;
744+
745+
// Start tracking repeater echoes for this ping
746+
startRepeaterTracking(li);
630747
}
631748
} catch (e) {
632749
console.error("Ping failed:", e);
@@ -762,6 +879,7 @@ async function connect() {
762879
}
763880
stopAutoCountdown();
764881
stopApiCountdown();
882+
stopRepeaterTracking();
765883
state.cooldownEndTime = null;
766884

767885
state.lastFix = null;

0 commit comments

Comments
 (0)