### Install WsnSimPy Source: https://github.com/sarahossein/wsn-simpy-/blob/master/README.md Install the WsnSimPy library using pip. This command should be run in your terminal. ```bash pip install wsnsimpy ``` -------------------------------- ### Run Flood Example in WsnSimPy Source: https://github.com/sarahossein/wsn-simpy-/blob/master/README.md Execute the flooding example for WsnSimPy from the command line. This demonstrates network-level messaging. ```python python -m wsnsimpy.examples.flood ``` -------------------------------- ### Run AODV Example in WsnSimPy Source: https://github.com/sarahossein/wsn-simpy-/blob/master/README.md Execute the AODV (Ad hoc On-Demand Distance Vector) routing protocol example for WsnSimPy from the command line. This demonstrates full-stack communications. ```python python -m wsnsimpy.examples.aodv ``` -------------------------------- ### Simulator.run Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Executes the simulation. This method initializes the simulator and all nodes, starts their processes, and runs the simulation until the specified end time. It also calls a finish() method on each node upon completion. ```APIDOC ## Simulator.run() — Execute the simulation ### Description Calls `init()` on the simulator, then `init()` and `run()` on each node, starts all node processes, and runs the SimPy environment until `until`. After completion, calls `finish()` on every node. ### Request Example ```python import wsnsimpy.wsnsimpy as wsp class PingNode(wsp.Node): tx_range = 150 def run(self): if self.id == 0: yield self.timeout(1) self.log("Sending ping") self.send(wsp.BROADCAST_ADDR) def on_receive(self, sender, **kwargs): self.log(f"Received ping from node {sender}") sim = wsp.Simulator(until=10, timescale=0, seed=0) sim.add_node(PingNode, (0, 0)) sim.add_node(PingNode, (100, 0)) sim.add_node(PingNode, (300, 0)) # Out of range sim.run() # Node # 0[ 1.00000] Sending ping # Node # 1[ 1.00010] Received ping from node 0 (in range) # Node # 2 never receives (300 units away, beyond tx_range=150) ``` ``` -------------------------------- ### Add Nodes to Simulator Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Instantiates a node of a given class at specified coordinates and adds it to the simulation. Neighbor lists are automatically updated. This example demonstrates adding multiple nodes in a grid pattern. ```python import wsnsimpy.wsnsimpy as wsp class SensorNode(wsp.Node): tx_range = 100 sim = wsp.Simulator(until=20, timescale=0) for x in range(5): for y in range(5): sim.add_node(SensorNode, (x * 80, y * 80)) print(f"Added {len(sim.nodes)} nodes") # Added 25 nodes ``` -------------------------------- ### Implement Periodic Node Behavior Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Defines a node's main process using the `run()` coroutine. Use `yield self.timeout(seconds)` to pause execution. This example shows a node periodically sending beacons and logging received beacons. ```python import wsnsimpy.wsnsimpy as wsp class PeriodicBeacon(wsp.Node): tx_range = 80 def run(self): seq = 0 while True: yield self.timeout(5) # Wait 5 simulation seconds self.log(f"Beacon seq={seq}") self.send(wsp.BROADCAST_ADDR, seq=seq) seq += 1 def on_receive(self, sender, seq, **kwargs): self.log(f"Heard beacon seq={seq} from {sender}") sim = wsp.Simulator(until=20, timescale=0) sim.add_node(PeriodicBeacon, (0, 0)) sim.add_node(PeriodicBeacon, (50, 0)) sim.run() ``` -------------------------------- ### AODV Routing Protocol Implementation Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Implements the Ad-hoc On-Demand Distance Vector (AODV) routing protocol using WsnSimPy's network-level API. This example demonstrates route discovery (RREQ, RREP) and data forwarding. ```python import random import wsnsimpy.wsnsimpy as wsp # Headless version SOURCE, DEST = 0, 24 class AodvNode(wsp.Node): tx_range = 120 def init(self): super().init() self.prev = None self.next = None def run(self): if self.id == SOURCE: yield self.timeout(1) self.send(wsp.BROADCAST_ADDR, msg='rreq', src=SOURCE) def on_receive(self, sender, msg, src, **kwargs): if msg == 'rreq': if self.prev is not None: return self.prev = sender if self.id == DEST: yield self.timeout(0.5) self.send(self.prev, msg='rrep', src=DEST) else: yield self.timeout(random.uniform(0.1, 0.3)) self.send(wsp.BROADCAST_ADDR, msg='rreq', src=src) elif msg == 'rrep': self.next = sender if self.id == SOURCE: self.log(f"Route to DEST established via node {self.next}") self.start_process(self._send_data()) else: yield self.timeout(0.1) self.send(self.prev, msg='rrep', src=src) elif msg == 'data': seq = kwargs['seq'] if self.id == DEST: self.log(f"Data arrived: seq={seq}") else: yield self.timeout(0.05) self.send(self.next, msg='data', src=src, seq=seq) def _send_data(self): for seq in range(5): yield self.timeout(1) self.send(self.next, msg='data', src=SOURCE, seq=seq) sim = wsp.Simulator(until=20, timescale=0, seed=7) for x in range(5): for y in range(5): sim.add_node(AodvNode, (x * 80, y * 80)) sim.run() ``` -------------------------------- ### Initialize Simulator (Headless) Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Creates a SimPy environment for simulation. Set timescale=0 for fast execution, or timescale > 0 for real-time simulation. A seed ensures reproducible results. ```python import wsnsimpy.wsnsimpy as wsp # Headless (no GUI), runs as fast as possible sim = wsp.Simulator(until=50, timescale=0, seed=42) ``` -------------------------------- ### Simulator.__init__ Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Initializes the core simulation engine, setting up the SimPy environment, simulation end time, and random number generator seed. Supports real-time simulation via the timescale parameter. ```APIDOC ## Simulator.__init__(until, timescale, seed) — Core simulation engine ### Description Creates the SimPy environment, sets the simulation end time, and seeds the random number generator. When `timescale > 0` the environment runs as a real-time simulation (useful for live visualization); `timescale=0` runs as fast as possible. ### Parameters - **until** (float) - The simulation end time. - **timescale** (float) - If greater than 0, runs simulation in real-time. If 0, runs as fast as possible. - **seed** (int) - Seed for the random number generator. ### Request Example ```python import wsnsimpy.wsnsimpy as wsp # Headless (no GUI), runs as fast as possible sim = wsp.Simulator(until=50, timescale=0, seed=42) ``` ``` -------------------------------- ### Node.create_event() / Node.start_process() Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Provides mechanisms to interact with SimPy's event and process scheduling. `create_event()` generates a SimPy Event, while `start_process()` schedules a generator function as a concurrent process. ```APIDOC ## `Node.create_event()` / `Node.start_process()` — SimPy events and processes `create_event()` creates a raw SimPy `Event` that can be triggered with `.succeed()`. `start_process(gen)` schedules a generator as a concurrent SimPy process on the node. ``` -------------------------------- ### Visualizing Simulations with wsnsimpy_tk.Simulator Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Utilizes `wsnsimpy_tk.Simulator` for real-time visual feedback of node movement, communication events, and collisions. Set `visual=False` for non-GUI batch runs. ```python import random import wsnsimpy.wsnsimpy_tk as wsp class FloodNode(wsp.Node): # wsp.Node here is wsnsimpy_tk.Node tx_range = 100 seen = False def run(self): if self.id == 0: self.scene.nodecolor(self.id, 1, 0, 0) # Mark source red yield self.timeout(1) self.send(wsp.BROADCAST_ADDR) def on_receive(self, sender, **kwargs): if self.seen: return self.seen = True self.scene.nodecolor(self.id, 0, 0.8, 0) # Mark received green yield self.timeout(random.uniform(0.3, 0.7)) self.send(wsp.BROADCAST_ADDR) sim = wsp.Simulator( until=30, timescale=1, # 1x real-time visual=True, terrain_size=(600, 600), title="Flood Demo" ) for x in range(8): for y in range(8): px = 40 + x*70 + random.uniform(-15, 15) py = 40 + y*70 + random.uniform(-15, 15) sim.add_node(FloodNode, (px, py)) sim.run() # Opens Tkinter window; blocks until simulation ends ``` -------------------------------- ### Move Node and Update Neighbors with MobileNode Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Demonstrates relocating a node during simulation using `move()` and observing the change in its neighbors. The MobileNode class logs its initial and final neighbors after moving. ```python import wsnsimpy.wsnsimpy as wsp class MobileNode(wsp.Node): tx_range = 100 def run(self): self.log(f"Start at {self.pos}, neighbors: {[n.id for n in self.neighbors]}") yield self.timeout(5) self.move(500, 500) # Move far away self.log(f"Moved to {self.pos}, neighbors: {[n.id for n in self.neighbors]}") sim = wsp.Simulator(until=10, timescale=0) sim.add_node(MobileNode, (0, 0)) sim.add_node(MobileNode, (50, 0)) sim.add_node(MobileNode, (500, 500)) sim.run() ``` -------------------------------- ### Create and Trigger SimPy Events with EventNode Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Demonstrates using `create_event()` to make a SimPy Event and `start_process()` to run a generator as a concurrent process. The EventNode waits for an event to be triggered before proceeding. ```python import wsnsimpy.wsnsimpy as wsp class EventNode(wsp.Node): tx_range = 200 def run(self): self.ready = self.create_event() self.start_process(self._waiter()) yield self.timeout(2) self.log("Triggering event") self.ready.succeed() def _waiter(self): yield self.ready self.log("Event received — proceeding") sim = wsp.Simulator(until=10, timescale=0) sim.add_node(EventNode, (0, 0)) sim.run() ``` -------------------------------- ### Customizing Layers in LayeredNode Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Demonstrates how to extend LayeredNode and use set_layers() to swap in custom PHY, MAC, or NET layer subclasses. Useful for simulating custom protocols or hardware behavior. ```python import wsnsimpy.wsnsimpy as wsp class CustomPhy(wsp.DefaultPhyLayer): """PHY with 1% bit error rate at 250 kbps""" def __init__(self, node): super().__init__(node, bitrate=250e3, ber=0.01) class SensorNode(wsp.LayeredNode): tx_range = 100 def init(self): super().init() self.set_layers(phy=CustomPhy) # Swap in custom PHY def run(self): if self.id == 0: yield self.timeout(1) self.send(1, nbits=512*8) # Send 512-byte message def on_receive(self, sender, *args, **kwargs): self.log(f"App layer received from {sender}") def finish(self): self.log(f"PHY TX={self.phy.stat.total_tx} RX={self.phy.stat.total_rx} " f"Collisions={self.phy.stat.total_collision} Errors={self.phy.stat.total_error}") self.log(f"MAC unicasts_sent={self.mac.stat.total_tx_unicast} " f"retransmits={self.mac.stat.total_retransmit} acks={self.mac.stat.total_ack}") sim = wsp.Simulator(until=10, timescale=0) sim.add_node(SensorNode, (0, 0)) sim.add_node(SensorNode, (80, 0)) sim.run() ``` -------------------------------- ### Schedule Delayed Execution with TimerNode Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Shows how to schedule a function to be called after a specified delay using `delayed_exec`. The TimerNode class demonstrates this by logging an alert after 3 seconds. ```python import wsnsimpy.wsnsimpy as wsp class TimerNode(wsp.Node): tx_range = 50 def run(self): if self.id == 0: self.log("Scheduling alert in 3s") self.delayed_exec(3.0, self._alert, message="Time's up!") def _alert(self, message): self.log(f"ALERT: {message}") sim = wsp.Simulator(until=10, timescale=0) sim.add_node(TimerNode, (0, 0)) sim.run() # Node # 0[ 0.00000] Scheduling alert in 3s # Node # 0[ 3.00000] ALERT: Time's up! ``` -------------------------------- ### Run Simulation with Node Communication Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Executes the simulation, initializing and running each node. Nodes can send messages (e.g., broadcast) and receive them. Nodes only receive messages from senders within their tx_range. ```python import wsnsimpy.wsnsimpy as wsp class PingNode(wsp.Node): tx_range = 150 def run(self): if self.id == 0: yield self.timeout(1) self.log("Sending ping") self.send(wsp.BROADCAST_ADDR) def on_receive(self, sender, **kwargs): self.log(f"Received ping from node {sender}") sim = wsp.Simulator(until=10, timescale=0, seed=0) sim.add_node(PingNode, (0, 0)) sim.add_node(PingNode, (100, 0)) sim.add_node(PingNode, (300, 0)) # Out of range sim.run() # Node # 0[ 1.00000] Sending ping # Node # 1[ 1.00010] Received ping from node 0 (in range) # Node # 2 never receives (300 units away, beyond tx_range=150) ``` -------------------------------- ### Modifying Node Visual Properties with scene commands Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Demonstrates using `self.scene.nodecolor()`, `self.scene.nodewidth()`, and `self.scene.nodelabel()` within `wsnsimpy_tk` nodes to dynamically alter node appearance during simulation. Useful for visualizing node states. ```python import wsnsimpy.wsnsimpy_tk as wsp class StateNode(wsp.Node): tx_range = 150 def run(self): self.scene.nodecolor(self.id, 0.7, 0.7, 0.7) # Gray = idle self.scene.nodelabel(self.id, f"N{self.id}") yield self.timeout(2) self.scene.nodecolor(self.id, 0, 0, 1) # Blue = active self.scene.nodewidth(self.id, 3) self.log("Now active") sim = wsp.Simulator(until=10, timescale=1, visual=True, terrain_size=(400, 200)) sim.add_node(StateNode, (100, 100)) sim.add_node(StateNode, (300, 100)) sim.run() ``` -------------------------------- ### Define Custom Link Styles with scene.linestyle() Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Registers a named line style for use with drawing primitives like addlink(), circle(), line(), and rect(). Styles should be defined before adding nodes to the simulator. ```python import wsnsimpy.wsnsimpy_tk as wsp sim = wsp.Simulator(until=10, timescale=1, visual=True, terrain_size=(400, 400)) # Define styles before adding nodes sim.scene.linestyle("route", color=(0, 0, 1), width=2, arrow="head") sim.scene.linestyle("broadcast", color=(0.8, 0, 0), dash=(4, 4)) sim.scene.linestyle("broken", color=(1, 0.5, 0), width=1, dash=(2, 2)) class StyledNode(wsp.Node): tx_range = 200 def run(self): if self.id == 0: yield self.timeout(1) self.scene.addlink(0, 1, "route") self.scene.addlink(1, 2, "broadcast") self.scene.addlink(2, 0, "broken") for pos in [(100, 200), (200, 100), (300, 200)]: sim.add_node(StyledNode, pos) sim.run() ``` -------------------------------- ### Send and Receive Data/Ack with AckNode Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Illustrates sending data and receiving an acknowledgment. The AckNode class handles 'data' messages by sending back an 'ack'. Requires the AckNode class inheriting from wsp.Node. ```python import wsnsimpy.wsnsimpy as wsp class AckNode(wsp.Node): tx_range = 200 def run(self): if self.id == 0: yield self.timeout(1) self.send(1, type='data', payload='hello') def on_receive(self, sender, type, payload='', **kwargs): self.log(f"RX from {sender}: type={type} payload={payload}") if type == 'data': yield self.timeout(0.05) # Processing delay self.send(sender, type='ack', payload='') sim = wsp.Simulator(until=5, timescale=0) sim.add_node(AckNode, (0, 0)) sim.add_node(AckNode, (100, 0)) sim.run() ``` -------------------------------- ### Accessing DefaultPhyLayer Statistics Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Shows how to access and log per-node PHY layer statistics after simulation completion using `self.phy.stat`. Useful for analyzing transmission, reception, collision, and error rates. ```python import wsnsimpy.wsnsimpy as wsp class StatsNode(wsp.LayeredNode): tx_range = 120 def run(self): yield self.timeout(1) for _ in range(5): self.send(wsp.BROADCAST_ADDR, nbits=256*8) yield self.timeout(0.5) def finish(self): s = self.phy.stat self.log(f"tx={s.total_tx} rx={s.total_rx} " f"bits_tx={s.total_bits_tx} bits_rx={s.total_bits_rx} " f"collision={s.total_collision} error={s.total_error} " f"channel_busy={s.total_channel_busy:.4f}s") sim = wsp.Simulator(until=20, timescale=0) sim.add_node(StatsNode, (0, 0)) sim.add_node(StatsNode, (80, 0)) sim.run() ``` -------------------------------- ### Broadcast and Re-broadcast Messages with RouterNode Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Demonstrates broadcasting a message and re-broadcasting it with an incremented hop count. Requires the RouterNode class to be defined, inheriting from wsp.Node. ```python import wsnsimpy.wsnsimpy as wsp BROADCAST_ADDR = wsp.BROADCAST_ADDR class RouterNode(wsp.Node): tx_range = 120 seen = False def run(self): if self.id == 0: yield self.timeout(1) # Broadcast with arbitrary payload fields self.send(BROADCAST_ADDR, msg='hello', hop=0) def on_receive(self, sender, msg, hop, **kwargs): if self.seen: return self.seen = True self.log(f"msg='{msg}' hop={hop} from {sender}") # Re-broadcast with incremented hop count yield self.timeout(0.1) self.send(BROADCAST_ADDR, msg=msg, hop=hop+1) sim = wsp.Simulator(until=10, timescale=0) for i, pos in enumerate([(0,0),(80,0),(160,0),(240,0)]): sim.add_node(RouterNode, pos) sim.run() ``` -------------------------------- ### Node.run Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt The main process coroutine for a node. This method must be overridden in subclasses to define the node's behavior during the simulation. It supports yielding timeouts and SimPy events. ```APIDOC ## Node.run() — Node main process (override) ### Description The primary coroutine for node behavior. Must be overridden in subclasses. Use `yield self.timeout(seconds)` to wait, and `yield` any SimPy event. The method is automatically started as a SimPy process by the simulator. ### Parameters This method does not take explicit parameters, but it can use `self` to access node properties and methods like `self.timeout()`, `self.send()`, and `self.log()`. ### Request Example ```python import wsnsimpy.wsnsimpy as wsp class PeriodicBeacon(wsp.Node): tx_range = 80 def run(self): seq = 0 while True: yield self.timeout(5) # Wait 5 simulation seconds self.log(f"Beacon seq={seq}") self.send(wsp.BROADCAST_ADDR, seq=seq) seq += 1 def on_receive(self, sender, seq, **kwargs): self.log(f"Heard beacon seq={seq} from {sender}") sim = wsp.Simulator(until=20, timescale=0) sim.add_node(PeriodicBeacon, (0, 0)) sim.add_node(PeriodicBeacon, (50, 0)) sim.run() ``` ``` -------------------------------- ### Node.move(x, y) Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Relocates a node to new coordinates (x, y) during the simulation. This action triggers a recalculation of all nodes' neighbor lists. ```APIDOC ## `Node.move(x, y)` — Relocate a node during simulation Moves the node to a new position and triggers a full recalculation of neighbor-distance lists across all nodes in the simulation. In the Tk variant, also updates the visual position. ``` -------------------------------- ### Node.delayed_exec(delay, func, *args, **kwargs) Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Schedules a function to be executed after a specified delay in simulation time. Supports both regular and generator functions. ```APIDOC ## `Node.delayed_exec(delay, func, *args, **kwargs)` — Schedule a callback Schedules `func(*args, **kwargs)` to be called after `delay` simulation seconds. Works with both regular functions and generator functions. ``` -------------------------------- ### Access Node Neighbors with NeighborPrinter Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Shows how to access a node's neighbors using the `neighbors` property. The NeighborPrinter class logs the IDs of nodes within its `tx_range`. ```python import wsnsimpy.wsnsimpy as wsp class NeighborPrinter(wsp.Node): tx_range = 120 def run(self): ids = [n.id for n in self.neighbors] self.log(f"My neighbors: {ids}") sim = wsp.Simulator(until=5, timescale=0) sim.add_node(NeighborPrinter, (0, 0)) sim.add_node(NeighborPrinter, (100, 0)) # Within range sim.add_node(NeighborPrinter, (200, 0)) # Within range of node 1, not node 0 sim.add_node(NeighborPrinter, (500, 0)) # Out of range of all sim.run() ``` -------------------------------- ### Node.send(dst, **kwargs) Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Sends a message to a destination node or broadcasts it. Propagation delay is calculated based on distance. All keyword arguments are passed to the recipient's on_receive method. ```APIDOC ## `Node.send(dst, **kwargs)` — Transmit a message Sends a message to destination node ID `dst`, or to `BROADCAST_ADDR` (0xFFFF) for broadcast. Propagation delay is computed as `distance / 1_000_000` seconds. All keyword arguments are forwarded to the recipient's `on_receive()`. ``` -------------------------------- ### Add Persistent Directional Links with scene.addlink() Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Draws persistent directional links between nodes with a named style to visualize routing trees or data-flow paths. Ensure the linestyle is defined before adding nodes. ```python import wsnsimpy.wsnsimpy_tk as wsp class TreeNode(wsp.Node): tx_range = 150 parent = None def run(self): if self.id == 0: yield self.timeout(1) self.send(wsp.BROADCAST_ADDR, root=self.id) def on_receive(self, sender, root, **kwargs): if self.parent is None: self.parent = sender self.scene.addlink(sender, self.id, "tree") # Draw tree edge yield self.timeout(0.1) self.send(wsp.BROADCAST_ADDR, root=root) sim = wsp.Simulator(until=20, timescale=1, visual=True, terrain_size=(500, 500)) sim.scene.linestyle("tree", color=(0, 0.7, 0), arrow="tail", width=2) for i, pos in enumerate([(250,250),(150,150),(350,150),(150,350),(350,350)]): sim.add_node(TreeNode, pos) sim.run() ``` -------------------------------- ### Node.on_receive(sender, **kwargs) Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt A handler called when a transmission reaches this node. It receives the sender's ID and any keyword arguments passed during the send operation. This method can be a regular function or a generator. ```APIDOC ## `Node.on_receive(sender, **kwargs)` — Receive handler (override) Called (as a SimPy process) when a transmission reaches this node. `sender` is the integer ID of the transmitting node. All keyword arguments passed to `send()` are forwarded here. Can be a regular function or a generator (using `yield`). ``` -------------------------------- ### Simulator.add_node Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt Adds a node to the simulation at a specified position. This method automatically assigns a unique ID to the node and updates the neighbor lists of all existing nodes. ```APIDOC ## Simulator.add_node(nodeclass, pos) — Add a node to the simulation ### Description Instantiates a node of the given class at position `(x, y)`, assigns it a sequential integer ID starting at 0, and updates all existing nodes' sorted neighbor-distance lists. ### Parameters - **nodeclass** (class) - The class of the node to instantiate (must be a subclass of `wsnsimpy.wsnsimpy.Node`). - **pos** (tuple) - A tuple `(x, y)` representing the node's position. ### Request Example ```python import wsnsimpy.wsnsimpy as wsp class SensorNode(wsp.Node): tx_range = 100 sim = wsp.Simulator(until=20, timescale=0) for x in range(5): for y in range(5): sim.add_node(SensorNode, (x * 80, y * 80)) print(f"Added {len(sim.nodes)} nodes") # Added 25 nodes ``` ``` -------------------------------- ### Node.neighbors Source: https://context7.com/sarahossein/wsn-simpy-/llms.txt A property that returns a list of Node objects within the node's transmission range (`tx_range`). This list is dynamically updated when a node moves. ```APIDOC ## `Node.neighbors` — Property: list of reachable nodes Returns a list of `Node` objects whose distance from this node is within `tx_range`. The list is derived from the pre-sorted `neighbor_distance_list` and is recomputed on every `move()`. ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.