Beispiel #1
0
    def __init__(self, simulator_config: SimulatorConfiguration, modulators,
                 expression_parser: SimulatorExpressionParser, project_manager: ProjectManager,
                 sniffer: ProtocolSniffer, sender: EndlessSender):
        super().__init__()
        self.simulator_config = simulator_config
        self.project_manager = project_manager
        self.expression_parser = expression_parser
        self.modulators = modulators  # type: list[Modulator]
        self.backend_handler = BackendHandler()

        self.transcript = Transcript()

        self.current_item = None
        self.last_sent_message = None
        self.is_simulating = False
        self.do_restart = False
        self.current_repeat = 0
        self.log_messages = []

        self.sniffer_ready = False
        self.sender_ready = False
        self.fatal_device_error_occurred = False

        self.verbose = True

        self.sniffer = sniffer
        self.sender = sender
Beispiel #2
0
    def __init__(self, simulator_config: SimulatorConfiguration, modulators,
                 expression_parser: SimulatorExpressionParser, project_manager: ProjectManager,
                 sniffer: ProtocolSniffer, sender: EndlessSender):
        super().__init__()
        self.simulator_config = simulator_config
        self.project_manager = project_manager
        self.expression_parser = expression_parser
        self.modulators = modulators  # type: list[Modulator]
        self.backend_handler = BackendHandler()

        self.transcript = Transcript()

        self.current_item = None
        self.last_sent_message = None
        self.is_simulating = False
        self.do_restart = False
        self.current_repeat = 0
        self.log_messages = []

        self.sniffer_ready = False
        self.sender_ready = False
        self.fatal_device_error_occurred = False

        self.verbose = True

        self.sniffer = sniffer
        self.sender = sender
