class QueueingSystem(Model): """QueueingSystem model represents a simple queue */*/1/N or */*/1, where any distribution can be used for arrival and service times. This model consists of four children: - `queue`: a model representing the packets queue (`Queue`) - `source`: a model representing the packets source (`Source`) - `server`: a model representing the packets server (`Server`) - `sink`: a model which collects served packets (`Sink`) """ def __init__(self, sim): super().__init__(sim) arrival = sim.params.arrival service = sim.params.service queue_capacity = sim.params.queue_capacity self.children['queue'] = Queue(sim, queue_capacity) self.children['source'] = Source(sim, arrival, index=0) self.children['server'] = Server(sim, service) self.children['sink'] = Sink(sim) # Building connections: self.source.connections['queue'] = self.queue self.queue.connections['server'] = self.server self.server.connections['queue'] = self.queue self.server.connections['next'] = self.sink # Statistics: self.system_size_trace = Trace() self.system_size_trace.record(self.sim.stime, 0) self.system_wait_intervals = Statistic() @property def queue(self): return self.children['queue'] @property def source(self): return self.children['source'] @property def server(self): return self.children['server'] @property def sink(self): return self.children['sink'] @property def system_size(self): return self.queue.size + (1 if self.server.busy else 0) def update_system_size(self, index=0): assert index == 0 self.system_size_trace.record(self.sim.stime, self.system_size) def add_system_wait_interval(self, value, index=0): assert index == 0 self.system_wait_intervals.append(value)
def test_record_adds_data(): trace = Trace() assert trace.as_tuple() == () trace.record(t=1, v=10) assert trace.as_tuple() == ((1, 10), ) trace.record(t=2, v=13) assert trace.as_tuple() == ( (1, 10), (2, 13), )
class QueueingSystem(Model): """QueueingSystem model is the top-level model for this test. This model consists of four children: - `queue`: a model representing the packets queue (`Queue`) - `source`: a model representing the packets source (`Source`) - `server`: a model representing the packets server (`Server`) - `sink`: a model which collects served packets (`Sink`) """ def __init__(self, sim): super().__init__(sim) self.children['queue'] = Queue(sim, sim.params.capacity) self.children['source'] = Source(sim, sim.params.arrival_mean) self.children['server'] = Server(sim, sim.params.service_mean) self.children['sink'] = Sink(sim) # Building connections: self.source.connections['queue'] = self.queue self.queue.connections['server'] = self.server self.server.connections['queue'] = self.queue self.server.connections['sink'] = self.sink # Statistics: self.system_size_trace = Trace() self.system_size_trace.record(self.sim.stime, 0) @property def queue(self): return self.children['queue'] @property def source(self): return self.children['source'] @property def server(self): return self.children['server'] @property def sink(self): return self.children['sink'] @property def system_size(self): return self.queue.size + (1 if self.server.busy else 0) def update_system_size(self): self.system_size_trace.record(self.sim.stime, self.system_size)
class Server(Model): """Server module represents a packet server with exponential service time. Connections: queue, sink Handlers: - on_service_end(): called upon service timeout Methods: - start_service(): start new packet service; generate error if busy. Statistics: - delays: `Statistic`, stores a set of service intervals - busy_trace: `Trace`, stores a vector of server busy status Parent: `QueueingSystem` """ def __init__(self, sim, service_mean): super().__init__(sim) self.__service_mean = service_mean self.__busy = False # Statistics: self.delays = Statistic() self.busy_trace = Trace() self.busy_trace.record(self.sim.stime, 0) @property def service_mean(self): return self.__service_mean @property def busy(self): return self.__busy def on_service_end(self): assert self.__busy queue = self.connections['queue'].module self.__busy = False self.busy_trace.record(self.sim.stime, 0) if queue.size > 0: queue.pop() self.start_service() self.connections['sink'].module.receive_packet() self.parent.update_system_size() def start_service(self): assert not self.__busy delay = exponential(self.service_mean) self.sim.schedule(delay, self.on_service_end) self.delays.append(delay) self.__busy = True self.busy_trace.record(self.sim.stime, 1)
class Queue(Model): """Queue module represents the packets queue, stores only current size. Connections: server Methods and properties: - push(): increase the queue size - pop(): decrease the queue size - size: get current queue size Statistics: - size_trace: Trace, holding the history of the queue size updates """ def __init__(self, sim, capacity): super().__init__(sim) self.__capacity = capacity self.__size = 0 # Statistics: self.size_trace = Trace() self.size_trace.record(self.sim.stime, 0) @property def capacity(self): return self.__capacity @property def size(self): return self.__size def push(self): server = self.connections['server'].module if self.__size == 0 and not server.busy: server.start_service() elif self.capacity < 0 or self.__size < self.capacity: self.__size += 1 self.size_trace.record(self.sim.stime, self.__size) self.parent.update_system_size() def pop(self): assert self.__size > 0 self.__size -= 1 self.size_trace.record(self.sim.stime, self.__size) def __str__(self): return f'Queue({self.size})'
class ModelData(Model): def __init__(self, sim): super().__init__(sim) # Topology comprises: # - `nodes`: each having position, properties # - `static_connections`: optional dictionary with static connections # We use it as a template for network creation. self.network = Network(sim.params.topology) self.repair_started = False self.failed_nodes = [] self.routing_mode = sim.params.routing_mode sim.logger.level = sim.params.log_level # Statistics: self.num_failed = Trace() self.num_offline = Trace() self.operable = Trace() self.num_failed_sampled = Trace() self.num_offline_sampled = Trace() # Record initial trace data: t = sim.stime num_offline = len(self.network.get_offline_nodes()) self.num_failed.record(t, 0) self.num_failed_sampled.record(t, 0) self.num_offline.record(t, num_offline) self.num_offline_sampled.record(t, num_offline) self.operable.record(t, 1) # Build network routes: self.network.build_routing_table(self.routing_mode) for sensor in self.network.sensors(): self.schedule_failure(sensor) # Check whether we record samples: if sim.params.record_samples: sim.schedule(sim.params.sample_interval, self.handle_sample_timeout) def handle_failure(self, node): """Handle node failure. Upon failure the following actions take place: 1) a node is marked as turned off in the network; 2) if repair hasn't started yet, we check whether enough nodes already failed: if num_offline_nodes > num_offline_till_repair, start the repair. """ self.network.turn_off(node) self.failed_nodes.append(node) # If static routing is used, no actual re-routing will take place. # However, in case of dynamic routing using, this can lead to # network reconfiguration and new alternative paths self.network.build_routing_table(self.routing_mode) # Count failed and offline nodes and record statistics: num_offline_nodes = len(self.network.get_offline_nodes()) num_failed_nodes = len(self.failed_nodes) self.num_failed.record(self.sim.stime, num_failed_nodes) self.num_offline.record(self.sim.stime, num_offline_nodes) # We treat all offline nodes as broken, while some of them are # offline since the routers they were connected to became unavailable: self.sim.logger.debug(f'node {node} failed. {num_failed_nodes} nodes ' f'failed, {num_failed_nodes} are offline. ' f'Failed nodes: {self.failed_nodes}') if num_offline_nodes >= self.sim.params.num_offline_till_repair: if not self.repair_started: # print(f'num failed node: {num_failed_nodes}, offline: {num_offline_nodes}, start repair') self.schedule_next_repair() self.operable.record(self.sim.stime, 0) def handle_repair_finished(self, node): """Handle repair of all nodes finished. Here we assume that during repair all failed nodes were fixed, so we schedule their failures again. """ # Turn on the repaired node, rebuild a routing table and schedule the # next failure: self.sim.logger.debug(f'node {node} repair finished') self.network.turn_on(node) self.network.build_routing_table(self.routing_mode) self.schedule_failure(node) # Remove the repaired node from the failed list, count failed and # offline nodes: self.failed_nodes.remove(node) num_failed = len(self.failed_nodes) num_offline = len(self.network.get_offline_nodes()) # Record statistics: t = self.sim.stime self.num_failed.record(t, num_failed) self.num_offline.record(t, num_offline) # Check whether there are other failed nodes. If they exist, schedule # the next repair. Otherwise, mark repair end. if num_failed > 0: self.schedule_next_repair() else: self.repair_started = False if num_offline >= self.sim.params.num_offline_till_repair: self.operable.record(t, 0) else: self.operable.record(t, 1) def handle_sample_timeout(self): t = self.sim.stime num_offline = len(self.network.get_offline_nodes()) self.num_failed_sampled.record(t, len(self.failed_nodes)) self.num_offline_sampled.record(t, num_offline) self.sim.schedule(self.sim.params.sample_interval, self.handle_sample_timeout) def schedule_failure(self, node): """Schedule next failure event for a given node. :param node: network node """ interval = self.sim.params.failure_interval() self.sim.schedule(interval, self.handle_failure, args=(node, )) def schedule_next_repair(self): """Schedule next repair duration. Since all broken nodes are repaired at once, we do not distinguish nodes here. """ if not self.failed_nodes: raise RuntimeError('can not schedule repair - no failed nodes') # Select a random node to repair, and schedule repair end: node_index = np.random.randint(len(self.failed_nodes)) node = self.failed_nodes[node_index] if not self.repair_started: interval = self.sim.params.repair_interval() self.repair_started = True else: interval = self.sim.params.cons_repair_interval() self.sim.logger.debug(f'started node {node} repair for {interval}s') self.sim.schedule(interval, self.handle_repair_finished, args=(node, ))
def test_record_with_past_time_causes_error(): trace = Trace([(10, 13)]) with pytest.raises(ValueError) as excinfo: trace.record(5, 10) assert 'adding data in past prohibited' in str(excinfo.value).lower()
class Server(Model): """Server module represents a packet server with exponential service time. Connections: queue, sink Handlers: - on_service_end(): called upon service timeout Methods: - start_service(): start new packet service; generate error if busy. Statistics: - delays: `Statistic`, stores a set of service intervals - busy_trace: `Trace`, stores a vector of server busy status - num_served: `int` Parent: `QueueingSystem` """ def __init__(self, sim, service_time, index=0): super().__init__(sim) self.service_time = service_time self.packet = None self.index = index # Statistics: self.service_intervals = Statistic() self.busy_trace = Trace() self.busy_trace.record(self.sim.stime, 0) self.departure_intervals = Intervals() self.departure_intervals.record(sim.stime) self.num_served = 0 @property def busy(self): return self.packet is not None @property def ready(self): return self.packet is None def handle_service_end(self): assert self.busy stime = self.sim.stime self.connections['next'].send(self.packet) self.parent.add_system_wait_interval(stime - self.packet.arrived_at, self.index) self.packet = None self.busy_trace.record(stime, 0) self.departure_intervals.record(stime) self.num_served += 1 # Requesting next packet from the queue: queue = self.connections['queue'].module if queue.size > 0: self.serve(queue.pop()) self.parent.update_system_size(self.index) def _start_service(self): delay = self.service_time() self.sim.schedule(delay, self.handle_service_end) return delay def serve(self, packet): assert not self.busy self.packet = packet delay = self._start_service() self.service_intervals.append(delay) self.busy_trace.record(self.sim.stime, 1)
class Queue(Model): """Queue module represents the packets queue, stores only current size. Connections: server Methods and properties: - push(): increase the queue size - pop(): decrease the queue size - size: get current queue size Statistics: - size_trace: Trace, holding the history of the queue size updates - num_arrived: `int` - num_dropped: `int` - drop_ratio: `float` """ def __init__(self, sim, capacity, index=0): super().__init__(sim) self.__capacity = capacity self.packets = deque() self.index = index # Statistics: self.size_trace = Trace() self.size_trace.record(self.sim.stime, 0) self.num_dropped = 0 self.arrival_intervals = Intervals() self.arrival_intervals.record(sim.stime) self.num_arrived = 0 self.wait_intervals = Statistic() @property def capacity(self): return self.__capacity @property def size(self): return len(self.packets) @property def drop_ratio(self): if self.num_arrived == 0: return 0 return self.num_dropped / self.num_arrived def handle_message(self, message, connection=None, sender=None): self.push(message) def push(self, packet): self.arrival_intervals.record(self.sim.stime) self.num_arrived += 1 server = self.connections['server'].module packet.arrived_at = self.sim.stime if self.size == 0 and not server.busy: server.serve(packet) self.wait_intervals.append(self.sim.stime - packet.arrived_at) elif self.capacity is None or self.size < self.capacity: self.packets.append(packet) self.size_trace.record(self.sim.stime, self.size) else: self.num_dropped += 1 self.parent.update_system_size(self.index) def pop(self): packet = self.packets.popleft() self.size_trace.record(self.sim.stime, self.size) self.wait_intervals.append(self.sim.stime - packet.arrived_at) return packet def __str__(self): return f'Queue({self.size})'
class WiredTransceiver(Model): """This module models a simple wired full-duplex transceiver. `WiredTransceiver` simulates a simple wired network card. It sends packets in `WireFrame`s instances with constant bitrate. Each frame can have a preamble and some header. After transmission, `WiredTransceiver` waits IFS before sending another frame. `WiredTransceiver` receives data from the queue. Upon construction, it schedules `_start()` call, which will request data from the queue. Note that `'queue'` connection MUST be added right after creation to make this work. Upon `_start()` call, `WiredTransceiver` will request packets with `queue.get_next(self)` call. `WiredTransceiver` requests other packets when TX operation finishes and it waits IFS. To request additional packets, it calls `queue.get_next()`. `WiredTransceiver` is expected to have the only one peer, which typically is another `WiredTransceiver` module. They MUST be connected with `'peer'` bi-directional connection. Reception is performed in two steps: when the frame reaches the transceiver, it marks RX as busy, and schedules frame reception end at `frame.duration`. Upon frame RX end, RX is marked as ready. If `WiredTransceiver` receives a packet from the `Queue` during another TX operation running, it will throw a `RuntimeError` exception. `WiredTransceiver` needs three connections: - `'peer'`: a bi-directional connection to another transceiver (mandatory); - `'queue'`: a bi-directional connection to the packets queue (mandatory); - `'up'`: a bi-directional connection to the switch (optional). If `'up'` connection is defined, the received packets are sent through it. Otherwise, they are silently dropped. Typically, `'up'` connects a `NetworkSwitch` module, or parent `NetworkInterface`. """ def __init__(self, sim, bitrate=inf, header_size=0, preamble=0, ifs=0): super().__init__(sim) self.bitrate = bitrate self.header_size = header_size self.preamble = preamble self.ifs = ifs # State variables: self.__started = False self.__tx_frame = None self.__wait_ifs = False self.__rx_frame = None # Statistics: self.__num_received_frames = 0 self.__num_received_bits = 0 self.__rx_busy_trace = Trace() self.__rx_busy_trace.record(0, 0) self.__num_transmitted_packets = 0 self.__num_transmitted_bits = 0 self.__tx_busy_trace = Trace() self.__tx_busy_trace.record(0, 0) self.__service_time = Statistic() self.__service_started_at = None # Initialization: self.sim.schedule(self.sim.stime, self.start) @property def started(self): return self.__started @property def tx_ready(self): return not self.tx_busy @property def tx_busy(self): return self.__tx_frame is not None or self.__wait_ifs @property def rx_ready(self): return self.__rx_frame is None @property def rx_busy(self): return not self.rx_ready @property def num_received_frames(self): return self.__num_received_frames @property def num_received_bits(self): return self.__num_received_bits @property def rx_busy_trace(self): return self.__rx_busy_trace @property def num_transmitted_packets(self): return self.__num_transmitted_packets @property def num_transmitted_bits(self): return self.__num_transmitted_bits @property def tx_busy_trace(self): return self.__tx_busy_trace @property def service_time(self): return self.__service_time @property def tx_frame(self): return self.__tx_frame def start(self): self.connections['queue'].module.get_next(self) self.__started = True def handle_message(self, message, connection=None, sender=None): if connection.name == 'queue': if self.tx_busy: raise RuntimeError( 'new NetworkPacket while another TX running') duration = ((self.header_size + message.size) / self.bitrate + self.preamble) frame = WireFrame(packet=message, duration=duration, header_size=self.header_size, preamble=self.preamble) self.connections['peer'].send(frame) self.sim.schedule(duration, self.handle_tx_end) self.__tx_frame = frame self.__tx_busy_trace.record(self.sim.stime, 1) self.__service_started_at = self.sim.stime self.sim.logger.debug(f'start transmitting frame {frame}', src=self) elif connection.name == 'peer': self.sim.schedule(message.duration, self.handle_rx_end, args=(message, )) self.__rx_frame = message self.__rx_busy_trace.record(self.sim.stime, 1) self.sim.logger.debug(f'start receiving frame {message}', src=self) def handle_tx_end(self): self.sim.schedule(self.ifs, self.handle_ifs_end) # Record statistics: self.__num_transmitted_packets += 1 self.__num_transmitted_bits += self.__tx_frame.size # Update state variables: self.__wait_ifs = True self.__tx_frame = None self.sim.logger.debug(f'finish transmitting, waiting IFS', src=self) def handle_ifs_end(self): self.__wait_ifs = False self.connections['queue'].module.get_next(self) # Record statistics: self.__tx_busy_trace.record(self.sim.stime, 0) self.__service_time.append(self.sim.stime - self.__service_started_at) self.__service_started_at = None self.sim.logger.debug(f'IFS end, ready to transmit', src=self) def handle_rx_end(self, frame): if 'up' in self.connections: self.connections['up'].send(frame.packet) self.__rx_frame = None self.__num_received_frames += 1 self.__num_received_bits += frame.size self.__rx_busy_trace.record(self.sim.stime, 0) self.sim.logger.debug(f'finish receiving frame', src=self) def __str__(self): prefix = f'{self.parent}.' if self.parent else '' return f'{prefix}RxTx'
class Receiver(Model): """Module simulating MAC (+PHY) DCF receiver. Connections: - `'radio'`: - `'channel'`: - `'transmitter'` - `'up'`: """ class State(Enum): IDLE = 0 RX = 1 TX1 = 2 TX2 = 3 COLLIDED = 4 WAIT_SEND_ACK = 5 SEND_ACK = 6 def __init__( self, sim, address=None, sifs=None, phy_header_size=None, ack_size=None, ): super().__init__(sim) # Properties: self.__address = address self.__sifs = sifs if sifs is not None else sim.params.sifs self.__phy_header_size = (phy_header_size if phy_header_size is not None else sim.params.phy_header_size) self.__ack_size = (ack_size if ack_size is not None else sim.params.ack_size) # State variables: self.__state = Receiver.State.IDLE self.__rxbuf = set() self.__cur_tx_pdu = None # Statistics: self.__num_collisions = 0 self.__num_received = 0 self.__busy_trace = Trace() self.__busy_trace.record(sim.stime, 0) @property def state(self): return self.__state @state.setter def state(self, state): if state != self.__state: if self.__state == Receiver.State.IDLE: self.__busy_trace.record(self.sim.stime, 1) elif state == Receiver.State.IDLE: self.__busy_trace.record(self.sim.stime, 0) self.sim.logger.debug(f'{self.__state.name} -> {state.name}', src=self) if state is Receiver.State.COLLIDED: self.__num_collisions += 1 self.__state = state @property def address(self): return self.__address @address.setter def address(self, address): self.__address = address @property def sifs(self): return self.__sifs @property def phy_header_size(self): return self.__phy_header_size @property def ack_size(self): return self.__ack_size @property def num_received(self): return self.__num_received @property def num_collisions(self): return self.__num_collisions @property def collision_ratio(self): num_ops = self.__num_received + self.__num_collisions if num_ops > 0: return self.__num_collisions / num_ops return 0 @property def busy_trace(self): return self.__busy_trace @property def radio(self): return self.connections['radio'].module @property def channel(self): return self.connections['channel'].module @property def transmitter(self): return self.connections['transmitter'].module @property def up(self): return self.connections['up'].module @property def collision_probability(self): if self.num_received > 0 or self.num_collisions > 0: return self.num_collisions / (self.num_collisions + self.num_received) return 0 def start_receive(self, pdu): if pdu in self.__rxbuf: self.sim.logger.error( f"PDU {pdu} is already in the buffer:\n{self.__rxbuf}", src=self) raise RuntimeError(f'PDU is already in the buffer, PDU={pdu}') if self.state is Receiver.State.IDLE and not self.__rxbuf: self.state = Receiver.State.RX self.channel.set_busy() elif (self.state is Receiver.State.RX or (self.state is Receiver.State.IDLE and self.__rxbuf)): self.state = Receiver.State.COLLIDED self.__rxbuf.add(pdu) # In all other states (e.g. TX2, WAIT_SEND_ACK, SEND_ACK) we just add # the packet to RX buffer. def finish_receive(self, pdu): assert pdu in self.__rxbuf self.__rxbuf.remove(pdu) if self.state is Receiver.State.RX: assert not self.__rxbuf if pdu.receiver_address == self.address: if pdu.type is DataPDU.Type.DATA: self.state = Receiver.State.WAIT_SEND_ACK self.__cur_tx_pdu = pdu self.sim.schedule(self.sifs, self.handle_timeout) elif pdu.type is DataPDU.Type.ACK: self.transmitter.acknowledged() self.state = Receiver.State.IDLE self.channel.set_ready() else: raise RuntimeError(f'unsupported packet type {pdu.type}') else: self.state = Receiver.State.IDLE self.channel.set_ready() elif self.state is Receiver.State.COLLIDED: if not self.__rxbuf: self.state = Receiver.State.IDLE self.channel.set_ready() # Otherwise stay in COLLIDED state # In all other states (e.g. IDLE, TX1, TX2, ...) we just purge the # packet from the RX buffer. def start_transmit(self): if self.state is Receiver.State.IDLE: self.state = Receiver.State.TX1 elif self.state in (Receiver.State.RX, Receiver.State.COLLIDED): self.state = Receiver.State.TX2 assert self.state is not self.state.WAIT_SEND_ACK def finish_transmit(self): if self.state is Receiver.State.TX1: if self.__rxbuf: self.channel.set_busy() self.state = Receiver.State.COLLIDED else: self.state = Receiver.State.IDLE elif self.state is Receiver.State.TX2: if self.__rxbuf: self.state = Receiver.State.COLLIDED else: self.channel.set_ready() self.state = Receiver.State.IDLE elif self.state is Receiver.State.SEND_ACK: payload = self.__cur_tx_pdu.packet self.connections['up'].send(payload) self.__num_received += 1 self.__cur_tx_pdu = None self.channel.set_ready() self.state = Receiver.State.IDLE def handle_timeout(self): assert self.state is Receiver.State.WAIT_SEND_ACK ack = AckPDU( header_size=self.phy_header_size, ack_size=self.ack_size, sender_address=self.address, receiver_address=self.__cur_tx_pdu.sender_address, ) self.state = Receiver.State.SEND_ACK self.radio.transmit(ack) def __str__(self): prefix = f'{self.parent}.' if self.parent else '' return f'{prefix}receiver'
class Transmitter(Model): """Models transmitter module at MAC layer. Connections: - `'channel'`: mandatory, to `Channel` instance; - `'radio'`: mandatory, to `Radio` module - `'queue'`: optional, to `Queue` module """ class State(Enum): IDLE = 0 BUSY = 1 BACKOFF = 2 TX = 3 WAIT_ACK = 4 def __init__( self, sim, address=None, phy_header_size=None, mac_header_size=None, ack_size=None, bitrate=None, preamble=None, max_propagation=0, ): super().__init__(sim) # Properties: self.__address = address self.__phy_header_size = (phy_header_size if phy_header_size is not None else sim.params.phy_header_size) self.__mac_header_size = (mac_header_size if mac_header_size is not None else sim.params.mac_header_size) self.__bitrate = bitrate if bitrate is not None else sim.params.bitrate self.__preamble = (preamble if preamble is not None else sim.params.preamble) self.__max_propagation = max_propagation self.__ack_size = (ack_size if ack_size is not None else sim.params.ack_size) # State variables: self.timeout = None self.cw = 65536 self.backoff = -1 self.num_retries = None self.pdu = None self.__state = Transmitter.State.IDLE self.__seqn = 0 # Statistics: self.backoff_vector = Statistic() self.__start_service_time = None self.service_time = Statistic() self.num_sent = 0 self.num_retries_vector = Statistic() self.__busy_trace = Trace() self.__busy_trace.record(sim.stime, 0) # Initialize: sim.schedule(0, self.start) @property def state(self): return self.__state @state.setter def state(self, state): if self.state != state: self.sim.logger.debug(f'{self.state.name} -> {state.name}', src=self) self.__state = state @property def address(self): return self.__address @address.setter def address(self, address): self.__address = address @property def phy_header_size(self): return self.__phy_header_size @property def mac_header_size(self): return self.__mac_header_size @property def ack_size(self): return self.__ack_size @property def bitrate(self): return self.__bitrate @property def preamble(self): return self.__preamble @property def max_propagation(self): return self.__max_propagation @property def busy_trace(self): return self.__busy_trace @property def channel(self): return self.connections['channel'].module @property def radio(self): return self.connections['radio'].module @property def queue(self): return self.connections['queue'].module def start(self): self.queue.get_next(self) def handle_message(self, packet, connection=None, sender=None): if connection.name == 'queue': assert self.state == Transmitter.State.IDLE self.cw = self.sim.params.cwmin self.backoff = randint(0, self.cw) self.num_retries = 1 # # Create the PDU: # self.pdu = DataPDU(packet, seqn=self.__seqn, header_size=self.phy_header_size + self.mac_header_size, sender_address=self.address, receiver_address=packet.receiver_address) self.__seqn += 1 self.__start_service_time = self.sim.stime self.backoff_vector.append(self.backoff) self.__busy_trace.record(self.sim.stime, 1) self.sim.logger.debug( f'backoff={self.backoff}; CW={self.cw},NR={self.num_retries}', src=self) if self.channel.is_busy: self.state = Transmitter.State.BUSY else: self.state = Transmitter.State.BACKOFF self.timeout = self.sim.schedule(self.sim.params.difs, self.handle_backoff_timeout) else: raise RuntimeError( f'unexpected handle_message({packet}, connection={connection}, ' f'sender={sender}) call') def channel_ready(self): if self.state == Transmitter.State.BUSY: self.timeout = self.sim.schedule(self.sim.params.difs, self.handle_backoff_timeout) self.state = Transmitter.State.BACKOFF def channel_busy(self): if self.state == Transmitter.State.BACKOFF: self.sim.cancel(self.timeout) self.state = Transmitter.State.BUSY def finish_transmit(self): if self.state == Transmitter.State.TX: self.sim.logger.debug('TX finished', src=self) ack_duration = ( (self.ack_size + self.mac_header_size + self.phy_header_size) / self.bitrate + self.preamble + 6 * self.max_propagation) self.timeout = self.sim.schedule( self.sim.params.sifs + ack_duration, self.handle_ack_timeout) self.state = Transmitter.State.WAIT_ACK def acknowledged(self): if self.state == Transmitter.State.WAIT_ACK: self.sim.logger.debug('received ACK', src=self) self.sim.cancel(self.timeout) self.pdu = None self.state = Transmitter.State.IDLE self.num_sent += 1 self.service_time.append(self.sim.stime - self.__start_service_time) self.__start_service_time = None self.num_retries_vector.append(self.num_retries) self.num_retries = None self.__busy_trace.record(self.sim.stime, 0) # # IMPORTANT: Informing the queue that we can handle the next packet # self.queue.get_next(self) def handle_ack_timeout(self): assert self.state == Transmitter.State.WAIT_ACK self.num_retries += 1 self.cw = min(2 * self.cw, self.sim.params.cwmax) self.backoff = randint(0, self.cw) self.backoff_vector.append(self.backoff) self.sim.logger.debug( f'backoff={self.backoff}; CW={self.cw}, NR={self.num_retries})', src=self) if self.channel.is_busy: self.state = Transmitter.State.BUSY else: self.state = Transmitter.State.BACKOFF self.timeout = self.sim.schedule(self.sim.params.difs, self.handle_backoff_timeout) def handle_backoff_timeout(self): if self.backoff == 0: self.state = Transmitter.State.TX self.sim.logger.debug(f'transmitting {self.pdu}', src=self) self.radio.transmit(self.pdu) else: assert self.backoff > 0 self.backoff -= 1 self.timeout = self.sim.schedule(self.sim.params.slot, self.handle_backoff_timeout) self.sim.logger.debug(f'backoff := {self.backoff}', src=self) def __str__(self): prefix = f'{self.parent}.' if self.parent else '' return f'{prefix}transmitter'