Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 76 additions & 27 deletions content/wardrive.js
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,7 @@ const state = {
capturedPingCoords: null, // { lat, lon, accuracy, noisefloor, timestamp } captured at ping time, used for API post after RX window
devicePublicKey: null, // Hex string of device's public key (used for auth)
deviceModel: null, // Manufacturer/model string exposed by companion
firmwareVersion: null, // Parsed firmware version { major, minor, patch } or null for nightly/unparseable
autoPowerSet: false, // Whether power was automatically set based on device model
lastNoiseFloor: null, // Most recent noise floor read from companion (dBm) or 'ERR'
noiseFloorUpdateTimer: null, // Timer for periodic noise floor updates (5s interval)
Expand Down Expand Up @@ -1766,6 +1767,40 @@ function updateDistanceUi() {
}
}

/**
* Parse firmware version from device model string
* @param {string} model - Device model string (e.g., "Elecrow ThinkNode-M1 v1.11.0-6d32193")
* @returns {{major: number, minor: number, patch: number}|null} Parsed version or null if unparseable/nightly
*/
function parseFirmwareVersion(model) {
if (!model) return null;
const match = model.match(/v(\d+)\.(\d+)\.(\d+)/);
if (!match) {
debugLog(`[BLE] Firmware version not found in model string (likely nightly build)`);
return null;
}
return {
major: parseInt(match[1], 10),
minor: parseInt(match[2], 10),
patch: parseInt(match[3], 10)
};
}

/**
* Check if firmware version supports noisefloor collection (requires 1.11.0+)
* @param {{major: number, minor: number, patch: number}|null} version - Parsed firmware version
* @returns {boolean} True if noisefloor is supported
*/
function firmwareSupportsNoisefloor(version) {
// Null version means nightly build - assume supported (bleeding edge)
if (version === null) return true;
// Version 2.x.x or higher always supported
if (version.major > 1) return true;
// Version 1.11.0+ supported
if (version.major === 1 && version.minor >= 11) return true;
return false;
}

