generated from Grigo/AndroidTemplate
fix elevation
This commit is contained in:
@@ -106,6 +106,8 @@ public class StatsExtractor {
|
|||||||
meta.put("fields", fields);
|
meta.put("fields", fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
meta.put("stats_at", System.currentTimeMillis() / 1000.0);
|
||||||
|
|
||||||
Double rangeM = matchDouble(rangePattern, normalized);
|
Double rangeM = matchDouble(rangePattern, normalized);
|
||||||
Double displayDbm = rssiDbm != null ? rssiDbm : txPower;
|
Double displayDbm = rssiDbm != null ? rssiDbm : txPower;
|
||||||
|
|
||||||
@@ -143,7 +145,7 @@ public class StatsExtractor {
|
|||||||
|| n.equals("snr") || n.contains("spreading factor") || n.equals("bandwidth")
|
|| n.equals("snr") || n.contains("spreading factor") || n.equals("bandwidth")
|
||||||
|| n.equals("packet") || n.contains("packet number") || n.equals("payload")
|
|| n.equals("packet") || n.contains("packet number") || n.equals("payload")
|
||||||
|| n.contains("on air") || n.contains("tx speed") || n.contains("rx speed")
|
|| n.contains("on air") || n.contains("tx speed") || n.contains("rx speed")
|
||||||
|| n.equals("per") || n.contains("rx quality");
|
|| n.equals("per") || n.contains("rx quality") || n.equals("timeout");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ExtractedStats empty(String frame) {
|
private static ExtractedStats empty(String frame) {
|
||||||
|
|||||||
@@ -379,7 +379,7 @@ def health():
|
|||||||
return {
|
return {
|
||||||
"ok": status["db_ok"],
|
"ok": status["db_ok"],
|
||||||
"ts": time.time(),
|
"ts": time.time(),
|
||||||
"api_build": "2026-06-16e",
|
"api_build": "2026-06-16f",
|
||||||
**status,
|
**status,
|
||||||
**elevation_status(),
|
**elevation_status(),
|
||||||
}
|
}
|
||||||
|
|||||||
+69
-31
@@ -59,8 +59,8 @@
|
|||||||
.radio-compare-head { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 6px; font-weight: 600; }
|
.radio-compare-head { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 6px; font-weight: 600; }
|
||||||
.radio-row { display: grid; grid-template-columns: 72px 1fr 1fr; gap: 6px; padding: 2px 0; }
|
.radio-row { display: grid; grid-template-columns: 72px 1fr 1fr; gap: 6px; padding: 2px 0; }
|
||||||
.radio-label { color: #aaa; }
|
.radio-label { color: #aaa; }
|
||||||
.radio-tx { color: #e94560; }
|
.radio-tx { color: #e94560; min-width: 0; word-break: break-all; }
|
||||||
.radio-rx { color: #4fc3f7; }
|
.radio-rx { color: #4fc3f7; min-width: 0; word-break: break-all; }
|
||||||
.radio-row .changed, .changed { background: #e9456033; border-radius: 3px; padding: 0 2px; }
|
.radio-row .changed, .changed { background: #e9456033; border-radius: 3px; padding: 0 2px; }
|
||||||
.radio-static summary { cursor: pointer; color: #aaa; margin: 4px 0; }
|
.radio-static summary { cursor: pointer; color: #aaa; margin: 4px 0; }
|
||||||
#stats .changed { background: #e9456033; border-radius: 3px; }
|
#stats .changed { background: #e9456033; border-radius: 3px; }
|
||||||
@@ -361,7 +361,7 @@
|
|||||||
{ position: 'topright', collapsed: true }
|
{ position: 'topright', collapsed: true }
|
||||||
).addTo(map);
|
).addTo(map);
|
||||||
|
|
||||||
const API_BUILD = '2026-06-16e';
|
const API_BUILD = '2026-06-16f';
|
||||||
|
|
||||||
const markers = {};
|
const markers = {};
|
||||||
let selectedId = null;
|
let selectedId = null;
|
||||||
@@ -511,7 +511,10 @@
|
|||||||
const p = Math.max(0, Math.min(1, progress));
|
const p = Math.max(0, Math.min(1, progress));
|
||||||
if (points.length === 1) {
|
if (points.length === 1) {
|
||||||
const one = points[0];
|
const one = points[0];
|
||||||
return { lat: Number(one.lat), lon: Number(one.lon), meta: one.meta, rssi: one.rssi };
|
return {
|
||||||
|
lat: Number(one.lat), lon: Number(one.lon), meta: one.meta, rssi: one.rssi,
|
||||||
|
pointTs: Number(one.ts),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const f = p * (points.length - 1);
|
const f = p * (points.length - 1);
|
||||||
const i = Math.floor(f);
|
const i = Math.floor(f);
|
||||||
@@ -520,13 +523,17 @@
|
|||||||
const a = points[i];
|
const a = points[i];
|
||||||
const b = points[j];
|
const b = points[j];
|
||||||
if (frac <= 0 || i === j) {
|
if (frac <= 0 || i === j) {
|
||||||
return { lat: Number(a.lat), lon: Number(a.lon), meta: a.meta, rssi: a.rssi };
|
return {
|
||||||
|
lat: Number(a.lat), lon: Number(a.lon), meta: a.meta, rssi: a.rssi,
|
||||||
|
pointTs: Number(a.ts),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
lat: Number(a.lat) + (Number(b.lat) - Number(a.lat)) * frac,
|
lat: Number(a.lat) + (Number(b.lat) - Number(a.lat)) * frac,
|
||||||
lon: Number(a.lon) + (Number(b.lon) - Number(a.lon)) * frac,
|
lon: Number(a.lon) + (Number(b.lon) - Number(a.lon)) * frac,
|
||||||
meta: frac < 0.5 ? a.meta : b.meta,
|
meta: frac < 0.5 ? a.meta : b.meta,
|
||||||
rssi: frac < 0.5 ? a.rssi : b.rssi,
|
rssi: frac < 0.5 ? a.rssi : b.rssi,
|
||||||
|
pointTs: Number(frac < 0.5 ? a.ts : b.ts),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,7 +571,7 @@
|
|||||||
function snapAtCursor(track, telemetryRows, cursor, roleFallback) {
|
function snapAtCursor(track, telemetryRows, cursor, roleFallback) {
|
||||||
const pos = trackPointAt(track, cursor);
|
const pos = trackPointAt(track, cursor);
|
||||||
if (pos?.meta && String(pos.meta).length > 2) {
|
if (pos?.meta && String(pos.meta).length > 2) {
|
||||||
const ts = timelineUseProgress ? null : cursor.t;
|
const ts = pos.pointTs ?? (timelineUseProgress ? null : cursor.t);
|
||||||
return snapFromTrackPoint(pos, ts, roleFallback || track?.role);
|
return snapFromTrackPoint(pos, ts, roleFallback || track?.role);
|
||||||
}
|
}
|
||||||
if (timelineUseProgress) {
|
if (timelineUseProgress) {
|
||||||
@@ -613,11 +620,12 @@
|
|||||||
|
|
||||||
function snapFromTrackPoint(pos, t, roleFallback) {
|
function snapFromTrackPoint(pos, t, roleFallback) {
|
||||||
if (!pos) return null;
|
if (!pos) return null;
|
||||||
|
const ts = pos.pointTs ?? t;
|
||||||
return {
|
return {
|
||||||
meta: pos.meta,
|
meta: pos.meta,
|
||||||
role: roleFallback,
|
role: roleFallback,
|
||||||
rssi: pos.rssi,
|
rssi: pos.rssi,
|
||||||
ts: t,
|
ts,
|
||||||
lat: pos.lat,
|
lat: pos.lat,
|
||||||
lon: pos.lon,
|
lon: pos.lon,
|
||||||
};
|
};
|
||||||
@@ -625,7 +633,12 @@
|
|||||||
|
|
||||||
function mergeTelCoords(tel, pos) {
|
function mergeTelCoords(tel, pos) {
|
||||||
if (!tel || !pos) return tel;
|
if (!tel || !pos) return tel;
|
||||||
return { ...tel, lat: pos.lat, lon: pos.lon };
|
return {
|
||||||
|
...tel,
|
||||||
|
lat: pos.lat,
|
||||||
|
lon: pos.lon,
|
||||||
|
ts: tel.ts ?? pos.pointTs ?? null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function snapAtTime(track, telemetryRows, t, roleFallback) {
|
function snapAtTime(track, telemetryRows, t, roleFallback) {
|
||||||
@@ -772,30 +785,45 @@
|
|||||||
return `${Number(tel.lat).toFixed(5)}, ${Number(tel.lon).toFixed(5)}`;
|
return `${Number(tel.lat).toFixed(5)}, ${Number(tel.lon).toFixed(5)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatPacketTime(tel) {
|
function formatTsValue(ts) {
|
||||||
if (!tel) return null;
|
if (ts == null || !Number.isFinite(Number(ts))) return null;
|
||||||
let ts = tel.ts;
|
|
||||||
if (tel.meta) {
|
|
||||||
let o = tel.meta;
|
|
||||||
if (typeof o === 'string') {
|
|
||||||
try { o = JSON.parse(o); } catch (e) { o = null; }
|
|
||||||
}
|
|
||||||
if (o) {
|
|
||||||
if (o.packet_ts != null) ts = o.packet_ts;
|
|
||||||
else if (o.ts != null) ts = o.ts;
|
|
||||||
else if (o.fields) {
|
|
||||||
for (const [k, v] of Object.entries(o.fields)) {
|
|
||||||
if (/time/i.test(k)) return String(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ts == null || !Number.isFinite(Number(ts)) || Number(ts) <= 1) return null;
|
|
||||||
const n = Number(ts);
|
const n = Number(ts);
|
||||||
|
if (n <= 1e8) return null;
|
||||||
const ms = n < 1e12 ? n * 1000 : n;
|
const ms = n < 1e12 ? n * 1000 : n;
|
||||||
return new Date(ms).toLocaleTimeString();
|
return new Date(ms).toLocaleTimeString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function packetTimeFromMetaFields(fields) {
|
||||||
|
if (!fields) return null;
|
||||||
|
const skip = /timeout|on\s*air|speed|airtime/i;
|
||||||
|
for (const [k, v] of Object.entries(fields)) {
|
||||||
|
const key = String(k).trim();
|
||||||
|
if (skip.test(key)) continue;
|
||||||
|
if (!/^(time|timestamp|packet.?time)$/i.test(key)) continue;
|
||||||
|
const text = String(v).trim();
|
||||||
|
if (/^\d+(\.\d+)?\s*ms$/i.test(text)) continue;
|
||||||
|
const parsed = formatTsValue(text);
|
||||||
|
if (parsed) return parsed;
|
||||||
|
if (text) return text;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPacketTime(tel) {
|
||||||
|
if (!tel) return null;
|
||||||
|
const direct = formatTsValue(tel.ts);
|
||||||
|
if (direct) return direct;
|
||||||
|
if (!tel.meta) return null;
|
||||||
|
let o = tel.meta;
|
||||||
|
if (typeof o === 'string') {
|
||||||
|
try { o = JSON.parse(o); } catch (e) { return null; }
|
||||||
|
}
|
||||||
|
if (!o) return null;
|
||||||
|
const fromMeta = formatTsValue(o.stats_at) || formatTsValue(o.packet_ts) || formatTsValue(o.ts);
|
||||||
|
if (fromMeta) return fromMeta;
|
||||||
|
return packetTimeFromMetaFields(o.fields);
|
||||||
|
}
|
||||||
|
|
||||||
function enrichSnapFromTel(snap, tel) {
|
function enrichSnapFromTel(snap, tel) {
|
||||||
snap.gps = formatCoords(tel) || '—';
|
snap.gps = formatCoords(tel) || '—';
|
||||||
snap.packetTime = formatPacketTime(tel) || '—';
|
snap.packetTime = formatPacketTime(tel) || '—';
|
||||||
@@ -1942,10 +1970,16 @@
|
|||||||
const t0 = Number(first.ts);
|
const t0 = Number(first.ts);
|
||||||
const t1 = Number(last.ts);
|
const t1 = Number(last.ts);
|
||||||
if (tNum <= t0) {
|
if (tNum <= t0) {
|
||||||
return { lat: Number(first.lat), lon: Number(first.lon), meta: first.meta, rssi: first.rssi };
|
return {
|
||||||
|
lat: Number(first.lat), lon: Number(first.lon), meta: first.meta, rssi: first.rssi,
|
||||||
|
pointTs: t0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (tNum >= t1) {
|
if (tNum >= t1) {
|
||||||
return { lat: Number(last.lat), lon: Number(last.lon), meta: last.meta, rssi: last.rssi };
|
return {
|
||||||
|
lat: Number(last.lat), lon: Number(last.lon), meta: last.meta, rssi: last.rssi,
|
||||||
|
pointTs: t1,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
for (let i = 0; i < points.length - 1; i++) {
|
for (let i = 0; i < points.length - 1; i++) {
|
||||||
const a = points[i];
|
const a = points[i];
|
||||||
@@ -1959,11 +1993,15 @@
|
|||||||
lat: Number(a.lat) + (Number(b.lat) - Number(a.lat)) * f,
|
lat: Number(a.lat) + (Number(b.lat) - Number(a.lat)) * f,
|
||||||
lon: Number(a.lon) + (Number(b.lon) - Number(a.lon)) * f,
|
lon: Number(a.lon) + (Number(b.lon) - Number(a.lon)) * f,
|
||||||
meta: tNum - ta < tb - tNum ? a.meta : b.meta,
|
meta: tNum - ta < tb - tNum ? a.meta : b.meta,
|
||||||
rssi: tNum - ta < tb - tNum ? a.rssi : b.rssi
|
rssi: tNum - ta < tb - tNum ? a.rssi : b.rssi,
|
||||||
|
pointTs: tNum - ta < tb - tNum ? ta : tb,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { lat: Number(last.lat), lon: Number(last.lon), meta: last.meta, rssi: last.rssi };
|
return {
|
||||||
|
lat: Number(last.lat), lon: Number(last.lon), meta: last.meta, rssi: last.rssi,
|
||||||
|
pointTs: t1,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function overlapRange(txPts, rxPts) {
|
function overlapRange(txPts, rxPts) {
|
||||||
|
|||||||
@@ -61,6 +61,7 @@
|
|||||||
if (o.rx_pkt_per_s != null) snap.rxPktPerS = Number(o.rx_pkt_per_s);
|
if (o.rx_pkt_per_s != null) snap.rxPktPerS = Number(o.rx_pkt_per_s);
|
||||||
if (o.per_percent != null) snap.perPercent = Number(o.per_percent);
|
if (o.per_percent != null) snap.perPercent = Number(o.per_percent);
|
||||||
if (o.rx_quality_percent != null) snap.rxQualityPercent = Number(o.rx_quality_percent);
|
if (o.rx_quality_percent != null) snap.rxQualityPercent = Number(o.rx_quality_percent);
|
||||||
|
if (o.stats_at != null) snap.statsAt = Number(o.stats_at);
|
||||||
if (o.fields && typeof o.fields === 'object') {
|
if (o.fields && typeof o.fields === 'object') {
|
||||||
for (const [k, v] of Object.entries(o.fields)) {
|
for (const [k, v] of Object.entries(o.fields)) {
|
||||||
if (!isKnownLabel(k)) snap.extraFields[k] = String(v);
|
if (!isKnownLabel(k)) snap.extraFields[k] = String(v);
|
||||||
|
|||||||
Reference in New Issue
Block a user