Skip to content
Closed
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
106 changes: 95 additions & 11 deletions 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,79 @@ 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 public Meshtastic map node locations. The map
endpoint currently returns a broad node list, so pass a local bounding box and
an explicit simulated antenna height:

```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 --map-antenna-height 1.5 --no-gui```

Terrain obstruction can be added to map or origin-backed scenario inputs without
creating a custom terrain file. `--terrain-srtm` downloads missing SRTM HGT
tiles into a local cache, samples the scenario bounding box, and feeds the
terrain grid directly into the link budget:

```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 `--map-antenna-height` for antenna height above local
ground. When `--terrain-srtm` is enabled, SRTM ground elevation is added to that
antenna height to place each node at absolute antenna altitude for 3D distance
calculations.

Land-cover clutter is a separate optional CSV grid. Use it for broad urban,
open, water, or forest excess-loss inputs without pretending Meshtasticator is a
building-level ray tracer:

```python3 loraMesh.py --from-file nodeConfig.yaml --terrain-srtm --clutter-grid clutter.csv --no-gui```

`tools/osm_to_clutter_csv.py` can build a coarse clutter grid from public
OpenStreetMap building, landuse, natural, and water polygons. The simulator
never fetches OpenStreetMap data implicitly.

Two optional RF models can make dense or weak-link runs less binary:

```python3 loraMesh.py 20 --no-gui --phy-loss-model --capture-collision-model```

`--phy-loss-model` keeps RSSI/sensitivity as the hearability gate, then applies
a smooth SNR-to-payload-success curve that depends on packet size and LoRa
coding rate. `--capture-collision-model` keeps CAD-detectable but undecodable
packets on the RF timeline as interference energy, and uses capture/preamble
overlap rules instead of treating every overlap as identical.

Packaged real-mesh presets can be listed and loaded directly:

```python3 loraMesh.py --list-presets```

The `batumi` preset includes sanitized Batumi/Georgia-area node geometry, a
matching terrain grid, an OpenStreetMap-derived land-cover clutter grid, and an
aggregate radio calibration over generated path features. Terrain, clutter, and
the fitted link-calibration model are enabled automatically for the preset
unless you pass different `--terrain-grid` or `--clutter-grid` inputs; use
`--no-clutter` for old-style comparison runs. The calibration report is in
`docs/batumi_radio_calibration.md`.

```python3 loraMesh.py --preset batumi --no-gui --simtime-seconds 5 --period-seconds 2 --phy-loss-model --capture-collision-model```

Dynamic Coding Rate is opt-in and chooses LoRa CR 4/5..4/8 per outgoing packet
without changing the preset's SF or bandwidth:

```python3 loraMesh.py 20 --no-gui --phy-loss-model --capture-collision-model --dcr```

The policy keeps ordinary first-attempt traffic compact, spends extra FEC on
quiet retries, ACKs, non-busy direct relays, and last-hop relays, then records
`dcrTxByCr` and `dcrAirtimeByCr` in simulation results. This keeps idle airtime
as a reserve instead of turning every quiet packet into CR 4/8.

Dynamic TX Power is also opt-in:

```python3 loraMesh.py 20 --no-gui --capture-collision-model --dtp```

DTP keeps configured `PTX` as the maximum regional/base power and only applies
temporary reductions just before transmission. Origin packets stay at max power;
relay packets may shrink power when channel pressure is high or the prior hop
was strong enough. Final retries and CR 4/8 rescue packets stay at full power so
the interference-reduction knob does not fight the reliability knob.

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 All @@ -41,16 +114,27 @@ To simulate different parameters, you will have to change the *batchSim.py* scri
Here we list some of the configurations, which you can change to model your scenario in */lib/config.py*. These apply to all nodes, except those that you configure per node when using the plot.
### Modem
The LoRa modem ([see Meshtastic radio settings](https://meshtastic.org/docs/overview/radio-settings#predefined-channels)) that is used, as defined below:
|Modem | Name | Bandwidth (kHz) | Coding rate | Spreading Factor | Data rate (kbps)
|--|--|--|--|--|--|
| 0 |Short Fast|250|4/8|7|6.8
| 1 |Short Slow|250|4/8|8|3.9
| 2 |Mid Fast|250|4/8|9|2.2
| 3 |Mid Slow|250|4/8|10|1.2
| 4 |Long Fast|250|4/8|11|0.67
| 5 |Long Moderate|125|4/8|11|0.335
| 6 |Long Slow|125|4/8|12|0.18
| 7 |Very Long Slow|62.5|4/8|12|0.09
| Modem | Name | Bandwidth (kHz) | Base coding rate | Spreading Factor | Nominal data rate (kbps) |
|--|--|--:|--:|--:|--:|
| 0 | Short Turbo | 500 | 4/5 | 7 | 21.9 |
| 1 | Short Fast | 250 | 4/5 | 7 | 10.9 |
| 2 | Short Slow | 250 | 4/5 | 8 | 6.25 |
| 3 | Medium Fast | 250 | 4/5 | 9 | 3.52 |
| 4 | Medium Slow | 250 | 4/5 | 10 | 1.95 |
| 5 | Long Turbo | 500 | 4/8 | 11 | 1.34 |
| 6 | Long Fast | 250 | 4/5 | 11 | 1.07 |
| 7 | Long Moderate | 125 | 4/8 | 11 | 0.336 |
| 8 | Long Slow | 125 | 4/8 | 12 | 0.183 |
| 9 | Very Long Slow | 62.5 | 4/8 | 12 | 0.0916 |

The simulator stores coding rates as their LoRa denominators (`5` through
`8`, meaning CR 4/5 through 4/8). This table shows the configured base CR; when
`--dcr` is enabled, the simulator may select a different CR for each outgoing
packet while leaving the preset's SF and bandwidth unchanged.

DCR and DTP can be combined. DCR changes airtime and forward-error-correction
strength; DTP changes how many receivers can CAD-detect, demodulate, or collide
with the packet.

### Period
Mean period (in ms) with which the nodes generate a new message following an exponential distribution. E.g. if you set it to 300s, each node will generate a message on average once every five minutes.
Expand Down
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,61 @@
# Meshtasticator
Discrete-event and interactive simulator for [Meshtastic](https://meshtastic.org/).

## Quick start

Install the Python dependencies, then ask the CLI what runnable scenarios it
already knows about:

```bash
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt

