Files
lora_route_py/sim/main.py
2026-02-24 16:21:30 +08:00

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()