From 157e1484d32780106c0d97e46fc1692612306491 Mon Sep 17 00:00:00 2001 From: grigo Date: Thu, 25 Jun 2026 11:27:10 +0300 Subject: [PATCH] added refference point --- .../loratester/ReferencePointRecorder.java | 12 ----- .../loratester/ui/AtFragment.java | 16 ------ .../core/__pycache__/schema.cpython-313.pyc | Bin 9794 -> 9694 bytes .../core/__pycache__/storage.cpython-313.pyc | Bin 52044 -> 50700 bytes server/core/schema.py | 3 -- server/core/storage.py | 36 ++----------- server/fastapi_app.py | 3 -- server/static/app.js | 49 +++++++++++++----- server/static/index.html | 2 +- .../test_schema.cpython-313-pytest-9.0.3.pyc | Bin 18574 -> 18124 bytes server/tests/test_schema.py | 4 -- 11 files changed, 42 insertions(+), 83 deletions(-) diff --git a/app/src/main/java/com/grigowashere/loratester/ReferencePointRecorder.java b/app/src/main/java/com/grigowashere/loratester/ReferencePointRecorder.java index e0ee43f..953e896 100644 --- a/app/src/main/java/com/grigowashere/loratester/ReferencePointRecorder.java +++ b/app/src/main/java/com/grigowashere/loratester/ReferencePointRecorder.java @@ -4,7 +4,6 @@ import android.os.Handler; import android.os.Looper; import android.util.Log; -import com.grigowashere.loratester.api.MacroPreset; import com.grigowashere.loratester.api.ServerApi; import com.grigowashere.loratester.location.GeoUtils; import com.grigowashere.loratester.net.NetworkMonitor; @@ -49,7 +48,6 @@ public class ReferencePointRecorder { private volatile boolean recording; private volatile long sessionId = -1; private volatile int sampleCount; - private volatile MacroPreset activePreset; private ScheduledFuture sampleTask; private ScheduledFuture flushTask; private Listener listener; @@ -77,10 +75,6 @@ public class ReferencePointRecorder { this.listener = listener; } - public void setActivePreset(MacroPreset preset) { - this.activePreset = preset; - } - public boolean isRecording() { return recording; } @@ -193,12 +187,6 @@ public class ReferencePointRecorder { if (stats.role != null) point.put("role", stats.role); if (stats.metaJson != null) point.put("meta", stats.metaJson); } - MacroPreset preset = activePreset; - if (preset != null) { - if (preset.id != null) point.put("preset_id", preset.id); - if (preset.name != null) point.put("preset_name", preset.name); - if (preset.params != null) point.put("params", preset.params); - } synchronized (buffer) { buffer.add(point); } diff --git a/app/src/main/java/com/grigowashere/loratester/ui/AtFragment.java b/app/src/main/java/com/grigowashere/loratester/ui/AtFragment.java index fea6e71..ef7daca 100644 --- a/app/src/main/java/com/grigowashere/loratester/ui/AtFragment.java +++ b/app/src/main/java/com/grigowashere/loratester/ui/AtFragment.java @@ -25,7 +25,6 @@ import com.grigowashere.loratester.LoraApp; import com.grigowashere.loratester.MacroPresetRepository; import com.grigowashere.loratester.PeerDevices; import com.grigowashere.loratester.R; -import com.grigowashere.loratester.ReferencePointRecorder; import com.grigowashere.loratester.TelemetryUploader; import com.grigowashere.loratester.api.DeviceInfo; import com.grigowashere.loratester.api.MacroPreset; @@ -50,7 +49,6 @@ public class AtFragment extends Fragment { private FragmentPollHelper pollHelper; private TelemetryUploader uploader; private CommandPoller commandPoller; - private ReferencePointRecorder referencePointRecorder; private MacroPresetRepository macroPresetRepository; private TextView atStatus; private TextView atCurrentSnapshot; @@ -78,7 +76,6 @@ public class AtFragment extends Fragment { LoraApp app = (LoraApp) context.getApplicationContext(); uploader = app.getTelemetryUploader(); commandPoller = app.getCommandPoller(); - referencePointRecorder = app.getReferencePointRecorder(); macroPresetRepository = app.getMacroPresetRepository(); } @@ -236,9 +233,6 @@ public class AtFragment extends Fragment { List lines = preset != null && preset.lines != null && !preset.lines.isEmpty() ? preset.lines : RadioMacroBuilder.apply(buildParams()); - if (referencePointRecorder != null) { - referencePointRecorder.setActivePreset(preset != null ? preset : buildTransientPreset(lines)); - } sendLines(lines); } @@ -253,16 +247,6 @@ public class AtFragment extends Fragment { return presets.get(pos - 1); } - private MacroPreset buildTransientPreset(List lines) { - MacroPreset preset = new MacroPreset(); - preset.id = "manual"; - preset.name = getString(R.string.at_preset_manual); - preset.params = paramsMap(buildParams()); - preset.lines = lines; - preset.local = true; - return preset; - } - private void saveCurrentAsLocalPreset() { if (macroPresetRepository == null) { return; diff --git a/server/core/__pycache__/schema.cpython-313.pyc b/server/core/__pycache__/schema.cpython-313.pyc index 03c46afc2df6e86279f3c9cbbf7144f257f16d20..c3181dce8c2c2c06a65a2811be27ff1f02eb9e9a 100644 GIT binary patch delta 77 zcmX@)bI+UiGcPX}0}!lzYLgYYk+)l#QE#%LknHAd(k6_X{bYq$x%hYmzAy`La(z(R foTapkk@4|nK@}w?{yZi|{;x@_f{X%1yg>Z`aOD&v delta 115 zcmccTeaMIRGcPX}0}!O%w8`4Lk+)l#anfW%A=%B_q)iw(3yM;UQ%mAAQzn0q6z7KW z@)C1XLHx;!<&-9$5$4_eU51sFOORLK3$rjM*9VQwY)Z=*8J}%ltE|MtU&zGB|22bE LkWrwB7ibFr{w6?l2*c8)odd^4GJpSsP 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 Yz^Xy}U~n+wOydL&6ZNd3y#FZoKc3Tz8vp list[str]: rssi REAL, role TEXT, meta TEXT, - preset_id TEXT, - preset_name TEXT, - params_json TEXT, FOREIGN KEY (session_id) REFERENCES reference_point_sessions(id) ); CREATE INDEX IF NOT EXISTS idx_reference_samples_session_ts diff --git a/server/core/storage.py b/server/core/storage.py index 98c26ca..8c333a9 100644 --- a/server/core/storage.py +++ b/server/core/storage.py @@ -554,28 +554,9 @@ def _metric_stats(values: list[float]) -> dict[str, Any]: def _reference_stats(samples: list[dict[str, Any]]) -> dict[str, Any]: per = [_metric_from_meta(s.get("meta"), "per_percent") for s in samples] quality = [_metric_from_meta(s.get("meta"), "rx_quality_percent") for s in samples] - by_preset: dict[str, dict[str, list[float]]] = {} - for sample in samples: - key = sample.get("preset_id") or sample.get("preset_name") or "none" - bucket = by_preset.setdefault( - str(key), {"per_percent": [], "rx_quality_percent": []} - ) - per_v = _metric_from_meta(sample.get("meta"), "per_percent") - q_v = _metric_from_meta(sample.get("meta"), "rx_quality_percent") - if per_v is not None: - bucket["per_percent"].append(per_v) - if q_v is not None: - bucket["rx_quality_percent"].append(q_v) return { "per_percent": _metric_stats([v for v in per if v is not None]), "rx_quality_percent": _metric_stats([v for v in quality if v is not None]), - "by_preset": { - key: { - "per_percent": _metric_stats(vals["per_percent"]), - "rx_quality_percent": _metric_stats(vals["rx_quality_percent"]), - } - for key, vals in by_preset.items() - }, } @@ -633,9 +614,8 @@ def add_reference_point_samples( conn.execute( """ INSERT INTO reference_point_samples - (session_id, ts, lat, lon, rssi, role, meta, - preset_id, preset_name, params_json) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + (session_id, ts, lat, lon, rssi, role, meta) + VALUES (?, ?, ?, ?, ?, ?, ?) """, ( session_id, @@ -645,9 +625,6 @@ def add_reference_point_samples( sample.get("rssi"), sample.get("role"), _json_or_none(sample.get("meta")), - sample.get("preset_id"), - sample.get("preset_name"), - _json_or_none(sample.get("params")), ), ) added += 1 @@ -722,19 +699,14 @@ def get_reference_point_session(session_id: int) -> dict[str, Any]: raise ValueError(f"reference point session {session_id} not found") rows = conn.execute( """ - SELECT id, session_id, ts, lat, lon, rssi, role, meta, - preset_id, preset_name, params_json + SELECT id, session_id, ts, lat, lon, rssi, role, meta FROM reference_point_samples WHERE session_id = ? ORDER BY ts ASC """, (session_id,), ).fetchall() - samples = [] - for row in rows: - sample = dict(row) - sample["params"] = _json_load(sample.pop("params_json")) - samples.append(sample) + samples = [dict(row) for row in rows] result = dict(session) result["sample_count"] = len(samples) result["samples"] = samples diff --git a/server/fastapi_app.py b/server/fastapi_app.py index 4d5ca57..ad9119a 100644 --- a/server/fastapi_app.py +++ b/server/fastapi_app.py @@ -96,9 +96,6 @@ class ReferencePointSample(BaseModel): rssi: Optional[float] = None role: Optional[str] = None meta: Optional[Any] = None - preset_id: Optional[str] = None - preset_name: Optional[str] = None - params: Optional[dict[str, Any]] = None class ReferencePointSamplesBody(BaseModel): diff --git a/server/static/app.js b/server/static/app.js index f80081e..4c7efb6 100644 --- a/server/static/app.js +++ b/server/static/app.js @@ -652,6 +652,23 @@ function applyMacroParams(params) { setCmdFormDirty(false); } +function paramsFromMacroLines(lines) { + const params = {}; + (lines || []).forEach(line => { + const s = String(line || '').trim(); + if (s.startsWith('AT+FQ=')) params.frequency_hz = Number(s.slice(6)); + else if (s.startsWith('AT+PW=')) params.power_dbm = Number(s.slice(6)); + else if (s.startsWith('AT+SF=')) params.sf = Number(s.slice(6)); + else if (s.startsWith('AT+BW=')) params.bw_khz = s.slice(6); + else if (s.startsWith('AT+CR=')) params.code_rate = s.slice(6); + else if (s.startsWith('AT+PL=')) params.preamble = Number(s.slice(6)); + else if (s.startsWith('AT+TM=')) params.send_timeout_ms = Number(s.slice(6)); + else if (s === 'AT+TX') params.role = 'TX'; + else if (s === 'AT+RX') params.role = 'RX'; + }); + return params; +} + function selectedMacroPreset() { const id = document.getElementById('macroPresetSelect')?.value; return macroPresets.find(p => p.id === id) || null; @@ -683,7 +700,24 @@ async function loadMacroPresets() { function fillMacroPresetEditor(preset) { document.getElementById('macroPresetName').value = preset?.name || ''; document.getElementById('macroPresetDescription').value = preset?.description || ''; - if (preset?.params) applyMacroParams(preset.params); +} + +function applySelectedMacroPresetToCommandForm() { + const preset = selectedMacroPreset(); + const status = document.getElementById('macroPresetStatus'); + if (!preset) { + if (status) status.textContent = 'Выберите пресет'; + return; + } + const params = Object.keys(preset.params || {}).length + ? preset.params + : paramsFromMacroLines(preset.lines); + applyMacroParams(params); + fillMacroPresetEditor(preset); + const panel = document.getElementById('controlPanel'); + panel?.scrollIntoView({ behavior: 'smooth', block: 'start' }); + document.getElementById('cmdTargetSelect')?.focus(); + if (status) status.textContent = `Пресет "${preset.name || preset.id}" перенесён в форму`; } async function saveMacroPreset() { @@ -848,20 +882,11 @@ async function showReferencePoint(sessionId) { if (!res.ok) throw new Error('reference point ' + res.status); const data = await res.json(); const stats = data.stats || {}; - const byPreset = stats.by_preset || {}; - let byPresetHtml = ''; - Object.entries(byPreset).forEach(([key, val]) => { - byPresetHtml += `
Пресет: ${escapeHtml(key)} - ${metricCard('PER', val.per_percent)} - ${metricCard('RX Quality', val.rx_quality_percent)} -
`; - }); if (summary) { summary.innerHTML = `
${escapeHtml(data.label || 'Реперная точка')} #${data.id}
${escapeHtml(deviceDisplayName(data))} · samples: ${data.sample_count || 0} · ${escapeHtml(data.status || '')}
${metricCard('PER', stats.per_percent)} - ${metricCard('RX Quality', stats.rx_quality_percent)} - ${byPresetHtml || '
Нет разреза по пресетам
'}`; + ${metricCard('RX Quality', stats.rx_quality_percent)}`; } drawReferencePointChart(data.samples || []); if (data.lat != null && data.lon != null) { @@ -3941,7 +3966,7 @@ document.getElementById('macroPresetSelect')?.addEventListener('change', () => { fillMacroPresetEditor(selectedMacroPreset()); }); document.getElementById('btnMacroPresetApply')?.addEventListener('click', () => { - fillMacroPresetEditor(selectedMacroPreset()); + applySelectedMacroPresetToCommandForm(); }); document.getElementById('btnMacroPresetSave')?.addEventListener('click', () => { saveMacroPreset().catch(e => alert(e.message || String(e))); diff --git a/server/static/index.html b/server/static/index.html index 113166e..e30cfc7 100644 --- a/server/static/index.html +++ b/server/static/index.html @@ -199,7 +199,7 @@ - +
diff --git a/server/tests/__pycache__/test_schema.cpython-313-pytest-9.0.3.pyc b/server/tests/__pycache__/test_schema.cpython-313-pytest-9.0.3.pyc index 1e69535217f16d41ced412c67578dc8b563f58c9..b6478b358c9c04eba9a922a2bd2347e4ff23ce76 100644 GIT binary patch delta 621 zcmeC1$atogk?%7vFBbz4TzhJh^+jkSABQee6We4)dA-STYD$wcbp4$4Sc7?jnS)t^ z*@Jn5`GUFhn1Z>1V*F4sfnY(Hm_D;96UZzEhCJp_=3pUK5Q`;PcyfZiIlD+Gn+apE z=;Q}RiM(QZOuB49Ird=jV2Q~ECP9<`=tXl%60cq&lrvZgq&!#}Ov*sD@)EWRU8^iq zs~jm><-xWpK(#6o)EdgDH2IiGq_ie$Q5ewGMcyF72SkK}2umO_*~?UEa-nJT=EtV| zj9jyTBE=w_Hu;ZPnCC}sHeS9D0u16(H^gN?NJ8d@hSmY4ixRFlq?9j8xIv`k#W=Y> zvr6)^HMo8-*}TO3I3x2F7Q4-tEPryaEdtp-fAU4AD#pc=eVjiqE}d-aBFngZasp}X+|7qvY?yUH&M5-%i#$Pu8Hf-961O;Pa`RJ4b5iY!wgb70KwKO(S=%FoPo2^J Ma|oj_qaIi>01{S@DF6Tf delta 902 zcmX@p%h)%Ok?%7vFBbz4Y`9PS;u?I^AOHGzB44V8+FPc-D44t8(G9X>S zvS3mUY62e#cB7gg4>du73=xAsK^^c_<)FT5Mc= z?7Vy*gc!u7Zivf(kc7+)4Xp!87bRQ~vXd3eM8x1+Wl2u1&#W@MYz?j-tTwxw9cN6x z%wl(gTWEs$MQ-UC-WRzwR)}8Y*5AN;k=t%Z`9*H`3oJe-48g=jZlCKcP;`mK=K{Ao zgnI>veHo-0t_scoD%mV(@sopXInV<|OC}3ARxz%a-0k>*an)o8QyHeBHIrvKC9!S- z$}Zf@=xoER3-WCdh+pIhBFsR95RkaVVUwGmQks)$SF{JnWd!2l=*d}bA^h5mzKow^ K7=;=2z^VaFBHiWy diff --git a/server/tests/test_schema.py b/server/tests/test_schema.py index 9c2fb1c..2178662 100644 --- a/server/tests/test_schema.py +++ b/server/tests/test_schema.py @@ -127,7 +127,6 @@ def test_reference_point_session_stats(temp_db): "lon": 37.62, "role": "RX", "meta": {"per_percent": 10, "rx_quality_percent": 80}, - "preset_id": "sf7", }, { "ts": 2.0, @@ -135,7 +134,6 @@ def test_reference_point_session_stats(temp_db): "lon": 37.62, "role": "RX", "meta": {"per_percent": 20, "rx_quality_percent": 90}, - "preset_id": "sf7", }, { "ts": 3.0, @@ -143,7 +141,6 @@ def test_reference_point_session_stats(temp_db): "lon": 37.62, "role": "RX", "meta": {"per_percent": 30, "rx_quality_percent": 100}, - "preset_id": "sf9", }, ], ) @@ -155,7 +152,6 @@ def test_reference_point_session_stats(temp_db): assert session["stats"]["per_percent"]["p50"] == 20 assert session["stats"]["per_percent"]["p75"] == 25 assert session["stats"]["rx_quality_percent"]["p90"] == 98 - assert session["stats"]["by_preset"]["sf7"]["per_percent"]["count"] == 2 def test_macro_presets_crud(temp_db):