Esempio n. 1
0
class Time(AhrsElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                 framebuffer_size):
        self.task_timer = TaskTimer('Time')
        self.__font__ = font
        font_height = font.get_height()
        text_half_height = int(font_height) >> 1
        self.__text_y_pos__ = framebuffer_size[
            1] - text_half_height - font_height
        self.__rhs__ = int(0.9 * framebuffer_size[0])

        self.__left_x__ = int(framebuffer_size[0] * 0.01)
        self.__center_x__ = framebuffer_size[0] >> 1

    def render(self, framebuffer, orientation):
        self.task_timer.start()

        time_text = str(orientation.utc_time).split(
            '.'
        )[0] + "UTC" if orientation.utc_time is not None else AhrsElement.GPS_UNAVAILABLE_TEXT
        texture = self.__font__.render(time_text, True, YELLOW, BLACK)
        width = texture.get_size()[0]

        framebuffer.blit(texture, (self.__center_x__ -
                                   (width >> 1), self.__text_y_pos__))
        self.task_timer.stop()
Esempio n. 2
0
class RollIndicatorText(AhrsElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font, framebuffer_size):
        self.task_timer = TaskTimer('RollIndicatorText')
        self.__roll_elements__ = {}
        self.__framebuffer_size__ = framebuffer_size
        self.__center__ = (framebuffer_size[0] >> 1, framebuffer_size[1] >> 1)
        half_texture_height = int(font.get_height()) >> 1
        self.__font__ = font
        self.__text_y_pos__ = self.__center__[1] - half_texture_height

        for reference_angle in range(-180, 181):
            text = font.render(
                "{0:3}".format(int(math.fabs(reference_angle))), True, display.WHITE, display.BLACK)
            size_x, size_y = text.get_size()
            self.__roll_elements__[reference_angle] = (
                text, (size_x >> 1, size_y >> 1))

    def render(self, framebuffer, orientation):
        self.task_timer.start()
        roll = int(orientation.roll)
        pitch = int(orientation.pitch)
        pitch_direction = ''
        if pitch > 0:
            pitch_direction = '+'
        attitude_text = "{0}{1:3} | {2:3}".format(pitch_direction, pitch, roll)

        roll_texture = self.__font__.render(
            attitude_text, True, display.BLACK, display.WHITE)
        texture_size = roll_texture.get_size()
        text_half_width, text_half_height = texture_size
        text_half_width = int(text_half_width / 2)
        framebuffer.blit(
            roll_texture, (self.__center__[0] - text_half_width, self.__text_y_pos__))
        self.task_timer.stop()
Esempio n. 3
0
class Altitude(AhrsElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font, framebuffer_size):
        self.task_timer = TaskTimer('Altitude')
        self.__font__ = font
        center_y = framebuffer_size[1] >> 2
        text_half_height = int(font.get_height()) >> 1
        self.__text_y_pos__ = center_y - text_half_height
        self.__rhs__ = int(framebuffer_size[0])  # was 0.9

    def render(self, framebuffer, orientation):
        self.task_timer.start()
        is_altitude_valid = orientation.alt is not None and isinstance(orientation.alt, Number)
        altitude_text = str(int(orientation.alt)) + \
            "' MSL" if is_altitude_valid else AhrsElement.INOPERATIVE_TEXT
        color = display.WHITE if is_altitude_valid else display.RED
        alt_texture = self.__font__.render(
            altitude_text,
            True,
            color,
            display.BLACK)
        text_width, text_height = alt_texture.get_size()

        framebuffer.blit(
            alt_texture, (self.__rhs__ - text_width, self.__text_y_pos__))
        self.task_timer.stop()
Esempio n. 4
0
class TrafficNotAvailable(AhrsElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                 framebuffer_size):
        self.task_timer = TaskTimer('TrafficNotAvailable')
        self.__font__ = font
        font_height = font.get_height()
        self.__text_y_pos__ = int(font_height * 0.7)
        self.__rhs__ = int(0.9 * framebuffer_size[0])

        self.__left_x__ = int(framebuffer_size[0] * 0.01)
        self.__center_x__ = framebuffer_size[0] >> 1

    def render(self, framebuffer, orientation):
        self.task_timer.start()

        if not HudDataCache.IS_TRAFFIC_AVAILABLE:
            (texture, size) = HudDataCache.get_cached_text_texture(
                "TRAFFIC UNAVAILABLE",
                self.__font__,
                text_color=RED,
                background_color=BLACK,
                use_alpha=True)
            width = size[0]

            framebuffer.blit(texture, (self.__center_x__ -
                                       (width >> 1), self.__text_y_pos__))
        self.task_timer.stop()
Esempio n. 5
0
class Groundspeed(AhrsElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font, framebuffer_size):
        self.task_timer = TaskTimer('Groundspeed')
        self.__font__ = font
        center_y = framebuffer_size[1] >> 2
        text_half_height = int(font.get_height()) >> 1
        self.__text_y_pos__ = (text_half_height << 2) + \
            center_y - text_half_height
        self.__rhs__ = int(0.9 * framebuffer_size[0])

        self.__left_x__ = 0  # WAS int(framebuffer_size[0] * 0.01)

    def render(self, framebuffer, orientation):
        self.task_timer.start()

        speed_units = configuration.CONFIGURATION.__get_config_value__(
            configuration.Configuration.DISTANCE_UNITS_KEY, units.STATUTE)

        groundspeed_text = units.get_converted_units_string(
            speed_units, orientation.groundspeed * units.feet_to_nm, units.SPEED)

        texture = self.__font__.render(
            groundspeed_text, True, WHITE, BLACK)

        framebuffer.blit(
            texture, (self.__left_x__, self.__text_y_pos__))
        self.task_timer.stop()
Esempio n. 6
0
class LevelReference(AhrsElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font, framebuffer_size):
        self.task_timer = TaskTimer('LevelReference')
        self.level_reference_lines = []

        width = framebuffer_size[0]
        center = (framebuffer_size[0] >> 1, framebuffer_size[1] >> 1)

        edge_reference_proportion = int(width * 0.05)

        artificial_horizon_level = [[int(width * 0.4),  center[1]],
                                    [int(width * 0.6),  center[1]]]
        left_hash = [[0, center[1]], [edge_reference_proportion, center[1]]]
        right_hash = [[width - edge_reference_proportion,
                       center[1]], [width, center[1]]]

        self.level_reference_lines.append(artificial_horizon_level)
        self.level_reference_lines.append(left_hash)
        self.level_reference_lines.append(right_hash)

    def render(self, framebuffer, orientation):
        """
        Renders a "straight and level" line to the HUD.
        """

        self.task_timer.start()
        [pygame.draw.lines(framebuffer, WHITE, False, line, 6)
         for line in self.level_reference_lines]
        self.task_timer.stop()
Esempio n. 7
0
class AhrsNotAvailable(object):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                 framebuffer_size):
        self.task_timer = TaskTimer('AhrsNotAvailable')
        self.__not_available_lines__ = []

        width, height = framebuffer_size

        self.__not_available_lines__.append([[0, 0], [width, height]])
        self.__not_available_lines__.append([[0, height], [width, 0]])
        self.__na_color__ = RED
        self.__na_line_width__ = 20

    def render(self, framebuffer, orientation):
        """
        Render an "X" over the screen to indicate the AHRS is not
        available.
        """

        self.task_timer.start()
        pygame.draw.line(framebuffer, self.__na_color__,
                         self.__not_available_lines__[0][0],
                         self.__not_available_lines__[0][1],
                         self.__na_line_width__)
        pygame.draw.line(framebuffer, self.__na_color__,
                         self.__not_available_lines__[1][0],
                         self.__not_available_lines__[1][1],
                         self.__na_line_width__)
        self.task_timer.stop()
class Aithre(AhrsElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                 framebuffer_size):
        self.task_timer = TaskTimer('Aithre')
        self.__font__ = font
        center_y = framebuffer_size[1] >> 2
        text_half_height = int(font.get_height()) >> 1
        self.__text_y_pos__ = center_y - text_half_height
        self.__lhs__ = 0
        self.__has_been_connected__ = False

    def render(self, framebuffer, orientation):
        self.task_timer.start()

        if aithre.sensor is not None and configuration.CONFIGURATION.aithre_enabled:
            co_level = aithre.sensor.get_co_level()

            if co_level is None or isinstance(co_level, basestring):
                if self.__has_been_connected__:
                    co_color = RED
                    co_ppm_text = "OFFLINE"
                else:
                    self.task_timer.stop()
                    return
            else:
                co_color = get_aithre_co_color(co_level)
                co_ppm_text = str(int(co_level)) + " PPM"
                self.__has_been_connected__ = True

            co_ppm_texture = self.__font__.render(co_ppm_text, True, co_color,
                                                  BLACK)

            framebuffer.blit(co_ppm_texture,
                             (self.__lhs__, self.__text_y_pos__))
        self.task_timer.stop()