Beispiel #3
0
class Simulator(QObject):
    simulation_started = pyqtSignal()
    simulation_stopped = pyqtSignal()

    def __init__(self, simulator_config: SimulatorConfiguration, modulators,
                 expression_parser: SimulatorExpressionParser, project_manager: ProjectManager,
                 sniffer: ProtocolSniffer, sender: EndlessSender):
        super().__init__()
        self.simulator_config = simulator_config
        self.project_manager = project_manager
        self.expression_parser = expression_parser
        self.modulators = modulators  # type: list[Modulator]
        self.backend_handler = BackendHandler()

        self.transcript = Transcript()

        self.current_item = None
        self.last_sent_message = None
        self.is_simulating = False
        self.do_restart = False
        self.current_repeat = 0
        self.log_messages = []

        self.sniffer_ready = False
        self.sender_ready = False
        self.fatal_device_error_occurred = False

        self.verbose = True

        self.sniffer = sniffer
        self.sender = sender

    def __initialize_counters(self):
        for item in self.simulator_config.get_all_items():
            if isinstance(item, SimulatorCounterAction):
                item.reset_value()

    def start(self):
        self.reset()

        self.transcript.clear()
        self.__initialize_counters()

        # start devices
        if self.sniffer:
            self.sniffer.rcv_device.fatal_error_occurred.connect(self.stop_on_error)
            self.sniffer.rcv_device.ready_for_action.connect(self.on_sniffer_ready)

        if self.sender:
            self.sender.device.fatal_error_occurred.connect(self.stop_on_error)
            self.sender.device.ready_for_action.connect(self.on_sender_ready)

        if self.sniffer:
            self.sniffer.sniff()

        if self.sender:
            self.sender.start()

        self._start_simulation_thread()

        # Ensure all ongoing qt signals can be processed
        time.sleep(0.1)

    @pyqtSlot(str)
    def stop_on_error(self, msg: str):
        self.fatal_device_error_occurred = True
        if self.is_simulating:
            self.stop(msg=msg)

    @pyqtSlot()
    def on_sniffer_ready(self):
        if not self.sniffer_ready:
            self.log_message("RX is ready to operate")
            self.sniffer_ready = True

    @pyqtSlot()
    def on_sender_ready(self):
        if not self.sender_ready:
            self.log_message("TX is ready to operate")
            self.sender_ready = True

    def stop(self, msg=""):
        self.simulation_stopped.emit()

        if self.is_simulating:
            self.log_message("Stop simulation" + (" ({})".format(msg.strip()) if msg else ""))
            self.is_simulating = False
            self.do_restart = False

        # stop devices
        if self.sniffer:
            self.sniffer.stop()

        if self.sender:
            self.sender.stop()

    def restart(self):
        self.transcript.start_new_round()
        self.reset()
        self.log_message("<b>Restarting simulation</b>")

    def reset(self):
        self.sniffer_ready = False
        self.sender_ready = False
        self.fatal_device_error_occurred = False

        if self.sniffer:
            self.sniffer.clear()

        self.current_item = self.simulator_config.rootItem

        for msg in self.simulator_config.get_all_messages():
            msg.send_recv_messages[:] = []

        self.last_sent_message = None
        self.is_simulating = True
        self.do_restart = False
        self.current_repeat = 0
        self.log_messages[:] = []

    @property
    def devices(self):
        result = []
        if self.sniffer is not None:
            result.append(self.sniffer.rcv_device)
        if self.sender is not None:
            result.append(self.sender.device)
        return result

    def device_messages(self) -> list:
        return [device.read_messages() for device in self.devices]

    def read_log_messages(self):
        result = self.log_messages[:]
        self.log_messages.clear()
        return result

    def cleanup(self):
        for device in self.devices:
            if device.backend not in (Backends.none, Backends.network):
                device.cleanup()

            if device is not None:
                device.free_data()

    def _start_simulation_thread(self):
        self.simulation_thread = threading.Thread(target=self.simulate)
        self.simulation_thread.daemon = True
        self.simulation_thread.start()

    def simulation_is_finished(self):
        if self.project_manager.simulator_num_repeat == 0:
            return False

        return self.current_repeat >= self.project_manager.simulator_num_repeat

    def __wait_for_devices(self):
        for i in range(10):
            if (self.sniffer is None or self.sniffer_ready) and (self.sender is None or self.sender_ready):
                return True
            if self.fatal_device_error_occurred:
                return False
            self.log_message("<i>Waiting for devices</i>")
            time.sleep(1)

        return True

    def __fill_counter_values(self, command: str):
        result = []
        regex = r"(item[0-9]+\.counter_value)"
        for token in re.split(regex, command):
            if re.match(regex, token) is not None:
                try:
                    result.append(str(self.simulator_config.item_dict[token].value))
                except (KeyError, ValueError, AttributeError):
                    logger.error("Could not get counter value for " + token)
            else:
                result.append(token)

        return "".join(result)

    def simulate(self):
        self.simulation_started.emit()
        self.is_simulating = self.__wait_for_devices()

        if not self.is_simulating:
            # Simulation may have ended due to device errors
            self.stop("Devices not ready")
            return

        self.log_message("<b>Simulation is running</b>")

        while self.is_simulating and not self.simulation_is_finished():
            if self.current_item is self.simulator_config.rootItem:
                next_item = self.current_item.next()

            elif isinstance(self.current_item, SimulatorProtocolLabel):
                next_item = self.current_item.next()

            elif isinstance(self.current_item, SimulatorMessage):
                self.process_message()
                next_item = self.current_item.next()

            elif isinstance(self.current_item, SimulatorGotoAction):
                next_item = self.current_item.target
                self.log_message("GOTO item " + next_item.index())

            elif isinstance(self.current_item, SimulatorTriggerCommandAction):
                next_item = self.current_item.next()
                command = self.__fill_counter_values(self.current_item.command)
                self.log_message("Calling {}".format(command))
                if self.current_item.pass_transcript:
                    transcript = "\n".join(self.transcript.get_for_all_participants(all_rounds=False))
                    result, rc = util.run_command(command, transcript, use_stdin=True, return_rc=True)
                else:
                    result, rc = util.run_command(command, param=None, detailed_output=True, return_rc=True)
                self.current_item.return_code = rc
                self.log_message(result)

            elif isinstance(self.current_item, SimulatorRule):
                condition = self.current_item.get_first_applying_condition()

                if condition is not None and condition.logging_active and condition.type != ConditionType.ELSE:
                    self.log_message("Rule condition " + condition.index() + " (" + condition.condition + ") applied")

                if condition is not None and condition.child_count() > 0:
                    next_item = condition.children[0]
                else:
                    next_item = self.current_item.next_sibling()

            elif isinstance(self.current_item, SimulatorRuleCondition):
                if self.current_item.type == ConditionType.IF:
                    next_item = self.current_item.parent()
                else:
                    next_item = self.current_item.parent().next_sibling()

            elif isinstance(self.current_item, SimulatorSleepAction):
                self.log_message(self.current_item.caption)
                time.sleep(self.current_item.sleep_time)
                next_item = self.current_item.next()

            elif isinstance(self.current_item, SimulatorCounterAction):
                self.current_item.progress_value()
                self.log_message("Increase counter by {} to {}".format(self.current_item.step, self.current_item.value))
                next_item = self.current_item.next()

            elif self.current_item is None:
                self.current_repeat += 1
                next_item = self.simulator_config.rootItem
                self.transcript.start_new_round()

            else:
                raise ValueError("Unknown action {}".format(type(self.current_item)))

            self.current_item = next_item

            if self.do_restart:
                self.restart()

        self.stop(msg="Finished")

    def process_message(self):
        assert isinstance(self.current_item, SimulatorMessage)
        msg = self.current_item

        if msg.source is None:
            return

        new_message = self.generate_message_from_template(msg)

        if msg.source.simulate:
            # we have to send a message
            sender = self.sender
            if sender is None:
                self.log_message("Fatal: No sender configured")
                return

            for lbl in new_message.message_type:
                if isinstance(lbl.label, ChecksumLabel):
                    checksum = lbl.label.calculate_checksum_for_message(new_message, use_decoded_bits=False)
                    label_range = new_message.get_label_range(lbl=lbl.label, view=0, decode=False)
                    start, end = label_range[0], label_range[1]
                    new_message.plain_bits[start:end] = checksum + array.array("B", [0] * (
                            (end - start) - len(checksum)))

            self.transcript.append(msg.source, msg.destination, new_message, msg.index())
            self.send_message(new_message, msg.repeat, sender, msg.modulator_index)
            self.log_message("Sending message " + msg.index())
            self.log_message_labels(new_message)
            msg.send_recv_messages.append(new_message)
            self.last_sent_message = msg
        else:
            # we have to receive a message
            self.log_message("<i>Waiting for message {}</i>".format(msg.index()))
            sniffer = self.sniffer
            if sniffer is None:
                self.log_message("Fatal: No sniffer configured")
                return

            retry = 0

            max_retries = self.project_manager.simulator_retries
            while self.is_simulating and not self.simulation_is_finished() and retry < max_retries:
                received_msg = self.receive_message(sniffer)

                if not self.is_simulating:
                    return

                if received_msg is None:
                    if self.project_manager.simulator_error_handling_index == 0:
                        self.resend_last_message()
                        retry += 1
                        continue
                    elif self.project_manager.simulator_error_handling_index == 1:
                        self.stop()
                        return
                    elif self.project_manager.simulator_error_handling_index == 2:
                        self.do_restart = True
                        return

                received_msg.decoder = new_message.decoder
                received_msg.message_type = new_message.message_type

                check_result, error_msg = self.check_message(received_msg, new_message, retry=retry,
                                                             msg_index=msg.index())

                if check_result:
                    decoded_msg = Message(received_msg.decoded_bits, 0,
                                          received_msg.message_type, decoder=received_msg.decoder)
                    msg.send_recv_messages.append(decoded_msg)
                    self.transcript.append(msg.source, msg.destination, decoded_msg, msg.index())
                    self.log_message("Received message " + msg.index() + ": ")
                    self.log_message_labels(decoded_msg)
                    return
                elif self.verbose:
                    self.log_message(error_msg)

                retry += 1

            if retry == self.project_manager.simulator_retries:
                self.log_message("Message " + msg.index() + " not received")
                self.stop()

    def log_message(self, message):
        timestamp = '{0:%b} {0.day} {0:%H}:{0:%M}:{0:%S}.{0:%f}'.format(datetime.datetime.now())

        if isinstance(message, list) and len(message) > 0:
            self.log_messages.append(timestamp + ": " + message[0])
            self.log_messages.extend(message[1:])
            logger.debug("\n".join(message))
        else:
            self.log_messages.append(timestamp + ": " + message)
            logger.debug(message)

    def check_message(self, received_msg, expected_msg, retry: int, msg_index: int) -> (bool, str):
        if len(received_msg.decoded_bits) == 0:
            return False, "Failed to decode message {}".format(msg_index)

        for lbl in received_msg.message_type:
            if lbl.value_type_index in (1, 4):
                # get live, random
                continue

            start_recv, end_recv = received_msg.get_label_range(lbl.label, 0, True)
            start_exp, end_exp = expected_msg.get_label_range(lbl.label, 0, False)

            if isinstance(lbl.label, ChecksumLabel):
                expected = lbl.label.calculate_checksum_for_message(received_msg, use_decoded_bits=True)
                start, end = received_msg.get_label_range(lbl.label, 0, True)
                actual = received_msg.decoded_bits[start:end]
            else:
                actual = received_msg.decoded_bits[start_recv:end_recv]
                expected = expected_msg[start_exp:end_exp]

            if actual != expected:
                log_msg = []
                log_msg.append("Attempt for message {} [{}/{}]".format(msg_index, retry + 1,
                                                                       self.project_manager.simulator_retries))
                log_msg.append(HTMLFormatter.indent_string("Mismatch for label: <b>{}</b>".format(lbl.name)))
                expected_str = util.convert_bits_to_string(expected, lbl.label.display_format_index)
                got_str = util.convert_bits_to_string(actual, lbl.label.display_format_index)
                log_msg.append(HTMLFormatter.align_expected_and_got_value(expected_str, got_str, align_depth=2))
                return False, log_msg

        return True, ""

    def log_message_labels(self, message: Message):
        message.split(decode=False)
        for lbl in message.message_type:
            if not lbl.logging_active:
                continue

            try:
                data = message.plain_bits[lbl.start:lbl.end]
            except IndexError:
                return None

            lsb = lbl.display_bit_order_index == 1
            lsd = lbl.display_bit_order_index == 2

            data = util.convert_bits_to_string(data, lbl.display_format_index, pad_zeros=True, lsb=lsb, lsd=lsd)
            if data is None:
                continue

            log_msg = lbl.name + ": " + HTMLFormatter.monospace(data)
            self.log_messages.append(HTMLFormatter.indent_string(log_msg))

    def resend_last_message(self):
        self.log_message("Resending last message")
        lsm = self.last_sent_message

        if lsm is None:
            return

        sender = self.sender
        self.send_message(lsm.send_recv_messages[-1], lsm.repeat, sender, lsm.modulator_index)

    def send_message(self, message, repeat, sender, modulator_index):
        modulator = self.modulators[modulator_index]
        modulated = modulator.modulate(message.encoded_bits, pause=message.pause)

        curr_repeat = 0

        while curr_repeat < repeat:
            sender.push_data(modulated)
            curr_repeat += 1

    def receive_message(self, sniffer):
        if len(sniffer.messages) > 0:
            return sniffer.messages.pop(0)

        spy = QSignalSpy(sniffer.message_sniffed)
        if spy.wait(self.project_manager.simulator_timeout_ms):
            try:
                return sniffer.messages.pop(0)
            except IndexError:
                self.log_message("Could not receive message")
                return None
        else:
            self.log_message("Receive timeout")
            return None

    def get_full_transcript(self, start=0, use_bit=True):
        result = []
        for source, destination, msg, msg_index in self.transcript[start:]:
            try:
                data = msg.plain_bits_str if use_bit else msg.plain_hex_str
                result.append(self.TRANSCRIPT_FORMAT.format(msg_index, source.shortname, destination.shortname, data))
            except AttributeError:
                result.append("")
        return result

    def generate_message_from_template(self, template_msg: SimulatorMessage):
        new_message = Message(template_msg.plain_bits, pause=template_msg.pause, rssi=0,
                              message_type=template_msg.message_type, decoder=template_msg.decoder)

        for lbl in template_msg.children:  # type: SimulatorProtocolLabel
            if lbl.value_type_index == 2:
                # formula
                valid, _, node = self.expression_parser.validate_expression(lbl.formula)
                assert valid
                result = self.expression_parser.evaluate_node(node)
            elif lbl.value_type_index == 3:
                transcript = self.transcript.get_for_participant(template_msg.source
                                                                 if template_msg.source.simulate
                                                                 else template_msg.destination)

                if template_msg.destination.simulate:
                    direction = "->" if template_msg.source.simulate else "<-"
                    transcript += "\n" + direction + new_message.plain_bits_str + "\n"

                cmd = self.__fill_counter_values(lbl.external_program)
                result = util.run_command(cmd, transcript, use_stdin=True)
                if len(result) != lbl.end - lbl.start:
                    log_msg = "Result value of external program {}: {} ({}) does not match label length {}"
                    logger.error(log_msg.format(cmd, result, len(result), lbl.end - lbl.start))
                    continue

                try:
                    new_message[lbl.start:lbl.end] = array.array("B", (map(bool, map(int, result))))
                except Exception as e:
                    log_msg = "Could not assign {} to range because {}".format(result, e)
                    logger.error(log_msg)

                continue
            elif lbl.value_type_index == 4:
                # random value
                result = numpy.random.randint(lbl.random_min, lbl.random_max + 1)
            else:
                continue

            self.set_label_value(new_message, lbl, result)

        return new_message

    @staticmethod
    def set_label_value(message, label, decimal_value: int):
        lbl_len = label.end - label.start
        f_string = "{0:0" + str(lbl_len) + "b}"
        bits = f_string.format(decimal_value)

        if len(bits) > lbl_len:
            logger.warning("Value {0} too big for label {1}, bits truncated".format(decimal_value, label.name))

        for i in range(lbl_len):
            message[label.start + i] = bool(int(bits[i]))