/**
* Start periodic noise floor updates (5 second interval)
* Only called if feature is supported by firmware
Expand All @@ -1783,8 +1818,8 @@ function startNoiseFloorUpdates() {
}

try {
// Don't pass timeout - let the interval handle cadence, avoids library timeout bug
const stats = await state.connection.getRadioStats(null);
// 5 second timeout as safety fallback
const stats = await state.connection.getRadioStats(5000);
if (stats && typeof stats.noiseFloor !== 'undefined') {
state.lastNoiseFloor = stats.noiseFloor;
debugLog(`[BLE] Noise floor updated: ${state.lastNoiseFloor}`);
Expand All @@ -1795,6 +1830,7 @@ function startNoiseFloorUpdates() {
}
} catch (e) {
// Silently ignore periodic update failures - keep showing last known value
debugLog(`[BLE] Noise floor update failed: ${e && e.message ? e.message : String(e)}`);
}
}, 5000);

Expand Down Expand Up @@ -5774,45 +5810,58 @@ async function connect() {
debugLog(`[BLE] deviceQuery response received: firmwareVer=${deviceInfo?.firmwareVer}, model=${deviceInfo?.manufacturerModel}`);
state.deviceModel = deviceInfo?.manufacturerModel || "-";
debugLog(`[BLE] Device model stored: ${state.deviceModel}`);
// Parse firmware version for feature detection
state.firmwareVersion = parseFirmwareVersion(state.deviceModel);
debugLog(`[BLE] Parsed firmware version: ${state.firmwareVersion ? `v${state.firmwareVersion.major}.${state.firmwareVersion.minor}.${state.firmwareVersion.patch}` : 'null (nightly/unparseable)'}`);
// Don't update deviceModelEl here - autoSetPowerLevel() will set it to shortName or "Unknown"
} catch (e) {
debugError(`[BLE] deviceQuery failed: ${e && e.message ? e.message : e}`);
state.deviceModel = "-";
state.firmwareVersion = null;
if (deviceModelEl) deviceModelEl.textContent = "-";
}

// Auto-configure radio power based on device model
await autoSetPowerLevel();

// Immediately attempt to read radio stats (noise floor) on connect
debugLog("[BLE] Requesting radio stats on connect");
try {
// Don't pass timeout - avoids library timeout bug
const stats = await conn.getRadioStats(null);
debugLog(`[BLE] getRadioStats returned: ${JSON.stringify(stats)}`);
if (stats && typeof stats.noiseFloor !== 'undefined') {
state.lastNoiseFloor = stats.noiseFloor;
debugLog(`[BLE] Radio stats acquired on connect: noiseFloor=${state.lastNoiseFloor}`);
} else {
debugWarn(`[BLE] Radio stats response missing noiseFloor field: ${JSON.stringify(stats)}`);
state.lastNoiseFloor = null;
// Check if firmware supports noisefloor before requesting
if (firmwareSupportsNoisefloor(state.firmwareVersion)) {
// Immediately attempt to read radio stats (noise floor) on connect
debugLog("[BLE] Requesting radio stats on connect");
try {
// 5 second timeout as safety fallback
const stats = await conn.getRadioStats(5000);
debugLog(`[BLE] getRadioStats returned: ${JSON.stringify(stats)}`);
if (stats && typeof stats.noiseFloor !== 'undefined') {
state.lastNoiseFloor = stats.noiseFloor;
debugLog(`[BLE] Radio stats acquired on connect: noiseFloor=${state.lastNoiseFloor}`);
} else {
debugWarn(`[BLE] Radio stats response missing noiseFloor field: ${JSON.stringify(stats)}`);
state.lastNoiseFloor = null;
}
} catch (e) {
// Timeout likely means firmware doesn't support GetStats command yet
if (e && e.message && e.message.includes('timeout')) {
debugLog(`[BLE] getRadioStats not supported by companion firmware (timeout)`);
} else {
debugWarn(`[BLE] getRadioStats failed on connect: ${e && e.message ? e.message : String(e)}`);
}
state.lastNoiseFloor = null; // Show '-' instead of 'ERR' for unsupported feature
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment states "Show '-' instead of 'ERR' for unsupported feature" but the actual UI behavior (in setConnStatus at line 1208) displays "Firmware 1.11+" when state.lastNoiseFloor is null, not "-". The comment should be updated to reflect the actual UI behavior.

Suggested change
state.lastNoiseFloor = null; // Show '-' instead of 'ERR' for unsupported feature
state.lastNoiseFloor = null; // Flag as unsupported so UI shows "Firmware 1.11+" instead of "ERR"

Copilot uses AI. Check for mistakes.
}
} catch (e) {
// Timeout likely means firmware doesn't support GetStats command yet
if (e && e.message && e.message.includes('timeout')) {
debugLog(`[BLE] getRadioStats not supported by companion firmware (timeout)`);

// Start periodic noise floor updates if feature is supported
if (state.lastNoiseFloor !== null) {
startNoiseFloorUpdates();
debugLog("[BLE] Started periodic noise floor updates (5s interval)");
} else {
debugWarn(`[BLE] getRadioStats failed on connect: ${e && e.message ? e.message : String(e)}`);
debugLog("[BLE] Noise floor updates not started (feature unsupported by firmware)");
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The debug log message is misleading when the initial radio stats request fails. At this point, we already know the firmware supports noisefloor (checked at line 5828), so the message "feature unsupported by firmware" at line 5857 is inaccurate. It should indicate that the initial request failed, not that the feature is unsupported. Consider changing the message to something like "Noise floor updates not started (initial request failed)" to avoid confusion during debugging.

Suggested change
debugLog("[BLE] Noise floor updates not started (feature unsupported by firmware)");
debugLog("[BLE] Noise floor updates not started (initial request failed)");

Copilot uses AI. Check for mistakes.
}
state.lastNoiseFloor = null; // Show '--' instead of 'ERR' for unsupported feature
}

// Start periodic noise floor updates if feature is supported
if (state.lastNoiseFloor !== null) {
startNoiseFloorUpdates();
debugLog("[BLE] Started periodic noise floor updates (5s interval)");
} else {
debugLog("[BLE] Noise floor updates not started (feature unsupported by firmware)");
// Firmware too old for noisefloor
const versionStr = state.firmwareVersion ? `v${state.firmwareVersion.major}.${state.firmwareVersion.minor}.${state.firmwareVersion.patch}` : 'unknown';
debugLog(`[BLE] Skipping noisefloor - firmware ${versionStr} does not support it (requires 1.11.0+)`);
state.lastNoiseFloor = null;
addErrorLogEntry(`Noisefloor requires firmware 1.11.0+ (detected: ${versionStr})`, "Firmware Version");
}

updateAutoButton();
Expand Down
21 changes: 20 additions & 1 deletion docs/CONNECTION_WORKFLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,26 @@ connectBtn.addEventListener("click", async () => {
- Shows status: "Unknown device - select power manually" (yellow, persistent until power selected)
- Ping controls remain disabled
- Sets `state.autoPowerSet = false`
- Requests radio statistics from the companion (noise floor, last RSSI, SNR) and stores the result in application state. The connection bar displays `Noise: <value>`. If stats fetch fails, an error marker is shown (`Noise: ERR`) but connection proceeds.
- **Firmware Version Parsing**:
- Parses semver from model string using regex `/v(\d+)\.(\d+)\.(\d+)/`
- Example: `"Elecrow ThinkNode-M1 v1.11.0-6d32193"` → `{ major: 1, minor: 11, patch: 0 }`
- Nightly builds (e.g., `"nightly-e31c46f"`) return `null` (no semver found)
- Stores result in `state.firmwareVersion`
- **Noise Floor Collection (Version-Gated)**:
- Checks `firmwareSupportsNoisefloor(state.firmwareVersion)`:
- Returns `true` if: version is `null` (nightly), OR `major > 1`, OR `(major === 1 && minor >= 11)`
- Returns `false` for firmware < 1.11.0
- **If supported** (1.11.0+ or nightly):
- Requests radio statistics via `getRadioStats(5000)` with 5-second timeout
- Stores noise floor in `state.lastNoiseFloor`
- Connection bar displays `🔊 <value>dBm`
- Starts periodic 5-second updates via `startNoiseFloorUpdates()`
Comment on lines +156 to +158
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation implies that periodic updates are always started when firmware supports noisefloor, but the implementation only starts them if the initial radio stats request succeeds. Consider adding a clarification that periodic updates are only started if the initial request returns a valid noisefloor value. For example: "Starts periodic 5-second updates via startNoiseFloorUpdates() if initial request succeeds"

Suggested change
- Stores noise floor in `state.lastNoiseFloor`
- Connection bar displays `🔊 <value>dBm`
- Starts periodic 5-second updates via `startNoiseFloorUpdates()`
- **If initial request succeeds and returns a valid noisefloor value**:
- Stores noise floor in `state.lastNoiseFloor`
- Connection bar displays `🔊 <value>dBm`
- Starts periodic 5-second updates via `startNoiseFloorUpdates()`
- **If initial request fails or returns no valid noisefloor**:
- Periodic noise floor updates are **not** started
- Connection continues without noise floor display/updates

Copilot uses AI. Check for mistakes.
- **If not supported** (< 1.11.0):
- Skips `getRadioStats()` call entirely (prevents app freeze)
- Sets `state.lastNoiseFloor = null`
- Connection bar displays `🔊 -`
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation states the connection bar displays "🔊 -" when noisefloor is not supported, but the actual UI implementation (setConnStatus function at line 1208) displays "🔊 Firmware 1.11+" when state.lastNoiseFloor is null. The documentation should be updated to reflect the actual UI behavior.

Suggested change
- Connection bar displays `🔊 -`
- Connection bar displays `🔊 Firmware 1.11+`

Copilot uses AI. Check for mistakes.
- Adds error log entry: "Noisefloor requires firmware 1.11.0+ (detected: vX.X.X)"
- Connection proceeds normally
- Changes button to "Disconnect" (red)
- **Connection Status**: `"Connecting"` (blue, maintained)
- **Dynamic Status**: Auto-power status message (green) or unknown device warning (yellow)
Expand Down
Loading