Esempio n. 9
0
class Groundspeed(AhrsElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                 framebuffer_size):
        self.task_timer = TaskTimer('Groundspeed')
        self.__font__ = font
        center_y = framebuffer_size[1] >> 2
        text_half_height = int(font.get_height()) >> 1
        self.__text_y_pos__ = (text_half_height << 2) + \
            center_y - text_half_height
        self.__rhs__ = int(0.9 * framebuffer_size[0])

        self.__left_x__ = 0  # WAS int(framebuffer_size[0] * 0.01)

    def render(self, framebuffer, orientation):
        self.task_timer.start()

        speed_units = configuration.CONFIGURATION.__get_config_value__(
            configuration.Configuration.DISTANCE_UNITS_KEY, units.STATUTE)

        groundspeed_text = units.get_converted_units_string(
            speed_units,
            orientation.groundspeed * units.feet_to_nm,
            unit_type=units.SPEED,
            decimal_places=False
        ) if orientation.groundspeed is not None and isinstance(
            orientation.groundspeed, Number) else "INOP"

        display_color = display.WHITE if orientation is not None and orientation.groundspeed is not None and orientation.gps_online else display.RED

        texture = self.__font__.render(groundspeed_text, True, display_color,
                                       display.BLACK)

        framebuffer.blit(texture, (self.__left_x__, self.__text_y_pos__))
        self.task_timer.stop()
Esempio n. 10
0
class Illyrian(AhrsElement):
    """
    Screen element to support the Illyrian blood/pulse oxymeter from Aithre
    """
    def uses_ahrs(self):
        """
        Does this element use AHRS data to render?

        Returns:
            bool -- True if the element uses AHRS data.
        """

        return False

    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                 framebuffer_size):
        self.task_timer = TaskTimer('Illyrian')
        self.__font__ = font
        center_y = framebuffer_size[1] >> 2
        text_half_height = int(font.get_height()) >> 1
        self.__text_y_pos__ = center_y + (6 * text_half_height)
        self.__pulse_y_pos__ = center_y + (8 * text_half_height)
        self.__lhs__ = 0
        self.__has_been_connected__ = False

    def render(self, framebuffer, orientation):
        self.task_timer.start()

        if AithreClient.INSTANCE is not None and configuration.CONFIGURATION.aithre_enabled:
            report = AithreClient.INSTANCE.get_spo2_report()
            spo2_level = report.spo2
            heartbeat = report.heartrate
            heartbeat_text = "{}BPM".format(heartbeat)

            if spo2_level is None or isinstance(spo2_level, basestring):
                if self.__has_been_connected__:
                    spo2_color = RED
                    spo2_text = "OFFLINE"
                else:
                    self.task_timer.stop()
                    return
            else:
                spo2_color = get_illyrian_spo2_color(spo2_level)
                spo2_text = str(int(spo2_level)) + "% SPO"
                self.__has_been_connected__ = True

            spo2_ppm_texture = self.__font__.render(spo2_text, True,
                                                    spo2_color, BLACK)

            heartbeat_texture = self.__font__.render(heartbeat_text, True,
                                                     GREEN, BLACK)

            framebuffer.blit(spo2_ppm_texture,
                             (self.__lhs__, self.__text_y_pos__))

            framebuffer.blit(heartbeat_texture,
                             (self.__lhs__, self.__pulse_y_pos__))

        self.task_timer.stop()
Esempio n. 11
0
class SystemInfo(AhrsElement):
    def uses_ahrs(self):
        """
        The diagnostics page does not use AHRS.

        Returns:
            bool -- Always returns False.
        """

        return False

    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font, framebuffer_size):
        self.task_timer = TaskTimer('Time')
        self.__font__ = font
        self.font_height = font.get_height()
        text_half_height = int(self.font_height) >> 1
        self.__text_y_pos__ = framebuffer_size[1] - \
            text_half_height - self.font_height
        self.__rhs__ = int(0.9 * framebuffer_size[0])

        self.__left_x__ = int(framebuffer_size[0] * 0.01)
        self.__center_x__ = framebuffer_size[0] >> 1

    def render(self, framebuffer, orientation):
        self.task_timer.start()

        # Status lines are pushed in as a stack.
        # First line in the array is at the bottom.
        # Last line in the array is towards the top.
        info_lines = [
            ["IP          : ", get_ip_address()],
            ["HUD CPU     : ", get_cpu_temp()],
            ["SOCKET      : ", get_websocket_uptime()],
            ["DECLINATION : ", [
                str(configuration.CONFIGURATION.get_declination()), BLUE]],
            ["OWNSHIP     : ", [configuration.CONFIGURATION.ownship, BLUE]]
        ]

        render_y = self.__text_y_pos__

        for line in info_lines:
            # Draw the label in a standard color.
            texture_lhs = self.__font__.render(line[0], True, BLUE, BLACK)
            framebuffer.blit(texture_lhs, (0, render_y))
            size = texture_lhs.get_size()

            # Draw the value in the encoded colors.
            texture_rhs = self.__font__.render(
                line[1][0], True, line[1][1], BLACK)
            framebuffer.blit(texture_rhs, (size[0], render_y))

            render_y = render_y - (self.font_height * 1.2)

        self.task_timer.stop()
Esempio n. 12
0
class Aithre(AhrsElement):
    def uses_ahrs(self):
        """
        Does this element use AHRS data to render?

        Returns:
            bool -- True if the element uses AHRS data.
        """

        return False

    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                 framebuffer_size):
        self.task_timer = TaskTimer('Aithre')
        self.__font__ = font
        center_y = framebuffer_size[1] >> 2
        text_half_height = int(font.get_height()) >> 1
        self.__text_y_pos__ = center_y + (10 * text_half_height)
        self.__lhs__ = 0

    def render(self, framebuffer, orientation):
        self.task_timer.start()

        if AithreClient.INSTANCE is not None and configuration.CONFIGURATION.aithre_enabled:
            co_level = AithreClient.INSTANCE.get_co_report()

            if (co_level.co is None
                    and co_level.has_been_connected) or isinstance(
                        co_level, basestring):
                co_color = RED
                co_ppm_text = "OFFLINE"
            elif not co_level.has_been_connected:
                self.task_timer.stop()
                return
            else:
                co_color = get_aithre_co_color(co_level.co)
                units_text = "PPM" if co_level.is_connected else ""
                co_ppm_text = "{}{}".format(co_level.co, units_text)

            co_ppm_texture = self.__font__.render(co_ppm_text, True, co_color,
                                                  BLACK)

            framebuffer.blit(co_ppm_texture,
                             (self.__lhs__, self.__text_y_pos__))
        self.task_timer.stop()
Esempio n. 13
0
class SkidAndGs(AhrsElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font, framebuffer_size):
        self.task_timer = TaskTimer('SkidAndGs')
        self.__font__ = font
        center_y = framebuffer_size[1] >> 2
        text_half_height = int(font.get_height()) >> 1
        self.__text_y_pos__ = (text_half_height << 2) + \
            center_y - text_half_height
        self.__rhs__ = int(framebuffer_size[0]) # was 0.9

    def render(self, framebuffer, orientation):
        self.task_timer.start()
        g_load_text = "{0:.1f}Gs".format(orientation.g_load)
        texture = self.__font__.render(
            g_load_text, True, WHITE, BLACK)
        text_width, text_height = texture.get_size()

        framebuffer.blit(
            texture, (self.__rhs__ - text_width, self.__text_y_pos__))
        self.task_timer.stop()
Esempio n. 14
0
class TargetCount(AhrsElement):
    def uses_ahrs(self):
        """
        Does this element use AHRS data to render?
               
        Returns:
            bool -- True if the element uses AHRS data.
        """

        return False

    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                 framebuffer_size):
        self.task_timer = TaskTimer('TargetCount')
        self.__font__ = font
        center_y = framebuffer_size[1] >> 2
        text_half_height = int(font.get_height()) >> 1
        self.__text_y_pos__ = center_y - text_half_height
        self.__rhs__ = int(0.9 * framebuffer_size[0])

        self.__left_x__ = 0  # WAS int(framebuffer_size[0] * 0.01)

    def render(self, framebuffer, orientation):
        self.task_timer.start()
        # Get the traffic, and bail out of we have none

        text = "NO TARGETS"

        try:
            count = len(targets.TARGET_MANAGER.targets)

            if count > 0:
                text = "{0} TARGETS".format(count)
        except Exception as e:
            text = "ERROR" + str(e)

        texture = self.__font__.render(text, True, WHITE, BLACK)

        framebuffer.blit(texture, (self.__left_x__, self.__text_y_pos__))
        self.task_timer.stop()
Esempio n. 15
0
class Groundspeed(AhrsElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font, framebuffer_size):
        self.task_timer = TaskTimer('Groundspeed')
        self.__font__ = font
        center_y = framebuffer_size[1] >> 2
        text_half_height = int(font.get_height()) >> 1
        self.__text_y_pos__ = (text_half_height << 2) + \
            center_y - text_half_height
        self.__rhs__ = int(0.9 * framebuffer_size[0])

        self.__left_x__ = 0 # WAS int(framebuffer_size[0] * 0.01)

    def render(self, framebuffer, orientation):
        self.task_timer.start()
        # TODO - Pass in the configuration to all elements so they can have access to the unit types.
        groundspeed_text = "{0:.1f}".format(orientation.groundspeed).rjust(5) + units.UNIT_LABELS[units.STATUTE][units.SPEED]
        texture = self.__font__.render(
            groundspeed_text, True, WHITE, BLACK)

        framebuffer.blit(
            texture, (self.__left_x__, self.__text_y_pos__))
        self.task_timer.stop()
