From 3e595b55e1485739cc783777ccd6cb030b0c5c79 Mon Sep 17 00:00:00 2001 From: John Marzulli Date: Tue, 11 Sep 2018 09:33:55 -0700 Subject: [PATCH] Code to improve performance and make heading bugs independant. --- heads_up_display.py | 32 +++++--- lib/colors.py | 7 +- lib/recurring_task.py | 53 +++++++++---- readme.md | 83 ++++++++++++--------- restful_host.py | 5 ++ traffic.py | 27 +++---- views.json | 80 +++++++++----------- views/adsb_target_bugs.py | 4 - views/adsb_traffic_listing.py | 36 +++++---- views/artificial_horizon.py | 38 +++++----- views/compass_and_heading_bottom_element.py | 5 +- views/compass_and_heading_top_element.py | 5 +- views/level_reference.py | 4 +- 13 files changed, 208 insertions(+), 171 deletions(-) diff --git a/heads_up_display.py b/heads_up_display.py index 6ced826..2aca17f 100755 --- a/heads_up_display.py +++ b/heads_up_display.py @@ -85,10 +85,19 @@ def run(self): while self.tick(clock): pass finally: - self.__connection_manager__.shutdown() pygame.display.quit() - sys.exit() + print('Shutting down Connection Manager') + self.__connection_manager__.shutdown() + + print('Shutting down HTTP') + self.web_server.stop() + + RecurringTask.kill_all() + + print('Finished with run()') + + return 0 def __render_view_title__(self, text): try: @@ -170,8 +179,10 @@ def tick(self, clock): # to overdraw the pitch lines # and improve readability self.log("---- VIEW RENDER START ----") - for hud_element in view: - self.__render_view_element__(hud_element, orientation) + + [self.__render_view_element__( + hud_element, orientation) for hud_element in view] + self.log("---------------------------") except Exception as e: self.warn("LOOP:" + str(e)) @@ -441,8 +452,9 @@ def __init__(self, logger): if self.__logger__ is not None: logger = self.__logger__.logger - web_server = restful_host.HudServer() - RecurringTask("rest_host", 0.1, web_server.run, start_immediate=False) + self.web_server = restful_host.HudServer() + RecurringTask("rest_host", 0.1, self.web_server.run, + start_immediate=False) RecurringTask("purge_old_traffic", 10.0, self.__purge_old_reports__, start_immediate=False) RecurringTask("update_traffic", 0.1, @@ -485,9 +497,11 @@ def __handle_input__(self): bool -- True if the loop should continue, False if it should quit. """ - for event in pygame.event.get(): - if not self.__handle_key_event__(event): - return False + events = pygame.event.get() + event_handling_repsonses = map(self.__handle_key_event__, events) + + if False in event_handling_repsonses: + return False self.__clamp_view__() diff --git a/lib/colors.py b/lib/colors.py index 6e56c29..c259d94 100644 --- a/lib/colors.py +++ b/lib/colors.py @@ -80,11 +80,8 @@ def get_color_mix(left_color, right_color, proportion): if array_length != len(right_color): return left_color - new_color = [] - - for index in range(0, array_length): - new_color.append( - int(interpolate(left_color[index], right_color[index], proportion))) + indices = range(0, array_length) + new_color = [int(interpolate(left_color[index], right_color[index], proportion)) for index in indices] return new_color diff --git a/lib/recurring_task.py b/lib/recurring_task.py index 98f49b0..ea95fdd 100755 --- a/lib/recurring_task.py +++ b/lib/recurring_task.py @@ -15,22 +15,37 @@ class RecurringTask(object): Object to control and handle a recurring task. """ - def stop(self): - try: - if self.__last_task__ is not None: - self.pause() - self.__last_task__.cancel() + __SPAWNED_TASKS__ = [] - return True - except: - return False + @staticmethod + def kill_all(): + timeout_sec = 5 + for task in RecurringTask.__SPAWNED_TASKS__: # list of your processes + print('Killing task {}'.format(task.__task_name__)) + + try: + task.stop() + except Exception as ex: + print('While shutting down EX:{}'.format(ex)) + + def stop(self): + self.__lock__.acquire() + self.__is_alive__ = False + self.__is_running__ = False + self.__lock__.release() def is_running(self): """ Returns True if the task is running. """ - return self.__task_callback__ is not None and self.__is_running__ + self.__lock__.acquire() + result = self.__is_alive__ \ + and self.__task_callback__ is not None \ + and self.__is_running__ + self.__lock__.release() + + return result def start(self): """ @@ -49,8 +64,9 @@ def pause(self): Pauses the task if it is running. """ - if self.is_running(): - self.__is_running__ = False + self.__lock__.acquire() + self.__is_running__ = False + self.__lock__.release() def __run_task__(self): """ @@ -60,10 +76,13 @@ def __run_task__(self): self.__last_task__ = threading.Thread(target=self.__run_loop__) self.__last_task__.start() + RecurringTask.__SPAWNED_TASKS__.append(self) + def __run_loop__(self): - while True: + while self.__is_alive__: if self.__is_running__ and self.__task_callback__ is not None: try: + self.__lock__.acquire() self.__task_callback__() except Exception as e: # + sys.exc_info()[0] @@ -73,8 +92,14 @@ def __run_loop__(self): self.__logger__.info(error_mesage) else: print(error_mesage) + finally: + self.__lock__.release() - time.sleep(int(self.__task_interval__)) + self.__lock__.acquire() + try: + time.sleep(int(self.__task_interval__) if self.__is_alive__ and self.__is_running__ else 0) + finally: + self.__lock__.release() def __init__(self, task_name, task_interval, task_callback, logger=None, start_immediate=False): """ @@ -86,8 +111,10 @@ def __init__(self, task_name, task_interval, task_callback, logger=None, start_i self.__task_interval__ = task_interval self.__task_callback__ = task_callback self.__logger__ = logger + self.__is_alive__ = True self.__is_running__ = False self.__last_task__ = None + self.__lock__ = threading.Lock() if start_immediate: self.start() diff --git a/readme.md b/readme.md index 067ab3b..aab23f2 100755 --- a/readme.md +++ b/readme.md @@ -18,9 +18,9 @@ Using the "HUDLY Classic" projector and a Raspberry Pi 3. Estimated cost is $300 -* $40 for RaspberryPi 3 -* $215 for HUDLY projector -* Fans, case, cables +- $40 for RaspberryPi 3 +- $215 for HUDLY projector +- Fans, case, cables Requires your aircraft to have a "12 Volt" cigarette power outlet. @@ -28,17 +28,17 @@ Requires your aircraft to have a "12 Volt" cigarette power outlet. A self contained system that uses a 3D printed case and teleprompter glass. This version can be built for the cost of a Raspberry Pi and the 3D print. -*NOTE:* This version does have visibility issues in daylight conditions. The HUDLY Version is fully daylight visible. +_NOTE:_ This version does have visibility issues in daylight conditions. The HUDLY Version is fully daylight visible. ![Teleprompter Glass Version In Flight](media/in_flight.jpg) Estimated Cost is $140 -* $40 for a RaspberryPi 3 -* $45 for the LCD screen -* $20 for Teleprompter Glass and shipping. -* Cost of 3D printing the special case. -* Cables +- $40 for a RaspberryPi 3 +- $45 for the LCD screen +- $20 for Teleprompter Glass and shipping. +- Cost of 3D printing the special case. +- Cables Can be powered by a USB powerbank or USB power. @@ -47,7 +47,7 @@ Can be powered by a USB powerbank or USB power. You may use a number pad as input device. I used velcro to secure the number pad to my dashboard. | Key | Action | -|-----------|------------------------------------------------------------------------------| +| --------- | ---------------------------------------------------------------------------- | | Backspace | Tell the Stratux that you are in a level position. Resets the AHRS to level. | | + | Next view | | - | Previous view | @@ -57,10 +57,10 @@ You may use a number pad as input device. I used velcro to secure the number pad ## Views -* Traffic -* AHRS -* Traffic List (Text only) -* Blank +- Traffic +- AHRS +- Traffic List (Text only) +- Blank ### Traffic View @@ -84,7 +84,7 @@ The closer the traffic, the larger the heading bug. This shows you all of the traffic with reliable ADSB data. -*NOTE: Any planes further than 10SM are excluded from this view.* +_NOTE: Any planes further than 10SM are excluded from this view._ It attempts to prioritize traffic by distance. @@ -121,9 +121,9 @@ We are experiencing 0.8Gs, less than normal gravity. ![Traffic View Screenshot](media/traffic_listing_view.jpg) -This shows us *at most* the five closest planes. +This shows us _at most_ the five closest planes. -*NOTE: Any planes further than 10SM are excluded from this view.* +_NOTE: Any planes further than 10SM are excluded from this view._ Here we see N435SP is the closest plane, bearing 345, 1.0 statute miles away, and 1,300' above us. @@ -137,26 +137,26 @@ A blank screen so no information is displayed. ### All Builds -*NOTE:* This _does not_ include a power source. You will need to supply ship power from a 5V USB port or from a battery. +_NOTE:_ This _does not_ include a power source. You will need to supply ship power from a 5V USB port or from a battery. -* [Raspberry Pi 3](https://www.amazon.com/Raspberry-Pi-RASPBERRYPI3-MODB-1GB-Model-Motherboard/dp/B01CD5VC92/ref=sr_1_3?s=electronics&ie=UTF8&qid=1529215701&sr=1-3&keywords=raspberry+pi+3) -* [Case For Raspberry Pi](https://www.amazon.com/iPhoenix-Raspberry-White-Compatible-Model/dp/B06XQSXZ97/ref=sr_1_3?s=electronics&dd=iYEspjjyeRXfqDW9BHwJFw%2C%2C&ddc_refnmnt=pfod&ie=UTF8&qid=1529215794&sr=1-3&keywords=white+raspberry+pi+3+case&refinements=p_97%3A11292772011) -* [Cooling Fan for Raspberry Pi](https://www.amazon.com/gp/product/B075R4S9GH/ref=od_aui_detailpages00?ie=UTF8&psc=1) -* [Micro USB Cable](https://www.amazon.com/AmazonBasics-Male-Micro-Cable-Black/dp/B0711PVX6Z/ref=sr_1_6?s=electronics&ie=UTF8&qid=1529215888&sr=1-6&keywords=micro+usb+cable) -* [Micro SD Card](https://www.amazon.com/SanDisk-Ultra-Micro-Adapter-SDSQUNC-016G-GN6MA/dp/B010Q57SEE/ref=sr_1_10?s=pc&ie=UTF8&qid=1529215944&sr=1-10&keywords=micro+sd+card) -* [Rottay Mechanical Keypad](https://www.amazon.com/Number-Rottay-Mechanical-Numeric-backlit/dp/B076FTSY6J/ref=sr_1_3?ie=UTF8&qid=1529215627&sr=8-3&keywords=mechanical+keypad) +- [Raspberry Pi 3](https://www.amazon.com/Raspberry-Pi-RASPBERRYPI3-MODB-1GB-Model-Motherboard/dp/B01CD5VC92/ref=sr_1_3?s=electronics&ie=UTF8&qid=1529215701&sr=1-3&keywords=raspberry+pi+3) +- [Case For Raspberry Pi](https://www.amazon.com/iPhoenix-Raspberry-White-Compatible-Model/dp/B06XQSXZ97/ref=sr_1_3?s=electronics&dd=iYEspjjyeRXfqDW9BHwJFw%2C%2C&ddc_refnmnt=pfod&ie=UTF8&qid=1529215794&sr=1-3&keywords=white+raspberry+pi+3+case&refinements=p_97%3A11292772011) +- [Cooling Fan for Raspberry Pi](https://www.amazon.com/gp/product/B075R4S9GH/ref=od_aui_detailpages00?ie=UTF8&psc=1) +- [Micro USB Cable](https://www.amazon.com/AmazonBasics-Male-Micro-Cable-Black/dp/B0711PVX6Z/ref=sr_1_6?s=electronics&ie=UTF8&qid=1529215888&sr=1-6&keywords=micro+usb+cable) +- [Micro SD Card](https://www.amazon.com/SanDisk-Ultra-Micro-Adapter-SDSQUNC-016G-GN6MA/dp/B010Q57SEE/ref=sr_1_10?s=pc&ie=UTF8&qid=1529215944&sr=1-10&keywords=micro+sd+card) +- [Rottay Mechanical Keypad](https://www.amazon.com/Number-Rottay-Mechanical-Numeric-backlit/dp/B076FTSY6J/ref=sr_1_3?ie=UTF8&qid=1529215627&sr=8-3&keywords=mechanical+keypad) ### Recommended HUDLY Build -* [HUDLY Classic](https://gethudly.com/classic) - * *NOTE:* You will most likely want to order the cigarette lighter powered version. - * *NOTE:* You can order the iPhone or Android cable option based on your phone. It will not matter for the StratuxHud. -* [12" HDMI Cable](https://www.amazon.com/StarTech-com-0-3m-Short-Speed-Cable/dp/B00K3HF276/ref=sr_1_11?s=electronics&ie=UTF8&qid=1529216822&sr=1-11&keywords=short%2Bhdmi%2Bcable&th=1) +- [HUDLY Classic](https://gethudly.com/classic) + - _NOTE:_ You will most likely want to order the cigarette lighter powered version. + - _NOTE:_ You can order the iPhone or Android cable option based on your phone. It will not matter for the StratuxHud. +- [12" HDMI Cable](https://www.amazon.com/StarTech-com-0-3m-Short-Speed-Cable/dp/B00K3HF276/ref=sr_1_11?s=electronics&ie=UTF8&qid=1529216822&sr=1-11&keywords=short%2Bhdmi%2Bcable&th=1) ### 3D Print Build -* [Teleprompter Glass Sample of both thickness of the 60/40 glass]() -* [SunFounder 5" TFT LCD](https://www.amazon.com/SunFounder-Monitor-Display-800X480-Raspberry/dp/B01HXSFIH6) +- [Teleprompter Glass Sample of both thickness of the 60/40 glass](https://telepromptermirror.com/sample/) +- [SunFounder 5" TFT LCD](https://www.amazon.com/SunFounder-Monitor-Display-800X480-Raspberry/dp/B01HXSFIH6) ## Install instructions @@ -182,6 +182,17 @@ A blank screen so no information is displayed. 18. "Other" -> "English US" -> "Default" -> "No compose" -> "Yes" 19. "Finish" +#### Raspberry Pi 3B+ + +If you are using a 3B+, it may suffer from undervoltage alerts. +These may be relieved by the following command to update your Linux install to the latest: + +```bash +sudo apt-get update && sudo apt-get dist-upgrade -y +``` + +Make sure you are using a high quality power cable if you are using a Pi 3B+ + ### Install Software 1. Enter `ping google.com`. Press ctrl+c after a while. This will confirm that you have internet access. If you do not, then use rasp-config to re-enter your wi-fi @@ -200,9 +211,9 @@ A blank screen so no information is displayed. 14. Select "Nano" (Option 1) 15. Enter the following text at the _bottom_ of the file: - ```code - @reboot sudo python /home/pi/StratuxHud/stratux_hud.py & - ``` +```bash +@reboot sudo python /home/pi/StratuxHud/stratux_hud.py & +``` 1. Save and quit. @@ -233,9 +244,9 @@ The initial project was inspired by Kris Knigga's PyAhrs project > 1, size_y >> 1)) + def __render_reference_line__(self, framebuffer, reference_angle, draw_line, rot_text, orientation): + 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__: + return + + draw_line(framebuffer, GREEN, False, line_coords, 4) + + text, half_size = self.__pitch_elements__[reference_angle] + text = rot_text(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)) + def render(self, framebuffer, orientation): self.task_timer.start() draw_line = pygame.draw.lines rot_text = pygame.transform.rotate - pitch = orientation.pitch - roll = orientation.roll - blit = framebuffer.blit for reference_angle in self.__pitch_elements__: - line_coords, line_center = self.__get_line_coords__( - pitch, 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 - - draw_line(framebuffer, GREEN, False, line_coords, 4) - - text, half_size = self.__pitch_elements__[reference_angle] - text = rot_text(text, roll) - half_x, half_y = half_size - center_x, center_y = line_center - - blit(text, (center_x - half_x, center_y - half_y)) + self.__render_reference_line__(framebuffer, reference_angle, draw_line, rot_text, orientation) self.task_timer.stop() def __get_cos__(self, degrees): diff --git a/views/compass_and_heading_bottom_element.py b/views/compass_and_heading_bottom_element.py index 1714d6b..1831fb3 100644 --- a/views/compass_and_heading_bottom_element.py +++ b/views/compass_and_heading_bottom_element.py @@ -65,9 +65,8 @@ def render(self, framebuffer, orientation): if heading > 360: heading -= 360 - for heading_mark_to_render in self.__heading_strip__[heading]: - self.__render_heading_mark__( - framebuffer, heading_mark_to_render[0], heading_mark_to_render[1]) + [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( diff --git a/views/compass_and_heading_top_element.py b/views/compass_and_heading_top_element.py index 45a612c..daabc64 100644 --- a/views/compass_and_heading_top_element.py +++ b/views/compass_and_heading_top_element.py @@ -114,9 +114,8 @@ def render(self, framebuffer, orientation): heading = orientation.get_onscreen_projection_heading() - for heading_mark_to_render in self.__heading_strip__[heading]: - self.__render_heading_mark__( - framebuffer, heading_mark_to_render[0], heading_mark_to_render[1]) + [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 = " " diff --git a/views/level_reference.py b/views/level_reference.py index eed8170..3163558 100644 --- a/views/level_reference.py +++ b/views/level_reference.py @@ -34,9 +34,7 @@ def render(self, framebuffer, orientation): """ self.task_timer.start() - for line in self.level_reference_lines: - pygame.draw.lines(framebuffer, - WHITE, False, line, 6) + [pygame.draw.lines(framebuffer, WHITE, False, line, 6) for line in self.level_reference_lines] self.task_timer.stop() if __name__ == '__main__':