Esempio n. 1
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})"
        )
    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")
Esempio n. 3
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."
        )
    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})"
            )
Esempio n. 5
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
Esempio n. 6
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}")
    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")
Esempio n. 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")
Esempio n. 9
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
Esempio n. 10
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()
Esempio n. 11
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 _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")
    def shutdown(self):
        """
        This is called when the app is being requested to shut down
        :return:
        :rtype:
        """
        LOGGER.debug(f"MainApp shutting down")
        self.ReadThread.shutdown()
        self.SendThread.shutdown()
        self.rocket_data.shutdown()
        for connection in self.connections.values():
            connection.shutdown()
        LOGGER.debug(
            f"All threads shut down, remaining threads: {threading.enumerate()}"
        )

        LOGGER.info("Saving...")
        self.rocket_data.save(
            os.path.join(LOGS_DIR, "finalsave_" + SESSION_ID + ".csv"))
        LOGGER.info("Saved!")
    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()
Esempio n. 15
0
    def doneButtonPressed(self) -> None:
        """

        """
        rocket = self.rocketBox.currentText()
        connection = self.typeBox.currentText()
        baud_rate = int(self.baudBox.currentText())
        com_port = self.comBox.currentText()

        LOGGER.debug(f"User has selected rocket={rocket}, connection={connection}, com_port={com_port}, baud_rate={baud_rate}")

        self.chosen_rocket = self.RocketProfiles[rocket]

        if connection == 'Serial':
            self.chosen_connection = self.chosen_rocket.construct_serial_connection(com_port, int(baud_rate))
        elif connection == 'Debug':
            self.chosen_connection = self.chosen_rocket.construct_debug_connection()
        elif connection == 'SIM':
            self.chosen_connection = self.chosen_rocket.construct_sim_connection()
        else:
            raise Exception("Unknown connection")

        self.close()
Esempio n. 16
0
    def genStitchedMap(self, overwrite: bool = False) -> np.ndarray:
        """

        :param overwrite:
        :type overwrite: bool
        :return:
        :rtype: numpy.ndarray
        """
        def appendv(A, B):
            if A is None:
                return B
            elif B is None:
                return A
            else:
                return np.vstack((A, B))

        def appendh(A, B):
            if A is None:
                return B
            elif B is None:
                return A
            else:
                return np.column_stack((A, B))

        out = os.path.join(MAPBOX_CACHE, "out")
        if not os.path.isdir(out):
            os.mkdir(out)

        outfile = os.path.join(out, f"output_{str(self)}.png")

        if (not os.path.isfile(outfile)) or overwrite:
            LOGGER.debug(f"Generating size {str(self.scale)} map!")

            t1 = time.perf_counter()

            is_img_not_blank = False
            img = None
            for i in self.ta:
                row = None
                for j in i:
                    row = appendh(row, j.getImage())
                    if j.is_tile_not_blank is True:
                        is_img_not_blank = True

                img = appendv(img, row)

            if is_img_not_blank is True:
                plt.imsave(outfile, img)

            t2 = time.perf_counter()
            LOGGER.debug(
                f"Successfully generated size {str(self.scale)} map in {t2 - t1} seconds."
            )
            return img
        else:
            LOGGER.debug(f"Found size {str(self.scale)} map!")
            return plt.imread(outfile, "jpeg")
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")
Esempio n. 18
0
    # QApplication expects the first argument to be the program name.
    qt_args = sys.argv[:1] + unparsed_args
    app = QtWidgets.QApplication(qt_args)

    font = app.font()
    font.setPointSize(max(MIN_APP_FONT_POINT_SIZE, font.pointSize()))
    app.setFont(font)

    if IS_PYINSTALLER and '_PYIBoot_SPLASH' in os.environ:
        # Now that we are all loaded, close the splash screen
        try:  # pyi_splash is not a real module, its only available if splash was successfully included in the build
            import pyi_splash
            pyi_splash.close()
        except:
            LOGGER.debug("pyi_splash module expected but not found")

    if not args.self_test:
        # Open com_window dialog to get startup details
        com_window = ComWindow()
        com_window.show()
        return_code = app.exec_()
        if return_code != 0 or com_window.chosen_rocket is None or com_window.chosen_connection is None:
            sys.exit(return_code)

        rocket = com_window.chosen_rocket
        connection = com_window.chosen_connection
        main_window = rocket.construct_app(connection)

    else:
        rocket = TantalusProfile()
Esempio n. 19
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")
    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")