added GFSK

This commit is contained in:
2026-07-06 13:13:14 +03:00
parent 11e8ee8d7f
commit 4388408ef8
8 changed files with 164 additions and 9 deletions
Binary file not shown.
+14 -1
View File
@@ -435,11 +435,24 @@ def list_tracks(device_id: Optional[str] = None, limit: int = 50) -> list[dict[s
WHERE p.track_id = t.id AND p.role IS NOT NULL AND p.role != ''
ORDER BY p.ts DESC LIMIT 1)
"""
modem_mode_sub = """
(SELECT COALESCE(
json_extract(p.meta, '$.modem_mode'),
CASE
WHEN json_extract(p.meta, '$.bitrate_bps') IS NOT NULL THEN 'GFSK'
WHEN json_extract(p.meta, '$.spreading_factor') IS NOT NULL THEN 'LORA'
ELSE NULL
END
) FROM track_points p
WHERE p.track_id = t.id AND p.meta IS NOT NULL AND p.meta != ''
ORDER BY p.ts DESC LIMIT 1)
"""
track_cols = f"""
SELECT t.id, t.device_id, t.started_at, t.ended_at, t.label,
d.label AS device_label,
(SELECT COUNT(*) FROM track_points p WHERE p.track_id = t.id) AS point_count,
{role_sub} AS role
{role_sub} AS role,
{modem_mode_sub} AS modem_mode
FROM tracks t
LEFT JOIN devices d ON d.device_id = t.device_id
"""
+43 -6
View File
@@ -1028,6 +1028,37 @@ function referenceSessionById(id) {
return referencePointSessions.find(s => String(s.id) === String(id)) || null;
}
function modemModeFromTxParams(params) {
if (!params) return null;
if (params.modem_mode) return RadioUI.normalizeModemMode(params.modem_mode);
if (params.bitrate_bps != null) return RadioUI.MODEM_GFSK;
if (params.spreading_factor != null) return RadioUI.MODEM_LORA;
return null;
}
function modemModeFromTrack(track) {
if (!track) return null;
if (track.modem_mode) return RadioUI.normalizeModemMode(track.modem_mode);
const points = track.points;
if (!points?.length) return null;
for (let i = points.length - 1; i >= 0; i--) {
let meta = points[i].meta;
if (!meta) continue;
if (typeof meta === 'string') {
try { meta = JSON.parse(meta); } catch (e) { continue; }
}
if (meta.modem_mode) return RadioUI.normalizeModemMode(meta.modem_mode);
if (meta.bitrate_bps != null) return RadioUI.MODEM_GFSK;
if (meta.spreading_factor != null) return RadioUI.MODEM_LORA;
}
return null;
}
function modemModeSuffix(mode) {
const label = mode ? RadioUI.modemModeLabel(mode) : '';
return label && label !== '—' ? ` · ${label}` : '';
}
function renderReferencePointList() {
const list = document.getElementById('referencePointList');
if (!list) return;
@@ -1043,9 +1074,10 @@ function renderReferencePointList() {
if (checked) row.classList.add('selected');
const ctx = session.reference_context || {};
const dist = ctx.distance_m != null ? ` · ${Number(ctx.distance_m).toFixed(0)} м` : '';
const mode = modemModeSuffix(modemModeFromTxParams(ctx.tx_params));
row.innerHTML = `<input type="checkbox" ${checked ? 'checked' : ''} />
<span><span class="preset-title">${escapeHtml(session.label || 'Репер #' + session.id)}</span>
<span class="preset-desc">#${session.id} · RX${dist}</span></span>`;
<span class="preset-desc">#${session.id} · RX${mode}${dist}</span></span>`;
const checkbox = row.querySelector('input');
checkbox.addEventListener('change', () => {
const sid = String(session.id);
@@ -1093,13 +1125,14 @@ function showReferencePointsOnMap() {
if (!rxPos || rxPos.lat == null || rxPos.lon == null) return;
const rxLatLng = [Number(rxPos.lat), Number(rxPos.lon)];
bounds.push(rxLatLng);
const refMode = modemModeSuffix(modemModeFromTxParams(ctx.tx_params));
const rxMarker = L.circleMarker(rxLatLng, {
radius: 9,
color: '#00ff88',
weight: 2,
fillColor: '#00ff88',
fillOpacity: 0.25
}).addTo(map).bindPopup(`RX репер #${session.id}`);
}).addTo(map).bindPopup(`RX репер #${session.id}${refMode}`);
let txMarker = null;
let line = null;
if (txPos && txPos.lat != null && txPos.lon != null) {
@@ -1259,8 +1292,9 @@ async function showReferencePoint(sessionId) {
const data = await res.json();
const stats = data.stats || {};
const ctx = data.reference_context || {};
const refMode = modemModeSuffix(modemModeFromTxParams(ctx.tx_params));
if (summary) {
summary.innerHTML = `<div><b>${escapeHtml(data.label || 'Реперная точка')} #${data.id}</b> · RX</div>
summary.innerHTML = `<div><b>${escapeHtml(data.label || 'Реперная точка')} #${data.id}</b> · RX${refMode}</div>
<div>${escapeHtml(deviceDisplayName(data))} · samples: ${data.sample_count || 0} · ${escapeHtml(data.status || '')}</div>
<div class="metric-card"><b>Контекст</b>
<div>${escapeHtml(formatReferenceTxParams(ctx.tx_params))}</div>
@@ -4329,8 +4363,9 @@ async function refreshTimelineTelemetry() {
function trackOptionLabel(t) {
const start = new Date(t.started_at * 1000).toLocaleString();
const role = t.role ? ` · ${t.role}` : '';
const mode = modemModeSuffix(modemModeFromTrack(t));
const dev = t.device_id ? ` · ${deviceDisplayName(t)}` : '';
return `#${t.id}${role}${dev} · ${start} (${t.point_count})`;
return `#${t.id}${mode}${role}${dev} · ${start} (${t.point_count})`;
}
async function loadAllTracks() {
@@ -4408,7 +4443,7 @@ async function showSingleTrack() {
updateTimelineAtSingle(timelineCursor());
}
document.getElementById('trackInfo').textContent =
`Трек #${loadedSingleTrack.id} (${loadedSingleTrack.points.length} точек)`;
`Трек #${loadedSingleTrack.id} (${loadedSingleTrack.points.length} точек)${modemModeSuffix(modemModeFromTrack(loadedSingleTrack))}`;
updateTrackButtons();
}
@@ -4470,8 +4505,10 @@ async function showDualTracks() {
rebuildQualityViz();
const modeHint = range && range.mode === 'union' ? ' · без пересечения по времени' : '';
const txMode = modemModeSuffix(modemModeFromTrack(loadedTxTrack));
const rxMode = modemModeSuffix(modemModeFromTrack(loadedRxTrack));
document.getElementById('trackInfo').textContent =
`TX #${loadedTxTrack.id} (${loadedTxTrack.points.length}) + RX #${loadedRxTrack.id} (${loadedRxTrack.points.length})${modeHint}`;
`TX #${loadedTxTrack.id} (${loadedTxTrack.points.length})${txMode} + RX #${loadedRxTrack.id} (${loadedRxTrack.points.length})${rxMode}${modeHint}`;
updateTrackButtons();
}
+27
View File
@@ -91,6 +91,33 @@ def test_sync_track_create_offline(temp_db, monkeypatch):
def test_tracks_crud(temp_db):
storage.init_db()
start = storage.start_track("android-12345678")
tid = start["track_id"]
storage.add_track_points(
tid,
[
{
"ts": 1.0,
"lat": 55.75,
"lon": 37.62,
"rssi": -70.0,
"role": "TX",
"meta": '{"modem_mode":"GFSK","bitrate_bps":20000}',
}
],
)
fin = storage.finish_track(tid)
assert fin["point_count"] == 1
listed = storage.list_tracks(limit=10)
gfsk = next((t for t in listed if t["id"] == tid), None)
assert gfsk is not None
assert gfsk["modem_mode"] == "GFSK"
track = storage.get_track(tid)
assert len(track["points"]) == 1
def test_tracks_crud_legacy(temp_db):
storage.init_db()
start = storage.start_track("android-12345678")
tid = start["track_id"]