Beispiel #4
0
class Simulator(QObject):
    simulation_started = pyqtSignal()
    simulation_stopped = pyqtSignal()

    def __init__(self, simulator_config: SimulatorConfiguration, modulators,
                 expression_parser: SimulatorExpressionParser, project_manager: ProjectManager,
                 sniffer: ProtocolSniffer, sender: EndlessSender):
        super().__init__()
        self.simulator_config = simulator_config
        self.project_manager = project_manager
        self.expression_parser = expression_parser
        self.modulators = modulators  # type: list[Modulator]
        self.backend_handler = BackendHandler()

        self.transcript = Transcript()

        self.current_item = None
        self.last_sent_message = None
        self.is_simulating = False
        self.do_restart = False
        self.current_repeat = 0
        self.log_messages = []

        self.sniffer_ready = False
        self.sender_ready = False
        self.fatal_device_error_occurred = False

        self.verbose = True

        self.sniffer = sniffer
        self.sender = sender

    def __initialize_counters(self):
        for item in self.simulator_config.get_all_items():
            if isinstance(item, SimulatorCounterAction):
                item.reset_value()

    def start(self):
        self.reset()

        self.transcript.clear()
        self.__initialize_counters()

        # start devices
        if self.sniffer:
            self.sniffer.rcv_device.fatal_error_occurred.connect(self.stop_on_error)
            self.sniffer.rcv_device.ready_for_action.connect(self.on_sniffer_ready)

        if self.sender:
            self.sender.device.fatal_error_occurred.connect(self.stop_on_error)
            self.sender.device.ready_for_action.connect(self.on_sender_ready)

        if self.sniffer:
            self.sniffer.sniff()

        if self.sender:
            self.sender.start()

        self._start_simulation_thread()

        # Ensure all ongoing qt signals can be processed
        time.sleep(0.1)

    @pyqtSlot(str)
    def stop_on_error(self, msg: str):
        self.fatal_device_error_occurred = True
        if self.is_simulating:
            self.stop(msg=msg)

    @pyqtSlot()
    def on_sniffer_ready(self):
        if not self.sniffer_ready:
            self.log_message("RX is ready to operate")
            self.sniffer_ready = True

    @pyqtSlot()
    def on_sender_ready(self):
        if not self.sender_ready:
            self.log_message("TX is ready to operate")
            self.sender_ready = True

    def stop(self, msg=""):
        self.simulation_stopped.emit()

        if self.is_simulating:
            self.log_message("Stop simulation" + (" ({})".format(msg.strip()) if msg else ""))
            self.is_simulating = False
            self.do_restart = False

        # stop devices
        if self.sniffer:
            self.sniffer.stop()

        if self.sender:
            self.sender.stop()

    def restart(self):
        self.transcript.start_new_round()
        self.reset()
        self.log_message("<b>Restarting simulation</b>")

    def reset(self):
        self.sniffer_ready = False
        self.sender_ready = False
        self.fatal_device_error_occurred = False

        if self.sniffer:
            self.sniffer.clear()

        self.current_item = self.simulator_config.rootItem

        for msg in self.simulator_config.get_all_messages():
            msg.send_recv_messages[:] = []

        self.last_sent_message = None
        self.is_simulating = True
        self.do_restart = False
        self.current_repeat = 0
        self.log_messages[:] = []

    @property
    def devices(self):
        result = []
        if self.sniffer is not None:
            result.append(self.sniffer.rcv_device)
        if self.sender is not None:
            result.append(self.sender.device)
        return result

    def device_messages(self) -> list:
        return [device.read_messages() for device in self.devices]

    def read_log_messages(self):
        result = self.log_messages[:]
        self.log_messages.clear()
        return result

    def cleanup(self):
        for device in self.devices:
            if device.backend not in (Backends.none, Backends.network):
                device.cleanup()

            if device is not None:
                device.free_data()

    def _start_simulation_thread(self):
        self.simulation_thread = threading.Thread(target=self.simulate)
        self.simulation_thread.daemon = True
        self.simulation_thread.start()

    def simulation_is_finished(self):
        if self.project_manager.simulator_num_repeat == 0:
            return False

        return self.current_repeat >= self.project_manager.simulator_num_repeat

    def __wait_for_devices(self):
        for i in range(10):
            if (self.sniffer is None or self.sniffer_ready) and (self.sender is None or self.sender_ready):
                return True
            if self.fatal_device_error_occurred:
                return False
            self.log_message("<i>Waiting for devices</i>")
            time.sleep(1)

        return True

    def __fill_counter_values(self, command: str):
        result = []
        regex = "(item[0-9]+\.counter_value)"
        for token in re.split(regex, command):
            if re.match(regex, token) is not None:
                try:
                    result.append(str(self.simulator_config.item_dict[token].value))
                except (KeyError, ValueError, AttributeError):
                    logger.error("Could not get counter value for " + token)
            else:
                result.append(token)

        return "".join(result)

    def simulate(self):
        self.simulation_started.emit()
        self.is_simulating = self.__wait_for_devices()

        if not self.is_simulating:
            # Simulation may have ended due to device errors
            self.stop("Devices not ready")
            return

        self.log_message("<b>Simulation is running</b>")

        while self.is_simulating and not self.simulation_is_finished():
            if self.current_item is self.simulator_config.rootItem:
                next_item = self.current_item.next()

            elif isinstance(self.current_item, SimulatorProtocolLabel):
                next_item = self.current_item.next()

            elif isinstance(self.current_item, SimulatorMessage):
                self.process_message()
                next_item = self.current_item.next()

            elif isinstance(self.current_item, SimulatorGotoAction):
                next_item = self.current_item.target
                self.log_message("GOTO item " + next_item.index())

            elif isinstance(self.current_item, SimulatorTriggerCommandAction):
                next_item = self.current_item.next()
                command = self.__fill_counter_values(self.current_item.command)
                self.log_message("Calling {}".format(command))
                if self.current_item.pass_transcript:
                    transcript = "\n".join(self.transcript.get_for_all_participants(all_rounds=False))
                    result, rc = util.run_command(command, transcript, use_stdin=True, return_rc=True)
                else:
                    result, rc = util.run_command(command, param=None, detailed_output=True, return_rc=True)
                self.current_item.return_code = rc
                self.log_message(result)

            elif isinstance(self.current_item, SimulatorRule):
                condition = self.current_item.get_first_applying_condition()

                if condition is not None and condition.logging_active and condition.type != ConditionType.ELSE:
                    self.log_message("Rule condition " + condition.index() + " (" + condition.condition + ") applied")

                if condition is not None and condition.child_count() > 0:
                    next_item = condition.children[0]
                else:
                    next_item = self.current_item.next_sibling()

            elif isinstance(self.current_item, SimulatorRuleCondition):
                if self.current_item.type == ConditionType.IF:
                    next_item = self.current_item.parent()
                else:
                    next_item = self.current_item.parent().next_sibling()

            elif isinstance(self.current_item, SimulatorSleepAction):
                self.log_message(self.current_item.caption)
                time.sleep(self.current_item.sleep_time)
                next_item = self.current_item.next()

            elif isinstance(self.current_item, SimulatorCounterAction):
                self.current_item.progress_value()
                self.log_message("Increase counter by {} to {}".format(self.current_item.step, self.current_item.value))
                next_item = self.current_item.next()

            elif self.current_item is None:
                self.current_repeat += 1
                next_item = self.simulator_config.rootItem
                self.transcript.start_new_round()

            else:
                raise ValueError("Unknown action {}".format(type(self.current_item)))

            self.current_item = next_item

            if self.do_restart:
                self.restart()

        self.stop(msg="Finished")

    def process_message(self):
        assert isinstance(self.current_item, SimulatorMessage)
        msg = self.current_item

        if msg.source is None:
            return

        new_message = self.generate_message_from_template(msg)

        if msg.source.simulate:
            # we have to send a message
            sender = self.sender
            if sender is None:
                self.log_message("Fatal: No sender configured")
                return

            for lbl in new_message.message_type:
                if isinstance(lbl.label, ChecksumLabel):
                    checksum = lbl.label.calculate_checksum_for_message(new_message, use_decoded_bits=False)
                    label_range = new_message.get_label_range(lbl=lbl.label, view=0, decode=False)
                    start, end = label_range[0], label_range[1]
                    new_message.plain_bits[start:end] = checksum + array.array("B", [0] * (
                            (end - start) - len(checksum)))

            self.transcript.append(msg.source, msg.destination, new_message, msg.index())
            self.send_message(new_message, msg.repeat, sender, msg.modulator_index)
            self.log_message("Sending message " + msg.index())
            self.log_message_labels(new_message)
            msg.send_recv_messages.append(new_message)
            self.last_sent_message = msg
        else:
            # we have to receive a message
            self.log_message("<i>Waiting for message {}</i>".format(msg.index()))
            sniffer = self.sniffer
            if sniffer is None:
                self.log_message("Fatal: No sniffer configured")
                return

            retry = 0

            max_retries = self.project_manager.simulator_retries
            while self.is_simulating and not self.simulation_is_finished() and retry < max_retries:
                received_msg = self.receive_message(sniffer)

                if not self.is_simulating:
                    return

                if received_msg is None:
                    if self.project_manager.simulator_error_handling_index == 0:
                        self.resend_last_message()
                        retry += 1
                        continue
                    elif self.project_manager.simulator_error_handling_index == 1:
                        self.stop()
                        return
                    elif self.project_manager.simulator_error_handling_index == 2:
                        self.do_restart = True
                        return

                received_msg.decoder = new_message.decoder
                received_msg.message_type = new_message.message_type

                check_result, error_msg = self.check_message(received_msg, new_message, retry=retry,
                                                             msg_index=msg.index())

                if check_result:
                    decoded_msg = Message(received_msg.decoded_bits, 0,
                                          received_msg.message_type, decoder=received_msg.decoder)
                    msg.send_recv_messages.append(decoded_msg)
                    self.transcript.append(msg.source, msg.destination, decoded_msg, msg.index())
                    self.log_message("Received message " + msg.index() + ": ")
                    self.log_message_labels(decoded_msg)
                    return
                elif self.verbose:
                    self.log_message(error_msg)

                retry += 1

            if retry == self.project_manager.simulator_retries:
                self.log_message("Message " + msg.index() + " not received")
                self.stop()

    def log_message(self, message):
        timestamp = '{0:%b} {0.day} {0:%H}:{0:%M}:{0:%S}.{0:%f}'.format(datetime.datetime.now())

        if isinstance(message, list) and len(message) > 0:
            self.log_messages.append(timestamp + ": " + message[0])
            self.log_messages.extend(message[1:])
            logger.debug("\n".join(message))
        else:
            self.log_messages.append(timestamp + ": " + message)
            logger.debug(message)

    def check_message(self, received_msg, expected_msg, retry: int, msg_index: int) -> (bool, str):
        if len(received_msg.decoded_bits) == 0:
            return False, "Failed to decode message {}".format(msg_index)

        for lbl in received_msg.message_type:
            if lbl.value_type_index in (1, 4):
                # get live, random
                continue

            start_recv, end_recv = received_msg.get_label_range(lbl.label, 0, True)
            start_exp, end_exp = expected_msg.get_label_range(lbl.label, 0, False)

            if isinstance(lbl.label, ChecksumLabel):
                expected = lbl.label.calculate_checksum_for_message(received_msg, use_decoded_bits=True)
                start, end = received_msg.get_label_range(lbl.label, 0, True)
                actual = received_msg.decoded_bits[start:end]
            else:
                actual = received_msg.decoded_bits[start_recv:end_recv]
                expected = expected_msg[start_exp:end_exp]

            if actual != expected:
                log_msg = []
                log_msg.append("Attempt for message {} [{}/{}]".format(msg_index, retry + 1,
                                                                       self.project_manager.simulator_retries))
                log_msg.append(HTMLFormatter.indent_string("Mismatch for label: <b>{}</b>".format(lbl.name)))
                expected_str = util.convert_bits_to_string(expected, lbl.label.display_format_index)
                got_str = util.convert_bits_to_string(actual, lbl.label.display_format_index)
                log_msg.append(HTMLFormatter.align_expected_and_got_value(expected_str, got_str, align_depth=2))
                return False, log_msg

        return True, ""

    def log_message_labels(self, message: Message):
        message.split(decode=False)
        for lbl in message.message_type:
            if not lbl.logging_active:
                continue

            try:
                data = message.plain_bits[lbl.start:lbl.end]
            except IndexError:
                return None

            lsb = lbl.display_bit_order_index == 1
            lsd = lbl.display_bit_order_index == 2

            data = util.convert_bits_to_string(data, lbl.display_format_index, pad_zeros=True, lsb=lsb, lsd=lsd)
            if data is None:
                continue

            log_msg = lbl.name + ": " + HTMLFormatter.monospace(data)
            self.log_messages.append(HTMLFormatter.indent_string(log_msg))

    def resend_last_message(self):
        self.log_message("Resending last message")
        lsm = self.last_sent_message

        if lsm is None:
            return

        sender = self.sender
        self.send_message(lsm.send_recv_messages[-1], lsm.repeat, sender, lsm.modulator_index)

    def send_message(self, message, repeat, sender, modulator_index):
        modulator = self.modulators[modulator_index]
        modulated = modulator.modulate(message.encoded_bits, pause=message.pause)

        curr_repeat = 0

        while curr_repeat < repeat:
            sender.push_data(modulated)
            curr_repeat += 1

    def receive_message(self, sniffer):
        if len(sniffer.messages) > 0:
            return sniffer.messages.pop(0)

        spy = QSignalSpy(sniffer.message_sniffed)
        if spy.wait(self.project_manager.simulator_timeout_ms):
            try:
                return sniffer.messages.pop(0)
            except IndexError:
                self.log_message("Could not receive message")
                return None
        else:
            self.log_message("Receive timeout")
            return None

    def get_full_transcript(self, start=0, use_bit=True):
        result = []
        for source, destination, msg, msg_index in self.transcript[start:]:
            try:
                data = msg.plain_bits_str if use_bit else msg.plain_hex_str
                result.append(self.TRANSCRIPT_FORMAT.format(msg_index, source.shortname, destination.shortname, data))
            except AttributeError:
                result.append("")
        return result

    def generate_message_from_template(self, template_msg: SimulatorMessage):
        new_message = Message(template_msg.plain_bits, pause=template_msg.pause, rssi=0,
                              message_type=template_msg.message_type, decoder=template_msg.decoder)

        for lbl in template_msg.children:  # type: SimulatorProtocolLabel
            if lbl.value_type_index == 2:
                # formula
                valid, _, node = self.expression_parser.validate_expression(lbl.formula)
                assert valid
                result = self.expression_parser.evaluate_node(node)
            elif lbl.value_type_index == 3:
                transcript = self.transcript.get_for_participant(template_msg.source
                                                                 if template_msg.source.simulate
                                                                 else template_msg.destination)

                if template_msg.destination.simulate:
                    direction = "->" if template_msg.source.simulate else "<-"
                    transcript += "\n" + direction + new_message.plain_bits_str + "\n"

                cmd = self.__fill_counter_values(lbl.external_program)
                result = util.run_command(cmd, transcript, use_stdin=True)
                if len(result) != lbl.end - lbl.start:
                    log_msg = "Result value of external program {}: {} ({}) does not match label length {}"
                    logger.error(log_msg.format(cmd, result, len(result), lbl.end - lbl.start))
                    continue

                try:
                    new_message[lbl.start:lbl.end] = array.array("B", (map(bool, map(int, result))))
                except Exception as e:
                    log_msg = "Could not assign {} to range because {}".format(result, e)
                    logger.error(log_msg)

                continue
            elif lbl.value_type_index == 4:
                # random value
                result = numpy.random.randint(lbl.random_min, lbl.random_max + 1)
            else:
                continue

            self.set_label_value(new_message, lbl, result)

        return new_message

    @staticmethod
    def set_label_value(message, label, decimal_value: int):
        lbl_len = label.end - label.start
        f_string = "{0:0" + str(lbl_len) + "b}"
        bits = f_string.format(decimal_value)

        if len(bits) > lbl_len:
            logger.warning("Value {0} too big for label {1}, bits truncated".format(decimal_value, label.name))

        for i in range(lbl_len):
            message[label.start + i] = bool(int(bits[i]))