Esempio n. 16
0
class AdsbOnScreenReticles(AdsbElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                 framebuffer_size):
        AdsbElement.__init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                             framebuffer_size)

        self.task_timer = TaskTimer('AdsbOnScreenReticles')

        self.__listing_text_start_y__ = int(self.__font__.get_height() * 4)
        self.__listing_text_start_x__ = int(self.__framebuffer_size__[0] *
                                            0.01)
        self.__next_line_distance__ = int(font.get_height() * 1.5)
        self.__top_border__ = int(self.__height__ * 0.1)
        self.__bottom_border__ = self.__height__ - self.__top_border__

    def __render_on_screen_reticle__(self, framebuffer, orientation, traffic):
        """
        Draws a single reticle on the screen.

        Arguments:
            framebuffer {Surface} -- Render target
            orientation {Orientation} -- The orientation of the plane.
            traffic {Traffic} -- The traffic to draw the reticle for.
        """

        identifier = traffic.get_identifer()

        # Find where to draw the reticle....
        reticle_x, reticle_y = self.__get_traffic_projection__(
            orientation, traffic)

        # Render using the Above us bug
        on_screen_reticle_scale = get_reticle_size(traffic.distance)
        reticle, reticle_size_px = self.get_onscreen_reticle(
            reticle_x, reticle_y, on_screen_reticle_scale)

        reticle_x, reticle_y = self.__rotate_reticle__(
            [[reticle_x, reticle_y]], orientation.roll)[0]

        self.__render_target_reticle__(framebuffer, identifier,
                                       (reticle_x, reticle_y), reticle,
                                       orientation.roll, reticle_size_px)

    def render(self, framebuffer, orientation):
        """
        Renders all of the on-screen reticles  for nearby traffic.

        Arguments:
            framebuffer {Surface} -- The render target.
            orientation {Orientation} -- The orientation of the plane the HUD is in.
        """

        self.task_timer.start()
        # Get the traffic, and bail out of we have none
        traffic_reports = HudDataCache.get_reliable_traffic()

        traffic_reports = filter(lambda x: not x.is_on_ground(),
                                 traffic_reports)
        traffic_reports = traffic_reports[:max_target_bugs]

        [
            self.__render_on_screen_reticle__(framebuffer, orientation,
                                              traffic)
            for traffic in traffic_reports
        ]

        self.task_timer.stop()

    def __render_target_reticle__(self, framebuffer, identifier, pos,
                                  reticle_lines, roll, reticle_size_px):
        """
        Renders a targetting reticle on the screen.
        Assumes the X/Y projection has already been performed.
        """

        center_x, center_y = pos
        border_space = int(reticle_size_px * 1.2)

        center_y = border_space if center_y < border_space else center_y
        center_y = int(self.__height__ - border_space) \
            if center_y > (self.__height__ - border_space) else center_y

        pygame.draw.lines(framebuffer, BLACK, True, reticle_lines, 20)
        pygame.draw.lines(framebuffer, RED, True, reticle_lines, 10)

    def __rotate_reticle__(self, reticle, roll):
        """
        Takes a series of line segments and rotates them (roll) about
        the screen's center

        Arguments:
            reticle {list of tuples} -- The line segments
            roll {float} -- The amount to rotate about the center by.

        Returns:
            list of lists -- The new list of line segments
        """

        # Takes the roll in degrees
        # Example input..
        # [
        #     [center_x, center_y - size],
        #     [center_x + size, center_y],
        #     [center_x, center_y + size],
        #     [center_x - size, center_y]
        # ]

        translated_points = []

        int_roll = int(-roll)
        cos_roll = COS_RADIANS_BY_DEGREES[int_roll]
        sin_roll = SIN_RADIANS_BY_DEGREES[int_roll]
        ox, oy = self.__center__

        translated_points = [[
            (ox + cos_roll * (x_y[0] - ox) - sin_roll * (x_y[1] - oy)),
            (oy + sin_roll * (x_y[0] - ox) + cos_roll * (x_y[1] - oy))
        ] for x_y in reticle]

        return translated_points
Esempio n. 17
0
class Groundspeed(AhrsElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                 framebuffer_size):
        self.task_timer = TaskTimer('Groundspeed')
        self.__font__ = font
        self.__font_height__ = font.get_height()
        center_y = framebuffer_size[1] >> 2
        text_half_height = int(self.__font_height__) >> 1
        self.__text_y_pos__ = center_y - text_half_height
        self.__rhs__ = int(0.9 * framebuffer_size[0])

        self.__left_x__ = 0  # WAS int(framebuffer_size[0] * 0.01)

    def render(self, framebuffer, orientation):
        self.task_timer.start()

        speed_units = configuration.CONFIGURATION.__get_config_value__(
            configuration.Configuration.DISTANCE_UNITS_KEY, units.STATUTE)

        airspeed_text = None
        is_valid_airspeed = orientation.is_avionics_source and isinstance(
            orientation.airspeed, Number)
        is_valid_groundspeed = orientation.groundspeed is not None and isinstance(
            orientation.groundspeed, Number)

        airspeed_text = units.get_converted_units_string(
            speed_units,
            orientation.airspeed * units.feet_to_nm,
            unit_type=units.SPEED,
            decimal_places=False) if is_valid_airspeed else None

        groundspeed_text = units.get_converted_units_string(
            speed_units, (orientation.groundspeed * units.yards_to_nm),
            unit_type=units.SPEED,
            decimal_places=False
        ) if is_valid_groundspeed else orientation.groundspeed

        groundspeed_text += " GND"

        if airspeed_text is not None:
            airspeed_text += " IAS"
            ias_len = len(airspeed_text)
            gnd_len = len(groundspeed_text)
            max_len = gnd_len if ias_len < gnd_len else ias_len
            airspeed_text = airspeed_text.rjust(max_len)
            groundspeed_text = groundspeed_text.rjust(max_len)

        gs_display_color = display.WHITE if is_valid_groundspeed and orientation.gps_online else display.RED
        airspeed_color = display.WHITE if is_valid_airspeed else display.RED

        ias_texture = self.__font__.render(
            airspeed_text, True, airspeed_color,
            display.BLACK) if airspeed_text is not None else None

        gs_texture = self.__font__.render(groundspeed_text, True,
                                          gs_display_color, display.BLACK)

        gs_position_adj = self.__font_height__ if ias_texture is not None else 0

        framebuffer.blit(
            gs_texture,
            (self.__left_x__, self.__text_y_pos__ + gs_position_adj))

        if ias_texture is not None:
            framebuffer.blit(ias_texture,
                             (self.__left_x__, self.__text_y_pos__))

        self.task_timer.stop()
