Skip to content
Merged
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
52 changes: 30 additions & 22 deletions DISCRETE_EVENT_SIM.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,48 @@
# Discrete-event simulator
The discrete-event simulator mimics the radio section of the device software. It is currently based on Meshtastic 2.1.
# Discrete-event simulator
The discrete-event simulator mimics the radio section of the device software. It is currently based on Meshtastic 2.1.

## Usage
Please `git clone` or download this repository, navigate to the Meshtasticator folder (optionally create a virtual environment) and install the necessary requirements using:
Please `git clone` or download this repository, navigate to the Meshtasticator folder (optionally create a virtual environment) and install the necessary requirements using:
```pip install -r requirements.txt```.

To start one simulation with the default configurations, run:

```python3 loraMesh.py [nr_nodes]```
```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.

![](/img/configNode.png)

If the number of nodes is given, it will randomly place nodes in the area. It makes sure that each node can reach at least one other node. Furthermore, all nodes are placed at a configurable minimum distance (MINDIST) from each other.
If the number of nodes is given, it will randomly place nodes in the area. It makes sure that each node can reach at least one other node. Furthermore, all nodes are placed at a configurable minimum distance (MINDIST) from each other.

For non-interactive smoke tests or CI runs, pass `--no-gui` together with either a node count or `--from-file`. This skips the Tk/Matplotlib placement graph and the final schedule plot while keeping the simulation logic unchanged:

```python3 loraMesh.py 10 --no-gui```

Short deterministic smoke runs can also override the configured duration and message period from the command line:

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

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```

If you want to change any of the configurations, adapt the file *out/nodeConfig.yaml* before running it with the above command.

For running multiple repetitions of simulations for a set of parameters, e.g. the number of nodes, run:
For running multiple repetitions of simulations for a set of parameters, e.g. the number of nodes, run:

```python3 batchSim.py```
```python3 batchSim.py```

After the simulations are done, it plots relevant metrics obtained from the simulations. It saves these metrics in */out/report/* to analyze them later on. See *plotExample.py* for an example Python script to plot the results.
After the simulations are done, it plots relevant metrics obtained from the simulations. It saves these metrics in */out/report/* to analyze them later on. See *plotExample.py* for an example Python script to plot the results.

To simulate different parameters, you will have to change the *batchSim.py* script yourself.
To simulate different parameters, you will have to change the *batchSim.py* script yourself.

## Custom configurations
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
Expand All @@ -45,31 +53,31 @@ The LoRa modem ([see Meshtastic radio settings](https://meshtastic.org/docs/over
| 7 |Very Long Slow|62.5|4/8|12|0.09

### 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.
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.

### Packet length
Payload size of each generated message in bytes. For a position packet, it will be around 40 bytes.
### Packet length
Payload size of each generated message in bytes. For a position packet, it will be around 40 bytes.

### Model
This feature is referred to the path loss model, i.e. what the simulator uses to calculate how well a signal will propagate. Note that this is only a rough estimation of the physical environment and will not be 100% accurate, as it depends on a lot of factors. The implemented pathloss models are:
* ```0``` set the log-distance model
* ```1``` set the Okumura-Hata for small and medium-size cities model
* ```2``` set the Okumura-Hata for metropolitan areas
* ```0``` set the log-distance model
* ```1``` set the Okumura-Hata for small and medium-size cities model
* ```2``` set the Okumura-Hata for metropolitan areas
* ```3``` set the Okumura-Hata for suburban environments
* ```4``` set the Okumura-Hata for rural areas
* ```5``` set the 3GPP for suburban macro-cell
* ```6``` set the 3GPP for metropolitan macro-cell
* ```4``` set the Okumura-Hata for rural areas
* ```5``` set the 3GPP for suburban macro-cell
* ```6``` set the 3GPP for metropolitan macro-cell

### Broadcasts or direct messages (DMs)
By default, *DMs* is set to False, meaning it will send broadcast messages only. If you set it to True, each node will only send DMs to a random other node in the network.

## Explanation
A discrete-event simulator jumps from event to event over time, where an event is a change in the state of the system. It is therefore well-suited for simulating communication networks.

For every node in the simulation, an instance is created that mimics the [Meshtastic logic](https://meshtastic.org/docs/overview/mesh-algo). Each node runs three processes in parallel: *generateMessage*, *transmit* and *receive*. The first creates an event by constructing a new message with unique sequence number at a random time, taken from an exponential distribution. For now, each generated message is of the same payload size. The second and third processes model the actual transmitting and receiving behavior, respectively.
For every node in the simulation, an instance is created that mimics the [Meshtastic logic](https://meshtastic.org/docs/overview/mesh-algo). Each node runs three processes in parallel: *generateMessage*, *transmit* and *receive*. The first creates an event by constructing a new message with unique sequence number at a random time, taken from an exponential distribution. For now, each generated message is of the same payload size. The second and third processes model the actual transmitting and receiving behavior, respectively.

The model of the LoRa physical (PHY) layer is in */lib/phy.py*. Depending on the modem used, it is calculated what the airtime of a packet is. The PHY layer uses a configurable pathloss model to estimate whether nodes at a specific distance can sense each other's packets. Furthermore, it determines whether two packets collide, which depends on the frequency, spreading factor, received time and received power of the two packets.
The model of the LoRa physical (PHY) layer is in */lib/phy.py*. Depending on the modem used, it is calculated what the airtime of a packet is. The PHY layer uses a configurable pathloss model to estimate whether nodes at a specific distance can sense each other's packets. Furthermore, it determines whether two packets collide, which depends on the frequency, spreading factor, received time and received power of the two packets.

