コード例 #1
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)")
コード例 #2
0
    def _run(self):
        LOGGER.debug(
            f"SIM connection started (device_address={self.device_address})")

        try:
            while True:

                id = self.stdout.read(1)[0]  # Returns 0 if process was killed

                if id not in SimConnection.packetHandlers.keys():
                    LOGGER.error(
                        f"SIM protocol violation!!! Shutting down. (device_address={self.device_address})"
                    )
                    for b in self.stdout.getHistory():
                        LOGGER.error(hex(b[0]))
                    LOGGER.error("^^^^ violation.")
                    return

                # Call packet handler
                SimConnection.packetHandlers[id](self)

        except Exception as ex:
            with self._shutdown_lock:
                if not self._is_shutting_down:
                    LOGGER.exception(
                        f"Error in SIM connection. (device_address={self.device_address})"
                    )

        LOGGER.warning(
            f"SIM connection thread shut down (device_address={self.device_address})"
        )
コード例 #3
0
    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")
コード例 #4
0
    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})"
            )
コード例 #5
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)
コード例 #6
0
    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))
コード例 #7
0
    def run(self):
        """

        """
        LOGGER.debug("Send thread started")

        # TODO : Once we have multiple connections, we will loop over and send a config request to each
        # Starting up, request hello/ha ndshake/identification
        for connection in self.connections.values():
            try:
                connection.broadcast(self.command_parser.broadcast_data(CommandType.CONFIG))
            except Exception as ex:
                LOGGER.exception("Exception in send thread while sending config requests")

        while True:
            try:
                message = self.commandQueue.get(block=True, timeout=None)  # Block until something new
                self.commandQueue.task_done()

                if message is None:  # Either received None or woken up for shutdown
                    with self._shutdown_lock:
                        if self._is_shutting_down:
                            break
                        else:
                            continue

                try:
                    (device, command, data) = self.command_parser.pase_command(message)
                except CommandParsingError as ex:
                    LOGGER.error(f"Error parsing command: {str(ex)}")
                    continue

                full_address = self.device_manager.get_full_address(device)
                if full_address is None:
                    LOGGER.error(f"Device not yet connected: {device.name}")
                    continue

                connection = self.connections[full_address.connection_name]

                LOGGER.info(f"Sending command {command.name} to device {device.name} ({full_address})")

                connection.send(full_address.device_address, data)

                LOGGER.info("Sent command!")
                COMMAND_SENT_EVENT.increment()

            except TimeoutException:  # TODO: Connection should have converted this to a generic exception for decoupling
                LOGGER.error("Message timed-out!")

            except queue.Empty:
                pass

            except Exception as ex:
                LOGGER.exception("Unexpected error while sending!")  # Automatically grabs and prints exception info

        LOGGER.warning("Send thread shut down")
コード例 #8
0
    def run(self) -> None:
        """

        """
        LOGGER.debug("Mapping thread started")
        last_latitude = None
        last_longitude = None
        last_desired_size = None
        last_update_time = 0
        while True:
            with self.cv:
                self.cv.wait()  # CV lock is released while waiting
                if self._is_shutting_down:
                    break

            try:
                # Prevent update spam
                current_time = time.time()
                if current_time - last_update_time < 0.5:
                    time.sleep(0.5)

                # copy location values to use, to keep the values consistent in synchronous but adjacent calls
                latitude = self.rocket_data.last_value_by_device(
                    self.device, DataEntryIds.LATITUDE)
                longitude = self.rocket_data.last_value_by_device(
                    self.device, DataEntryIds.LONGITUDE)
                desired_size = self.getDesiredMapSize()

                # Prevent unnecessary work while no location data is received
                if latitude is None or longitude is None:
                    continue

                # Prevent unnecessary work while data hasnt changed
                if (latitude, longitude,
                        desired_size) == (last_latitude, last_longitude,
                                          last_desired_size):
                    continue

                if self.plotMap(latitude, longitude, DEFAULT_RADIUS,
                                DEFAULT_ZOOM):
                    # notify UI that new data is available to be displayed
                    self.sig_received.emit()
                else:
                    continue

                last_latitude = latitude
                last_longitude = longitude
                last_update_time = current_time
                last_desired_size = desired_size

            except Exception:
                LOGGER.exception(
                    "Error in map thread loop"
                )  # Automatically grabs and prints exception info

        LOGGER.warning("Mapping thread shut down")
コード例 #9
0
    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()
コード例 #10
0
    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()
