def config(self, byte_stream: BytesIO, header: Header):
        """

        :param byte_stream:
        :type byte_stream:
        :param header:
        :type header:
        :return:
        :rtype:
        """

        data = {}
        data[DataEntryIds.IS_SIM] = bool(byte_stream.read(1)[0])
        data[DataEntryIds.DEVICE_TYPE] = ID_TO_DEVICE_TYPE[byte_stream.read(1)
                                                           [0]]
        version_id = byte_stream.read(VERSION_ID_LEN)
        data[DataEntryIds.VERSION_ID] = version_id.decode('ascii')

        LOGGER.info("Config: SIM? %s, Rocket type = %s, Version ID = %s",
                    str(data[DataEntryIds.IS_SIM]),
                    str(data[DataEntryIds.DEVICE_TYPE]),
                    str(data[DataEntryIds.VERSION_ID]))

        CONFIG_EVENT.increment()
        return data
    def extract(self, byte_stream: BytesIO):
        """
        Return dict of parsed subpacket data and length of subpacket

        :param byte_stream:
        :type byte_stream:
        :return: parsed_data
        :rtype: Dict[Any, Any]
        """
        if self.big_endian_ints is None or self.big_endian_floats is None:
            raise Exception("Endianness not set before parsing")

        # header extraction
        header: Header = self.header(byte_stream)

        # data extraction
        parsed_data: Dict[Any, Any] = {}
        try:
            parsed_data = self.parse_data(byte_stream, header)
        except Exception as e:
            LOGGER.exception("Error parsing data"
                             )  # Automatically grabs and prints exception info

        parsed_data[DataEntryIds.TIME] = header.timestamp

        self.big_endian_ints = None
        self.big_endian_floats = None
        return parsed_data
示例#3
0
    def _handleBuzzer(self):
        length = self._getLength()
        assert length == 1
        data = self.stdout.read(length)

        songType = int(data[0])
        LOGGER.info(f"SIM: Bell rang with song type {songType} (device_address={self.device_address})")
示例#4
0
    def __init__(self, p1: MapPoint, p2: MapPoint, s: int):
        """

        :param p1:
        :type p1: MapPoint
        :param p2:
        :type p2: MapPoint
        :param s:
        :type s: int
        """
        self.p1 = p1
        self.p2 = p2
        self.scale = min(s, 18)
        self.ta = []
        self.xMin = None
        self.xMax = None
        self.yMin = None
        self.yMax = None
        self.tile_x_min = None
        self.tile_x_max = None
        self.tile_y_min = None
        self.tile_y_max = None
        self.width = None
        self.height = None
        self.genTileArray()
        if self.width > 5 or self.height > 5:
            LOGGER.warning(f"Large map ({self.width}x{self.height} tiles)")
    def _run(self) -> None:
        """

        """
        with self.cv:
            LOGGER.debug(
                f"Debug connection thread started (device_address={self.device_address})"
            )
            while True:
                self.cv.wait_for(lambda: self._is_shutting_down,
                                 timeout=PACKET_INTERVAL_S)

                if self._is_shutting_down:
                    break

                if not self.callback:
                    continue

                full_arr: bytearray = bytearray()
                # full_arr.extend(self.config_mock_set_values())
                # full_arr.extend(self.message_mock_set_values())
                full_arr.extend(self.bulk_sensor_mock_random())
                # full_arr.extend(self.bad_subpacket_id_mock()) # bad id, to see handling of itself and remaining data
                full_arr.extend(self.gps_mock_random())
                full_arr.extend(self.orientation_mock_random())
                self.receive(full_arr)

            LOGGER.warning(
                f"Debug connection thread shut down (device_address={self.device_address})"
            )
