diff --git a/meshview/lang/en.json b/meshview/lang/en.json
index 62a5cc1..a471e11 100644
--- a/meshview/lang/en.json
+++ b/meshview/lang/en.json
@@ -218,6 +218,11 @@
"show_qr_code": "Show QR Code",
"toggle_coverage": "Predicted Coverage",
"toggle_observed_coverage": "Observed Coverage",
+ "observed_settings": "Observed Settings",
+ "max_hops": "Max Hops",
+ "bearing_step": "Bearing Step",
+ "packets_limit": "Packets",
+ "refresh": "Refresh",
"location_required": "Location required for coverage",
"coverage_help": "Coverage Help",
"share_contact_qr": "Share Contact QR",
diff --git a/meshview/lang/es.json b/meshview/lang/es.json
index 442f4aa..ee7bb52 100644
--- a/meshview/lang/es.json
+++ b/meshview/lang/es.json
@@ -204,6 +204,11 @@
"show_qr_code": "Mostrar código QR",
"toggle_coverage": "Cobertura predicha",
"toggle_observed_coverage": "Cobertura observada",
+ "observed_settings": "Ajustes observados",
+ "max_hops": "Máx. saltos",
+ "bearing_step": "Paso de rumbo",
+ "packets_limit": "Paquetes",
+ "refresh": "Actualizar",
"location_required": "Se requiere ubicación para la cobertura",
"coverage_help": "Ayuda de cobertura",
"share_contact_qr": "Compartir contacto QR",
diff --git a/meshview/templates/node.html b/meshview/templates/node.html
index 802a7b8..b71576e 100644
--- a/meshview/templates/node.html
+++ b/meshview/templates/node.html
@@ -348,6 +348,24 @@
Coverage Help
+
+ Observed Settings
+
+
+
+
+
@@ -647,6 +665,7 @@ let currentPacketRows = [];
let map, markers = {};
let coverageLayer = null;
let observedCoverageLayer = null;
+let observedControlsVisible = false;
let chartData = {}, neighborData = { ids:[], names:[], snrs:[] };
let fromNodeId = new URLSearchParams(window.location.search).get("from_node_id");
@@ -743,6 +762,9 @@ async function loadNodeInfo(){
const hasLocation = Boolean(node.last_lat && node.last_long);
coverageHelp.style.display = hasLocation ? "" : "none";
}
+ if (!(node.last_lat && node.last_long)) {
+ setObservedControlsVisible(false);
+ }
let lastSeen = "—";
if (node.last_seen_us) {
@@ -881,6 +903,7 @@ async function toggleObservedCoverage() {
if (observedCoverageLayer) {
map.removeLayer(observedCoverageLayer);
observedCoverageLayer = null;
+ setObservedControlsVisible(false);
return;
}
if (coverageLayer) {
@@ -892,8 +915,12 @@ async function toggleObservedCoverage() {
if (!nodeId) return;
try {
+ setObservedControlsVisible(true);
+ const maxHops = getObservedNumber("observedMaxHops", 1, 0, 10);
+ const bearingStep = getObservedNumber("observedBearingStep", 5, 1, 90);
+ const packetsLimit = getObservedNumber("observedPacketsLimit", 50, 1, 1000);
const res = await fetch(
- `/api/coverage_observed/${encodeURIComponent(nodeId)}?max_hops=1&bearing_step=10&packets_limit=10`
+ `/api/coverage_observed/${encodeURIComponent(nodeId)}?max_hops=${maxHops}&bearing_step=${bearingStep}&packets_limit=${packetsLimit}`
);
if (!res.ok) {
console.error("Observed coverage request failed", res.status);
@@ -918,6 +945,30 @@ async function toggleObservedCoverage() {
}
}
+function setObservedControlsVisible(show) {
+ const controls = document.getElementById("observedCoverageControls");
+ if (!controls) return;
+ observedControlsVisible = show;
+ controls.style.display = show ? "" : "none";
+}
+
+function getObservedNumber(id, fallback, min, max) {
+ const el = document.getElementById(id);
+ if (!el) return fallback;
+ const num = Number(el.value);
+ if (Number.isNaN(num)) return fallback;
+ return Math.max(min, Math.min(max, num));
+}
+
+async function refreshObservedCoverage() {
+ if (!observedCoverageLayer) {
+ await toggleObservedCoverage();
+ return;
+ }
+ await toggleObservedCoverage();
+ await toggleObservedCoverage();
+}
+
function hideMap(){
const mapDiv = document.getElementById("map");
if (mapDiv) {
diff --git a/meshview/web_api/api.py b/meshview/web_api/api.py
index 0146832..4cfc88b 100644
--- a/meshview/web_api/api.py
+++ b/meshview/web_api/api.py
@@ -58,6 +58,9 @@ def _bearing_deg(lat1, lon1, lat2, lon2):
return (bearing + 360.0) % 360.0
+OBSERVED_MAX_DISTANCE_KM = 50.0
+
+
def init_api_module(packet_class, seq_regex, lang_dir):
"""Initialize API module with dependencies from main web module."""
global Packet, SEQ_REGEX, LANG_DIR
@@ -1240,6 +1243,8 @@ async def api_coverage_observed(request):
gw_lat = gw.last_lat * 1e-7
gw_lon = gw.last_long * 1e-7
dist_km = _haversine_km(src_lat, src_lon, gw_lat, gw_lon)
+ if dist_km > OBSERVED_MAX_DISTANCE_KM:
+ continue
bearing = _bearing_deg(src_lat, src_lon, gw_lat, gw_lon)
bucket = int(bearing // bearing_step) * bearing_step