class AdsbTrafficListing(AdsbElement):
    def uses_ahrs(self):
        """
        Does this element use AHRS data to render?

        Returns:
            bool -- False as this element does not use AHRS data.
        """

        return False

    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font, framebuffer_size):
        AdsbElement.__init__(
            self, degrees_of_pitch, pixels_per_degree_y, font, framebuffer_size)

        self.task_timer = TaskTimer('AdsbTargetBugs')
        self.__listing_text_start_y__ = int(self.__font__.get_height())
        self.__listing_text_start_x__ = int(
            self.__framebuffer_size__[0] * 0.01)
        self.__next_line_distance__ = int(font.get_height())
        self.__max_reports__ = int(
            (self.__height__ - self.__listing_text_start_y__) / self.__next_line_distance__) - 1
        self.__top_border__ = int(self.__height__ * 0.2)
        self.__bottom_border__ = self.__height__ - int(self.__height__ * 0.1)

    def __get_listing__(self, report, max_string_lengths):
        identifier = report[0]
        try:
            bearing = str(utils.apply_declination(float(report[1])))
        except:
            bearing = str(report[1])
        distance_text = report[2]
        altitude = report[3]
        icao = report[4]

        # if self.__show_list__:
        traffic_report = "{0} {1} {2} {3}".format(
            identifier.ljust(max_string_lengths[0]),
            bearing.rjust(max_string_lengths[1]),
            distance_text.rjust(max_string_lengths[2]),
            altitude.rjust(max_string_lengths[3]))

        return (icao, traffic_report)

    def __get_padded_traffic_reports__(self, traffic_reports):
        pre_padded_text, max_string_lengths = self.__get_pre_padded_text_reports__(
            traffic_reports)

        out_padded_reports = [self.__get_listing__(report,
                                                   max_string_lengths) for report in pre_padded_text]

        return out_padded_reports

    def __get_report_text__(self, traffic):
        identifier = str(traffic.get_display_name())
        altitude_delta = int(traffic.altitude / 100.0)
        distance_text = self.__get_distance_string__(traffic.distance, True)
        delta_sign = ''
        if altitude_delta > 0:
            delta_sign = '+'
        altitude_text = "{0}{1}".format(delta_sign, altitude_delta)
        bearing_text = "{0:.0f}".format(
            utils.apply_declination(traffic.bearing))

        return [identifier, bearing_text, distance_text, altitude_text, traffic.icao_address]

    def __get_pre_padded_text_reports__(self, traffic_reports):
        # We do not want to show traffic on the ground.
        reports_to_show = filter(
            lambda x: not x.is_on_ground(), traffic_reports)

        # The __max_reports__ value is set based on the screen size
        # and how much can fit on the screen
        reports_to_show = reports_to_show[:self.__max_reports__]

        pre_padded_text = [['IDENT', 'BEAR', 'DIST', 'ALT', None]] + \
            [self.__get_report_text__(traffic) for traffic in reports_to_show]
        # An ICAO code is the worst case display length,
        # but add a little buffer so the columns do
        # not shift around.
        # len(max(pre_padded_text, key = lambda x: len(str(x[4])))[0])
        max_identifier_length = 10
        # Since the bearing length should never be any more the 3 digits
        max_bearing_length = 4
        # len(max(pre_padded_text, key = lambda x: len(x[2]))[2]) + 1
        max_distance_length = 8
        # We really should never get anything more than 35k above, but same some room
        # len(max(pre_padded_text, key = lambda x: len(x[3]))[3])
        max_altitude_length = 5

        return pre_padded_text, (max_identifier_length, max_bearing_length, max_distance_length, max_altitude_length)

    def render(self, framebuffer, orientation):
        # Render a heading strip along the top

        self.task_timer.start()

        # Get the traffic, and bail out of we have none
        traffic_reports = HudDataCache.get_reliable_traffic()

        if traffic_reports is None:
            self.task_timer.stop()
            return

        # Render a list of traffic that we have positions
        # for, along with the tail number

        y_pos = self.__listing_text_start_y__
        x_pos = self.__listing_text_start_x__

        padded_traffic_reports = self.__get_padded_traffic_reports__(
            traffic_reports)

        if len(padded_traffic_reports) == 0:
            framebuffer.blit(HudDataCache.get_cached_text_texture("NO TRAFFIC", self.__font__)[0],
                             (x_pos, y_pos))

        for identifier, traffic_report in padded_traffic_reports:
            traffic_text_texture = HudDataCache.get_cached_text_texture(traffic_report,
                                                                        self.__font__)[0]

            framebuffer.blit(traffic_text_texture, (x_pos, y_pos))

            y_pos += self.__next_line_distance__
        self.task_timer.stop()
Esempio n. 19
0
class ArtificialHorizon(AhrsElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                 framebuffer_size):
        self.task_timer = TaskTimer('ArtificialHorizon')
        self.__pitch_elements__ = {}
        self.__framebuffer_size__ = framebuffer_size
        self.__center__ = (framebuffer_size[0] >> 1, framebuffer_size[1] >> 1)
        self.__long_line_width__ = self.__framebuffer_size__[0] * 0.4
        self.__short_line_width__ = self.__framebuffer_size__[0] * 0.2
        self.__pixels_per_degree_y__ = pixels_per_degree_y
        self.__height__ = framebuffer_size[1]

        for reference_angle in range(-degrees_of_pitch, degrees_of_pitch + 1,
                                     10):
            text = font.render(str(reference_angle), True, WHITE,
                               BLACK).convert()
            size_x, size_y = text.get_size()
            self.__pitch_elements__[reference_angle] = (text, (size_x >> 1,
                                                               size_y >> 1))

    def render(self, framebuffer, orientation):
        self.task_timer.start()

        for reference_angle in self.__pitch_elements__:
            line_coords, line_center = self.__get_line_coords__(
                orientation.pitch, orientation.roll, reference_angle)

            # Perform some trivial clipping of the lines
            # This also prevents early text rasterization
            if line_center[1] < 0 or line_center[1] > self.__height__:
                continue

            pygame.draw.lines(framebuffer, GREEN, False, line_coords, 4)

            text, half_size = self.__pitch_elements__[reference_angle]
            text = pygame.transform.rotate(text, orientation.roll)
            half_x, half_y = half_size
            center_x, center_y = line_center

            framebuffer.blit(text, (center_x - half_x, center_y - half_y))
        self.task_timer.stop()

    def __get_line_coords__(self, pitch, roll, reference_angle):
        """
        Get the coordinate for the lines for a given pitch and roll.
        """

        if reference_angle == 0:
            length = self.__long_line_width__
        else:
            length = self.__short_line_width__

        pitch = int(pitch)
        roll = int(roll)

        ahrs_center_x, ahrs_center_y = self.__center__
        pitch_offset = self.__pixels_per_degree_y__ * \
            (-pitch + reference_angle)

        roll_delta = 90 - roll

        center_x = int((ahrs_center_x -
                        (pitch_offset * COS_RADIANS_BY_DEGREES[roll_delta])) +
                       0.5)
        center_y = int((ahrs_center_y -
                        (pitch_offset * SIN_RADIANS_BY_DEGREES[roll_delta])) +
                       0.5)

        x_len = int((length * COS_RADIANS_BY_DEGREES[roll]) + 0.5)
        y_len = int((length * SIN_RADIANS_BY_DEGREES[roll]) + 0.5)

        half_x_len = x_len >> 1
        half_y_len = y_len >> 1

        start_x = center_x - half_x_len
        end_x = center_x + half_x_len
        start_y = center_y + half_y_len
        end_y = center_y - half_y_len

        return [[int(start_x), int(start_y)],
                [int(end_x), int(end_y)]], (int(center_x), int(center_y))
Esempio n. 20
0
class AdsbTargetBugsOnly(AdsbElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font, framebuffer_size):
        AdsbElement.__init__(
            self, degrees_of_pitch, pixels_per_degree_y, font, framebuffer_size)

        self.task_timer = TaskTimer('AdsbTargetBugs')
        self.__listing_text_start_y__ = int(self.__font__.get_height() * 4)
        self.__listing_text_start_x__ = int(
            self.__framebuffer_size__[0] * 0.01)
        self.__next_line_distance__ = int(font.get_height() * 1.5)
        self.__top_border__ = 0
        self.__bottom_border__ = self.__height__ - int(self.__height__ * 0.1)

    def __render_traffic_heading_bug__(self, traffic_report, heading, orientation, framebuffer):
        """
        Render a single heading bug to the framebuffer.

        Arguments:
            traffic_report {Traffic} -- The traffic we want to render a bug for.
            heading {int} -- Our current heading.
            orientation {Orientation} -- Our plane's current orientation.
            framebuffer {Framebuffer} -- What we are going to draw to.
        """

        # Render using the Above us bug
        # target_bug_scale = 0.04
        target_bug_scale = get_reticle_size(traffic_report.distance)

        heading_bug_x = get_heading_bug_x(
            heading, traffic_report.bearing, self.__pixels_per_degree_x__)

        try:
            is_below = (orientation.alt - 100) > traffic_report.altitude
            reticle, reticle_edge_positon_y = self.get_below_reticle(
                heading_bug_x, target_bug_scale) if is_below else self.get_above_reticle(heading_bug_x, target_bug_scale)

            bug_color = display.BLUE if traffic_report.is_on_ground() == True else display.RED

            pygame.draw.polygon(framebuffer, bug_color, reticle)
        except:
            pass

    def render(self, framebuffer, orientation):
        # Render a heading strip along the top

        self.task_timer.start()
        heading = orientation.get_onscreen_projection_heading()

        # Get the traffic, and bail out of we have none
        traffic_reports = HudDataCache.get_reliable_traffic()

        if traffic_reports is None:
            self.task_timer.stop()
            return

        reports_to_show = filter(lambda x: math.fabs(x.altitude - orientation.alt) < max_altitude_delta, traffic_reports)
        reports_to_show = reports_to_show[:max_target_bugs]

        [self.__render_traffic_heading_bug__(
            traffic_report, heading, orientation, framebuffer) for traffic_report in reports_to_show]

        self.task_timer.stop()