示例#6
0
    def downloadArrayImages(self,
                            attempts: int = 3,
                            overwrite: bool = False) -> None:
        """

        :param attempts:
        :type attempts: int
        :param overwrite:
        :type overwrite: bool
        """
        LOGGER.debug(f"Beginning download of size {str(self.scale)} tiles.")
        t1 = time.perf_counter()

        def getRow(row):
            for i in row:
                a = attempts
                while (not i.imageExists()) and (a > 0):
                    i.getImage(overwrite=overwrite)
                    a = a - 1

        with concurrent.futures.ThreadPoolExecutor() as executor:
            executor.map(getRow, self.ta)
        t2 = time.perf_counter()
        LOGGER.debug(
            f"Successfully downloaded size {str(self.scale)} tiles in {t2 - t1} seconds."
        )
示例#7
0
    def _handlePinMode(self):
        length = self._getLength()
        assert length == 2
        pin, mode = self.stdout.read(2)

        self._hw_sim.set_pin_mode(pin, mode)
        LOGGER.info(f"SIM: Mode for pin {pin} set to {mode} (device_address={self.device_address})")
示例#8
0
 def set_pin_mode(self, pin, mode):
     """
     :param pin: Should be a test pin
     :param mode: The mode you want to set the pin to -> 0 for INPUT and 1 for OUTPUT
     """
     with self._lock:
         self._pin_modes[pin] = mode
         LOGGER.debug(f"Pin mode of pin={pin} set to={mode}")
示例#9
0
    def launch(self) -> None:
        assert self.get_flight_state() == FlightState.STANDBY

        self._state = FlightState.FLIGHT
        self._launch_time = self.get_time()

        LOGGER.info(
            f"Rocket launched at time {self.get_time()} s, and altitude {self.get_data(FlightDataType.TYPE_ALTITUDE)} m")
示例#10
0
 def get_pin_mode(self, pin):
     """
     :param pin: Should be a test pin
     """
     with self._lock:
         val = self._pin_modes[pin]
         LOGGER.debug(f"Pin mode read from pin={pin} returned value={val}")
         return val
示例#11
0
    def _handleRadio(self):
        length = self._getLength()

        if length == 0:
            LOGGER.warning(f"Empty SIM radio packet received (device_address={self.device_address})")

        data = self.stdout.read(length)
        self._xbee.recieved_from_rocket(data)