./loraMesh.py --list-presets
./loraMesh.py --list-modem-presets
```

Run the packaged Batumi/Georgia-area radio scenario headlessly:

```bash
./loraMesh.py --preset batumi --no-gui --simtime-seconds 60 --period-seconds 5
```

Compare the static, Dynamic Coding Rate, and Dynamic Coding Rate + Dynamic TX
Power policies in one command:

```bash
python3 tools/radio_policy_compare.py --simtime-seconds 60 --period-seconds 5
```

For CI-style runs, write JSON/Markdown artifacts and make regressions fail the
job:

```bash
python3 tools/radio_policy_compare.py \
--simtime-seconds 120 \
--period-seconds 5 \
--json-output out/radio_policy_compare.json \
--markdown-output out/radio_policy_compare.md \
--max-reach-drop-pp 1.0 \
--max-tx-air-increase-pp 1.0
```

For manual Dynamic Coding Rate or Dynamic TX Power experiments, keep the same
scenario and traffic load while enabling packet loss and capture-aware
collisions:

```bash
./loraMesh.py --preset batumi --no-gui --simtime-seconds 60 --period-seconds 5 \
--phy-loss-model --capture-collision-model --dcr

./loraMesh.py --preset batumi --no-gui --simtime-seconds 60 --period-seconds 5 \
--phy-loss-model --capture-collision-model --dcr --dtp
```

See [Radio Physics Quickstart](docs/radio_physics_quickstart.md) for what the
flags mean and which result fields to compare.

## Discrete-event simulator
The discrete-event simulator mimics the radio section of the device software in order to understand its working. It can also be used to assess the performance of your scenario, or the scalability of the protocol.

Expand Down
7 changes: 3 additions & 4 deletions batchSim.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@
print('Tkinter is needed. Install python3-tk with your package manager.')
exit(1)

import simpy
import numpy as np
import random
import matplotlib.pyplot as plt

from lib.config import Config
from lib.common import find_random_position, setup_asymmetric_links
from lib.discrete_event import BroadcastPipe, sim_report
from lib.common import find_random_position
from lib.discrete_event import sim_report
from lib.discrete_event_sim import DiscreteEventSim
from lib.gui import Graph, run_graph_updates
from lib.gui import Graph
from lib.node import NodeConfig
from lib.point import Point

Expand Down
Loading