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 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 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 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, 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 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 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
class SimMessage: 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 __copy__(self): message = SimMessage(timestamp=self.timestamp, source=self.source, payload=self.payload, destination=self.destination, type=self.type, content=self.content, power_level=self.power_level, modulation=self.modulation, band=self.band, id=self.id) message.hop_count = self.hop_count message.tx_start = self.tx_start self.freeze_hop_count = self.hop_count self.freeze_power_level = self.power_level return message def copy(self): return self.__copy__() def __str__(self): return "<SimMessage {:f},{:d},{:d},{},{},{},{}>".format( self.timestamp, self.modulation, self.power_level, self.type, self.source, self.destination, self.payload) def increase_timestamp(self, offset): self.timestamp += offset def freeze(self): self.freeze_hop_count = self.hop_count self.freeze_power_level = self.power_level self.freeze_timestamp = self.timestamp @property def tx_end(self): return self.tx_start + self.radio_math.get_message_toa( payload_size=self.payload) @property def hash(self): return "{timestamp},{source},{destination},{type},{power_level},{hop_count},{id}".format( timestamp=self.timestamp, source=self.source, destination=self.destination, type=self.type, content=self.content, power_level=self.power_level, hop_count=self.hop_count, id=self.id)
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 __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)
class GloriaTimings: 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) @property def rx_setup_time(self): # delay_fsk_config_rx # delay_fsk_rx_boost # delay_rx_2_fs tmp = (0.000471490321 + 1.40729712e-05 * self.safety_factor + 7.71854385e-05 + 1.18305852e-06 * self.safety_factor + RX2RF[0] + TX2RF[1] * self.safety_factor) return tmp @property def rx_irq_time(self): # irq_delay_finish # status_delay tmp = (5.92039801e-05 + 4.86421406e-07 * self.safety_factor + 4.16382322e-05 + 1.03885748e-06 * self.safety_factor + self.payload_get_time) return tmp @property def tx_setup_time(self): # delay_config_tx # delay_fsk_tx # delay_tx_2_fs tmp = (0.000526660267 + 2.67004382e-05 * self.safety_factor + 1.76468861e-05 + 1.90078286e-09 * self.safety_factor + TX2RF[0] + TX2RF[1] * self.safety_factor + self.payload_set_time) return tmp @property def tx_irq_time(self): # irq_delay_finish tmp = 5.92039801e-05 + 4.86421406e-07 * self.safety_factor return tmp @property def sleep_time(self): return 1.52926223e-05 + 1.82871623e-06 * self.safety_factor @property def wakeup_time(self): # 14us for STM32L4 (Standby LPR SRAM2): # (http://www.st.com/content/ccc/resource/technical/document/application_note/9e/9b/ca/a3/92/5d/44/ff/DM00148033.pdf/files/DM00148033.pdf/jcr:content/translations/en.DM00148033.pdf) return 0.000488257072 + 1.01439514e-05 * self.safety_factor + 14E-6 @property def payload_get_time(self): return 0.000528727308 + 1.63738628e-06 * self.safety_factor @property def payload_set_time(self): return 0.000528918379 + 3.12690783e-06 * self.safety_factor @property def wakeup_config(self): return 0.0008073303060942998 @property def preamble_len(self): return self.radio_config.preamble_len @property def rx_offset(self): return self.radio_math.get_preamble_time() * ( PREAMBLE_PRE_LISTENING[self.modulation]) @property def rx_end_offset(self): return RX_TIME_OFFSETS[self.modulation][0] + RX_TIME_OFFSETS[ self.modulation][1] * self.safety_factor @property def tx_end_offset(self): return TX_TIME_OFFSETS[self.modulation][0] + TX_TIME_OFFSETS[ self.modulation][1] * self.safety_factor @property def slot_overhead(self): tx2rx_overhead = (self.tx_end_offset + self.tx_irq_time + self.rx_setup_time + self.rx_offset + GAP) rx2tx_overhead = (self.rx_end_offset + self.rx_irq_time + self.rx_setup_time + GAP) return np.max([tx2rx_overhead, rx2tx_overhead]) @property def slot_ack_overhead(self): max_normal_overhead = self.slot_overhead rx2rx_overhead = (self.rx_end_offset + self.rx_irq_time + self.rx_setup_time + self.rx_offset + GAP) return np.max([max_normal_overhead, rx2rx_overhead]) @property def flood_init_overhead(self): return self.wakeup_time + np.max( [self.tx_setup_time, self.rx_setup_time + self.rx_offset]) @property def flood_finish_overhead(self): return self.sleep_time def get_timings(self): return { 'slot_overhead': GloriaTimings.timer_ticks(self.slot_overhead), 'slot_ack_overhead': GloriaTimings.timer_ticks(self.slot_ack_overhead), 'flood_init_overhead': GloriaTimings.timer_ticks(self.flood_init_overhead), 'flood_finish_overhead': GloriaTimings.timer_ticks(self.flood_finish_overhead), 'rx_offset': GloriaTimings.timer_ticks(self.rx_offset), 'rx_trigger_delay': GloriaTimings.timer_ticks(RX2RF[0]), 'tx_trigger_delay': GloriaTimings.timer_ticks(TX2RF[0]), 'tx_sync': GloriaTimings.timer_ticks(TX2SYNC[self.modulation]), 'rx_setup': GloriaTimings.timer_ticks(self.rx_setup_time), 'tx_setup': GloriaTimings.timer_ticks(self.tx_setup_time), 'preamble_timeout': GloriaTimings.radio_timer_ticks( self.radio_math.get_preamble_time() * (1 + PREAMBLE_PRE_LISTENING[self.modulation] + PREAMBLE_POST_LISTENING[self.modulation])), 'mcu_timeout': GloriaTimings.timer_ticks( (RX2RF[0] + RX2RF[1] * self.safety_factor + self.rx_offset + TX2SYNC[self.modulation] * 1.5 + self.radio_math.get_preamble_time() * PREAMBLE_POST_LISTENING[self.modulation])), } @staticmethod def timer_ticks(time: float) -> int: return int(np.round(time * TIMER_FREQUENCY)) @staticmethod def radio_timer_ticks(time: float) -> int: return int(np.round(time / RADIO_TIMER_PERIOD))
class CADSearch: def __init__(self, node: 'sim_node.SimNode', callback, start_modulation: int = None): self.node = node if start_modulation is not None: self.current_modulation = start_modulation + 1 else: self.current_modulation = len(lwb_slot.RADIO_MODULATIONS) self.current_band = gloria.DEFAULT_BAND self.callback = callback self.rx_start = None self.potential_message: SimMessage = None self.potential_node: sim_node.SimNode = None self.rx_timeout_event = None self.radio_config: RadioConfiguration = None self.radio_math: RadioMath = None self.process_next_mod() @property def rx_symbol_timeout(self): return [ lwb_slot.LWBSlot.create_empty_slot(i, payload=255).total_time for i in range(len(lwb_slot.RADIO_MODULATIONS)) ] 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 process_rx(self): self.rx_start = self.node.local_timestamp + gloria.GloriaTimings( lwb_slot.RADIO_MODULATIONS[self.current_modulation]).rx_setup_time self.node.mm.register_rx( self.node, self.rx_start, lwb_slot.RADIO_MODULATIONS[self.current_modulation], self.current_band, self.process_tx_done_before_rx_timeout_callback) self.rx_timeout_event = self.node.em.register_event( self.rx_start + self.rx_symbol_timeout[self.current_modulation], self.node, sim_event_manager.SimEventType.RX_TIMEOUT, self.process_rx_timeout) def process_tx_done_before_rx_timeout_callback(self, event): message, node = self.node.network.mc.receive_message_on_tx_done_before_rx_timeout( self.node, lwb_slot.RADIO_MODULATIONS[self.current_modulation], self.current_band, event['data']['message'], self.rx_start, event['data']['message'].tx_start, event['data']) if message is not None: self.callback(message) def process_rx_timeout(self, event): self.potential_message, self.potential_node = self.node.network.mc.receive_message_on_rx_timeout( modulation=lwb_slot.RADIO_MODULATIONS[self.current_modulation], band=self.current_band, rx_node=self.node, rx_start=self.rx_start, rx_timeout=self.node.local_timestamp) if self.potential_message is not None: self.node.em.register_event(self.potential_message.tx_end, self.node, sim_event_manager.SimEventType.RX_DONE, self.process_rx_done) else: self.process_next_mod() def process_rx_done(self, event): if self.node.network.mc.check_if_successfully_received( self.current_modulation, self.current_band, self.potential_message, self.rx_start, self.node, self.potential_node): self.callback(self.potential_message) else: self.process_next_mod() def process_lora_cad(self): self.node.em.register_event( self.node.local_timestamp + gloria.GloriaTimings(self.current_modulation).rx_setup_time + self.radio_math.get_symbol_time() * (CAD_SYMBOL_TIMEOUT[self.current_modulation] + 0.5), self.node, sim_event_manager.SimEventType.CAD_DONE, self.process_lora_cad_done) def process_lora_cad_done(self, event): if self.node.network.mc.cad_process( modulation=lwb_slot.RADIO_MODULATIONS[self.current_modulation], band=self.current_band, rx_node=self.node, timestamp=self.node.local_timestamp) is not None: self.process_rx() else: self.process_next_mod()