示例#12
0
    def _handleDigitalPinWrite(self):
        length = self._getLength()
        assert length == 2
        pin, value = self.stdout.read(2)
        self._hw_sim.set_pin_mode(pin, PinModes.INPUT)

        self._hw_sim.digital_write(pin, value)
        LOGGER.info(f"SIM: Pin {pin} set to {value} (device_address={self.device_address})")
    def _parse_tx_request_frame(self, data, frame_len) -> None:
        """
        :brief: Parses a TX Request frame, and passes a TX Status packet to the rocket.
        :param data: Iterable
        :param frame_len: length as defined in XBee protocol
        """
        calculated_checksum = FrameType.TX_REQUEST.value  # Checksum includes frame type

        frame_id = next(data)
        calculated_checksum += frame_id

        destination_address = bytearray()
        for _ in range(8):  # 64 bit destination address
            b = next(data)
            destination_address.append(b)
            calculated_checksum += b

        # Reserved 2 bytes. But in one case it's labelled as network address?
        network_addr_msb = next(data)
        calculated_checksum += network_addr_msb

        network_addr_lsb = next(data)
        calculated_checksum += network_addr_lsb

        broadcast_radius = next(data)  # Broadcast radius - not used
        calculated_checksum += broadcast_radius

        transmit_options = next(data)
        calculated_checksum += transmit_options

        payload = bytearray()
        for _ in range(frame_len - 14):
            b = next(data)
            payload.append(b)
            calculated_checksum += b

        received_checksum = next(data)
        calculated_checksum = 0xFF - (calculated_checksum & 0xFF
                                      )  # As per XBee's spec

        if received_checksum != calculated_checksum:
            raise ChecksumMismatchError()

        if destination_address == bytearray(
                self.gs_address) or destination_address == bytearray(
                    XBEE_BROADCAST_ADDRESS):
            self.ground_callback(payload)
        else:
            LOGGER.warning(
                f"Discarding tx request frame with destination address other than GS ({destination_address.hex()})"
            )

        # Send acknowledge
        status_payload = bytearray(
            (frame_id, network_addr_msb, network_addr_lsb, 0, 0, 0))
        self.rocket_callback(
            self._create_frame(FrameType.TX_STATUS, status_payload))
    def __init__(self, connections: Dict[str, Connection], rocket_profile: RocketProfile) -> None:
        """

        :param connections:
        :param rocket_profile:
        """
        super().__init__(connections, rocket_profile)

        self.map_data = map_data.MapData()
        self.im = None  # Plot im

        self.command_history = []
        self.command_history_index = None

        # Attach functions for static buttons
        self.sendButton.clicked.connect(self.send_button_pressed)
        self.actionSave.triggered.connect(self.save_file)
        self.actionSave.setShortcut("Ctrl+S")
        self.actionReset.triggered.connect(self.reset_view)

        # Hook into some of commandEdit's events
        qtHook(self.commandEdit, 'focusNextPrevChild', lambda _: False, override_return=True)  # Prevents tab from changing focus
        qtHook(self.commandEdit, 'keyPressEvent', self.command_edit_key_pressed)
        qtHook(self.commandEdit, 'showPopup', self.command_edit_show_popup)

        # Hook-up logger to UI text output
        # Note: Currently doesn't print logs from other processes (e.g. mapping process)
        log_format = logging.Formatter("[%(asctime)s] (%(levelname)s) %(message)s")
        log_handler = qtSignalLogHandler(exception_traces=False)
        log_handler.setLevel(logging.INFO)
        log_handler.setFormatter(log_format)
        log_handler.qt_signal.connect(self.print_to_ui)
        LOGGER.addHandler(log_handler)

        # Setup dynamic UI elements
        self.setup_buttons()
        self.setup_labels()
        self.setup_subwindow().showMaximized()
        self.setup_view_menu()

        self.setWindowIcon(QtGui.QIcon(mapbox_utils.MARKER_PATH))

        # Setup user window preferences
        self.original_geometry = self.saveGeometry()
        self.original_state = self.saveState()
        self.settings = QtCore.QSettings('UBCRocket', 'UBCRGS')
        self.restore_view()

        # Init and connection of MappingThread
        self.MappingThread = MappingThread(self.connections, self.map_data, self.rocket_data, self.rocket_profile)
        self.MappingThread.sig_received.connect(self.receive_map)
        self.MappingThread.start()

        LOGGER.info(f"Successfully started app (version = {GIT_HASH})")
示例#15
0
 def digital_write(self, pin, val):
     """
     :param pin: Should be a test pin.
     :param val: True to set high, False to set low
     """
     with self.lock:
         LOGGER.debug(f"Digital write to pin={pin} with value value={val}")
         if pin in self._ignitor_tests:
             self._ignitor_tests[pin].write(val)
         elif pin in self._ignitor_fires and val:
             self._ignitor_fires[pin].fire()
示例#16
0
    def analog_read(self, pin):
        """
        :param pin: Should be a read pin. Don't rely on behaviour if the pin isn't a readable pin.
        """
        with self.lock:
            val = 0
            if pin in self._ignitor_reads:
                val = self._ignitor_reads[pin].read()

            LOGGER.debug(f"Analog read from pin={pin} returned value={val}")
            return val
 def broadcast(self, data) -> None:  # must be thead safe
     LOGGER.info(
         f"{data} sent to address={self.device_address} on DebugConnection")
     with self.lock:
         if data == bytes([0x41]):
             ARMED_EVENT.increment()
         elif data == bytes([0x44]):
             DISARMED_EVENT.increment()
         elif data == bytes([0x50]):
             self.receive(self.status_ping_mock_set_values())
         elif data == bytes([0x43]):
             self.receive(self.config_mock_set_values())