コード例 #11
0
    def _run_rocket_rx(self) -> None:
        """
        :brief: Process the incoming rocket data queue.
        This is the top level function, and handles any unescaped start delimiters.
        """
        LOGGER.debug(f"Xbee sim thread started")

        while True:
            try:
                start = next(self._rocket_rx_queue)
                assert start == START_DELIMITER
                self._parse_API_frame()
            except UnescapedDelimiterError:
                LOGGER.warning("Caught UnescapedDelimiterError exception")
                continue  # drop it and try again
            except ShuttingDown:
                break

        LOGGER.warning("Xbee sim thread shut down")
コード例 #12
0
def processMap(requestQueue, resultQueue):
    """To be run in a new process as the stitching and resizing is a CPU bound task

    :param requestQueue:
    :type requestQueue: Queue
    :param resultQueue:
    :type resultQueue: Queue
    """

    # On Windows, process forking does not copy globals and thus all packeges are re-imported. Not for threads
    # though.
    # Note: This means that on Windows the logger will create one log file per process because the session ID
    # is based on the import time
    # https://docs.python.org/3/library/multiprocessing.html#logging
    # TODO: Fix by creating .session file which contains session ID and other
    #  process-global constants. Look into file-locks to make this multiprocessing safe. This is an OS feature

    LOGGER.debug("Mapping process started")
    while True:
        try:
            request = requestQueue.get()

            if request is None:  # Shutdown request
                break

            (p0, p1, p2, zoom, desiredSize) = request

            location = mapbox_utils.TileGrid(p1, p2, zoom)
            location.downloadArrayImages()

            largeMapImage = location.genStitchedMap()
            x_min, x_max, y_min, y_max = location.xMin, location.xMax, location.yMin, location.yMax

            if desiredSize is None:
                resizedMapImage = largeMapImage
            else:

                if desiredSize[0] / desiredSize[1] > abs(p1.x - p2.x) / abs(
                        p1.y - p2.y):  # Wider aspect ratio
                    x_crop_size = (abs(p1.x - p2.x) * largeMapImage.shape[1]
                                   ) / (location.xMax - location.xMin)
                    y_crop_size = (x_crop_size *
                                   desiredSize[1]) / desiredSize[0]
                else:  # Taller aspect ratio
                    y_crop_size = (abs(p1.y - p2.y) * largeMapImage.shape[0]
                                   ) / (location.yMax - location.yMin)
                    x_crop_size = (y_crop_size *
                                   desiredSize[0]) / desiredSize[1]

                center_x = (
                    (p0.x - location.xMin) *
                    largeMapImage.shape[1]) / (location.xMax - location.xMin)
                center_y = (
                    (p0.y - location.yMin) *
                    largeMapImage.shape[0]) / (location.yMax - location.yMin)

                # Crop image centered around p0 (point of interest) and at the desired aspect ratio.
                # Crop is largest possible within rectangle defined by p1 & p2
                x_crop_start = round(center_x - x_crop_size / 2)
                x_crop_end = round(x_crop_start + x_crop_size)
                y_crop_start = round(center_y - y_crop_size / 2)
                y_crop_end = round(y_crop_start + y_crop_size)
                croppedMapImage = largeMapImage[y_crop_start:y_crop_end,
                                                x_crop_start:x_crop_end]

                # Check obtained desired aspect ratio (within one pixel)
                assert abs(x_crop_size / y_crop_size -
                           desiredSize[0] / desiredSize[1]) < 1 / max(
                               croppedMapImage.shape[0:2])
                assert croppedMapImage.shape[1] == round(x_crop_size)
                assert croppedMapImage.shape[0] == round(y_crop_size)

                x_min, x_max, y_min, y_max = min(p1.x, p2.x), max(
                    p1.x, p2.x), min(p1.y, p2.y), max(p1.y, p2.y)

                if croppedMapImage.shape[1] < desiredSize[0]:
                    # Dont scale up the image. Waste of memory.
                    resizedMapImage = croppedMapImage
                else:
                    # Downsizing the map here to the ideal size for the plot reduces the amount of work required in the
                    # main thread and thus reduces stuttering
                    resizedMapImage = np.array(
                        Image.fromarray(croppedMapImage).resize(
                            (desiredSize[0], desiredSize[1]
                             )))  # x,y order is opposite for resize

            resultQueue.put((resizedMapImage, x_min, x_max, y_min, y_max))
        except Exception as ex:
            LOGGER.exception("Exception in processMap process"
                             )  # Automatically grabs and prints exception info
            resultQueue.put(None)

    resultQueue.cancel_join_thread()
    requestQueue.cancel_join_thread()
    resultQueue.close()
    requestQueue.close()
    LOGGER.warning("Mapping process shut down")
