mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-06-27 13:31:05 +02:00
Fix freq and BW values, add geofence calc to dry run log
This commit is contained in:
+17
-14
@@ -84,14 +84,10 @@ def _ed25519_sign_expanded(
|
||||
def _get_radio_params() -> dict:
|
||||
"""Read radio frequency parameters from the connected radio's self_info.
|
||||
|
||||
The standalone AU divides raw freq/bw values by 1000 before sending.
|
||||
The meshcore Python library returns radio_freq and radio_bw at the same
|
||||
scale as the JS library, so we apply the same /1000 division.
|
||||
|
||||
IMPORTANT: verify the actual values in dry_run logs before enabling live
|
||||
sends. The units reported by the Python library should be confirmed; if
|
||||
the logged freq/bw look wrong (e.g. 0.915 instead of 915000), the
|
||||
division factor here may need adjusting.
|
||||
The Python meshcore library returns radio_freq in MHz (e.g. 910.525) and
|
||||
radio_bw in kHz (e.g. 62.5). These are exactly the units the map API
|
||||
expects, matching what the JS reference uploader produces after its own
|
||||
/1000 division on raw integer values. No further scaling is applied here.
|
||||
"""
|
||||
try:
|
||||
mc = radio_runtime.meshcore
|
||||
@@ -105,10 +101,10 @@ def _get_radio_params() -> dict:
|
||||
sf = info.get("radio_sf", 0) or 0
|
||||
cr = info.get("radio_cr", 0) or 0
|
||||
return {
|
||||
"freq": freq / 1000.0 if freq else 0,
|
||||
"freq": freq,
|
||||
"cr": cr,
|
||||
"sf": sf,
|
||||
"bw": bw / 1000.0 if bw else 0,
|
||||
"bw": bw,
|
||||
}
|
||||
except Exception as exc:
|
||||
logger.debug("MapUpload: could not read radio params: %s", exc)
|
||||
@@ -222,16 +218,17 @@ class MapUploadModule(FanoutModule):
|
||||
lon: float,
|
||||
) -> None:
|
||||
# Geofence check: if enabled, skip nodes outside the configured radius
|
||||
geofence_dist_km: float | None = None
|
||||
if self.config.get("geofence_enabled"):
|
||||
fence_lat = float(self.config.get("geofence_lat", 0) or 0)
|
||||
fence_lon = float(self.config.get("geofence_lon", 0) or 0)
|
||||
fence_radius_km = float(self.config.get("geofence_radius_km", 0) or 0)
|
||||
dist_km = _haversine_km(fence_lat, fence_lon, lat, lon)
|
||||
if dist_km > fence_radius_km:
|
||||
geofence_dist_km = _haversine_km(fence_lat, fence_lon, lat, lon)
|
||||
if geofence_dist_km > fence_radius_km:
|
||||
logger.debug(
|
||||
"MapUpload: skipping %s — outside geofence (%.2f km > %.2f km)",
|
||||
pubkey[:12],
|
||||
dist_km,
|
||||
geofence_dist_km,
|
||||
fence_radius_km,
|
||||
)
|
||||
return
|
||||
@@ -271,10 +268,16 @@ class MapUploadModule(FanoutModule):
|
||||
}
|
||||
|
||||
if dry_run:
|
||||
geofence_note = (
|
||||
f" | geofence: {geofence_dist_km:.2f} km from observer"
|
||||
if geofence_dist_km is not None
|
||||
else ""
|
||||
)
|
||||
logger.info(
|
||||
"MapUpload [DRY RUN] %s (%s) → would POST to %s\n payload: %s",
|
||||
"MapUpload [DRY RUN] %s (%s)%s → would POST to %s\n payload: %s",
|
||||
pubkey[:12],
|
||||
role_name,
|
||||
geofence_note,
|
||||
api_url,
|
||||
json.dumps(request_payload, separators=(",", ":")),
|
||||
)
|
||||
|
||||
@@ -294,7 +294,7 @@ const CREATE_INTEGRATION_DEFINITIONS: readonly CreateIntegrationDefinition[] = [
|
||||
label: 'Map Upload',
|
||||
section: 'Bulk Forwarding',
|
||||
description:
|
||||
'Upload node positions to map.meshcore.dev or a compatible map API endpoint.',
|
||||
'Upload repeaters and room servers to map.meshcore.dev or a compatible map API endpoint.',
|
||||
defaultName: 'Map Upload',
|
||||
nameMode: 'counted',
|
||||
defaults: {
|
||||
@@ -1093,7 +1093,7 @@ function MapUploadConfigEditor({
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Automatically upload heard repeater and room advertisements to{' '}
|
||||
Automatically upload heard repeater and room server advertisements to{' '}
|
||||
<a
|
||||
href="https://map.meshcore.dev"
|
||||
target="_blank"
|
||||
|
||||
@@ -676,11 +676,12 @@ class TestGetRadioParams:
|
||||
params = _get_radio_params()
|
||||
assert params == {"freq": 0, "cr": 0, "sf": 0, "bw": 0}
|
||||
|
||||
def test_divides_freq_and_bw_by_1000(self):
|
||||
def test_passes_freq_and_bw_directly(self):
|
||||
"""Python lib already returns freq in MHz and bw in kHz — no division needed."""
|
||||
mock_rt = MagicMock()
|
||||
mock_rt.meshcore.self_info = {
|
||||
"radio_freq": 915000,
|
||||
"radio_bw": 125000,
|
||||
"radio_freq": 915.0,
|
||||
"radio_bw": 125.0,
|
||||
"radio_sf": 10,
|
||||
"radio_cr": 5,
|
||||
}
|
||||
@@ -893,3 +894,58 @@ class TestGeofence:
|
||||
assert ("ab" * 32) in mod._seen
|
||||
|
||||
await mod.stop()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dry_run_geofence_logs_distance(self):
|
||||
"""dry_run + geofence_enabled must include the calculated distance in the log line."""
|
||||
import logging
|
||||
|
||||
mod = _make_module({
|
||||
"dry_run": True,
|
||||
"geofence_enabled": True,
|
||||
"geofence_lat": 51.5,
|
||||
"geofence_lon": -0.1,
|
||||
"geofence_radius_km": 100.0,
|
||||
})
|
||||
await mod.start()
|
||||
|
||||
fake_private = bytes(range(64))
|
||||
fake_public = bytes(range(32))
|
||||
|
||||
with (
|
||||
patch("app.fanout.map_upload.get_private_key", return_value=fake_private),
|
||||
patch("app.fanout.map_upload.get_public_key", return_value=fake_public),
|
||||
patch("app.fanout.map_upload._get_radio_params", return_value={"freq": 0, "cr": 0, "sf": 0, "bw": 0}),
|
||||
):
|
||||
with patch("app.fanout.map_upload.logger") as mock_logger:
|
||||
# ~50 km north — inside the fence
|
||||
await mod._upload("ab" * 32, 1000, 2, "aabb", 51.95, -0.1)
|
||||
mock_logger.info.assert_called_once()
|
||||
log_message = mock_logger.info.call_args[0][0] % mock_logger.info.call_args[0][1:]
|
||||
assert "geofence:" in log_message
|
||||
assert "km from observer" in log_message
|
||||
|
||||
await mod.stop()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dry_run_no_geofence_no_distance_in_log(self):
|
||||
"""dry_run without geofence_enabled must not include a distance note in the log."""
|
||||
mod = _make_module({"dry_run": True, "geofence_enabled": False})
|
||||
await mod.start()
|
||||
|
||||
fake_private = bytes(range(64))
|
||||
fake_public = bytes(range(32))
|
||||
|
||||
with (
|
||||
patch("app.fanout.map_upload.get_private_key", return_value=fake_private),
|
||||
patch("app.fanout.map_upload.get_public_key", return_value=fake_public),
|
||||
patch("app.fanout.map_upload._get_radio_params", return_value={"freq": 0, "cr": 0, "sf": 0, "bw": 0}),
|
||||
):
|
||||
with patch("app.fanout.map_upload.logger") as mock_logger:
|
||||
await mod._upload("ab" * 32, 1000, 2, "aabb", 51.5, -0.1)
|
||||
mock_logger.info.assert_called_once()
|
||||
log_message = mock_logger.info.call_args[0][0] % mock_logger.info.call_args[0][1:]
|
||||
assert "geofence:" not in log_message
|
||||
|
||||
await mod.stop()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user