Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion DISCRETE_EVENT_SIM.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ To start one simulation with the default configurations, run:

```python3 loraMesh.py [nr_nodes]```

If no argument is given, you first have to place the nodes on a plot. After you place a node, you can change its [role](https://meshtastic.org/docs/settings/config/device#role), hopLimit, height (elevation) and antenna gain. These settings will automatically save when you place a new node or when you start the simulation.
If no argument is given, you first have to place the nodes on a plot. After you place a node, you can change its [role](https://meshtastic.org/docs/settings/config/device#role), hopLimit, antenna height above local ground, and antenna gain. These settings will automatically save when you place a new node or when you start the simulation.

![](/img/configNode.png)

Expand All @@ -23,6 +23,61 @@ Short deterministic smoke runs can also override the configured duration and mes

```python3 loraMesh.py 2 --no-gui --simtime-seconds 5 --period-seconds 0.5```

The same headless path can import positioned real-mesh nodes. `--from-map`
reads a Meshtastic map `/api/v1/nodes` JSON endpoint; the public default is
`https://meshtastic.liamcottle.net/api/v1/nodes`, but you can pass another
compatible endpoint URL. These map endpoints usually return a broad node list,
so pass a local area-of-interest bounding box. `--map-bbox` uses the common
`min_lat,min_lon,max_lat,max_lon` order that most GIS tools call
`south,west,north,east`; you can copy those four numbers from OpenStreetMap's
Export panel, geojson.io's bbox readout, QGIS, or any other tool that shows the
extent of the map view or selected polygon. Keep the box tight enough for the
local scenario you want to simulate:

```python3 loraMesh.py --from-map https://meshtastic.liamcottle.net/api/v1/nodes --map-bbox 41.50,41.50,41.82,41.86 --map-limit 50 --no-gui```

You can also import positioned nodes from the NodeDB cached by a local
Meshtastic device. This uses the Python client `interface.nodesByNum` data that
backs `meshtastic --nodes`, not the pretty-printed table. Use TCP for a network
device, or omit `--nodedb-host` to use Meshtastic serial auto-detection. For a
quick local-device run, pass the device address and cap the imported node count:

```python3 loraMesh.py --from-nodedb --nodedb-host 192.168.1.23 --map-limit 50 --no-gui```

NodeDB often contains old or far-away positions. Add `--map-bbox` when you want
to restrict the run to one local area:

```python3 loraMesh.py --from-nodedb --nodedb-host 192.168.1.23 --map-bbox 41.50,41.50,41.82,41.86 --map-limit 50 --no-gui```

Imported nodes use the same `HM` antenna height and `hopLimit` defaults as
generated and file-backed scenarios. Change those config values when the
position source does not carry the simulation value you want.

Terrain obstruction can be added to map, NodeDB, or origin-backed scenario inputs
without creating a custom terrain file. `--terrain-srtm` downloads missing SRTM
HGT tiles from Mapzen Terrain Tiles on AWS into a local cache and feeds the
terrain grid directly into terrain-aware node geometry:

```python3 loraMesh.py --from-nodedb --nodedb-host 192.168.1.23 --map-limit 50 --terrain-srtm --no-gui```

With an explicit `--map-bbox`, SRTM samples that whole requested rectangle. When
the terrain bbox is derived from imported or file-backed nodes, Meshtasticator
keeps the download smaller: it loads tiles around the selected nodes and along
flat-link candidate paths, instead of downloading every tile in a large
edge-to-edge rectangle. When publishing screenshots, reports, or derived
datasets from this terrain source, attribute the terrain data to
[Mapzen Terrain Tiles on AWS](https://registry.opendata.aws/terrain-tiles/),
SRTM/NASA, and their underlying open elevation sources:

```python3 loraMesh.py --from-map https://meshtastic.liamcottle.net/api/v1/nodes --map-bbox 41.50,41.50,41.82,41.86 --map-limit 50 --terrain-srtm --no-gui```

Map payload `altitude` values are absolute GPS/MSL altitude, not antenna height,
so map import keeps using `HM` as the fallback antenna height above local
ground. When `--terrain-srtm` is enabled, each map node is checked
against its own SRTM ground sample: plausible positive map altitudes are used as
absolute node altitude, while missing, below-ground, or implausibly high values
fall back to `SRTM ground + antenna height` for 3D distance calculations.

If you placed the nodes yourself, after a simulation the number of nodes, their coordinates and configuration are automatically saved and you can rerun the scenario with:

```python3 loraMesh.py --from-file```
Expand Down
2 changes: 1 addition & 1 deletion batchSim.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ def __init__(self, conf, x, y):
x, y = coords[nodeId]

# We create a NodeConfig object so that MeshNode will use that
nodeConfig = NodeConfig(nodeId, Point(x, y, routerTypeConf.HM), routerTypeConf.PERIOD, antenna_gain=routerTypeConf.GL, hop_limit=routerTypeConf.hopLimit)
nodeConfig = NodeConfig(nodeId, Point(x, y, routerTypeConf.HM), routerTypeConf.PERIOD, routerTypeConf.PTX, routerTypeConf.FREQ, antenna_gain=routerTypeConf.GL, hop_limit=routerTypeConf.hopLimit)
node_configs.append(nodeConfig)

if SHOW_GRAPH:
Expand Down
8 changes: 6 additions & 2 deletions lib/common.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import random
import os

import numpy as np

from lib import phy
from lib.point import Point


def node_antenna_height(node):
"""Return antenna height above ground, falling back to legacy Point.z."""
return getattr(node, "antennaHeight", getattr(node, "antenna_height", node.position.z))

def find_random_position(conf, node_configs) -> (float, float):
"""Given a simulation config and list of existing node configs/nodes, find a
randomly chosen position for the next node such that it is within the
Expand Down Expand Up @@ -79,7 +83,7 @@ def setup_asymmetric_links(conf, nodes):
nodeA = nodes[a]
nodeB = nodes[b]
distAB = nodeA.position.euclidean_distance(nodeB.position)
pathLossAB = phy.estimate_path_loss(conf, distAB, conf.FREQ, nodeA.position.z, nodeB.position.z)
pathLossAB = phy.estimate_path_loss(conf, distAB, conf.FREQ, node_antenna_height(nodeA), node_antenna_height(nodeB))

offsetAB = conf.LINK_OFFSET[(a, b)]
offsetBA = conf.LINK_OFFSET[(b, a)]
Expand Down
23 changes: 23 additions & 0 deletions lib/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,29 @@ def __init__(self):
self.NPREAM = 16 # number of preamble symbols from RadioInterface.h
### End of PHY parameters ###

#################################################
####### TERRAIN OBSTRUCTION MODEL ###############
#################################################
# Disabled by default. When enabled, TERRAIN_GRID holds an in-memory
# grid sampled from SRTM HGT tiles.
self.TERRAIN_ENABLED = False
self.TERRAIN_GRID = None
# "ground": Point.z is antenna height above local ground.
# "sea_level": Point.z is absolute antenna altitude after adding
# terrain ground elevation.
self.NODE_Z_REFERENCE = "ground"
self.GEO_ORIGIN_LAT = None
self.GEO_ORIGIN_LON = None
self.TERRAIN_PROFILE_SAMPLES = 24
self.TERRAIN_FRESNEL_CLEARANCE = 0.6
# Match the common radio-planning 4/3 Earth-radius approximation. The
# terrain model uses it as an earth-bulge term so long coastal and ridge
# links do not look unrealistically flat.
self.TERRAIN_EFFECTIVE_EARTH_RADIUS_MULTIPLIER = 4.0 / 3.0
self.TERRAIN_MIN_ANTENNA_HEIGHT_M = 1.5
self.TERRAIN_MAX_LOSS_DB = 35.0
self.TERRAIN_LOSS_CACHE_MAX_ENTRIES = 16384

# Misc
self.SEED = 44 # random seed to use
# End of misc
Expand Down
13 changes: 13 additions & 0 deletions lib/geo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Small geographic validation helpers shared by map-oriented inputs."""

import math


def valid_lat_lon(lat, lon):
"""Return whether latitude/longitude are finite WGS84-style coordinates."""
return (
math.isfinite(lat)
and math.isfinite(lon)
and -90.0 <= lat <= 90.0
and -180.0 <= lon <= 180.0
)
Loading
Loading