Esempio n. 21
0
class ArtificialHorizon(AhrsElement):
    def __generate_reference_angle__(self, reference_angle):
        """
        Renders the text for the reference angle.

        Arguments:
            reference_angle {int} -- The angle that we are going to produce text for.

        Returns:
            (Surface, (int, int)) -- Tuple of the texture and the half size x & y.
        """

        text = self.__font__.render(str(reference_angle), False, WHITE,
                                    BLACK).convert()
        size_x, size_y = text.get_size()

        return (text, (size_x >> 1, size_y >> 1))

    def __generate_rotated_reference_angle__(self, reference_angle):
        """
        Returns the text for the reference angle rotated.

        Arguments:
            reference_angle {int} -- The angle marking to generate the textures for.

        Returns:
            ({int, Surface}, (int, int)) -- A map of the textures keyed by roll angle and the half size of the texture.
        """

        rotate = pygame.transform.rotate
        reference_angle, half_size = self.__generate_reference_angle__(
            reference_angle)
        rotated_angles = {
            roll: rotate(reference_angle, roll)
            for roll in range(-45, 46, 1)
        }

        # Make sure that the un-rolled version is the original
        # so as to not have any artifacts for later rotations
        # that get added.
        rotated_angles[0] = reference_angle

        return rotated_angles, half_size

    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                 framebuffer_size):
        self.task_timer = TaskTimer('ArtificialHorizon')
        self.__framebuffer_size__ = framebuffer_size
        self.__center__ = (framebuffer_size[0] >> 1, framebuffer_size[1] >> 1)
        self.__long_line_width__ = self.__framebuffer_size__[0] * 0.4
        self.__short_line_width__ = self.__framebuffer_size__[0] * 0.2
        self.__pixels_per_degree_y__ = int(pixels_per_degree_y)
        self.__height__ = framebuffer_size[1]
        self.__font__ = font

        self.__reference_angles__ = range(-degrees_of_pitch,
                                          degrees_of_pitch + 1, 10)
        self.__pitch_elements__ = {
            reference_angle:
            self.__generate_rotated_reference_angle__(reference_angle)
            for reference_angle in self.__reference_angles__
        }

    def __render_reference_line__(self, framebuffer, line_info, draw_line,
                                  roll):
        """
        Renders a single line of the AH ladder.

        Arguments:
            framebuffer {Surface} -- The target framebuffer to draw to.
            line_info {triplet} -- The line coords, center, and angle.
            draw_line {function} -- The function to draw the line.
            rot_text {function} -- The function to rotate the text.
            roll {float} -- How much the plane is rolled.
        """

        line_coords, line_center, reference_angle = line_info
        draw_line(framebuffer, GREEN, False, line_coords, 4)

        text, half_size = self.__pitch_elements__[reference_angle]
        roll = int(roll)
        # Since we will start with a limited number of pre-cached
        # pre-rotated textures (to improve boot time),
        # add any missing rotated textures using the upright
        # as the base.
        if roll not in text:
            rotated_surface = pygame.transform.rotate(text[0], roll)
            self.__pitch_elements__[reference_angle][0][roll] = rotated_surface
            text[roll] = rotated_surface

        text = text[roll]
        half_x, half_y = half_size
        center_x, center_y = line_center

        framebuffer.blit(text, (center_x - half_x, center_y - half_y))

    def render(self, framebuffer, orientation):
        """
        Renders the artifical horizon to the framebuffer

        Arguments:
            framebuffer {Surface} -- Target framebuffer to draw to.
            orientation {orientation} -- The airplane's orientation (roll & pitch)
        """

        self.task_timer.start()

        # Creating aliases to the functions saves time...
        draw_line = pygame.draw.lines
        pitch = orientation.pitch
        roll = orientation.roll

        # Calculating the coordinates ahead of time...
        lines_centers_and_angles = [
            self.__get_line_coords__(pitch, roll, reference_angle)
            for reference_angle in self.__reference_angles__
        ]
        # ... only to use filter to throw them out saves time.
        # This allows for the cores to be used and removes the conditionals
        # from the actual render function.
        lines_centers_and_angles = filter(
            lambda center: center[1][1] >= 0 and center[1][1] <= self.
            __height__, lines_centers_and_angles)

        [
            self.__render_reference_line__(framebuffer, line_info, draw_line,
                                           roll)
            for line_info in lines_centers_and_angles
        ]

        self.task_timer.stop()

    def __get_line_coords__(self, pitch, roll, reference_angle):
        """
        Get the coordinate for the lines for a given pitch and roll.

        Arguments:
            pitch {float} -- The pitch of the plane.
            roll {float} -- The roll of the plane.
            reference_angle {int} -- The pitch angle to be marked on the AH.

        Returns:
            [tuple] -- An array[4] of the X/Y line coords.
        """

        length = self.__long_line_width__ if reference_angle == 0 else self.__short_line_width__

        roll_int = int(roll)

        ahrs_center_x, ahrs_center_y = self.__center__
        pitch_offset = self.__pixels_per_degree_y__ * \
            (-pitch + reference_angle)

        roll_delta = 90 - roll_int

        center_x = ahrs_center_x - \
            (pitch_offset * COS_RADIANS_BY_DEGREES[roll_delta]) + 0.5
        center_y = ahrs_center_y - \
            (pitch_offset * SIN_RADIANS_BY_DEGREES[roll_delta]) + 0.5

        center_x = int(center_x)
        center_y = int(center_y)

        x_len = int(length * COS_RADIANS_BY_DEGREES[roll_int] + 0.5)
        y_len = int(length * SIN_RADIANS_BY_DEGREES[roll_int] + 0.5)

        half_x_len = x_len >> 1
        half_y_len = y_len >> 1

        start_x = center_x - half_x_len
        end_x = center_x + half_x_len
        start_y = center_y + half_y_len
        end_y = center_y - half_y_len

        return [[start_x, start_y],
                [end_x, end_y]], (center_x, center_y), reference_angle
Esempio n. 22
0
class AdsbTargetBugs(AdsbElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                 framebuffer_size):
        AdsbElement.__init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                             framebuffer_size)

        self.task_timer = TaskTimer('AdsbTargetBugs')
        self.__listing_text_start_y__ = int(self.__font__.get_height() * 4)
        self.__listing_text_start_x__ = int(self.__framebuffer_size__[0] *
                                            0.01)
        self.__next_line_distance__ = int(font.get_height() * 1.5)
        self.__max_reports__ = int(
            (self.__height__ - self.__listing_text_start_y__) /
            self.__next_line_distance__)
        self.__top_border__ = int(self.__height__ * 0.2)
        self.__bottom_border__ = self.__height__ - int(self.__height__ * 0.1)

    def render(self, framebuffer, orientation):
        # Render a heading strip along the top

        self.task_timer.start()
        heading = orientation.get_onscreen_projection_heading()

        # Get the traffic, and bail out of we have none
        traffic_reports = AdsbTrafficClient.TRAFFIC_MANAGER.get_traffic_with_position(
        )

        if traffic_reports is None:
            self.task_timer.stop()
            return

        # Draw the heading bugs in reverse order so the traffic closest to
        # us will be the most visible
        traffic_bug_reports = sorted(traffic_reports,
                                     key=lambda traffic: traffic.distance,
                                     reverse=True)

        for traffic_report in traffic_bug_reports:
            if traffic_report.distance > imperial_occlude:
                continue

            try:
                altitude_delta = int(
                    (traffic_report.altitude - orientation.alt) / 100.0)

                # TEST - Ignore stuff crazy separated
                if math.fabs(altitude_delta) > 50:
                    continue
            finally:
                pass

            # Now find where to draw the reticle....
            reticle_x, reticle_y = self.__get_traffic_projection__(
                orientation, traffic_report)

            # Render using the Above us bug
            # target_bug_scale = 0.04
            target_bug_scale = get_reticle_size(traffic_report.distance)

            heading_bug_x = get_heading_bug_x(heading, traffic_report.bearing,
                                              self.__pixels_per_degree_x__)

            additional_info_text = self.__get_additional_target_text__(
                traffic_report, orientation)

            try:
                self.__render_heading_bug__(
                    framebuffer, str(traffic_report.get_identifer()),
                    additional_info_text, heading_bug_x, target_bug_scale,
                    traffic_report.is_on_ground(), traffic_report.get_age())
            except Exception as e:
                print(str(e))
        self.task_timer.stop()
class CompassAndHeadingBottomElement(CompassAndHeadingTopElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                 framebuffer_size):
        CompassAndHeadingTopElement.__init__(self, degrees_of_pitch,
                                             pixels_per_degree_y, font,
                                             framebuffer_size)
        self.task_timer = TaskTimer('CompassAndHeadingBottomElement')
        self.__line_top__ = framebuffer_size[1] - self.line_height
        self.__line_bottom__ = framebuffer_size[1]
        self.heading_text_y = self.__line_top__ - (font.get_height() * 1.2)

        self.compass_text_y = framebuffer_size[1] - \
            int(font.get_height() * 2)
        self.__border_width__ = 4
        text_height = font.get_height()
        border_vertical_size = (text_height >> 1) + (text_height >> 2)
        vertical_alignment_offset = int((border_vertical_size / 2.0) +
                                        0.5) + self.__border_width__
        half_width = int(self.__heading_text__[360][1][0] * 3.5)
        self.__heading_text_box_lines__ = [
            [
                self.__center_x__ - half_width, self.compass_text_y -
                border_vertical_size + vertical_alignment_offset
            ],
            [
                self.__center_x__ + half_width, self.compass_text_y -
                border_vertical_size + vertical_alignment_offset
            ],
            [
                self.__center_x__ + half_width, self.compass_text_y +
                border_vertical_size + vertical_alignment_offset
            ],
            [
                self.__center_x__ - half_width, self.compass_text_y +
                border_vertical_size + vertical_alignment_offset
            ]
        ]

    def __render_heading_mark__(self, framebuffer, x_pos, heading):
        pygame.draw.line(framebuffer, GREEN, [x_pos, self.__line_top__],
                         [x_pos, self.__line_bottom__], self.__border_width__)

        self.__render_heading_text__(framebuffer,
                                     utils.apply_declination(heading), x_pos,
                                     self.compass_text_y)

    def render(self, framebuffer, orientation):
        """
        Renders the current heading to the HUD.
        """

        self.task_timer.start()

        # Render a crude compass
        # Render a heading strip along the top

        heading = orientation.get_onscreen_projection_heading()

        if heading < 0:
            heading += 360

        if heading > 360:
            heading -= 360

        [
            self.__render_heading_mark__(framebuffer,
                                         heading_mark_to_render[0],
                                         heading_mark_to_render[1])
            for heading_mark_to_render in self.__heading_strip__[heading]
        ]

        # Render the text that is showing our AHRS and GPS headings
        heading_text = "{0} | {1}".format(
            utils.apply_declination(
                orientation.get_onscreen_projection_display_heading()),
            utils.apply_declination(orientation.gps_heading))

        rendered_text = self.__font__.render(heading_text, True, BLACK, GREEN)
        text_width, text_height = rendered_text.get_size()

        pygame.draw.polygon(framebuffer, GREEN,
                            self.__heading_text_box_lines__)

        framebuffer.blit(rendered_text,
                         (self.__center_x__ -
                          (text_width >> 1), self.compass_text_y))
        self.task_timer.stop()
