├── .github
└── FUNDING.yml
├── .gitignore
├── .python-version
├── CHANGELOG.md
├── LICENSE
├── README.md
├── config_example.cfg
├── docs
├── HUD Type B USB Power Connections.png
├── Instructions for Installing the Latest 64.md
├── Python Install Guide for Windows Development.md
├── dev
│ └── profiling.MD
├── efis_HUD_rv8.jpg
├── efis_cockpit1.jpeg
├── efis_cockpit2.jpeg
├── efis_data.MD
├── efis_screenshot.png
├── efis_screenshot_text.png
├── how_to_intall.md
├── hud_animated_example.gif
├── imgs
│ ├── logo_black.png
│ ├── logo_txt_large.txt
│ ├── logo_txt_small.txt
│ ├── pinz1.jpeg
│ ├── tronview_thumb.png
│ └── tronview_trimed.png
├── input_analog.MD
├── old_readme.MD
├── quick_start_macos.MD
├── quick_start_pi.MD
├── quick_start_pi_image.MD
├── rpi_setup.md
├── screen_F18.MD
├── screenshots
│ ├── pi_imager1.png
│ ├── pi_imager2.png
│ ├── pi_imager3.png
│ ├── screenshot_1.jpg
│ ├── screenshot_2_editor.png
│ ├── tv3d.png
│ └── tv_mesh.png
└── windows_install_howto.md
├── lib
├── __init__.py
├── common
│ ├── __init__.py
│ ├── assets
│ │ └── tronview_logo1.png
│ ├── dataship
│ │ ├── dataship.py
│ │ ├── dataship_air.py
│ │ ├── dataship_analog.py
│ │ ├── dataship_engine_fuel.py
│ │ ├── dataship_gps.py
│ │ ├── dataship_imu.py
│ │ ├── dataship_nav.py
│ │ └── dataship_targets.py
│ ├── event_manager.py
│ ├── graphic
│ │ ├── __init__.py
│ │ ├── edit_EditEventsWindow.py
│ │ ├── edit_EditOptionsBar.py
│ │ ├── edit_EditToolBar.py
│ │ ├── edit_TronViewScreenObject.py
│ │ ├── edit_clone.py
│ │ ├── edit_dropdown.py
│ │ ├── edit_find_module.py
│ │ ├── edit_help.py
│ │ ├── edit_history.py
│ │ ├── edit_mode.py
│ │ ├── edit_rulers.py
│ │ ├── edit_save_load.py
│ │ ├── edit_textinput.py
│ │ ├── graphic_mode.py
│ │ ├── growl_manager.py
│ │ ├── objects
│ │ │ └── TronViewImageObject.py
│ │ └── theme.json
│ ├── helpers
│ │ └── faa_aircraft_database.py
│ ├── shared.py
│ └── text
│ │ ├── __init__.py
│ │ └── text_mode.py
├── geomag
│ ├── WMM.COF
│ ├── __init__.py
│ └── geomag.py
├── hud_graphics.py
├── hud_text.py
├── hud_utils.py
├── inputs
│ ├── __init__.py
│ ├── _example_data
│ │ ├── Garmin G5 serial capture.txt
│ │ ├── MGL_Flight1.bin
│ │ ├── MGL_G430_Data_3Feb19_v7_Horz_Vert_Nedl_come_to_center.bin
│ │ ├── MGL_LOG092921_1500_1st_Flt.bin
│ │ ├── MGL_V10.bin
│ │ ├── MGL_V11.bin
│ │ ├── MGL_V12.bin
│ │ ├── MGL_V2.bin
│ │ ├── MGL_V3.bin
│ │ ├── MGL_V4.bin
│ │ ├── MGL_V5.bin
│ │ ├── MGL_V6.bin
│ │ ├── MGL_V7.bin
│ │ ├── MGL_V8.bin
│ │ ├── MGL_V9.bin
│ │ ├── bno085_1.dat
│ │ ├── dynon_d100_RV4.txt
│ │ ├── dynon_d100_data1.txt
│ │ ├── dynon_skyview_data1.txt
│ │ ├── dynon_skyview_data2.txt
│ │ ├── flyonspeed2efisdata_data1.csv
│ │ ├── g3x_1.dat
│ │ ├── g3x_aoa.dat
│ │ ├── g3x_aoa_00.dat
│ │ ├── g3x_aoa_10.dat
│ │ ├── g3x_aoa_10_99.dat
│ │ ├── g3x_aoa_20.dat
│ │ ├── g3x_aoa_30.dat
│ │ ├── g3x_aoa_40.dat
│ │ ├── g3x_aoa_49.dat
│ │ ├── g3x_aoa_50.dat
│ │ ├── g3x_aoa_55.dat
│ │ ├── g3x_aoa_60.dat
│ │ ├── g3x_aoa_68.dat
│ │ ├── g3x_aoa_70.dat
│ │ ├── g3x_aoa_79.dat
│ │ ├── g3x_aoa_80.dat
│ │ ├── g3x_aoa_90.dat
│ │ ├── g3x_aoa_99.dat
│ │ ├── g3x_fltAOAdata1.dat
│ │ ├── garmin_g3x_data1.txt
│ │ ├── mgl_2.dat
│ │ ├── mgl_4.dat
│ │ ├── mgl_5.dat
│ │ ├── mgl_6.dat
│ │ ├── mgl_7.dat
│ │ ├── mgl_8.dat
│ │ ├── mgl_9.dat
│ │ ├── mgl_G430_v7_Horz_Vert_Nedl_come_to_center.bin
│ │ ├── mgl_chase_rv6_1.dat
│ │ ├── mgl_chase_rv6_2.dat
│ │ ├── mgl_chase_rv6_3.dat
│ │ ├── mgl_data1.bin
│ │ ├── stratux_1.dat
│ │ ├── stratux_2.dat
│ │ ├── stratux_4.dat
│ │ ├── stratux_5.dat
│ │ ├── stratux_54.dat
│ │ ├── stratux_57.dat
│ │ ├── stratux_6.dat
│ │ ├── stratux_7.dat
│ │ ├── stratux_8.dat
│ │ ├── stratux_9.dat
│ │ ├── stratux_chase_rv6_1.dat
│ │ ├── stratux_chase_rv6_2.dat
│ │ └── stratux_chase_rv6_3.dat
│ ├── _input.py
│ ├── _input_file_utils.py
│ ├── _utils.py
│ ├── adc_ads1115.py
│ ├── gyro_i2c_bno055.py
│ ├── gyro_i2c_bno085.py
│ ├── gyro_joystick.py
│ ├── gyro_virtual.py
│ ├── levil_wifi.py
│ ├── meshtastic.py
│ ├── serial_d100.py
│ ├── serial_flyonspeed2efisdata.py
│ ├── serial_g3x.py
│ ├── serial_grt_eis.py
│ ├── serial_logger.py
│ ├── serial_mgl.py
│ ├── serial_nmea.py
│ ├── serial_onspeedaoa.py
│ ├── serial_skyview.py
│ └── stratux_wifi.py
├── main_kivy.py
├── main_sphere.py
├── modules
│ ├── __init__.py
│ ├── _module.py
│ ├── efis
│ │ ├── __init__.py
│ │ ├── artificalhorz
│ │ │ ├── .DS_Store
│ │ │ ├── __init__.py
│ │ │ ├── artificalhorz.py
│ │ │ ├── attitude-indicator-1280.png
│ │ │ ├── horiz.bmp
│ │ │ ├── horiz.png
│ │ │ ├── horiz_square.bmp
│ │ │ └── lowres.png
│ │ ├── buoy_manager
│ │ │ └── buoy_manager.py
│ │ ├── traffic_bar
│ │ │ └── traffic_bar.py
│ │ └── trafficscope
│ │ │ └── trafficscope.py
│ ├── general
│ │ ├── __init__.py
│ │ ├── gauge_arc
│ │ │ └── gauge_arc.py
│ │ ├── gauge_bar
│ │ │ └── gauge_bar.py
│ │ ├── image
│ │ │ └── image.py
│ │ ├── object3d
│ │ │ └── object3d.py
│ │ ├── play_controls
│ │ │ └── play_controls.py
│ │ ├── text
│ │ │ └── text.py
│ │ ├── text_segments
│ │ │ └── text_segments.py
│ │ └── video_in
│ │ │ └── video_in.py
│ ├── gui
│ │ └── menu
│ │ │ └── _menu.py
│ └── hud
│ │ ├── __init__.py
│ │ ├── aoa
│ │ └── aoa.py
│ │ ├── cdi
│ │ └── cdi.py
│ │ ├── gcross
│ │ └── gcross.py
│ │ ├── heading
│ │ └── heading.py
│ │ ├── horizon
│ │ └── horizon.py
│ │ ├── horizon_v2
│ │ └── horizon_v2.py
│ │ ├── hsi
│ │ ├── hsi.py
│ │ ├── tick_m.jpg
│ │ └── tick_m.png
│ │ ├── rollindicator
│ │ ├── rollindicator.py
│ │ ├── tick_w.bmp
│ │ └── tick_w.png
│ │ ├── slipskid
│ │ └── slipskid.py
│ │ └── wind
│ │ ├── arrow_g.bmp
│ │ ├── arrow_g.png
│ │ └── wind.py
├── modules_kivy
│ └── horizon_3d
│ │ ├── __init__.py
│ │ └── horizon_3d.py
├── screens
│ ├── __init__.py
│ └── templates
│ │ ├── DataShipAll_LG.json
│ │ ├── DataShipAll_SM.json
│ │ ├── DataShipIMU.json
│ │ ├── EFIS_XL.json
│ │ ├── Engine.json
│ │ ├── Engine_color.json
│ │ ├── TrafficScope_LG.json
│ │ ├── TrafficScope_SM.json
│ │ └── default.json
├── smartdisplay.py
├── util
│ ├── __init__.py
│ ├── drawTimer.py
│ ├── mac_hardware.py
│ ├── rpi_hardware.py
│ └── virtualKeyboard.py
└── version.py
├── main.py
├── test.txt
└── util
├── git_pull.sh
├── macosx
├── requirements.txt
└── setup.sh
├── menu
├── faa_register.py
└── serial_getlist.py
├── network
└── udp_repeater.py
├── old
├── kill_process.sh
├── run_choose.sh
├── run_demo.sh
├── run_mgl.sh
└── run_mgllive.sh
├── rpi
├── NotoSans-Regular.ttf
├── autologin@.service
├── cube_test.py
├── i2c
│ └── calibrate_bno085.py
├── sdr_angel.sh
├── setup.sh
└── setup_autorun.sh
├── run.sh
├── tests
├── 3d
│ ├── cube.glsl
│ ├── cube.py
│ ├── sphere.glsl
│ ├── sphere.py
│ └── trackball
│ │ ├── Digital.tga
│ │ ├── MQ-27-2.obj
│ │ ├── MQ-27-2.obj.mtl
│ │ ├── MQ-27.mtl
│ │ ├── MQ-27.obj
│ │ ├── Tan.tga
│ │ ├── gun.tga
│ │ ├── gun_normal.tga
│ │ ├── gun_spec.tga
│ │ ├── main.py
│ │ ├── mq_normal.tga
│ │ ├── mq_spec.tga
│ │ ├── rotor.tga
│ │ ├── simple.glsl
│ │ ├── sphere_trackball.py
│ │ ├── test1.mtl
│ │ └── test1.obj
├── README.md
├── i2c_test.py
├── joystick.py
├── nmea_gps.py
├── serial_raw.py
├── serial_read.py
└── test_stratux_wifi.sh
└── version-select.sh
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | #github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | #patreon: # Replace with a single Patreon username
5 | #open_collective: # Replace with a single Open Collective username
6 | ko_fi: TronView
7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | #liberapay: # Replace with a single Liberapay username
10 | #issuehunt: # Replace with a single IssueHunt username
11 | #lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | #polar: # Replace with a single Polar username
13 | #buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
14 | #thanks_dev: # Replace with a single thanks.dev username
15 | #custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | hud.cfg
2 | config.cfg
3 | output.log
4 | output_error.log
5 | *.pyc
6 | **/.DS_Store
7 | .DS_Store
8 | .vscode/
9 | .venv/
10 | __pycache__/
11 | *.pyc
12 | data/
13 | flightlog/
14 | profile.html
15 | profile.json
16 | venv/
17 | venv_*
18 |
19 |
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
1 | TronView-3.12.6
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [0.0.33] - 2025-04-21
4 |
5 | - meshtastic target scope
6 | - meshtastic send messages and location
7 | - FAA database n number search
8 | - target scope update, mouse wheel support
9 | - track flight number vs n number from stratux
10 |
11 |
12 |
13 | ## [0.0.32] - 2025-04-07
14 |
15 | - meshtastic fixes for merging node data into dataship targets.
16 | - added script for faa database download and sqlite creation
17 |
18 |
19 |
20 | ## [0.0.31] - 2025-04-07
21 |
22 | - video in screen module
23 |
24 |
25 |
26 | ## [0.0.30] - 2025-03-22
27 |
28 | - Gauges show color ranges. different modes. alpha transparency.
29 | - text parsing better.
30 | - support knots,mph,kph,c,f.. using format_specifier example {airData[0].IAS:kts}
31 | - added image object. supports alpha. saves base64 in screen json. scale or fit modes.
32 |
33 |
34 |
35 | ## [0.0.29] - 2025-03-13
36 |
37 | - sub menu work
38 | - variable selection
39 | - theme updates for gui
40 | - fixes for gauges (bar and arc). now pick from drop down.
41 | - updated engine template.
42 |
43 |
44 |
45 | ## [0.0.28] - 2025-03-10
46 |
47 | - data logging work. _input.py saves to correct file.
48 |
49 |
50 |
51 |
52 | ## [0.0.27] - 2025-02-26
53 |
54 | - serial unique names using udev script for pi. /etc/udev/rules.d/99-tronview-serial.rules
55 | - save last console logs file.
56 | - added view option in menu to see last log
57 |
58 |
59 |
60 | ## [0.0.26] - 2025-02-23
61 |
62 | - serial port list menu.
63 | - templates clean up
64 | - dropdown menu refactor
65 | - config file updates
66 |
67 |
68 | ## [0.0.25] - 2025-02-19
69 |
70 | - refactor work.
71 | - template updates.
72 | - fix to save and load text and segment text in json.
73 | - menu updates.
74 |
75 | ## [0.0.24] - 2025-02-01
76 |
77 | - nmea gps test script added
78 |
79 |
80 |
81 | ## [0.0.23] - 2025-01-05
82 |
83 | - 3d tests added
84 | - update for default screen json
85 | - dynon skyview input updates. nav and engine
86 | - fix for yaw in imu of stratux
87 | - fix for bouy if mag_head is None
88 |
89 |
90 | ## [0.0.22] - 2024-12-08
91 |
92 | - New 16 segment style text module for retro text.
93 | - Heading module fix
94 | - fix for to front and back buttons.
95 | - updated md
96 | - work on event manager
97 |
98 |
99 |
100 | ## [0.0.21] - 2024-12-06
101 |
102 | - menu system updates
103 | - new live input picker menu
104 | - update from git. and reload run.sh
105 | - main.py fixes for 100 inputs
106 | - added i2c test script
107 | - bno055 and bno085 init fixes. auto set address for 2nd imu.
108 |
109 |
110 | ## [0.0.20] - 2024-12-06
111 | ### Added
112 | - Added Changelog!
113 | - Support for versioning
114 | - Growl notifications
115 |
116 | ### Changed
117 | - Refactored lots
118 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # TronView
6 | Project for connecting EFIS or any external sensor data to a Screen, HUD, or AR glasses. This was created for the aviation community but can be used for any application where you want to display data in a custom GUI (and interact with it).
7 |
8 | 
9 | ## Active development! (April 2025)
10 | If you want to help with development or testing please join our [Join Discord](https://discord.gg/pdnxWa32aW) sever.
11 | We are working on several new features.
12 | - Meshtastic integration (Works, but still adding more)
13 | - XReal and Viture AR glasses support
14 | - Show/Hide screen modules based on key commands or other inputs. (event handlers)
15 | - Moving Map.
16 | - Video Input (Done)
17 | - Add images to screen design (Done)
18 |
19 | ## Features Include:
20 | - Build custom efis or hud screens or AR glasses screen. (or any kinda of screen you want)
21 | - Can build any kind of UI for external sensors or external data.
22 | - Record and Playback flight log data ( and fast forward through playback )
23 | - All screens look and work the same for all supported data input.
24 | - All display screen sizes and ratios supported.
25 | - Built in editor to make creating or editing your own screen.
26 | - Text mode
27 | - Touch screen support
28 | - 30 + FPS on Raspberry Pi 4/5 (60+ FPS on Mac M1)
29 | - Remote keypad / user input support.
30 | - Display flight data in Knots, Standard, Metric, F or C
31 | - Designed for Raspberry Pi 4/5 but also runs on Mac OSx, Windows, and other linux systems.
32 | - Show NAV needles for approaches. (If NAV data is available)
33 | - Use multiple data input sources, (MGL, G3x, Dynon, GRT EIS, iLevil BOM, Stratux, Analog CDI via ADS1115, IMU BNO055 & BNO085, Generic Serial Logger, Joystick)
34 |
35 |
36 | # Quick Start for Raspberry Pi and Mac OS
37 |
38 | [Quick Start for Raspberry Pi](docs/quick_start_pi.MD)
39 |
40 | [Quick Start for Mac OS](docs/quick_start_macos.MD)
41 |
42 | ## 3d wire frame terrain view through AR glasses (work in progress)
43 |
44 | 
45 |
46 | ## 3d synthetic world (work in progress)
47 |
48 | 
49 |
50 | ## Use as backup display screen on dash
51 |
52 | 
53 |
54 | ## Editor Screenshot
55 | 
56 |
57 | ## F18 Style HUD
58 | 
59 |
60 | ## Text Mode
61 | 
62 |
63 | ## Engine/System Display in Classic Pinzgauer Swiss Military Vehicle
64 | 
65 |
66 |
67 | # About
68 |
69 | This is a python3 application that will take in data from different input sources, process them into a common format, then output (draw)
70 | them to custom screens. The system is created to have the inputs and screens seperate and non-dependent of each other.
71 | For example a user running a MGL iEFIS can run the same screen as a user with a Dynon D100. Issues can come up with a input source does not
72 | have all the data available as other input sources do. But if the screen is written well enough it will hide or show data if it's available.
73 |
74 |
75 | ## Currently supported input sources:
76 |
77 | MGL iEFIS
78 |
79 | Garmin G3x
80 |
81 | Dynon Skyview
82 |
83 | Dynon D10/100
84 |
85 | GRT EIS
86 |
87 | Levil BOM (wifi)
88 |
89 | Stratux or any stratux compatiable device (wifi)
90 |
91 | IFR Navigator (Analog CDI) (ADS 1115)
92 |
93 | BNO055 IMU (9DOF) and BNO085 (9DOF)
94 |
95 | Generic serial logger (Used for recording any serial data)
96 |
97 | Joystick (USB or Bluetooth)
98 |
99 | Meshtastic SDK
100 |
101 | We are using the rapberry pi 4, or 5 for taking serial data from a EFIS (MGL,Dynon,G3x,etc) and displaying a graphical Display out the hdmi output on the pi. This can be displayed on a screen (touchscreen, etc) ,a HUD, or glasses.
102 |
103 | Code is written in Python 3.7 and the Pygame-CE 2.0 module for handling the graphics.
104 |
105 |
106 | # More details
107 |
108 | Analog CDI from IFR Navigator: [input analog details](docs/input_analog.MD)
109 |
110 | Config example: [config_example.cfg](config_example.cfg)
111 |
112 | Lots of different efis test data: [Test Data](docs/efis_data.MD)
113 |
114 | IncludesF-18 style HUD screen: [screen_F18.MD](docs/screen_F18.MD)
115 |
116 | # Join our Discord server
117 |
118 | We are active on Discord helping each other with development and testing. Come help or share ideas.
119 |
120 | [Join Discord](https://discord.gg/pdnxWa32aW)
121 |
122 |
--------------------------------------------------------------------------------
/config_example.cfg:
--------------------------------------------------------------------------------
1 | # TronView config file.
2 |
3 | [Main]
4 | # if "window" is set then screen will run in windowed mode if running in xwindows or other window type os.
5 | # this is the size of the waveshare lcd screen
6 | #window=1280,400
7 | # small screen size
8 | #window=640,480
9 | #window=1280,768
10 |
11 | #drawable_area is used to set the boundry for where the efis can draw on the screen.
12 | #For Epic HUD use the following
13 | #drawable_area=0,159,1280,651
14 |
15 | # Show Mouse? set to true if you want to show the mouse. Defaults to false
16 | #showMouse = true
17 |
18 | # Set max frame rate. defaults to 40
19 | #maxframerate = 40
20 |
21 | # Ignore any traffic targets beyond a given distance in miles (defaults to importing all traffic into aircraft traffic object)
22 | #ignore_traffic_beyond_distance = 5
23 |
24 | # Set screen to load on startup.
25 | screen = template:default.json
26 |
27 |
28 | # serial port notes:
29 | # rpi built in serial is /dev/ttyS0
30 | # rpi usb serial is usually /dev/ttyUSB0
31 |
32 | [meshtastic]
33 | # set serial port for meshtastic input
34 | #port = /dev/ttyUSB0
35 | #baudrate = 115200
36 | # should mestastic show self in the list of nodes? defaults to true
37 | #ignore_self = true
38 | # should mestastic auto reply to messages? defaults to true
39 | #auto_reply = true
40 | # what message should mestastic auto reply with? defaults to "ACK"
41 | #auto_reply_message = ACK
42 |
43 |
44 | [DataRecorder]
45 | # change the path were the default flight log files are saved. Make sure this dir exists.
46 | # default path is /flightlog/
47 | #path = /flightlog/
48 |
49 | # check if usb drive is available for creating log files?
50 | # defaults to true
51 | #check_usb_drive = true
52 |
53 | [dynon_d100]
54 | # set serial port for dynon d100 input
55 | #port = /dev/ttyS0
56 | #baudrate = 115200
57 |
58 | [dynon_skyview]
59 | # set serial port for dynon skyview input
60 | #port = /dev/ttyS0
61 | #baudrate = 115200
62 |
63 | [flyon_speed2efisdata]
64 | # set serial port for flyon speed2efisdata input
65 | #port = /dev/ttyS0
66 | #baudrate = 115200
67 |
68 | [grt_eis]
69 | # set serial port for grt eis input
70 | #port = /dev/ttyS0
71 | #baudrate = 9600
72 |
73 | [garmin_g3x]
74 | # set serial port for garmin g3x input
75 | #port = /dev/ttyS0
76 | #baudrate = 115200
77 |
78 | [stratux]
79 | # set UPD port for network input.. defaults to 4000
80 | #udpport = 4000
81 |
82 | [levil]
83 | # set UDP port for levil input
84 | #udpport = 43211
85 |
86 | [serial_logger]
87 | # set serial port for serial logger input
88 | #port = /dev/ttyS0
89 | #baudrate = 115200
90 |
91 | [mgl]
92 | # set serial port for mgl input
93 | #port = /dev/ttyS0
94 | #baudrate = 115200
95 |
96 | [nmea]
97 | # set serial port for nmea input
98 | #port = /dev/ttyUSB0
99 | #baudrate = 9600
100 |
101 | [bno055]
102 | # bno055 settings. Supports multiple bno055 IMU devices.
103 | # id of bno055 device. (defaults to bno055_1 for the first device)
104 | #device1_id = bno055_1
105 | # address of bno055 device. which is 0x28 in hex. If you change the address to 0x29 hex then enter 41 here.
106 | #device1_address = 40
107 | # if you have a 2nd device then enter the address here.
108 | #device2_address = 41
109 | # feed into aircraft roll/pitch/yaw?
110 | #device1_aircraft = true
111 |
112 | [bno085]
113 | # bno085 settings. Supports multiple bno085 IMU devices.
114 | # id of bno085 device. (defaults to bno085_1 for the first device)
115 | #device1_id = bno085_1
116 | # address of bno085 device. (base 10 address)
117 | #device1_address =
118 | # if you have a 2nd device then enter the address here.
119 | #device2_address =
120 | # feed into aircraft roll/pitch/yaw?
121 | #device1_aircraft = true
122 |
123 |
124 | [HUD]
125 | #HUD screen module settings.
126 |
127 | # how thick to draw hud lines... defaults to 2 pixels
128 | #line_thickness = 2
129 |
130 | # how many vertical degrees to have per line seperation. default 5
131 | #vertical_degrees = 5
132 |
133 | # line mode. can be 0 or 1.
134 | #line_mode = 1
135 |
136 | # center circle radius. default 4
137 | #center_circle = 4
138 |
139 | #field of view width in degrees. This is the total number of degrees for the FOV Defaults to 13.942
140 | #fov_x = 13.942
141 |
142 | # pixel per degree for hud. default is 30.
143 | #vertical_pixels_per_degree = 30
144 |
145 | # show traffic within mileage range. defaults to 5
146 | # this shows traffic targets in HUD view that are less then X miles distance from aircraft.
147 | # if set to 0 then don't show any traffic data.
148 | #show_traffic_within_miles = 5
149 |
150 | # font size for target details. default 40
151 | #target_font_size = 40
152 |
153 | # Set Horizon Center Offset in Pixels (- = Up/+ = Down) (Default is 0)
154 | #Horizon_Offset = -100
155 |
--------------------------------------------------------------------------------
/docs/HUD Type B USB Power Connections.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/docs/HUD Type B USB Power Connections.png
--------------------------------------------------------------------------------
/docs/Instructions for Installing the Latest 64.md:
--------------------------------------------------------------------------------
1 | **Instructions for Installing the Latest 64-bit Raspberry Pi OS on a Blank SD Card, and Setting Up TronView HUD Project**
2 |
3 | **Step 1: Download the Latest Raspberry Pi OS**
4 |
5 | 1. **Visit the Official Raspberry Pi Website:**
6 | * Open your web browser and go to the Raspberry Pi OS download page.
7 | 2. **Download the Raspberry Pi Imager:**
8 | * Scroll down and click on the “Download for Windows/MacOS/Linux” button to download the Raspberry Pi Imager for your operating system.
9 | 3. **Install the Raspberry Pi Imager:**
10 | * Once downloaded, open the installer and follow the instructions to install the Raspberry Pi Imager on your computer.
11 | 4. **Prepare Your SD Card:**
12 | * Insert your blank SD card (at least 8GB) into your computer.
13 | 5. **Write the Raspberry Pi OS to the SD Card:**
14 | * Launch the Raspberry Pi Imager.
15 | * Choose the OS: Select “Raspberry Pi OS (64-bit)” from the list.
16 | * Choose the SD card: Select your SD card from the list of available drives.
17 | * Click “Write” to begin writing the OS to the SD card. This process may take a few minutes.
18 | 6. **Eject the SD Card:**
19 | * Once the process is complete, safely eject the SD card from your computer.
20 |
21 | **Step 2: Set Up Raspberry Pi and Install Support Programs**
22 |
23 | 1. **Boot Your Raspberry Pi:**
24 | * Insert the SD card into your Raspberry Pi 5 and power it on.
25 | * Follow the on-screen instructions to complete the initial setup (e.g., setting up Wi-Fi, localization settings, etc.).
26 | 2. **Update the System:**
27 | * Open a terminal on your Raspberry Pi and run the following commands to update the package list and upgrade installed packages:
28 | Copy code and run in Python command line
29 | sudo apt update
30 | sudo apt upgrade \-y
31 | 3. **Install Required Support Programs:**
32 | * Install Python 3 and necessary development tools:
33 | Copy code and run in Python command line
34 | sudo apt install \-y python3-dev python3-pip
35 | * Install additional dependencies required for the TronView project:
36 | Copy code and run in Python command line
37 | sudo apt install \-y libsdl1.2-dev libsdl-image1.2-dev libsdl-mixer1.2-dev libsdl-ttf2.0-dev libgeographic-dev cmake
38 | * Install Pygame:
39 | Copy code and run in Python command line
40 | sudo apt install \-y python3-pygame
41 | * Install GeographicLib:
42 | Copy code and run in Python command line
43 | sudo apt install python3-geographiclib
44 |
45 | **Step 3: Clone and Install the TronView HUD Project**
46 |
47 | 1. **Clone the TronView GitHub Repository:**
48 | * Navigate to the home directory and clone the repository:
49 | Copy code and run in Python command line
50 | cd \~
51 | **git clone [https://github.com/flyonspeed/TronView.git](http://\_blank)**
52 | 2. **Install TronView Dependencies:**
53 | * Navigate to the TronView directory:
54 | Copy code and run in Python command line
55 | **cd TronView**
56 | * If there are any additional dependencies listed in the project’s documentation or requirements file, install them using pip:
57 | Copy code and run in Python command line
58 | **pip3 install \-r requirements.txt**
59 | 3. **Run the TronView HUD Project:**
60 | * You can now run the TronView HUD program by executing the following command:
61 | Copy code and run in the Python command line
62 | **sudo python3 main.py to stop press the “q” key**
63 | 4. **Set Up Automatic HUD program Start on Boot:**
64 | * To ensure the TronView HUD starts automatically on boot, you can add a line to your “rc.local” file in the /etc folder
65 | Copy code and run in Python command line
66 | “**cd /etc**”
67 | “**sudo nano rc.local**”
68 | * Add the line/lines below in rc.local after “**\# fi**” and before “**exit 0**”:
69 | **/home/pi/start\_pygame.sh & \# sets up Pi 5 cmd line graphics**
70 | **/bin/sleep 2; cd /home/pi/TronView/ && sudo python3 main.py**
71 | \# above auto starts HUD on bootup per config.cfg file
72 |
73 | **/bin/sleep 2; cd /home/pi/TronView/ && sudo python3 main.py \--in1 serial\_g3x \--playfile1 garmin\_g3x\_data1.txt**
74 | \# above and lines below optional for demo playback of
75 | \# of recorded HUD files stored in
76 | \# TronView/lib/inputs/\_example\_data folder, HUD program will
77 | \# sequence through each example with “**q**” key press
78 | \# you do not need different config.cfg files for this mode
79 | \# you can also playback 2 simultaneous (Serial & WIFI) files
80 | **/bin/sleep 2; cd /home/pi/efis\_to\_hud/ && sudo python3 main.py**
81 | **/bin/sleep 2; cd /home/pi/TronView/ && sudo python3 main.py \--in1 serial\_mgl \--playfile1 mgl\_11\_RV8Landing.dat**
82 | **/bin/sleep 2; cd /home/pi/TronView/ && sudo python3 main.py \--in1 serial\_mgl \--playfile1 mgl\_8.dat \--in2 stratux\_wifi \--playfile2 stratux\_8.dat**
83 |
84 | * Save rc.local with a ctrl-o (write-out) and exit with a ctrl-x. Be very careful editing this file as it can cause boot up hangs and crashes if not correct.
85 |
86 | **5\. Reboot and Verify**
87 |
88 | 1. **Reboot Your Raspberry Pi:**
89 | Copy code and run in Python command line
90 | “**reboot**”
91 | 2. **Verify the Installation:**
92 | * After rebooting, your Raspberry Pi should automatically start the TronView HUD project.
93 | * Verify that everything is working as expected.
94 |
95 | This process should have you up and running with the latest Raspberry Pi OS and the TronView HUD project.
96 |
97 |
--------------------------------------------------------------------------------
/docs/Python Install Guide for Windows Development.md:
--------------------------------------------------------------------------------
1 | **Install Guide for Development on Windows (64-bit) Aug 2024**
2 |
3 | These steps will configure a Windows PC to allow development for the HUD project without needing an actual Raspberry Pi.
4 |
5 | 1. **Install Python 3.x (64-bit version):**
6 | * Download and install the latest Python 3.x (64-bit) version for Windows from the official Python website:
7 | * [Download Python 3.x](http://\_blank)
8 | * During installation, ensure you check the option to "Add Python to PATH."
9 | 2. **Install Pygame:**
10 | * Pygame is a set of Python modules designed for writing video games, and it's necessary for the HUD project.
11 | * Open Command Prompt (you can search for cmd in the Start menu) and run:
12 | Copy code and run in Python command line
13 | pip install pygame
14 | 3. **Install PySerial:**
15 | * PySerial is needed for serial communication, such as reading from and writing to serial ports.
16 | * In the same Command Prompt, run:
17 | Copy code and run in Python command line
18 | pip install pyserial
19 | 4. **Install Windows Curses (Required for Windows):**
20 | * Windows doesn’t have native support for curses, so you'll need the windows-curses module to provide this functionality.
21 | * Run the following command:
22 | Copy code and run in Python command line
23 | pip install windows-curses
24 | 5. **Clone the HUD Project to Your Local Windows PC:**
25 | * Now, clone the project repository to your local machine using Git. If you don't have Git installed, download and install it from [Git for Windows](http://\_blank).
26 | * In Command Prompt, navigate to the directory where you want to store the project, then run:
27 | Copy code and run in Python command line
28 | * git clone [https://github.com/flyonspeed/TronView.git](http://\_blank)
29 |
30 |
31 |
32 | * Navigate to the project directory:
33 | Copy code and run in Python command line
34 | cd efis\_to\_hud
35 | 6. **Adjust the Configuration for Windows:**
36 | * To run the HUD project on Windows, you need to configure the hud.cfg file correctly, especially regarding the serial port settings.
37 | * Open the hud.cfg file in a text editor and find the section for serial port configuration.
38 | * Use the correct COM port (e.g., COM1, COM2, etc.) that is available on your Windows PC. You can find available COM ports by opening the **Device Manager** and expanding the **Ports (COM & LPT)** section.
39 |
40 | **Running the HUD Project on Windows:**
41 |
42 | * Once you have everything installed and configured, you can run the HUD project using Python:
43 | Copy code and run in Python command line
44 | python hud.py
45 | * You may not need to run the setup script meant for the Raspberry Pi (setup.sh), as those steps are specific to the Raspberry Pi environment.
46 |
47 | **Notes:**
48 |
49 | * **Development Differences on Windows vs. Raspberry Pi:**
50 | * Be aware of slight differences in development and runtime behavior between Windows and Linux (Raspberry Pi). For example, sudo is not required on Windows, and path separators are different (backslashes \\ for Windows vs. forward slashes / for Linux).
51 | * Ensure your code is portable and works across both environments by testing on the Raspberry Pi as well before deployment.
52 |
53 | This guide should help you set up and develop the HUD project on a Windows PC without the need for an actual Raspberry Pi. Let me know if you encounter any issues or need further assistance\!
54 |
55 |
--------------------------------------------------------------------------------
/docs/dev/profiling.MD:
--------------------------------------------------------------------------------
1 | # Profiling Python Code
2 |
3 | use https://github.com/emeryberger/scalene
4 |
5 | Run with:
6 |
7 | ```
8 | scalene main.py {TronView args here}
9 | ```
10 |
--------------------------------------------------------------------------------
/docs/efis_HUD_rv8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/docs/efis_HUD_rv8.jpg
--------------------------------------------------------------------------------
/docs/efis_cockpit1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/docs/efis_cockpit1.jpeg
--------------------------------------------------------------------------------
/docs/efis_cockpit2.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/docs/efis_cockpit2.jpeg
--------------------------------------------------------------------------------
/docs/efis_data.MD:
--------------------------------------------------------------------------------
1 | # Demo Sample EFIS Data
2 |
3 | Demo data is saved in lib/inputs/_example_data . Demo data lets you run previously recorded data through the hud app for demo or testing purposes. Each input source module uses it's own format for data. So for example if you want to run dynon demo data then you must run it through the dynon input source module.
4 |
5 | Using the -c FILENAME command line option loads data from the _example_data folder.
6 |
7 | Example to run the MGL_2.dat demo data file would look like this:
8 |
9 | `sudo python3 main.py --in1 serial_mgl --playfile1 MGL_2.dat`
10 |
11 | Note this is setting up input1.
12 |
13 |
14 | # MGL Data
15 |
16 | MGL EFIS Sample Data Link: https://drive.google.com/open?id=1mPOmQuIT-Q5IvIoVmyfRCtvBxCsLUuvz
17 |
18 | MGL EFIS Serial Protocol Link: https://drive.google.com/open?id=1OYj000ghHJqSfvacaHMO-jOcfd1raoVo
19 |
20 | MGL example data in this git repo include: (they can be ran by using the -c command line option.)
21 |
22 | MGL_V2.bin = G430_Bit_Test_24Mar19
23 |
24 | MGL_V3.bin = G430_Data_3Feb19_VertNdlFullUp_HzNdl_SltRt_toCtr
25 |
26 | MGL_V4.bin = G430_Data_3Feb19_HSI_Nedl_2degsRt_Vert_SlightLow
27 |
28 | MGL_V5.bin = G430_Data_3Feb19_HSI_Nedl_2degsLft_Vert_2Degs_Dwn
29 |
30 | MGL_V6.bin = G430_Data_3Feb19_HSI_Nedl_2degsRt_Vert_2Degs_Up
31 |
32 | MGL_V7.bin = G430_Data_3Feb19_Horz_Vert_Nedl_come to center
33 |
34 | MGL_V8.bin = G430_Data_13Ap19_VertNdlFullDwn_HzNdl_FullLft
35 |
36 | MGL_V9.bin = 13Ap_AltBug_0_500_1k_1.5k_to_10k_5.1K_5.2k_5.9kv10_v10
37 |
38 | MGL_V10.bin = 13Ap_XC_Nav
39 |
40 | MGL_V11.bin = iEfis_NavDataCapture_5Feb19
41 |
42 | MGL_V12.bin = NavData_13Ap_HdgBug_360_10_20_30_to_360_001_002_003_to_010_v9
43 |
44 | # Dynon Data
45 |
46 | Dynon Skyview EFIS Sample Data Link: https://drive.google.com/open?id=1jQ0q4wkq31C7BRn7qzMSMClqPw1Opwnp
47 |
48 | Dynon Skyview EFIS Serial Protocol Link: https://drive.google.com/open?id=1isurAOIiTzki4nh59lg6IDsy1XcsJqoG
49 |
50 | Dynon D100 Series EFIS Sample Data Link: https://drive.google.com/open?id=1_os-xv0Cv0AGFVypLfSeg6ucGv5lwDVj
51 |
52 | Dynon D100 Series EFIS Serial Protocol Link: https://drive.google.com/open?id=1vUBMJZC3W85fBu33ObuurYx81kj09rqE
53 |
54 | # Garmin Data
55 |
56 | Garmin G3X EFIS Sample Data Link: https://drive.google.com/open?id=1gHPC3OipAs9K06wj5zMw_uXn3_iqZriS
57 |
58 | Garmin G3X EFIS Serial Protocol Link: https://drive.google.com/open?id=1uRRO-wdG7ya6_6-CfDVrZaKJsiYit-lm
59 |
60 | # Stratux Data
61 |
62 | Recorded data for stratux is saved in the example data. stratux_1.dat shows traffic near by. Like 0.5 miles away.
63 |
--------------------------------------------------------------------------------
/docs/efis_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/docs/efis_screenshot.png
--------------------------------------------------------------------------------
/docs/efis_screenshot_text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/docs/efis_screenshot_text.png
--------------------------------------------------------------------------------
/docs/hud_animated_example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/docs/hud_animated_example.gif
--------------------------------------------------------------------------------
/docs/imgs/logo_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/docs/imgs/logo_black.png
--------------------------------------------------------------------------------
/docs/imgs/logo_txt_small.txt:
--------------------------------------------------------------------------------
1 |
2 | ,okxol:,'.. ..';:ldxko.
3 | . ..';:loxkkkxoc:,.. ..';:loxkkkdoc:;'.. .
4 | 'xOOkdoll:.. :kkkkkkkkkl. ,dkkkkkkkkd' ..:cloxxkOo.
5 | ..',:okdxkkl. :kkkkkkkkko';xkkkkkkkkd' 'dOkxdxl;,...
6 | .l:;:;;,'. ... .codxO0OOkkkOOOOkxdl, .... ..'',,,c:
7 | .c: ....','...... ..';,'.. ......','.... .o:
8 | .c;.................... ... .................'l;
9 | :;.';codddxxxxxxxdddddddodddddddddddddddddddddddddddxddxddxxxxxxxxdddo;,.'c,
10 | 'lllkOOOOOOOOOOOOOOOOOOO000000000000OOOOOOOOOOOOOOOOOOOO0OOOOOOOOdll:.
11 | ,kO, 'ccclddxxxxxxxxxkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkxxxxxxddoolcccc:. :kl.
12 | 'kO; 'ccclllooddxxxkkkkkkkkkolx0kkkkkOOOOolxkkkkkkkkxxxddooolllcc:. :Ol.
13 | ,kO; 'ccccccccccccccccccloxKWW0xxxxddKWW0xolccccccccccccccccc:. :Ol.
14 | ,O0:.....'',,;::ccccldOXWWK: lXWWXOdccccc::;;,''.....lOo.
15 | ,kOkkxdoolc:,'...cKNWWNc oNWWN0;..'',:clodxxkkOo.
16 | .....',:clodxkkO000l. ...dKK0OOkkdolc:,''....
17 | ......... .cK.,k: ........
18 | .. . .cxkK.'kkx:. . ..
19 | 'l:. '''........lxxxx0.'xxxxx:........'.. .;:.
20 | ;xo. .clccclldxxxxxx0..xxxxxxdolccccc:. 'oo,
21 | .c0d..ddddddxxxxdl;, ':ldxxddddddo' 'xO;
22 | .l0Oxddddl:,. .;..'. ..,codddo:kO:
23 | .lx:'. .,coxx0.'xxdl;'. .,lk:.
24 | ';cdxxdddxxo .xxxxddddo:,.
25 | .lxxxddddo:' ,coddddddd:.
26 | 'odc,. .; .,. ..;cdl.
27 | ,coxxx..oxdl:'
28 | .;ccclc .cclcc,
29 | ';;; .;;;.
30 | .. ..
31 |
--------------------------------------------------------------------------------
/docs/imgs/pinz1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/docs/imgs/pinz1.jpeg
--------------------------------------------------------------------------------
/docs/imgs/tronview_thumb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/docs/imgs/tronview_thumb.png
--------------------------------------------------------------------------------
/docs/imgs/tronview_trimed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/docs/imgs/tronview_trimed.png
--------------------------------------------------------------------------------
/docs/input_analog.MD:
--------------------------------------------------------------------------------
1 |
2 | ## Analog CDI from IFR Navigator
3 |
4 | TronView supports reading CDI and VDI data from an IFR Navigator for use on EFIS systems that do not output this data over RS-232. This is done via the ADS 1115 Analog to Digital Converting I2C chip from Texas Instruments. The ADS 1115 is available on a carrier board from Adafruit to make connecting to it more convenient with a Pi.
5 |
6 | This interface enables a "crosshair" style presentation of CDI data on the defaul and F18 hud pages, plus any page you may come up with. This essentially creates the ability for one to fly an ILS or GPS/LPV approach using TronView, though since TronView is NOT a certified instrument nor is it intended in any way to be used as primary instrumentation.
7 |
8 | In order to wire this up, you'll need an IFR navigator (GPS or NAV radio) that supports the output of +L/+R and +U/+D signals. Examples of radios that do this include the Garmin GNS, GTN and 2-inch GPS Navigator series, Garmin GNC 255/215, SL-30, Avidyne IFD and King KX 165 (NOT 155) units. There are certainly more that will do this, but these are the most popular examples.
9 |
10 | This hookup uses the same wiring as most mechanical CDIs.
11 |
12 | The ADC has 4 input channels, labeled 0-3. Channel 0 is CDI +L, Channel 1 is CDI +R, Channel 2 is VDI +U, and Channel 3 is VDI +D. Refer to your navgiator's installation manual/pinout to determine the matching pins.
13 |
14 | We recommend installing an intermediate D-Sub connector for both diagnostics and future changes.
15 |
16 |
--------------------------------------------------------------------------------
/docs/quick_start_macos.MD:
--------------------------------------------------------------------------------
1 |
2 | # Steps to get the software running on Mac OS
3 |
4 | 1) Open a terminal window.
5 |
6 | 2) run the install script on the terminal.
7 |
8 | `/bin/bash -c "$(curl -fsSL https://tronview.org/macos.sh)"`
9 |
10 | or goto https://tronview.org/ where you can copy and paste the script.
11 |
12 | This will download the latest install script and run it.
13 |
14 | Then it will give you some demo options to run.
15 |
16 | Some keyboard commands:
17 |
18 | q - quit
19 |
20 | ? - show help
21 |
22 |
--------------------------------------------------------------------------------
/docs/quick_start_pi.MD:
--------------------------------------------------------------------------------
1 |
2 | # Steps to get the software running on raspberry pi
3 |
4 | 1) Create a fresh image of the latest Raspberry Pi OS. See [quick_start_pi_image](quick_start_pi_image.MD)
5 | for more details.
6 |
7 | 2) run the install script on the pi terminal.
8 |
9 | `curl -fsSL https://tronview.org/rpi.sh | sh`
10 |
11 | This download the latest install script and runs it.
12 |
13 | It will ask you a few questions about your setup.
14 |
15 | After it's done it will show you a list of demo options to run.
16 |
17 | While running some keyboard commands:
18 |
19 | q - quit
20 |
21 | ? - show help
22 |
23 |
24 | # If you want to run live serial data from your efis.
25 | run the serial_read.py script to confirm data being sent is correct.
26 | go into TronView dir (if you are not already there) then type
27 |
28 | `cd TronView`
29 |
30 | `sudo python util/serial_read.py`
31 |
32 | you will have to pass in -m or -s for mgl or skyview data.
33 |
34 | make sure the data is coming through and looks good.
35 |
36 | to exit hit cntrl-c
37 |
38 | 4) run it
39 |
40 | Note: You have to run using sudo in order to get access to serial port.
41 |
42 | Example:
43 |
44 | `sudo python3 main.py -i serial_mgl -e`
45 |
46 | will show you command line arguments.
47 |
48 | `-i {input data source 1}`
49 |
50 | load a input module for where to get the air data from.
51 |
52 | --in1 {input source 1} (same as -i)
53 |
54 | --in2 {input source 2}
55 |
56 | -s (optional) {screen module name to load} (located in the lib/screens folder)
57 |
58 | -t (optional) start up in text mode
59 |
60 | -e (optional) demo mode. load default example demo data for input source selected.
61 |
62 | -c FILENAME (optional) custom playback file. Enter filename of custom example demo file to use.
63 |
64 | --playfile1 (optional) same as -c.. set playback file for input 1
65 |
66 | --playfile2 (optional) set playback file for input 2
67 |
68 | --listlogs (optional) show logs you saved. location id defined in config.cfg
69 |
70 | --listusblogs (optional) show logs saved to usb drive (if available)
71 |
72 | --listexamplelogs (optional) show example log files to playback
73 |
74 |
75 | Run the command with no arguments and it will show you which input modules and screen modules are available to use.
76 |
77 | `sudo python3 main.py`
78 |
79 | ## Examples command line arguments
80 |
81 | To set Input1 to garmin g3x and Input2 to stratux wifi and run default example data run the following.
82 |
83 | `sudo python3 main.py --in1 serial_g3x --in2 stratux_wifi -e`
84 |
85 |
86 | To run Input1 to MGL and Input2 as stratux. then supply custom example log files... Note that these custom log files are in the example data dir. This will also check the DataRecorder path dir that you set in the config file.
87 |
88 | `python3 main.py --in1 serial_mgl --playfile1 mgl_1.dat --in2 stratux_wifi --playfile2 startux_1.dat`
89 |
90 |
91 | Lauch app in text mode playing dynon D100 example data. Press 'Q' to exit Text Mode.
92 |
93 | `python3 main.py --in1 serial_d100 -t -e`
94 |
95 | ## More help on raspberry pi.
96 |
97 | Here are more instructions on setting up for raspberry pi.
98 |
99 | https://github.com/flyonspeed/TronView/blob/master/docs/rpi_setup.md
100 |
101 |
--------------------------------------------------------------------------------
/docs/quick_start_pi_image.MD:
--------------------------------------------------------------------------------
1 |
2 | ## How to create a Raspberry Pi image for TronView
3 |
4 | 1) Download and install the [pi imager](https://www.raspberrypi.com/software/) to create an image of the latest Raspberry Pi OS.
5 |
6 | 2) Choose Pi 4 or 5. Which ever pi you have.
7 |
8 | 
9 |
10 |
11 | 3) Choose Raspberry Pi OS (64-bit) Debian Bookworm. (desktop)
12 |
13 | 
14 |
15 | 4) Choose a storage device. Select the SD card you want to install the image to. Then click Next.
16 |
17 |
18 |
19 | 5) Choose advanced options.
20 | - Set a hostname (optional)
21 | - Enable SSH
22 | - Set a username and password (this will be your pi login
23 | - setup wifi (required!) You need to setup wifi before writing the image to the SD card. so you can continue with installing TronView.
24 | - Set timezone (recommended)
25 |
26 | 
27 |
28 | 6) Click Write. This will take a while. It will create a new SD card image.
29 |
30 | Then you can continue with installing TronView.
31 |
32 |
33 | ## Manually setup wifi using raspi-config.
34 |
35 | If you don't setup wifi in the imager, you can do it manually with raspi-config after you boot the pi.
36 |
37 | If you want to do it manually, here is the link: https://www.raspberrypi.org/documentation/configuration/wireless/wireless-cli.md
38 |
39 | ## Auto login to the pi console
40 |
41 | Enter the command `sudo raspi-config`
42 |
43 | - System Options, Scroll down to S5 `Select Boot AutoLogin Console Autologin`
44 |
45 | - select B2 `Console Autologin`.
46 |
47 | - Enter, then then TAB down to Select and Enter, Then Finish, then exit the configuration
48 |
49 | - Select Reboot.
50 |
51 |
--------------------------------------------------------------------------------
/docs/rpi_setup.md:
--------------------------------------------------------------------------------
1 |
2 | # Raspberry Pi setup
3 |
4 | The following is here to help getting the software running on your raspberry pi.
5 |
6 | ## Steps to get the HUD software running on Pi.
7 | ```
8 | 1. If in the Windows like GUI Press ctrl+alt+f1 to quit from the GUI to the desktop
9 | 2. Type “sudo raspi-config”
10 | 3. Select “boot options” -> desktop / cli -> "Console auto-login"
11 | 4. Select "Advanced options" -> "Expand Filesystem"
12 | 5. Select: "OK"
13 | 6. Select: "Finish"
14 | 7. Select: "Yes"
15 | 8. Wait for the reboot
16 | 9. Enter “sudo raspi-config”
17 | 10. Select: "Network options" -> "WiFi"
18 | 11. (If required) Choose your country. Pressing "u" will take you to USA.
19 | 12. Enter your home WIFI network name and password.
20 | 13. "Interfacing Options" -> "Enable SSH"
21 | 14. (If Required) "Localization" -> "Change Keyboard Layout" -> "Generic 104"
22 | "Other" -> "English US" -> "Default" -> "No compose" -> "Yes"
23 | 15. Select Boot Options
24 | B1 – AutoLogin
25 | 16. Select: Interfacing (to Select Serial/TTL Input)
26 | P6 – Serial
27 | NO – Login Shell
28 | Yes – Serial Port Hardware
29 | 17. Select: "Finish"
30 | 18. Selecting/Changing Screen Resolution Type “sudo raspi-config”
31 | Select "Advanced options"
32 | Select A5 – Resolution (Screen) -> Recommend Default (Pi will Select resolution at BootUp)
33 | Select “OK”
34 | Select: "Finish"
35 | Select: "Yes"
36 | 19. Wait for the reboot
37 | 20. Install git command. This will let you get the latest source from github directly onto the pi.
38 | sudo apt-get -y install git
39 | 21. To Check WIFI Connection: Enter “ping google.com”. Press ctrl+c after a while. This will Confirm that you have internet access. If no google.com then reset your wifi (see above) using raspi-config
40 | 22. Next clone the HUD development programs source from github
41 | Enter: git clone https://github.com/dinglewanker/efis_to_hud.git
42 | GitHub will ask for your username/email & password.
43 | when done this will create a efis_to_hud dir
44 | Check PI directory by entering: ls (HUD directory programs should appear)
45 | 23. Next you need to run the setup.sh script to finish install. This will setup serial port (if not all ready setup) and install the python libraries you need.
46 | 24. Change to the correct sub-directory (efis_to_hud) by typing “cd efis_to_hud”
47 | then to run the script type ./setup.sh
48 | It will take several minutes to complete the setup
49 | Next Reboot the PI. Enter: reboot
50 | 25. To input/playback (from separate PC) External Recorded HUD Data start data playback into Serial-TTL Converter
51 | 26. Next run the “serial_read.py” script to confirm correct data is being sent to the PI
52 | Change directory back to “efis_to_hud” dir then type Enter: cd efis_to_hud
53 | 27. Enter: python serial_read.py
54 | 28. Make sure the data is coming through and looks good.
55 | 29. To exit enter cntrl-c
56 | 30. To Run current HUD program enter “sudo python hud.py” (arguments as shown below will appear).
57 |
58 | ```
59 |
60 | ## How to auto run hud on start up
61 |
62 | For making the hud automatically run when the raspberry pi boots up you will want to do the following. (There are a few methods for doing this, but this one of the simplest)
63 |
64 | Run the following from the command line. This will let you edit the crontab file. Crontab is a tool that lets you run programs automatically at different times. In this case we just want it to run the hud with the pi first boots up.
65 |
66 | `sudo crontab -e`
67 |
68 | When you do this the first time you may be asked which editor you want to use to edit cron. Choose your favorite sword.
69 |
70 | If you choose nano as your crontab editor. Then hit cntrl-x and it will ask you if you want to save. hit y to save.
71 |
72 | Then add this next line to the top of the file.
73 |
74 | `@reboot cd /home/pi/efis_to_hud/ && python hud.py`
75 |
76 | Notice that this assumes you have installed the hud application in the efis_to_hud directory. If you put it somewhere else then change this.
77 |
78 | You can also pass different arguments to the hud.py script. For example if you want it to start in demo mode when it boots up.
79 |
80 | `@reboot cd /home/pi/efis_to_hud/ && python hud.py -i serial_mgl -c MGL_V5.bin`
81 |
82 | If you don't pass arguments it will try to use the hud.cfg file configuration on startup. How to use this file is described later in this document.
83 |
84 |
85 | ## Speeding up boot up time on Pi.
86 |
87 | Editing the /boot/config.txt with the following changes:
88 |
89 | ```
90 | # Disable the rainbow splash screen
91 | disable_splash=1
92 |
93 | # Disable bluetooth
94 | dtoverlay=pi3-disable-bt
95 |
96 | # Overclock the SD Card from 50 to 100MHz
97 | # This can only be done with at least a UHS Class 1 card
98 | dtoverlay=sdtweak,overclock_50=100
99 |
100 | # Set the bootloader delay to 0 seconds. The default is 1s if not specified.
101 | boot_delay=0
102 |
103 | ```
104 |
105 | Make the kernel output less verbose by adding the "quiet" flag to the kernel command line in file /boot/cmdline.txt
106 |
107 | ```
108 | dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=PARTUUID=32e07f87-02 rootfstype=ext4 elevator=deadline fsck.repair=yes quiet rootwait
109 | ```
110 |
--------------------------------------------------------------------------------
/docs/screen_F18.MD:
--------------------------------------------------------------------------------
1 | # Includes F-18 style HUD screen:
2 | Comes with a built F-18 HUD screen which features the following.
3 |
4 | - F-18 style artificial horizon
5 | - Flight Path marker
6 | - Velocity Vector and Ghost Velocity Vector
7 | - Waterline
8 | - Glideslobe (If data is available)
9 | - Bank angle
10 | - Traffic Target Radar (Key 3 cycles with ranges)
11 | - A-A Gun Cross Funnel for A-A Gunnery demonstration Use Keypad Key Num 8 to cycle from Off to 25ft Tgt Wingspan/to 30ft wingspan to 35ft wingspan to Off,
12 | wingSpan ranges stert at 250ft at wide part of big U (& the Yellow + graphic), then extend to 500ft at first Yellow circle pipper, then to 750 ft, then
13 | to 1,000 ft at next pipper, the 1500ft, and last at 2,000ft. for better description of this function check these links; http://falcon4.wikidot.com/avionics:hud & Video at https://www.youtube.com/watch?v=oOa9eWgFllE
14 | - Shows RPM and next Way Point Distance read out to right of HUD below the Altitude.
15 | - Amoung other commn things like Airspeed / altitude / VSI ...
16 |
17 |
--------------------------------------------------------------------------------
/docs/screenshots/pi_imager1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/docs/screenshots/pi_imager1.png
--------------------------------------------------------------------------------
/docs/screenshots/pi_imager2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/docs/screenshots/pi_imager2.png
--------------------------------------------------------------------------------
/docs/screenshots/pi_imager3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/docs/screenshots/pi_imager3.png
--------------------------------------------------------------------------------
/docs/screenshots/screenshot_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/docs/screenshots/screenshot_1.jpg
--------------------------------------------------------------------------------
/docs/screenshots/screenshot_2_editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/docs/screenshots/screenshot_2_editor.png
--------------------------------------------------------------------------------
/docs/screenshots/tv3d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/docs/screenshots/tv3d.png
--------------------------------------------------------------------------------
/docs/screenshots/tv_mesh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/docs/screenshots/tv_mesh.png
--------------------------------------------------------------------------------
/docs/windows_install_howto.md:
--------------------------------------------------------------------------------
1 | # Optional instructions for development on Windows
2 |
3 | Follow this install guide to get Windows configured properly to enable HUD project development on Windows without requiring an actual Raspberry pi for development.
4 |
5 | 1- First install Python 2.7.16 or later (do not install the 3.x version). Install the 32 bit version:
6 |
7 | https://www.python.org/ftp/python/2.7.16/python-2.7.16.msi
8 |
9 | 2- Second install Pygame:
10 |
11 | https://www.pygame.org/wiki/GettingStarted
12 |
13 | 3- Third install Pyserial by opening a command prompt and typing:
14 |
15 | pip install pyserial
16 |
17 | 4- Fourth install windows-curses by opening a command prompt and typing:
18 |
19 | pip install windows-curses
20 |
21 | 5- Clone the HUD project into your local Windows PC:
22 |
23 | git clone https://github.com/dinglewanker/efis_to_hud.git
24 |
25 | Now it should be possible to follow the normal HUD project directions to run the HUD project within Windows. You will not need to run the setup.sh script or perform any of the steps required to configure the Raspberry pi since you are using Windows instead. Be aware of some of the differences in development/running Python in Windows vs Linux. Example: You don't need to put "sudo" in front of commands in Windows.
26 |
27 | NOTE:
28 | In order to run the HUD project on Windows, you will need to use the option hud.cfg file to configure the serial port to use with Windows. You must select a serial port that is available and not in use. Find the ports by opening the Windows Device Manager and expanding the
29 | Ports (COM & LPT) menu item. Enter the proper COM port selection in the hud.cfg file ie: COM1, COM2, COM6, COM? etc.
30 |
--------------------------------------------------------------------------------
/lib/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/common/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/common/assets/tronview_logo1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/common/assets/tronview_logo1.png
--------------------------------------------------------------------------------
/lib/common/dataship/dataship_air.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from enum import Enum
4 | import inspect
5 | from typing import List, Any
6 |
7 | #############################################
8 | ## Class: AirData
9 | ##
10 | class AirData(object):
11 |
12 | def __init__(self):
13 | self.inputSrcName = None
14 | self.inputSrcNum = None
15 |
16 | self.sys_time_string = None
17 |
18 | self.IAS = None # Indicated Air Speed in mph
19 | self.TAS = None # True Air Speed in mph
20 | self.Alt = None # Altitude in Ft
21 |
22 | self.Alt_agl = None # Above Ground Level ft
23 | self.Alt_pres = None # Pressure Altitude ft (when baro is set to sea level)
24 | self.Alt_baro = None # Barometric Altitude ft
25 | self.Alt_da = None # Density Altitude ft
26 | self.AOA = None # Angle of Attack percentage 0 to 100 (if available)
27 | self.Baro = None # Barometric Pressure in inches of mercury
28 | self.Baro_diff = None # Barometric Pressure difference in inches of mercury
29 |
30 | self.VSI = None # Vertical Speed Indicator ft/min
31 | self.OAT = None # outside air temp in F
32 |
33 | self.AOA = None # Angle of Attack percentage 0 to 100 (if available)
34 |
35 | self.Wind_speed = None # Wind Speed in mph
36 | self.Wind_dir = None # Wind Direction in degrees
37 | self.Wind_dir_corr = None # corrected for aircraft heading for wind direction pointer
38 | self.Mag_decl = None #magnetic declination
39 |
40 | self.data_format = 0 # 0 is ft/in, 1 is m/mm, 2 is km/km, 3 is nm/nm
41 | self.data_format_temp = 0 # 0 is F, 1 is C
42 |
43 | self.msg_count = 0
44 | self.msg_last = None
45 | self.msg_bad = 0
46 |
47 | # get IAS in converted format.
48 | def get_ias(self):
49 | if(self.data_format==0):
50 | return self.ias # mph
51 | if(self.data_format==1):
52 | return self.ias * 0.8689758 #knots
53 | if(self.data_format==2):
54 | return self.ias * 1.609 #km/h
55 |
56 | # get true airspeed in converted format.
57 | def get_tas(self):
58 | if(self.data_format==0):
59 | return self.tas # mph
60 | if(self.data_format==1):
61 | return self.tas * 0.8689758 #knots
62 | if(self.data_format==2):
63 | return self.tas * 1.609 #km/h
64 |
65 | # get speed format description
66 | def get_speed_description(self):
67 | if(self.data_format==0):
68 | return "mph"
69 | if(self.data_format==1):
70 | return "kts"
71 | if(self.data_format==2):
72 | return "km/h"
73 |
74 | # get ALT in converted format.
75 | def get_alt(self):
76 | #use GPS Alt?
77 | if(self.data_format==0):
78 | return self.alt # ft
79 | if(self.data_format==1):
80 | return self.alt #ft
81 | if(self.data_format==2):
82 | return self.alt * 0.3048 #meters
83 |
84 | # get Baro Alt in converted format.
85 | def get_balt(self):
86 | if((self.BALT == None and self.gps.GPSAlt != None) or self.altUseGPS == True):
87 | self.BALT = self.gps.GPSAlt
88 | self.altUseGPS = True
89 | elif(self.BALT == None):
90 | return 0
91 | if(self.data_format==0):
92 | return self.BALT # ft
93 | if(self.data_format==1):
94 | return self.BALT #ft
95 | if(self.data_format==2):
96 | return self.BALT * 0.3048 #meters
97 |
98 | # get distance format description
99 | def get_distance_description(self):
100 | if(self.data_format==0):
101 | return "ft"
102 | if(self.data_format==1):
103 | return "ft"
104 | if(self.data_format==2):
105 | return "m"
106 |
107 | # get baro in converted format.
108 | def get_baro(self):
109 | if(self.data_format==0):
110 | return self.baro # inHg
111 | if(self.data_format==1):
112 | return self.baro #inHg
113 | if(self.data_format==2):
114 | return self.baro * 33.863886666667 #mbars
115 |
116 | # get baro format description
117 | def get_baro_description(self):
118 | if(self.data_format==0):
119 | return "in"
120 | if(self.data_format==1):
121 | return "in"
122 | if(self.data_format==2):
123 | return "mb"
124 |
125 | # get oat in converted format.
126 | def get_oat(self):
127 | if(self.data_format_temp==0):
128 | return self.oat # f
129 | if(self.data_format_temp==1):
130 | return ((self.oat - 32) / 1.8)
131 |
132 | # get temp format description
133 | def get_temp_description(self):
134 | if(self.data_format_temp==0):
135 | return "\xb0f"
136 | if(self.data_format_temp==1):
137 | return "\xb0c"
138 |
139 | # get Vertical speed in converted format.
140 | def get_vsi_string(self):
141 | if(self.data_format==0):
142 | v = self.vsi # ft
143 | d = "fpm"
144 | elif(self.data_format==1):
145 | v = self.vsi #ft
146 | d = "fpm"
147 | elif(self.data_format==2):
148 | v = round(self.vsi * 0.00508) #meters per second
149 | d = "mps"
150 |
151 | if v == 0:
152 | return " %d %s" % (v,d)
153 | elif v < 0:
154 | return "%d %s" % (v,d)
155 | else:
156 | return "+%d %s" % (v,d)
157 |
158 |
159 | # vi: modeline tabstop=8 expandtab shiftwidth=4 softtabstop=4 syntax=python
160 |
--------------------------------------------------------------------------------
/lib/common/dataship/dataship_analog.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from enum import Enum
4 | import inspect
5 | from typing import List, Any
6 | import time
7 |
8 | #############################################
9 | ## Class: Analog Input Data
10 | class AnalogData(object):
11 | def __init__(self):
12 | self.inputSrcName = None
13 | self.inputSrcNum = None
14 |
15 | self.Name = None
16 | self.Num = 0
17 | self.Min = None
18 | self.Max = None
19 | self.Data = [0,0,0,0,0,0,0,0]
20 |
21 | def setup(self, name, num, min, max):
22 | self.Name = name
23 | self.Num = num
24 | self.Min = min
25 | self.Max = max
26 | # create self.Data list with size of num
27 | self.Data = [0] * num
28 |
29 |
30 |
31 | # vi: modeline tabstop=8 expandtab shiftwidth=4 softtabstop=4 syntax=python
32 |
--------------------------------------------------------------------------------
/lib/common/dataship/dataship_engine_fuel.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from enum import Enum
4 | import inspect
5 | from typing import List, Any
6 | import time
7 |
8 | #############################################
9 | ## Class: EngineData
10 | class EngineData(object):
11 | def __init__(self):
12 | self.id = None
13 | self.name = None
14 | self.inputSrcName = None
15 | self.inputSrcNum = None
16 |
17 | self.NumberOfCylinders = 0
18 | self.RPM = 0
19 | self.ManPress = 0 # manifold pressure in PSI
20 | self.OilPress = 0 # oil pressure in PSI
21 | self.OilPress2 = 0 # oil pressure in PSI
22 | self.OilTemp = 0 # oil temperature in F
23 | self.OilTemp2 = 0 # oil temperature in F
24 | self.CoolantTemp = 0 # coolant temperature in F
25 | self.FuelFlow = 0 # fuel flow in GPH
26 | self.FuelFlow2 = 0 # fuel flow in GPH
27 | self.FuelPress = 0 # fuel pressure in PSI
28 | self.EGT = [0,0,0,0,0,0,0,0] # EGT in F
29 | self.CHT = [0,0,0,0,0,0,0,0] # CHT in F
30 |
31 | self.volts1 = None
32 | self.volts2 = None
33 | self.amps = None
34 |
35 | self.hobbs_time = None
36 | self.tach_time = None
37 |
38 |
39 | self.msg_count = 0
40 | self.msg_last = ""
41 |
42 | #############################################
43 | ## Class: FuelData
44 | class FuelData(object):
45 | def __init__(self):
46 |
47 | self.FuelLevels = [0,0,0,0]
48 |
49 | self.FuelRemain = None
50 |
51 | self.msg_count = 0
52 | self.msg_last = ""
53 |
54 |
55 | # vi: modeline tabstop=8 expandtab shiftwidth=4 softtabstop=4 syntax=python
56 |
--------------------------------------------------------------------------------
/lib/common/dataship/dataship_gps.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from enum import Enum
4 | import inspect
5 | from typing import List, Any
6 | import time
7 |
8 | #############################################
9 | ## Class: GPSData
10 | class GPSData(object):
11 | def __init__(self):
12 | self.inputSrcName = None
13 | self.inputSrcNum = None
14 |
15 | self.Source = None # Source Name.
16 |
17 | self.Lat = None # latitude in decimal degrees
18 | self.Lon = None # longitude in decimal degrees
19 | self.Alt = None # in feet MSL
20 | self.GPSTime = None
21 | self.GPSTime_string = None
22 | self.GPSDate_string = None
23 | self.LastUpdate = None
24 |
25 | self.Alt = None # ft MSL
26 | self.GndTrack = None # True track from GPS.
27 | self.GndSpeed = None
28 |
29 | self.AltPressure = None # Pressure altitude in feet (some GPS units report this)
30 |
31 | self.Mag_Decl = None # Magnetic variation 10th/deg West = Neg
32 |
33 | self.EWVelDir = None # E or W
34 | self.EWVelmag = None # x.x m/s
35 | self.NSVelDir = None # N or S
36 | self.NSVelmag = None # x.x m/s
37 | self.VVelDir = None # U or D
38 | self.VVelmag = None # x.xx m/s
39 |
40 | self.SatsTracked = None
41 | self.SatsVisible = None
42 | self.GPSStatus = None # GPS status. 0=Acquiring, 1=dead reckoning, 2=2d fix, 3=3dfix, 4=2dfix(imu), 5=3dfix(imu)
43 | self.GPSWAAS = None # GPS waas. False, or True
44 | self.Accuracy = None # GPS accuracy. 0=None, 1=2D, 2=3D
45 |
46 | self.msg_count = 0
47 | self.msg_last = None
48 | self.msg_bad = 0
49 | self.data_format = 0
50 |
51 | def get_status_string(self):
52 | if(self.GPSStatus==None):
53 | return "NA"
54 | elif(self.GPSStatus==0):
55 | return "Acquiring"
56 | elif(self.GPSStatus==1):
57 | return "Acquiring" #GPS internal dead reckoning
58 | elif(self.GPSStatus==2):
59 | return "2D fix"
60 | elif(self.GPSStatus==3):
61 | return "3D fix"
62 | elif(self.GPSStatus==4):
63 | return "2D fix+" #2D fix EFIS dead reckoning (IMU)
64 | elif(self.GPSStatus==5):
65 | return "3D fix+" #3D fix EFIS dead reckoning (IMU)
66 |
67 | def set_gps_location(self, lat, lon, alt, timeString=None):
68 | self.Lat = lat
69 | self.Lon = lon
70 | self.Alt = alt
71 | if timeString is not None:
72 | self.GPSTime = timeString
73 |
74 | self.LastUpdate = time.time()
75 |
76 | # get ground speed in converted format.
77 | def get_gs(self):
78 | if(self.GndSpeed == None):
79 | return None
80 | if(self.data_format==0):
81 | return self.GndSpeed # mph
82 | if(self.data_format==1):
83 | return self.GndSpeed * 0.8689758 #knots
84 | if(self.data_format==2):
85 | return self.GndSpeed* 1.609 #km/h
86 |
87 | # get speed format description
88 | def get_speed_description(self):
89 | if(self.data_format==0):
90 | return "mph"
91 | if(self.data_format==1):
92 | return "kts"
93 | if(self.data_format==2):
94 | return "km/h"
95 |
96 |
97 | # vi: modeline tabstop=8 expandtab shiftwidth=4 softtabstop=4 syntax=python
98 |
--------------------------------------------------------------------------------
/lib/common/dataship/dataship_imu.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 | ## Enum: Purpose
4 | class IMU_Purpose(Enum):
5 | NONE = 0
6 | CAMERA = 1
7 | AHRS = 2
8 |
9 |
10 | #############################################
11 | ## Class: IMU
12 | class IMUData(object):
13 | def __init__(self):
14 | self.inputSrcName = None
15 | self.inputSrcNum = None
16 |
17 | self.id = ""
18 | self.name = ""
19 | self.purpose: IMU_Purpose = None # IMU_Purpose
20 | self.address = 0
21 | self.hz = 0
22 |
23 | self.pitch = None
24 | self.roll = None
25 | self.yaw = None
26 | self.turn_rate = None # Turn rate in 10th of a degree per second
27 | self.slip_skid = None # -99 to +99. (-99 is full right)
28 | self.mag_head = None # Magnetic heading in degrees
29 | self.vert_G = None # Vertical G force.
30 |
31 | self.cali_mag = None
32 | self.cali_accel = None
33 | self.cali_gyro = None
34 |
35 | self.home_pitch = None
36 | self.home_roll = None
37 | self.home_yaw = None
38 |
39 | self.org_pitch = None
40 | self.org_roll = None
41 | self.org_yaw = None
42 |
43 | self.input = None
44 |
45 | self.msg_count = 0
46 | self.msg_last = None
47 | self.msg_bad = 0
48 | self.msg_unknown = 0
49 |
50 |
51 | def home(self, delete=False):
52 | '''
53 | Set the home position.
54 | '''
55 | if delete:
56 | self.home_pitch = None
57 | self.home_roll = None
58 | self.home_yaw = None
59 | else:
60 | # now set the new home position.
61 | self.home_pitch = self.org_pitch
62 | self.home_roll = self.org_roll
63 | self.home_yaw = self.org_yaw
64 |
65 | def updatePos(self, pitch, roll, yaw):
66 | '''
67 | Update the position of the IMU.
68 | This will adjust the pitch, roll, and yaw to be relative to the home position (if set)
69 | '''
70 | if pitch is None or roll is None:
71 | self.pitch = None
72 | self.roll = None
73 | self.yaw = None
74 | return
75 | else:
76 | self.org_pitch = pitch
77 | self.org_roll = roll
78 |
79 | # convert yaw from 0-360 to -180 to 180.
80 | if yaw is not None:
81 | self.org_yaw = ((yaw + 180) % 360) - 180
82 |
83 | # if not None then adjust all values relative to home.
84 | if self.home_pitch is not None:
85 | # Subtract home values and normalize to -180 to 180 range
86 | self.pitch = round(((self.org_pitch - self.home_pitch + 180) % 360) - 180,3)
87 | self.roll = round(((self.org_roll - self.home_roll + 180) % 360) - 180,3)
88 | self.yaw = round(((self.org_yaw - self.home_yaw + 180) % 360) - 180,3)
89 | else:
90 | self.pitch = self.org_pitch
91 | self.roll = self.org_roll
92 | self.yaw = self.org_yaw
93 |
94 |
--------------------------------------------------------------------------------
/lib/common/dataship/dataship_nav.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from enum import Enum
4 | import inspect
5 | from typing import List, Any
6 | import time
7 |
8 | #############################################
9 | ## Class: NavData
10 | class NavData(object):
11 | def __init__(self):
12 | self.id = None
13 | self.name = None
14 | self.inputSrcName = None
15 | self.inputSrcNum = None
16 |
17 | self.NavStatus = ""
18 | self.HSISource = 0
19 | self.VNAVSource = 0
20 | self.SourceDesc = ""
21 | self.HSINeedle = 0
22 | self.HSIRoseHeading = 0
23 | self.HSIHorzDev = 0
24 | self.HSIVertDev = 0
25 |
26 | self.AP = None # AP enabled? 0 = off, 1 = on
27 |
28 | self.AP_RollForce = None
29 | self.AP_RollPos = None
30 | self.AP_RollSlip = None
31 |
32 | self.AP_PitchForce = None
33 | self.AP_PitchPos = None
34 | self.AP_PitchSlip = None
35 |
36 | self.AP_YawForce = None
37 | self.AP_YawPos = None
38 | self.AP_YawSlip = None
39 |
40 | self.HeadBug = 0
41 | self.AltBug = 0
42 | self.ASIBug = 0
43 | self.VSBug = 0
44 |
45 | self.WPDist = 0
46 | self.WPTrack = 0
47 | self.WPName = None
48 | self.WPLat = None
49 | self.WPLon = None
50 |
51 | self.ILSDev = 0
52 | self.GSDev = 0
53 | self.GLSHoriz = 0
54 | self.GLSVert = 0
55 |
56 | self.XPDR_Status = None
57 | self.XPDR_Reply = None
58 | self.XPDR_Code = None
59 | self.XPDR_Ident = None
60 |
61 |
62 |
63 | self.msg_count = 0
64 | self.msg_last = ""
65 |
66 |
67 | # vi: modeline tabstop=8 expandtab shiftwidth=4 softtabstop=4 syntax=python
68 |
--------------------------------------------------------------------------------
/lib/common/event_manager.py:
--------------------------------------------------------------------------------
1 | from typing import List, Dict, Callable, Optional
2 | from dataclasses import dataclass
3 | from enum import Enum
4 | import time
5 |
6 | class EventPriority(Enum):
7 | LOW = 0
8 | NORMAL = 1
9 | HIGH = 2
10 | CRITICAL = 3
11 |
12 | @dataclass
13 | class Event:
14 | id: str
15 | callback: Callable
16 | priority: EventPriority = EventPriority.NORMAL
17 | delay: float = 0.0 # Delay in seconds before executing
18 | created_at: float = 0.0
19 | repeat: bool = False
20 | repeat_interval: float = 0.0 # Time between repeats in seconds
21 | last_run: float = 0.0
22 | conditions: Dict = None # Optional conditions that must be met
23 |
24 | class EventManager:
25 | def __init__(self):
26 | self.events: List[Event] = []
27 | self.running = True
28 |
29 | def add_event(
30 | self,
31 | id: str,
32 | callback: Callable,
33 | priority: EventPriority = EventPriority.NORMAL,
34 | delay: float = 0.0,
35 | repeat: bool = False,
36 | repeat_interval: float = 0.0,
37 | conditions: Dict = None
38 | ) -> None:
39 | """
40 | Add a new event to the manager.
41 |
42 | Args:
43 | id: Unique identifier for the event
44 | callback: Function to call when event is processed
45 | priority: Priority level for the event
46 | delay: Time to wait before first execution
47 | repeat: Whether the event should repeat
48 | repeat_interval: Time between repeats
49 | conditions: Dictionary of conditions that must be met for event to run
50 | """
51 | event = Event(
52 | id=id,
53 | callback=callback,
54 | priority=priority,
55 | delay=delay,
56 | created_at=time.time(),
57 | repeat=repeat,
58 | repeat_interval=repeat_interval,
59 | conditions=conditions
60 | )
61 | self.events.append(event)
62 | # Sort events by priority
63 | self.events.sort(key=lambda x: x.priority.value, reverse=True)
64 |
65 | def remove_event(self, event_id: str) -> None:
66 | """Remove an event by its ID."""
67 | self.events = [e for e in self.events if e.id != event_id]
68 |
69 | def check_conditions(self, conditions: Dict) -> bool:
70 | """
71 | Check if all conditions for an event are met.
72 | Override this method to implement custom condition checking logic.
73 | """
74 | if not conditions:
75 | return True
76 | # Implement your condition checking logic here
77 | return True
78 |
79 | def process_events(self) -> None:
80 | """
81 | Process all pending events based on their priority and timing.
82 | Should be called regularly from the main loop.
83 | """
84 | if not self.running:
85 | return
86 |
87 | current_time = time.time()
88 | processed_events = []
89 |
90 | for event in self.events:
91 | # Skip if delay hasn't elapsed yet
92 | if current_time - event.created_at < event.delay:
93 | continue
94 |
95 | # For repeating events, check if enough time has passed since last run
96 | if event.repeat and event.last_run > 0:
97 | if current_time - event.last_run < event.repeat_interval:
98 | continue
99 |
100 | # Check if conditions are met
101 | if not self.check_conditions(event.conditions):
102 | continue
103 |
104 | try:
105 | event.callback()
106 | event.last_run = current_time
107 |
108 | # If event is not repeating, mark it for removal
109 | if not event.repeat:
110 | processed_events.append(event)
111 |
112 | except Exception as e:
113 | print(f"Error processing event {event.id}: {str(e)}")
114 | processed_events.append(event) # Remove failed events
115 |
116 | # Remove processed non-repeating events
117 | for event in processed_events:
118 | self.events.remove(event)
119 |
120 | def clear_events(self) -> None:
121 | """Clear all events from the manager."""
122 | self.events.clear()
123 |
124 | def pause(self) -> None:
125 | """Pause event processing."""
126 | self.running = False
127 |
128 | def resume(self) -> None:
129 | """Resume event processing."""
130 | self.running = True
131 |
132 | def get_event(self, event_id: str) -> Optional[Event]:
133 | """Get an event by its ID."""
134 | for event in self.events:
135 | if event.id == event_id:
136 | return event
137 | return None
--------------------------------------------------------------------------------
/lib/common/graphic/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/common/graphic/edit_clone.py:
--------------------------------------------------------------------------------
1 | from lib.common.graphic.edit_TronViewScreenObject import TronViewScreenObject
2 |
3 | def clone_screen_objects(selected_objects, mouse_x, mouse_y):
4 | if not selected_objects:
5 | return []
6 |
7 | cloned_objects = []
8 | reference_obj = selected_objects[0]
9 | offset_x = mouse_x - reference_obj.x
10 | offset_y = mouse_y - reference_obj.y
11 |
12 | for obj in selected_objects:
13 | new_obj = TronViewScreenObject(
14 | obj.pygamescreen,
15 | obj.type,
16 | obj.title + "_clone",
17 | module=None,
18 | x=obj.x + offset_x,
19 | y=obj.y + offset_y,
20 | width=obj.width,
21 | height=obj.height,
22 | id=None
23 | )
24 |
25 | new_obj.showBounds = obj.showBounds
26 | new_obj.showOptions = obj.showOptions
27 |
28 | if obj.type == 'group':
29 | new_obj.childScreenObjects = [clone_screen_object(child, offset_x, offset_y) for child in obj.childScreenObjects]
30 | else:
31 | if obj.module:
32 | new_module = type(obj.module)()
33 | for attr, value in vars(obj.module).items():
34 | if not callable(value) and not attr.startswith("__"):
35 | setattr(new_module, attr, value)
36 | new_obj.setModule(new_module)
37 | new_obj.showOptions = False
38 | new_obj.showEvents = False
39 |
40 | # reset the width and height to the original values.
41 | new_obj.resize(obj.width, obj.height)
42 | cloned_objects.append(new_obj)
43 |
44 | return cloned_objects
45 |
46 | def clone_screen_object(obj, offset_x, offset_y):
47 | new_obj = TronViewScreenObject(
48 | obj.pygamescreen,
49 | obj.type,
50 | obj.title + "_clone",
51 | module=None,
52 | x=obj.x + offset_x,
53 | y=obj.y + offset_y,
54 | width=obj.width,
55 | height=obj.height,
56 | id=None
57 | )
58 |
59 | new_obj.showBounds = obj.showBounds
60 | new_obj.showOptions = obj.showOptions
61 |
62 | if obj.type == 'group':
63 | new_obj.childScreenObjects = [clone_screen_object(child, offset_x, offset_y) for child in obj.childScreenObjects]
64 | else:
65 | if obj.module:
66 | # new_module = type(obj.module)()
67 | # for attr, value in vars(obj.module).items():
68 | # if not callable(value) and not attr.startswith("__"):
69 | # setattr(new_module, attr, value)
70 | # new_obj.setModule(new_module, showOptions = obj.showOptions, width = obj.width, height = obj.height)
71 | new_obj.from_dict(obj.to_dict(), load_grid_position = False)
72 | new_obj.showOptions = False
73 | new_obj.showEvents = False
74 | new_obj.move(obj.x, obj.y)
75 |
76 | # reset the width and height to the original values.
77 | new_obj.resize(obj.width, obj.height)
78 | return new_obj
79 |
80 |
--------------------------------------------------------------------------------
/lib/common/graphic/edit_find_module.py:
--------------------------------------------------------------------------------
1 | import os
2 | import importlib
3 |
4 | def find_module(byName = None, debugOutput = False):
5 | # find all modules in the lib/modules folder recursively. look for all .py files.
6 | # for each file, look for a class that inherits from Module.
7 | # return a list of all modules found.
8 | # example: lib/modules/efis/trafficscope/trafficscope.py
9 | modules = []
10 | moduleNames = []
11 | for root, dirs, files in os.walk("lib/modules"):
12 | for file in files:
13 | if file.endswith(".py") and not file.startswith("_"):
14 | # get full path to the file
15 | path = os.path.join(root, file)
16 | # get the name after lib/modules but before the file name and remove slashes
17 | modulePath = path.split("lib/modules/")[1].split("/"+file)[0].replace("/",".")
18 | # import the file
19 | module = ".%s" % (file[:-3])
20 | mod = importlib.import_module(module, "lib.modules."+modulePath) # dynamically load class
21 | ClassToload = file[:-3] # get classname to load
22 | class_ = getattr(mod, ClassToload)
23 | newModuleClass = class_()
24 | # if the module has a hide attribute, and it is true, skip it.
25 | if hasattr(newModuleClass, "hide_from_add_menu"):
26 | if newModuleClass.hide_from_add_menu:
27 | continue
28 | if byName is not None:
29 | #print("Looking for "+byName+" .. Checking module: %s (%s)" % (newModuleClass.name, path))
30 | if newModuleClass.name == byName:
31 | #print("Found module: %s (%s)" % (newModuleClass.name, path))
32 | modules.append(newModuleClass)
33 | moduleNames.append(newModuleClass.name)
34 | return modules, moduleNames
35 | else:
36 | if debugOutput:
37 | print("module: %s (%s)" % (newModuleClass.name, path))
38 | modules.append(newModuleClass)
39 | moduleNames.append(newModuleClass.name)
40 |
41 | # sort the modules by name
42 | modules.sort(key=lambda x: x.name)
43 | moduleNames.sort()
44 |
45 | #print("Found %d modules" % len(modules))
46 | #print(modules)
47 | return modules, moduleNames
48 |
--------------------------------------------------------------------------------
/lib/common/graphic/edit_help.py:
--------------------------------------------------------------------------------
1 | import pygame
2 | import pygame_gui
3 | from pygame_gui.elements import UIWindow, UITextBox
4 | from lib.version import __version__, __build_date__, __build__, __build_time__
5 |
6 | def show_help_dialog(pygame_gui_manager):
7 | help_text = f"""
8 | TronView {__version__}
9 | Build: {__build__} {__build_date__} {__build_time__}
10 | By running this software you agree to the terms of the license.
11 | Use at own risk!
12 | TronView.org
13 |
14 | Key Commands:
15 | ? - Show this help dialog
16 | E - Exit Edit Mode (goto normal mode)
17 | Q - Quit
18 | ESC - unselect all objects
19 | DELETE or BACKSPACE - Delete selected object
20 | TAB - Cycle through selected objects
21 | SHIFT - multiple select objects
22 |
23 | A - Add new screen object
24 | G - Group selected objects
25 | Ctrl G - Ungroup selected group
26 | B - Toggle boundary boxes
27 | C - Clone selected object(s)
28 |
29 | S - Save current screen to JSON
30 | L - Load screen from JSON
31 | F - Toggle FPS and draw time display
32 | R - Toggle ruler mode and grid display mode
33 | Arrow keys - Move selected object(s)
34 | Ctrl + Arrow keys - Move selected object(s) by 10 pixels
35 | PAGE UP - Move selected object up in draw order
36 | PAGE DOWN - Move selected object down in draw order
37 | Ctrl Z - Undo last change
38 | """
39 |
40 | window_width = 500
41 | window_height = 550
42 | screen_width, screen_height = pygame.display.get_surface().get_size()
43 | x = (screen_width - window_width) // 2
44 | y = (screen_height - window_height) // 2
45 |
46 | help_window = UIWindow(pygame.Rect(x, y, window_width, window_height),
47 | pygame_gui_manager,
48 | window_display_title="Help",
49 | object_id="#help_window")
50 |
51 | UITextBox(help_text,
52 | pygame.Rect(10, 10, window_width - 20, window_height - 40),
53 | pygame_gui_manager,
54 | container=help_window,
55 | object_id="#help_textbox")
56 |
57 | print("Help window created") # Add this line for debugging
58 | return help_window
59 |
--------------------------------------------------------------------------------
/lib/common/graphic/edit_history.py:
--------------------------------------------------------------------------------
1 | from collections import deque
2 |
3 | class ChangeHistory:
4 | def __init__(self, max_history=100):
5 | self.history = deque(maxlen=max_history)
6 |
7 | def add_change(self, change_type, data):
8 | self.history.append({"type": change_type, "data": data})
9 | #print(f"history added: {change_type}, {data}")
10 |
11 | def undo(self):
12 | if self.history:
13 | return self.history.pop()
14 | return None
15 |
16 | def clear(self):
17 | self.history.clear()
18 |
19 | def undo_last_change(change_history, shared):
20 | change = change_history.undo()
21 | if change:
22 | print(f"undoing change: {change}")
23 | if change["type"] == "move":
24 | change["data"]["object"].move(*change["data"]["old_pos"])
25 | elif change["type"] == "delete":
26 | shared.CurrentScreen.ScreenObjects.append(change["data"]["object"])
27 | elif change["type"] == "add":
28 | # if it's a list of objects then remove all of them
29 | if isinstance(change["data"]["object"], list):
30 | for obj in change["data"]["object"]:
31 | shared.CurrentScreen.ScreenObjects.remove(obj)
32 | else:
33 | # else just remove the one object
34 | shared.CurrentScreen.ScreenObjects.remove(change["data"]["object"])
35 | elif change["type"] == "option_change":
36 | setattr(change["data"]["object"].module, change["data"]["option"], change["data"]["old_value"])
37 | if hasattr(change["data"]["object"].module, 'update_option'):
38 | change["data"]["object"].module.update_option(change["data"]["option"], change["data"]["old_value"])
39 | elif change["type"] == "resize":
40 | change["data"]["object"].resize(*change["data"]["old_size"])
41 | else:
42 | print("no change to undo")
43 |
--------------------------------------------------------------------------------
/lib/common/graphic/edit_save_load.py:
--------------------------------------------------------------------------------
1 | import os
2 | import importlib
3 | import json
4 | from lib.common import shared
5 | from lib.common.graphic.edit_TronViewScreenObject import TronViewScreenObject
6 | from datetime import datetime
7 | from lib import hud_utils
8 |
9 | def save_screen_to_json(filename=None, update_settings_last_run=True):
10 |
11 | # shared.pygamescreen is pygame screen
12 | # shared.smartdisplay is smartdisplay object
13 | # shared.CurrentScreen is current screen object
14 | if filename is not None:
15 | shared.CurrentScreen.filename = filename
16 |
17 | if shared.CurrentScreen.filename is None:
18 | shared.CurrentScreen.filename = "screen.json"
19 |
20 | if not shared.CurrentScreen.filename.endswith(".json"):
21 | shared.CurrentScreen.filename = shared.CurrentScreen.filename + ".json"
22 |
23 | # if the screen name is empty or none, then use the filename without the extension
24 | if shared.CurrentScreen.name == "" or shared.CurrentScreen.name is None:
25 | shared.CurrentScreen.name = shared.CurrentScreen.filename.split(".")[0]
26 |
27 | # Convert screen objects to dictionaries, ensuring proper serialization
28 | screen_objects_data = []
29 | for obj in shared.CurrentScreen.ScreenObjects:
30 | try:
31 | obj_dict = obj.to_dict()
32 | screen_objects_data.append(obj_dict)
33 | except Exception as e:
34 | print(f"Warning: Failed to serialize screen object {obj.title}: {str(e)}")
35 | continue
36 |
37 | data = {
38 | "ver": {"version": "1.0"}, # You can update this version as needed
39 | "screen": {
40 | "title": shared.CurrentScreen.name,
41 | "filename": shared.CurrentScreen.filename,
42 | "width": shared.smartdisplay.x_end,
43 | "height": shared.smartdisplay.y_end,
44 | "date_updated": datetime.now().strftime("%Y%m%d_%H%M%S")
45 | },
46 | "screenObjects": screen_objects_data
47 | }
48 |
49 | filename = shared.CurrentScreen.filename
50 |
51 | #timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
52 | filename = shared.DataDir + "screens/" + filename
53 | # if it doesn't end with .json then add it.
54 |
55 | with open(filename, "w") as f:
56 | json.dump(data, f, indent=2)
57 |
58 | print(f"Screen saved to {filename}")
59 |
60 | # update the last run config
61 | if update_settings_last_run:
62 | hud_utils.writeConfig("Main", "screen", shared.CurrentScreen.filename)
63 |
64 |
65 |
66 | ########################################################
67 | # Load screen from json file
68 | ########################################################
69 | def load_screen_from_json(filename,from_templates=False, update_settings_last_run=True):
70 |
71 | if not hasattr(shared.CurrentScreen, "ScreenObjects"):
72 | shared.CurrentScreen.ScreenObjects = []
73 | shared.CurrentScreen.ScreenObjects = []
74 |
75 | try:
76 |
77 | # if filename starts with "template:" then load from templates
78 | if filename.startswith("template:"):
79 | filename = filename.split(":")[1]
80 | from_templates = True
81 |
82 | if from_templates:
83 | filename = "lib/screens/templates/" + filename
84 | else:
85 | filename = shared.DataDir + "screens/" + filename
86 | # if it doesn't end with .json then add it.
87 | if not filename.endswith(".json"):
88 | filename = filename + ".json"
89 | with open(filename, 'r') as f:
90 | data = json.load(f)
91 |
92 | just_filename = filename.split("/")[-1]
93 |
94 |
95 | # Clear existing screen objects
96 | shared.CurrentScreen.ScreenObjects.clear()
97 |
98 | # Set screen properties
99 | shared.CurrentScreen.name = data['screen']['title']
100 | if from_templates:
101 | shared.CurrentScreen.filename = "" # reset filename to empty string so when we save it the user will enter a new filename.
102 | # make sure filename exists and is not empty
103 | shared.CurrentScreen.loaded_from_template = just_filename
104 | else:
105 | shared.CurrentScreen.filename = just_filename
106 |
107 | # Load screen objects using the from_dict method
108 | for obj_data in data['screenObjects']:
109 | new_obj = TronViewScreenObject(shared.pygamescreen, obj_data['type'], obj_data['title'])
110 | new_obj.from_dict(obj_data)
111 | shared.CurrentScreen.ScreenObjects.append(new_obj)
112 |
113 | print(f"Screen loaded from {filename}")
114 |
115 |
116 | # update the last run config
117 | if update_settings_last_run:
118 | # if template then add template: to the filename
119 | if from_templates:
120 | just_filename = "template:" + just_filename
121 | hud_utils.writeConfig("Main", "screen", just_filename)
122 |
123 | except Exception as e:
124 | # get full error
125 | import traceback
126 | traceback.print_exc()
127 | print(f"Error loading screen from {filename}: {str(e)}")
128 |
--------------------------------------------------------------------------------
/lib/common/graphic/objects/TronViewImageObject.py:
--------------------------------------------------------------------------------
1 | import os
2 | from dataclasses import dataclass
3 | from typing import Optional
4 | import io
5 | import base64
6 | from PIL import Image
7 |
8 | @dataclass
9 | class TronViewImageObject:
10 | """Class for storing image information"""
11 | file_name: str
12 | file_path: str
13 | file_type: str = "image"
14 | file_size: Optional[int] = None
15 | base64: Optional[str] = None
16 |
17 | width: Optional[int] = None
18 | height: Optional[int] = None
19 |
20 | def __post_init__(self):
21 | """Calculate file size if not provided"""
22 | if self.file_size is None and os.path.exists(self.file_path):
23 | self.file_size = os.path.getsize(self.file_path)
24 |
25 | def to_dict(self) -> dict:
26 | """Convert the object to a dictionary representation"""
27 | return {
28 | "file_name": self.file_name,
29 | "file_path": self.file_path,
30 | "file_size": self.file_size,
31 | "file_type": self.file_type,
32 | "base64": self.base64
33 | }
34 |
35 | @classmethod
36 | def from_dict(cls, data: dict) -> 'TronViewImageObject':
37 | """Create an instance from a dictionary"""
38 | # if no width and height.. figure it out based on the base64
39 | if 'width' not in data or 'height' not in data:
40 | # use PIL to get the width and height. load from base64
41 | if data['base64']:
42 | image = Image.open(io.BytesIO(base64.b64decode(data['base64'])))
43 | else:
44 | # else error
45 | raise ValueError("No base64 data provided")
46 | data['width'] = image.width
47 | data['height'] = image.height
48 | print(f"calculated width: {data['width']}, height: {data['height']}")
49 | return cls(**data)
50 |
--------------------------------------------------------------------------------
/lib/common/graphic/theme.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaults": {
3 | "misc": {
4 | "enable_close_button": "0"
5 | },
6 | "colours": {
7 | "normal_bg":"#45494e",
8 | "hovered_bg":"#35393e",
9 | "disabled_bg":"#25292e",
10 | "selected_bg":"#193754",
11 | "dark_bg":"#15191e",
12 | "normal_text":"#c5cbd8",
13 | "hovered_text":"#FFFFFF",
14 | "selected_text":"#FFFFFF",
15 | "disabled_text":"#6d736f",
16 | "link_text": "#0000EE",
17 | "link_hover": "#2020FF",
18 | "link_selected": "#551A8B",
19 | "text_shadow": "#777777",
20 | "normal_border": "#DDDDDD",
21 | "hovered_border": "#B0B0B0",
22 | "disabled_border": "#808080",
23 | "selected_border": "#8080B0",
24 | "active_border": "#8080B0",
25 | "filled_bar":"#f4251b",
26 | "unfilled_bar":"#CCCCCC"
27 | }
28 | },
29 | "#options_window": {
30 | "misc": {
31 | "enable_close_button": "0"
32 | }
33 | },
34 | "#handler_details_window": {
35 | "misc": {
36 | "enable_close_button": "0"
37 | }
38 | },
39 | "#events_window": {
40 | "misc": {
41 | "enable_close_button": "0"
42 | }
43 | },
44 | "#help_window": {
45 | "misc": {
46 | "enable_close_button": "0"
47 | }
48 | }
49 |
50 | }
--------------------------------------------------------------------------------
/lib/common/shared.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | #######################################################################################################################################
4 | #######################################################################################################################################
5 | # Shared globals objects
6 | #
7 |
8 | from lib.common.dataship import dataship
9 | from lib import smartdisplay
10 | from lib.common.graphic.growl_manager import GrowlManager
11 | from lib.common.event_manager import EventManager
12 | from lib.common.graphic.edit_TronViewScreenObject import TronViewScreenObject
13 |
14 | ####################################
15 | ## DataShip object
16 | ## All input data is stuffed into this dataship object in a "standard format"
17 | ## Then dataship object is passed on to different screens for displaying data.
18 | Dataship = dataship.Dataship()
19 |
20 |
21 | ####################################
22 | ## Input objects
23 | ## Input objects take in external data, process it if needed then
24 | ## stuff the data into the dataship object for the screens to use.
25 | ## Inputs can be from Serial, Files, wifi, etc...
26 |
27 | Inputs = {}
28 |
29 | ####################################
30 | ## SmartDisplay Obect
31 | ## This is a helper object that knows the screen size and ratio.
32 | ## Makes it easier for screens to write data to the screen without having to
33 | ## know all the details of the screen.
34 | smartdisplay = smartdisplay.SmartDisplay()
35 |
36 |
37 | ####################################
38 | ## Screen Obect
39 | ## This is the current graphical screen object that is being displayed.
40 | class Screen2d(object):
41 | def __init__(self):
42 | self.ScreenObjects: list[TronViewScreenObject] = []
43 | self.show_FPS = False
44 | self.name = None
45 | self.filename = None
46 | self.loaded_from_template = None
47 |
48 | def clear(self):
49 | self.ScreenObjects.clear()
50 | self.filename = None
51 | self.loaded_from_template = None
52 | self.name = None
53 |
54 | CurrentScreen = Screen2d()
55 |
56 | pygamescreen = None
57 |
58 | ####################################
59 | ## Default flight log dir.
60 | ## default location where flight logs are saved. Can be overwritten in config file.
61 | DefaultFlightLogDir = "./flightlog/"
62 |
63 |
64 | ####################################
65 | ## Default data dir.
66 | ## default location where data is saved. Can be overwritten in config file.
67 | DataDir = "./data/"
68 |
69 |
70 | ####################################
71 | ## Change History
72 | ## This is a global object that is used to store the history of changes to the screen objects while in edit mode.
73 | ## This is used for the undo functionality.
74 | Change_history = None
75 |
76 | ####################################
77 | ## Growl Manager
78 | ## This is a global object that is used to manage the growl messages.
79 | GrowlManager = GrowlManager()
80 |
81 | ####################################
82 | ## Event Manager
83 | ## This is a global object that is used to manage events.
84 | EventManager = EventManager()
85 |
86 | ####################################
87 | ## Active Dropdown
88 | ## This is a global reference to the currently active dropdown menu in edit mode
89 | active_dropdown = None
90 |
91 | # vi: modeline tabstop=8 expandtab shiftwidth=4 softtabstop=4 syntax=python
92 |
93 |
--------------------------------------------------------------------------------
/lib/common/text/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/common/text/text_mode.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | #######################################################################################################################################
4 | #######################################################################################################################################
5 | # text mode related
6 | #
7 | #
8 | #
9 | from lib.common import shared
10 |
11 | import threading
12 | import math, os, sys, random
13 | import argparse, pygame
14 | import time
15 | import configparser
16 | import importlib
17 | import curses
18 | from lib import hud_utils
19 | from lib import hud_text
20 | from lib.common.dataship import dataship
21 |
22 |
23 | #############################################
24 | # Text mode Main loop
25 | def main_text_mode():
26 | hud_text.print_Clear()
27 | threadKey = threadReadKeyboard() # read keyboard input for text mode using curses
28 | threadKey.start()
29 | print(shared.Dataship.textMode)
30 | clearTimer = 0
31 | while not shared.Dataship.errorFoundNeedToExit and shared.Dataship.textMode:
32 | clearTimer += 1
33 | if(clearTimer>20):
34 | hud_text.print_Clear()
35 | clearTimer = 0
36 | if(shared.Dataship.errorFoundNeedToExit==False):
37 | shared.Inputs[0].printTextModeData(shared.Dataship)
38 | time.sleep(.05)
39 |
40 | #############################################
41 | ## Class: threadReadKeyboard (FOR TEXT MODE ONLY...)
42 | # thread for reading in data. used during text mode. curses module used for keyboard input.
43 | class threadReadKeyboard(threading.Thread):
44 | def __init__(self):
45 | threading.Thread.__init__(self)
46 | self.stdscr = curses.initscr()
47 | self.stdscr.keypad(1)
48 |
49 | def run(self):
50 | while not shared.Dataship.errorFoundNeedToExit and shared.Dataship.textMode:
51 | key = self.stdscr.getch()
52 | if key==ord('q'):
53 | curses.endwin()
54 | shared.Dataship.errorFoundNeedToExit = True
55 | if key==ord('p'):
56 | if(shared.Inputs[0].isPlaybackMode==True):
57 | if(shared.Inputs[0].isPaused==False):
58 | shared.Inputs[0].isPaused = True
59 | else:
60 | shared.Inputs[0].isPaused = False
61 | elif key==curses.KEY_RIGHT:
62 | shared.Inputs[0].fastForward(shared.Dataship,500)
63 | if len(shared.Inputs) > 1:
64 | shared.Inputs[1].fastForward(shared.Dataship,500)
65 | elif key==curses.KEY_LEFT:
66 | shared.Inputs[0].fastBackwards(shared.Dataship,500)
67 | if len(shared.Inputs) > 1:
68 | shared.Inputs[1].fastBackwards(shared.Dataship,500)
69 | elif key==27: # escape key.
70 | curses.endwin()
71 | shared.Dataship.textMode = False
72 | loadScreen(hud_utils.findScreen("current"))
73 | elif key==23 or key==ord('1'): #cntrl w
74 | try:
75 | shared.Inputs[0].startLog(shared.Dataship)
76 | if len(shared.Inputs) > 1:
77 | shared.Inputs[1].startLog(shared.Dataship)
78 | except:
79 | pass
80 | elif key==5 or key==ord('2'): #cnrtl e
81 | try:
82 | shared.Inputs[0].stopLog(shared.Dataship)
83 | if len(shared.Inputs) > 1:
84 | shared.Inputs[1].stopLog(shared.Dataship)
85 | except:
86 | pass
87 | else:
88 | try:
89 | retrn,returnMsg = shared.Inputs[0].textModeKeyInput(key,shared.Dataship)
90 | if retrn == 'quit':
91 | curses.endwin()
92 | shared.Dataship.errorFoundNeedToExit = True
93 | hud_text.print_Clear()
94 | print(returnMsg)
95 | except AttributeError:
96 | print(("Key: %d \r\n"%(key)))
97 |
98 |
99 | # vi: modeline tabstop=8 expandtab shiftwidth=4 softtabstop=4 syntax=python
100 |
--------------------------------------------------------------------------------
/lib/geomag/WMM.COF:
--------------------------------------------------------------------------------
1 | 2015.0 WMM-2015v2 09/18/2018
2 | 1 0 -29438.2 0.0 7.0 0.0
3 | 1 1 -1493.5 4796.3 9.0 -30.2
4 | 2 0 -2444.5 0.0 -11.0 0.0
5 | 2 1 3014.7 -2842.4 -6.2 -29.6
6 | 2 2 1679.0 -638.8 0.3 -17.3
7 | 3 0 1351.8 0.0 2.4 0.0
8 | 3 1 -2351.6 -113.7 -5.7 6.5
9 | 3 2 1223.6 246.5 2.0 -0.8
10 | 3 3 582.3 -537.4 -11.0 -2.0
11 | 4 0 907.5 0.0 -0.8 0.0
12 | 4 1 814.8 283.3 -0.9 -0.4
13 | 4 2 117.8 -188.6 -6.5 5.8
14 | 4 3 -335.6 180.7 5.2 3.8
15 | 4 4 69.7 -330.0 -4.0 -3.5
16 | 5 0 -232.9 0.0 -0.3 0.0
17 | 5 1 360.1 46.9 0.6 0.2
18 | 5 2 191.7 196.5 -0.8 2.3
19 | 5 3 -141.3 -119.9 0.1 -0.0
20 | 5 4 -157.2 16.0 1.2 3.3
21 | 5 5 7.7 100.6 1.4 -0.6
22 | 6 0 69.4 0.0 -0.8 0.0
23 | 6 1 67.7 -20.1 -0.5 0.3
24 | 6 2 72.3 32.8 -0.1 -1.5
25 | 6 3 -129.1 59.1 1.6 -1.2
26 | 6 4 -28.4 -67.1 -1.6 0.4
27 | 6 5 13.6 8.1 0.0 0.2
28 | 6 6 -70.3 61.9 1.2 1.3
29 | 7 0 81.7 0.0 -0.3 0.0
30 | 7 1 -75.9 -54.3 -0.2 0.6
31 | 7 2 -7.1 -19.5 -0.3 0.5
32 | 7 3 52.2 6.0 0.9 -0.8
33 | 7 4 15.0 24.5 0.1 -0.2
34 | 7 5 9.1 3.5 -0.6 -1.1
35 | 7 6 -3.0 -27.7 -0.9 0.1
36 | 7 7 5.9 -2.9 0.7 0.2
37 | 8 0 24.2 0.0 -0.1 0.0
38 | 8 1 8.9 10.1 0.2 -0.4
39 | 8 2 -16.9 -18.3 -0.2 0.6
40 | 8 3 -3.1 13.3 0.5 -0.1
41 | 8 4 -20.7 -14.5 -0.1 0.6
42 | 8 5 13.3 16.2 0.4 -0.2
43 | 8 6 11.6 6.0 0.4 -0.5
44 | 8 7 -16.3 -9.2 -0.1 0.5
45 | 8 8 -2.1 2.4 0.4 0.1
46 | 9 0 5.5 0.0 -0.1 0.0
47 | 9 1 8.8 -21.8 -0.1 -0.3
48 | 9 2 3.0 10.7 -0.0 0.1
49 | 9 3 -3.2 11.8 0.4 -0.4
50 | 9 4 0.6 -6.8 -0.4 0.3
51 | 9 5 -13.2 -6.9 0.0 0.1
52 | 9 6 -0.1 7.9 0.3 -0.0
53 | 9 7 8.7 1.0 0.0 -0.1
54 | 9 8 -9.1 -3.9 -0.0 0.5
55 | 9 9 -10.4 8.5 -0.3 0.2
56 | 10 0 -2.0 0.0 0.0 0.0
57 | 10 1 -6.1 3.3 -0.0 0.0
58 | 10 2 0.2 -0.4 -0.1 0.1
59 | 10 3 0.6 4.6 0.2 -0.2
60 | 10 4 -0.5 4.4 -0.1 0.1
61 | 10 5 1.8 -7.9 -0.2 -0.1
62 | 10 6 -0.7 -0.6 -0.0 0.1
63 | 10 7 2.2 -4.2 -0.1 -0.0
64 | 10 8 2.4 -2.9 -0.2 -0.1
65 | 10 9 -1.8 -1.1 -0.1 0.2
66 | 10 10 -3.6 -8.8 -0.0 -0.0
67 | 11 0 3.0 0.0 -0.0 0.0
68 | 11 1 -1.4 -0.0 0.0 0.0
69 | 11 2 -2.3 2.1 -0.0 0.1
70 | 11 3 2.1 -0.6 0.0 0.0
71 | 11 4 -0.8 -1.1 -0.0 0.1
72 | 11 5 0.6 0.7 -0.1 -0.0
73 | 11 6 -0.7 -0.2 0.0 -0.0
74 | 11 7 0.1 -2.1 -0.0 0.1
75 | 11 8 1.7 -1.5 -0.0 -0.0
76 | 11 9 -0.2 -2.6 -0.1 -0.1
77 | 11 10 0.4 -2.0 -0.0 -0.0
78 | 11 11 3.5 -2.3 -0.1 -0.1
79 | 12 0 -2.0 0.0 0.0 0.0
80 | 12 1 -0.1 -1.0 0.0 -0.0
81 | 12 2 0.5 0.3 -0.0 0.0
82 | 12 3 1.2 1.8 0.0 -0.1
83 | 12 4 -0.9 -2.2 -0.1 0.1
84 | 12 5 0.9 0.3 -0.0 -0.0
85 | 12 6 0.1 0.7 0.0 0.0
86 | 12 7 0.6 -0.1 -0.0 -0.0
87 | 12 8 -0.4 0.3 0.0 0.0
88 | 12 9 -0.5 0.2 -0.0 0.0
89 | 12 10 0.2 -0.9 -0.0 -0.0
90 | 12 11 -0.9 -0.2 -0.0 0.0
91 | 12 12 -0.0 0.8 -0.1 -0.1
92 | 999999999999999999999999999999999999999999999999
93 | 999999999999999999999999999999999999999999999999
94 |
--------------------------------------------------------------------------------
/lib/geomag/__init__.py:
--------------------------------------------------------------------------------
1 | """geomag package
2 | by Christopher Weiss cmweiss@gmail.com
3 |
4 | Adapted from the geomagc software and World Magnetic Model of the NOAA
5 | Satellite and Information Service, National Geophysical Data Center
6 | http://www.ngdc.noaa.gov/geomag/WMM/DoDWMM.shtml
7 |
8 | Suggestions for improvements are appreciated.
9 |
10 | USAGE:
11 | >>> import geomag
12 | >>> geomag.declination(80,0)
13 | -3.382344140520556
14 | """
15 |
16 | from . import geomag
17 |
18 | __singleton__ = geomag.GeoMag()
19 |
20 | def declination(*args, **kargs):
21 | """Calculate magnetic declination in degrees
22 | dlat = latitude in degrees
23 | dlon = longitude in degrees
24 | h = altitude in feet, default=0
25 | time = date for computing declination, default=today
26 | """
27 | mag = __singleton__.GeoMag(*args, **kargs)
28 | return mag.dec
29 |
30 | def mag_heading(hdg, *args, **kargs):
31 | """Calculates the magnetic heading from a true heading.
32 | hdg = true heading in degrees
33 | All other parameters are the same as declination.
34 | """
35 | dec = declination(*args, **kargs)
36 | return (hdg - dec + 360.0) % 360
37 |
--------------------------------------------------------------------------------
/lib/inputs/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/inputs/_example_data/MGL_Flight1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/MGL_Flight1.bin
--------------------------------------------------------------------------------
/lib/inputs/_example_data/MGL_G430_Data_3Feb19_v7_Horz_Vert_Nedl_come_to_center.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/MGL_G430_Data_3Feb19_v7_Horz_Vert_Nedl_come_to_center.bin
--------------------------------------------------------------------------------
/lib/inputs/_example_data/MGL_LOG092921_1500_1st_Flt.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/MGL_LOG092921_1500_1st_Flt.bin
--------------------------------------------------------------------------------
/lib/inputs/_example_data/MGL_V10.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/MGL_V10.bin
--------------------------------------------------------------------------------
/lib/inputs/_example_data/MGL_V11.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/MGL_V11.bin
--------------------------------------------------------------------------------
/lib/inputs/_example_data/MGL_V12.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/MGL_V12.bin
--------------------------------------------------------------------------------
/lib/inputs/_example_data/MGL_V2.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/MGL_V2.bin
--------------------------------------------------------------------------------
/lib/inputs/_example_data/MGL_V3.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/MGL_V3.bin
--------------------------------------------------------------------------------
/lib/inputs/_example_data/MGL_V4.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/MGL_V4.bin
--------------------------------------------------------------------------------
/lib/inputs/_example_data/MGL_V5.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/MGL_V5.bin
--------------------------------------------------------------------------------
/lib/inputs/_example_data/MGL_V6.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/MGL_V6.bin
--------------------------------------------------------------------------------
/lib/inputs/_example_data/MGL_V7.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/MGL_V7.bin
--------------------------------------------------------------------------------
/lib/inputs/_example_data/MGL_V8.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/MGL_V8.bin
--------------------------------------------------------------------------------
/lib/inputs/_example_data/MGL_V9.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/MGL_V9.bin
--------------------------------------------------------------------------------
/lib/inputs/_example_data/mgl_2.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/mgl_2.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/mgl_4.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/mgl_4.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/mgl_5.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/mgl_5.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/mgl_6.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/mgl_6.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/mgl_7.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/mgl_7.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/mgl_8.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/mgl_8.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/mgl_9.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/mgl_9.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/mgl_G430_v7_Horz_Vert_Nedl_come_to_center.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/mgl_G430_v7_Horz_Vert_Nedl_come_to_center.bin
--------------------------------------------------------------------------------
/lib/inputs/_example_data/mgl_chase_rv6_1.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/mgl_chase_rv6_1.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/mgl_chase_rv6_2.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/mgl_chase_rv6_2.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/mgl_chase_rv6_3.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/mgl_chase_rv6_3.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/mgl_data1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/mgl_data1.bin
--------------------------------------------------------------------------------
/lib/inputs/_example_data/stratux_1.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/stratux_1.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/stratux_2.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/stratux_2.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/stratux_4.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/stratux_4.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/stratux_5.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/stratux_5.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/stratux_54.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/stratux_54.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/stratux_57.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/stratux_57.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/stratux_6.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/stratux_6.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/stratux_7.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/stratux_7.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/stratux_8.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/stratux_8.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/stratux_9.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/stratux_9.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/stratux_chase_rv6_1.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/stratux_chase_rv6_1.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/stratux_chase_rv6_2.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/stratux_chase_rv6_2.dat
--------------------------------------------------------------------------------
/lib/inputs/_example_data/stratux_chase_rv6_3.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/inputs/_example_data/stratux_chase_rv6_3.dat
--------------------------------------------------------------------------------
/lib/inputs/_utils.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | ##Library of useful functions that can be used inside of input modules
4 | ##02/06/2019 Brian Chesteen
5 |
6 |
7 | import math
8 | from lib.geomag import declination
9 |
10 | #############################################
11 | ## Function: ias2tas By: Brian Chesteen
12 | ## Converts indicated airspeed to true airspeed based on
13 | ## outside air temp and pressure altitude.
14 | # inputs are in knots, cel, ft.
15 |
16 | def ias2tas(ias, oat, palt):
17 | tas = ias * (math.sqrt((273.0 + oat) / 288.0)) * ((1.0 - palt / 144000.0) ** -2.75)
18 |
19 | return tas
20 |
21 |
22 | def is_number(s):
23 | try:
24 | float(s)
25 | return True
26 | except ValueError:
27 | return False
28 |
29 | #############################################
30 | ## Function: geomag By: Brian Chesteen
31 | ## Called Module By: Christopher Weiss cmweiss@gmail.com
32 | ## Looks up magnetic declination dynamically from GPS Lat and Lon
33 | ## Uses coefficients from the World Magnetic Model of the NOAA
34 | ## Satellite and Information Service, National Geophysical Data Center
35 | ## http://www.ngdc.noaa.gov/geomag/WMM/DoDWMM.shtml
36 |
37 | def calc_geomag(LatHemi, LatDeg, LatMin, LonHemi, LonDeg, LonMin):
38 | if LatHemi == "N":
39 | GeoMagLat = LatDeg + (LatMin / 60)
40 | else:
41 | GeoMagLat = ((LatDeg + (LatMin / 60)) * -1)
42 | if LonHemi == "W":
43 | GeoMagLon = ((LonDeg + (LonMin/60)) * -1)
44 | else:
45 | GeoMagLon = LonDeg + (LonMin/60)
46 | mag_decl = declination(GeoMagLat, GeoMagLon)
47 |
48 | return mag_decl, GeoMagLat, GeoMagLon
49 |
50 | #############################################
51 | ## Function: gndspeed By: Brian Chesteen
52 | ## Calculates GPS ground speed in knots from GPS EW NS velocities in meters/sec.
53 |
54 | def gndspeed(EWVelmag, NSVelmag):
55 | gndspeed = (math.sqrt(((int(EWVelmag) * 0.1)**2) + ((int(NSVelmag) * 0.1)**2))) * 1.94384
56 |
57 | return gndspeed
58 |
59 | #############################################
60 | ## Function: gndtrack By: Brian Chesteen
61 | ## Calculates GPS ground track in knots from GPS EW NS quadrant direction and
62 | ## velocities in meters/sec.
63 |
64 | def gndtrack(EWVelDir, EWVelmag, NSVelDir, NSVelmag):
65 | if EWVelDir == "W":
66 | EWVelmag = int(EWVelmag) * -0.1
67 | else:
68 | EWVelmag = int(EWVelmag) * 0.1
69 | if NSVelDir == "S":
70 | NSVelmag = int(NSVelmag) * -0.1
71 | else:
72 | NSVelmag = int(NSVelmag) * 0.1
73 |
74 | gndtrack = (math.degrees(math.atan2(EWVelmag, NSVelmag))) % 360
75 |
76 | return gndtrack
77 |
78 | #############################################
79 | ## Function: windSpdDir By: Brian Chesteen
80 | ## Calculates wind speed and direction
81 | # tas and gndspeed in knots
82 | def windSpdDir(tas, gndspeed, gndtrack, mag_head, mag_decl):
83 | if tas > 30 and gndspeed > 30:
84 | crs = math.radians(gndtrack) #convert degrees to radians
85 | head = math.radians(mag_head + mag_decl) #convert degrees to radians
86 | wind_speed = round(math.sqrt(math.pow(tas - gndspeed, 2) + 4 * tas * gndspeed * math.pow(math.sin((head - crs) / 2), 2)), 1)
87 | wind_dir = crs + math.atan2(tas * math.sin(head-crs), tas * math.cos(head-crs) - gndspeed)
88 | if wind_dir < 0:
89 | wind_dir = wind_dir + 2 * math.pi
90 | if wind_dir > 2 * math.pi:
91 | wind_dir = wind_dir - 2 * math.pi
92 | wind_dir = round(math.degrees(wind_dir), 1) #convert radians to degrees
93 | norm_wind_dir = round((mag_head - wind_dir + mag_decl) % 360, 1) #normalize the wind direction to the airplane heading
94 |
95 | else:
96 | wind_speed = None
97 | wind_dir = None
98 | norm_wind_dir = None
99 |
100 | return (wind_speed, wind_dir, norm_wind_dir)
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/lib/inputs/adc_ads1115.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # ads1115 input source
4 |
5 |
6 | from ._input import Input
7 | from lib import hud_utils
8 | from . import _utils
9 | import struct
10 | import time
11 | import Adafruit_ADS1x15
12 | import statistics
13 | import traceback
14 | from lib.common.dataship.dataship import Dataship
15 | from lib.common.dataship.dataship_analog import AnalogData
16 | from lib.common.dataship.dataship_nav import NavData
17 |
18 | class adc_ads1115(Input):
19 | def __init__(self):
20 | self.name = "ads1115"
21 | self.version = 1.0
22 | self.inputtype = "adc"
23 | self.values = []
24 | self.ApplySmoothing = 1
25 | self.SmoothingAVGMaxCount = 10
26 | self.smoothingA = []
27 | self.smoothingB = []
28 | self.analogData = AnalogData()
29 |
30 | def initInput(self,num,dataship: Dataship):
31 | Input.initInput( self,num, dataship ) # call parent init Input.
32 | if(self.PlayFile!=None):
33 | self.isPlaybackMode = True
34 | else:
35 | self.isPlaybackMode = False
36 |
37 | # setup comm i2c to chipset.
38 | self.adc = Adafruit_ADS1x15.ADS1115()
39 | # Choose a gain of 1 for reading voltages from 0 to 4.09V.
40 | # Or pick a different gain to change the range of voltages that are read:
41 | # - 2/3 = +/-6.144V
42 | # - 1 = +/-4.096V
43 | # - 2 = +/-2.048V
44 | # - 4 = +/-1.024V
45 | # - 8 = +/-0.512V
46 | # - 16 = +/-0.256V
47 | # See table 3 in the ADS1015/ADS1115 datasheet for more info on gain.
48 | self.GAIN = 2/3
49 | dataship.analog.Name = "ads1115"
50 | self.Amplify = 6.144/32767
51 | self.values = [0,0,0,0,0,0,0,0]
52 |
53 | # create analog data object.
54 | self.analogData = AnalogData()
55 | self.analogData.name = self.name
56 | self.index = len(dataship.analogData)
57 | self.analogData.id = self.name + "_" + str(self.index)
58 | dataship.analogData.append(self.analogData)
59 |
60 | # create nav data object.
61 | self.navData = NavData()
62 | self.navData.name = self.name
63 | self.index = len(dataship.navData)
64 | self.navData.id = self.name + "_" + str(self.index)
65 | dataship.navData.append(self.navData)
66 |
67 | def closeInput(self,dataship: Dataship):
68 | print("ads1115 close")
69 |
70 |
71 | #############################################
72 | ## Function: readMessage
73 | def readMessage(self, dataship: Dataship):
74 | if self.shouldExit == True: dataship.errorFoundNeedToExit = True
75 | if dataship.errorFoundNeedToExit: return dataship
76 | if self.skipReadInput == True: return dataship
77 |
78 | try:
79 | time.sleep(0.025) # delay because of i2c read.
80 | self.values[1] = self.adc.read_adc_difference(0, gain=self.GAIN) * self.Amplify
81 | time.sleep(0.025)
82 | self.values[0] = self.adc.read_adc_difference(3, gain=self.GAIN) * self.Amplify
83 |
84 | # apply smoothing avg of adc values?
85 | if(self.ApplySmoothing):
86 | self.smoothingA.append(self.values[0])
87 | if(len(self.smoothingA)>self.SmoothingAVGMaxCount): self.smoothingA.pop(0)
88 | self.analogData.Data[0] = statistics.mean(self.smoothingA)
89 |
90 | self.smoothingB.append(self.values[1])
91 | if(len(self.smoothingB)>self.SmoothingAVGMaxCount): self.smoothingB.pop(0)
92 | self.analogData.Data[1] = statistics.mean(self.smoothingB)
93 | else:
94 | #else don't apply smoothing.
95 | self.analogData.Data = self.values
96 |
97 | # TODO: have config file define what this analog input is for.
98 |
99 | # if analog input is for nav needles.. .then.
100 | # limit the output voltages to be within +/- 0.25V
101 | # format value to +/- 4095 for needle left/right up/down.
102 | self.navData.GSDev = round (16380 * (max(min(self.analogData.Data[0], 0.25), -0.25)))
103 | self.navData.ILSDev = round (16380 * (max(min(self.analogData.Data[1], 0.25), -0.25)))
104 |
105 | except Exception as e:
106 | dataship.errorFoundNeedToExit = True
107 | print(e)
108 | print(traceback.format_exc())
109 | return dataship
110 |
111 |
112 |
113 |
114 | # vi: modeline tabstop=8 expandtab shiftwidth=4 softtabstop=4 syntax=python
115 |
--------------------------------------------------------------------------------
/lib/inputs/serial_flyonspeed2efisdata.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Serial input source
4 | # Fly on speed gen 2 box serial csv format
5 | # 2/2/2019 Christopher Jones
6 |
7 | from ._input import Input
8 | from lib import hud_utils
9 | import serial
10 | import struct
11 | from lib import hud_text
12 | import time
13 |
14 | class serial_flyonspeed2efisdata(Input):
15 | def __init__(self):
16 | self.name = "FlyOnSpeed2EfisOut"
17 | self.version = 1.0
18 | self.inputtype = "serial"
19 |
20 | def initInput(self,num,aircraft):
21 | Input.initInput( self,num, aircraft ) # call parent init Input.
22 |
23 | if(self.PlayFile!=None and self.PlayFile!=False):
24 | if self.PlayFile==True:
25 | defaultTo = "flyonspeed2efisdata_data1.csv"
26 | self.PlayFile = hud_utils.readConfig(self.name, "playback_file", defaultTo)
27 | self.ser,self.input_logFileName = Input.openLogFile(self,self.PlayFile,"r")
28 | self.isPlaybackMode = True
29 | else:
30 | self.efis_data_port = hud_utils.readConfig(self.name, "port", "/dev/ttyS0")
31 | self.efis_data_baudrate = hud_utils.readConfigInt( self.name, "baudrate", 115200 )
32 |
33 | # open serial connection.
34 | self.ser = serial.Serial(
35 | port=self.efis_data_port,
36 | baudrate=self.efis_data_baudrate,
37 | parity=serial.PARITY_NONE,
38 | stopbits=serial.STOPBITS_ONE,
39 | bytesize=serial.EIGHTBITS,
40 | timeout=1,
41 | )
42 |
43 | # close this data input
44 | def closeInput(self,aircraft):
45 | if self.isPlaybackMode:
46 | self.ser.close()
47 | else:
48 | self.ser.close()
49 |
50 | #############################################
51 | ## Function: readMessage
52 | def readMessage(self, aircraft):
53 | if aircraft.errorFoundNeedToExit:
54 | return aircraft;
55 | try:
56 | x = 0
57 | msg = ""
58 | while x != 10: # wait for CR (10) because this is the end of line of csv file.
59 | t = self.ser.read(1)
60 | if len(t) != 0:
61 | x = ord(t)
62 | msg += t
63 | #print(t)
64 | else:
65 | if self.isPlaybackMode: # if no bytes read and in playback mode. then reset the file pointer to the start of the file.
66 | self.ser.seek(0)
67 | return aircraft
68 | msgArray = msg.split(",") # split up csv format
69 | if len(msgArray) == 23: # should be 23 different values. if not then we didn't get a good line.
70 |
71 | aircraft.msg_last = msg
72 | # format for csv file from gen 2 box is:
73 | # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
74 | # timeStamp,Pfwd,PfwdSmoothed,P45,P45Smoothed,PStatic,Palt,IAS,AngleofAttack,flapsPos,Ax,Ay,Az,efisIAS,efisPitch,efisRoll,efisLateralG,efisVerticalG,efisPercentLift,efisPalt,efisVSI,efisAge,efisTime
75 |
76 | if True:
77 |
78 | aircraft.ias = float(msgArray[13])
79 | aircraft.pitch = float(msgArray[14])
80 | aircraft.roll = float(msgArray[15])
81 |
82 | aircraft.aoa = float(msgArray[18]) # aoa percent of lift.
83 |
84 | aircraft.BALT = int(msgArray[19])
85 |
86 | aircraft.vsi = int(msgArray[20])
87 | aircraft.msg_count += 1
88 |
89 | if self.isPlaybackMode: #if playback mode then add a delay. Else reading a file is way to fast.
90 | time.sleep(.05)
91 | else:
92 | self.ser.flushInput() # flush the serial after every message else we see delays
93 | return aircraft
94 | else:
95 | aircraft.msg_unknown += 1 # unknown message found.
96 |
97 | else:
98 | aircraft.msg_bad += 1 # count this as a bad message
99 | if self.isPlaybackMode: #if playback mode then add a delay. Else reading a file is way to fast.
100 | time.sleep(.01)
101 | else:
102 | self.ser.flushInput() # flush the serial after every message else we see delays
103 | return aircraft
104 | except ValueError as ex:
105 | print("flyonspeed2efisdata data conversion error")
106 | aircraft.errorFoundNeedToExit = True
107 | except serial.serialutil.SerialException:
108 | print("flyonspeed2efisdata serial exception")
109 | aircraft.errorFoundNeedToExit = True
110 | return aircraft
111 |
112 |
113 |
114 | #############################################
115 | ## Function: printTextModeData
116 | def printTextModeData(self, aircraft):
117 | hud_text.print_header("Decoded data from Input Module: %s"%(self.name))
118 | hud_text.print_object(aircraft)
119 | hud_text.print_DoneWithPage()
120 |
121 | # vi: modeline tabstop=8 expandtab shiftwidth=4 softtabstop=4 syntax=python
122 |
--------------------------------------------------------------------------------
/lib/inputs/serial_logger.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Serial input source
4 | # Generic serial logger
5 | # 10/14/2021 Topher
6 | # 1/3/2025 Added dataship refacor
7 |
8 | from ._input import Input
9 | from lib import hud_utils
10 | from . import _utils
11 | import serial
12 | import struct
13 | from lib import hud_text
14 | import binascii
15 | import time
16 | import datetime
17 | from ..common.dataship.dataship import Dataship
18 | from ..common.dataship.dataship_imu import IMUData
19 | from ..common.dataship.dataship_gps import GPSData
20 | from ..common.dataship.dataship_air import AirData
21 | from ..common.dataship.dataship_targets import TargetData, Target
22 | from ._input import Input
23 | from ..common import shared
24 | from . import _input_file_utils
25 |
26 |
27 | class serial_logger(Input):
28 | def __init__(self):
29 | self.name = "serial_logger"
30 | self.version = 1.0
31 | self.inputtype = "serial"
32 |
33 | def initInput(self,num,dataship: Dataship):
34 | Input.initInput( self,num, dataship ) # call parent init Input.
35 | if(self.PlayFile!=None):
36 | print("serial_logger can not play back files. Only used to record data.")
37 | dataship.errorFoundNeedToExit = True
38 | else:
39 | self.efis_data_port = hud_utils.readConfig(self.name, "port", "/dev/ttyS0")
40 | self.efis_data_baudrate = hud_utils.readConfigInt(
41 | self.name, "baudrate", 115200
42 | )
43 | self.read_in_size = hud_utils.readConfigInt( self.name, "read_in_size", 100)
44 |
45 | # open serial connection.
46 | self.ser = serial.Serial(
47 | port=self.efis_data_port,
48 | baudrate=self.efis_data_baudrate,
49 | parity=serial.PARITY_NONE,
50 | stopbits=serial.STOPBITS_ONE,
51 | bytesize=serial.EIGHTBITS,
52 | timeout=1,
53 | )
54 |
55 | self.textMode_whatToShow = 1 # default to only showing basic air info.
56 |
57 | def closeInput(self,dataship: Dataship):
58 | if self.isPlaybackMode == True:
59 | dataship.errorFoundNeedToExit = True
60 | else:
61 | self.ser.close()
62 |
63 | #############################################
64 | ## Function: readMessage
65 | def readMessage(self, dataship: Dataship):
66 | if self.shouldExit == True: dataship.errorFoundNeedToExit = True
67 | if dataship.errorFoundNeedToExit: return dataship
68 | if self.skipReadInput == True: return dataship
69 | try:
70 | Message = self.ser.read(self.read_in_size)
71 |
72 | if(len(Message)==0):
73 | dataship.msg_last = "No serial data recieved"
74 | return dataship
75 |
76 | #dataship.msg_count += 1
77 | #dataship.msg_last = binascii.hexlify(Message) # save last message.
78 |
79 | # make the aircraft look like it's doing something.
80 | #if(dataship.roll>180): dataship.roll = -180
81 | #dataship.roll = dataship.roll + .1 #
82 |
83 | #dataship.sys_time_string = datetime.datetime.now().time()
84 |
85 | if self.output_logFile != None:
86 | Input.addToLog(self,self.output_logFile,Message)
87 |
88 | except serial.serialutil.SerialException:
89 | print("serial_logger exception")
90 | dataship.errorFoundNeedToExit = True
91 | return dataship
92 |
93 |
94 |
95 |
96 | # vi: modeline tabstop=8 expandtab shiftwidth=4 softtabstop=4 syntax=python
97 |
--------------------------------------------------------------------------------
/lib/inputs/serial_onspeedaoa.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import serial
4 | import time
5 | import struct
6 |
7 | class SerialParser:
8 | def __init__(self, port='/dev/ttyS0', baudrate=115200):
9 | self.ser = serial.Serial(
10 | port=port,
11 | baudrate=baudrate,
12 | parity=serial.PARITY_NONE,
13 | stopbits=serial.STOPBITS_ONE,
14 | bytesize=serial.EIGHTBITS,
15 | timeout=1,
16 | )
17 | self.serial_buffer = ''
18 | self.serial_millis = time.time()
19 | self.Pitch = 0.0
20 | self.Roll = 0.0
21 | self.IAS = 0.0
22 | self.Palt = 0.0
23 | self.TurnRate = 0.0
24 | self.LateralG = 0.0
25 | self.VerticalG = 0.0
26 | self.PercentLift = 0
27 | self.AOA = 0.0
28 | self.iVSI = 0.0
29 | self.OAT = 0
30 | self.FlightPath = 0.0
31 | self.FlapPos = 0
32 | self.OnSpeedStallWarnAOA = 0.0
33 | self.OnSpeedSlowAOA = 0.0
34 | self.OnSpeedFastAOA = 0.0
35 | self.OnSpeedTonesOnAOA = 0.0
36 | self.gOnsetRate = 0.0
37 | self.SpinRecoveryCue = 0
38 | self.DataMark = 0
39 |
40 | def read_serial(self):
41 | while True:
42 | if self.ser.in_waiting > 0:
43 | inChar = self.ser.read(1).decode('utf-8')
44 | if inChar == '#':
45 | self.serial_buffer = inChar
46 | continue
47 |
48 | if len(self.serial_buffer) > 80:
49 | self.serial_buffer = ''
50 | print("Serial data buffer overflow")
51 | continue
52 |
53 | if self.serial_buffer:
54 | self.serial_buffer += inChar
55 |
56 | if len(self.serial_buffer) == 80 and self.serial_buffer[0] == '#' and self.serial_buffer[1] == '1' and inChar == '\n':
57 | # Calculate CRC
58 | calcCRC = sum([ord(c) for c in self.serial_buffer[:76]]) & 0xFF
59 | receivedCRC = int(self.serial_buffer[76:78], 16)
60 |
61 | if calcCRC == receivedCRC:
62 | self.parse_data()
63 | self.serial_millis = time.time()
64 | else:
65 | print("ONSPEED CRC Failed")
66 |
67 | def parse_data(self):
68 | self.Pitch = float(self.serial_buffer[2:6]) / 10
69 | self.Roll = float(self.serial_buffer[6:11]) / 10
70 | self.IAS = float(self.serial_buffer[11:15]) / 10
71 | self.Palt = float(self.serial_buffer[15:21])
72 | self.TurnRate = float(self.serial_buffer[21:26]) / 10
73 | self.LateralG = float(self.serial_buffer[26:29]) / 100
74 | self.VerticalG = float(self.serial_buffer[29:32]) / 10
75 | self.PercentLift = int(self.serial_buffer[32:34])
76 | self.AOA = float(self.serial_buffer[34:38]) / 10
77 | self.iVSI = float(self.serial_buffer[38:42]) * 10
78 | self.OAT = int(self.serial_buffer[42:45])
79 | self.FlightPath = float(self.serial_buffer[45:49]) / 10
80 | self.FlapPos = int(self.serial_buffer[49:52])
81 | self.OnSpeedStallWarnAOA = float(self.serial_buffer[52:56]) / 10
82 | self.OnSpeedSlowAOA = float(self.serial_buffer[56:60]) / 10
83 | self.OnSpeedFastAOA = float(self.serial_buffer[60:64]) / 10
84 | self.OnSpeedTonesOnAOA = float(self.serial_buffer[64:68]) / 10
85 | self.gOnsetRate = float(self.serial_buffer[68:72]) / 100
86 | self.SpinRecoveryCue = int(self.serial_buffer[72:74])
87 | self.DataMark = int(self.serial_buffer[74:76])
88 |
89 | self.serial_buffer = ""
90 | self.serial_process()
91 |
92 | def serial_process(self):
93 | if self.AOA == -100:
94 | self.AOA = 0.0
95 |
96 | # Add smoothing and processing logic as needed
97 | # Example:
98 | # self.SmoothedAOA = self.SmoothedAOA * aoaSmoothingAlpha + (1 - aoaSmoothingAlpha) * self.AOA
99 |
100 | # Print or process the parsed values
101 | print(f"ONSPEED data: IAS {self.IAS:.2f}, Pitch {self.Pitch:.1f}, Roll {self.Roll:.1f}, LateralG {self.LateralG:.2f}, VerticalG {self.VerticalG:.2f}, Palt {self.Palt:.1f}, iVSI {self.iVSI:.1f}, AOA: {self.AOA:.1f}")
102 |
103 | if __name__ == "__main__":
104 | parser = SerialParser()
105 | parser.read_serial()
106 |
--------------------------------------------------------------------------------
/lib/main_kivy.py:
--------------------------------------------------------------------------------
1 | from kivy.app import App
2 | from kivy.uix.widget import Widget
3 | from kivy.uix.boxlayout import BoxLayout
4 | from kivy.uix.label import Label
5 | from kivy.core.window import Window
6 | from kivy.clock import Clock
7 | from kivy.graphics.transformation import Matrix
8 | from modules_kivy.horizon_3d.horizon_3d import Horizon3D
9 |
10 | class TronViewApp(App):
11 | def __init__(self, **kwargs):
12 | super().__init__(**kwargs)
13 | self.modules = {}
14 | self.show_fps = False
15 | self.fps_counter = 0
16 | self.fps_time = 0
17 | self.current_fps = 0
18 | self._keyboard = None
19 |
20 | def build(self):
21 | # Create the main layout
22 | self.main_layout = BoxLayout(orientation='vertical')
23 |
24 | # Create FPS label
25 | self.fps_label = Label(
26 | text='FPS: 0',
27 | size_hint=(None, None),
28 | pos_hint={'right': 0.98, 'top': 0.98}, # Slightly inset from corners
29 | size=(100, 30),
30 | color=(1, 1, 0, 1) # Yellow text
31 | )
32 | self.fps_label.opacity = 0 # Initially hidden
33 |
34 | # Create and initialize modules
35 | self.init_modules()
36 |
37 | # Add FPS label directly to window
38 | Window.add_widget(self.fps_label)
39 |
40 | # Set up keyboard after window is created
41 | Window.bind(on_key_down=self._on_key_down)
42 |
43 | # Set up the update loop
44 | Clock.schedule_interval(self.update, 1.0 / 60.0)
45 |
46 | # Bind to window resize
47 | Window.bind(on_resize=self.on_window_resize)
48 |
49 | # Initial perspective setup
50 | self.setup_perspective()
51 |
52 | return self.main_layout
53 |
54 | def _on_key_down(self, instance, keycode, text, scancode, modifiers):
55 | """Handle keyboard input"""
56 | if keycode == 113: # 'q' key
57 | App.get_running_app().stop()
58 | return True
59 | elif keycode == 102: # 'f' key
60 | self.toggle_fps()
61 | return True
62 | return False
63 |
64 | def toggle_fps(self):
65 | """Toggle FPS display"""
66 | self.show_fps = not self.show_fps
67 | self.fps_label.opacity = 1 if self.show_fps else 0
68 |
69 | def update_fps(self, dt):
70 | """Update FPS counter"""
71 | self.fps_counter += 1
72 | self.fps_time += dt
73 |
74 | if self.fps_time >= 1.0: # Update every second
75 | self.current_fps = self.fps_counter
76 | self.fps_label.text = f'FPS: {self.current_fps}'
77 | self.fps_counter = 0
78 | self.fps_time = 0
79 |
80 | def init_modules(self):
81 | """Initialize all visualization modules"""
82 | # Create the horizon module
83 | horizon = Horizon3D(size_hint=(1, 1))
84 | self.modules['horizon'] = horizon
85 | self.main_layout.add_widget(horizon)
86 |
87 | def setup_perspective(self, *args):
88 | """Set up the perspective projection matrix"""
89 | aspect = Window.width / float(Window.height)
90 | fovy = 60.0 # Field of view in degrees
91 | near = 0.1 # Near clipping plane
92 | far = 100.0 # Far clipping plane
93 |
94 | # Create perspective projection matrix
95 | proj = Matrix()
96 | proj.perspective(fovy, aspect, near, far)
97 |
98 | # Apply to all modules that have a canvas
99 | for module in self.modules.values():
100 | if hasattr(module, 'canvas'):
101 | module.canvas['projection_mat'] = proj
102 |
103 | def on_window_resize(self, instance, width, height):
104 | """Handle window resize"""
105 | self.setup_perspective()
106 |
107 | def update(self, dt):
108 | """Main update loop"""
109 | # Update FPS if enabled
110 | if self.show_fps:
111 | self.update_fps(dt)
112 |
113 | # Update all modules
114 | for module in self.modules.values():
115 | if hasattr(module, 'update'):
116 | module.update(dt)
117 |
118 | def on_stop(self):
119 | """Clean up when the application stops"""
120 | Window.remove_widget(self.fps_label)
121 | Window.unbind(on_key_down=self._on_key_down)
122 |
123 | def main():
124 | # Set window properties
125 | Window.size = (1024, 768)
126 | Window.title = "TronView Kivy"
127 |
128 | # Create and run the application
129 | app = TronViewApp()
130 | app.run()
131 |
132 | if __name__ == '__main__':
133 | main()
134 |
--------------------------------------------------------------------------------
/lib/modules/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/modules/efis/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/modules/efis/artificalhorz/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/modules/efis/artificalhorz/.DS_Store
--------------------------------------------------------------------------------
/lib/modules/efis/artificalhorz/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/modules/efis/artificalhorz/attitude-indicator-1280.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/modules/efis/artificalhorz/attitude-indicator-1280.png
--------------------------------------------------------------------------------
/lib/modules/efis/artificalhorz/horiz.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/modules/efis/artificalhorz/horiz.bmp
--------------------------------------------------------------------------------
/lib/modules/efis/artificalhorz/horiz.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/modules/efis/artificalhorz/horiz.png
--------------------------------------------------------------------------------
/lib/modules/efis/artificalhorz/horiz_square.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/modules/efis/artificalhorz/horiz_square.bmp
--------------------------------------------------------------------------------
/lib/modules/efis/artificalhorz/lowres.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/modules/efis/artificalhorz/lowres.png
--------------------------------------------------------------------------------
/lib/modules/general/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/modules/gui/menu/_menu.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | #################################################
4 | # Module: Menu
5 | # Topher 2021.
6 |
7 | from lib.modules._module import Module
8 | from lib import hud_graphics
9 | from lib import hud_utils
10 | from lib import smartdisplay
11 | from lib.common.dataship import dataship
12 | import pygame
13 | import math
14 | #import pygame_menu
15 | from typing import Tuple, Any
16 |
17 | class menu(Module):
18 | # called only when object is first created.
19 | def __init__(self):
20 | Module.__init__(self)
21 | self.name = "Menu" # set name
22 |
23 | # called once for setup
24 | def initMod(self, pygamescreen, width, height,title=""):
25 | Module.initMod(
26 | self, pygamescreen, width, height
27 | ) # call parent init screen.
28 | print(("Init Mod: %s %dx%d"%(self.name,self.width,self.height)))
29 |
30 | self.menu = pygame_menu.Menu(title, self.width, self.height,
31 | theme=pygame_menu.themes.THEME_BLUE)
32 |
33 | #self.menu.add.text_input('Name :', default='John Doe')
34 | #self.menu.add.selector('Difficulty :', [('Hard', 1), ('Easy', 2)], onchange=self.set_difficulty)
35 | self.menu.add.button('Back', self.stop)
36 | self.menu.add.button('Quit', pygame_menu.events.EXIT)
37 |
38 |
39 | def set_difficulty(self, selected: Tuple, value: Any) -> None:
40 | print('Set difficulty to {} ({})'.format(selected[0], value))
41 |
42 | def stop(self):
43 | self.menu.disable()
44 |
45 | # called every redraw for the mod
46 | def draw(self, aircraft, smartdisplay,pos):
47 |
48 | print("draw menu")
49 |
50 | self.menu.enable()
51 | self.menu.mainloop(smartdisplay.pygamescreen)
52 |
53 | # called before screen draw. To clear the screen to your favorite color.
54 | def clear(self):
55 | pass
56 |
57 | # handle key events
58 | def processEvent(self, event):
59 | pass
60 |
61 |
62 | # vi: modeline tabstop=8 expandtab shiftwidth=4 softtabstop=4 syntax=python
63 |
--------------------------------------------------------------------------------
/lib/modules/hud/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/modules/hud/cdi/cdi.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | #################################################
4 | # Module: CDI for Localizer Glideslope
5 | # Supported by Topher 2021.
6 | # Created by Cecil Jones
7 |
8 | from lib.modules._module import Module
9 | from lib import hud_graphics
10 | from lib import hud_utils
11 | from lib import smartdisplay
12 | from lib.common.dataship.dataship import Dataship
13 | from lib.common.dataship.dataship_nav import NavData
14 | from lib.common import shared
15 | import pygame
16 | import math
17 |
18 |
19 | class cdi(Module):
20 | # called only when object is first created.
21 | def __init__(self):
22 | Module.__init__(self)
23 | self.name = "HUD CDI" # set name
24 | self.navData = NavData()
25 |
26 | # called once for setup
27 | def initMod(self, pygamescreen, width = None, height = None):
28 | if width is None:
29 | width = 200 # default width
30 | if height is None:
31 | height = 200 # default height
32 | Module.initMod(
33 | self, pygamescreen, width, height
34 | ) # call parent init screen.
35 | print(("Init Mod: %s %dx%d"%(self.name,self.width,self.height)))
36 |
37 | # fonts
38 | self.font = pygame.font.SysFont(
39 | None, int(self.height / 20)
40 | )
41 | self.surface = pygame.Surface((self.width, self.height), pygame.SRCALPHA)
42 |
43 | self.MainColor = (255, 255, 255) # CDI Needles color = White
44 |
45 | self.yCenter = self.height / 2
46 | self.xCenter = self.width / 2
47 |
48 | self.showLDMax = True
49 | self.yLDMaxDots = self.height - (self.height/8)
50 | self.xLDMaxDotsFromCenter = self.width/2
51 |
52 | self.cdi_color = (255, 255, 255) # start with white.
53 | # pull water line offset from HUD config area.
54 | self.y_offset = hud_utils.readConfigInt("HUD", "Horizon_Offset", 0) # Horizon/Waterline Pixel Offset from HUD Center Neg Numb moves Up, Default=0
55 |
56 | self.navData = NavData()
57 | if len(shared.Dataship.navData) > 0:
58 | self.navData = shared.Dataship.navData[0]
59 |
60 |
61 | # called every redraw for the mod
62 | def draw(self, dataship:Dataship, smartdisplay, pos):
63 |
64 | self.surface.fill((0, 0, 0, 0)) # clear surface
65 |
66 | x,y = pos
67 |
68 | new_y_center = self.yCenter + self.y_offset
69 |
70 | if self.navData.HSISource == 1:
71 | pygame.draw.line(
72 | self.surface,
73 | self.cdi_color,
74 | (self.xCenter - (self.navData.ILSDev / 60), new_y_center - 67),
75 | (self.xCenter - (self.navData.ILSDev / 60), new_y_center - 8),
76 | 4,
77 | )
78 | pygame.draw.line(
79 | self.surface,
80 | self.cdi_color,
81 | (self.xCenter - (self.navData.ILSDev / 60), new_y_center + 8),
82 | (self.xCenter - (self.navData.ILSDev / 60), new_y_center + 67),
83 | 4,
84 | )
85 |
86 | if self.navData.VNAVSource == 1:
87 | pygame.draw.line(
88 | self.surface,
89 | self.cdi_color,
90 | (self.xCenter-70, (self.navData.GSDev / 60) + new_y_center),
91 | (self.xCenter-8, (self.navData.GSDev / 60) + new_y_center),
92 | 4,
93 | )
94 | pygame.draw.line(
95 | self.surface,
96 | self.cdi_color,
97 | (self.xCenter+8, (self.navData.GSDev / 60) + new_y_center),
98 | (self.xCenter+70, (self.navData.GSDev / 60) + new_y_center),
99 | 4,
100 | )
101 |
102 | # Draw the surface at the given position
103 | self.pygamescreen.blit(self.surface, pos)
104 |
105 | # cycle through NAV sources
106 | def cycleNavSource(self,dataship):
107 | if self.navData.HSISource == 0 and self.navData.VNAVSource == 0:
108 | self.navData.HSISource = 1
109 | self.navData.SourceDesc = "Localizer"
110 | elif self.navData.HSISource == 1 and self.navData.VNAVSource == 0:
111 | self.navData.VNAVSource = 1
112 | self.navData.SourceDesc = "ILS"
113 | else:
114 | self.navData.HSISource = 0
115 | self.navData.VNAVSource = 0
116 | self.navData.SourceDesc = ""
117 |
118 |
119 | # called before screen draw. To clear the screen to your favorite color.
120 | def clear(self):
121 | #self.ahrs_bg.fill((0, 0, 0)) # clear screen
122 | print("clear")
123 |
124 | # handle key events
125 | def processEvent(self, event):
126 | print("processEvent")
127 |
128 | def get_module_options(self):
129 | return {
130 | "cdi_color": {
131 | "type": "color",
132 | "default": self.cdi_color,
133 | "label": "CDI Color",
134 | "description": "Color of the CDI needles.",
135 | }
136 | }
137 |
138 |
139 | # vi: modeline tabstop=8 expandtab shiftwidth=4 softtabstop=4 syntax=python
140 |
--------------------------------------------------------------------------------
/lib/modules/hud/hsi/tick_m.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/modules/hud/hsi/tick_m.jpg
--------------------------------------------------------------------------------
/lib/modules/hud/hsi/tick_m.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/modules/hud/hsi/tick_m.png
--------------------------------------------------------------------------------
/lib/modules/hud/rollindicator/tick_w.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/modules/hud/rollindicator/tick_w.bmp
--------------------------------------------------------------------------------
/lib/modules/hud/rollindicator/tick_w.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/modules/hud/rollindicator/tick_w.png
--------------------------------------------------------------------------------
/lib/modules/hud/slipskid/slipskid.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | #################################################
4 | # Module: SlipSkid
5 | # Topher 2021.
6 | # Adapted from F18 HUD Screen code by Brian Chesteen.
7 | # reads data in from aircraft object slip data. -100 to 100. Postive is to the left.
8 |
9 | from lib.modules._module import Module
10 | from lib import hud_graphics
11 | from lib import hud_utils
12 | from lib import smartdisplay
13 | from lib.common.dataship.dataship import Dataship
14 | from lib.common.dataship.dataship_imu import IMUData
15 | from lib.common.dataship.dataship_air import AirData
16 | from lib.common import shared
17 | import pygame
18 | import math
19 |
20 |
21 | class slipskid(Module):
22 | # called only when object is first created.
23 | def __init__(self):
24 | Module.__init__(self)
25 | self.name = "Slip Skid" # set name
26 |
27 | self.x_offset = 0
28 |
29 | self.imuData = IMUData()
30 | self.airData = AirData()
31 |
32 | # called once for setup
33 | def initMod(self, pygamescreen, width=None, height=None):
34 | if width is None:
35 | width = 400 # default width
36 | if height is None:
37 | height = 80 # default height
38 | Module.initMod(
39 | self, pygamescreen, width, height
40 | ) # call parent init screen.
41 | print(("Init Mod: %s %dx%d"%(self.name,self.width,self.height)))
42 | self.MainColor = (255, 255, 255) # main color
43 | self.BallColor = (255, 255, 255) # ball color
44 |
45 | self.xLineFromCenter = int(self.width / 8)
46 | self.BallSize = int(self.height / 2.5) # ball size
47 | self.yLineHeight = self.height
48 | self.yCenterForBall = int(self.height / 2)
49 |
50 | self.imuData = IMUData()
51 | self.airData = AirData()
52 | if len(shared.Dataship.imuData) > 0:
53 | self.imuData = shared.Dataship.imuData[0]
54 | if len(shared.Dataship.airData) > 0:
55 | self.airData = shared.Dataship.airData[0]
56 |
57 | # called every redraw for the mod
58 | def draw(self, dataship:Dataship, smartdisplay, pos=(None,None)):
59 |
60 | if pos[0] is None:
61 | x = smartdisplay.x_center
62 | else:
63 | x = pos[0] + self.width / 2
64 | if pos[1] is None:
65 | y = smartdisplay.y_center
66 | else:
67 | y = pos[1]
68 |
69 | # Slip/Skid Indicator
70 | if self.imuData.slip_skid != None:
71 | pygame.draw.circle(
72 | self.pygamescreen,
73 | self.BallColor,
74 | (
75 | int(x + self.x_offset) - int(self.imuData.slip_skid * 150),
76 | y + self.yCenterForBall,
77 | ),
78 | self.BallSize,
79 | 0,
80 | )
81 | pygame.draw.line(
82 | self.pygamescreen,
83 | self.MainColor,
84 | (x + self.xLineFromCenter + self.x_offset, y ),
85 | (x + self.xLineFromCenter + self.x_offset, y + self.yLineHeight),
86 | 3,
87 | )
88 | pygame.draw.line(
89 | self.pygamescreen,
90 | self.MainColor,
91 | (x - self.xLineFromCenter + self.x_offset, y ),
92 | (x - self.xLineFromCenter + self.x_offset, y + self.yLineHeight),
93 | 3,
94 | )
95 |
96 | # called before screen draw. To clear the screen to your favorite color.
97 | def clear(self):
98 | #self.ahrs_bg.fill((0, 0, 0)) # clear screen
99 | pass
100 |
101 | # handle key events
102 | def processEvent(self, event):
103 | pass
104 |
105 | def get_module_options(self):
106 | return {
107 | "MainColor": {
108 | "type": "color",
109 | "default": self.MainColor,
110 | "label": "Main Color",
111 | "description": "Color of the main line.",
112 | },
113 | "BallColor": {
114 | "type": "color",
115 | "default": self.BallColor,
116 | "label": "Ball Color",
117 | "description": "Color of the ball.",
118 | }
119 | }
120 |
121 |
122 |
123 | # vi: modeline tabstop=8 expandtab shiftwidth=4 softtabstop=4 syntax=python
124 |
--------------------------------------------------------------------------------
/lib/modules/hud/wind/arrow_g.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/modules/hud/wind/arrow_g.bmp
--------------------------------------------------------------------------------
/lib/modules/hud/wind/arrow_g.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/lib/modules/hud/wind/arrow_g.png
--------------------------------------------------------------------------------
/lib/modules/hud/wind/wind.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | #################################################
4 | # Module: Wind
5 | # Topher 2021.
6 | # Adapted from F18 HUD Screen code by Brian Chesteen.
7 | # 2/9/2025 - added dataship refactor.
8 |
9 | from lib.modules._module import Module
10 | from lib import hud_graphics
11 | from lib import hud_utils
12 | from lib import smartdisplay
13 | from lib.common.dataship.dataship import Dataship
14 | from lib.common.dataship.dataship_air import AirData
15 | from lib.common import shared
16 | import pygame
17 | import math
18 |
19 |
20 | class wind(Module):
21 | # called only when object is first created.
22 | def __init__(self):
23 | Module.__init__(self)
24 | self.name = "Wind" # set name
25 | self.arrow_size = 50
26 | self.x_offset = 0
27 | self.y_offset = 0
28 | self.arrow_size = 50
29 |
30 | self.airData = AirData()
31 |
32 | # called once for setup
33 | def initMod(self, pygamescreen, width=None, height=None):
34 | if width is None:
35 | width = 60 # default width
36 | if height is None:
37 | height = 100 # default height
38 | Module.initMod(
39 | self, pygamescreen, width, height
40 | ) # call parent init screen.
41 | print(("Init Mod: %s %dx%d"%(self.name,self.width,self.height)))
42 |
43 | self.myfont = pygame.font.SysFont("monospace", 20, bold=True) #
44 | self.MainColor = (0, 255, 0) # main color
45 |
46 | self.arrow = pygame.image.load("lib/modules/hud/wind/arrow_g.bmp").convert()
47 | self.arrow.set_colorkey((255, 255, 255))
48 | self.arrow_scaled = pygame.transform.scale(self.arrow, (50, 50))
49 | self.update_arrow_size()
50 |
51 | # get the airData object from the shared object
52 | self.airData = AirData()
53 | if len(shared.Dataship.airData) > 0:
54 | self.airData = shared.Dataship.airData[0]
55 |
56 | # called every redraw for the mod
57 | def draw(self, dataship:Dataship, smartdisplay, pos):
58 |
59 | x,y = pos
60 |
61 | # Wind Speed
62 | if self.airData.Wind_speed != None:
63 | label = self.myfont.render(
64 | "%dkt" % self.airData.Wind_speed, 1, (255, 255, 0)
65 | )
66 | self.pygamescreen.blit(label, (x, y + 80))
67 | else:
68 | label = self.myfont.render("--kt", 1, (255, 255, 0))
69 | self.pygamescreen.blit(label, (x, y + 80))
70 |
71 | # Wind Dir
72 | if self.airData.Wind_dir != None:
73 | label = self.myfont.render(
74 | "%d\xb0" % self.airData.Wind_dir, 1, (255, 255, 0)
75 | )
76 | self.pygamescreen.blit(label, (x, y ))
77 | else:
78 | label = self.myfont.render("--\xb0", 1, (255, 255, 0))
79 | self.pygamescreen.blit(label, (x, y ))
80 |
81 | # draw the arrow. first rotate it.
82 | if self.airData.Wind_dir != None:
83 | arrow_rotated = pygame.transform.rotate(
84 | self.arrow_scaled, self.airData.Wind_dir
85 | )
86 | arrow_rect = arrow_rotated.get_rect()
87 | self.pygamescreen.blit(
88 | arrow_rotated,
89 | (
90 | x + 20 - arrow_rect.center[0],
91 | (y + 50) - arrow_rect.center[1],
92 | ),
93 | )
94 |
95 |
96 | # called before screen draw. To clear the screen to your favorite color.
97 | def clear(self):
98 | #self.ahrs_bg.fill((0, 0, 0)) # clear screen
99 | print("clear")
100 |
101 | # handle key events
102 | def processEvent(self, event):
103 | print("processEvent")
104 |
105 | def get_module_options(self):
106 | return {
107 | "arrow_size": {
108 | "type": "int",
109 | "default": self.arrow_size,
110 | "min": 10,
111 | "max": 150,
112 | "label": "Arrow Size",
113 | "description": "Size of the arrow.",
114 | "post_change_function": "update_arrow_size"
115 | }
116 | }
117 |
118 | def update_arrow_size(self):
119 | self.arrow_scaled = pygame.transform.scale(
120 | self.arrow, (self.arrow_size, self.arrow_size)
121 | )
122 | self.arrow_scaled_rect = self.arrow_scaled.get_rect()
123 |
124 |
125 |
126 | # vi: modeline tabstop=8 expandtab shiftwidth=4 softtabstop=4 syntax=python
127 |
--------------------------------------------------------------------------------
/lib/modules_kivy/horizon_3d/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/screens/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/util/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/util/drawTimer.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import pygame
4 | from lib.common import shared
5 |
6 | white = (255, 255, 255)
7 | tron_whi = (189, 254, 255)
8 | red = (255, 80, 60)
9 | green = ( 50, 255, 50)
10 | blue = ( 120, 90, 255)
11 | tron_blu = ( 0, 219, 232)
12 | black = ( 0, 0, 0)
13 | cyan = ( 50, 255, 255)
14 | magenta = (255, 0, 255)
15 | yellow = (255, 255, 0)
16 | tron_yel = (255, 218, 10)
17 | orange = (255, 127, 0)
18 | tron_ora = (255, 202, 0)
19 | nerd_yellow = (255, 202, 100)
20 |
21 | tron_regular = tron_ora
22 | tron_light = tron_yel
23 | tron_inverse = tron_whi
24 |
25 | CENTER = 1
26 | TOP_CENTER = 2
27 | TOP_LEFT = 3
28 | TOP_RIGHT = 4
29 |
30 | MIDDLE_LEFT = 5
31 | MIDDLE_RIGHT = 6
32 | MIDDLE_CENTER = 1
33 |
34 | BOTTOM_LEFT = 7
35 | BOTTOM_RIGHT = 8
36 | BOTTOM_CENTER = 9
37 |
38 |
39 | globalDrawTimers = []
40 |
41 | #############################################
42 | ## Class: DrawTimer
43 | ##
44 | ##
45 | class DrawTimer(object):
46 |
47 |
48 |
49 | def __init__(self,ttype,text,ticksTillDone,color,postion=0,callBackHandler=0):
50 | self.type = ttype # type: 0=none, 1=growl notice
51 | self.startTime = pygame.time.get_ticks()
52 | self.endTime = self.startTime + ticksTillDone
53 | self.callBackHandler = callBackHandler
54 | self.text = text
55 | self.font = pygame.font.SysFont( "monospace", 32,True)
56 | self.postion = postion
57 | self.color = color
58 | self.customDrawCallBack = 0
59 |
60 | def draw(self,pyscreen):
61 | if self.customDrawCallBack != 0:
62 | self.customDrawCallBack()
63 | elif self.type == 1:
64 | make_box_label(self.font,self.text,self.postion,self.color,pyscreen)
65 |
66 | def checkIfDone(self):
67 | now = pygame.time.get_ticks()
68 | if now >= self.endTime:
69 | if self.callBackHandler != 0:
70 | self.callBackHandler()
71 | return True
72 | return False
73 |
74 | def addCustomDraw(drawFunc,time,cb=0):
75 | timer = DrawTimer(0,"",time,cb)
76 | timer.customDrawCallBack = drawFunc
77 | globalDrawTimers.append(timer)
78 |
79 | def addGrowlNotice(text,time,color,postion=BOTTOM_RIGHT,cb=0):
80 | # first check if there is any in this same postion. if so remove it.
81 | for existingTimer in globalDrawTimers:
82 | if existingTimer.postion == postion:
83 | globalDrawTimers.remove(existingTimer)
84 | timer = DrawTimer(1,text,time,color,postion,cb)
85 | globalDrawTimers.append(timer)
86 |
87 | def processAllDrawTimers(pyscreen):
88 | if len(globalDrawTimers) > 0:
89 | for timer in globalDrawTimers:
90 | if timer.checkIfDone() == True: # if timer is done then remove it from the array of timers.
91 | globalDrawTimers.remove(timer)
92 | else:
93 | timer.draw(pyscreen)
94 |
95 | def make_box_label(font,text, postion, colour, pyscreen):
96 | width, height = font.size(text)
97 | padding = 15
98 |
99 | x_start = shared.smartdisplay.x_start
100 | y_start = shared.smartdisplay.y_start
101 | x_end = shared.smartdisplay.x_end
102 | y_end = shared.smartdisplay.y_end
103 | screen_w = shared.smartdisplay.width
104 | screen_h = shared.smartdisplay.height
105 |
106 | if postion == CENTER: # center of screen.
107 | x = x_start + (shared.smartdisplay.widthCenter)-(width/2)
108 | y = y_start + (shared.smartdisplay.heightCenter)-(height/2)
109 | if postion == TOP_LEFT: # top left
110 | x = x_start + padding
111 | y = y_start + padding
112 | if postion == TOP_CENTER: # top middle
113 | x = x_start + (shared.smartdisplay.widthCenter)-(width/2)
114 | y = y_start + padding
115 | if postion == TOP_RIGHT: # top right
116 | x = (x_end)-(width)-padding
117 | y = y_start + padding
118 | if postion == MIDDLE_LEFT: # middle left
119 | x = x_start + padding
120 | y = (screen_h/2)-(height/2)
121 | if postion == MIDDLE_RIGHT: # middle right
122 | x = (x_end)-(width)-padding
123 | y = (shared.smartdisplay.heightCenter)-(height/2)
124 | if postion == BOTTOM_LEFT: # bottom left
125 | x = x_start + padding
126 | y = (y_end)-(height)-padding
127 | if postion == BOTTOM_CENTER: # bottom middle
128 | x = (shared.smartdisplay.widthCenter)-(width/2)
129 | y = (y_end)-(height)-padding
130 | if postion == BOTTOM_RIGHT: # bottom right
131 | x = (screen_w)-(width)-padding
132 | y = (y_end)-(height)-padding
133 |
134 | pygame.draw.rect(pyscreen, colour, (x-padding,y-padding,width+padding+10,height+padding+10),0)
135 | label=font.render(text, 1, (0,0,0))
136 | pyscreen.blit(label,(x,y))
137 |
138 | # vi: modeline tabstop=8 expandtab shiftwidth=4 softtabstop=4 syntax=python
139 |
140 |
--------------------------------------------------------------------------------
/lib/util/mac_hardware.py:
--------------------------------------------------------------------------------
1 | import re, subprocess
2 | from sys import platform
3 |
4 | def is_macosx():
5 | try:
6 | if platform == "darwin":
7 | # OS X
8 | return True
9 | except Exception: pass
10 | return False
11 |
12 | # using MacTmp package. https://pypi.org/project/MacTmp/
13 | def check_CPU_temp():
14 | #import MacTmp
15 | #return float(MacTmp.CPU_Temp())
16 | return 0
17 |
18 | def check_GPU_temp():
19 | #import MacTmp
20 | #return MacTmp.GPU_Temp()
21 | return 0
22 |
23 |
--------------------------------------------------------------------------------
/lib/util/rpi_hardware.py:
--------------------------------------------------------------------------------
1 | import serial, re, subprocess, platform
2 |
3 | def is_raspberrypi():
4 | try:
5 | print(platform.machine())
6 | if platform.machine() == 'armv7l':
7 | return True
8 | #with io.open('/sys/firmware/devicetree/base/model', 'r') as m:
9 | # if m.read().lower().startswith('raspberry pi',0,12): return True
10 | except Exception: pass
11 | return False
12 |
13 | def check_CPU_temp():
14 | temp = None
15 | err, msg = subprocess.getstatusoutput('vcgencmd measure_temp')
16 | if not err:
17 | m = re.search(r'-?\d\.?\d*', msg) # a solution with a regex
18 | try:
19 | temp = float(m.group())
20 | except ValueError: # catch only error needed
21 | pass
22 | return temp, msg
23 |
24 | def mount_usb_drive():
25 | global rpi_usb_drive_mount
26 | import os
27 | if is_raspberrypi() == False:
28 | return False
29 | partitionsFile = open("/proc/partitions")
30 | lines = partitionsFile.readlines()[2:]#Skips the header lines
31 | for line in lines:
32 | words = [x.strip() for x in line.split()]
33 | minorNumber = int(words[1])
34 | deviceName = words[3]
35 | if minorNumber % 16 == 0:
36 | path = "/sys/class/block/" + deviceName
37 | if os.path.islink(path):
38 | if os.path.realpath(path).find("/usb") > 0:
39 | checkForDevice = "/dev/"+deviceName+"1"
40 | if os.path.exists(checkForDevice)!=True:
41 | checkForDevice = "/dev/"+deviceName
42 | rpi_usb_drive_mount = checkForDevice
43 | print (checkForDevice+" -> /mnt/usb")
44 | os.system('mkdir /mnt/usb 2>/dev/null')
45 | os.system("mount "+checkForDevice+" /mnt/usb 2>/dev/null")
46 | return True
47 | return False
48 |
49 | def unmount_usb_drive():
50 | global rpi_usb_drive_mount
51 | import os
52 | if is_raspberrypi() == False:
53 | return False
54 | os.system("umount "+rpi_usb_drive_mount+" /mnt/usb 2>/dev/null")
55 | return True
56 |
57 | def list_serial_ports(printthem):
58 |
59 | # List all the Serial COM Ports on Raspberry Pi
60 | proc = subprocess.Popen(['ls /dev/tty[A-Za-z]*'], shell=True, stdout=subprocess.PIPE)
61 | com_ports = proc.communicate()[0]
62 | com_ports_list = str(com_ports).split("\\n") # find serial ports
63 | rtn = []
64 | for com_port in com_ports_list:
65 | if 'ttyS' in com_port:
66 | if(printthem==True): print(com_port)
67 | rtn.append(com_port)
68 | if 'ttyUSB' in com_port:
69 | if(printthem==True): print(com_port)
70 | rtn.append(com_port)
71 | return rtn
72 |
73 |
74 | def is_server_available():
75 | import urllib.request
76 | host = "http://flyonspeed.org"
77 | urllib.request.urlopen(host)
78 | print(host+" available")
79 | return True
80 |
81 | # hostname = "flyonspeed.org"
82 | # response = os.system("ping -n 1 " + hostname)
83 |
84 | # err, msg = subprocess.getstatusoutput("ping -n 1 " + hostname)
85 | # if not err:
86 | # m = re.search(r'-?\d\.?\d*', msg) # a solution with a regex
87 | # try:
88 | # temp = float(m.group())
89 | # except ValueError: # catch only error needed
90 | # pass
91 | # return temp, msg
92 |
93 |
94 | # if response == 0:
95 | # return True
96 | # else:
97 | # return False
98 |
99 |
100 | def get_thermal_temperature():
101 | thermal = subprocess.check_output(
102 | "cat /sys/class/thermal/thermal_zone0/temp", shell=True).decode("utf8")
103 | return float(thermal) / 1000.0
104 |
105 | # uptime in seconds
106 | def get_uptime():
107 | uptime = subprocess.check_output(
108 | "cat /proc/uptime", shell=True).decode("utf8")
109 | return float(uptime.split(" ")[0])
110 |
111 | # returns load averages for 1, 5, and 15 minutes
112 | def get_load_average():
113 | uptime = subprocess.check_output("uptime", shell=True).decode("utf8")
114 | load_average = uptime.split("load average:")[1].split(",")
115 | return list(map(float, load_average))
116 |
117 | def get_kernel_release():
118 | return subprocess.check_output("uname -r", shell=True).decode("utf8").strip()
119 |
120 | def get_full_os_name():
121 | import platform
122 | import os
123 | return os.name + " " + platform.system() + " " + str(platform.release())
124 |
125 |
126 | #returns total, free and available memory in kB
127 | def get_memory_usage():
128 | meminfo = subprocess.check_output("cat /proc/meminfo", shell=True).decode("utf8").strip()
129 | memory_usage = meminfo.split("\n")
130 |
131 | total_memory = [x for x in memory_usage if 'MemTotal' in x][0]
132 | free_memory = [x for x in memory_usage if 'MemFree' in x][0]
133 | available_memory = [x for x in memory_usage if 'MemAvailable' in x][0]
134 |
135 | total_memory = re.findall(r'\d+', total_memory)[0]
136 | free_memory = re.findall(r'\d+', free_memory)[0]
137 | available_memory = re.findall(r'\d+', available_memory)[0]
138 |
139 | data = {
140 | "total_memory": int(total_memory),
141 | "free_memory": int(free_memory),
142 | "available_memory": int(available_memory)
143 | }
144 | return data
145 |
--------------------------------------------------------------------------------
/lib/version.py:
--------------------------------------------------------------------------------
1 | # Semantic versioning (major.minor.patch)
2 | __version__ = "0.0.33"
3 | __version_info__ = tuple(map(int, __version__.split('.')))
4 |
5 | # Optional additional version info
6 | __author__ = "TronView.org"
7 | __license__ = "GPLv3"
8 | __copyright__ = f"Copyright 2025 {__author__}"
9 |
10 | # Build info
11 | __build__ = "alpha" # or "stable", "beta", etc.
12 | __build_date__ = "2025-04-21"
13 | __build_time__ = "14:26:27 PDT"
14 |
15 | __build_version__ = f"{__version__} {__build__} {__build_date__} {__build_time__}"
16 |
--------------------------------------------------------------------------------
/test.txt:
--------------------------------------------------------------------------------
1 | test
2 |
3 |
--------------------------------------------------------------------------------
/util/git_pull.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | git config --global credential.helper cache
4 | git config --global credential.helper 'cache --timeout=9999999'
5 | git pull
6 |
7 |
8 |
--------------------------------------------------------------------------------
/util/macosx/requirements.txt:
--------------------------------------------------------------------------------
1 | MacTmp == 0.0.9
2 | geographiclib == 2.0.0
3 | numpy == 2.1.1
4 | pygame-ce == 2.5.1
5 | pygame_gui == 0.6.10
6 | #serial == 0.0.97
7 | # use pyserial instead of serial on macos..
8 | pyserial == 3.5
9 | #kivy == 2.1.0
10 | numpy-stl>=2.17.1
11 | pynmea2
12 | textual
13 | configupdater
14 | geographiclib
15 | pillow
16 | opencv-python
17 | prettytable
18 |
19 |
--------------------------------------------------------------------------------
/util/macosx/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # make sure they are on macos
4 | if [[ $(uname) != "Darwin" ]]; then
5 | echo "This script is only supported on macOS."
6 | exit 1
7 | fi
8 |
9 | # Get absolute paths. remove 2 levels from the path because we are in util/macosx
10 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
11 | TRONVIEW_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
12 | echo "TronView directory: $TRONVIEW_DIR"
13 |
14 | # Function to get user input with a prompt
15 | get_input() {
16 | prompt="$1"
17 | # Print prompt to stderr and read from stdin
18 | echo "$prompt" >&2
19 | read response /dev/null
31 | then
32 | echo "Homebrew could not be found. Installing..."
33 | ruby -e '$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)'
34 | echo export PATH='usr/local/bin:$PATH' >> ~/.bash_profile
35 | brew update
36 | brew doctor
37 | else
38 | echo "Checking if brew is up to date..."
39 | brew update
40 | brew doctor
41 | fi
42 |
43 | # install dialog via homebrew
44 | brew install dialog
45 |
46 | # check if python3 is installed
47 | if ! command -v python3 &> /dev/null
48 | then
49 | echo "Python3 could not be found. Installing..."
50 | brew install python3
51 | fi
52 |
53 | # check if virtual environment is activated
54 | if ! [[ "$VIRTUAL_ENV" ]]; then
55 |
56 | echo "Python Virtual environment is not activated. Creating venv..."
57 | python3 -m venv "$TRONVIEW_DIR/venv"
58 | source "$TRONVIEW_DIR/venv/bin/activate"
59 | fi
60 |
61 | # install python libs in virtual environment
62 | echo "Installing python libs"
63 | python3 -m pip install -r "$TRONVIEW_DIR/util/macosx/requirements.txt"
64 |
65 | # yn=$(get_input "Install Python libraries required by TronView (macOS)? (y or n)")
66 | # case $yn in
67 | # [Yy]* )echo "Installing python libs"
68 | # # install python libs in virtual environment
69 | # echo "TronView directory: $TRONVIEW_DIR"
70 | # python3 -m pip install -r "$TRONVIEW_DIR/util/macosx/requirements.txt"
71 | # ;;
72 | # [Nn]* )echo "...";
73 | # esac
74 |
75 |
76 |
--------------------------------------------------------------------------------
/util/old/kill_process.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | sudo pkill -f -9 'main.py'
4 |
--------------------------------------------------------------------------------
/util/old/run_demo.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd /home/pi/efis_to_hud
4 | sudo pkill -f 'python3'
5 | #sudo python3 main.py -i serial_d100 -e
6 |
7 | sudo python3 main.py -i serial_g3x -s F18_HUD -e
8 |
9 | #sudo python3 main.py -i serial_mgl -s F18_HUD
10 |
11 |
--------------------------------------------------------------------------------
/util/old/run_mgl.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd /home/pi/efis_to_hud
4 | #sudo pkill -f 'python3'
5 | #sudo python3 main.py -i mgl_serial -e
6 |
7 | #sudo python3 main.py -i serial_g3x -s F18_HUD -e
8 |
9 | python3 main.py -i serial_mgl -s F18_HUD -e
10 |
11 |
--------------------------------------------------------------------------------
/util/old/run_mgllive.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd /home/pi/efis_to_hud
4 | sudo pkill -f 'python3'
5 | #sudo python3 main.py -i serial_d100 -e
6 |
7 | sudo python3 main.py -i serial_mgl -s F18_HUD
8 |
9 |
--------------------------------------------------------------------------------
/util/rpi/NotoSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/util/rpi/NotoSans-Regular.ttf
--------------------------------------------------------------------------------
/util/rpi/autologin@.service:
--------------------------------------------------------------------------------
1 | # This file is part of systemd.
2 | #
3 | # systemd is free software; you can redistribute it and/or modify it
4 | # under the terms of the GNU Lesser General Public License as published by
5 | # the Free Software Foundation; either version 2.1 of the License, or
6 | # (at your option) any later version.
7 |
8 | [Unit]
9 | Description=Getty on %I
10 | Documentation=man:agetty(8) man:systemd-getty-generator(8)
11 | Documentation=http://0pointer.de/blog/projects/serial-console.html
12 | After=systemd-user-sessions.service plymouth-quit-wait.service
13 | After=rc-local.service
14 |
15 | # If additional gettys are spawned during boot then we should make
16 | # sure that this is synchronized before getty.target, even though
17 | # getty.target didn't actually pull it in.
18 | Before=getty.target
19 | IgnoreOnIsolate=yes
20 |
21 | # On systems without virtual consoles, don't start any getty. Note
22 | # that serial gettys are covered by serial-getty@.service, not this
23 | # unit.
24 | ConditionPathExists=/dev/tty0
25 |
26 | [Service]
27 | # the VT is cleared by TTYVTDisallocate
28 | ExecStart=-/sbin/agetty --autologin pi --noclear %I $TERM
29 | Type=idle
30 | Restart=always
31 | RestartSec=0
32 | UtmpIdentifier=%I
33 | TTYPath=/dev/%I
34 | TTYReset=yes
35 | TTYVHangup=yes
36 | TTYVTDisallocate=yes
37 | KillMode=process
38 | IgnoreSIGPIPE=no
39 | SendSIGHUP=yes
40 |
41 | # Unset locale for the console getty since the console has problems
42 | # displaying some internationalized messages.
43 | Environment=LANG= LANGUAGE= LC_CTYPE= LC_NUMERIC= LC_TIME= LC_COLLATE= LC_MONETARY= LC_MESSAGES= LC_PAPER= LC_NAME= LC_ADDRESS= LC_TELEPHONE= LC_MEASUREMENT= LC_IDENTIFICATION=
44 |
45 | [Install]
46 | WantedBy=getty.target
47 | DefaultInstance=tty1
48 |
--------------------------------------------------------------------------------
/util/rpi/i2c/calibrate_bno085.py:
--------------------------------------------------------------------------------
1 | # calibrate_bno085.py
2 |
3 | import time
4 | import math
5 | import board
6 | import busio
7 | import pygame
8 | import adafruit_bno08x
9 | from adafruit_bno08x.i2c import BNO08X_I2C
10 | from digitalio import DigitalInOut
11 |
12 | # Initialize Pygame
13 | pygame.init()
14 | screen = pygame.display.set_mode((800, 600))
15 | pygame.display.set_caption('BNO085 Calibration')
16 | clock = pygame.time.Clock()
17 | font = pygame.font.Font(None, 36)
18 |
19 | # Colors
20 | BLACK = (0, 0, 0)
21 | WHITE = (255, 255, 255)
22 | GREEN = (0, 255, 0)
23 | YELLOW = (255, 255, 0)
24 | RED = (255, 0, 0)
25 |
26 | i2c = busio.I2C(board.SCL, board.SDA)
27 | reset_pin = DigitalInOut(board.D5)
28 | bno = BNO08X_I2C(i2c, reset=reset_pin, debug=False)
29 |
30 | bno.begin_calibration()
31 |
32 | bno.enable_feature(adafruit_bno08x.BNO_REPORT_MAGNETOMETER)
33 | bno.enable_feature(adafruit_bno08x.BNO_REPORT_GAME_ROTATION_VECTOR)
34 | #bno.enable_feature(adafruit_bno08x.BNO_REPORT_ROTATION_VECTOR)
35 |
36 |
37 | def quaternion_to_euler(x, y, z, w):
38 | roll = math.atan2(2.0 * (w * x + y * z), 1.0 - 2.0 * (x * x + y * y))
39 | roll = round(math.degrees(roll), 3)
40 |
41 | pitch_raw = 2.0 * (w * y - z * x)
42 | pitch = math.asin(max(-1.0, min(1.0, pitch_raw)))
43 | pitch = round(math.degrees(pitch), 3)
44 |
45 | yaw = -math.atan2(2.0 * (w * z + x * y), 1.0 - 2.0 * (y * y + z * z))
46 | yaw = round(math.degrees(yaw), 3)
47 |
48 | if yaw > 180:
49 | yaw -= 360
50 | elif yaw < -180:
51 | yaw += 360
52 |
53 | return roll, pitch, yaw
54 |
55 | def draw_text(text, position, color=WHITE):
56 | text_surface = font.render(text, True, color)
57 | screen.blit(text_surface, position)
58 |
59 | calibration_good_at = None
60 | running = True
61 | fps = 10 # Initial FPS setting
62 |
63 | while running:
64 | for event in pygame.event.get():
65 | if event.type == pygame.QUIT:
66 | running = False
67 | elif event.type == pygame.KEYDOWN:
68 | if event.key == pygame.K_s and calibration_good_at and (time.monotonic() - calibration_good_at > 5.0):
69 | bno.save_calibration_data()
70 | running = False
71 | elif event.key == pygame.K_q: # Add 'q' key to quit
72 | running = False
73 | elif event.key == pygame.K_p: # Add 'p' key to toggle FPS
74 | fps = 40 if fps == 10 else 10
75 | elif event.key == pygame.K_1: # Add '1' key for soft reset
76 | bno.soft_reset()
77 | elif event.key == pygame.K_2: # Add '2' key for hard reset
78 | bno.hard_reset()
79 |
80 | screen.fill(BLACK)
81 |
82 | # Get sensor data
83 | mag_x, mag_y, mag_z = bno.magnetic
84 | game_quat_i, game_quat_j, game_quat_k, game_quat_real = bno.game_quaternion
85 | calibration_status = bno.calibration_status
86 |
87 | # Calculate Euler angles
88 | roll, pitch, yaw = quaternion_to_euler(game_quat_i, game_quat_j, game_quat_k, game_quat_real)
89 |
90 | # Display magnetometer data
91 | draw_text("Magnetometer (uT):", (20, 20))
92 | draw_text(f"X: {mag_x:0.6f}", (20, 60))
93 | draw_text(f"Y: {mag_y:0.6f}", (20, 100))
94 | draw_text(f"Z: {mag_z:0.6f}", (20, 140))
95 |
96 | # Display quaternion data
97 | draw_text("Vector Quaternion:", (20, 200))
98 | draw_text(f"I: {game_quat_i:0.6f}", (20, 240))
99 | draw_text(f"J: {game_quat_j:0.6f}", (20, 280))
100 | draw_text(f"K: {game_quat_k:0.6f}", (20, 320))
101 | draw_text(f"Real: {game_quat_real:0.6f}", (20, 360))
102 |
103 | # Display Euler angles
104 | draw_text("Euler Angles (degrees):", (20, 420))
105 | draw_text(f"Roll: {roll:0.3f}", (20, 460))
106 | draw_text(f"Pitch: {pitch:0.3f}", (20, 500))
107 | draw_text(f"Yaw: {yaw:0.3f}", (20, 540))
108 |
109 | # Display calibration status
110 | status_color = RED if calibration_status < 2 else YELLOW if calibration_status == 2 else GREEN
111 | draw_text(f"{adafruit_bno08x.REPORT_ACCURACY_STATUS[calibration_status]} ({calibration_status})",
112 | (300, 20), status_color)
113 |
114 | if calibration_status >= 2:
115 | draw_text("Calibration Complete", (300, 60), GREEN)
116 | elif calibration_status == 1:
117 | draw_text("Calibration in Progress", (300, 60), YELLOW)
118 | else:
119 | draw_text("Calibration Not Started", (300, 60), RED)
120 |
121 | # Display FPS
122 | fps_text = f"FPS: {fps}"
123 | draw_text(fps_text, (300, 80))
124 |
125 | # Display "q" key to quit
126 | draw_text("Press 'Q' to quit", (300, 100))
127 |
128 | # Display "p" key to toggle FPS
129 | draw_text("Press 'P' to toggle FPS", (300, 140))
130 |
131 | # Display reset options
132 | draw_text("Press '1' for soft reset", (300, 180))
133 | draw_text("Press '2' for hard reset", (300, 200))
134 |
135 | if not calibration_good_at and calibration_status >= 2:
136 | calibration_good_at = time.monotonic()
137 |
138 | if calibration_good_at and (time.monotonic() - calibration_good_at > 5.0):
139 | draw_text("Press 'S' to save calibration", (300, 160), GREEN)
140 |
141 | pygame.display.flip()
142 | clock.tick(fps) # Use variable fps instead of fixed value
143 |
144 | pygame.quit()
145 | print("Calibration complete")
--------------------------------------------------------------------------------
/util/rpi/setup_autorun.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | sudo echo '@lxterminal -e /home/pi/TronView/util/run.sh' >> /etc/xdg/lxsession/LXDE-pi/autostart
4 |
--------------------------------------------------------------------------------
/util/tests/3d/cube.glsl:
--------------------------------------------------------------------------------
1 | ---vertex
2 | attribute vec3 my_vertex_position;
3 |
4 | uniform mat4 center_the_cube;
5 | uniform mat4 my_rotation;
6 | uniform mat4 my_view;
7 | uniform mat4 my_proj;
8 |
9 | void main()
10 | {
11 | gl_Position = my_proj * my_view * my_rotation * center_the_cube *
12 | vec4(my_vertex_position,1);
13 | }
14 |
15 | ---fragment
16 | void main()
17 | {
18 | gl_FragColor = vec4(0, 1, 0.5, 1);
19 | }
--------------------------------------------------------------------------------
/util/tests/3d/cube.py:
--------------------------------------------------------------------------------
1 | from kivy.app import App
2 | from kivy.uix.widget import Widget
3 | from kivy.core.window import Window
4 | from kivy.graphics.instructions import RenderContext
5 | from kivy.graphics.transformation import Matrix
6 | from kivy.graphics import Mesh
7 | from kivy.clock import Clock
8 | import os
9 |
10 | class MyRoot(Widget):
11 | axis = (0,0,1)
12 | angle = 0
13 | def __init__(self):
14 | super().__init__()
15 | shader_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cube.glsl')
16 | self.canvas = RenderContext()
17 | self.canvas.shader.source = shader_path
18 | ff = ( (b'my_vertex_position', 3, 'float'), )
19 | vv = ( 0, 0, 0, 1., 0, 0, 1., 1., 0, 0, 1., 0,
20 | 0, 0, 1., 1., 0, 1., 1., 1., 1., 0, 1., 1. )
21 | ii = ( 0,1, 1,2, 2,3, 3,0, 0,4, 1,5,
22 | 2,6, 3,7, 4,5, 5,6, 6,7, 7,4 )
23 | with self.canvas:
24 | Mesh(fmt=ff, vertices=vv, indices=ii, mode='lines')
25 | self.canvas['center_the_cube'] = Matrix().translate(-.5,-.5,-.5)
26 | self.canvas['my_view'] = Matrix().look_at(3,3,2, 0,0,0, 0,0,1)
27 | self.canvas['my_proj'] = Matrix()
28 | aspect = Window.size[0] / Window.size[1]
29 | self.canvas['my_proj'].perspective(30,aspect,.1,100)
30 | Clock.schedule_interval(self.increase_angle, 1/60)
31 | def increase_angle(self,_):
32 | self.angle += .01
33 | self.canvas['my_rotation'] = Matrix().rotate(self.angle,*self.axis)
34 |
35 | class MyApp(App):
36 | def build(self): return MyRoot()
37 |
38 | MyApp().run()
--------------------------------------------------------------------------------
/util/tests/3d/sphere.glsl:
--------------------------------------------------------------------------------
1 | ---vertex
2 | attribute vec3 my_vertex_position;
3 |
4 | uniform mat4 center_the_sphere;
5 | uniform mat4 my_rotation;
6 | uniform mat4 my_view;
7 | uniform mat4 my_proj;
8 |
9 | void main()
10 | {
11 | gl_Position = my_proj * my_view * my_rotation * center_the_sphere *
12 | vec4(my_vertex_position,1);
13 | }
14 |
15 | ---fragment
16 |
17 | void main()
18 | {
19 | gl_FragColor = vec4(0.3, 0.6, 1.0, 1); // Light blue color for the sphere
20 | }
--------------------------------------------------------------------------------
/util/tests/3d/sphere.py:
--------------------------------------------------------------------------------
1 | from kivy.app import App
2 | from kivy.uix.widget import Widget
3 | from kivy.uix.floatlayout import FloatLayout
4 | from kivy.core.window import Window
5 | from kivy.graphics.instructions import RenderContext
6 | from kivy.graphics.transformation import Matrix
7 | from kivy.graphics import Mesh, Color, Rectangle
8 | from kivy.clock import Clock
9 | from kivy.core.text import Label as CoreLabel
10 | import os
11 | import math
12 | import time
13 |
14 | class Sphere3D(Widget):
15 | def __init__(self, **kwargs):
16 | super().__init__(**kwargs)
17 | self.canvas = RenderContext()
18 | shader_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'sphere.glsl')
19 | self.canvas.shader.source = shader_path
20 |
21 | # Create sphere mesh
22 | self.create_sphere(radius=1.0, segments=20)
23 |
24 | # Initialize matrices
25 | self.canvas['center_the_sphere'] = Matrix().translate(0, 0, 0)
26 | self.canvas['my_view'] = Matrix().look_at(3, 3, 2, 0, 0, 0, 0, 0, 1)
27 | self.canvas['my_proj'] = Matrix()
28 | aspect = Window.size[0] / float(Window.size[1])
29 | self.canvas['my_proj'].perspective(30, aspect, 0.1, 100)
30 |
31 | # Set up rotation
32 | self.angle = 0
33 | Clock.schedule_interval(self.update, 1.0/60.0)
34 |
35 | def create_sphere(self, radius=1.0, segments=20):
36 | vertices = []
37 | indices = []
38 | vertex_count = 0
39 |
40 | # Create vertices
41 | for i in range(segments + 1):
42 | lat = math.pi * (-0.5 + float(i) / segments)
43 | for j in range(segments + 1):
44 | lon = 2 * math.pi * float(j) / segments
45 | x = math.cos(lat) * math.cos(lon)
46 | y = math.sin(lat)
47 | z = math.cos(lat) * math.sin(lon)
48 | vertices.extend([x * radius, y * radius, z * radius])
49 |
50 | # Create indices for lines
51 | if i < segments and j < segments:
52 | # Horizontal lines
53 | indices.extend([vertex_count, vertex_count + 1])
54 | # Vertical lines
55 | indices.extend([vertex_count, vertex_count + segments + 1])
56 | vertex_count += 1
57 |
58 | with self.canvas:
59 | self.mesh = Mesh(
60 | vertices=vertices,
61 | indices=indices,
62 | fmt=[(b'my_vertex_position', 3, 'float')],
63 | mode='lines'
64 | )
65 |
66 | def update(self, dt):
67 | self.angle += 0.01
68 | self.canvas['my_rotation'] = Matrix().rotate(self.angle, 0, 0, 1)
69 |
70 | class MainWidget(FloatLayout):
71 | def __init__(self, **kwargs):
72 | super().__init__(**kwargs)
73 |
74 | # Add 3D sphere
75 | self.sphere = Sphere3D()
76 | self.add_widget(self.sphere)
77 |
78 | # Set up keyboard
79 | self._keyboard = Window.request_keyboard(self._on_keyboard_closed, self)
80 | self._keyboard.bind(on_key_down=self._on_key_down)
81 |
82 | # FPS counter setup
83 | self.fps = 0
84 | self.frame_count = 0
85 | self.last_time = time.time()
86 |
87 | with self.canvas.after:
88 | self.fps_color = Color(1, 1, 1, 1)
89 | self.fps_rect = Rectangle(pos=(0, 0), size=(0, 0))
90 |
91 | Clock.schedule_interval(self.update_fps, 1.0/60.0)
92 |
93 | def update_fps(self, dt):
94 | self.frame_count += 1
95 | current_time = time.time()
96 | if current_time - self.last_time > 1.0:
97 | self.fps = self.frame_count / (current_time - self.last_time)
98 | self.frame_count = 0
99 | self.last_time = current_time
100 |
101 | # Update FPS display
102 | label = CoreLabel(text=f'FPS: {self.fps:.1f}', font_size=20)
103 | label.refresh()
104 | texture = label.texture
105 | self.fps_rect.pos = (Window.width - texture.width - 10,
106 | Window.height - texture.height - 10)
107 | self.fps_rect.size = texture.size
108 | self.fps_rect.texture = texture
109 |
110 | def _on_keyboard_closed(self):
111 | self._keyboard.unbind(on_key_down=self._on_key_down)
112 | self._keyboard = None
113 |
114 | def _on_key_down(self, keyboard, keycode, text, modifiers):
115 | if keycode[1] == 'q':
116 | App.get_running_app().stop()
117 | return True
118 | return False
119 |
120 | class MyApp(App):
121 | def build(self):
122 | return MainWidget()
123 |
124 | if __name__ == '__main__':
125 | MyApp().run()
126 |
--------------------------------------------------------------------------------
/util/tests/3d/trackball/Digital.tga:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/util/tests/3d/trackball/Digital.tga
--------------------------------------------------------------------------------
/util/tests/3d/trackball/MQ-27-2.obj.mtl:
--------------------------------------------------------------------------------
1 | #
2 | # Wavefront material file
3 | # Converted by Meshlab Group
4 | #
5 |
6 | newmtl material_0
7 | Ka 0.200000 0.200000 0.200000
8 | Kd 0.584314 0.584314 0.584314
9 | Ks 1.000000 1.000000 1.000000
10 | Tr 0.000000
11 | illum 2
12 | Ns 0.000000
13 | map_Kd Tan.tga
14 |
15 | newmtl material_1
16 | Ka 0.200000 0.200000 0.200000
17 | Kd 0.584314 0.584314 0.584314
18 | Ks 1.000000 1.000000 1.000000
19 | Tr 0.000000
20 | illum 2
21 | Ns 0.000000
22 | map_Kd gun.tga
23 |
24 | newmtl material_2
25 | Ka 0.200000 0.200000 0.200000
26 | Kd 0.584314 0.584314 0.584314
27 | Ks 1.000000 1.000000 1.000000
28 | Tr 0.000000
29 | illum 2
30 | Ns 0.000000
31 | map_Kd rotor.tga
32 |
33 |
--------------------------------------------------------------------------------
/util/tests/3d/trackball/MQ-27.mtl:
--------------------------------------------------------------------------------
1 | # 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
2 | # File Created: 16.01.2013 16:31:12
3 |
4 | newmtl Material__0
5 | Ns 10.0000
6 | Ni 1.5000
7 | d 1.0000
8 | Tr 0.0000
9 | Tf 1.0000 1.0000 1.0000
10 | illum 2
11 | Ka 0.5880 0.5880 0.5880
12 | Kd 0.5880 0.5880 0.5880
13 | Ks 0.0000 0.0000 0.0000
14 | Ke 0.0000 0.0000 0.0000
15 | map_Ka Tan.tga
16 | map_Kd Tan.tga
17 |
18 | newmtl Material__1
19 | Ns 10.0000
20 | Ni 1.5000
21 | d 1.0000
22 | Tr 0.0000
23 | Tf 1.0000 1.0000 1.0000
24 | illum 2
25 | Ka 0.5880 0.5880 0.5880
26 | Kd 0.5880 0.5880 0.5880
27 | Ks 0.0000 0.0000 0.0000
28 | Ke 0.0000 0.0000 0.0000
29 | map_Ka gun.tga
30 | map_Kd gun.tga
31 |
32 | newmtl Material__3
33 | Ns 10.0000
34 | Ni 1.5000
35 | d 1.0000
36 | Tr 0.0000
37 | Tf 1.0000 1.0000 1.0000
38 | illum 2
39 | Ka 0.5880 0.5880 0.5880
40 | Kd 0.5880 0.5880 0.5880
41 | Ks 0.0000 0.0000 0.0000
42 | Ke 0.0000 0.0000 0.0000
43 | map_Ka rotor.tga
44 | map_Kd rotor.tga
45 |
--------------------------------------------------------------------------------
/util/tests/3d/trackball/Tan.tga:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/util/tests/3d/trackball/Tan.tga
--------------------------------------------------------------------------------
/util/tests/3d/trackball/gun.tga:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/util/tests/3d/trackball/gun.tga
--------------------------------------------------------------------------------
/util/tests/3d/trackball/gun_normal.tga:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/util/tests/3d/trackball/gun_normal.tga
--------------------------------------------------------------------------------
/util/tests/3d/trackball/gun_spec.tga:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/util/tests/3d/trackball/gun_spec.tga
--------------------------------------------------------------------------------
/util/tests/3d/trackball/mq_normal.tga:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/util/tests/3d/trackball/mq_normal.tga
--------------------------------------------------------------------------------
/util/tests/3d/trackball/mq_spec.tga:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/util/tests/3d/trackball/mq_spec.tga
--------------------------------------------------------------------------------
/util/tests/3d/trackball/rotor.tga:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flyonspeed/TronView/dd6462d3c888e2ae30ee7d0a7fe2c0b4865c7a8c/util/tests/3d/trackball/rotor.tga
--------------------------------------------------------------------------------
/util/tests/3d/trackball/simple.glsl:
--------------------------------------------------------------------------------
1 | ---VERTEX SHADER-------------------------------------------------------
2 | #ifdef GL_ES
3 | precision highp float;
4 | #endif
5 |
6 | attribute vec3 v_pos;
7 | uniform mat4 modelview_mat;
8 | uniform mat4 projection_mat;
9 |
10 | void main (void) {
11 | vec4 pos = projection_mat * modelview_mat * vec4(v_pos, 1.0);
12 | gl_Position = pos;
13 | }
14 |
15 | ---FRAGMENT SHADER-----------------------------------------------------
16 | #ifdef GL_ES
17 | precision highp float;
18 | #endif
19 |
20 | void main (void) {
21 | gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); // White color for wireframe
22 | }
23 |
--------------------------------------------------------------------------------
/util/tests/3d/trackball/sphere_trackball.py:
--------------------------------------------------------------------------------
1 | import os
2 | import math
3 | from kivy.app import App
4 | from kivy.clock import Clock
5 | from kivy.core.window import Window
6 | from kivy3 import Scene, Renderer, PerspectiveCamera
7 | from kivy3.objects.mesh import Mesh
8 | from kivy3.materials import Material
9 | from kivy.uix.floatlayout import FloatLayout
10 |
11 |
12 | class ObjectTrackball(FloatLayout):
13 | def __init__(self, camera, radius, *args, **kw):
14 | super(ObjectTrackball, self).__init__(*args, **kw)
15 | self.camera = camera
16 | self.radius = radius
17 | self.phi = 90
18 | self.theta = 0
19 | self._touches = []
20 | self.camera.pos.z = radius
21 | camera.look_at((0, 0, 0))
22 | self.auto_rotate = False
23 | self._keyboard = Window.request_keyboard(self._on_keyboard_closed, self)
24 | self._keyboard.bind(on_key_down=self._on_key_down)
25 | Clock.schedule_interval(self._update_rotation, 1.0/60.0)
26 |
27 | def _on_keyboard_closed(self):
28 | self._keyboard.unbind(on_key_down=self._on_key_down)
29 | self._keyboard = None
30 |
31 | def _on_key_down(self, keyboard, keycode, text, modifiers):
32 | if keycode[1] == 'a':
33 | self.auto_rotate = not self.auto_rotate
34 | elif keycode[1] == 'q':
35 | App.get_running_app().stop()
36 | elif keycode[1] == 'c':
37 | self.camera.pos = (0, 0, 0)
38 | self.camera.look_at((1, 0, 0)) # Look along X-axis when centered
39 | return True
40 |
41 | def _update_rotation(self, dt):
42 | if self.auto_rotate:
43 | self.theta += 1 # Rotate 1 degree per frame
44 | _phi = math.radians(self.phi)
45 | _theta = math.radians(self.theta)
46 | z = self.radius * math.cos(_theta) * math.sin(_phi)
47 | x = self.radius * math.sin(_theta) * math.sin(_phi)
48 | y = self.radius * math.cos(_phi)
49 | self.camera.pos = x, y, z
50 | self.camera.look_at((0, 0, 0))
51 |
52 | def define_rotate_angle(self, touch):
53 | theta_angle = (touch.dx / self.width) * -360
54 | phi_angle = -1 * (touch.dy / self.height) * 360
55 | return phi_angle, theta_angle
56 |
57 | def on_touch_down(self, touch):
58 | touch.grab(self)
59 | self._touches.append(touch)
60 |
61 | def on_touch_up(self, touch):
62 | touch.ungrab(self)
63 | self._touches.remove(touch)
64 |
65 | def on_touch_move(self, touch):
66 | if touch in self._touches and touch.grab_current == self:
67 | if len(self._touches) == 1:
68 | self.do_rotate(touch)
69 |
70 | def do_rotate(self, touch):
71 | d_phi, d_theta = self.define_rotate_angle(touch)
72 | self.phi += d_phi
73 | self.theta += d_theta
74 | _phi = math.radians(self.phi)
75 | _theta = math.radians(self.theta)
76 | z = self.radius * math.cos(_theta) * math.sin(_phi)
77 | x = self.radius * math.sin(_theta) * math.sin(_phi)
78 | y = self.radius * math.cos(_phi)
79 | self.camera.pos = x, y, z
80 | self.camera.look_at((0, 0, 0))
81 |
82 |
83 | def create_wireframe_sphere(radius=1.0, segments=20):
84 | vertices = []
85 | indices = []
86 |
87 | # Create vertices
88 | for i in range(segments + 1):
89 | lat = math.pi * (-0.5 + float(i) / segments)
90 | for j in range(segments + 1):
91 | lon = 2 * math.pi * float(j) / segments
92 | x = math.cos(lat) * math.cos(lon)
93 | y = math.cos(lat) * math.sin(lon)
94 | z = math.sin(lat)
95 | vertices.extend([x * radius, y * radius, z * radius])
96 |
97 | # Create indices for wireframe
98 | for i in range(segments):
99 | for j in range(segments):
100 | current = i * (segments + 1) + j
101 | next_row = (i + 1) * (segments + 1) + j
102 |
103 | # Horizontal lines
104 | indices.extend([current, current + 1])
105 | # Vertical lines
106 | indices.extend([current, next_row])
107 |
108 | return vertices, indices
109 |
110 |
111 | class MainApp(App):
112 | def build(self):
113 | scene = Scene()
114 | camera = PerspectiveCamera(15, 1, 1, 1000)
115 |
116 | # Create renderer
117 | shader_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "simple.glsl")
118 | renderer = Renderer(shader_file=shader_path)
119 | renderer.set_camera(camera)
120 |
121 | # Create sphere mesh
122 | vertices, indices = create_wireframe_sphere(radius=3.0, segments=20)
123 | material = Material(color=(1, 1, 1))
124 | sphere_mesh = Mesh(vertices=vertices, indices=indices, material=material)
125 | scene.add(sphere_mesh)
126 |
127 | # Create trackball for camera
128 | trackball = ObjectTrackball(camera=camera, radius=10.0, size=renderer.size)
129 |
130 | root = FloatLayout()
131 | root.add_widget(renderer)
132 | root.add_widget(trackball)
133 |
134 | renderer.render(scene)
135 | return root
136 |
137 | def _adjust_aspect(self, inst, val):
138 | rsize = self.renderer.size
139 | aspect = rsize[0] / float(rsize[1])
140 | self.renderer.camera.aspect = aspect
141 |
142 |
143 | if __name__ == '__main__':
144 | MainApp().run()
145 |
--------------------------------------------------------------------------------
/util/tests/3d/trackball/test1.mtl:
--------------------------------------------------------------------------------
1 | newmtl default
2 | # Ambient color - bluish
3 | Ka 0.0 0.1 0.2
4 |
5 | # Diffuse color - metallic blue
6 | Kd 0.1 0.4 0.8
7 |
8 | # Specular color - bright white for shininess
9 | Ks 1.0 1.0 1.0
10 |
11 | # Specular exponent - high for metallic look
12 | Ns 90.0
13 |
14 | # Transparency - fully opaque
15 | d 1.0
16 |
17 | # Optical density (index of refraction)
18 | Ni 1.45
19 |
20 | # Illumination model: 3 = highlights on and ray trace on
21 | illum 3
22 |
23 | # Adding slight emission for glow effect
24 | Ke 0.05 0.05 0.1
25 |
--------------------------------------------------------------------------------
/util/tests/3d/trackball/test1.obj:
--------------------------------------------------------------------------------
1 | mtllib test1.mtl
2 | usemtl default
3 | # OpenSCAD obj exporter
4 | v 13.2798 18 91.6094
5 | v 13.2798 24 91.6094
6 | v 12.3442 24 89.9889
7 | v 12.3442 18 89.9889
8 | v 13.4753 18 93.4704
9 | v 13.4753 24 93.4704
10 | v 10.8303 18 88.889
11 | v 10.8303 24 88.889
12 | v 9 24 88.5
13 | v 9 18 88.5
14 | v 11.645 24 96.6406
15 | v 12.8971 24 95.25
16 | v 12.8971 18 95.25
17 | v 11.645 18 96.6406
18 | v 9.9356 24 97.4017
19 | v 9.9356 18 97.4017
20 | v 9 24 97.4017
21 | v 9 18 97.4017
22 | v 222 0 9
23 | v 222 30 9
24 | v 0 30 9
25 | v 0 0 9
26 | v 9 24 9
27 | v 29 24 9
28 | v 29 18 9
29 | v 9 18 9
30 | v 93 24 9
31 | v 113 24 9
32 | v 113 18 9
33 | v 93 18 9
34 | v 29 18 113
35 | v 29 24 113
36 | v 9 18 113
37 | v 9 24 113
38 | v 108.72 24 91.6094
39 | v 108.72 18 91.6094
40 | v 109.656 18 89.9889
41 | v 109.656 24 89.9889
42 | v 108.525 24 93.4704
43 | v 108.525 18 93.4704
44 | v 111.17 24 88.889
45 | v 111.17 18 88.889
46 | v 113 18 88.5
47 | v 113 24 88.5
48 | v 110.355 18 96.6406
49 | v 109.103 18 95.25
50 | v 109.103 24 95.25
51 | v 110.355 24 96.6406
52 | v 112.064 18 97.4017
53 | v 112.064 24 97.4017
54 | v 113 18 97.4017
55 | v 113 24 97.4017
56 | v 93 18 113
57 | v 93 24 113
58 | v 113 24 113
59 | v 113 18 113
60 | v 222 0 0
61 | v 222 30 0
62 | v 0 0 0
63 | v 0 30 0
64 | f 4 2 3
65 | f 2 4 1
66 | f 1 6 2
67 | f 6 1 5
68 | f 9 7 8
69 | f 7 9 10
70 | f 13 11 12
71 | f 11 13 14
72 | f 8 4 3
73 | f 4 8 7
74 | f 5 12 6
75 | f 12 5 13
76 | f 16 11 14
77 | f 11 16 15
78 | f 18 15 16
79 | f 15 18 17
80 | f 29 20 28
81 | f 20 27 28
82 | f 27 24 30
83 | f 21 27 20
84 | f 27 21 24
85 | f 23 21 26
86 | f 24 21 23
87 | f 20 29 19
88 | f 22 29 30
89 | f 25 30 24
90 | f 22 30 25
91 | f 22 25 26
92 | f 29 22 19
93 | f 22 26 21
94 | f 31 24 32
95 | f 24 31 25
96 | f 26 9 23
97 | f 9 26 10
98 | f 18 34 17
99 | f 34 18 33
100 | f 34 31 32
101 | f 31 34 33
102 | f 11 34 32
103 | f 34 11 15
104 | f 32 12 11
105 | f 32 6 12
106 | f 32 2 6
107 | f 24 2 32
108 | f 2 24 3
109 | f 24 8 3
110 | f 23 8 24
111 | f 8 23 9
112 | f 34 15 17
113 | f 16 33 18
114 | f 14 33 16
115 | f 33 14 31
116 | f 13 31 14
117 | f 5 31 13
118 | f 1 31 5
119 | f 25 1 4
120 | f 26 7 10
121 | f 25 7 26
122 | f 7 25 4
123 | f 1 25 31
124 | f 36 38 35
125 | f 38 36 37
126 | f 40 35 39
127 | f 35 40 36
128 | f 41 43 44
129 | f 43 41 42
130 | f 45 47 48
131 | f 47 45 46
132 | f 38 42 41
133 | f 42 38 37
134 | f 46 39 47
135 | f 39 46 40
136 | f 45 50 49
137 | f 50 45 48
138 | f 49 52 51
139 | f 52 49 50
140 | f 30 54 27
141 | f 54 30 53
142 | f 43 28 44
143 | f 28 43 29
144 | f 56 52 55
145 | f 52 56 51
146 | f 54 56 55
147 | f 56 54 53
148 | f 50 55 52
149 | f 48 55 50
150 | f 55 48 54
151 | f 47 54 48
152 | f 39 54 47
153 | f 35 54 39
154 | f 27 35 38
155 | f 28 41 44
156 | f 27 41 28
157 | f 41 27 38
158 | f 35 27 54
159 | f 45 56 53
160 | f 56 45 49
161 | f 53 46 45
162 | f 53 40 46
163 | f 53 36 40
164 | f 30 36 53
165 | f 36 30 37
166 | f 30 42 37
167 | f 29 42 30
168 | f 42 29 43
169 | f 56 49 51
170 | f 19 58 20
171 | f 58 19 57
172 | f 59 58 57
173 | f 58 59 60
174 | f 59 21 60
175 | f 21 59 22
176 | f 58 21 20
177 | f 21 58 60
178 | f 59 19 22
179 | f 19 59 57
180 |
--------------------------------------------------------------------------------
/util/tests/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Kivy install on mac:
4 |
5 | python -m pip install "kivy[base]" --pre --extra-index-url https://kivy.org/downloads/simple/
--------------------------------------------------------------------------------
/util/tests/i2c_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # i2c test
4 |
5 | import time
6 | import math
7 | import binascii
8 | import traceback
9 | import board
10 | import busio
11 |
12 | # Normal I2C initialization
13 | i2c = busio.I2C(board.SCL, board.SDA)
14 |
15 | # scan for devices
16 | devices = i2c.scan()
17 | print("Devices addresses found:", devices)
18 |
19 | # 40 is bno055
20 | if 40 in devices:
21 | print("BNO055 (1st imu gyro) found (hex: 0x{:02X} decimal: {:02})".format(40, 40))
22 |
23 | # 41 is 2nd bno055
24 | if 41 in devices:
25 | print("BNO055 (2nd imu gyro) found (hex: 0x{:02X} decimal: {:02})".format(41, 41))
26 |
27 | # 64 is INA3221 (current sensor)
28 | if 64 in devices:
29 | print("INA3221 (current sensor) found (hex: 0x{:02X} decimal: {:02})".format(64, 64))
30 |
31 | # check if 72. (ads1115) analog voltage sensor
32 | if 72 in devices:
33 | print("ADS1115 (analog voltage sensor) found (hex: 0x{:02X} decimal: {:02})".format(72, 72))
34 |
35 | # 74 is bno085
36 | if 74 in devices:
37 | print("BNO085 (1st imu gyro) found (hex: 0x{:02X} decimal: {:02})".format(74, 74))
38 |
39 | # 75 is 2nd bno085
40 | if 75 in devices:
41 | print("BNO085 (2nd imu gyro) found (hex: 0x{:02X} decimal: {:02})".format(75, 75))
42 |
43 | # 92 LPS28
44 | if 92 in devices:
45 | print("LPS28 (pressure sensor) found (hex: 0x{:02X} decimal: {:02})".format(92, 92))
46 |
47 | # 93 LSP22 (pressure sensor)
48 | if 93 in devices:
49 | print("LSP22 (pressure sensor) found (hex: 0x{:02X} decimal: {:02})".format(93, 93))
50 |
51 | # 104 is mcp3421
52 | if 104 in devices:
53 | print("MCP3421 (voltage sensor) found (hex: 0x{:02X} decimal: {:02})".format(104, 104))
54 |
55 |
--------------------------------------------------------------------------------
/util/tests/serial_raw.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 |
4 | import time
5 | import serial
6 | import struct
7 | import sys
8 | import os, getopt, subprocess, platform
9 |
10 | version = "0.1"
11 |
12 | ser = None
13 | counter = 0
14 |
15 | def list_serial_ports(printthem):
16 |
17 | # List all the Serial COM Ports on Raspberry Pi
18 | proc = subprocess.Popen(['ls /dev/tty[A-Za-z]*'], shell=True, stdout=subprocess.PIPE)
19 | com_ports = proc.communicate()[0]
20 | com_ports_list = str(com_ports).split("\\n") # find serial ports
21 | rtn = []
22 | for com_port in com_ports_list:
23 | if 'ttyS' in com_port:
24 | if(printthem==True): print("found serial port: "+com_port)
25 | rtn.append(com_port)
26 | if 'ttyUSB' in com_port:
27 | if(printthem==True): print("found USB serial port: "+com_port)
28 | rtn.append(com_port)
29 | return rtn
30 |
31 |
32 | # print at location
33 | def print_xy(x, y, text):
34 | sys.stdout.write("\x1b7\x1b[%d;%df%s\x1b8" % (x, y, text))
35 | sys.stdout.flush()
36 |
37 |
38 | def readMessage():
39 | global ser
40 | try:
41 | t = ser.read(1)
42 | if(len(t)>0):
43 | if int(t[0]) < 32: # if its binary then convert it to string first.
44 | x = str(t)
45 | else:
46 | x = t
47 |
48 | print(x, end=" ")
49 |
50 | except serial.serialutil.SerialException:
51 | print("exception")
52 |
53 | if sys.platform.startswith('win'):
54 | os.system('cls') # on windows
55 | else:
56 | os.system("clear") # on Linux / os X
57 | argv = sys.argv[1:]
58 | showBin = 0
59 | port = "/dev/ttyS0" # default serial port
60 | backup_port = "/dev/ttyUSB0"
61 | try:
62 | opts, args = getopt.getopt(argv, "hbi:l", ["bin="])
63 | except getopt.GetoptError:
64 | print("raw_serial.py -b")
65 | sys.exit(2)
66 | for opt, arg in opts:
67 | if opt == "-h":
68 | print("raw_serial.py [-i ] -l")
69 | print(" -l (list serial ports found)")
70 | print(" -i select input serial port")
71 | sys.exit()
72 | if opt == "-i":
73 | port=arg
74 | if opt == "-l":
75 | list_serial_ports(True)
76 | sys.exit()
77 |
78 | try:
79 | ser=serial.Serial(
80 | port=port,
81 | baudrate=115200,
82 | parity=serial.PARITY_NONE,
83 | stopbits=serial.STOPBITS_ONE,
84 | bytesize=serial.EIGHTBITS,
85 | timeout=1,
86 | )
87 |
88 | except:
89 | print(f"Unable to open primary serial port: {port}")
90 | print(f"Trying backup port: {backup_port}")
91 | try:
92 | ser = serial.Serial(
93 | port=backup_port,
94 | baudrate=115200,
95 | parity=serial.PARITY_NONE,
96 | stopbits=serial.STOPBITS_ONE,
97 | bytesize=serial.EIGHTBITS,
98 | timeout=1,
99 | )
100 | print(f"Successfully opened port: {backup_port}")
101 | port = backup_port
102 | except:
103 | print(f"Unable to open port: {backup_port}")
104 | print("Try passing in port to command line with -i ")
105 | print("Here is a list of ports found:")
106 | list_serial_ports(True)
107 | sys.exit()
108 |
109 | print(f"Opened port: {port} @115200 baud (cntrl-c to quit)")
110 | while 1:
111 | readMessage()
112 |
113 |
--------------------------------------------------------------------------------
/util/tests/test_stratux_wifi.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # check if nc is installed
3 | if ! command -v nc &> /dev/null
4 | then
5 | # install nc depending on OS
6 | if [[ "$OSTYPE" == "darwin"* ]]; then
7 | brew install nc
8 | else
9 | sudo apt-get install -y netcat-traditional
10 | fi
11 | fi
12 |
13 | # check if hd is installed
14 | if ! command -v hd &> /dev/null
15 | then
16 | if [[ "$OSTYPE" == "darwin"* ]]; then
17 | # don't know how to install hd on macos
18 | echo "Running on macos"
19 | else
20 | # todo: install hd??
21 | echo "Running on linux"
22 | fi
23 | fi
24 |
25 | echo "-=-=- Test stratux or iLevil connection -=-=-"
26 | echo "For UDP, If hex data shows then its connected. Then hit cntrl-c to exit"
27 | echo "Press u for iLevil UDP port 43211"
28 | echo "Press t for iLevil TCP port 2000"
29 | echo "Press s for stratux UDP port 4000"
30 |
31 | # Get a single character input without waiting for return
32 | get_char() {
33 | # Save current terminal settings
34 | old_tty=$(stty -g)
35 | # Set terminal to raw mode
36 | stty raw -echo
37 | # Read single character
38 | char=$(dd if=/dev/tty bs=1 count=1 2>/dev/null)
39 | # Restore terminal settings
40 | stty "$old_tty"
41 | # Output the character
42 | echo "$char"
43 | }
44 |
45 | # Get input and process it
46 | char=$(get_char | tr '[:upper:]' '[:lower:]')
47 | echo "----------------------------------------"
48 |
49 | case $char in
50 | [Uu]* )echo "Listening for iLevil UDP port 43211"
51 | if [[ "$OSTYPE" == "darwin"* ]]; then
52 | nc -lu 43211 | hexdump -C
53 | else
54 | nc -u -l -p 43211 -k | hd
55 | fi
56 | ;;
57 | [Tt]* )echo "Listening for iLevil 192.168.1.1 TCP port 2000"
58 | if [[ "$OSTYPE" == "darwin"* ]]; then
59 | nc -zv 192.168.1.1 2000 | hexdump -C
60 | else
61 | nc -zv 192.168.1.1 2000 | hd
62 | fi
63 | ;;
64 | [Ss]* )echo "Listening for Stratux UDP port 4000"
65 | if [[ "$OSTYPE" == "darwin"* ]]; then
66 | nc -lu 4000 | hexdump -C
67 | else
68 | nc -u -l -p 4000 -k | hd
69 | fi
70 | ;;
71 | *) echo "Later skater!"
72 | esac
73 |
74 |
--------------------------------------------------------------------------------
/util/version-select.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Function to extract current version from lib/version.py
4 | get_current_version() {
5 | version=$(awk -F\" '/^__version__/ {print $2}' lib/version.py)
6 | echo $version
7 | }
8 |
9 | # Function to update version in version.py
10 | update_version() {
11 | old_version=$(get_current_version)
12 | new_version=$1
13 | current_date=$(date +%Y-%m-%d)
14 | current_time="$(date +%H:%M:%S) $(date +%Z)"
15 | # Check if running on macOS
16 | if [[ "$OSTYPE" == "darwin"* ]]; then
17 | # macOS requires an extension with -i flag
18 | sed -i '' "s/^__version__ = \"$old_version\"/__version__ = \"$new_version\"/" lib/version.py
19 | # find line containing __build_date__ and replace it with the new date and time
20 | sed -i '' "s/^__build_date__ = .*/__build_date__ = \"$current_date\"/" lib/version.py
21 | sed -i '' "s/^__build_time__ = .*/__build_time__ = \"$current_time\"/" lib/version.py
22 | else
23 | # Linux version
24 | sed -i "s/^__version__ = \"$old_version\"/__version__ = \"$new_version\"/" lib/version.py
25 | # find line containing __build_date__ and replace it with the new date and time
26 | sed -i "s/^__build_date__ = .*/__build_date__ = \"$current_date\"/" lib/version.py
27 | # find line containing __build_time__ and replace it with the new date and time
28 | sed -i "s/^__build_time__ = .*/__build_time__ = \"$current_time\"/" lib/version.py
29 | fi
30 | if [ $? -ne 0 ]; then
31 | echo "Failed to update version.py version. Exiting."
32 | exit 1
33 | fi
34 |
35 | }
36 |
37 | # Function to update CHANGELOG.md
38 | update_changelog() {
39 | new_version=$1
40 | changelog_entries=$2
41 | current_date=$(date +%Y-%m-%d)
42 |
43 | # Create temporary file with new content
44 | echo -e "# Changelog\n" > temp_changelog
45 | echo -e "## [$new_version] - $current_date" >> temp_changelog
46 | #echo -e "### Added" >> temp_changelog
47 | # add empty line
48 | echo "" >> temp_changelog
49 | echo -e "$changelog_entries\n" >> temp_changelog
50 | # Skip the first line (# Changelog) from the existing file
51 | tail -n +2 CHANGELOG.md >> temp_changelog
52 | mv temp_changelog CHANGELOG.md
53 | }
54 |
55 | # Function to increment version
56 | increment_version() {
57 | current=$1
58 | type=$2
59 |
60 | IFS='.' read -ra ADDR <<< "$current"
61 | major="${ADDR[0]}"
62 | minor="${ADDR[1]}"
63 | patch="${ADDR[2]}"
64 |
65 | case $type in
66 | "major")
67 | major=$((major + 1))
68 | minor=0
69 | patch=0
70 | ;;
71 | "minor")
72 | minor=$((minor + 1))
73 | patch=0
74 | ;;
75 | "patch")
76 | patch=$((patch + 1))
77 | ;;
78 | esac
79 |
80 | echo "$major.$minor.$patch"
81 | }
82 |
83 | # Main script
84 | current_version=$(get_current_version)
85 | echo "Current version: $current_version"
86 | echo "Select version update type:"
87 | echo "1) Major (x.0.0)"
88 | echo "2) Minor (0.x.0)"
89 | echo "3) Patch (0.0.x)"
90 | read -p "Enter choice (1-3): " choice
91 |
92 | case $choice in
93 | 1)
94 | new_version=$(increment_version "$current_version" "major")
95 | ;;
96 | 2)
97 | new_version=$(increment_version "$current_version" "minor")
98 | ;;
99 | 3)
100 | new_version=$(increment_version "$current_version" "patch")
101 | ;;
102 | *)
103 | echo "No choice selected. Exiting."
104 | exit 1
105 | ;;
106 | esac
107 |
108 | echo "Updating to version: $new_version"
109 |
110 | # Collect changelog entries
111 | echo "Enter changelog entries (press Enter with no input when done):"
112 | changelog_entries=""
113 | while true; do
114 | read -p "- " entry
115 | if [ -z "$entry" ]; then
116 | break
117 | fi
118 | changelog_entries+="- $entry\n"
119 | done
120 |
121 | # Update all files
122 | update_version "$new_version"
123 | update_changelog "$new_version" "$changelog_entries"
124 |
125 | echo "Version updated successfully!"
126 | echo "- version.py: $(get_current_version)"
127 | echo "- CHANGELOG.md updated with new entries"
--------------------------------------------------------------------------------