示例#18
0
    def _getEndianness(self):
        id = self.stdout.read(1)[0]
        assert id == SimRxId.CONFIG.value

        length = self._getLength()
        assert length == 8
        data = self.stdout.read(length)

        self.bigEndianInts = data[0] == 0x04
        self.bigEndianFloats = data[4] == 0xC0

        LOGGER.info(
            f"SIM: Big Endian Ints - {self.bigEndianInts}, Big Endian Floats - {self.bigEndianFloats} (device_address={self.device_address})"
        )
    def timer(self):
        """

        """
        LOGGER.debug("Auto-save thread started")

        while True:

            with self.as_cv:
                self.as_cv.wait_for(lambda: self._as_is_shutting_down,
                                    timeout=AUTOSAVE_INTERVAL_S)

                if self._as_is_shutting_down:
                    break

            try:
                t1 = time.perf_counter()
                self.save(self.session_name)
                t2 = time.perf_counter()
                # LOGGER.debug("Auto-Save Successful.")
                LOGGER.debug(f"Successfully Auto-Saved in {t2 - t1} seconds.")
            except Exception as e:
                LOGGER.exception(
                    "Exception in autosave thread"
                )  # Automatically grabs and prints exception info

        LOGGER.warning("Auto save thread shut down")
示例#20
0
    def receive_data(self) -> None:
        """
        This is called when new data is available to be displayed.
        :return:
        :rtype:
        """

        for label in self.rocket_profile.labels:
            try:
                getattr(self, label.name + "Label").setText(
                    label.update(self.rocket_data))
            except:
                LOGGER.exception(f'Failed to update {label.name}Label:')

        LABLES_UPDATED_EVENT.increment()
    def setViewedDevice(self, devices) -> None:
        """

        :param devices
        :type devices: list of rocket devices
        """
        with self.cv:
            self.viewed_devices = devices
            for device in self.viewed_devices:
                if self.rocket_data.last_value_by_device(
                        device, DataEntryIds.LATITUDE) is None:
                    LOGGER.warning("Data unavailable for {}".format(
                        device.name))

            self.notify()
    def __init__(self, connections: Dict[str, Connection], rocket_profile: RocketProfile) -> None:
        """

        :param connection:
        :type connection: Connection
        :param rocket_profile:
        :type rocket_profile: RocketProfile
        """
        super().__init__(connections, rocket_profile)

        self.map = map_data.MapData()

        self.im = None  # Plot im

        # Attach functions for static buttons
        self.sendButton.clicked.connect(self.send_button_pressed)
        self.commandEdit.returnPressed.connect(self.send_button_pressed)
        self.actionSave.triggered.connect(self.save_file)
        self.actionSave.setShortcut("Ctrl+S")
        self.actionReset.triggered.connect(self.reset_view)

        # Hook-up logger to UI text output
        # Note: Currently doesnt print logs from other processes (e.g. mapping process)
        log_format = logging.Formatter("[%(asctime)s] (%(levelname)s) %(message)s")
        log_handler = qtSignalLogHandler(exception_traces=False)
        log_handler.setLevel(logging.INFO)
        log_handler.setFormatter(log_format)
        log_handler.qt_signal.connect(self.print_to_ui)
        LOGGER.addHandler(log_handler)

        self.setup_buttons()
        self.setup_labels()
        self.setup_subwindow().showMaximized()

        self.setWindowIcon(QtGui.QIcon(mapbox_utils.MARKER_PATH))

        self.original_geometry = self.saveGeometry()
        self.original_state = self.saveState()
        self.settings = QtCore.QSettings('UBCRocket', 'UBCRGS')
        self.restore_view()

        # Init and connection of MappingThread
        self.MappingThread = MappingThread(self.connections, self.map, self.rocket_data, self.rocket_profile)
        self.MappingThread.sig_received.connect(self.receive_map)
        self.MappingThread.start()

        LOGGER.info(f"Successfully started app (version = {GIT_HASH})")
    def state(self, byte_stream: BytesIO, header: Header, print_state=True):
        """

        :param byte_stream:
        :param header:
        :return:
        """
        data = {}
        state_id = self.bytestoint(byte_stream.read(2))
        data_entry_value = STATE_IDS[state_id]
        data[DataEntryIds.STATE] = data_entry_value

        if print_state:
            LOGGER.info("State: %s", str(data_entry_value.name))

        STATE_EVENT.increment()
        return data