Esempio n. 24
0
class CompassAndHeadingBottomElement(CompassAndHeadingTopElement):
    def __init__(
        self,
        degrees_of_pitch,
        pixels_per_degree_y,
        font,
        framebuffer_size
    ):
        CompassAndHeadingTopElement.__init__(
            self, degrees_of_pitch, pixels_per_degree_y, font, framebuffer_size)
        self.task_timer = TaskTimer('CompassAndHeadingBottomElement')
        self.__line_top__ = framebuffer_size[1] - self.line_height
        self.__line_bottom__ = framebuffer_size[1]
        self.heading_text_y = self.__line_top__ - (font.get_height() * 1.2)

        self._heading_box_y_ = framebuffer_size[1] - \
            int(font.get_height() * 2.8)
        self.compass_text_y = framebuffer_size[1] - \
            int(font.get_height())
        self.__border_width__ = 4
        text_height = font.get_height()
        border_vertical_size = (text_height >> 1) + (text_height >> 2)
        vertical_alignment_offset = int(
            (border_vertical_size / 2.0) + 0.5) + self.__border_width__
        half_width = int(self.__heading_text__[360][1][0] * 3.5)
        self.__heading_text_box_lines__ = [
            [self.__center_x__ - half_width,
             self._heading_box_y_ - border_vertical_size + vertical_alignment_offset],
            [self.__center_x__ + half_width,
             self._heading_box_y_ - border_vertical_size + vertical_alignment_offset],
            [self.__center_x__ + half_width,
             self._heading_box_y_ + border_vertical_size + vertical_alignment_offset],
            [self.__center_x__ - half_width,
             self._heading_box_y_ + border_vertical_size + vertical_alignment_offset]]

    def __render_heading_mark__(self, framebuffer, x_pos, heading):
        pygame.draw.line(
            framebuffer,
            GREEN,
            [x_pos, self.__line_top__],
            [x_pos, self.__line_bottom__],
            self.__border_width__)

        self.__render_heading_text__(
            framebuffer,
            heading,
            x_pos,
            self.compass_text_y)

    def render(self, framebuffer, orientation):
        """
        Renders the current heading to the HUD.
        """

        self.task_timer.start()

        # Render a crude compass
        # Render a heading strip along the top

        heading = orientation.get_onscreen_projection_heading()

        if isinstance(heading, Number):
            if heading < 0:
                heading += 360

            if heading > 360:
                heading -= 360

            [self.__render_heading_mark__(framebuffer, heading_mark_to_render[0], heading_mark_to_render[1])
             for heading_mark_to_render in self.__heading_strip__[heading]]

        self._render_hallow_heading_box_(
            orientation,
            framebuffer,
            self._heading_box_y_)
        self.task_timer.stop()
class AdsbTargetBugs(AdsbElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font, framebuffer_size):
        AdsbElement.__init__(
            self, degrees_of_pitch, pixels_per_degree_y, font, framebuffer_size)

        self.task_timer = TaskTimer('AdsbTargetBugs')
        self.__listing_text_start_y__ = int(self.__font__.get_height() * 4)
        self.__listing_text_start_x__ = int(
            self.__framebuffer_size__[0] * 0.01)
        self.__next_line_distance__ = int(font.get_height() * 1.5)
        self.__top_border__ = int(self.__height__ * 0.2)
        self.__bottom_border__ = self.__height__ - int(self.__height__ * 0.1)

    def __render_traffic_heading_bug__(self, traffic_report, heading, orientation, framebuffer):
        """
        Render a single heading bug to the framebuffer.

        Arguments:
            traffic_report {Traffic} -- The traffic we want to render a bug for.
            heading {int} -- Our current heading.
            orientation {Orientation} -- Our plane's current orientation.
            framebuffer {Framebuffer} -- What we are going to draw to.
        """

        heading_bug_x = get_heading_bug_x(
            heading, utils.apply_declination(traffic_report.bearing), self.__pixels_per_degree_x__)

        additional_info_text = self.__get_additional_target_text__(
            traffic_report, orientation)

        try:
            self.__render_info_card__(framebuffer,
                                      str(traffic_report.get_display_name()),
                                      additional_info_text,
                                      heading_bug_x,
                                      traffic_report.get_age())
        except Exception as ex:
            print("EX:{}".format(ex))
            pass

    def render(self, framebuffer, orientation):
        # Render a heading strip along the top

        self.task_timer.start()
        heading = orientation.get_onscreen_projection_heading()

        # Get the traffic, and bail out of we have none
        traffic_reports = HudDataCache.get_reliable_traffic()

        if traffic_reports is None:
            self.task_timer.stop()
            return

        traffic_reports = traffic_reports[:max_target_bugs]

        # Draw the heading bugs in reverse order so the traffic closest to
        # us will be the most visible
        traffic_reports.reverse()

        [self.__render_traffic_heading_bug__(
            traffic_report, heading, orientation, framebuffer) for traffic_report in traffic_reports]

        self.task_timer.stop()
Esempio n. 26
0
class SystemInfo(AhrsElement):
    def uses_ahrs(self):
        """
        The diagnostics page does not use AHRS.

        Returns:
            bool -- Always returns False.
        """

        return False

    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                 framebuffer_size):
        self.task_timer = TaskTimer('Time')
        self.__font__ = font
        self.font_height = font.get_height()
        self.__text_y_pos__ = framebuffer_size[1] - self.font_height
        self.__rhs__ = int(0.9 * framebuffer_size[0])

        self.__left_x__ = int(framebuffer_size[0] * 0.01)
        self.__center_x__ = framebuffer_size[0] >> 1
        self.__update_ip_timer__ = 0
        self.__update_temp_timer__ = 0
        self.__ip_address__ = get_ip_address()
        self.__cpu_temp__ = None
        self.__framebuffer_size__ = framebuffer_size
        self.__line_spacing__ = 1.01

    def __get_aithre_text_and_color__(self):
        """
        Gets the text and text color for the Aithre status.
        """

        if AithreClient.INSTANCE is None:
            return (DISCONNECTED_TEXT,
                    RED) if configuration.CONFIGURATION.aithre_enabled else (
                        DISABLED_TEXT, BLUE)

        co_report = AithreClient.INSTANCE.get_co_report()

        battery_text = 'UNK'
        battery_color = RED

        try:
            battery = co_report.battery
            battery_suffix = "%"
            if isinstance(battery, basestring):
                battery_suffix = ""
            if battery is not None:
                battery_color = get_aithre_battery_color(battery)
                battery_text = "bat:{}{}".format(battery, battery_suffix)
        except Exception as ex:
            battery_text = 'ERR'

        co_text = 'UNK'
        co_color = RED

        try:
            co_ppm = co_report.co

            if co_ppm is not None and OFFLINE_TEXT not in co_ppm:
                co_text = 'co:{}ppm'.format(co_ppm)
                co_color = get_aithre_co_color(co_ppm)
        except Exception as ex:
            co_text = 'ERR'

        color = RED if co_color is RED or battery_color is RED else \
            (YELLOW if co_color is YELLOW or battery_color is YELLOW else BLUE)

        return ('{} {}'.format(co_text, battery_text), color)

    def render(self, framebuffer, orientation):
        self.task_timer.start()

        self.__update_ip_timer__ -= 1
        if self.__update_ip_timer__ <= 0:
            self.__ip_address__ = get_ip_address()
            self.__update_ip_timer__ = 120

        self.__update_temp_timer__ -= 1
        if self.__update_temp_timer__ <= 0:
            self.__cpu_temp__ = get_cpu_temp()
            self.__update_temp_timer__ = 60

        info_lines = [
            ["VERSION     : ", [configuration.VERSION, YELLOW]],
            [
                "DECLINATION : ",
                [str(configuration.CONFIGURATION.get_declination()), BLUE]
            ],
            [
                "TRAFFIC     : ",
                [
                    configuration.CONFIGURATION.get_traffic_manager_address(),
                    BLUE
                ]
            ]
        ]

        addresses = self.__ip_address__[0].split(' ')
        for addr in addresses:
            info_lines.append(
                ["IP          : ", (addr, self.__ip_address__[1])])

        info_lines.append(
            ["AITHRE      : ",
             self.__get_aithre_text_and_color__()])

        # Status lines are pushed in as a stack.
        # First line in the array is at the bottom.
        # Last line in the array is towards the top.
        info_lines.append(["HUD CPU     : ", self.__cpu_temp__])
        info_lines.append([
            "DISPLAY RES : ",
            [
                "{} x {}".format(self.__framebuffer_size__[0],
                                 self.__framebuffer_size__[1]), BLUE
            ]
        ])

        render_y = self.__text_y_pos__

        for line in info_lines:
            # Draw the label in a standard color.
            texture_lhs = self.__font__.render(line[0], True, BLUE, BLACK)
            framebuffer.blit(texture_lhs, (0, render_y))
            size = texture_lhs.get_size()

            # Draw the value in the encoded colors.
            texture_rhs = self.__font__.render(line[1][0], True, line[1][1],
                                               BLACK)
            framebuffer.blit(texture_rhs, (size[0], render_y))

            render_y = render_y - (self.font_height * self.__line_spacing__)

        self.task_timer.stop()
