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
示例#4
0
    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)
示例#5
0
    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)
示例#6
0
    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
示例#7
0
    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
示例#9
0
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)
示例#10
0
    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
示例#11
0
    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)
示例#12
0
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))
示例#13
0
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()