示例#24
0
    def analog_read(self, pin):
        """
        :param pin: Should be a read pin. Don't rely on behaviour if the pin isn't a readable pin.
        """
        with self._lock:
            val = 0
            if pin in self._ignitor_reads:
                val = self._ignitor_reads[pin].read()
                LOGGER.debug(
                    f"Analog read from pin={pin} returned value={val}")

            else:
                voltage_sensor = self._sensors[SensorType.VOLTAGE]
                if voltage_sensor is not None and pin == voltage_sensor.pin:
                    val = self._sensors[SensorType.VOLTAGE].read()

            return val
    def event(self, byte_stream: BytesIO, header: Header):
        """

        :param byte_stream:
        :type byte_stream:
        :param header:
        :type header:
        :return:
        """
        data: Dict = {}
        event_bytes = byte_stream.read(2)
        event_int = self.bytestoint(event_bytes)
        data_entry_value = EVENT_IDS[event_int]
        data[DataEntryIds.EVENT] = data_entry_value

        LOGGER.info("Event: %s", str(data_entry_value.name))
        EVENT_EVENT.increment()
        return data
    def register_device(self, device_type: DeviceType, device_version: str,
                        full_address: FullAddress) -> None:
        with self._lock:
            if device_type not in self._device_type_to_device and full_address not in self._full_address_to_device:
                pass

            elif device_type in self._device_type_to_device and full_address != self._device_type_to_device[
                    device_type].full_address:
                raise InvalidRegistration(
                    f"Cannot reassign device_type={device_type.name} (full_address={self._device_type_to_device[device_type].full_address}) to full_address={full_address}"
                )

            elif full_address in self._full_address_to_device and device_type != self._full_address_to_device[
                    full_address].device_type:
                raise InvalidRegistration(
                    f"Cannot reassign full_address={full_address} (device={self._full_address_to_device[full_address].device_type}) to device={device_type}"
                )

            else:
                LOGGER.info(
                    f"Already registered. Device={device_type.name}, full_address={full_address}"
                )
                return

            if device_type in self.required_versions and device_version != self.required_versions[
                    device_type]:
                error_str = f"Version {device_version} does not match required version {self.required_versions[device_type]} for device {device_type.name}"
                if self.strict_versions:
                    raise InvalidDeviceVersion(error_str)
                else:
                    LOGGER.warning(error_str)

            self._device_type_to_device[device_type] = RegisteredDevice(
                device_type=device_type, full_address=full_address)
            self._full_address_to_device = {
                d.full_address: d
                for d in self._device_type_to_device.values()
            }

            # If two devices somehow have the same full address, mapping wont be one-to-one.
            assert len(self._device_type_to_device) == len(
                self._full_address_to_device)

            if device_type in self.expected_devices:
                LOGGER.info(
                    f"Registered expected device={device_type.name}, full_address={full_address}, {self.num_expected_registered()}/{len(self.expected_devices)} expected devices"
                )
            else:
                LOGGER.warning(
                    f"Registered unexpected device={device_type.name}, full_address={full_address}, {self.num_expected_registered()}/{len(self.expected_devices)} expected devices"
                )

            DEVICE_REGISTERED_EVENT.increment()
