├── .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 | logo 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 | ![cockpit1](docs/efis_cockpit1.jpeg?raw=true) 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 | ![3d_mesh](docs/screenshots/tv_mesh.png?raw=true) 45 | 46 | ## 3d synthetic world (work in progress) 47 | 48 | ![3d_world](docs/screenshots/tv3d.png?raw=true) 49 | 50 | ## Use as backup display screen on dash 51 | 52 | ![cockpit2](docs/efis_cockpit2.jpeg?raw=true) 53 | 54 | ## Editor Screenshot 55 | ![screenshot1](docs/screenshots/screenshot_2_editor.png?raw=true) 56 | 57 | ## F18 Style HUD 58 | ![rv8Hud](docs/efis_HUD_rv8.jpg?raw=true) 59 | 60 | ## Text Mode 61 | ![hud_animation](docs/efis_screenshot_text.png?raw=true) 62 | 63 | ## Engine/System Display in Classic Pinzgauer Swiss Military Vehicle 64 | ![pinzgauer](docs/imgs/pinz1.jpeg?raw=true) 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 | ![cockpit1](screenshots/pi_imager1.png?raw=true) 9 | 10 | 11 | 3) Choose Raspberry Pi OS (64-bit) Debian Bookworm. (desktop) 12 | 13 | ![cockpit1](screenshots/pi_imager2.png?raw=true) 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 | ![cockpit1](screenshots/pi_imager3.png?raw=true) 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" --------------------------------------------------------------------------------