generated from Grigo/AndroidTemplate
fix elevation
This commit is contained in:
@@ -772,8 +772,21 @@ public class MapFragment extends Fragment {
|
|||||||
|
|
||||||
private static int qualityArgb(double pct) {
|
private static int qualityArgb(double pct) {
|
||||||
double p = Math.max(0.0, Math.min(100.0, pct));
|
double p = Math.max(0.0, Math.min(100.0, pct));
|
||||||
int r = p < 50.0 ? (int) Math.round(255.0 * (p / 50.0)) : 255;
|
int r;
|
||||||
int g = p < 50.0 ? 255 : (int) Math.round(255.0 * (1.0 - (p - 50.0) / 50.0));
|
int g;
|
||||||
|
if (p < 40.0) {
|
||||||
|
double t = p / 40.0;
|
||||||
|
r = 255;
|
||||||
|
g = (int) Math.round(140.0 * t);
|
||||||
|
} else if (p < 85.0) {
|
||||||
|
double t = (p - 40.0) / 45.0;
|
||||||
|
r = 255;
|
||||||
|
g = (int) Math.round(140.0 + 115.0 * t);
|
||||||
|
} else {
|
||||||
|
double t = (p - 85.0) / 15.0;
|
||||||
|
r = (int) Math.round(255.0 * (1.0 - t));
|
||||||
|
g = 255;
|
||||||
|
}
|
||||||
return 0xFF000000 | (r << 16) | (g << 8);
|
return 0xFF000000 | (r << 16) | (g << 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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-16d",
|
"api_build": "2026-06-16e",
|
||||||
**status,
|
**status,
|
||||||
**elevation_status(),
|
**elevation_status(),
|
||||||
}
|
}
|
||||||
|
|||||||
+152
-28
@@ -361,7 +361,7 @@
|
|||||||
{ position: 'topright', collapsed: true }
|
{ position: 'topright', collapsed: true }
|
||||||
).addTo(map);
|
).addTo(map);
|
||||||
|
|
||||||
const API_BUILD = '2026-06-16d';
|
const API_BUILD = '2026-06-16e';
|
||||||
|
|
||||||
const markers = {};
|
const markers = {};
|
||||||
let selectedId = null;
|
let selectedId = null;
|
||||||
@@ -406,6 +406,8 @@
|
|||||||
let elevProfileTx = null;
|
let elevProfileTx = null;
|
||||||
let elevProfileRx = null;
|
let elevProfileRx = null;
|
||||||
let elevProfileSingle = null;
|
let elevProfileSingle = null;
|
||||||
|
let elevProfileLink = null;
|
||||||
|
let elevProfileLinkKey = null;
|
||||||
let elevProfileMapLine = null;
|
let elevProfileMapLine = null;
|
||||||
let elevationLoadState = 'idle';
|
let elevationLoadState = 'idle';
|
||||||
let mapRulerOpen = false;
|
let mapRulerOpen = false;
|
||||||
@@ -621,6 +623,11 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mergeTelCoords(tel, pos) {
|
||||||
|
if (!tel || !pos) return tel;
|
||||||
|
return { ...tel, lat: pos.lat, lon: pos.lon };
|
||||||
|
}
|
||||||
|
|
||||||
function snapAtTime(track, telemetryRows, t, roleFallback) {
|
function snapAtTime(track, telemetryRows, t, roleFallback) {
|
||||||
const pos = positionAt(track?.points, t);
|
const pos = positionAt(track?.points, t);
|
||||||
if (pos?.meta && String(pos.meta).length > 2) {
|
if (pos?.meta && String(pos.meta).length > 2) {
|
||||||
@@ -640,8 +647,21 @@
|
|||||||
function qualityColor(pct) {
|
function qualityColor(pct) {
|
||||||
if (pct == null || Number.isNaN(pct)) return null;
|
if (pct == null || Number.isNaN(pct)) return null;
|
||||||
const p = Math.max(0, Math.min(100, Number(pct)));
|
const p = Math.max(0, Math.min(100, Number(pct)));
|
||||||
const r = p < 50 ? Math.round(255 * (p / 50)) : 255;
|
let r;
|
||||||
const g = p < 50 ? 255 : Math.round(255 * (1 - (p - 50) / 50));
|
let g;
|
||||||
|
if (p < 40) {
|
||||||
|
const t = p / 40;
|
||||||
|
r = 255;
|
||||||
|
g = Math.round(140 * t);
|
||||||
|
} else if (p < 85) {
|
||||||
|
const t = (p - 40) / 45;
|
||||||
|
r = 255;
|
||||||
|
g = Math.round(140 + 115 * t);
|
||||||
|
} else {
|
||||||
|
const t = (p - 85) / 15;
|
||||||
|
r = Math.round(255 * (1 - t));
|
||||||
|
g = 255;
|
||||||
|
}
|
||||||
return `rgb(${r},${g},0)`;
|
return `rgb(${r},${g},0)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -747,9 +767,50 @@
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatCoords(tel) {
|
||||||
|
if (!tel || tel.lat == null || tel.lon == null || isNullIsland(tel.lat, tel.lon)) return null;
|
||||||
|
return `${Number(tel.lat).toFixed(5)}, ${Number(tel.lon).toFixed(5)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPacketTime(tel) {
|
||||||
|
if (!tel) 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 ms = n < 1e12 ? n * 1000 : n;
|
||||||
|
return new Date(ms).toLocaleTimeString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function enrichSnapFromTel(snap, tel) {
|
||||||
|
snap.gps = formatCoords(tel) || '—';
|
||||||
|
snap.packetTime = formatPacketTime(tel) || '—';
|
||||||
|
return snap;
|
||||||
|
}
|
||||||
|
|
||||||
function renderTimelineCompare(txTel, rxTel, txId, rxId) {
|
function renderTimelineCompare(txTel, rxTel, txId, rxId) {
|
||||||
const txSnap = txTel ? telemetryToSnap(txTel) : RadioUI.parseRadioSnapshot(null);
|
const txSnap = enrichSnapFromTel(
|
||||||
const rxSnap = rxTel ? telemetryToSnap(rxTel) : RadioUI.parseRadioSnapshot(null);
|
txTel ? telemetryToSnap(txTel) : RadioUI.parseRadioSnapshot(null),
|
||||||
|
txTel
|
||||||
|
);
|
||||||
|
const rxSnap = enrichSnapFromTel(
|
||||||
|
rxTel ? telemetryToSnap(rxTel) : RadioUI.parseRadioSnapshot(null),
|
||||||
|
rxTel
|
||||||
|
);
|
||||||
const chTx = RadioUI.diffSnapshots(prevTimelineTxSnap, txSnap);
|
const chTx = RadioUI.diffSnapshots(prevTimelineTxSnap, txSnap);
|
||||||
const chRx = RadioUI.diffSnapshots(prevTimelineRxSnap, rxSnap);
|
const chRx = RadioUI.diffSnapshots(prevTimelineRxSnap, rxSnap);
|
||||||
prevTimelineTxSnap = txSnap;
|
prevTimelineTxSnap = txSnap;
|
||||||
@@ -1435,13 +1496,27 @@
|
|||||||
function getTimelineElevationSeries(cursors) {
|
function getTimelineElevationSeries(cursors) {
|
||||||
const series = [];
|
const series = [];
|
||||||
if (dualTracksActive) {
|
if (dualTracksActive) {
|
||||||
|
if (elevationPointCount(elevProfileLink) > 0) {
|
||||||
|
const pts = elevProfileLink.points.filter(p => p.elevation_m != null);
|
||||||
|
const elevA = pts[0]?.elevation_m;
|
||||||
|
const elevB = pts[pts.length - 1]?.elevation_m;
|
||||||
|
series.push({
|
||||||
|
color: '#00ff88',
|
||||||
|
profile: elevProfileLink,
|
||||||
|
label: 'рельеф TX↔RX',
|
||||||
|
losLine: elevA != null && elevB != null ? { elevA, elevB } : null,
|
||||||
|
});
|
||||||
|
return series;
|
||||||
|
}
|
||||||
if (elevationPointCount(elevProfileTx) > 0) {
|
if (elevationPointCount(elevProfileTx) > 0) {
|
||||||
series.push({ color: TX_COLOR, profile: elevProfileTx, cursor: cursors?.tx, label: 'TX' });
|
series.push({ color: TX_COLOR, profile: elevProfileTx, cursor: cursors?.tx, label: 'TX' });
|
||||||
}
|
}
|
||||||
if (elevationPointCount(elevProfileRx) > 0) {
|
if (elevationPointCount(elevProfileRx) > 0) {
|
||||||
series.push({ color: RX_COLOR, profile: elevProfileRx, cursor: cursors?.rx, label: 'RX' });
|
series.push({ color: RX_COLOR, profile: elevProfileRx, cursor: cursors?.rx, label: 'RX' });
|
||||||
}
|
}
|
||||||
} else if (singleTrackActive && elevationPointCount(elevProfileSingle) > 0) {
|
return series;
|
||||||
|
}
|
||||||
|
if (singleTrackActive && elevationPointCount(elevProfileSingle) > 0) {
|
||||||
const color = loadedSingleTrack?.role === 'RX' ? RX_COLOR : TX_COLOR;
|
const color = loadedSingleTrack?.role === 'RX' ? RX_COLOR : TX_COLOR;
|
||||||
const label = loadedSingleTrack?.role === 'RX' ? 'RX' : 'TX';
|
const label = loadedSingleTrack?.role === 'RX' ? 'RX' : 'TX';
|
||||||
series.push({ color, profile: elevProfileSingle, cursor: cursors?.single, label });
|
series.push({ color, profile: elevProfileSingle, cursor: cursors?.single, label });
|
||||||
@@ -1532,6 +1607,23 @@
|
|||||||
else ctx.lineTo(x, y);
|
else ctx.lineTo(x, y);
|
||||||
});
|
});
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
if (s.losLine && pts.length >= 2) {
|
||||||
|
const x0 = margin.l;
|
||||||
|
const x1 = margin.l + plotW;
|
||||||
|
const y0 = margin.t + plotH - ((s.losLine.elevA - minE) / (maxE - minE)) * plotH;
|
||||||
|
const y1 = margin.t + plotH - ((s.losLine.elevB - minE) / (maxE - minE)) * plotH;
|
||||||
|
ctx.strokeStyle = 'rgba(255, 136, 0, 0.95)';
|
||||||
|
ctx.lineWidth = 1.5;
|
||||||
|
ctx.setLineDash([5, 4]);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x0, y0);
|
||||||
|
ctx.lineTo(x1, y1);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.setLineDash([]);
|
||||||
|
ctx.fillStyle = '#ffb74d';
|
||||||
|
ctx.font = '9px system-ui';
|
||||||
|
ctx.fillText('прямая', x1 - 42, y1 - 4);
|
||||||
|
}
|
||||||
if (s.cursor != null && maxDist > 0) {
|
if (s.cursor != null && maxDist > 0) {
|
||||||
const cx = margin.l + (s.cursor / maxDist) * plotW;
|
const cx = margin.l + (s.cursor / maxDist) * plotW;
|
||||||
const elev = elevationAtDist(s.profile, s.cursor);
|
const elev = elevationAtDist(s.profile, s.cursor);
|
||||||
@@ -1649,10 +1741,32 @@
|
|||||||
drawMapRulerChart();
|
drawMapRulerChart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function scheduleLinkElevation(txPos, rxPos) {
|
||||||
|
if (!txPos || !rxPos) return;
|
||||||
|
const dist = haversineM(txPos.lat, txPos.lon, rxPos.lat, rxPos.lon);
|
||||||
|
const key = `${txPos.lat.toFixed(5)},${txPos.lon.toFixed(5)}|${rxPos.lat.toFixed(5)},${rxPos.lon.toFixed(5)}`;
|
||||||
|
if (key === elevProfileLinkKey && elevProfileLink) return;
|
||||||
|
elevProfileLinkKey = key;
|
||||||
|
const linePts = [{ lat: txPos.lat, lon: txPos.lon }, { lat: rxPos.lat, lon: rxPos.lon }];
|
||||||
|
const profile = await fetchElevationProfile(linePts, null, {
|
||||||
|
targetPoints: getMapRulerTargetPoints(dist),
|
||||||
|
});
|
||||||
|
if (elevProfileLinkKey !== key) return;
|
||||||
|
elevProfileLink = profile;
|
||||||
|
const n = elevationPointCount(profile);
|
||||||
|
if (n > 0) {
|
||||||
|
const src = profile.source === 'elevation' ? 'высоты'
|
||||||
|
: profile.source === 'server' ? 'сервер' : (profile.source || 'данные');
|
||||||
|
setElevationStatus(`срез TX↔RX · ${dist.toFixed(0)} m · ${src} · ${n} точек · оранжевая — прямая`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadElevationProfiles() {
|
async function loadElevationProfiles() {
|
||||||
elevProfileTx = null;
|
elevProfileTx = null;
|
||||||
elevProfileRx = null;
|
elevProfileRx = null;
|
||||||
elevProfileSingle = null;
|
elevProfileSingle = null;
|
||||||
|
elevProfileLink = null;
|
||||||
|
elevProfileLinkKey = null;
|
||||||
elevationLoadState = 'loading';
|
elevationLoadState = 'loading';
|
||||||
setElevationStatus('загрузка…');
|
setElevationStatus('загрузка…');
|
||||||
drawElevationChart();
|
drawElevationChart();
|
||||||
@@ -1661,25 +1775,26 @@
|
|||||||
elevProfileSingle = await fetchElevationProfile(
|
elevProfileSingle = await fetchElevationProfile(
|
||||||
loadedSingleTrack.points, loadedSingleTrack.id);
|
loadedSingleTrack.points, loadedSingleTrack.id);
|
||||||
} else if (dualTracksActive) {
|
} else if (dualTracksActive) {
|
||||||
const [txProf, rxProf] = await Promise.all([
|
const txPos = positionAtCursor(loadedTxTrack?.points, timelineCursor());
|
||||||
loadedTxTrack?.points?.length
|
const rxPos = positionAtCursor(loadedRxTrack?.points, timelineCursor());
|
||||||
? fetchElevationProfile(loadedTxTrack.points, loadedTxTrack.id) : null,
|
if (txPos && rxPos) {
|
||||||
loadedRxTrack?.points?.length
|
await scheduleLinkElevation(txPos, rxPos);
|
||||||
? fetchElevationProfile(loadedRxTrack.points, loadedRxTrack.id) : null
|
}
|
||||||
]);
|
|
||||||
elevProfileTx = txProf;
|
|
||||||
elevProfileRx = rxProf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
elevationLoadState = 'done';
|
elevationLoadState = 'done';
|
||||||
const hasData = elevationPointCount(elevProfileSingle) > 0
|
const hasData = elevationPointCount(elevProfileSingle) > 0
|
||||||
|
|| elevationPointCount(elevProfileLink) > 0
|
||||||
|| elevationPointCount(elevProfileTx) > 0
|
|| elevationPointCount(elevProfileTx) > 0
|
||||||
|| elevationPointCount(elevProfileRx) > 0;
|
|| elevationPointCount(elevProfileRx) > 0;
|
||||||
if (hasData) {
|
if (hasData) {
|
||||||
const ref = elevProfileSingle || elevProfileTx || elevProfileRx;
|
const ref = elevProfileSingle || elevProfileLink || elevProfileTx || elevProfileRx;
|
||||||
const srcLabel = ref?.source === 'elevation' ? 'высоты'
|
const srcLabel = ref?.source === 'elevation' ? 'высоты'
|
||||||
: ref?.source === 'server' ? 'сервер' : (ref?.source || 'данные');
|
: ref?.source === 'server' ? 'сервер' : (ref?.source || 'данные');
|
||||||
if (dualTracksActive && elevProfileTx && elevProfileRx) {
|
if (dualTracksActive && elevProfileLink) {
|
||||||
|
const n = elevationPointCount(elevProfileLink);
|
||||||
|
setElevationStatus(`срез TX↔RX · ${srcLabel} · ${n} точек · оранжевая — прямая`);
|
||||||
|
} else if (dualTracksActive && elevProfileTx && elevProfileRx) {
|
||||||
const nTx = elevationPointCount(elevProfileTx);
|
const nTx = elevationPointCount(elevProfileTx);
|
||||||
const nRx = elevationPointCount(elevProfileRx);
|
const nRx = elevationPointCount(elevProfileRx);
|
||||||
setElevationStatus(`TX + RX · ${srcLabel} · ${nTx}/${nRx} точек`);
|
setElevationStatus(`TX + RX · ${srcLabel} · ${nTx}/${nRx} точек`);
|
||||||
@@ -1687,7 +1802,8 @@
|
|||||||
setElevationStatus(`${srcLabel} · ${elevationPointCount(ref)} точек`);
|
setElevationStatus(`${srcLabel} · ${elevationPointCount(ref)} точек`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const err = elevProfileSingle?.api_error || elevProfileTx?.api_error || elevProfileRx?.api_error;
|
const err = elevProfileSingle?.api_error || elevProfileLink?.api_error
|
||||||
|
|| elevProfileTx?.api_error || elevProfileRx?.api_error;
|
||||||
setElevationStatus(err ? `ошибка: ${err}` : 'нет данных');
|
setElevationStatus(err ? `ошибка: ${err}` : 'нет данных');
|
||||||
}
|
}
|
||||||
drawElevationChart();
|
drawElevationChart();
|
||||||
@@ -1976,6 +2092,8 @@
|
|||||||
elevProfileTx = null;
|
elevProfileTx = null;
|
||||||
elevProfileRx = null;
|
elevProfileRx = null;
|
||||||
elevProfileSingle = null;
|
elevProfileSingle = null;
|
||||||
|
elevProfileLink = null;
|
||||||
|
elevProfileLinkKey = null;
|
||||||
drawElevationChart();
|
drawElevationChart();
|
||||||
if (playTimer) {
|
if (playTimer) {
|
||||||
clearInterval(playTimer);
|
clearInterval(playTimer);
|
||||||
@@ -2039,12 +2157,17 @@
|
|||||||
const dist = haversineM(txPos.lat, txPos.lon, rxPos.lat, rxPos.lon);
|
const dist = haversineM(txPos.lat, txPos.lon, rxPos.lat, rxPos.lon);
|
||||||
let html = `<b>${formatTimelineClock(cursor)}</b><br>`;
|
let html = `<b>${formatTimelineClock(cursor)}</b><br>`;
|
||||||
html += `Расстояние: ${dist.toFixed(0)} m (GPS)<br><br>`;
|
html += `Расстояние: ${dist.toFixed(0)} m (GPS)<br><br>`;
|
||||||
html += `<span class="legend-tx">TX</span> ${txPos.lat.toFixed(5)}, ${txPos.lon.toFixed(5)}<br>`;
|
const txTel = mergeTelCoords(snapAtCursor(loadedTxTrack, telemetryTx, cursor, 'TX'), txPos);
|
||||||
const txTel = snapAtCursor(loadedTxTrack, telemetryTx, cursor, 'TX');
|
const rxTel = mergeTelCoords(snapAtCursor(loadedRxTrack, telemetryRx, cursor, 'RX'), rxPos);
|
||||||
const rxTel = snapAtCursor(loadedRxTrack, telemetryRx, cursor, 'RX');
|
|
||||||
html += renderTimelineCompare(
|
html += renderTimelineCompare(
|
||||||
txTel || { meta: txPos.meta, role: 'TX', rssi: null },
|
txTel || mergeTelCoords({
|
||||||
rxTel || { meta: rxPos.meta, role: 'RX', rssi: null },
|
meta: txPos.meta, role: 'TX', rssi: null,
|
||||||
|
ts: timelineUseProgress ? null : cursor.t
|
||||||
|
}, txPos),
|
||||||
|
rxTel || mergeTelCoords({
|
||||||
|
meta: rxPos.meta, role: 'RX', rssi: null,
|
||||||
|
ts: timelineUseProgress ? null : cursor.t
|
||||||
|
}, rxPos),
|
||||||
deviceDisplayName(loadedTxTrack?.device_id),
|
deviceDisplayName(loadedTxTrack?.device_id),
|
||||||
deviceDisplayName(loadedRxTrack?.device_id)
|
deviceDisplayName(loadedRxTrack?.device_id)
|
||||||
);
|
);
|
||||||
@@ -2098,8 +2221,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const txTel = snapAtCursor(loadedTxTrack, telemetryTx, cursor, 'TX');
|
const txTel = mergeTelCoords(snapAtCursor(loadedTxTrack, telemetryTx, cursor, 'TX'), txPos);
|
||||||
const rxTel = snapAtCursor(loadedRxTrack, telemetryRx, cursor, 'RX');
|
const rxTel = mergeTelCoords(snapAtCursor(loadedRxTrack, telemetryRx, cursor, 'RX'), rxPos);
|
||||||
const timelineStatsEl = document.getElementById('timelineStats');
|
const timelineStatsEl = document.getElementById('timelineStats');
|
||||||
setPanelHtml(timelineStatsEl, renderTimelineCompare(
|
setPanelHtml(timelineStatsEl, renderTimelineCompare(
|
||||||
txTel,
|
txTel,
|
||||||
@@ -2107,10 +2230,11 @@
|
|||||||
deviceDisplayName(loadedTxTrack?.device_id),
|
deviceDisplayName(loadedTxTrack?.device_id),
|
||||||
deviceDisplayName(loadedRxTrack?.device_id)
|
deviceDisplayName(loadedRxTrack?.device_id)
|
||||||
));
|
));
|
||||||
drawElevationChart({
|
if (txPos && rxPos) {
|
||||||
tx: trackDistanceAtCursor(loadedTxTrack, cursor),
|
scheduleLinkElevation(txPos, rxPos).then(() => drawElevationChart());
|
||||||
rx: trackDistanceAtCursor(loadedRxTrack, cursor)
|
} else {
|
||||||
});
|
drawElevationChart();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTimelineAtSingle(cursor, openModal) {
|
function updateTimelineAtSingle(cursor, openModal) {
|
||||||
|
|||||||
@@ -77,9 +77,9 @@
|
|||||||
function diffSnapshots(a, b) {
|
function diffSnapshots(a, b) {
|
||||||
const changed = new Set();
|
const changed = new Set();
|
||||||
if (!a || !b) return changed;
|
if (!a || !b) return changed;
|
||||||
const keys = ['role', 'rssiDbm', 'snrDb', 'rxQualityPercent', 'packet', 'payload', 'perPercent',
|
const keys = ['gps', 'packetTime', 'role', 'rssiDbm', 'snrDb', 'rxQualityPercent', 'packet', 'payload', 'perPercent',
|
||||||
'txPktPerS', 'rxPktPerS', 'frequencyMhz', 'sf', 'bwKhz', 'powerDbm'];
|
'txPktPerS', 'rxPktPerS', 'frequencyMhz', 'sf', 'bwKhz', 'powerDbm'];
|
||||||
const map = { role: 'role', rssiDbm: 'rssi', snrDb: 'snr', rxQualityPercent: 'rxQuality',
|
const map = { gps: 'gps', packetTime: 'packetTime', role: 'role', rssiDbm: 'rssi', snrDb: 'snr', rxQualityPercent: 'rxQuality',
|
||||||
packet: 'packet',
|
packet: 'packet',
|
||||||
payload: 'payload', perPercent: 'per', txPktPerS: 'txSpeed', rxPktPerS: 'rxSpeed',
|
payload: 'payload', perPercent: 'per', txPktPerS: 'txSpeed', rxPktPerS: 'rxSpeed',
|
||||||
frequencyMhz: 'frequency', sf: 'sf', bwKhz: 'bw', powerDbm: 'power' };
|
frequencyMhz: 'frequency', sf: 'sf', bwKhz: 'bw', powerDbm: 'power' };
|
||||||
@@ -90,6 +90,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DYNAMIC_ROWS = [
|
const DYNAMIC_ROWS = [
|
||||||
|
{ key: 'gps', label: 'GPS', fmt: s => s.gps || '—' },
|
||||||
|
{ key: 'packetTime', label: 'Время пакета', fmt: s => s.packetTime || '—' },
|
||||||
{ key: 'rssi', label: 'RSSI', fmt: s => s.rssiDbm != null ? `${s.rssiDbm} dBm` : '—' },
|
{ key: 'rssi', label: 'RSSI', fmt: s => s.rssiDbm != null ? `${s.rssiDbm} dBm` : '—' },
|
||||||
{ key: 'snr', label: 'SNR', fmt: s => s.snrDb != null ? `${s.snrDb} dB` : '—' },
|
{ key: 'snr', label: 'SNR', fmt: s => s.snrDb != null ? `${s.snrDb} dB` : '—' },
|
||||||
{ key: 'rxQuality', label: 'RX Quality', fmt: s => s.rxQualityPercent != null ? `${s.rxQualityPercent} %` : '—' },
|
{ key: 'rxQuality', label: 'RX Quality', fmt: s => s.rxQualityPercent != null ? `${s.rxQualityPercent} %` : '—' },
|
||||||
|
|||||||
Reference in New Issue
Block a user