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.

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.

-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__':