Esempio n. 27
0
class HeadingTargetBugs(AdsbElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                 framebuffer_size):
        AdsbElement.__init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                             framebuffer_size)

        self.task_timer = TaskTimer('HeadingTargetBugs')
        self.__listing_text_start_y__ = int(self.__font__.get_height())
        self.__listing_text_start_x__ = int(self.__framebuffer_size__[0] *
                                            0.01)
        self.__next_line_distance__ = int(font.get_height() * 1.5)
        self.__top_border__ = int(self.__height__ * 0.2)
        self.__bottom_border__ = self.__height__ - int(self.__height__ * 0.1)

    def __get_additional_target_text__(self,
                                       time_until_drop=0.0,
                                       altitude_delta=0.0,
                                       distance_meters=0.0):
        """
        Returns a tuple of text to be rendered with the target card.
        
        Keyword Arguments:
            time_until_drop {float} -- The number of seconds until the flour bomb should be dropped. (default: {0.0})
            altitude_delta {float} -- The number of feet above the target. (default: {0.0})
            distance_meters {float} -- The distance (in meters) to the target. (default: {0.0})
        
        Returns:
            string[] -- Tuple of strings.
        """

        if altitude_delta < 0:
            altitude_delta = 0.0

        if time_until_drop < 0.0:
            altitude_delta = 0

        distance_text = self.__get_distance_string__(distance_meters)
        altitude_text = "{0:.1f}AGL".format(altitude_delta)
        time_until_drop = "{0:.1f}S".format(time_until_drop)

        return [altitude_text, distance_text, time_until_drop]

    def render(self, framebuffer, orientation):
        # Render a heading strip along the top

        self.task_timer.start()
        heading = orientation.get_onscreen_projection_heading()

        # Get the traffic, and bail out of we have none
        if targets.TARGET_MANAGER is None or targets.TARGET_MANAGER.targets is None:
            return

        for target_position in targets.TARGET_MANAGER.targets:
            ground_speed_ms = units.get_meters_per_second_from_mph(
                orientation.groundspeed)
            distance_miles = norden.get_distance(orientation.position,
                                                 target_position)
            distance_meters = units.get_meters_from_feet(
                units.get_feet_from_miles(distance_miles))
            time_to_target = norden.get_time_to_distance(
                distance_meters, ground_speed_ms)
            # NOTE:
            # Remember that the altitude off the AHRS is
            # in terms of MSL. That means that we need to
            # subtract out the altitude of the target.
            delta_altitude = orientation.alt - target_position[2]
            time_to_impact = norden.get_time_to_impact(
                units.get_meters_from_feet(delta_altitude))
            time_until_drop = time_to_target - time_to_impact
            # target_altitude_for_drop = units.get_feet_from_meters(
            #     norden.get_altitude(time_to_target))
            bearing_to_target = norden.get_bearing(target_position,
                                                   orientation.position)
            # time_to_impact_from_ideal_current_altitude = norden.get_time_to_impact(
            #    target_altitude_for_drop)

            # Render using the Above us bug
            # target_bug_scale = 0.04
            # target_bug_scale = get_reticle_size(distance_miles)

            heading_bug_x = get_heading_bug_x(heading, bearing_to_target,
                                              self.__pixels_per_degree_x__)

            additional_info_text = self.__get_additional_target_text__(
                time_until_drop, delta_altitude,
                units.get_feet_from_miles(distance_miles))

            self.__render_info_card__(
                framebuffer,
                "{0:.1f}".format(utils.apply_declination(bearing_to_target)),
                additional_info_text, heading_bug_x, False)
        self.task_timer.stop()
Esempio n. 28
0
class CompassAndHeadingTopElement(AhrsElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                 framebuffer_size):
        self.task_timer = TaskTimer('CompassAndHeadingTopElement')
        self.__framebuffer_size__ = framebuffer_size
        self.__center__ = (framebuffer_size[0] >> 1, framebuffer_size[1] >> 1)
        self.__long_line_width__ = self.__framebuffer_size__[0] * 0.2
        self.__short_line_width__ = self.__framebuffer_size__[0] * 0.1
        self.__pixels_per_degree_y__ = pixels_per_degree_y

        self.heading_text_y = int(font.get_height())
        self.compass_text_y = int(font.get_height())

        self.pixels_per_degree_x = framebuffer_size[0] / 360.0
        cardinal_direction_line_proportion = 0.2
        self.line_height = int(framebuffer_size[1] *
                               cardinal_direction_line_proportion)
        self.__font__ = font

        self.__heading_text__ = {}
        for heading in range(-1, 361):
            texture = self.__font__.render(str(heading), True, display.BLACK,
                                           display.YELLOW).convert()
            width, height = texture.get_size()
            self.__heading_text__[heading] = texture, (width >> 1, height >> 1)

        text_height = font.get_height()
        border_vertical_size = (text_height >> 1) + (text_height >> 2)
        half_width = int(self.__heading_text__[360][1][0] * 3.5)

        self.__center_x__ = self.__center__[0]

        self.__heading_text_box_lines__ = [
            [
                self.__center_x__ - half_width, self.compass_text_y +
                (1.5 * text_height) - border_vertical_size
            ],
            [
                self.__center_x__ + half_width, self.compass_text_y +
                (1.5 * text_height) - border_vertical_size
            ],
            [
                self.__center_x__ + half_width, self.compass_text_y +
                (1.5 * text_height) + border_vertical_size
            ],
            [
                self.__center_x__ - half_width, self.compass_text_y +
                (1.5 * text_height) + border_vertical_size
            ]
        ]

        self.__heading_strip_offset__ = {}
        for heading in range(0, 181):
            self.__heading_strip_offset__[heading] = int(
                self.pixels_per_degree_x * heading)

        self.__heading_strip__ = {}

        for heading in range(0, 361):
            self.__heading_strip__[heading] = self.__generate_heading_strip__(
                heading)

        self.__render_heading_mark_timer__ = TaskTimer("HeadingRender")

    def __generate_heading_strip__(self, heading):
        things_to_render = []
        for heading_strip in self.__heading_strip_offset__:
            to_the_left = (heading - heading_strip)
            to_the_right = (heading + heading_strip)

            displayed_left = utils.apply_declination(to_the_left)
            displayed_right = utils.apply_declination(to_the_right)
            if to_the_left < 0:
                to_the_left += 360

            if to_the_right > 360:
                to_the_right -= 360

            if (displayed_left == 0) or ((displayed_left % 90) == 0):
                line_x_left = self.__center_x__ - \
                    self.__heading_strip_offset__[heading_strip]
                things_to_render.append([line_x_left, to_the_left])

            if to_the_left == to_the_right:
                continue

            if (displayed_right % 90) == 0:
                line_x_right = self.__center_x__ + \
                    self.__heading_strip_offset__[heading_strip]
                things_to_render.append([line_x_right, to_the_right])

        return things_to_render

    def __render_heading_mark__(self, framebuffer, x_pos, heading):
        pygame.draw.line(framebuffer, display.GREEN, [x_pos, self.line_height],
                         [x_pos, 0], 4)

        self.__render_heading_text__(framebuffer,
                                     utils.apply_declination(heading), x_pos,
                                     self.compass_text_y)

    def render(self, framebuffer, orientation):
        """
        Renders the current heading to the HUD.
        """

        self.task_timer.start()

        # Render a crude compass
        # Render a heading strip along the top

        heading = orientation.get_onscreen_projection_heading()

        [
            self.__render_heading_mark__(framebuffer,
                                         heading_mark_to_render[0],
                                         heading_mark_to_render[1])
            for heading_mark_to_render in self.__heading_strip__[heading]
        ]

        # Render the text that is showing our AHRS and GPS headings
        cover_old_rendering_spaces = " "
        heading_text = "{0}{1} | {2}{0}".format(
            cover_old_rendering_spaces,
            str(
                int(
                    utils.apply_declination(
                        orientation.get_onscreen_projection_display_heading()))
            ).rjust(3),
            str(int(utils.apply_declination(
                orientation.gps_heading))).rjust(3))

        rendered_text = self.__font__.render(heading_text, True, display.GREEN,
                                             display.BLACK)
        text_width, text_height = rendered_text.get_size()

        framebuffer.blit(rendered_text, (self.__center_x__ -
                                         (text_width >> 1), text_height << 1))

        pygame.draw.lines(framebuffer, display.GREEN, True,
                          self.__heading_text_box_lines__, 2)
        self.task_timer.stop()

    def __render_heading_text__(self, framebuffer, heading, position_x,
                                position_y):
        """
        Renders the text with the results centered on the given
        position.
        """
        rendered_text, half_size = self.__heading_text__[heading]

        framebuffer.blit(
            rendered_text,
            (position_x - half_size[0], position_y - half_size[1]))