コード例 #13
0
    def run(self):
        """

        """
        LOGGER.debug("Send thread started")

        # TODO : Once we have multiple connections, we will loop over and send a config request to each
        # Starting up, request hello/ha ndshake/identification
        for connection in self.connections.values():
            try:
                connection.broadcast(bytes([CommandType.CONFIG.value]))
            except Exception as ex:
                LOGGER.exception(
                    "Exception in send thread while sending config requests")

        while True:
            try:
                message = self.commandQueue.get(
                    block=True, timeout=None)  # Block until something new
                self.commandQueue.task_done()

                if message is None:  # Either received None or woken up for shutdown
                    with self._shutdown_lock:
                        if self._is_shutting_down:
                            break
                        else:
                            continue

                message_parts = message.split('.')

                if len(message_parts) != 2:
                    LOGGER.error("Bad command format")
                    continue

                (device_str, command_str) = message_parts

                try:
                    device = DeviceType[device_str.upper()]
                except KeyError:
                    LOGGER.error(f"Unknown device: {device_str}")
                    continue

                full_address = self.device_manager.get_full_address(device)
                if full_address is None:
                    LOGGER.error(f"Device not yet connected: {device.name}")
                    continue

                connection = self.connections[full_address.connection_name]

                try:
                    command = CommandType[command_str.upper()]
                except KeyError:
                    LOGGER.error(f"Unknown command {command_str}")
                    continue

                LOGGER.info(
                    f"Sending command {command} to device {device.name} ({full_address})"
                )

                data = bytes([command.value])
                connection.send(full_address.device_address, data)

                LOGGER.info("Sent command!")
                COMMAND_SENT_EVENT.increment()

            except TimeoutException:  # TODO: Connection should have converted this to a generic exception for decoupling
                LOGGER.error("Message timed-out!")

            except queue.Empty:
                pass

            except Exception as ex:
                LOGGER.exception(
                    "Unexpected error while sending!"
                )  # Automatically grabs and prints exception info

        LOGGER.warning("Send thread shut down")
コード例 #14
0
    def run(self):
        """This thread loop waits for new data and processes it when available"""
        LOGGER.debug("Read thread started")
        while True:

            connection_message = self.dataQueue.get(
                block=True, timeout=None)  # Block until something new
            self.dataQueue.task_done()

            if connection_message is None:  # Either received None or woken up for shutdown
                with self._shutdown_lock:
                    if self._is_shutting_down:
                        break
                    else:
                        continue

            connection = connection_message.connection
            full_address = FullAddress(
                connection_name=self.connection_to_name[connection],
                device_address=connection_message.device_address)
            data = connection_message.data

            byte_stream: BytesIO = BytesIO(data)

            # Get length of bytes (without using len(data) for decoupling)
            byte_stream.seek(0, SEEK_END)
            end = byte_stream.tell()
            byte_stream.seek(0)

            # Iterate over stream to extract subpackets where possible
            while byte_stream.tell() < end:
                try:
                    self.packet_parser.set_endianness(
                        connection.isIntBigEndian(),
                        connection.isFloatBigEndian())
                    parsed_data: Dict[DataEntryIds,
                                      any] = self.packet_parser.extract(
                                          byte_stream)

                    if DataEntryIds.DEVICE_TYPE in parsed_data and DataEntryIds.VERSION_ID in parsed_data:
                        self.device_manager.register_device(
                            parsed_data[DataEntryIds.DEVICE_TYPE],
                            parsed_data[DataEntryIds.VERSION_ID], full_address)
                    elif DataEntryIds.DEVICE_TYPE in parsed_data:
                        LOGGER.warning(
                            'Received DEVICE_TYPE but not VERSION_ID')
                    elif DataEntryIds.VERSION_ID in parsed_data:
                        LOGGER.warning(
                            'Received VERSION_ID but not DEVICE_TYPE')

                    self.rocket_data.add_bundle(full_address, parsed_data)

                    # notify UI that new data is available to be displayed
                    self.sig_received.emit()
                except Exception as e:
                    LOGGER.exception("Error decoding new packet! %s", e)
                    # Just discard rest of data TODO Review policy on handling remaining data or problem packets. Consider data errors too
                    byte_stream.seek(0, SEEK_END)

            CONNECTION_MESSAGE_READ_EVENT.increment()

        LOGGER.warning("Read thread shut down")