241 lines
5.8 KiB
Python
241 lines
5.8 KiB
Python
"""
|
|
Main simulation entry point.
|
|
|
|
Simulation flow:
|
|
1. Create SimPy environment
|
|
2. Create wireless channel
|
|
3. Randomly deploy nodes
|
|
4. Designate sink node
|
|
5. Start node processes
|
|
6. Run simulation
|
|
7. Output metrics
|
|
"""
|
|
|
|
import random
|
|
import json
|
|
import simpy
|
|
|
|
from sim.node.node import Node
|
|
from sim.radio.channel import Channel
|
|
from sim.core.metrics import MetricsCollector
|
|
from sim import config
|
|
|
|
|
|
def deploy_nodes(
|
|
env: simpy.Environment,
|
|
channel: Channel,
|
|
num_nodes: int = None,
|
|
area_size: float = None,
|
|
) -> list:
|
|
"""
|
|
Deploy nodes randomly in the area.
|
|
|
|
Args:
|
|
env: SimPy environment
|
|
channel: Wireless channel
|
|
num_nodes: Number of nodes (default from config)
|
|
area_size: Area size (default from config)
|
|
|
|
Returns:
|
|
List of Node objects
|
|
"""
|
|
if num_nodes is None:
|
|
num_nodes = config.NODE_COUNT
|
|
if area_size is None:
|
|
area_size = config.AREA_SIZE
|
|
|
|
nodes = []
|
|
|
|
# Deploy sink node at center
|
|
sink_x = area_size / 2
|
|
sink_y = area_size / 2
|
|
|
|
sink = Node(
|
|
env=env,
|
|
node_id=config.SINK_NODE_ID,
|
|
x=sink_x,
|
|
y=sink_y,
|
|
channel=channel,
|
|
is_sink=True,
|
|
)
|
|
nodes.append(sink)
|
|
|
|
# Deploy other nodes randomly
|
|
for i in range(1, num_nodes):
|
|
x = random.uniform(0, area_size)
|
|
y = random.uniform(0, area_size)
|
|
|
|
node = Node(env=env, node_id=i, x=x, y=y, channel=channel)
|
|
nodes.append(node)
|
|
|
|
return nodes
|
|
|
|
|
|
def setup_receive_callback(nodes: list, channel: Channel):
|
|
"""
|
|
Setup receive callback from channel to nodes.
|
|
|
|
Args:
|
|
nodes: List of nodes
|
|
channel: Wireless channel
|
|
"""
|
|
|
|
def receive_dispatcher(node_id: int, received):
|
|
"""Dispatch received packet to appropriate node."""
|
|
for node in nodes:
|
|
if node.node_id == node_id:
|
|
node.on_receive(received)
|
|
break
|
|
|
|
channel.receive_callback = receive_dispatcher
|
|
|
|
|
|
def run_simulation(
|
|
num_nodes: int = None,
|
|
area_size: float = None,
|
|
sim_time: float = None,
|
|
seed: int = None,
|
|
) -> dict:
|
|
"""
|
|
Run the LoRa network simulation.
|
|
|
|
Args:
|
|
num_nodes: Number of nodes
|
|
area_size: Area size in meters
|
|
sim_time: Simulation time in seconds
|
|
seed: Random seed for reproducibility
|
|
|
|
Returns:
|
|
Simulation results including metrics
|
|
"""
|
|
# Set random seed
|
|
if seed is not None:
|
|
random.seed(seed)
|
|
|
|
# Create environment
|
|
env = simpy.Environment()
|
|
|
|
# Create channel
|
|
channel = Channel(env)
|
|
|
|
# Deploy nodes
|
|
if num_nodes is None:
|
|
num_nodes = config.NODE_COUNT
|
|
if area_size is None:
|
|
area_size = config.AREA_SIZE
|
|
if sim_time is None:
|
|
sim_time = config.SIM_TIME
|
|
|
|
nodes = deploy_nodes(env, channel, num_nodes, area_size)
|
|
|
|
# Setup receive callbacks
|
|
setup_receive_callback(nodes, channel)
|
|
|
|
# Create metrics collector
|
|
metrics = MetricsCollector()
|
|
metrics.set_start_time(0.0)
|
|
|
|
# Add collision callback
|
|
initial_collisions = channel.collision_count
|
|
|
|
# Start all nodes
|
|
for node in nodes:
|
|
node.start()
|
|
|
|
# Run simulation
|
|
env.run(until=sim_time)
|
|
|
|
# Collect metrics
|
|
convergence_time = config.HELLO_PERIOD * 3 # Estimate convergence
|
|
metrics.set_convergence_time(convergence_time)
|
|
|
|
# First add stats for non-sink nodes
|
|
for node in nodes:
|
|
if node.is_sink:
|
|
continue
|
|
stats = node.get_stats()
|
|
metrics.add_node_stats(node.node_id, stats)
|
|
|
|
# Then add sink stats
|
|
for node in nodes:
|
|
if node.is_sink:
|
|
stats = node.get_stats()
|
|
metrics.add_sink_stats(node.node_id, stats)
|
|
break
|
|
|
|
metrics.add_collision(channel.collision_count - initial_collisions)
|
|
|
|
# Get results
|
|
results = {
|
|
"config": {
|
|
"num_nodes": num_nodes,
|
|
"area_size": area_size,
|
|
"sim_time": sim_time,
|
|
"seed": seed,
|
|
},
|
|
"metrics": metrics.get_metrics().get_summary(),
|
|
"topology": [],
|
|
}
|
|
|
|
# Add topology info
|
|
for node in nodes:
|
|
results["topology"].append(
|
|
{
|
|
"node_id": node.node_id,
|
|
"is_sink": node.is_sink,
|
|
"x": node.x,
|
|
"y": node.y,
|
|
"cost": node.routing.cost if node.routing.cost != float("inf") else -1,
|
|
"parent": node.routing.parent,
|
|
}
|
|
)
|
|
|
|
return results
|
|
|
|
|
|
def main():
|
|
"""Main entry point."""
|
|
print("=" * 60)
|
|
print("LoRa Multi-Hop Network Simulation")
|
|
print("=" * 60)
|
|
|
|
# Run simulation
|
|
results = run_simulation()
|
|
|
|
# Print results
|
|
print("\n--- Configuration ---")
|
|
print(f"Nodes: {results['config']['num_nodes']}")
|
|
print(
|
|
f"Area: {results['config']['area_size']}m x {results['config']['area_size']}m"
|
|
)
|
|
print(f"Simulation time: {results['config']['sim_time']}s")
|
|
|
|
print("\n--- Metrics ---")
|
|
metrics = results["metrics"]
|
|
print(f"Total sent: {metrics['total_sent']}")
|
|
print(f"Total received: {metrics['total_received']}")
|
|
print(f"Packet Delivery Ratio: {metrics['pdr']}%")
|
|
print(f"Average hops: {metrics['avg_hop']}")
|
|
print(f"Average retries: {metrics['avg_retries']}")
|
|
print(f"Convergence time: {metrics['convergence_time']}s")
|
|
print(f"Collisions: {metrics['collisions']}")
|
|
|
|
print("\n--- Topology ---")
|
|
for node_info in results["topology"]:
|
|
parent_str = (
|
|
f"-> {node_info['parent']}" if node_info["parent"] is not None else ""
|
|
)
|
|
print(
|
|
f"Node {node_info['node_id']:2d}: cost={node_info['cost']:3d} {parent_str}"
|
|
)
|
|
|
|
# Save results
|
|
with open("simulation_results.json", "w") as f:
|
|
json.dump(results, f, indent=2)
|
|
|
|
print("\nResults saved to simulation_results.json")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|