示例#27
0
    def _deploy_recovery(self):
        if self.get_flight_state() == FlightState.DROGUE_DESCENT: # Drogue deploying
            self._drogue_deployment_time = self.get_time_since_launch()
        elif self.get_flight_state() == FlightState.MAIN_DESCENT: # Main deploying
            self._main_deployment_time = self.get_time_since_launch()
        else:
            assert False

        new_data, new_events = self._run_simulation()

        assert abs(new_events[FlightEvent.RECOVERY_DEVICE_DEPLOYMENT][-1] - self.get_time_since_launch()) < 0.1

        merged_events = dict()

        # Retain original events if already passed
        for event, times in self._events.items():
            for i in range(len(times)):
                time = times[i]
                if time <= self.get_time_since_launch():

                    if i in range(len(new_events[event])):
                        assert abs(new_events[event][i] - time) < 0.5 # TODO: Figure out where slight discrepancies come from

                    if event in merged_events:
                        merged_events[event].append(time)
                    else:
                        merged_events[event] = [time]

        # Update with new events
        for event, times in new_events.items():
            for time in times:
                if time >= new_events[FlightEvent.RECOVERY_DEVICE_DEPLOYMENT][-1]:
                    if event in merged_events:
                        merged_events[event].append(time)
                    else:
                        merged_events[event] = [time]

        self._data = new_data
        self._events = merged_events

        LOGGER.info(
            f"Recovery deployed at time {self.get_time()} s, and altitude {self.get_data(FlightDataType.TYPE_ALTITUDE)} m")
    def message(self, byte_stream: BytesIO, header: Header):
        """
        Save and log an extracted message.

        :param byte_stream:
        :type byte_stream:
        :param header:
        :type header:
        :return:
        :rtype:
        """
        data: Dict = {}
        length: int = byte_stream.read(1)[0]

        # Two step: immutable int[] -> string
        byte_data = byte_stream.read(length)
        data[DataEntryIds.MESSAGE] = byte_data.decode('ascii')

        # Do something with data
        LOGGER.info("Incoming message: " + str(data[DataEntryIds.MESSAGE]))
        return data
    def header(self, byte_stream: BytesIO) -> Header:
        """
        Header extractor helper.

        :param byte_stream:
        :type byte_stream:
        :return:
        :rtype:
        """
        # Get ID
        subpacket_id: int = byte_stream.read(1)[0]

        # check that id is valid:
        if subpacket_id not in self.packet_type_to_parser:
            LOGGER.error("Subpacket id %d not valid.", subpacket_id)
            raise ValueError("Subpacket id " + str(subpacket_id) + " not valid.")

        # Get timestamp
        timestamp: int = self.bytestoint(byte_stream.read(4))

        return Header(subpacket_id, timestamp)
    def __init__(self, connections: Dict[str, Connection],
                 rocket_profile: RocketProfile) -> None:
        """

        :param connection:
        :type connection: Connection
        :param rocket_profile:
        :type rocket_profile: RocketProfile
        """
        # Prints constructor arguments, leave at top of constructor
        LOGGER.debug(f"Starting MainApp with {locals()}")

        if connections is None:
            raise Exception("Invalid connections provided")

        QtWidgets.QMainWindow.__init__(self)
        self.setupUi(self)

        self.connections = connections
        self.rocket_profile = rocket_profile
        self.device_manager = DeviceManager(
            self.rocket_profile.expected_devices,
            self.rocket_profile.required_device_versions,
            strict_versions=False)
        self.rocket_data = RocketData(self.device_manager)
        self.command_parser = CommandParser(self.device_manager)

        packet_parser = self.rocket_profile.construct_packet_parser()

        # Init and connection of ReadThread
        self.ReadThread = ReadThread(self.connections, self.rocket_data,
                                     packet_parser, self.device_manager)
        self.ReadThread.sig_received.connect(self.receive_data)
        self.ReadThread.start()

        # Init and connection of SendThread
        self.SendThread = SendThread(self.connections, self.device_manager,
                                     self.command_parser)
        self.sig_send.connect(self.SendThread.queueMessage)
        self.SendThread.start()