def get_energy_per_bit(modulation, power=22): MAX_PACKET = 255 * 8 if modulation.modem.value is RadioModem.LORA.value: if modulation.bandwidth == 125000: bandwidth = 0 elif modulation.bandwidth == 250000: bandwidth = 1 elif modulation.bandwidth == 500000: bandwidth = 2 elif modulation.bandwidth == 10400: bandwidth = 3 else: bandwidth = 0 config = RadioConfiguration(int(12 - modulation.sf), bandwidth=bandwidth) else: config = RadioConfiguration(8, bandwidth=modulation.bandwidth, bitrate=modulation.bitrate) math = RadioMath(config) return config.tx_energy( power, math.get_message_toa(MAX_PACKET)) / (MAX_PACKET * 8)
def describe_network(self): edges = [[edge[0], edge[1]] for edge in self.network.G.edges] return { 'modulations': [{ 'modulation': modulation, 'gloria_modulation': lwb_slot.RADIO_MODULATIONS[modulation], 'color': RadioConfiguration( lwb_slot.RADIO_MODULATIONS[modulation]).color, 'name': RadioConfiguration( lwb_slot.RADIO_MODULATIONS[modulation]).modulation_name } for modulation in range(len(lwb_slot.RADIO_MODULATIONS))], 'nodes': [{ 'id': node.id, 'role': str(node.role) } for node in self.network.nodes], 'edges': edges, 'pos': self.network.pos }
def cad_process(self, timestamp, rx_node: 'sim_node.SimNode', modulation, band): timestamp = rx_node.transform_local_to_global_timestamp(timestamp) def mark_reachable_message(item): return self.is_reachable(modulation, rx_node, item.source, power=item.power) def calc_power_message(item): return -self.calculate_path_loss(rx_node, item.source) + item.power config = RadioConfiguration(modulation) math = RadioMath(config) cad_start = timestamp - math.get_symbol_time() * ( 1.5 - 0.5) # -0.5 as signal had to present for longer time cad_end = timestamp - math.get_symbol_time() * 0.5 subset = (self.mm.mq.loc[(self.mm.mq.modulation == modulation) & (self.mm.mq.band == band) & (self.mm.mq.tx_end >= cad_start) & (self.mm.mq.tx_start <= cad_end)]).copy() if len(subset): subset.loc[:, 'reachable'] = subset.apply(mark_reachable_message, axis=1) subset = subset.loc[subset.reachable == True] if len(subset): subset.loc[:, 'rx_power'] = subset.apply(calc_power_message, axis=1) self.network.tracer.log_activity( CADActivity( cad_start, timestamp, rx_node, RadioConfiguration.rx_energy(timestamp - cad_start), modulation, True), ) return subset.rx_power.max() else: self.network.tracer.log_activity( CADActivity( cad_start, timestamp, rx_node, RadioConfiguration.rx_energy(timestamp - cad_start), modulation, False), ) return None else: self.network.tracer.log_activity( CADActivity( cad_start, timestamp, rx_node, RadioConfiguration.rx_energy(timestamp - cad_start), modulation, False), ) return None
def check_if_successfully_received(self, modulation, band, potential_message: 'SimMessage', rx_start: float, rx_node: 'sim_node.SimNode', tx_node: 'sim_node.SimNode'): rx_start = rx_node.transform_local_to_global_timestamp(rx_start) def calc_power_message(item): return -self.calculate_path_loss( rx_node, item['source']) + lwb_slot.RADIO_POWERS[ item['message'].power_level] interfering_set = (self.mm.mq.loc[ (self.mm.mq.modulation == modulation) & (self.mm.mq.band == band) & (self.mm.mq.tx_end > rx_start) & (self.mm.mq.tx_start < potential_message.tx_end) & ((self.mm.mq.message_hash != potential_message.hash) | ((self.mm.mq.tx_start < (potential_message.tx_start - 100E6)) | (self.mm.mq.tx_start > (potential_message.tx_start + 100E6))))]).copy() if len(interfering_set): interfering_set['rx_power'] = interfering_set.apply( calc_power_message, axis=1) rx_power = calc_power_message({ 'message': potential_message, 'source': tx_node }) interfering_power = 0 if len(interfering_set): for i in interfering_set.rx_power: interfering_power += np.power(10, i / 10) if np.power(10, rx_power / 10) > ( interfering_power * np.power(10, RADIO_SNR[modulation] / 10)): self.network.tracer.log_activity( RxActivity( rx_start, potential_message.tx_end, rx_node, RadioConfiguration.rx_energy(potential_message.tx_end - rx_start), modulation, True), ) return True else: self.network.tracer.log_activity( RxActivity( rx_start, self.network.global_timestamp, rx_node, RadioConfiguration.rx_energy( self.network.global_timestamp - rx_start), modulation, False), ) return False
def energy(self): if self.type is GloriaSlotType.TX or self.type is GloriaSlotType.TX_ACK: return RadioConfiguration(self.flood.modulation).tx_energy( self.power, self.active_time) + ( self.flood.gloria_timings.tx_setup_time + self.flood.gloria_timings.tx_irq_time) * MCU_PROC_POWER else: return RadioConfiguration(self.flood.modulation).rx_energy( self.active_time) + ( self.flood.gloria_timings.rx_setup_time + self.flood.gloria_timings.rx_irq_time) * MCU_PROC_POWER
def receive_message_on_tx_done_before_rx_timeout( self, rx_node: 'sim_node.SimNode', modulation, band, message: SimMessage, rx_start: float, tx_start: float, transmission ) -> Tuple[Optional[SimMessage], Optional['sim_node.SimNode']]: if not self.is_reachable(modulation, rx_node, message.source, lwb_slot.RADIO_POWERS[message.power_level]): return None, None config = RadioConfiguration( modulation, preamble=gloria.GloriaTimings(modulation).preamble_len) math = RadioMath(config) valid_rx_start = rx_start + math.get_symbol_time() * 0.1 if valid_rx_start > message.tx_start: return None, None interfering_set = ( self.mm.mq.loc[(self.mm.mq.modulation == modulation) & (self.mm.mq.band == band) & (self.mm.mq.tx_end >= message.tx_start) & (self.mm.mq.tx_start <= message.tx_end)]).copy() def calc_power_message(item): return -self.calculate_path_loss(rx_node, item.source) + item.power interfering_set['rx_power'] = interfering_set.apply(calc_power_message, axis=1) rx_power = -self.calculate_path_loss( rx_node, message.source) + lwb_slot.RADIO_POWERS[message.power_level] interfering_power = 0 for interferer_index, interferer in interfering_set.iterrows(): if interferer['message_hash'] != message.hash or not ( (tx_start - 100E6) < interferer['tx_start'] < (tx_start + 100E6)): interfering_power += np.power(10, interferer['rx_power'] / 10) if np.power(10, rx_power / 10) > (interfering_power * np.power(10, RADIO_SNR[modulation])): rx_node.mm.unregister_rx(rx_node) self.network.tracer.log_activity( RxActivity( rx_start, self.network.global_timestamp, rx_node, RadioConfiguration.rx_energy( self.network.global_timestamp - rx_start), modulation, True)) return message.copy(), transmission['source'] else: return None, None
def __init__(self, round: 'lwb_round.LWBRound', slot_offset: float, modulation: int, payload: int, type: LWBSlotType, is_ack=True, master: 'sim_node.SimNode' = None, index: int = None, power_level: int = None, stream=None): self.round = round self.slot_offset = slot_offset self.modulation = modulation self.gloria_modulation = RADIO_MODULATIONS[self.modulation] self.payload = payload self.type = type self.is_ack = is_ack self.master = master self.index = index self.stream = stream if power_level is None: self.power_level = GLORIA_DEFAULT_POWER_LEVELS[ self.gloria_modulation] else: self.power_level = power_level self.flood: gloria.GloriaFlood = None self.radio_configuration = RadioConfiguration(self.gloria_modulation) self.generate()
def draw(self, modulation=None, power=22): if modulation is not None: H = self.G.copy() config = RadioConfiguration(modulation) math = RadioMath(config) edges_to_remove = [] for (u, v, pl) in H.edges.data('path_loss'): if pl > math.link_budget(power=power): edges_to_remove.append((u, v)) H.remove_edges_from(edges_to_remove) config = RadioConfiguration(modulation) if self.pos is None: pos = nx.spring_layout(H) else: pos = self.pos edge_labels = dict([((u, v), "{:.2f}".format(d['path_loss'])) for u, v, d in H.edges(data=True)]) nx.draw(H, with_labels=True, node_size=500, node_color=config.color, font_color='white', pos=pos) nx.draw_networkx_edge_labels(H, pos=pos, edge_labels=edge_labels) else: if self.pos is None: pos = nx.spring_layout(self.G) else: pos = self.pos edge_labels = dict([((u, v), "{:.2f}".format(d['path_loss'])) for u, v, d in self.G.edges(data=True)]) nx.draw(self.G, with_labels=True, node_size=500, node_color='black', font_color='white', pos=pos) nx.draw_networkx_edge_labels(self.G, pos=pos, edge_labels=edge_labels)
def __init__(self, round_marker, modulation, type: LWBRoundType, master: 'sim_node.SimNode' = None, layout: typing.List[LWBSlotItem] = []): self.round_marker = round_marker self.modulation = modulation self.gloria_modulation = lwb_slot.RADIO_MODULATIONS[self.modulation] self.radio_configuration = RadioConfiguration(self.gloria_modulation) self.type = type self.master = master self.layout = layout self.slots: typing.List[lwb_slot.LWBSlot] = [] self.generate()
def is_reachable(self, modulation, node_a: 'sim_node.SimNode', node_b: 'sim_node.SimNode', power=22): config = RadioConfiguration(modulation) math = RadioMath(config) pl = self.calculate_path_loss(node_a, node_b) if pl <= math.link_budget(power=power): return True else: return False
def process_next_mod(self): self.current_modulation -= 1 if self.current_modulation >= 0: self.radio_config = RadioConfiguration( modulation=lwb_slot.RADIO_MODULATIONS[self.current_modulation]) self.radio_math = RadioMath(self.radio_config) if self.radio_config.modem is RadioModem.FSK: self.process_rx() else: self.process_lora_cad() else: self.callback(None)
def __init__(self, timestamp, source: 'sim_node.SimNode', payload, modulation, destination=None, type=SimMessageType.DATA, content=None, power_level=0, id=None, band=None, tx_start=None): self.timestamp = timestamp if id is not None: self.id = id else: self.id = np.random.randint(1024) self.source = source self.destination = destination self.type = type self.payload = payload self.content = content self.modulation = modulation self.band = band self.tx_start = tx_start if id is not None: self.id = id else: self.id = np.random.randint(256) self.power_level = power_level self.hop_count = 0 self.radio_configuration = RadioConfiguration( lwb_slot.RADIO_MODULATIONS[modulation], lwb_slot.RADIO_POWERS[self.power_level], tx=True, preamble=(2 if self.modulation > 7 else 3)) self.radio_math = RadioMath(self.radio_configuration) self.freeze_hop_count = self.hop_count self.freeze_power_level = self.power_level self.freeze_timestamp = None
def get_bitrate(modulation): if modulation.modem == RadioModem.LORA: if modulation.bandwidth == 125000: bandwidth = 0 elif modulation.bandwidth == 250000: bandwidth = 1 elif modulation.bandwidth == 500000: bandwidth = 2 elif modulation.bandwidth == 10400: bandwidth = 3 else: bandwidth = 0 config = RadioConfiguration(int(12 - modulation.sf), bandwidth=bandwidth) return config.symbol_rate * (config.sf - (2 if config.low_data_rate else 0) ) * (4 / (config.coderate % 4 + 4)) else: return modulation.bitrate
def tx(self, source: 'sim_node.SimNode', modulation, band, message: SimMessage): power = lwb_slot.RADIO_POWERS[message.power_level] message = copy(message) message.hop_count += 1 message.tx_start = source.transform_local_to_global_timestamp( message.timestamp) self.mq.loc[len(self.mq)] = [ source, modulation, band, power, message.tx_start, message.tx_end, message, message.hash ] self.network.tracer.log_activity( TxActivity(message.tx_start, message.tx_end, source, RadioConfiguration.tx_energy( power, message.tx_end - message.tx_start), power, modulation, ack=(message.type is SimMessageType.GLORIA_ACK))) for rx_node_item_index, rx_node_item in self.rxq.iterrows(): if (rx_node_item is not None and rx_node_item['rx_start'] < message.tx_start and rx_node_item['modulation'] is modulation and rx_node_item['band'] is band): self.network.em.register_event( message.tx_end, rx_node_item['rx_node'], sim_event_type.SimEventType.TX_DONE_BEFORE_RX_TIMEOUT, rx_node_item['callback'], { 'source': source, 'message': message, 'rx': rx_node_item }, local=False)
def iterate_from_node(self, tx_node: Node): for preamble_len in PREAMBLES: for modulation in lwb_slot.RADIO_MODULATIONS: for power in POWERS: # lwb_slot.POWERS: self.logger.info( "Tx on Node {}: Mod: {}, Power: {}, Preamble_Length: {}" .format(tx_node.id, modulation, power, preamble_len)) config = RadioConfiguration(modulation, preamble=preamble_len) math = RadioMath(config) message = "Hello World! from FlockLab Node {}: Mod: {}, Pow: {}, Prmbl: {}".format( tx_node.id, modulation, power, preamble_len) for node in self.nodes: self.configure_node(node, (node.id == tx_node.id), modulation, power, preamble_len) if node.id != tx_node.id: self.receive(node) time.sleep(0.1) self.send(tx_node, message=message) time.sleep( math.get_message_toa(len(message) + 1) * 1.5 + 0.1)
def __init__(self, modulation: int, safety_factor: int = 2): self.modulation = modulation self.safety_factor = safety_factor self.radio_config = RadioConfiguration(self.modulation) self.radio_math = RadioMath(self.radio_config)
def reconstruct_receptions(df, csv_path): receptions = [ pd.DataFrame(columns=[ 'tx_node', 'rx_node', 'modulation', 'power', 'preamble', 'rssi', 'snr', 'timestamp' ], dtype='float') ] nodes = df.node_id.sort_values().unique() for node in nodes: subset = df[(df.node_id == node) & (df.rx == True)] counter = 0 for index, row in subset.iterrows(): counter += 1 if (counter % 100) == 0: print("{}@{}".format(counter, node), end=',') if (type(row['output']) is dict and 'type' in row['output'] and row['output']['type'] == 'radio_cfg' and 'power' in row['output']): modulation = row['output']['modulation'] power = row['output']['power'] preamble = row['output']['preamble'] config = RadioConfiguration(modulation, preamble=preamble) math = RadioMath(config) message = "Hello World! from FlockLab Node {}: Mod: {:d}, Pow: {:d}, Prmbl: {:d}".format( node, modulation, power, preamble) offset = math.get_message_toa(len(message) + 1) * 1.5 + 0.3 rx_subset = df[(df.timestamp > row.timestamp) & (df.timestamp < (row.timestamp + offset)) & (df.node_id != node) & (df.rx == True)] receptions.append( pd.DataFrame({ 'tx_node': [node], 'rx_node': [None], 'modulation': [modulation], 'power': [power], 'preamble': [preamble], 'rssi': [None], 'snr': [None], 'timestamp': [row.timestamp], })) for rx_index, rx_row in rx_subset.iterrows(): if (type(rx_row['output']) is dict and 'type' in rx_row['output'] and rx_row['output']['type'] == 'radio_rx_msg' and 'text' in rx_row['output'] and rx_row['output']['text'] == message): receptions.append( pd.DataFrame({ 'tx_node': [node], 'rx_node': [rx_row['node_id']], 'modulation': [modulation], 'power': [power], 'preamble': [preamble], 'rssi': [rx_row['output']['rssi']], 'snr': [rx_row['output']['snr']], 'timestamp': [row.timestamp], })) receptions = pd.concat(receptions, ignore_index=True) receptions.to_csv(csv_path) return receptions
def configure_node(node: Node, tx: bool, modulation, power, preamble: int): config = RadioConfiguration(modulation, power=power, tx=tx, preamble=preamble) node.cmd(config.cmd)
def receive_message_on_rx_timeout( self, modulation, band, rx_node: 'sim_node.SimNode', rx_start, rx_timeout ) -> Tuple[Optional[SimMessage], Optional['sim_node.SimNode']]: self.mm.unregister_rx(rx_node) rx_start = rx_node.transform_local_to_global_timestamp(rx_start) def mark_reachable_message(item): return self.is_reachable(modulation, rx_node, item['source'], power=item['power']) def calc_power_message(item): return -self.calculate_path_loss( rx_node, self.network.nodes[item.source.id]) + item['power'] config = RadioConfiguration(modulation) math = RadioMath(config) valid_rx_start = rx_start + math.get_symbol_time() * 0.1 keep_quiet_start = rx_start - 100E-6 interfering_set = ( self.mm.mq.loc[(self.mm.mq.band == band) & (self.mm.mq.tx_end >= keep_quiet_start) & (self.mm.mq.tx_start <= valid_rx_start)]).copy() subset = (self.mm.mq.loc[(self.mm.mq.modulation == modulation) & (self.mm.mq.band == band) & (self.mm.mq.tx_start >= valid_rx_start) & (self.mm.mq.tx_start <= rx_timeout) & (self.mm.mq.tx_end > rx_timeout)]).copy() if len(subset): subset['reachable'] = subset.apply(mark_reachable_message, axis=1) subset = subset.loc[subset.reachable == True] if len(subset) > 0: if len(interfering_set): interfering_set['rx_power'] = interfering_set.apply( calc_power_message, axis=1) subset['rx_power'] = subset.apply(calc_power_message, axis=1) candidates = [] for index_candidate, candidate in subset.sort_values( by=['tx_start'], ascending=False).iterrows(): interfering_power = 0 for index_interferer, interferer in interfering_set.iterrows( ): if interferer['message_hash'] is not candidate[ 'message_hash'] or not ( (candidate['tx_start'] - 100E6) < interferer['tx_start'] < (candidate['tx_start'] + 100E6)): interfering_power += np.power( 10, interferer['rx_power'] / 10) if np.power(10, candidate['rx_power'] / 10) > ( interfering_power * np.power(10, RADIO_SNR[modulation] / 10)): candidates.append(candidate) if len(candidates) > 0: best_candidate = max( candidates, key=lambda candidate: candidate['rx_power']) return best_candidate['message'].copy( ), best_candidate['source'] else: self.network.tracer.log_activity( RxActivity( rx_start, self.network.global_timestamp, rx_node, RadioConfiguration.rx_energy( self.network.global_timestamp - rx_start), modulation, False)) return None, None else: self.network.tracer.log_activity( RxActivity( rx_start, self.network.global_timestamp, rx_node, RadioConfiguration.rx_energy( self.network.global_timestamp - rx_start), modulation, False)) return None, None else: self.network.tracer.log_activity( RxActivity( rx_start, self.network.global_timestamp, rx_node, RadioConfiguration.rx_energy( self.network.global_timestamp - rx_start), modulation, False)) return None, None
def analyze_tx_count(receptions_no_ack, receptions_ack): with plt.style.context("bmh"): columns = [ 'modulation_name', 'tx_gloria_no_ack', 'tx_gloria_ack', 'tx_gloria_ack_acks' ] tx_count = pd.DataFrame(columns=columns) tx_count.index.name = 'modulation' receptions_no_ack.modulation = receptions_no_ack.modulation.astype( int) receptions_ack.modulation = receptions_ack.modulation.astype(int) modulations = receptions_ack.modulation.sort_values().unique() for modulation in modulations: config = RadioConfiguration(modulation) subset_no_ack = receptions_no_ack[( receptions_no_ack.modulation == modulation)] subset_ack = receptions_ack[( receptions_ack.modulation == modulation)] tx_gloria_no_ack = lwb_slot.GLORIA_RETRANSMISSIONS_COUNTS[ modulation] - subset_no_ack.remaining_tx.mean() tx_gloria_ack = lwb_slot.GLORIA_RETRANSMISSIONS_COUNTS[ modulation] - subset_ack.remaining_tx.mean() tx_gloria_ack_acks = np.mean(subset_ack.acked) config = RadioConfiguration(modulation) math = RadioMath(config) msg_toa = math.get_message_toa( payload_size=50 ) # receptions_ack[receptions_ack.size != 0].size.mean()) ack_toa = math.get_message_toa(payload_size=GLORIA_ACK_LENGTH) ack_ratio = ack_toa / msg_toa tx_count.loc[len(tx_count)] = [ config.modulation_name, tx_gloria_no_ack, tx_gloria_ack, tx_gloria_ack_acks * ack_ratio ] fig = plt.figure(figsize=(10, 8)) ax = plt.gca() plt.title( "Average Transmission count of Gloria Flood on FlockLab (based on {} floods)" .format(receptions_no_ack.shape[0])) gloria_no_ack_rects = ax.bar(modulations - 0.3, tx_count.loc[:, 'tx_gloria_no_ack'], width=0.3) gloria_acks_rects = ax.bar(modulations, tx_count.loc[:, 'tx_gloria_ack'], width=0.3) gloria_acks_ack_rects = ax.bar(modulations + 0.3, tx_count.loc[:, 'tx_gloria_ack_acks'], width=0.3) # for i, rect in zip(modulations, gloria_no_ack_rects): # rect.set_fc(RadioConfiguration(i).color) rect = gloria_no_ack_rects[1] ax.text(rect.get_x() + rect.get_width() / 2, rect.get_height() / 2, "Gloria Tx without ACK", rotation=90, ha='center', va='center', color='white') rect = gloria_acks_rects[1] ax.text(rect.get_x() + rect.get_width() / 2, rect.get_height() / 2, "Gloria Tx with ACK", rotation=90, ha='center', va='center', color='white') rect = gloria_acks_ack_rects[1] ax.text(rect.get_x() + rect.get_width() / 2, rect.get_height() / 2, "Gloria ACKs", rotation=90, ha='center', va='center', color='white') ax.grid(False) ax.grid(which='minor', axis='y', alpha=0.2) ax.grid(which='major', axis='y', alpha=0.5) plt.ylabel('Average Tx count') plt.xticks( modulations, map(RadioConfiguration.get_modulation_name, modulations))
def get_sync_time(modulation): return RadioMath(RadioConfiguration(modulation)).sync_time