The routing behavior is implemented in each of the processes of the node. Inside *generateMessage*, reliable retransmissions are handled if no implicit acknowledgement is received. A MeshPacket (defined in */lib/packet.py*) is created to transfer the message. Note that there may be multiple packets created containing the same message, due to retransmissions and rebroadcasting. In *receive*, it is decided what to do on reception of a packet. A packet is flooded if its hoplimit is not zero and no rebroadcast of this packet was heard before. In *transmit*, delays of the Medium Access Control (MAC) layer are called from */lib/mac.py*. The MAC uses a listen-before-talk mechanism, including introducing (random or SNR-based) delays before transmitting a packet. When a packet is ready to be transferred over the air, it is first checked whether in the meantime still no acknowledgement was received, otherwise the transmission is canceled.

The actual communication between processes of different nodes is handled by a BroadcastPipe of [Simpy](https://simpy.readthedocs.io/en/latest/examples/process_communication.html). This ensures that a transmitted packet by one node creates events (one at the start of a packet and one at the end) at the receiving nodes.
The actual communication between processes of different nodes is handled by a BroadcastPipe of [Simpy](https://simpy.readthedocs.io/en/latest/examples/process_communication.html). This ensures that a transmitted packet by one node creates events (one at the start of a packet and one at the end) at the receiving nodes.
1 change: 1 addition & 0 deletions lib/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ def __init__(self):
self.REGION = self.regions["US"] # Select a different region here
self.CHANNEL_NUM = 27 # Channel number

self.GUI_ENABLED = True # whether to update/save the Tk/Matplotlib node-placement graph during CLI simulation
self.PLOT = True # whether to plot the time schedule of packets after the simulation
### End of discrete-event specific ###

Expand Down
14 changes: 8 additions & 6 deletions lib/discrete_event_sim.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import logging
from typing import TYPE_CHECKING

# probably not necessary, but "Environment" seemed too generic to me
from simpy import Environment as SimpyEnvironment
import numpy as np

from lib.common import setup_asymmetric_links
from lib.config import Config
from lib.discrete_event import BroadcastPipe
from lib.discrete_event_sim_components import SimulationState, SimulationDataTracking
from lib.gui import Graph, run_graph_updates
from lib.node import MeshNode, NodeConfig, default_generate_node_list
from lib.packet import MeshPacket
from lib.node import MeshNode, NodeConfig

if TYPE_CHECKING:
from lib.gui import Graph

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -110,7 +111,7 @@ class DiscreteEventSim:
simulation config, all necessary state, and sim plumbing.
"""

def __init__(self, conf: Config, node_configs: [NodeConfig], graph: Graph | None = None):
def __init__(self, conf: Config, node_configs: [NodeConfig], graph: "Graph | None" = None):
"""Constructor.

Arguments:
Expand Down Expand Up @@ -154,6 +155,8 @@ def __init__(self, conf: Config, node_configs: [NodeConfig], graph: Graph | None
# TODO: revisit this design decision sometime. Do we want graphing/GUI to be handled in this object,
# or by some external object the user wires in, like how batchSim.py adds in the simulation_progress process?
# TODO: batchSim does this, but without the 4th parameter
from lib.gui import run_graph_updates

self.env.process(run_graph_updates(self.env, self.graph, self.mutated_state.nodes, self.conf.ONE_MIN_INTERVAL))
self.conf.update_router_dependencies()

Expand Down Expand Up @@ -190,4 +193,3 @@ def get_results(self) -> SimulationResults:
results.finalize(self.conf)

return results

Loading