Esempio n. 29
0
class AdsbOnScreenReticles(AdsbElement):
    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font, framebuffer_size):
        AdsbElement.__init__(
            self, degrees_of_pitch, pixels_per_degree_y, font, framebuffer_size)

        self.task_timer = TaskTimer('AdsbOnScreenReticles')

        self.__listing_text_start_y__ = int(self.__font__.get_height() * 4)
        self.__listing_text_start_x__ = int(
            self.__framebuffer_size__[0] * 0.01)
        self.__next_line_distance__ = int(font.get_height() * 1.5)
        self.__max_reports__ = int(
            (self.__height__ - self.__listing_text_start_y__) / self.__next_line_distance__)
        self.__top_border__ = int(self.__height__ * 0.1)
        self.__bottom_border__ = self.__height__ - self.__top_border__

    def render(self, framebuffer, orientation):
        self.task_timer.start()
        # Get the traffic, and bail out of we have none
        traffic_reports = AdsbTrafficClient.TRAFFIC_MANAGER.get_traffic_with_position()

        if traffic_reports is None:
            self.task_timer.stop()
            return

        for traffic in traffic_reports:
            # Do not render reticles for things to far away
            if traffic.distance > imperial_occlude:
                continue

            if traffic.is_on_ground():
                continue

            identifier = traffic.get_identifer()

            # Find where to draw the reticle....
            reticle_x, reticle_y = self.__get_traffic_projection__(
                orientation, traffic)

            # Render using the Above us bug
            on_screen_reticle_scale = get_reticle_size(traffic.distance)
            reticle, reticle_size_px = self.get_onscreen_reticle(
                reticle_x, reticle_y, on_screen_reticle_scale)

            if reticle_y < self.__top_border__ or reticle_y > self.__bottom_border__ or \
                    reticle_x < 0 or reticle_x > self.__width__:
                continue
            else:
                reticle_x, reticle_y = self.__rotate_reticle__(
                    [[reticle_x, reticle_y]], orientation.roll)[0]

                self.__render_target_reticle__(
                    framebuffer, identifier, reticle_x, reticle_y, reticle, orientation.roll, reticle_size_px)
        self.task_timer.stop()

    def __render_target_reticle__(self, framebuffer, identifier, center_x, center_y, reticle_lines, roll, reticle_size_px):
        """
        Renders a targetting reticle on the screen.
        Assumes the X/Y projection has already been performed.
        """

        border_space = int(reticle_size_px * 1.2)

        if center_y < border_space:
            center_y = border_space

        if center_y > (self.__height__ - border_space):
            center_y = int(self.__height__ - border_space)

        pygame.draw.lines(framebuffer,
                          BLACK, True, reticle_lines, 20)
        pygame.draw.lines(framebuffer,
                          RED, True, reticle_lines, 10)

    def __rotate_reticle__(self, reticle, roll):
        """
        Takes a series of line segments and rotates them (roll) about
        the screen's center

        Arguments:
            reticle {list of tuples} -- The line segments
            roll {float} -- The amount to rotate about the center by.

        Returns:
            list of lists -- The new list of line segments
        """

        # Takes the roll in degrees
        # Example input..
        # [
        #     [center_x, center_y - size],
        #     [center_x + size, center_y],
        #     [center_x, center_y + size],
        #     [center_x - size, center_y]
        # ]

        translated_points = []

        int_roll = int(-roll)
        cos_roll = COS_RADIANS_BY_DEGREES[int_roll]
        sin_roll = SIN_RADIANS_BY_DEGREES[int_roll]
        ox, oy = self.__center__

        for x_y in reticle:
            px, py = x_y

            qx = ox + cos_roll * (px - ox) - sin_roll * (py - oy)
            qy = oy + sin_roll * (px - ox) + cos_roll * (py - oy)

            translated_points.append([qx, qy])

        return translated_points
Esempio n. 30
0
class AdsbTrafficListing(AdsbElement):
    def uses_ahrs(self):
        """
        Does this element use AHRS data to render?

        Returns:
            bool -- False as this element does not use AHRS data.
        """

        return False

    def __init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                 framebuffer_size):
        AdsbElement.__init__(self, degrees_of_pitch, pixels_per_degree_y, font,
                             framebuffer_size)

        self.task_timer = TaskTimer('AdsbTargetBugs')
        self.__listing_text_start_y__ = int(self.__font__.get_height() * 2)
        self.__listing_text_start_x__ = int(self.__framebuffer_size__[0] *
                                            0.01)
        self.__next_line_distance__ = int(font.get_height() * 1.5)
        self.__max_reports__ = int(
            (self.__height__ - self.__listing_text_start_y__) /
            self.__next_line_distance__)
        self.__top_border__ = int(self.__height__ * 0.2)
        self.__bottom_border__ = self.__height__ - int(self.__height__ * 0.1)

    def __get_padded_traffic_reports__(self, traffic_reports):
        max_identifier_length = 0
        max_bearing_length = 0
        max_altitude_length = 0
        max_distance_length = 0
        pre_padded_text = []

        max_identifier_length, max_distance_length, max_altitude_length = self.__get_pre_padded_text_reports__(
            traffic_reports, max_identifier_length, max_bearing_length,
            max_altitude_length, max_distance_length, pre_padded_text)

        out_padded_reports = []

        for report in pre_padded_text:
            identifier = report[0]
            bearing = str(utils.apply_declination(float(report[1])))
            distance_text = report[2]
            altitude = report[3]
            icao = report[4]

            # if self.__show_list__:
            traffic_report = "{0} {1} {2} {3}".format(
                identifier.ljust(max_identifier_length), bearing.rjust(3),
                distance_text.rjust(max_distance_length),
                altitude.rjust(max_altitude_length))
            out_padded_reports.append((icao, traffic_report))

        return out_padded_reports

    def __get_pre_padded_text_reports__(self, traffic_reports,
                                        max_identifier_length,
                                        max_bearing_length,
                                        max_altitude_length,
                                        max_distance_length, pre_padded_text):
        report_count = 0
        for traffic in traffic_reports:
            # Do not list traffic too far away
            if report_count > self.__max_reports__ or traffic.distance > imperial_occlude or traffic.is_on_ground(
            ):
                continue

            report_count += 1

            identifier = str(traffic.get_identifer())
            altitude_delta = int(traffic.altitude / 100.0)
            distance_text = self.__get_distance_string__(traffic.distance)
            delta_sign = ''
            if altitude_delta > 0:
                delta_sign = '+'
            altitude_text = "{0}{1}".format(delta_sign, altitude_delta)
            bearing_text = "{0:.0f}".format(traffic.bearing)

            identifier_length = len(identifier)
            bearing_length = len(bearing_text)
            altitude_length = len(altitude_text)
            distance_length = len(distance_text)

            if identifier_length > max_identifier_length:
                max_identifier_length = identifier_length

            if bearing_length > max_bearing_length:
                max_bearing_length = bearing_length

            if altitude_length > max_altitude_length:
                max_altitude_length = altitude_length

            if distance_length > max_distance_length:
                max_distance_length = distance_length

            pre_padded_text.append([
                identifier, bearing_text, distance_text, altitude_text,
                traffic.icao_address
            ])

        return max_identifier_length, max_distance_length, max_altitude_length

    def render(self, framebuffer, orientation):
        # Render a heading strip along the top

        self.task_timer.start()

        # Get the traffic, and bail out of we have none
        traffic_reports = AdsbTrafficClient.TRAFFIC_MANAGER.get_traffic_with_position(
        )

        if traffic_reports is None:
            self.task_timer.stop()
            return

        # Render a list of traffic that we have positions
        # for, along with the tail number

        y_pos = self.__listing_text_start_y__
        x_pos = self.__listing_text_start_x__

        padded_traffic_reports = self.__get_padded_traffic_reports__(
            traffic_reports)

        if len(padded_traffic_reports) == 0:
            framebuffer.blit(
                HudDataCache.get_cached_text_texture("NO TRAFFIC",
                                                     self.__font__),
                (x_pos, y_pos))

        for identifier, traffic_report in padded_traffic_reports:
            traffic_text_texture = HudDataCache.get_cached_text_texture(
                traffic_report, self.__font__)

            framebuffer.blit(traffic_text_texture, (x_pos, y_pos))

            y_pos += self.__next_line_distance__
        self.task_timer.stop()