├── .gitignore ├── README.md ├── assets ├── 0Up.jpg ├── 1Up.jpg ├── 2Up.jpg ├── 3Up.jpg ├── 4Up.jpg ├── 5Up.jpg ├── 6Up.jpg ├── 7Seg-0.jpg ├── 7Seg-1.jpg ├── 7Seg-2.jpg ├── 7Seg-3.jpg ├── 7Seg-4.jpg ├── 7Seg-5.jpg ├── 7Seg-6.jpg ├── 7Seg-7.jpg ├── 7Seg-8.jpg ├── 7Seg-9.jpg ├── 7SegOff.jpg ├── 7Up.jpg ├── 8Up.jpg ├── 9Up.jpg ├── AltOff.jpg ├── AltOn.jpg ├── ApolloPatch2.png ├── AutoOff.jpg ├── AutoOn.jpg ├── AutoOnO.jpg ├── BlankOff.jpg ├── BlankOn.jpg ├── BlankOnO.jpg ├── CenterBlock.jpg ├── ClrUp.jpg ├── CompActyOff.jpg ├── CompActyOn.jpg ├── EntrUp.jpg ├── FrameHorizontal.jpg ├── FrameMiddle.jpg ├── FrameVertical.jpg ├── FrameVerticalL.jpg ├── FrameVerticalR.jpg ├── FreeOff.jpg ├── FreeOn.jpg ├── FreeOnO.jpg ├── GimbalLockOff.jpg ├── GimbalLockOn.jpg ├── KeyRelOff.jpg ├── KeyRelOn.jpg ├── KeyRelUp.jpg ├── MinusOn.jpg ├── MinusUp.jpg ├── NoAttOff.jpg ├── NoAttOn.jpg ├── NoAttOnO.jpg ├── NoDapOff.jpg ├── NoDapOn.jpg ├── NoDapOnO.jpg ├── NounOff.jpg ├── NounOn.jpg ├── NounUp.jpg ├── OprErrOff.jpg ├── OprErrOn.jpg ├── OprErrOnO.jpg ├── PlusMinusOff.jpg ├── PlusOn.jpg ├── PlusUp.jpg ├── PrioDispOff.jpg ├── PrioDispOn.jpg ├── PrioDispOnO.jpg ├── ProUp.jpg ├── ProgOff.jpg ├── ProgOn.jpg ├── ProgUp.jpg ├── RestartOff.jpg ├── RestartOn.jpg ├── RsetUp.jpg ├── Separator.jpg ├── SeparatorOn.jpg ├── ShortHorizontal.jpg ├── StbyOff.jpg ├── StbyOn.jpg ├── StbyOnO.jpg ├── StbyUp.jpg ├── TempOff.jpg ├── TempOn.jpg ├── TrackerOff.jpg ├── TrackerOn.jpg ├── UplinkActyOff.jpg ├── UplinkActyOn.jpg ├── UplinkActyOnO.jpg ├── VelOff.jpg ├── VelOn.jpg ├── VerbOff.jpg ├── VerbOn.jpg ├── VerbUp.jpg ├── icon.png ├── rProgOff.jpg └── rProgOn.jpg ├── basagc.py ├── basagc ├── __init__.py ├── computer.py ├── config.py ├── dsky.py ├── gui.py ├── imu.py ├── maneuver.py ├── nouns.py ├── programs.py ├── routines.py ├── telemachus.py ├── uplink.txt ├── utils.py └── verbs.py ├── doc ├── changelog.md ├── checklists │ ├── Launch Checklist.md │ └── TMI Calc.md ├── credits.md ├── installation.md ├── known_issues.md ├── manual.md └── todo.md ├── licence └── ui ├── basaGC.ui ├── basaGC_dialog_info_display.ui ├── basaGC_dialog_log.ui └── basaGC_dialog_settings.ui /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .idea/ 3 | *.nja 4 | *.pyc 5 | doc/api_list.txt 6 | scripts/ 7 | wxglade_output/ 8 | doc/notes 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | basaGC: Apollo Guidance Computer for Kerbal Space Program. 2 | ====== 3 | 4 | This is my reimplementation of the [Apollo Guidance Computer](https://en.wikipedia.org/wiki/Apollo_Guidance_Computer) 5 | (AGC) and Display/Keyboard (DSKY) for Kerbal Space Program. 6 | 7 | Screenshots here: http://imgur.com/a/Tj5mJ 8 | 9 | Prerequisites: 10 | --- 11 | 12 | - KSP (tested on 1.0.5), should work on any version that Telemachus works on 13 | - [Python 3.4](https://www.python.org/downloads/release/python-344/) 14 | - [PyQt5](https://www.riverbankcomputing.com/software/pyqt/download5) 15 | - [Intrepid mod](https://github.com/tcannonfodder/Telemachus/releases/tag/v1.4.32.0) (fork of Telemachus mod) for KSP 16 | 1.0.5 (for KSP 1.1, try [this release](https://github.com/tcannonfodder/Telemachus/releases/tag/v1.5.32.1)) 17 | - [Mechjeb mod](https://github.com/MuMech/MechJeb2) 18 | 19 | Installation: 20 | --- 21 | See [doc/installation.md](https://github.com/cashelcomputers/basaGC/blob/master/doc/installation.md) 22 | 23 | Running basaGC: 24 | ----- 25 | 26 | To run basaGC, unzip the download to a folder of your choice. On Linux, in a terminal change to 27 | that directory and type "./basagc.py", on Windows double-click on the file basagc.py. 28 | 29 | 30 | 31 | Please Note! This is a work in progress. Only a few functions of the AGC are implemented. Some buttons and warning 32 | lamps don't work. 33 | 34 | Historical note: I have attempted to follow, as closely as possible and with the documentation available to me, the 35 | real life AGC. It should be noted that this is a superficial recreation, not a hard-core simulation of the AGC. Google 36 | "Virtual AGC" for that! 37 | 38 | Known issues: 39 | ------------ 40 | - see [doc/known_issues.md](https://github.com/cashelcomputers/basaGC/blob/master/doc/known_issues.md) 41 | 42 | 43 | 44 | *** 45 | Proudly bought to you by Buchanan and Son Avionics (a division of Buchanan and Son Aerospace). 46 | -------------------------------------------------------------------------------- /assets/0Up.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/0Up.jpg -------------------------------------------------------------------------------- /assets/1Up.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/1Up.jpg -------------------------------------------------------------------------------- /assets/2Up.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/2Up.jpg -------------------------------------------------------------------------------- /assets/3Up.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/3Up.jpg -------------------------------------------------------------------------------- /assets/4Up.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/4Up.jpg -------------------------------------------------------------------------------- /assets/5Up.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/5Up.jpg -------------------------------------------------------------------------------- /assets/6Up.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/6Up.jpg -------------------------------------------------------------------------------- /assets/7Seg-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/7Seg-0.jpg -------------------------------------------------------------------------------- /assets/7Seg-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/7Seg-1.jpg -------------------------------------------------------------------------------- /assets/7Seg-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/7Seg-2.jpg -------------------------------------------------------------------------------- /assets/7Seg-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/7Seg-3.jpg -------------------------------------------------------------------------------- /assets/7Seg-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/7Seg-4.jpg -------------------------------------------------------------------------------- /assets/7Seg-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/7Seg-5.jpg -------------------------------------------------------------------------------- /assets/7Seg-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/7Seg-6.jpg -------------------------------------------------------------------------------- /assets/7Seg-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/7Seg-7.jpg -------------------------------------------------------------------------------- /assets/7Seg-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/7Seg-8.jpg -------------------------------------------------------------------------------- /assets/7Seg-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/7Seg-9.jpg -------------------------------------------------------------------------------- /assets/7SegOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/7SegOff.jpg -------------------------------------------------------------------------------- /assets/7Up.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/7Up.jpg -------------------------------------------------------------------------------- /assets/8Up.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/8Up.jpg -------------------------------------------------------------------------------- /assets/9Up.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/9Up.jpg -------------------------------------------------------------------------------- /assets/AltOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/AltOff.jpg -------------------------------------------------------------------------------- /assets/AltOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/AltOn.jpg -------------------------------------------------------------------------------- /assets/ApolloPatch2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/ApolloPatch2.png -------------------------------------------------------------------------------- /assets/AutoOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/AutoOff.jpg -------------------------------------------------------------------------------- /assets/AutoOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/AutoOn.jpg -------------------------------------------------------------------------------- /assets/AutoOnO.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/AutoOnO.jpg -------------------------------------------------------------------------------- /assets/BlankOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/BlankOff.jpg -------------------------------------------------------------------------------- /assets/BlankOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/BlankOn.jpg -------------------------------------------------------------------------------- /assets/BlankOnO.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/BlankOnO.jpg -------------------------------------------------------------------------------- /assets/CenterBlock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/CenterBlock.jpg -------------------------------------------------------------------------------- /assets/ClrUp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/ClrUp.jpg -------------------------------------------------------------------------------- /assets/CompActyOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/CompActyOff.jpg -------------------------------------------------------------------------------- /assets/CompActyOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/CompActyOn.jpg -------------------------------------------------------------------------------- /assets/EntrUp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/EntrUp.jpg -------------------------------------------------------------------------------- /assets/FrameHorizontal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/FrameHorizontal.jpg -------------------------------------------------------------------------------- /assets/FrameMiddle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/FrameMiddle.jpg -------------------------------------------------------------------------------- /assets/FrameVertical.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/FrameVertical.jpg -------------------------------------------------------------------------------- /assets/FrameVerticalL.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/FrameVerticalL.jpg -------------------------------------------------------------------------------- /assets/FrameVerticalR.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/FrameVerticalR.jpg -------------------------------------------------------------------------------- /assets/FreeOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/FreeOff.jpg -------------------------------------------------------------------------------- /assets/FreeOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/FreeOn.jpg -------------------------------------------------------------------------------- /assets/FreeOnO.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/FreeOnO.jpg -------------------------------------------------------------------------------- /assets/GimbalLockOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/GimbalLockOff.jpg -------------------------------------------------------------------------------- /assets/GimbalLockOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/GimbalLockOn.jpg -------------------------------------------------------------------------------- /assets/KeyRelOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/KeyRelOff.jpg -------------------------------------------------------------------------------- /assets/KeyRelOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/KeyRelOn.jpg -------------------------------------------------------------------------------- /assets/KeyRelUp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/KeyRelUp.jpg -------------------------------------------------------------------------------- /assets/MinusOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/MinusOn.jpg -------------------------------------------------------------------------------- /assets/MinusUp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/MinusUp.jpg -------------------------------------------------------------------------------- /assets/NoAttOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/NoAttOff.jpg -------------------------------------------------------------------------------- /assets/NoAttOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/NoAttOn.jpg -------------------------------------------------------------------------------- /assets/NoAttOnO.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/NoAttOnO.jpg -------------------------------------------------------------------------------- /assets/NoDapOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/NoDapOff.jpg -------------------------------------------------------------------------------- /assets/NoDapOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/NoDapOn.jpg -------------------------------------------------------------------------------- /assets/NoDapOnO.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/NoDapOnO.jpg -------------------------------------------------------------------------------- /assets/NounOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/NounOff.jpg -------------------------------------------------------------------------------- /assets/NounOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/NounOn.jpg -------------------------------------------------------------------------------- /assets/NounUp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/NounUp.jpg -------------------------------------------------------------------------------- /assets/OprErrOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/OprErrOff.jpg -------------------------------------------------------------------------------- /assets/OprErrOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/OprErrOn.jpg -------------------------------------------------------------------------------- /assets/OprErrOnO.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/OprErrOnO.jpg -------------------------------------------------------------------------------- /assets/PlusMinusOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/PlusMinusOff.jpg -------------------------------------------------------------------------------- /assets/PlusOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/PlusOn.jpg -------------------------------------------------------------------------------- /assets/PlusUp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/PlusUp.jpg -------------------------------------------------------------------------------- /assets/PrioDispOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/PrioDispOff.jpg -------------------------------------------------------------------------------- /assets/PrioDispOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/PrioDispOn.jpg -------------------------------------------------------------------------------- /assets/PrioDispOnO.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/PrioDispOnO.jpg -------------------------------------------------------------------------------- /assets/ProUp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/ProUp.jpg -------------------------------------------------------------------------------- /assets/ProgOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/ProgOff.jpg -------------------------------------------------------------------------------- /assets/ProgOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/ProgOn.jpg -------------------------------------------------------------------------------- /assets/ProgUp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/ProgUp.jpg -------------------------------------------------------------------------------- /assets/RestartOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/RestartOff.jpg -------------------------------------------------------------------------------- /assets/RestartOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/RestartOn.jpg -------------------------------------------------------------------------------- /assets/RsetUp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/RsetUp.jpg -------------------------------------------------------------------------------- /assets/Separator.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/Separator.jpg -------------------------------------------------------------------------------- /assets/SeparatorOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/SeparatorOn.jpg -------------------------------------------------------------------------------- /assets/ShortHorizontal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/ShortHorizontal.jpg -------------------------------------------------------------------------------- /assets/StbyOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/StbyOff.jpg -------------------------------------------------------------------------------- /assets/StbyOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/StbyOn.jpg -------------------------------------------------------------------------------- /assets/StbyOnO.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/StbyOnO.jpg -------------------------------------------------------------------------------- /assets/StbyUp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/StbyUp.jpg -------------------------------------------------------------------------------- /assets/TempOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/TempOff.jpg -------------------------------------------------------------------------------- /assets/TempOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/TempOn.jpg -------------------------------------------------------------------------------- /assets/TrackerOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/TrackerOff.jpg -------------------------------------------------------------------------------- /assets/TrackerOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/TrackerOn.jpg -------------------------------------------------------------------------------- /assets/UplinkActyOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/UplinkActyOff.jpg -------------------------------------------------------------------------------- /assets/UplinkActyOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/UplinkActyOn.jpg -------------------------------------------------------------------------------- /assets/UplinkActyOnO.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/UplinkActyOnO.jpg -------------------------------------------------------------------------------- /assets/VelOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/VelOff.jpg -------------------------------------------------------------------------------- /assets/VelOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/VelOn.jpg -------------------------------------------------------------------------------- /assets/VerbOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/VerbOff.jpg -------------------------------------------------------------------------------- /assets/VerbOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/VerbOn.jpg -------------------------------------------------------------------------------- /assets/VerbUp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/VerbUp.jpg -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/icon.png -------------------------------------------------------------------------------- /assets/rProgOff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/rProgOff.jpg -------------------------------------------------------------------------------- /assets/rProgOn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cashelcomputers/basaGC/bef7d5440d713277f8e7e14f662cc3d893805047/assets/rProgOn.jpg -------------------------------------------------------------------------------- /basagc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import sys 5 | 6 | from PyQt5.QtWidgets import QApplication, QMainWindow 7 | 8 | from basagc import config # need to import this first to set debug flag 9 | 10 | if __name__ == "__main__": 11 | 12 | # arg parser for debug flag 13 | parser = argparse.ArgumentParser(description='basaGC: AGC for KSP') 14 | parser.add_argument('-d','--debug', help='Set debug mode on', required=False, action='store_true') 15 | args = parser.parse_args() 16 | if args.debug: 17 | config.DEBUG = True 18 | config.current_log_level = "DEBUG" 19 | print("================DEBUG MODE================") 20 | 21 | from basagc import gui, computer # import the rest 22 | app = QApplication(sys.argv) 23 | main_window = QMainWindow() 24 | 25 | ui = gui.GUI(main_window) 26 | computer = computer.Computer(ui) 27 | main_window.setWindowTitle('basaGC'); 28 | main_window.show() 29 | 30 | sys.exit(app.exec_()) 31 | 32 | -------------------------------------------------------------------------------- /basagc/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /basagc/computer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """This file contains the guts of the guidance computer""" 3 | 4 | import os 5 | 6 | from PyQt5.QtCore import QTimer 7 | 8 | 9 | from basagc import config 10 | if config.DEBUG: 11 | from pudb import set_trace 12 | from basagc import dsky 13 | from basagc import nouns 14 | from basagc import programs 15 | from basagc import routines 16 | from basagc import telemachus 17 | from basagc import utils 18 | from basagc import verbs 19 | from basagc import imu 20 | from basagc import maneuver 21 | 22 | 23 | class Computer: 24 | 25 | """ This object models the core of the guidance computer. 26 | """ 27 | 28 | computer_instance = None 29 | 30 | def __init__(self, ui): 31 | 32 | """ Class constructor. 33 | :param gui: the wxPython frame object 34 | :return: None 35 | """ 36 | 37 | Computer.computer_instance = self 38 | verbs.Verb.computer = self 39 | programs.Program.computer = self 40 | nouns.computer = self 41 | maneuver.computer = self 42 | 43 | self.ui = ui 44 | self.dsky = dsky.DSKY(self, self.ui) 45 | self.imu = imu.IMU(self) 46 | 47 | self.keyboard_state = { 48 | "input_data_buffer": "", 49 | "register_index": 0, 50 | "is_verb_being_loaded": False, 51 | "is_noun_being_loaded": False, 52 | "is_data_being_loaded": False, 53 | "verb_position": 0, 54 | "noun_position": 0, 55 | "requested_verb": "", 56 | "requested_noun": "", 57 | "current_verb": 0, 58 | "current_noun": 0, 59 | "current_program": 0, 60 | "display_lock": None, 61 | "backgrounded_update": None, 62 | "is_expecting_data": False, 63 | "is_expecting_proceed": False, 64 | "object_requesting_data": None, 65 | "display_location_to_load": None, 66 | "set_keyboard_state_setter": self.set_keyboard_state, 67 | } 68 | self.main_loop_timer = QTimer() 69 | self.main_loop_timer.timeout.connect(self.main_loop) 70 | 71 | # init slow loop (for less important tasks that can be ran approx 2 seconds) 72 | self.slow_loop_timer = QTimer() 73 | self.slow_loop_timer.timeout.connect(self.slow_loop) 74 | 75 | self.comp_acty_timer = QTimer() 76 | self.comp_acty_timer.timeout.connect(self._comp_acty_off) 77 | 78 | self.uplink_queue = [] 79 | self.is_powered_on = False 80 | self.main_loop_table = [] 81 | self.alarm_codes = [0, 0, 0] 82 | self.running_programs = [] 83 | self.noun_data = { 84 | "30": ["00002"], 85 | "25": ["00000", "00000", ""], 86 | "31": ["00000", "00000"], 87 | "38": ["00000", "", ""], 88 | } 89 | self.next_burn = None 90 | #self._burn_queue = [] 91 | self.is_ksp_connected = False 92 | self.ksp_paused_state = None 93 | self.is_direction_autopilot_engaged = False 94 | self.is_thrust_autopilot_engaged = False 95 | self.moi_burn_delta_v = 0.0 # a bit of a hack, need to rethink this 96 | # self.jobs = [] 97 | 98 | self.nouns = nouns.nouns 99 | self.verbs = verbs.verbs 100 | self.programs = programs.programs 101 | 102 | self.option_codes = { 103 | "00001": "", 104 | "00002": "", 105 | "00003": "", 106 | "00004": "", 107 | "00007": "", 108 | "00024": "", 109 | } 110 | # register key handler with qt ui 111 | self.register_charin() 112 | self.on() 113 | 114 | def accept_uplink(self): 115 | try: 116 | uplink_file = open(os.path.join(config.BASE_DIR, "basagc/", "uplink.txt"), "r") 117 | except FileNotFoundError: # lint:ok 118 | self.program_alarm(501) 119 | return 120 | self.dsky.set_annunciator("uplink_acty") 121 | uplink_data = uplink_file.read().strip() 122 | uplink_file.close() 123 | for char in uplink_data: 124 | if char == "\n": 125 | continue 126 | self.uplink_queue.append(char) 127 | 128 | def charin(self, keypress): 129 | ''' 130 | Receives a keypress event and passes it on to routines.charin 131 | :param keypress: the value of the key pressed 132 | :type keypress: str 133 | :returns: None 134 | ''' 135 | routines.charin(keypress, self.keyboard_state, self.dsky, self) 136 | 137 | def process_uplink_data(self): 138 | 139 | # check if any data ready to be uplinked 140 | if len(self.uplink_queue) > 0: 141 | char = self.uplink_queue.pop(0) 142 | self.charin(char) 143 | return True 144 | else: 145 | self.dsky.set_annunciator("uplink_acty", False) 146 | return False 147 | 148 | def add_to_mainloop(self, func): 149 | self.main_loop_table.append(func) 150 | 151 | def remove_from_mainloop(self, func): 152 | if func in self.main_loop_table: 153 | self.main_loop_table.remove(func) 154 | else: 155 | utils.log("Cannot remove function from mainloop, function {} not found".format(func)) 156 | 157 | def register_charin(self): 158 | ''' 159 | Registers the charin handler with the GUI 160 | :returns: None 161 | ''' 162 | self.ui.register_key_event_handler(self.charin) 163 | 164 | def set_keyboard_state(self, state_name, new_value): 165 | ''' 166 | setter for keyboard state 167 | :param state_name: the name of the state to change 168 | :type state_name: str 169 | :param new_value: the new value 170 | :type new_value: str 171 | :returns: None 172 | ''' 173 | self.keyboard_state[state_name] = new_value 174 | 175 | def add_burn(self, burn_object): 176 | 177 | """ Adds a Burn object to the computer burn queue. If no burn is 178 | assigned to next_burn, load new burn to next_burn 179 | :param burn_object: a Burn object that contains parameters for the burn 180 | :param execute: if true, execute the added burn 181 | :return: None 182 | """ 183 | self.next_burn = burn_object 184 | #self.add_to_mainloop(burn_object._coarse_start_time_monitor) 185 | 186 | def enable_burn(self): 187 | self.next_burn.execute() 188 | 189 | def remove_burn(self): 190 | 191 | """ Removes a given Burn object from the computers burn queue 192 | :param this_burn: the Burn object to remove 193 | :return: None 194 | """ 195 | self.next_burn = None 196 | 197 | 198 | #def burn_complete(self): 199 | 200 | #""" Removes a completed burn and loads next queued burn if available. 201 | #:return: None 202 | #""" 203 | #utils.log("Removing {} from burn queue".format(self.next_burn)) 204 | #self.next_burn = None 205 | #if self._burn_queue: 206 | #utils.log("Adding {} as next burn".format(self._burn_queue[0])) 207 | #self.next_burn = self._burn_queue.pop() 208 | 209 | def disable_direction_autopilot(self): 210 | 211 | """ Disables the directional autopilot 212 | :return: None 213 | """ 214 | 215 | telemachus.disable_smartass() 216 | self.is_direction_autopilot_engaged = False 217 | utils.log("Autopilot disabled", log_level="INFO") 218 | 219 | def quit(self): 220 | 221 | """ Quits basaGC. 222 | :return: None 223 | """ 224 | 225 | # disables SMARTASS 226 | try: 227 | telemachus.disable_smartass() 228 | except TypeError: 229 | pass 230 | # if self.loop_timer.is_running: 231 | # self.loop_timer.stop() 232 | self.gui.Destroy() 233 | 234 | def on(self): 235 | 236 | """ Turns the guidance computer on. 237 | :return: None 238 | """ 239 | utils.log("Computer booting...", log_level="INFO") 240 | 241 | # attempt to load telemetry listing 242 | # set_trace() 243 | try: 244 | telemachus.get_api_listing() 245 | except telemachus.KSPNotConnected: 246 | utils.log("Cannot retrieve telemetry listing - no connection to KSP", log_level="WARNING") 247 | else: 248 | utils.log("Retrieved telemetry listing", log_level="INFO") 249 | 250 | # add uplink function to main loop 251 | self.add_to_mainloop(self.process_uplink_data) 252 | 253 | self.main_loop_timer.start(config.LOOP_TIMER_INTERVAL) 254 | self.slow_loop_timer.start(config.SLOW_LOOP_TIMER_INTERVAL) 255 | self.is_powered_on = True 256 | 257 | def main_loop(self): 258 | 259 | """ The guidance computer main loop. 260 | :return: None 261 | """ 262 | 263 | # check KSP paused state 264 | # self.check_paused_state() 265 | 266 | # run each item in process queue 267 | for item in self.main_loop_table: 268 | item() 269 | 270 | 271 | def slow_loop(self): 272 | ''' 273 | A slower loop to handle tasks that are less frequently run 274 | :returns: 275 | ''' 276 | if not telemachus.check_connection(): 277 | self.dsky.annunciators["no_att"].on() 278 | if config.ENABLE_COMP_ACTY_FLASH: 279 | self.flash_comp_acty() 280 | 281 | 282 | def go_to_poo(self): 283 | 284 | """ Executes program 00. Name comes from NASA documentation :) 285 | :return: None 286 | """ 287 | 288 | poo = self.programs["00"]() 289 | poo.execute() 290 | 291 | def execute_verb(self, verb=None, noun=None, **kwargs): 292 | 293 | """ Executes the verb as stored in self.keyboard_state 294 | :return: None 295 | """ 296 | if not verb: 297 | verb = self.keyboard_state["requested_verb"] 298 | self.dsky.set_register(value=verb, register="verb") 299 | 300 | if not noun: 301 | # if verb doesn't exist, smack operator over head 302 | try: 303 | # if there is a noun entered by user, pass it to verb 304 | if self.keyboard_state["requested_noun"] == "": 305 | verb_to_execute = self.verbs[verb](**kwargs) 306 | else: 307 | print(self.keyboard_state["requested_noun"]) 308 | verb_to_execute = self.verbs[verb](self.keyboard_state["requested_noun"], **kwargs) 309 | except KeyError: 310 | self.operator_error("Verb {} does not exist :(".format(verb)) 311 | return 312 | else: 313 | verb_to_execute = self.verbs[verb](noun, **kwargs) 314 | 315 | self.flash_comp_acty(200) 316 | verb_to_execute.execute() 317 | 318 | def execute_program(self, program_number): 319 | ''' 320 | Executes the given program.Must have between 1 and 6 values to disp 321 | :param program_number: the program number to execute 322 | :type program_number: str 323 | :returns: 324 | ''' 325 | try: 326 | program = self.programs[program_number]() 327 | except KeyError: 328 | self.program_alarm(116) 329 | self.go_to_poo() 330 | return 331 | program.execute() 332 | 333 | 334 | def flash_comp_acty(self, duration=config.COMP_ACTY_FLASH_DURATION): 335 | ''' 336 | Flashes the Computer Activity annunciator. 337 | 338 | :returns: 339 | ''' 340 | self.dsky.annunciators["comp_acty"].on() 341 | self.comp_acty_timer.start(duration) 342 | 343 | def _comp_acty_off(self): 344 | ''' 345 | Turns off the comp acty annunciator 346 | :returns: None 347 | ''' 348 | self.dsky.annunciators["comp_acty"].off() 349 | self.comp_acty_timer.stop() 350 | 351 | def operator_error(self, message=None): 352 | 353 | """ Called when the astronaut has entered invalid keyboard input. 354 | :param message: Optional message to send to log 355 | :return: None 356 | """ 357 | 358 | if message: 359 | utils.log("OPERATOR ERROR: " + message, log_level="ERROR") 360 | self.dsky.annunciators["opr_err"].blink_timer.start(500) 361 | 362 | #def remove_job(self, job): 363 | #utils.log("Removing job from jobs list: {}".format(job)) 364 | #self.jobs.remove(job) 365 | 366 | #def add_job(self, job): 367 | #utils.log("Adding job to jobs list: {}".format(job)) 368 | #self.jobs.append(job) 369 | 370 | def reset_alarm_codes(self): 371 | 372 | """ Resets the alarm codes. 373 | :return: None 374 | """ 375 | 376 | self.alarm_codes[2] = self.alarm_codes[0] 377 | self.alarm_codes[0] = 0 378 | self.alarm_codes[1] = 0 379 | 380 | def program_alarm(self, alarm_code): 381 | 382 | """ Sets the program alarm codes in memory and turns the PROG annunciator on. 383 | :param alarm_code: a 3 or 4 digit octal int of the alarm code to raise 384 | :return: None 385 | """ 386 | utils.log("PROGRAM ALARM {}: {}".format(str(alarm_code), config.ALARM_CODES[alarm_code]), log_level="ERROR") 387 | alarm_code += 1000 388 | if self.alarm_codes[0] != 0: 389 | self.alarm_codes[1] = self.alarm_codes[0] 390 | self.alarm_codes[0] = alarm_code 391 | self.alarm_codes[2] = self.alarm_codes[0] 392 | self.dsky.annunciators["prog"].on() 393 | 394 | def poodoo_abort(self, alarm_code, message=None): 395 | 396 | """ Terminates the faulty program, and executes Program 00 (P00) 397 | :param alarm_code: a 3 digit octal int of the alarm code to raise 398 | :return: None 399 | """ 400 | 401 | # alarm_message = config.ALARM_CODES[alarm_code] 402 | alarm_code += 2000 403 | if self.alarm_codes[0] != 0: 404 | self.alarm_codes[1] = self.alarm_codes[0] 405 | self.alarm_codes[0] = alarm_code 406 | self.alarm_codes[2] = self.alarm_codes[0] 407 | self.dsky.annunciators["prog"].on() 408 | self.running_program.terminate() 409 | utils.log("P00DOO ABORT {}: {}".format(str(alarm_code), message), log_level="ERROR") 410 | poo = self.programs["00"]() 411 | poo.execute() 412 | 413 | def program_restart(self, alarm_code, message=None): 414 | 415 | """ Triggers a program restart. 416 | :param alarm_code: a 3 or 4 digit octal int of the alarm code to raise 417 | :param message: optional message to print to log 418 | :return: None 419 | """ 420 | 421 | # TODO: insert terminate and restart program 422 | utils.log("Program fresh start not implemented yet... watch this space...") 423 | if message: 424 | utils.log(message, log_level="ERROR") 425 | 426 | def computer_restart(self, alarm_code, message=None): 427 | 428 | """ Triggers a guidance computer hardware restart. The most severe of the errors! 429 | :param alarm_code: a 3 or 4 digit octal int of the alarm code to raise 430 | :param message: optional message to print to log 431 | :return: None 432 | """ 433 | 434 | # insert computer reboot 435 | # self.fresh_start() 436 | if message: 437 | utils.log(message, log_level="CRITICAL") 438 | pass 439 | 440 | def servicer(self): 441 | 442 | """ For future use. The servicer updates the spacecrafts state vector. 443 | """ 444 | 445 | pass 446 | 447 | #def check_ksp_connection(self): 448 | 449 | #""" checks if we have a connection to Telemachus / KSP 450 | #:return: None 451 | #""" 452 | ## set_trace() 453 | #if not telemachus.check_connection(): 454 | #if self.is_ksp_connected: 455 | ## we have just lost the connection, illuminate NO ATT annunciator and log it 456 | #self.dsky.annunciators["no_att"].on() 457 | #utils.log("No connection to KSP, navigation functions unavailable", log_level="ERROR") 458 | #self.is_ksp_connected = False 459 | #else: 460 | #if not self.is_ksp_connected: 461 | ## have just regained connection, deluminate NO ATT annunciator and log it 462 | #self.dsky.annunciators["no_att"].off() 463 | #utils.log("Connection to KSP established", log_level="INFO") 464 | #self.is_ksp_connected = True 465 | #if not telemachus.telemetry: 466 | #telemachus.get_api_listing() 467 | 468 | #def check_paused_state(self): 469 | 470 | #""" Checks the paused state of KSP, and illuminates STBY annunciator and logs state as necessary. 471 | #:return: None 472 | #""" 473 | 474 | #if self.is_ksp_connected: 475 | #paused_state = telemachus.get_telemetry("paused") 476 | ## if the paused state hasn't changed, skip any annunciator changes 477 | #if paused_state != self.ksp_paused_state: 478 | #if paused_state == 0: 479 | #self.dsky.annunciators["stby"].off() 480 | #utils.log("KSP unpaused, all systems go", log_level="INFO") 481 | #elif paused_state == 1: 482 | #self.dsky.annunciators["stby"].on() 483 | #utils.log("KSP paused", log_level="INFO") 484 | #elif paused_state == 2: 485 | #self.dsky.annunciators["stby"].on() 486 | #utils.log("No power to Telemachus antenna", log_level="WARNING") 487 | #elif paused_state == 3: 488 | #self.dsky.annunciators["stby"].on() 489 | #utils.log("Telemachus antenna off", log_level="WARNING") 490 | #elif paused_state == 4: 491 | #self.dsky.annunciators["stby"].on() 492 | #utils.log("No Telemachus antenna found", log_level="WARNING") 493 | #self.ksp_paused_state = paused_state 494 | 495 | -------------------------------------------------------------------------------- /basagc/config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ This file contains config information common to the whole package.""" 4 | 5 | import os 6 | 7 | from collections import OrderedDict 8 | 9 | DEBUG = False 10 | 11 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 12 | PROGRAM_NAME = "basaGC" 13 | VERSION = "2.2.0" 14 | LICENCE_FILE = os.path.join(BASE_DIR, "licence") 15 | 16 | 17 | IMAGES_DIR = os.path.join(BASE_DIR, "assets/") 18 | IP = "127.0.0.1" 19 | PORT = "8085" 20 | URL = "http://" + IP + ":" + PORT + "/telemachus/datalink?" 21 | DISPLAY_UPDATE_INTERVAL = 500 22 | COMP_ACTY_FLASH_DURATION = 100 23 | LOOP_TIMER_INTERVAL = 50 24 | SLOW_LOOP_TIMER_INTERVAL = 2000 25 | ENABLE_COMP_ACTY_FLASH = True 26 | 27 | LOG_LEVELS = [ 28 | "DEBUG", 29 | "INFO", 30 | "WARNING", 31 | "ERROR", 32 | "CRITICAL", 33 | ] 34 | 35 | current_log_level = "INFO" 36 | 37 | DIRECTIONS = [ 38 | "prograde", 39 | "retrograde", 40 | "normalplus", 41 | "normalminus", 42 | "radialplus", 43 | "radialminus", 44 | "node", 45 | ] 46 | 47 | TELEMACHUS_BODY_IDS = { 48 | "Kerbol": "0", 49 | "Kerbin": "1", 50 | "Mun": "2", 51 | "Minmus": "3", 52 | "Moho": "4", 53 | "Eve": "5", 54 | "Duna": "6", 55 | "Ike": "7", 56 | "Jool": "8", 57 | "Laythe": "9", 58 | "Vall": "10", 59 | "Bop": "11", 60 | "Tylo": "12", 61 | "Gilly": "13", 62 | "Pol": "14", 63 | "Dres": "15", 64 | "Eeloo": "16", 65 | } 66 | 67 | _UNSORTED_ALARM_CODES = { 68 | 110: "Error contacting KSP", 69 | 111: "Telemetry not available", 70 | 115: "No burn data loaded", 71 | 116: "Program doesn't exist", 72 | 120: "No phase angle data available", 73 | 223: "Invalid target selected", 74 | 224: "Orbit not circular", 75 | 225: "Vessel and target orbits inclination too far apart", 76 | 226: "Time of ignition less than 2 minutes in the future", 77 | 310: "Program hasn't been finished yet, watch this space :)", 78 | 410: "Autopilot error", 79 | 501: "Uplink file does not exist, aborting uplink", 80 | } 81 | 82 | ALARM_CODES = OrderedDict(sorted(_UNSORTED_ALARM_CODES.items())) 83 | OCTAL_BODY_IDS = {} 84 | 85 | for key, value in TELEMACHUS_BODY_IDS.items(): 86 | value = oct(int(value)) 87 | value = value[2:] 88 | OCTAL_BODY_IDS[value] = key 89 | 90 | 91 | # OCTAL_BODY_IDS = {key: str(int(oct(int(value)))) for key, value in TELEMACHUS_BODY_IDS.items()} # FIXME: abomination 92 | OCTAL_BODY_NAMES = {value: key for key, value in OCTAL_BODY_IDS.items()} 93 | 94 | PROGRAM_DESCRIPTION = "basaGC is a implementation of the Apollo Guidance Computer (AGC) for Kerbal Space Program." + ( 95 | "\n\nbasaGC includes code and images from the Virtual AGC Project ") + ( 96 | "(http://www.ibiblio.org/apollo/index.html) by Ronald S. Burkey ") 97 | 98 | with open(LICENCE_FILE) as f: 99 | LICENCE = f.readlines() 100 | LICENCE = "".join(LICENCE) 101 | 102 | SHORT_LICENCE = "basaGC is free software; you can redistribute it and/or modify it under the terms of the GNU " + ( 103 | "General Public License as published by the Free Software Foundation; either version 2 of the ") + ( 104 | "License, or (at your option) any later version.\n\nThis program is distributed in the hope that ") + ( 105 | "it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of ") + ( 106 | "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for ") + ( 107 | "more details.\n\nYou should have received a copy of the GNU General Public License along with ") + ( 108 | "this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, ") + ( 109 | "Fifth Floor, Boston, MA 02110-1301, USA.") 110 | 111 | COPYRIGHT = "(C) 2014-2016 Tim Buchanan (cashelcomputers@gmail.com)" 112 | WEBSITE = "https://github.com/cashelcomputers/basaGC/" 113 | DEVELOPERS = "Tim Buchanan" 114 | ICON = os.path.join(BASE_DIR, "icon.png") 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /basagc/dsky.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This module contains code for the DSKY (the guidance computer/user interface). It should be considered to be the 4 | interface between the computer and the gui toolkit. 5 | """ 6 | 7 | from basagc import utils, config 8 | if config.DEBUG: 9 | from pudb import set_trace # lint:ok 10 | 11 | 12 | class DSKY: 13 | """ This class models the DSKY. 14 | """ 15 | 16 | dsky_instance = None 17 | 18 | def __init__(self, computer, ui): 19 | 20 | """ Class constructor. 21 | :type ui: object 22 | :param computer: the instance of the guidance computer 23 | :return: None 24 | """ 25 | 26 | DSKY.dsky_instance = self 27 | self.computer = computer 28 | output_widgets = ui.get_output_widgets() 29 | self.annunciators = output_widgets[0] 30 | self._control_registers = output_widgets[1] 31 | self._data_registers = output_widgets[2] 32 | 33 | self.registers = { 34 | "program": { 35 | "1": self._control_registers["program"].digits[0], 36 | "2": self._control_registers["program"].digits[1], 37 | }, 38 | "verb": { 39 | "1": self._control_registers["verb"].digits[0], 40 | "2": self._control_registers["verb"].digits[1], 41 | }, 42 | "noun": { 43 | "1": self._control_registers["noun"].digits[0], 44 | "2": self._control_registers["noun"].digits[1], 45 | }, 46 | "data": { 47 | "1": { 48 | "sign": self._data_registers[1].digits[0], 49 | "1": self._data_registers[1].digits[1], 50 | "2": self._data_registers[1].digits[2], 51 | "3": self._data_registers[1].digits[3], 52 | "4": self._data_registers[1].digits[4], 53 | "5": self._data_registers[1].digits[5], 54 | }, 55 | "2": { 56 | "sign": self._data_registers[2].digits[0], 57 | "1": self._data_registers[2].digits[1], 58 | "2": self._data_registers[2].digits[2], 59 | "3": self._data_registers[2].digits[3], 60 | "4": self._data_registers[2].digits[4], 61 | "5": self._data_registers[2].digits[5], 62 | }, 63 | "3": { 64 | "sign": self._data_registers[3].digits[0], 65 | "1": self._data_registers[3].digits[1], 66 | "2": self._data_registers[3].digits[2], 67 | "3": self._data_registers[3].digits[3], 68 | "4": self._data_registers[3].digits[4], 69 | "5": self._data_registers[3].digits[5], 70 | }, 71 | }, 72 | } 73 | 74 | 75 | def blank_all_registers(self): 76 | for register in ["verb", "noun", "program", "data_1", "data_2", "data_3"]: 77 | self.blank_register(register) 78 | 79 | def blink_register(self, register): 80 | 81 | """ 82 | Blinks the named register. 83 | :param register: the name of the register to blink 84 | :return: None 85 | """ 86 | 87 | # check if we are to blink a control register 88 | for digit in self.registers[register].digits: 89 | digit.blink_data["is_blinking_lit"] = False 90 | digit.blink_data["is_blinking"] = True 91 | digit.display("b") 92 | 93 | # bind andstart the timer 94 | digit.blink_timer.timeout.connect(self._blink_event) 95 | digit.blink_timer.start(500) 96 | 97 | def _blink_event(self): 98 | """alternates the digit between a value and blank ie to flash the digit.""" 99 | 100 | # digit displaying the number, switch to blank 101 | if self.is_blinking_lit: 102 | self.display(10) 103 | self.is_blinking_lit = False 104 | else: 105 | # digit displaying blank, change to number 106 | self.display(self.last_value) 107 | self.is_blinking_lit = True 108 | 109 | def blank_register(self, register): 110 | ''' 111 | Blanks the given register. 112 | :param register: The register to blank. 113 | :type register: register object or string name of register 114 | :returns: None 115 | ''' 116 | 117 | 118 | # if we are passed a string name of a register, get the register 119 | if isinstance(register, str): 120 | register = self.get_register(register) 121 | 122 | for digit in register.values(): 123 | digit.display("b") 124 | 125 | 126 | def get_register(self, register): 127 | ''' 128 | maps the given register name to a actual register. 129 | :param register: the name of the register 130 | :type register: str 131 | :returns: the register object 132 | ''' 133 | registers = { 134 | "verb": self.registers["verb"], 135 | "noun": self.registers["noun"], 136 | "program": self.registers["program"], 137 | "data_1": self.registers["data"]["1"], 138 | "data_2": self.registers["data"]["2"], 139 | "data_3": self.registers["data"]["3"], 140 | } 141 | return registers[register] 142 | 143 | def set_register(self, value, register, digit=None): 144 | ''' 145 | Displays some data on a register. 146 | :param value: the value to display 147 | :type value: str 148 | :param register: the register to display it on 149 | :type register: str 150 | :param digit: if set, indicates that just one digit is to be set 151 | :type digit: str 152 | :returns: False if value checks fail, True if display set sucessfully 153 | ''' 154 | 155 | # some sanity checks. If checks fail, return False 156 | # if digit is set, only should be one digit supplied 157 | if digit: 158 | if len(value) != 1: 159 | utils.log("You are trying to display a single digit, but got {} digits instead!".format(len(value))) 160 | return False 161 | # if register is a control register, should have either 1 or 2 values to display 162 | else: 163 | if register in ["verb", "noun", "program"]: 164 | if not 1 <= len(value) <= 2: 165 | utils.log("You are trying to display a value in the {} register, expecting 1 or 2 digits, " \ 166 | "got {}".format(register, len(value))) 167 | return False 168 | else: 169 | # otherwise, check for value being length 1 to 6 170 | if not 1 <= len(value) <= 6: 171 | # set_trace() 172 | utils.log("Must have between 1 and 6 values to display in data register, got {}".format(len(value))) 173 | return False 174 | # also check that the first digit is either a "+", "-" or "b" (for blank) 175 | if value[0] not in ["+", "-", "b"]: 176 | utils.log("First digit to display should be either +, -, or b, got {}".format(value[0])) 177 | return False 178 | 179 | # setting control register 180 | if register in ["verb", "noun", "program"]: 181 | this_register = self.get_register(register) # get the register we want to change 182 | if digit is not None: 183 | this_register[digit].display(value) 184 | else: 185 | for index in range(len(value)): 186 | this_register[str(index + 1)].display(value[index]) 187 | return True 188 | 189 | # setting data register 190 | else: 191 | display_map = { 192 | 0: "sign", 193 | 1: "1", 194 | 2: "2", 195 | 3: "3", 196 | 4: "4", 197 | 5: "5", 198 | } 199 | # data register 1 200 | if register == "data_1": 201 | this_register = self.registers["data"]["1"] # get the register we want to change 202 | if digit: 203 | this_register[str(digit)].display(value) 204 | else: 205 | for index in range(len(value)): 206 | digit_to_set = display_map[index] 207 | value_to_set = value[index] 208 | this_register[digit_to_set].display(value_to_set) 209 | 210 | elif register == "data_2": 211 | this_register = self.registers["data"]["2"] # get the register we want to change 212 | if digit: 213 | this_register[str(digit)].display(value) 214 | else: 215 | for index in range(len(value)): 216 | digit_to_set = display_map[index] 217 | value_to_set = value[index] 218 | this_register[digit_to_set].display(value_to_set) 219 | 220 | elif register == "data_3": 221 | this_register = self.registers["data"]["3"] # get the register we want to change 222 | if digit: 223 | this_register[str(digit)].display(value) 224 | else: 225 | for index in range(len(value)): 226 | digit_to_set = display_map[index] 227 | value_to_set = value[index] 228 | this_register[digit_to_set].display(value_to_set) 229 | 230 | def set_annunciator(self, name, set_to=True): 231 | 232 | try: 233 | if set_to: 234 | self.annunciators[name].on() 235 | else: 236 | self.annunciators[name].off() 237 | except KeyError: 238 | utils.log("You tried to change a annunciator that doesnt exist :(", "WARNING") 239 | 240 | def start_annunciator_blink(self, name): 241 | self.annunciators[name].start_blink() 242 | 243 | def stop_annunciator_blink(self, name): 244 | self.annunciators[name].stop_blink() 245 | 246 | def stop_comp_acty_flash(self, event): 247 | 248 | """ Stops the COMP ACTY annunciator from flashing. 249 | :param event: wxPython event (not used). 250 | :return: None 251 | """ 252 | 253 | self.annunciators["comp_acty"].off() 254 | 255 | # def request_proceed_or_data(self, requesting_object, location): 256 | # 257 | # utils.log("{} requesting PROCEED or data".format(requesting_object)) 258 | # self.verb_noun_flash_on() 259 | # self.object_requesting_data = requesting_object 260 | # self.is_expecting_data = True 261 | # self.display_location_to_load = location 262 | # 263 | # def request_proceed(self, requesting_object): 264 | # utils.log("{} requesting PROCEED or data".format(requesting_object)) 265 | # self.verb_noun_flash_on() 266 | # self.object_requesting_data = requesting_object 267 | # self.computer.keyboard_state["is_expecting_data"] = True 268 | 269 | def request_data(self, requesting_object, display_location, is_proceed_available=False): 270 | 271 | """ Requests data entry from the user. 272 | :param requesting_object: the object requesting the data 273 | :param display_location: the register that entered data will be displayed in 274 | :param is_proceed_available: True if the user can key in PROCEED instead of data 275 | :return: None 276 | """ 277 | # if is_proceed_available is set True, don't blank display as it should be showing data to proceed with 278 | if not is_proceed_available: 279 | self.blank_register(display_location) 280 | utils.log("{}, method {}() requesting data".format(requesting_object.__self__, requesting_object.__name__)) 281 | self.verb_noun_flash_on() 282 | self.computer.keyboard_state["object_requesting_data"] = requesting_object 283 | self.computer.keyboard_state["is_expecting_data"] = True 284 | self.computer.keyboard_state["display_location_to_load"] = display_location 285 | # if PROCEED is a valid option, don't blank the data register (user needs to be able to see value :) 286 | # if not is_proceed_available: 287 | # if isinstance(display_location, DataRegister): 288 | # for register in list(self.data_registers.values()): 289 | # register.blank() 290 | # else: 291 | 292 | 293 | def verb_noun_flash_on(self): 294 | 295 | """ Starts the verb/noun flash. 296 | :return: None 297 | """ 298 | 299 | self.registers["verb"]["1"].start_blink() 300 | self.registers["verb"]["2"].start_blink() 301 | self.registers["noun"]["1"].start_blink() 302 | self.registers["noun"]["2"].start_blink() 303 | 304 | def verb_noun_flash_off(self): 305 | 306 | """ Starts the verb/noun flash. 307 | :return: None 308 | """ 309 | 310 | self.registers["verb"]["1"].stop_blink() 311 | self.registers["verb"]["2"].stop_blink() 312 | self.registers["noun"]["1"].stop_blink() 313 | self.registers["noun"]["2"].stop_blink() 314 | 315 | def stop_blink(self): 316 | 317 | """ 318 | THIS METHOD IS DEPRECIATED. It is left in here to catch any other usages. 319 | 320 | Stops the verb/noun flash 321 | :return: None 322 | """ 323 | utils.log(message="Called depreciated method stop_blink(). Please use verb_noun_flash_off()", log_level="ERROR") 324 | 325 | 326 | 327 | def flash_comp_acty(self): 328 | 329 | """ Flashes the COMP ACTY annunciator. 330 | :return: None 331 | """ 332 | 333 | pass 334 | # self.annunciators["comp_acty"].on() 335 | # self.comp_acty_timer.Start(config.COMP_ACTY_FLASH_DURATION, oneShot=True) 336 | 337 | #def set_noun(self, noun): 338 | 339 | #""" Sets the required noun. 340 | #:param noun: Noun to set 341 | #:return: 342 | #""" 343 | 344 | #self.requested_noun = noun 345 | #self.control_registers["noun"].display(noun) 346 | 347 | def reset_annunciators(self): 348 | 349 | for annunciator in self.annunciators: 350 | self.annunciators[annunciator].off() 351 | 352 | def set_tooltip(self, register, tooltip): 353 | ''' 354 | Sets the tooltop on the given register 355 | :param register: the name of the register to set the tooltip on 356 | :type register: str 357 | :param tooltip: the tooltip to display 358 | :type tooltip: str 359 | :returns: 360 | ''' 361 | this_register = self.get_register(register) 362 | for digit in this_register.values(): 363 | digit.set_tooltip(tooltip) 364 | -------------------------------------------------------------------------------- /basagc/imu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """This module contains a class to model the IMU found on Apollo spacecraft.""" 3 | 4 | from basagc.telemachus import check_connection, get_telemetry 5 | from basagc import utils, config 6 | if config.DEBUG: 7 | from pudb import set_trace # lint:ok 8 | 9 | class IMU: 10 | ''' 11 | This class models the IMU used in Apollo spacecraft. 12 | ''' 13 | def __init__(self, computer): 14 | ''' 15 | Class init 16 | :param computer: the instance of the computer 17 | :type computer: Computer object 18 | :returns: None 19 | ''' 20 | 21 | self.computer = computer 22 | self._is_on = False 23 | self.is_course_aligned = False 24 | self.is_fine_aligned = False 25 | self.gyro_angles = { 26 | "inner": 0.0, # Y axis, aka pitch 27 | "middle": 0.0, # Z axis, aka heading 28 | "outer": 0.0, # X axis, aka roll 29 | } 30 | 31 | def on(self): 32 | ''' 33 | Turns the IMU on 34 | :returns: True if successful, False otherwise 35 | ''' 36 | if check_connection() == False: 37 | utils.log("Cannot connect to KSP", "WARNING") 38 | else: 39 | self.set_coarse_align() 40 | 41 | 42 | # add check for gimbal lock to computer main loop 43 | 44 | 45 | def update_gyro_angles(self): 46 | ''' 47 | Gets the latest attitude from KSP and sets those values in IMU 48 | :returns: None 49 | ''' 50 | 51 | self.gyro_angles["inner"] = get_telemetry("pitch") 52 | self.gyro_angles["middle"] = get_telemetry("heading") 53 | self.gyro_angles["outer"] = get_telemetry("roll") 54 | 55 | def check_for_gimbal_lock(self): 56 | ''' 57 | Checks if middle gimbal is approaching gimbal lock, only applies to fine align mode 58 | :returns: False if ok, True if approaching gimbal lock 59 | ''' 60 | if self.is_fine_aligned: 61 | # check if approaching gimbal lock 62 | if (70 <= self.gyro_angles["middle"] <= 85) or \ 63 | (95 <= self.gyro_angles["middle"] <= 110) or \ 64 | (250 <= self.gyro_angles["middle"] <= 265) or \ 65 | (275 <= self.gyro_angles["middle"] <= 290): 66 | self.computer.dsky.set_annunciator("gimbal_lock") 67 | else: 68 | # if middle gimbal = 90 +- 5 or 270 +- 5, gimbal lock has occured 69 | if (85 < self.gyro_angles["middle"] < 95) or \ 70 | (265 < self.gyro_angles["middle"] <= 275): 71 | self.set_coarse_align() 72 | 73 | def set_coarse_align(self): 74 | ''' 75 | Sets coarse align mode. 76 | :returns: None 77 | ''' 78 | self.is_fine_aligned = False 79 | self.is_course_aligned = True 80 | self.computer.dsky.set_annunciator("no_att") 81 | if self.update_gyro_angles in self.computer.main_loop_table: 82 | self.computer.main_loop_table.remove(self.update_gyro_angles) 83 | if self.check_for_gimbal_lock in self.computer.main_loop_table: 84 | self.computer.main_loop_table.remove(self.check_for_gimbal_lock) 85 | utils.log("IMU coarse align set") 86 | 87 | def set_fine_align(self): 88 | ''' 89 | Sets fine align mode. 90 | :returns: None 91 | ''' 92 | # if no connection to KSP, stop fine align and go back to coarse align 93 | if check_connection() == False: 94 | utils.log("IMU: cannot complete fine align, no connection to KSP", log_level="ERROR") 95 | return 96 | self.is_fine_aligned = True 97 | self.is_course_aligned = False 98 | self.computer.dsky.set_annunciator("gimbal_lock", False) 99 | self.computer.dsky.set_annunciator("no_att", False) 100 | #self.computer.main_loop_table.append(self.update_gyro_angles) 101 | #self.computer.main_loop_table.append(self.check_for_gimbal_lock) 102 | utils.log("IMU fine align set") -------------------------------------------------------------------------------- /basagc/maneuver.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | 4 | from pudb import set_trace 5 | 6 | from basagc import config, telemachus, utils 7 | from basagc.config import TELEMACHUS_BODY_IDS 8 | from basagc.telemachus import get_telemetry 9 | 10 | if config.DEBUG: 11 | from pudb import set_trace # lint:ok 12 | 13 | computer = None 14 | 15 | class HohmannTransfer: 16 | 17 | def __init__(self): 18 | self.delta_v_1 = 0.0 19 | self.delta_v_2 = 0.0 20 | self.orbiting_body = get_telemetry("body") 21 | self.radius = get_telemetry("body_radius", body_number=config.TELEMACHUS_BODY_IDS[self.orbiting_body]) 22 | 23 | self.phase_angle_required = 0.0 24 | self.time_of_ignition_first_burn = 0.0 25 | self.target_name = "Mun" 26 | self.departure_body = get_telemetry("body") 27 | self.departure_altitude = get_telemetry("sma") 28 | self.radius = get_telemetry("body_radius", body_number=config.TELEMACHUS_BODY_IDS[self.orbiting_body]) 29 | self.destination_altitude = 13500000 + self.radius 30 | self.grav_param = get_telemetry("body_gravParameter", 31 | body_number=config.TELEMACHUS_BODY_IDS[self.orbiting_body]) 32 | 33 | self.orbital_period = get_telemetry("period") 34 | self.departure_body_period = get_telemetry("body_period", 35 | body_number=config.TELEMACHUS_BODY_IDS["Kerbin"]) 36 | self.first_burn = None 37 | self.second_burn = None 38 | self.target_id = config.TELEMACHUS_BODY_IDS[self.target_name] 39 | 40 | self.time_of_node = 0.0 41 | #self.time_of_second_node = 0.0 42 | self.duration_of_burn = 0 43 | 44 | def calculate_sma_transfer_ellipse(self): 45 | 46 | radius_departure_orbit = self.departure_altitude 47 | radius_arrival_orbit = self.destination_altitude 48 | sma = (radius_departure_orbit + radius_arrival_orbit) / 2 49 | return sma 50 | 51 | def calculate_velocity_initial(self): 52 | 53 | radius_departure_orbit = self.departure_altitude 54 | vi = math.sqrt(self.grav_param / radius_departure_orbit) 55 | return vi 56 | 57 | def calculate_velocity_final(self): 58 | radius_arrival_orbit = self.destination_altitude 59 | vf = math.sqrt(self.grav_param / radius_arrival_orbit) 60 | return vf 61 | 62 | def calculate_velocity_initial_on_transfer_orbit(self): 63 | radius_departure_orbit = self.departure_altitude 64 | vtxi = math.sqrt(self.grav_param * ((2 / radius_departure_orbit) - (1 / self.calculate_sma_transfer_ellipse()))) 65 | return vtxi 66 | 67 | def calculate_velocity_final_on_transfer_orbit(self): 68 | radius_arrival_orbit = self.destination_altitude 69 | vtxf = math.sqrt(self.grav_param * ((2 / radius_arrival_orbit) - (1 / self.calculate_sma_transfer_ellipse()))) 70 | return vtxf 71 | 72 | def calculate_initial_delta_v(self): 73 | return self.calculate_velocity_initial_on_transfer_orbit() - self.calculate_velocity_initial() 74 | 75 | def calculate_final_delta_v(self): 76 | return self.calculate_velocity_final() - self.calculate_velocity_final_on_transfer_orbit() 77 | 78 | def calculate_total_delta_v(self): 79 | return self.calculate_initial_delta_v() + self.calculate_final_delta_v() 80 | 81 | def calculate_other_parameters(self): 82 | utils.log("Initial velocity at start of transfer: {:.2f} m/s".format(self.calculate_velocity_initial())) 83 | utils.log("Velocity on transfer orbit at initial orbit: {:.2f} m/s".format(self.calculate_velocity_initial_on_transfer_orbit())) 84 | utils.log("Velocity on transfer orbit at final orbit: {:.2f} m/s".format(self.calculate_velocity_final_on_transfer_orbit())) 85 | utils.log("Initial velocity change (delta-v): {:.2f} m/s".format(self.calculate_initial_delta_v())) 86 | utils.log("Final velocity change (delta-v): {:.2f} m/s".format(self.calculate_final_delta_v())) 87 | utils.log("Total chance in velocity (delta-v): {:.2f} m/s".format(self.calculate_total_delta_v())) 88 | utils.log() 89 | 90 | @staticmethod 91 | def check_orbital_parameters(): 92 | 93 | if get_telemetry("eccentricity") > 0.002: 94 | return (False, 224) 95 | 96 | # check if orbit is excessively inclined 97 | target_inclination = get_telemetry("target_inclination") 98 | vessel_inclination = get_telemetry("inclination") 99 | if (vessel_inclination > (target_inclination - 1)) and (vessel_inclination > (target_inclination + 1)): 100 | #self.computer.poodoo_abort(225) 101 | return (False, 225) 102 | else: 103 | return True 104 | 105 | def update_parameters(self): 106 | 107 | # update departure altitide 108 | self.departure_altitude = get_telemetry("altitude") 109 | self.calculate() 110 | self.calculate_burn_timings() 111 | self.first_burn.delta_v = self.delta_v_1 112 | self.first_burn.time_of_ignition = self.time_of_ignition_first_burn 113 | self.first_burn.time_of_node = self.time_of_node 114 | telemachus.update_maneuver_node(ut=self.time_of_node, delta_v=(0.0, 0.0, self.delta_v_1)) 115 | if config.current_log_level == "DEBUG": 116 | self.print_maneuver_data() 117 | 118 | def print_maneuver_data(self): 119 | self.calculate_other_parameters() 120 | utils.log("-" * 40) 121 | utils.log("Hohmann Transfer Data:") 122 | utils.log("Delta-V required: {:.2f}".format(self.delta_v_1)) 123 | utils.log("Phase angle required: {:.2f}".format(self.phase_angle_required)) 124 | utils.log("Burn duration: {:.2f} seconds".format(self.duration_of_burn)) 125 | utils.log("-" * 40) 126 | 127 | def execute(self): 128 | 129 | self.calculate() 130 | time_to_node = HohmannTransfer.get_time_to_node(self.phase_angle_difference(), 131 | self.orbital_period, 132 | self.departure_body_period) 133 | if time_to_node <= 120: 134 | utils.log("Time of ignition less that 2 minutes in the future, starting burn during next orbit") 135 | time_to_node += self.orbital_period 136 | self.calculate_burn_timings() 137 | self.first_burn = Burn(delta_v=self.delta_v_1, 138 | direction="node", 139 | time_of_ignition=self.time_of_ignition_first_burn, 140 | time_of_node=self.time_of_node, 141 | burn_duration=self.duration_of_burn, 142 | ) 143 | if config.current_log_level == "DEBUG": 144 | self.print_maneuver_data() 145 | computer.add_burn(self.first_burn) 146 | #self.add_maneuver_node() 147 | #self.first_burn.execute() 148 | 149 | def phase_angle_difference(self): 150 | #set_trace() 151 | current_phase_angle = get_telemetry("body_phaseAngle", body_number=self.target_id) 152 | phase_angle_difference = current_phase_angle - self.phase_angle_required 153 | #if phase_angle_difference < 0: 154 | #phase_angle_difference = 180 + abs(phase_angle_difference) 155 | #utils.log("Adding 180 degrees to phase angle difference") 156 | utils.log() 157 | utils.log("Current Phase Angle: {} degrees".format(current_phase_angle)) 158 | utils.log("Phase Angle Required: {} degrees".format(self.phase_angle_required)) 159 | utils.log("Phase Angle Difference: {} degrees".format(phase_angle_difference)) 160 | return phase_angle_difference 161 | 162 | def calculate(self): 163 | 164 | # determine correct phase angle 165 | self.phase_angle_required = HohmannTransfer.calculate_phase_angle( 166 | self.departure_altitude, 167 | self.destination_altitude, 168 | self.grav_param, 169 | ) 170 | 171 | # determine delta-v for burns 1 and 2 172 | self.delta_v_1, self.delta_v_2 = HohmannTransfer.calculate_delta_v(self.departure_altitude, 173 | self.destination_altitude) 174 | 175 | # calculate time to transfer 176 | self.time_to_transfer = HohmannTransfer.time_to_transfer(self.departure_altitude, 177 | self.destination_altitude, 178 | self.grav_param) 179 | 180 | #self.time_of_second_node = self.time_of_node + self.time_to_transfer 181 | 182 | def calculate_burn_timings(self): 183 | time_to_node = HohmannTransfer.get_time_to_node(self.phase_angle_difference(), 184 | self.orbital_period, 185 | self.departure_body_period) 186 | 187 | self.time_of_node = get_telemetry("universalTime") + time_to_node 188 | initial_mass = float(computer.noun_data["25"][0] + "." + computer.noun_data["25"][1]) 189 | thrust = float(computer.noun_data["31"][0] + "." + computer.noun_data["31"][1]) 190 | specific_impulse = float(computer.noun_data["38"][0]) 191 | self.duration_of_burn = calc_burn_duration(initial_mass, thrust, specific_impulse, self.delta_v_1) 192 | self.time_of_ignition_first_burn = self.time_of_node - (self.duration_of_burn / 2) # TIG 193 | 194 | @staticmethod 195 | def get_time_to_node(phase_angle_difference, orbital_period, departure_orbital_period): 196 | tig = phase_angle_difference / ((360 / orbital_period) - (360 / departure_orbital_period)) 197 | if tig < 0: 198 | utils.log("Setting node at next orbit") 199 | tig += orbital_period 200 | return tig 201 | 202 | @staticmethod 203 | def time_to_transfer(departure_orbit, destination_orbit, grav_param): 204 | """ 205 | Calculates the time to transfer from one orbit to another, 206 | :param departure_orbit: departure orbit altitude 207 | :param destination_orbit: destination orbit altitude 208 | :param grav_param: orbiting body gravitational parameter 209 | :return: a float in seconds of the time to transfer 210 | """ 211 | tH = math.pi * math.sqrt(math.pow(departure_orbit + destination_orbit, 3) / (8 * grav_param)) 212 | return tH 213 | 214 | @staticmethod 215 | def calculate_phase_angle(departure_orbit, destination_orbit, grav_param): 216 | """ Calculates the required phase angle for transfer. 217 | :param departure_orbit: departure orbit altitude 218 | :param destination_orbit: destination orbit altitude 219 | :param grav_param: orbiting body gravitational parameter 220 | :return: the required phase angle 221 | """ 222 | #departure_planet_radius = get_telemetry("body_radius", body_number=TELEMACHUS_BODY_IDS["Kerbin"]) 223 | #departure_orbit += departure_planet_radius 224 | #destination_orbit += departure_planet_radius 225 | tH = HohmannTransfer.time_to_transfer(departure_orbit, destination_orbit, grav_param) 226 | required_phase_angle = 180 - math.sqrt(grav_param / destination_orbit) * (tH / destination_orbit) * 180 / math.pi 227 | return required_phase_angle 228 | 229 | @staticmethod 230 | def calculate_delta_v(departure_altitude, destination_altitude, departure_body="Kerbin"): 231 | """ 232 | Given a circular orbit at altitude departure_altitude and a target orbit at altitude 233 | destination_altitude, return the delta-V budget of the two burns required for a Hohmann 234 | transfer. 235 | 236 | departure_altitude and destination_altitude are in meters above the surface. 237 | returns a float of the burn delta-v required, positive means prograde, negative means retrograde 238 | 239 | :param departure_body: 240 | :param departure_altitude: departure orbit altitude 241 | :param destination_altitude: destination orbit altitude 242 | """ 243 | #departure_planet_radius = get_telemetry("body_radius", body_number=TELEMACHUS_BODY_IDS[departure_body]) 244 | r1 = departure_altitude 245 | r2 = destination_altitude 246 | mu = float(get_telemetry("body_gravParameter", body_number=TELEMACHUS_BODY_IDS[departure_body])) 247 | sqrt_r1 = math.sqrt(r1) 248 | sqrt_r2 = math.sqrt(r2) 249 | sqrt_2_sum = math.sqrt(2 / (r1 + r2)) 250 | sqrt_mu = math.sqrt(mu) 251 | delta_v_1 = sqrt_mu / sqrt_r1 * (sqrt_r2 * sqrt_2_sum - 1) 252 | delta_v_2 = sqrt_mu / sqrt_r2 * (1 - sqrt_r1 * sqrt_2_sum) 253 | return delta_v_1, delta_v_2 254 | 255 | 256 | class Burn: 257 | 258 | """ This object models a burn maneuver """ 259 | 260 | def __init__(self, delta_v, direction, time_of_ignition, time_of_node, burn_duration, recalc_function=None): 261 | 262 | """ Class constructor 263 | 264 | :param delta_v: delta_v required for burn 265 | :type delta_v: float 266 | :param direction: direction of burn 267 | :type direction: str (should be in config.DIRECTIONS) 268 | :param time_of_ignition: Time of Ignition, relative to Mission Elapsed Time 269 | :type time_of_ignition: float 270 | :return: None 271 | """ 272 | self.burn_duration = burn_duration 273 | self.recalc_function = recalc_function 274 | self.delta_v_required = delta_v 275 | self.direction = direction 276 | self.time_of_ignition = time_of_ignition 277 | self.time_of_node = time_of_node 278 | self.time_until_ignition = self.calculate_time_to_ignition() 279 | 280 | self.is_display_blanked = False 281 | self.is_verb_99_executed = False 282 | 283 | self.is_directional_autopilot_engaged = False 284 | self.is_thrust_autopilot_engaged = False 285 | self.is_active = False 286 | 287 | self.initial_speed = 0.0 288 | self.accumulated_delta_v = 0.0 289 | self._is_thrust_reduced = False 290 | self.current_velocity = 0.0 291 | 292 | def recalculate(self): 293 | self.recalc_function() 294 | self.time_until_ignition = self.calculate_time_to_ignition() 295 | self.velocity_at_cutoff = self._calculate_velocity_at_cutoff() 296 | 297 | def execute(self): 298 | 299 | """ Entry point to execute this burn. 300 | :return: None 301 | """ 302 | 303 | # load the course start time monitor into the computers main loop 304 | self.add_maneuver_node() 305 | computer.main_loop_table.append(self._coarse_start_time_monitor) 306 | computer.execute_verb(verb="16", noun="40") 307 | 308 | def add_maneuver_node(self): 309 | 310 | telemachus.add_maneuver_node(ut=self.time_of_node, delta_v=(0.0, 0.0, self.delta_v_required)) 311 | 312 | def terminate(self): 313 | 314 | """ Terminates the burn, disabling autopilot if running 315 | :return: None 316 | """ 317 | self._disable_directional_autopilot() 318 | 319 | # if the throttle is open, close it 320 | telemachus.cut_throttle() 321 | computer.remove_burn() 322 | 323 | def _coarse_start_time_monitor(self): 324 | 325 | self.time_until_ignition = self.calculate_time_to_ignition() 326 | 327 | # at TIG - 105 seconds: 328 | # ensure we only blank display first time through the loop 329 | if int(self.time_until_ignition) == 105 and not self.is_display_blanked: 330 | # also recalculate burn parameters 331 | #self.recalculate() 332 | computer.dsky.current_verb.terminate() 333 | for register in ["verb", "noun", "program", "data_1", "data_2", "data_3"]: 334 | computer.dsky.blank_register(register) 335 | self.is_display_blanked = True 336 | # at TIG - 100 seconds, reenable display and enable directional autopilot 337 | 338 | if int(self.time_until_ignition) <= 100 and self.is_display_blanked: 339 | # restore the displayed program number 340 | computer.dsky.set_register(computer.running_program.number, "program") 341 | computer.execute_verb(verb="16", noun="40") 342 | self.is_display_blanked = False 343 | self._enable_directional_autopilot() 344 | 345 | # at TIG - 10, execute verb 99 346 | if int(self.time_until_ignition) <= 10: 347 | computer.main_loop_table.remove(self._coarse_start_time_monitor) 348 | computer.execute_verb(verb="99", object_requesting_proceed=self._accept_enable_engine) 349 | 350 | def _accept_enable_engine(self, data): 351 | if data == "proceed": 352 | utils.log("Go for burn!", log_level="INFO") 353 | else: 354 | return 355 | computer.main_loop_table.append(self._fine_start_time_monitor) 356 | computer.execute_verb(verb="16", noun="40") 357 | 358 | def _fine_start_time_monitor(self): 359 | 360 | self.time_until_ignition = self.calculate_time_to_ignition() 361 | if float(self.time_until_ignition) < 1.1: # ADJUSTED FROM 0.1 to 1.1 to dry fix start delay of approx 1 second 362 | utils.log("Engine Ignition", log_level="INFO") 363 | self._begin_burn() 364 | computer.main_loop_table.remove(self._fine_start_time_monitor) 365 | 366 | def _begin_burn(self): 367 | 368 | self.initial_speed = get_telemetry("orbitalVelocity") 369 | self.velocity_at_cutoff = self._calculate_velocity_at_cutoff() 370 | 371 | # start thrusting 372 | # set actual TIG 373 | 374 | #self.actual_time_of_ignition = get_telemetry("universalTime") 375 | #self.time_of_cutoff = self.actual_time_of_ignition + self.burn_duration 376 | telemachus.set_throttle(100) 377 | computer.main_loop_table.append(self._thrust_monitor) 378 | 379 | #def _burn_time_monitor(self): 380 | #burn_duration_so_far = get_telemetry("universalTime") - self.actual_time_of_ignition 381 | #time_from_cutoff = self.burn_duration - burn_duration_so_far 382 | #print("T+{:.2f}s, Time to cutoff: {:.2f} seconds".format(burn_duration_so_far, time_from_cutoff)) 383 | #if time_from_cutoff < 0.2: 384 | ##shutdown 385 | #telemachus.cut_throttle() 386 | #utils.log("Closing throttle, burn complete!", log_level="INFO") 387 | #utils.log("Calculated burn duration: {:.2f} seconds, actual duration: {:.2f} seconds".format(self.burn_duration, 388 | #burn_duration_so_far)) 389 | #utils.log("Error: {:.2f} seconds".format(self.burn_duration - burn_duration_so_far)) 390 | #computer.dsky.current_verb.terminate() 391 | #computer.execute_verb(verb="06", noun="14") 392 | #computer.main_loop_table.remove(self._burn_time_monitor) 393 | ##computer.burn_complete() 394 | #self.terminate() 395 | #computer.go_to_poo() 396 | 397 | 398 | def _thrust_monitor(self): 399 | 400 | # recalculate accumulated delta-v so far 401 | self.accumulated_delta_v = self._calculate_accumulated_delta_v() 402 | current_velocity = get_telemetry("orbitalVelocity") 403 | #print("Accumulated dV: {:.2f}".format(self.accumulated_delta_v)) 404 | #print("dV required: {:.2f}".format(self.delta_v_required)) 405 | #print("Velocity at start: {:.2f}".format(self.initial_speed)) 406 | #print("Current Velocity: {:.2f}".format(current_velocity)) 407 | #print("Expected dV at cutoff: {}".format(self.velocity_at_cutoff)) 408 | 409 | 410 | if current_velocity > (self.velocity_at_cutoff - 13.5) and not self._is_thrust_reduced: 411 | utils.log("Throttling back to 10%", log_level="DEBUG") 412 | telemachus.set_throttle(10) 413 | self._is_thrust_reduced = True 414 | telemachus.disable_smartass() 415 | telemachus.send_command_to_ksp("command=f.sas") 416 | 417 | if current_velocity > (self.velocity_at_cutoff - 3.5): # the 3.5 a hack otherwise it overshoots, FIXME! 418 | telemachus.cut_throttle() 419 | utils.log("Closing throttle, burn complete!", log_level="DEBUG") 420 | computer.dsky.current_verb.terminate() 421 | computer.execute_verb(verb="06", noun="14") 422 | computer.main_loop_table.remove(self._thrust_monitor) 423 | #computer.burn_complete() 424 | self.terminate() 425 | computer.go_to_poo() 426 | 427 | 428 | def _calculate_velocity_at_cutoff(self): 429 | return self.initial_speed + self.delta_v_required 430 | 431 | def calculate_time_to_ignition(self): 432 | 433 | """ Calculates the time to ignition in seconds 434 | :return: time to ignition in seconds 435 | :rtype : float 436 | """ 437 | current_time = get_telemetry("universalTime") 438 | return self.time_of_ignition - current_time 439 | 440 | def _calculate_accumulated_delta_v(self): 441 | current_speed = get_telemetry("orbitalVelocity") 442 | return current_speed - self.initial_speed 443 | 444 | def _disable_directional_autopilot(self): 445 | 446 | telemachus.disable_smartass() 447 | utils.log("Directional autopilot disabled", log_level="INFO") 448 | return True 449 | 450 | def _enable_directional_autopilot(self): 451 | 452 | telemachus.set_mechjeb_smartass("node") 453 | utils.log("Directional autopilot enabled", log_level="INFO") 454 | return True 455 | 456 | def calc_burn_duration(initial_mass, thrust, specific_impulse, delta_v): 457 | ''' 458 | Calculates the duration of a burn in seconds. 459 | :param initial_mass: initial mass of spacecraft 460 | :type initial_mass: float 461 | :param thrust: total thrust of the spacecraft 462 | :type thrust: float 463 | :param specific_impulse: Isp 464 | :type specific_impulse: int or float 465 | :param delta_v: delta_v for burn 466 | :type delta_v: float 467 | :returns: float time of burn in seconds 468 | ''' 469 | exhaust_velocity = specific_impulse * 9.81 470 | burn_duration = (initial_mass * exhaust_velocity / thrust) * (1 - math.exp(-delta_v / exhaust_velocity)) 471 | utils.log(log_level="info") 472 | utils.log("-" * 40, log_level="info") 473 | utils.log("Burn duration calculations:", log_level="info") 474 | utils.log("Initial mass: {} tonnes".format(initial_mass), log_level="info") 475 | utils.log("Thrust: {} kN".format(thrust), log_level="info") 476 | utils.log("Specific Impulse: {} seconds".format(specific_impulse), log_level="info") 477 | utils.log("Exhaust Velocity: {:.2f} kg/s".format(exhaust_velocity), log_level="info") 478 | utils.log("Burn Duration: {:.1f} seconds".format(burn_duration), log_level="info") 479 | utils.log("-" * 40, log_level="info") 480 | return burn_duration -------------------------------------------------------------------------------- /basagc/nouns.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ This module contains all nouns used by the guidance computer.""" 3 | 4 | import inspect 5 | import sys 6 | from collections import OrderedDict 7 | 8 | from basagc import config 9 | if config.DEBUG: 10 | from pudb import set_trace # lint:ok 11 | from basagc import utils 12 | from basagc.telemachus import get_telemetry, TelemetryNotAvailable 13 | 14 | computer = None 15 | 16 | def octal(value): 17 | 18 | """ Converts a value to octal, but not written as Ooxxx 19 | :param value: the value to convert 20 | :return: the octal value 21 | :rtype: int 22 | """ 23 | 24 | return int(oct(value)) 25 | 26 | 27 | class NounNotImplementedError(Exception): 28 | 29 | """ This exception should be raised when a selected noun is not implemented 30 | yet 31 | """ 32 | 33 | pass 34 | 35 | 36 | class Noun(object): 37 | 38 | def __init__(self, description, number): 39 | self.description = description 40 | self.number = number 41 | 42 | def return_data(self): 43 | raise NounNotImplementedError 44 | 45 | # -----------------------BEGIN NORMAL NOUNS-------------------------------------- 46 | 47 | class Noun09(Noun): 48 | 49 | def __init__(self): 50 | super().__init__(description="Alarm Codes", number="09") 51 | 52 | def return_data(self): 53 | 54 | utils.log("Noun 09 requested") 55 | alarm_codes = computer.alarm_codes 56 | data = { 57 | 1: str(alarm_codes[0]), 58 | 2: str(alarm_codes[1]), 59 | 3: str(alarm_codes[2]), 60 | "is_octal": True, 61 | "tooltips": [ 62 | "First alarm code", 63 | "Second alarm code", 64 | "Last alarm code", 65 | ], 66 | } 67 | return data 68 | 69 | 70 | class Noun14(Noun): 71 | 72 | def __init__(self): 73 | super().__init__(description="Burn error display (Expected Δv at cutoff (xxxxx m/s), Actual Δv at" 74 | "cutoff (xxxxx m/s), Difference (xxxx.x m/s)", 75 | number="14") 76 | 77 | def return_data(self): 78 | if not computer.next_burn: 79 | computer.program_alarm(115) 80 | return False 81 | burn = computer.next_burn 82 | expected_delta_v_at_cutoff = burn.velocity_at_cutoff 83 | actual_delta_v_at_cutoff = get_telemetry("orbitalVelocity") 84 | delta_v_error = actual_delta_v_at_cutoff - expected_delta_v_at_cutoff 85 | 86 | expected_delta_v_at_cutoff = str(int(expected_delta_v_at_cutoff)).replace(".", "") 87 | actual_delta_v_at_cutoff = str(int(actual_delta_v_at_cutoff)).replace(".", "") 88 | delta_v_error = str(round(delta_v_error, 1)).replace(".", "") 89 | 90 | data = { 91 | 1: expected_delta_v_at_cutoff, 92 | 2: actual_delta_v_at_cutoff, 93 | 3: delta_v_error, 94 | "tooltips": [ 95 | "Expected velocity at cutoff (xxxxx m/s)", 96 | "Actual velocity at cutoff (xxxxx m/s)", 97 | "Velocity error (xxxx.x m/s)" 98 | ], 99 | "is_octal": False, 100 | } 101 | return data 102 | 103 | # 104 | # def noun15(calling_verb): 105 | # raise NounNotImplementedError 106 | # 107 | # def noun16(calling_verb): 108 | # raise NounNotImplementedError 109 | 110 | class Noun17(Noun): 111 | 112 | def __init__(self): 113 | super().__init__("Attitude (Roll, Pitch, Yaw)", number="17") 114 | 115 | def return_data(self): 116 | 117 | # FIXME: need to make sure that data is correct length (sometimes drops the last 0 when input is xxx.x rather 118 | # then xxx.xx 119 | try: 120 | roll = str(round(get_telemetry("roll"), 1)) 121 | pitch = str(round(get_telemetry("pitch"), 1)) 122 | yaw = str(round(get_telemetry("heading"), 1)) 123 | except TelemetryNotAvailable: 124 | raise 125 | 126 | roll = roll.replace(".", "") 127 | pitch = pitch.replace(".", "") 128 | yaw = yaw.replace(".", "") 129 | 130 | data = { 131 | 1: roll, 132 | 2: pitch, 133 | 3: yaw, 134 | "is_octal": False, 135 | "tooltips": [ 136 | "Roll (0xxx.x°)", 137 | "Pitch (0xxx.x°)", 138 | "Yaw (0xxx.x°)", 139 | ], 140 | } 141 | return data 142 | 143 | 144 | class Noun25(Noun): 145 | 146 | def __init__(self): 147 | 148 | super().__init__("Spacecraft mass", number="25") 149 | self.mass_whole_part = computer.noun_data["25"][0] 150 | self.mass_fractional_part = computer.noun_data["25"][1] 151 | 152 | def return_data(self): 153 | 154 | data = { 155 | 1: self.mass_whole_part, 156 | 2: self.mass_fractional_part, 157 | 3: "bbbbb", 158 | "tooltips": ["Spacecraft mass ", None, None], 159 | "is_octal": True, 160 | } 161 | return data 162 | 163 | 164 | 165 | 166 | class Noun30(Noun): 167 | 168 | def __init__(self): 169 | 170 | super().__init__("Octal Target ID (000XX)", number="30") 171 | 172 | def return_data(self): 173 | 174 | target_id = computer.noun_data["30"][0] 175 | data = { 176 | 1: target_id, 177 | 2: "", 178 | 3: "", 179 | "tooltips": ["Target Octal ID", None, None], 180 | "is_octal": True, 181 | } 182 | return data 183 | 184 | def receive_data(self, data): 185 | computer.noun_data["30"] = data 186 | 187 | class Noun31(Noun): 188 | 189 | def __init__(self): 190 | 191 | super().__init__("Stage Max Thrust", number="31") 192 | 193 | def return_data(self): 194 | 195 | 196 | data = { 197 | 1: computer.noun_data["31"][0], 198 | 2: computer.noun_data["31"][1], 199 | 3: "bbbbb", 200 | "tooltips": ["Stage Max Thrust (s) ", None, None], 201 | "is_octal": False, 202 | } 203 | return data 204 | 205 | class Noun33(Noun): 206 | 207 | def __init__(self): 208 | super().__init__("Time to Ignition (00xxx hours, 000xx minutes, 0xx.xx seconds)", number="33") 209 | 210 | def return_data(self): 211 | 212 | if not computer.next_burn: 213 | computer.program_alarm(alarm_code=115, message="No burn data loaded") 214 | return False 215 | time_until_ignition = utils.seconds_to_time(computer.next_burn.calculate_time_to_ignition()) 216 | hours = str(int(time_until_ignition["hours"])) 217 | minutes = str(int(time_until_ignition["minutes"])) 218 | seconds = str(int(time_until_ignition["seconds"])).replace(".", "") 219 | 220 | data = { 221 | 1: "-" + hours, 222 | 2: "-000" + minutes, 223 | 3: "-000" + seconds, 224 | "tooltips": [ 225 | "Time To Ignition (hhhhh)", 226 | "Time To Ignition (bbbmm)", 227 | "Time To Ignition (bbbss)", 228 | ], 229 | "is_octal": False, 230 | } 231 | return data 232 | 233 | 234 | class Noun36(Noun): 235 | 236 | def __init__(self): 237 | super().__init__("Mission Elapsed Time (MET) (dddhh, bbbmm, bss.ss)", number="36") 238 | 239 | def return_data(self): 240 | try: 241 | telemetry = get_telemetry("missionTime") 242 | except TelemetryNotAvailable: 243 | raise 244 | 245 | minutes, seconds = divmod(telemetry, 60) 246 | hours, minutes = divmod(minutes, 60) 247 | days, hours = divmod(hours, 24) 248 | 249 | days = str(int(days)).zfill(2) 250 | hours = str(int(hours)).zfill(2) 251 | minutes = str(int(minutes)).zfill(2) 252 | seconds = str(round(seconds, 2)).replace(".", "").zfill(4) 253 | data = { 254 | 1: days + "b" + hours, 255 | 2: "bbb" + minutes, 256 | 3: "b" + seconds, 257 | "tooltips": [ 258 | "Mission Elapsed Time (ddbhh)", 259 | "Mission Elapsed Time (bbbmm)", 260 | "Mission Elapsed Time (bss.ss)", 261 | ], 262 | "is_octal": False, 263 | } 264 | return data 265 | 266 | class Noun38(Noun): 267 | 268 | def __init__(self): 269 | 270 | super().__init__("Specific Impulse", number="38") 271 | 272 | def return_data(self): 273 | 274 | 275 | data = { 276 | 1: computer.noun_data["38"][0], 277 | 2: "bbbbb", 278 | 3: "bbbbb", 279 | "tooltips": ["Stage Specific Impulse (s) ", None, None], 280 | "is_octal": False, 281 | } 282 | return data 283 | 284 | #-----------------------BEGIN MIXED NOUNS-------------------------------------- 285 | 286 | class Noun40(Noun): 287 | 288 | def __init__(self): 289 | super().__init__("Burn Data (Time from ignition, orbital velocity, accumulated Δv", number="40") 290 | 291 | def return_data(self): 292 | if not computer.next_burn: 293 | computer.program_alarm(115) 294 | return False 295 | burn = computer.next_burn 296 | time_to_ignition = utils.seconds_to_time(burn.time_until_ignition) 297 | minutes_to_ignition = str(int(time_to_ignition["minutes"])).zfill(2) 298 | seconds_to_ignition = str(int(time_to_ignition["seconds"])).zfill(2) 299 | velocity = str(int(get_telemetry("orbitalVelocity"))).replace(".", "") 300 | accumulated_delta_v = str(int(burn.accumulated_delta_v)).replace(".", "") 301 | 302 | data = { 303 | 1: "-" + minutes_to_ignition + "b" + seconds_to_ignition, 304 | 2: velocity, 305 | 3: accumulated_delta_v, 306 | "is_octal": False, 307 | "tooltips": [ 308 | "Time From Ignition (mmbss minutes, seconds)", 309 | "Orbital Velocity (xxxxx m/s)", 310 | "Accumulated Δv (xxxxx m/s)", 311 | ], 312 | } 313 | return data 314 | 315 | class Noun43(Noun): 316 | 317 | def __init__(self): 318 | super().__init__("Geographic Position (Latitude, Longitude, Altitude)", number="43") 319 | 320 | def return_data(self): 321 | try: 322 | # latitude = str(round(get_telemetry("lat"), 2)).replace(".", "").zfill(5) 323 | # longitude = str(round(get_telemetry("long"), 2)).replace(".", "").zfill(5) 324 | # altitude = str(round(get_telemetry("altitude") / 1000, 1)).replace(".", "").zfill(5) 325 | latitude = str(round(get_telemetry("lat"), 2)) 326 | longitude = str(round(get_telemetry("long"), 2)) 327 | altitude = str(round(get_telemetry("altitude") / 1000, 1)) 328 | except TelemetryNotAvailable: 329 | raise 330 | 331 | # the following fixes a problem that round() will discard a trailing 0 eg 100.10 becomes 100.1 332 | if latitude[-2] == ".": 333 | latitude += "0" 334 | if longitude[-2] == ".": 335 | longitude += "0" 336 | 337 | latitude = latitude.replace(".", "") 338 | longitude = longitude.replace(".", "") 339 | altitude = altitude.replace(".", "") 340 | 341 | data = { 342 | 1: latitude, 343 | 2: longitude, 344 | 3: altitude, 345 | "is_octal": False, 346 | "tooltips": [ 347 | "Latitude (xxx.xx°)", 348 | "Longitude (xxx.xx°)", 349 | "Altitude", # TODO 350 | ], 351 | } 352 | return data 353 | 354 | class Noun44(Noun): 355 | def __init__(self): 356 | super().__init__("Apoapsis (xxx.xx km), Periapsis (xxx.xx km), Time To Apoapsis (hmmss)", 357 | number="44") 358 | 359 | def return_data(self): 360 | try: 361 | apoapsis = str(round(get_telemetry("ApA") / 100, 1)) 362 | periapsis = str(round(get_telemetry("PeA") / 100, 1)) 363 | tff = int(get_telemetry("timeToAp")) 364 | except TelemetryNotAvailable: 365 | raise 366 | 367 | apoapsis = apoapsis.replace(".", "") 368 | periapsis = periapsis.replace(".", "") 369 | 370 | tff_minutes, tff_seconds = divmod(tff, 60) 371 | tff_hours, tff_minutes = divmod(tff_minutes, 60) 372 | 373 | tff = str(tff_hours).zfill(1) + str(tff_minutes).zfill(2) + str(tff_seconds).zfill(2) 374 | 375 | data = { 376 | 1: apoapsis, 377 | 2: periapsis, 378 | 3: tff, 379 | "tooltips": [ 380 | "Apoapsis Altitude (xxx.xx km)", 381 | "Periapsis Altitude (xxx.xx km)", 382 | "Time to Apoapsis (hmmss)" 383 | ], 384 | "is_octal": False, 385 | } 386 | return data 387 | 388 | 389 | #class Noun49(Noun): 390 | #def __init__(self): 391 | #super().__init__("Phase angles for automaneuver", number="49") 392 | # 393 | #def return_data(self): 394 | ## check that the maneuver has phase angles loaded 395 | #try: 396 | #if not computer.next_burn.calling_program and not computer.next_burn.calling_program.phase_angle_required: 397 | #computer.program_alarm(120) 398 | #return False 399 | #except AttributeError: 400 | #computer.program_alarm(120) 401 | #return False 402 | # 403 | #phase_angle_required = computer.next_burn.calling_program.phase_angle_required 404 | #telemachus_target_id = config.TELEMACHUS_BODY_IDS[computer.next_burn.calling_program.target_name] 405 | #current_phase_angle = get_telemetry("body_phaseAngle", body_number=telemachus_target_id) 406 | #phase_angle_difference = str(round(current_phase_angle - phase_angle_required, 1)).replace(".", "") 407 | #current_phase_angle = str(round(current_phase_angle, 1)).replace(".", "") 408 | #phase_angle_required = str(round(phase_angle_required, 1)).replace(".", "") 409 | # 410 | #data = { 411 | #1: phase_angle_required, 412 | #2: current_phase_angle, 413 | #3: phase_angle_difference, 414 | #"tooltips": [ 415 | #"Phase Angle Required (0xxx.x °)", 416 | #"Current Phase Angle (0xxx.x °)", 417 | #"Phase Angle Difference (0xxx.x °)", 418 | #], 419 | #"is_octal": False, 420 | #} 421 | #return data 422 | 423 | 424 | class Noun50(Noun): 425 | def __init__(self): 426 | super().__init__("Surface Velocity Display (X, Y, Z in xxxx.x m/s)", number="50") 427 | 428 | def return_data(self): 429 | surface_velocity_x = str(round(get_telemetry("surfaceVelocityx"), 1)).replace(".", "") 430 | surface_velocity_y = str(round(get_telemetry("surfaceVelocityy"), 1)).replace(".", "") 431 | surface_velocity_z = str(round(get_telemetry("surfaceVelocityz"), 1)).replace(".", "") 432 | 433 | data = { 434 | 1: surface_velocity_x, 435 | 2: surface_velocity_y, 436 | 3: surface_velocity_z, 437 | "tooltips": [ 438 | "Surface Velocity X (xxxx.x m/s)", 439 | "Surface Velocity Y (xxxx.x m/s)", 440 | "Surface Velocity Z (xxxx.x m/s)" 441 | ], 442 | "is_octal": False, 443 | } 444 | return data 445 | 446 | 447 | class Noun62(Noun): 448 | def __init__(self): 449 | super().__init__("Orbital Velocity, Altitude Rate, Altitude", number="62") 450 | 451 | def return_data(self): 452 | surface_velocity = str(round(get_telemetry("relativeVelocity"), 1)) 453 | altitude_rate = str(round(get_telemetry("verticalSpeed"), 1)) 454 | altitude = str(round(get_telemetry("altitude") / 1000, 1)) 455 | 456 | surface_velocity = surface_velocity.replace(".", "") 457 | altitude_rate = altitude_rate.replace(".", "") 458 | altitude = altitude.replace(".", "") 459 | 460 | data = { 461 | 1: surface_velocity, 462 | 2: altitude_rate, 463 | 3: altitude, 464 | "is_octal": False, 465 | "tooltips": [ 466 | "Inertial Velocity (xxxx.x m/s)", 467 | "Altitude Rate (xxxx.x m/s)", 468 | "Altitude (xxxx.x km)", 469 | ], 470 | } 471 | return data 472 | 473 | 474 | class Noun95(Noun): 475 | 476 | def __init__(self): 477 | super().__init__(description="TMI Burn Data Display", number="95") 478 | 479 | def return_data(self): 480 | 481 | if not computer.next_burn: 482 | computer.program_alarm(115) 483 | return False 484 | 485 | time_to_ignition = utils.seconds_to_time(computer.next_burn.time_until_ignition) 486 | minutes_to_ignition = str(int(time_to_ignition["minutes"])).zfill(2) 487 | seconds_to_ignition = str(int(time_to_ignition["seconds"])).zfill(2) 488 | delta_v = str(int(computer.next_burn.delta_v_required)) 489 | burn_duration = str(int(computer.next_burn.burn_duration)) 490 | 491 | data = { 492 | 1: "-" + minutes_to_ignition + "b" + seconds_to_ignition, 493 | 2: delta_v, 494 | 3: burn_duration, 495 | "is_octal": False, 496 | "tooltips": [ 497 | "Time To Ignition (TIG) (xxbxx mins, seconds)", 498 | "Δv (xxxxx m/s)", 499 | "Burn duration (xxxxx seconds)", 500 | ], 501 | } 502 | return data 503 | 504 | # generate a OrderedDict of all nouns for inclusion in the computer 505 | 506 | # JRI many verbs access nouns['xx'] without first testing 507 | # if we have a class for Nounxx. As a workaround, create a container class 508 | # here that dynamically creates a new subclass of Noun named Nounxx as needed 509 | class OrderedDictDefaultNoun(OrderedDict): 510 | 511 | def __init__(self): 512 | super().__init__() 513 | 514 | def __getitem__(self, name): 515 | try: 516 | return super().__getitem__(name) 517 | except KeyError: 518 | self[name] = type("Noun" + name, (Noun,), {'__init__' : lambda self: Noun.__init__(self, description='Undefined', number=name)}) 519 | return self[name] 520 | 521 | nouns = OrderedDictDefaultNoun() 522 | clsmembers = inspect.getmembers(sys.modules[__name__], inspect.isclass) 523 | for class_tuple in clsmembers: 524 | if class_tuple[0][-1].isdigit(): 525 | nouns[class_tuple[0][-2:]] = class_tuple[1] 526 | -------------------------------------------------------------------------------- /basagc/programs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ This module contains all programs (major modes) used by the guidance computer.""" 3 | 4 | import inspect 5 | import sys 6 | import math 7 | from collections import OrderedDict 8 | 9 | 10 | from PyQt5.QtCore import QTimer 11 | 12 | from basagc import config 13 | if config.DEBUG: 14 | from pudb import set_trace # lint:ok 15 | 16 | from basagc import utils, maneuver 17 | 18 | from basagc.maneuver import Burn 19 | from basagc.telemachus import get_telemetry, KSPNotConnected, check_connection 20 | 21 | 22 | class Program(object): 23 | 24 | """ Major mode base class. 25 | """ 26 | computer = None 27 | 28 | def __init__(self, description, number): 29 | 30 | """ Class constructor. 31 | :param description: description of the program 32 | :param number: program number 33 | :return: None 34 | """ 35 | self.computer = Program.computer 36 | self.description = description 37 | self.number = number 38 | 39 | def execute(self): 40 | 41 | """ Executes the program. 42 | :return: None 43 | """ 44 | 45 | utils.log("Executing Program {}: {}".format(self.number, self.description)) 46 | self.computer.flash_comp_acty(500) 47 | self.computer.dsky.set_register(self.number, "program") 48 | self.computer.running_program = self 49 | 50 | def terminate(self): 51 | 52 | """Terminates the program""" 53 | 54 | if self.computer.running_program == self: 55 | self.computer.running_program = None 56 | 57 | def restart(self): 58 | 59 | """ Restarts the program if required by program alarms. 60 | :return: None 61 | """ 62 | 63 | self.execute() 64 | 65 | def __str__(self): 66 | return "Program {} ({}) ".format(self.number, self.description) 67 | 68 | 69 | class Program00(Program): 70 | 71 | """ AGC Idling. 72 | :return: None 73 | """ 74 | 75 | def __init__(self): 76 | 77 | """ Class constructor. 78 | :return: None 79 | """ 80 | 81 | super(Program00, self).__init__(description="AGC Idling", number="00") 82 | 83 | 84 | class Program01(Program): 85 | 86 | ''' 87 | Prelaunch or service - Initialization program 88 | 89 | ''' 90 | 91 | def __init__(self): 92 | 93 | """ Class constructor. 94 | :return: None 95 | """ 96 | super().__init__(description="Prelaunch or service - Initialization program", number="01") 97 | self.timer = QTimer() 98 | self.timer.timeout.connect(self.timeout) 99 | 100 | def execute(self): 101 | 102 | """ Executes the program. 103 | :return: None 104 | """ 105 | 106 | super().execute() 107 | if check_connection() == False: 108 | Program.computer.poodoo_abort(111) 109 | self.terminate() 110 | return 111 | Program.computer.imu.on() 112 | self.timer.start(10000) 113 | 114 | def timeout(self): 115 | ''' 116 | called when timer hits 0 117 | :returns: None 118 | ''' 119 | 120 | Program.computer.imu.set_fine_align() 121 | Program.computer.execute_program("02") 122 | 123 | 124 | class Program02(Program): 125 | ''' 126 | Waits until liftoff is detected, blanks display and starts P11 127 | ''' 128 | 129 | def __init__(self): 130 | 131 | """ Class constructor. 132 | :return: None 133 | """ 134 | super().__init__(description="Prelaunch or service - Gyrocompassing program", number="02") 135 | self.timer = QTimer() 136 | self.timer.timeout.connect(self.timeout) 137 | 138 | def execute(self): 139 | 140 | """ Executes the program. 141 | :return: None 142 | """ 143 | super().execute() 144 | Program.computer.add_to_mainloop(self.check_for_liftoff) 145 | 146 | def check_for_liftoff(self): 147 | if get_telemetry("verticalSpeed") > 1: 148 | utils.log("Liftoff discrete") 149 | Program.computer.remove_from_mainloop(self.check_for_liftoff) 150 | 151 | # Clear display 152 | for register in ["verb", "noun", "program", "data_1", "data_2", "data_3"]: 153 | Program.computer.dsky.blank_register(register) 154 | # pause for 1 second, then run P11 155 | self.timer.start(1000) 156 | 157 | def timeout(self): 158 | self.timer.stop() 159 | Program.computer.execute_program("11") 160 | 161 | class Program11(Program): 162 | 163 | """ Earth Orbit Insertion Monitor. 164 | :return: None 165 | """ 166 | 167 | def __init__(self): 168 | 169 | """ Class constructor. 170 | :return: None 171 | """ 172 | 173 | super().__init__(description="Earth Orbit Insertion Monitor", number="11") 174 | 175 | def execute(self): 176 | 177 | """ Executes the program. 178 | :return: None 179 | """ 180 | 181 | super().execute() 182 | utils.log("Program 11 executing", log_level="INFO") 183 | 184 | # test if KSP is connected 185 | if check_connection() == False: 186 | return 187 | 188 | # --> call average G integration with ΔV integration 189 | # self.computer.run_average_g_routine = True 190 | 191 | # --> terminate gyrocompassing 192 | if "02" in self.computer.running_programs: 193 | self.computer.programs["02"].terminate() 194 | 195 | # --> compute initial state vector 196 | # self.computer.routines["average_g"]() 197 | 198 | # --> Display on DSKY: 199 | # --> V06 N62 (we are going to use V16N62 though, so we can have a updated display 200 | # --> R1: Velocity 201 | # --> R2: Rate of change of vehicle altitude 202 | # --> R3: Vehicle altitude in km to nearest .1 km 203 | self.computer.execute_verb(verb="16", noun="62") 204 | 205 | 206 | class Program15(Program): 207 | 208 | """ Calculates TMI burn 209 | :return: None 210 | """ 211 | 212 | def __init__(self): 213 | 214 | """ Class constructor. 215 | :return: None 216 | """ 217 | # sequence of events: 218 | # V37E15E 219 | # Flashing V01N30 displays target octal ID 220 | # PRO to accept, V21 to change 221 | # Display blanks for 5 seconds at TIG - 105 seconds 222 | # Display V16N95 223 | # at TIG - 10 seconds: Flashing V99 224 | # if proceed: execute maneuver 225 | 226 | # FIXME: this program should only *calculate* the maneuver, the actual execution of the burn should be 227 | # FIXME: performed by P40 228 | 229 | # TODO: scale final altitude based on crafts TWR 230 | # TODO: request twr from user 231 | 232 | super().__init__(description="TMI Calculate", number="15") 233 | 234 | 235 | def execute(self): 236 | 237 | """ Entry point for the program 238 | :return: None 239 | """ 240 | 241 | super().execute() 242 | 243 | # if no connection to KSP, do P00DOO abort 244 | if not check_connection(): 245 | self.computer.poodoo_abort(111) 246 | self.terminate() 247 | return 248 | 249 | # check that orbital parameters are within range to conduct burn 250 | is_orbit_ok = maneuver.HohmannTransfer.check_orbital_parameters() 251 | if is_orbit_ok == True: 252 | # request mass 253 | self.computer.execute_verb(verb="21", noun="25") 254 | self.computer.dsky.request_data(requesting_object=self._accept_initial_mass_whole_part, display_location="data_1") 255 | else: 256 | self.computer.poodoo_abort(is_orbit_ok[1]) 257 | 258 | def _accept_initial_mass_whole_part(self, mass): 259 | Program.computer.noun_data["25"][0] = mass 260 | self.computer.execute_verb(verb="22", noun="25") 261 | self.computer.dsky.request_data(requesting_object=self._accept_initial_mass_fractional_part, display_location="data_2") 262 | 263 | def _accept_initial_mass_fractional_part(self, mass): 264 | Program.computer.noun_data["25"][1] = mass 265 | self.computer.execute_verb(verb="21", noun="31") 266 | self.computer.dsky.request_data(requesting_object=self._accept_thrust_whole_part, display_location="data_1") 267 | 268 | def _accept_thrust_whole_part(self, thrust): 269 | Program.computer.noun_data["31"][0] = thrust 270 | self.computer.execute_verb(verb="22", noun="31") 271 | self.computer.dsky.request_data(requesting_object=self._accept_thrust_fractional_part, display_location="data_2") 272 | 273 | def _accept_thrust_fractional_part(self, thrust): 274 | Program.computer.noun_data["31"][1] = thrust 275 | self.computer.execute_verb(verb="21", noun="38") 276 | self.computer.dsky.request_data(requesting_object=self._accept_isp, display_location="data_1") 277 | 278 | def _accept_isp(self, isp): 279 | Program.computer.noun_data["38"][0] = isp 280 | self.calculate_maneuver() 281 | 282 | def calculate_maneuver(self): 283 | 284 | """ Calculates the maneuver parameters and creates a Burn object 285 | :return: Nothing 286 | """ 287 | self.maneuver = maneuver.HohmannTransfer() 288 | self.maneuver.execute() 289 | # display burn parameters and go to poo 290 | self.computer.execute_verb(verb="06", noun="95") 291 | self.computer.go_to_poo() 292 | 293 | class Program31(Program): 294 | ''' 295 | Mun Orbital Insertion (MOI) burn calculator 296 | ''' 297 | def __init__(self): 298 | 299 | """ Class constructor. 300 | :return: None 301 | """ 302 | super().__init__(description="MOI Burn Calculator", number="31") 303 | self.delta_v = Program.computer.moi_burn_delta_v 304 | self.time_of_node = get_telemetry("timeOfPeriapsisPassage") 305 | self.time_of_ignition = None 306 | 307 | def update_parameters(self): 308 | 309 | self.delta_v = Program.computer.moi_burn_delta_v 310 | self.time_of_node = get_telemetry("timeOfPeriapsisPassage") 311 | 312 | initial_mass = float(self.computer.noun_data["25"][0] + "." + self.computer.noun_data["25"][1]) 313 | thrust = float(self.computer.noun_data["31"][0] + "." + self.computer.noun_data["31"][1]) 314 | specific_impulse = float(self.computer.noun_data["38"][0]) 315 | self.duration_of_burn = maneuver.calc_burn_duration(initial_mass, thrust, specific_impulse, self.delta_v) 316 | self.time_of_ignition = self.time_of_node - (self.duration_of_burn / 2) 317 | 318 | def execute(self): # FIXME: this needs to be refactored into something better, just trying to get it working now 319 | 320 | self.computer.execute_verb(verb="21", noun="25") 321 | self.computer.dsky.request_data(requesting_object=self._accept_initial_mass_whole_part, display_location="data_1") 322 | 323 | def _accept_initial_mass_whole_part(self, mass): 324 | Program.computer.noun_data["25"][0] = mass 325 | self.computer.execute_verb(verb="22", noun="25") 326 | self.computer.dsky.request_data(requesting_object=self._accept_initial_mass_fractional_part, display_location="data_2") 327 | 328 | def _accept_initial_mass_fractional_part(self, mass): 329 | Program.computer.noun_data["25"][1] = mass 330 | self.computer.execute_verb(verb="21", noun="31") 331 | self.computer.dsky.request_data(requesting_object=self._accept_thrust_whole_part, display_location="data_1") 332 | 333 | def _accept_thrust_whole_part(self, thrust): 334 | Program.computer.noun_data["31"][0] = thrust 335 | self.computer.execute_verb(verb="22", noun="31") 336 | self.computer.dsky.request_data(requesting_object=self._accept_thrust_fractional_part, display_location="data_2") 337 | 338 | def _accept_thrust_fractional_part(self, thrust): 339 | Program.computer.noun_data["31"][1] = thrust 340 | self.computer.execute_verb(verb="21", noun="38") 341 | self.computer.dsky.request_data(requesting_object=self._accept_isp, display_location="data_1") 342 | 343 | def _accept_isp(self, isp): 344 | Program.computer.noun_data["38"][0] = isp 345 | self.calculate_maneuver() 346 | 347 | def calculate_maneuver(self): 348 | self.update_parameters() 349 | self.burn = Burn(delta_v=self.delta_v, 350 | direction="retrograde", 351 | time_of_ignition=self.time_of_of_ignition, 352 | time_of_node=self.time_of_node, 353 | calling_program=self) 354 | 355 | # load the Burn object into computer 356 | self.computer.add_burn_to_queue(self.burn, execute=False) 357 | 358 | ## display burn parameters and go to poo 359 | self.computer.execute_verb(verb="06", noun="95") 360 | self.computer.go_to_poo() 361 | 362 | class Program40(Program): 363 | 364 | ''' 365 | Controls a SPS (Service Propulsion System) burn. 366 | ''' 367 | 368 | def __init__(self): 369 | ''' 370 | instance constructor. 371 | :returns: None 372 | ''' 373 | super().__init__(description="SPS Burn", number="40") 374 | self.burn = self.computer.next_burn 375 | 376 | def execute(self): 377 | ''' 378 | Executes the program 379 | :returns: None 380 | ''' 381 | super().execute() 382 | # if TIG < 2 mins away, abort burn 383 | if utils.seconds_to_time(self.burn.time_until_ignition)["minutes"] < 2: 384 | self.computer.remove_burn() 385 | self.computer.poodoo_abort(226) 386 | return 387 | # if time to ignition if further than a hour away, display time to ignition 388 | if utils.seconds_to_time(self.burn.time_until_ignition)["hours"] > 0: 389 | utils.log("TIG > 1 hour away") 390 | self.computer.execute_verb(verb="16", noun="33") 391 | self.computer.main_loop_table.append(self._ten_minute_monitor) 392 | else: 393 | utils.log("TIG < 1 hour away, enabling burn") 394 | self.burn.execute() 395 | 396 | def _ten_minute_monitor(self): 397 | ''' 398 | Part of the sequence of P40 399 | :returns: None 400 | ''' 401 | if utils.seconds_to_time(self.burn.time_until_ignition)["minutes"] < 10: 402 | self.computer.main_loop_table.remove(self._ten_minute_monitor) 403 | self.burn.execute() 404 | 405 | def terminate(self): 406 | ''' 407 | Terminates the program. 408 | :returns: None 409 | ''' 410 | super().terminate() 411 | self.burn.terminate() 412 | 413 | class ProgramNotImplementedError(Exception): 414 | 415 | """ This exception is raised when the selected program hasn't been implemented yet. 416 | """ 417 | 418 | pass 419 | 420 | 421 | programs = OrderedDict() 422 | clsmembers = inspect.getmembers(sys.modules[__name__], inspect.isclass) 423 | for class_tuple in clsmembers: 424 | if class_tuple[0][-1].isdigit(): 425 | programs[class_tuple[0][-2:]] = class_tuple[1] 426 | -------------------------------------------------------------------------------- /basagc/routines.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """This module contains internal routines used by the guidance computer.""" 3 | 4 | 5 | from basagc import utils, config 6 | if config.DEBUG: 7 | from pudb import set_trace # lint:ok 8 | 9 | def charin(keypress, state, dsky, computer): 10 | ''' 11 | This function is called whenever a keypress is sent from the UI. 12 | :param keypress: What key was pressed 13 | :type keypress: str 14 | :param state: the dsky and computer state relevant to dsky operations 15 | :type state: dict 16 | :param dsky: the instance of the dsky 17 | :type dsky: basagc.dsky.DSKY 18 | :param computer: the instance of the computer 19 | :type computer: basagc.computer.Computer 20 | :returns: None 21 | ''' 22 | 23 | def handle_control_register_load(): 24 | 25 | """ Handles control register loading 26 | :returns: None 27 | """ 28 | # we are expecting a numeric digit as input 29 | if keypress.isalpha(): 30 | computer.operator_error("Expecting numeric input") 31 | return 32 | # otherwise, add the input to buffer 33 | display_register = state["display_location_to_load"] 34 | if state["register_index"] == 0: 35 | dsky.set_register(keypress, display_register, "1") 36 | #display_register["1"].display(keypress) 37 | state["input_data_buffer"] = keypress 38 | state["register_index"] += 1 39 | else: 40 | dsky.set_register(keypress, display_register, "2") 41 | state["register_index"] = 0 42 | state["input_data_buffer"] += keypress 43 | 44 | 45 | def handle_data_register_load(): 46 | 47 | """ Handles data register loading 48 | :return: None 49 | """ 50 | if keypress.isdigit() == False: 51 | utils.log("Expecting a digit for data load, got {}".format(keypress), log_level="ERROR") 52 | return 53 | display_register = state["display_location_to_load"] 54 | if state["register_index"] == 0: 55 | if keypress == "+": 56 | dsky.set_register("+", display_register) 57 | elif keypress == "-": 58 | dsky.set_register("-", display_register) 59 | else: 60 | dsky.set_register("b", display_register) 61 | state["register_index"] += 1 62 | if 1 <= state["register_index"] <= 5: 63 | dsky.set_register(keypress, display_register, digit=state["register_index"]) 64 | if state["register_index"] >= 5: 65 | state["register_index"] = 0 66 | else: 67 | state["register_index"] += 1 68 | state["input_data_buffer"] += keypress 69 | 70 | def handle_expected_data(): 71 | 72 | """ Handles expected data entry. 73 | :return: None 74 | """ 75 | #set_trace() 76 | if keypress == "P": 77 | dsky.verb_noun_flash_off() 78 | utils.log("Proceeding without input, calling {}".format(str(state["object_requesting_data"]))) 79 | state["object_requesting_data"]("proceed") 80 | state["input_data_buffer"] = "" 81 | state["is_expecting_data"] = False 82 | return 83 | 84 | # if we receive ENTER, the load is complete and we will call the 85 | # program or verb requesting the data load 86 | 87 | elif keypress == "E": 88 | input_data = state["input_data_buffer"] 89 | state["input_data_buffer"] = "" 90 | state["is_expecting_data"] = False 91 | dsky.verb_noun_flash_off() 92 | data_requester = state["object_requesting_data"] 93 | utils.log("Data load complete, calling {}({})".format(data_requester.__self__, data_requester.__name__)) 94 | data_requester(input_data) 95 | 96 | return 97 | if state["display_location_to_load"] in ["verb", "noun", "program"]: 98 | handle_control_register_load() 99 | else: 100 | handle_data_register_load() 101 | 102 | # if the user as entered anything other than a numeric d, 103 | # trigger a OPR ERR and recycle program 104 | if keypress.isalpha(): 105 | # if a program is running, recycle it 106 | # INSERT TRY HERE!!! 107 | # computer.get_state("running_program").terminate() 108 | # INSERT EXCEPT HERE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 109 | # if a verb is running, recycle it 110 | # computer.get_state("running_verb").terminate() 111 | computer.operator_error("Expecting numeric input") 112 | return 113 | #else: 114 | #print(state["input_data_buffer"]) 115 | #state["input_data_buffer"] += keypress 116 | #print(state["input_data_buffer"]) 117 | #register = state["display_location_to_load"] 118 | #dsky.set_register(state["input_data_buffer"], register) 119 | 120 | 121 | 122 | def handle_verb_entry(): 123 | 124 | """ Handles verb entry 125 | :return: None 126 | """ 127 | 128 | if keypress == "C": # user has pushed CLEAR 129 | state["verb_position"] = 0 130 | state["requested_verb"] = "" 131 | dsky.blank_register("verb") 132 | dsky.blank_register("noun") 133 | #dsky.control_registers["verb"].digits[1].display("blank") 134 | #dsky.control_registers["verb"].digits[2].display("blank") 135 | return 136 | 137 | if keypress == "N": # user has finished entering verb 138 | state["is_verb_being_loaded"] = False 139 | state["is_noun_being_loaded"] = True 140 | state["verb_position"] = 0 141 | elif keypress == "E": 142 | state["is_verb_being_loaded"] = False 143 | state["verb_position"] = 0 144 | elif keypress.isalpha(): 145 | computer.operator_error("Expected a number for verb choice") 146 | return 147 | elif state["verb_position"] == 0: 148 | dsky.set_register(value=keypress, register="verb", digit="1") 149 | state["requested_verb"] = keypress 150 | state["verb_position"] = 1 151 | elif state["verb_position"] == 1: 152 | dsky.set_register(value=keypress, register="verb", digit="2") 153 | state["requested_verb"] += keypress 154 | state["verb_position"] = 2 155 | 156 | def handle_noun_entry(): 157 | 158 | """ Handles noun entry. 159 | :return: None 160 | """ 161 | 162 | if keypress == "C": # user has pushed CLEAR 163 | state["noun_position"] = 0 164 | state["requested_noun"] = "" 165 | dsky.control_registers["noun"].digits[1].display("blank") 166 | dsky.control_registers["noun"].digits[2].display("blank") 167 | return 168 | 169 | if keypress == "N": # user has finished entering noun 170 | state["is_noun_being_loaded"] = False 171 | state["is_verb_being_loaded"] = True 172 | state["noun_position"] = 0 173 | elif keypress == "E": 174 | state["is_noun_being_loaded"] = False 175 | state["noun_position"] = 0 176 | elif keypress.isalpha(): 177 | computer.operator_error("Expected a number for noun choice") 178 | return 179 | elif state["noun_position"] == 0: 180 | dsky.set_register(keypress, "noun", digit="1") 181 | state["requested_noun"] = keypress 182 | state["noun_position"] = 1 183 | elif state["noun_position"] == 1: 184 | dsky.set_register(keypress, "noun", digit="2") 185 | state["requested_noun"] += keypress 186 | state["noun_position"] = 2 187 | 188 | def handle_entr_keypress(): 189 | 190 | """ Handles ENTR keypress 191 | :return: None 192 | """ 193 | 194 | computer.execute_verb() 195 | state["requested_noun"] = "" 196 | 197 | def handle_reset_keypress(): 198 | 199 | """ Handles RSET keypress 200 | :return: None 201 | """ 202 | 203 | computer.reset_alarm_codes() 204 | dsky.reset_annunciators() 205 | if dsky.annunciators["opr_err"].blink_timer.isActive(): 206 | dsky.annunciators["opr_err"].stop_blink() 207 | 208 | def handle_noun_keypress(): 209 | 210 | """ Handles NOUN keypress 211 | :return: None 212 | """ 213 | 214 | state["is_verb_being_loaded"] = False 215 | state["is_noun_being_loaded"] = True 216 | state["requested_noun"] = "" 217 | dsky.blank_register("noun") 218 | 219 | def handle_verb_keypress(): 220 | 221 | """ Handles VERB keypress 222 | :return: None 223 | """ 224 | 225 | state["is_noun_being_loaded"] = False 226 | state["is_verb_being_loaded"] = True 227 | state["requested_verb"] = "" 228 | dsky.blank_register("verb") 229 | 230 | def handle_key_release_keypress(): 231 | 232 | """ Handles KEY REL keypress 233 | :return: None 234 | """ 235 | if state["backgrounded_update"]: 236 | backgrounded_update = state["backgrounded_update"].resume 237 | if state["display_lock"]: 238 | state["display_lock"].terminate() 239 | dsky.stop_annunciator_blink("key_rel") 240 | backgrounded_update() 241 | state["backgrounded_update"] = None 242 | state["is_verb_being_loaded"] = False 243 | state["is_noun_being_loaded"] = False 244 | state["is_data_being_loaded"] = False 245 | state["verb_position"] = 0 246 | state["noun_position"] = 0 247 | state["requested_verb"] = "" 248 | state["requested_noun"] = "" 249 | return 250 | 251 | # if the computer is off, we only want to accept the PRO key input, 252 | # all other keys are ignored 253 | if computer.is_powered_on == False: 254 | if keypress == "P": 255 | computer.on() 256 | else: 257 | utils.log("Key {} ignored because gc is off".format(keypress)) 258 | return 259 | 260 | if state["is_expecting_data"]: 261 | handle_expected_data() 262 | return 263 | 264 | if keypress == "R": 265 | handle_reset_keypress() 266 | return 267 | 268 | if keypress == "K": 269 | handle_key_release_keypress() 270 | return 271 | 272 | if state["display_lock"]: 273 | state["display_lock"].background() 274 | 275 | if state["is_verb_being_loaded"]: 276 | handle_verb_entry() 277 | 278 | elif state["is_noun_being_loaded"]: 279 | handle_noun_entry() 280 | 281 | if keypress == "E": 282 | handle_entr_keypress() 283 | 284 | if keypress == "V": 285 | handle_verb_keypress() 286 | 287 | if keypress == "N": 288 | handle_noun_keypress() 289 | 290 | if keypress == "C": 291 | pass # TODO 292 | 293 | -------------------------------------------------------------------------------- /basagc/telemachus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """This module contains code that interacts with the Telemachus mod to access KSP telemetry""" 3 | 4 | import json 5 | import urllib.error 6 | import urllib.parse 7 | import urllib.request 8 | 9 | from basagc import config 10 | from basagc import utils 11 | if config.DEBUG: 12 | from pudb import set_trace # lint:ok 13 | 14 | telemetry = {} 15 | commands = {} 16 | 17 | 18 | class TelemetryNotAvailable(Exception): 19 | """This exception should be raised when we do not have a list of available telemetry""" 20 | pass 21 | 22 | 23 | class KSPNotConnected(Exception): 24 | """ This exception should be raised when there is no connection to KSP """ 25 | pass 26 | 27 | 28 | def check_connection(): 29 | 30 | """ Checks if there is a connection available to Telemachus 31 | Returns True if so, False otherwise 32 | """ 33 | 34 | try: 35 | urllib.request.urlopen(config.URL + "paused=p.paused") 36 | except urllib.error.URLError: 37 | return False 38 | else: 39 | return True 40 | 41 | 42 | def get_api_listing(): 43 | 44 | """ Gets the list of API calls provided by Telemachus 45 | :rtype: dict 46 | """ 47 | global telemetry 48 | global commands 49 | try: 50 | response = urllib.request.urlopen(config.URL + "api=a.api") 51 | except urllib.error.URLError: 52 | raise KSPNotConnected 53 | response_string = response.read().decode('utf-8') 54 | data = json.loads(response_string) 55 | 56 | for a in data.values(): 57 | for b in a: 58 | if b["apistring"].startswith("b."): 59 | name = "body_" + b["apistring"].rsplit(".", 1)[1] 60 | elif b["apistring"].startswith("tar."): 61 | name = "target_" + b["apistring"].rsplit(".", 1)[1] 62 | elif b["apistring"].startswith("f.") or b["apistring"].startswith("mj.") or \ 63 | b["apistring"].startswith("v.set"): 64 | command = b["apistring"].rsplit(".", 1)[1] 65 | commands[command] = b["apistring"] 66 | continue 67 | else: 68 | name = b["apistring"].rsplit(".", 1)[1] 69 | telemetry[name] = b["apistring"] 70 | 71 | 72 | def get_telemetry(data, body_number=None): 73 | """ Contacts telemachus for the requested data. 74 | 75 | :param data: The API call required 76 | :type data: str | float 77 | :param body_number: Specify which body to obtain data for 78 | :type body_number: string 79 | :rtype: string 80 | """ 81 | 82 | # if telemetry is None: 83 | # raise TelemetryNotAvailable 84 | try: 85 | query_string = data + "=" + telemetry[data] 86 | except KeyError: 87 | raise KSPNotConnected 88 | return 89 | if body_number: 90 | query_string += "[{}]".format(body_number) 91 | 92 | try: 93 | raw_response = urllib.request.urlopen(config.URL + query_string) 94 | except urllib.error.URLError: 95 | utils.log("Query string: {}".format(query_string), log_level="ERROR") 96 | utils.log("Caught exception urllib2.URLERROR", log_level="ERROR") 97 | raise KSPNotConnected 98 | response_string = raw_response.read().decode("utf-8)") 99 | json_response = json.loads(response_string) 100 | return json_response[data] 101 | 102 | # def enable_smartass(): 103 | # query_string = "command=" 104 | 105 | def set_mechjeb_smartass(direction): 106 | 107 | command_string = "command=" + commands[direction] 108 | send_command_to_ksp(command_string) 109 | 110 | def disable_smartass(): 111 | command_string = "command=" + commands["smartassoff"] 112 | send_command_to_ksp(command_string) 113 | 114 | def set_throttle(throttle_percent): 115 | if throttle_percent == 0: 116 | throttle_magnitude = 0 117 | else: 118 | throttle_magnitude = throttle_percent / 100.0 119 | command_string = "command=" + commands["setThrottle"] + "[" + str(throttle_magnitude) + "]" 120 | send_command_to_ksp(command_string) 121 | 122 | def cut_throttle(): 123 | command_string = "command=" + commands["throttleZero"] 124 | send_command_to_ksp(command_string) 125 | 126 | def send_command_to_ksp(command_string): 127 | 128 | try: 129 | urllib.request.urlopen(config.URL + command_string) 130 | except urllib.error.URLError: 131 | utils.log("Query string: {}".format(command_string), log_level="ERROR") 132 | utils.log("Caught exception urllib2.URLERROR", log_level="ERROR") 133 | raise KSPNotConnected 134 | 135 | def print_all_telemetry(): 136 | print("Telemetry available:") 137 | for item in sorted(telemetry): 138 | print("- " + item) 139 | print() 140 | print("Commands available:") 141 | for item in sorted(commands): 142 | print("- " + item) 143 | 144 | def add_maneuver_node(ut, delta_v): 145 | 146 | ut = str(round(ut, 2)) 147 | delta_v_x = str(round(delta_v[0], 2)) 148 | delta_v_y = str(round(delta_v[1], 2)) 149 | delta_v_z = str(round(delta_v[2], 2)) 150 | command_string = "command=" + telemetry["addManeuverNode"] + "[" + str(ut) + "," + delta_v_x + "," + delta_v_y + "," + delta_v_z + "]" 151 | send_command_to_ksp(command_string) 152 | 153 | def update_maneuver_node(ut, delta_v): 154 | ut = str(round(ut, 2)) 155 | delta_v_x = str(round(delta_v[0], 2)) 156 | delta_v_y = str(round(delta_v[1], 2)) 157 | delta_v_z = str(round(delta_v[2], 2)) 158 | command_string = "command=" + telemetry["updateManeuverNode"] + "[0," + str(ut) + "," + delta_v_x + "," + delta_v_y + "," + delta_v_z + "]" 159 | send_command_to_ksp(command_string) -------------------------------------------------------------------------------- /basagc/uplink.txt: -------------------------------------------------------------------------------- 1 | V37E15E 2 | 00019E 3 | 73000E 4 | 00206E 5 | 30000E 6 | 00345E 7 | -------------------------------------------------------------------------------- /basagc/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ This module contains various utility functions and classes used by basaGC""" 3 | 4 | import logging 5 | import time 6 | 7 | from basagc import config 8 | if config.DEBUG: 9 | from pudb import set_trace # lint:ok 10 | 11 | LOG_VIEWER = None 12 | 13 | logging.basicConfig(level=logging.DEBUG, 14 | format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s', 15 | datefmt='%d/%m/%y %H:%M', 16 | filename='../gc.log', 17 | filemode='a') 18 | 19 | gc_log = logging.getLogger() 20 | 21 | 22 | def seconds_to_time(seconds): 23 | 24 | """ Converts a time in seconds to days, hours, minutes and seconds 25 | :param seconds: time in seconds to convert 26 | :type seconds: int or float 27 | :return: tuple containing days, hours, minutes, seconds 28 | :rtype: tuple of ints 29 | """ 30 | 31 | minutes, seconds = divmod(seconds, 60) 32 | hours, minutes = divmod(minutes, 60) 33 | days, hours = divmod(hours, 24) 34 | return { 35 | "days": days, 36 | "hours": hours, 37 | "minutes": minutes, 38 | "seconds": round(seconds, 2), 39 | } 40 | 41 | 42 | def log(message="", log_level="DEBUG"): 43 | 44 | """ Logs messages to log file and log viewer 45 | :param message: the message to log 46 | :type message: string 47 | :param log_level: the log level for this message 48 | :type log_level: string 49 | :return: nothing 50 | """ 51 | 52 | if log_level not in config.LOG_LEVELS: 53 | gc_log.error("Log level does not exist!") 54 | return 55 | now = time.strftime("%d/%m/%Y %H:%M:%S") 56 | 57 | if log_level == "DEBUG": 58 | gc_log.debug(message) 59 | elif log_level == "INFO": 60 | gc_log.info(message) 61 | elif log_level == "WARNING": 62 | gc_log.warning(message) 63 | elif log_level == "ERROR": 64 | gc_log.error(message) 65 | elif log_level == "CRITICAL": 66 | gc_log.critical(message) 67 | 68 | # since there is no logging window yet, print message to stdout 69 | print("{:20}{:10}{}".format(now, log_level, message)) 70 | -------------------------------------------------------------------------------- /basagc/verbs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ This module contains classes of all of the verbs used by basaGC.""" 3 | 4 | import inspect 5 | import logging 6 | import sys 7 | from collections import OrderedDict 8 | 9 | from PyQt5.QtCore import QTimer 10 | from basagc import config, nouns, utils, dsky 11 | from basagc.telemachus import KSPNotConnected, TelemetryNotAvailable 12 | from basagc import telemachus 13 | if config.DEBUG: 14 | from pudb import set_trace # lint:ok 15 | 16 | log = logging.getLogger("Verbs") 17 | 18 | INVALID_VERBS = [ 19 | 0, 20 | 8, 21 | 9, 22 | 10, 23 | 18, 24 | 19, 25 | 20, 26 | 26, 27 | 28, 28 | 29, 29 | 38, 30 | 39, 31 | 68, 32 | 76, 33 | 77, 34 | 79, 35 | 84, 36 | 95, 37 | 98, 38 | ] 39 | 40 | 41 | class NounNotAcceptableError(Exception): 42 | 43 | """ This exception is raised when the noun selected is not available with the verb selected.""" 44 | pass 45 | 46 | # ------------------------BEGIN BASE CLASS DEFINITIONS--------------------------- 47 | class Verb: 48 | 49 | """ Base class for verbs 50 | """ 51 | 52 | computer = None 53 | 54 | def __init__(self, name, verb_number, noun=None): 55 | 56 | """ Class constructor. 57 | :param name: the name (or description) of the verb 58 | :type name: string 59 | :param verb_number: the number of the verb. Valid ranges are 01 to 99 with some verb numbers not used 60 | :type verb_number: str 61 | """ 62 | 63 | self.computer = Verb.computer 64 | self.dsky = dsky.DSKY.dsky_instance 65 | self.name = name 66 | self.number = verb_number 67 | self.illegal_nouns = [] 68 | self.data = [] 69 | self.noun = noun 70 | 71 | def _format_output_data(self, data): 72 | 73 | """ Formats data for output to the DSKY. 74 | :param data: data to display 75 | :type data: dict 76 | :return: DSKY formatted output 77 | :rtype: list of strings 78 | """ 79 | 80 | raw_data = [data[1], data[2], data[3]] 81 | out_data = [] 82 | for item in raw_data: 83 | if not item: 84 | continue 85 | output = "" 86 | if data["is_octal"]: 87 | output = "b" 88 | output += item.zfill(5) 89 | elif item[0] == "-": 90 | output += item.zfill(6) 91 | else: 92 | output = "+" 93 | output += item.zfill(5) 94 | out_data.append(output) 95 | return out_data 96 | 97 | def execute(self): 98 | 99 | """ Executes the verb 100 | :return: 101 | """ 102 | 103 | if self.noun in self.illegal_nouns: 104 | raise NounNotAcceptableError 105 | utils.log("Executing Verb {}: {}".format(self.number, self.name)) 106 | self.dsky.current_verb = self 107 | if self.noun: 108 | Verb.computer.dsky.set_register(self.noun, "noun") 109 | utils.log(" With noun {}".format(self.noun)) # JRI 110 | 111 | # def _activity(self, event): 112 | # 113 | # """ Flashes the COMP ACTY annunciator. Not currently used. 114 | # :param event: 115 | # :return: 116 | # """ 117 | # 118 | # #dsky.flash_comp_acty() 119 | # pass 120 | 121 | def terminate(self): 122 | 123 | """ Terminates the verb 124 | """ 125 | 126 | Verb.computer.dsky.current_verb = None 127 | 128 | def receive_data(self, data): 129 | 130 | """ Allows the verb to receive requested data from the DSKY. 131 | :param data: the data sent by DSKY 132 | """ 133 | 134 | utils.log("{} received data: {}".format(self, data)) 135 | self.data = data 136 | self.execute() 137 | 138 | def __str__(self): 139 | return "Verb {} ({})".format(self.number, self.name) 140 | 141 | 142 | class ExtendedVerb(Verb): 143 | 144 | """ Base class for extended verbs (40 through 99 inclusive) 145 | """ 146 | 147 | def __init__(self, name, verb_number): 148 | ''' 149 | class constructor 150 | :param name: name of the verb 151 | :type name: str 152 | :param verb_number: the verb number 153 | :type verb_number: 154 | :returns: 155 | ''' 156 | super().__init__(name, verb_number, noun=None) 157 | 158 | 159 | class DisplayVerb(Verb): 160 | 161 | """ Base class for display verbs (verbs 01 through 07 inclusive) 162 | """ 163 | 164 | def __init__(self, name, verb_number, noun): 165 | 166 | """ Class constructor 167 | :param name: name (description) of verb 168 | :type name: string 169 | :param verb_number: the verb number 170 | :type verb_number: str 171 | :return: None 172 | """ 173 | 174 | super().__init__(name, verb_number, noun) 175 | 176 | def execute(self): 177 | 178 | """ Executes the verb 179 | :return: None 180 | """ 181 | 182 | super(DisplayVerb, self).execute() 183 | 184 | 185 | 186 | class MonitorVerb(DisplayVerb): 187 | 188 | """ Base class for Monitor verbs (verbs 11 through 17 inclusive) 189 | """ 190 | 191 | def __init__(self, name, verb_number, noun): 192 | 193 | """ Class constructor 194 | :param name: name (description) of verb 195 | :type name: string 196 | :param verb_number: the verb number 197 | :type verb_number: str 198 | :return: None 199 | """ 200 | 201 | super().__init__(name, verb_number, noun) 202 | self.timer = QTimer() 203 | self.timer.timeout.connect(self._update_display) 204 | self.is_tooltips_set = False 205 | 206 | def _send_output(self): 207 | 208 | """ Sends the requested output to the DSKY """ 209 | 210 | # check if the display update interval needs to be changed 211 | if self.timer.interval() != config.DISPLAY_UPDATE_INTERVAL: 212 | # stop and start the timer to change the update interval 213 | self.timer.stop() 214 | self.timer.start(config.DISPLAY_UPDATE_INTERVAL) 215 | 216 | if self.noun is None: 217 | self.noun = Verb.computer.keyboard_state["requested_noun"] 218 | if self.noun in self.illegal_nouns: 219 | raise NounNotAcceptableError 220 | noun_function = Verb.computer.nouns[self.noun]() 221 | try: 222 | data = noun_function.return_data() 223 | except nouns.NounNotImplementedError: 224 | self.computer.operator_error("Noun {} not implemented yet. Sorry about that...".format(self.noun)) 225 | self.terminate() 226 | return 227 | except KSPNotConnected: 228 | utils.log("KSP not connected, terminating V{}".format(self.number), 229 | log_level="ERROR") 230 | Verb.computer.program_alarm(110) 231 | self.terminate() 232 | raise 233 | except TelemetryNotAvailable: 234 | utils.log("Telemetry not available, terminating V{}".format(self.number), 235 | log_level="ERROR") 236 | Verb.computer.program_alarm(111) 237 | self.terminate() 238 | raise 239 | if not data: 240 | # if the noun returns False, the noun *should* have already raised a program alarm, so we just need to 241 | # terminate and return 242 | self.terminate() 243 | return 244 | output = self._format_output_data(data) 245 | 246 | # set tooltips 247 | if not self.is_tooltips_set: 248 | Verb.computer.dsky.set_tooltip("data_1", data["tooltips"][0]) 249 | Verb.computer.dsky.set_tooltip("data_2", data["tooltips"][1]) 250 | Verb.computer.dsky.set_tooltip("data_3", data["tooltips"][2]) 251 | 252 | self.is_tooltips_set = True 253 | 254 | # display data on DSKY registers 255 | Verb.computer.dsky.set_register(output[0], "data_1") 256 | Verb.computer.dsky.set_register(output[1], "data_2") 257 | Verb.computer.dsky.set_register(output[2], "data_3") 258 | 259 | Verb.computer.dsky.flash_comp_acty() 260 | 261 | def start_monitor(self): 262 | 263 | """ Starts the timer to monitor the verb """ 264 | 265 | # if Verb.computer.keyboard_state["backgrounded_update"] is not None: 266 | # Verb.computer.keyboard_state["backgrounded_update"].terminate() 267 | Verb.computer.keyboard_state["display_lock"] = self 268 | 269 | try: 270 | self._send_output() 271 | except KSPNotConnected: 272 | return 273 | except TelemetryNotAvailable: 274 | return 275 | if self.noun is None: #JRI if monitoring was terminated noun=None and we don't want to start the timer. 276 | return 277 | 278 | self.timer.start(config.DISPLAY_UPDATE_INTERVAL) 279 | 280 | def _update_display(self): 281 | 282 | """ a simple wrapper to call the display update method """ 283 | 284 | # if not self.activity_timer.active(): 285 | # self.activity_timer.Start(1000) 286 | self._send_output() 287 | 288 | def terminate(self): 289 | 290 | """ Terminates the verb 291 | :return: None 292 | """ 293 | 294 | utils.log("Terminating V{}".format(self.number)) 295 | Verb.computer.dsky.stop_annunciator_blink("key_rel") 296 | Verb.computer.keyboard_state["display_lock"] = None 297 | Verb.computer.keyboard_state["backgrounded_update"] = None 298 | self.timer.stop() 299 | self.noun = None 300 | # self.activity_timer.Stop() 301 | # reset tooltips to "" 302 | Verb.computer.dsky.set_tooltip("data_1", "") 303 | Verb.computer.dsky.set_tooltip("data_2", "") 304 | Verb.computer.dsky.set_tooltip("data_3", "") 305 | 306 | def background(self): 307 | 308 | """ Backgrounds verb display updates 309 | :return: None 310 | """ 311 | 312 | Verb.computer.keyboard_state["backgrounded_update"] = self 313 | Verb.computer.keyboard_state["display_lock"] = None 314 | self.timer.stop() 315 | Verb.computer.dsky.start_annunciator_blink("key_rel") 316 | 317 | 318 | def resume(self): 319 | 320 | """ Resumes verb display updates 321 | :return: None 322 | """ 323 | 324 | Verb.computer.keyboard_state["display_lock"] = self 325 | Verb.computer.keyboard_state["backgrounded_update"] = None 326 | Verb.computer.dsky.set_register(self.number, "verb") 327 | Verb.computer.dsky.set_register(self.noun, "noun") 328 | 329 | self.start_monitor() 330 | 331 | 332 | class LoadVerb(Verb): 333 | 334 | """ Base class for Load verbs (verbs 21 through 25 inclusive) 335 | """ 336 | 337 | def __init__(self, name, verb_number, noun): 338 | """ Class constructor 339 | :param name: name (description) of verb 340 | :type name: string 341 | :param verb_number: the verb number 342 | :type verb_number: int 343 | :return: None 344 | """ 345 | 346 | super().__init__(name, verb_number, noun) 347 | 348 | def accept_input(self, data): 349 | """ Accepts data provided by user via DSKY 350 | :param data: the data 351 | :return: None 352 | """ 353 | #Verb.computer.noun_data[self.noun].[append(data)] 354 | 355 | Verb.computer.noun_data[self.noun][21 - int(self.number)] = data 356 | 357 | #---------------------------BEGIN VERB CLASS DEFINITIONS------------------------ 358 | 359 | # no verb 00 360 | 361 | 362 | class Verb01(DisplayVerb): 363 | 364 | """ Displays Octal component 1 in R1 365 | """ 366 | 367 | def __init__(self, noun): 368 | 369 | """ Class constructor 370 | :return: None 371 | """ 372 | 373 | super().__init__(name="Display Octal component 1 in R1", verb_number="01", noun=noun) 374 | 375 | def execute(self): 376 | 377 | """ Executes the verb 378 | :return: None 379 | """ 380 | 381 | super().execute() 382 | noun_function = Verb.computer.nouns[self.noun]() 383 | noun_data = noun_function.return_data() 384 | if noun_data is False: 385 | # No data returned from noun, noun should have raised a program alarm, all we need to do it quit here 386 | return 387 | output = self._format_output_data(noun_data) 388 | Verb.computer.dsky.set_register(output[0], "data_1") 389 | 390 | 391 | class Verb02(DisplayVerb): 392 | 393 | """ Displays Octal component 2 in R1 394 | """ 395 | 396 | def __init__(self, noun): 397 | 398 | """ Class constructor 399 | :return: None 400 | """ 401 | 402 | super().__init__(name="Display Octal component 2 in R1", verb_number="02", noun=noun) 403 | 404 | def execute(self): 405 | 406 | """ Executes the verb 407 | :return: None 408 | """ 409 | 410 | super().execute() 411 | noun_function = Verb.computer.nouns[self.noun]() 412 | noun_data = noun_function.return_data() 413 | if noun_data is False: 414 | # No data returned from noun, noun should have raised a program alarm, all we need to do it quit here 415 | return 416 | output = self._format_output_data(noun_data) 417 | Verb.computer.dsky.set_register(output[1], "data_2") 418 | 419 | class Verb03(DisplayVerb): 420 | 421 | """ Displays Octal component 3 in R1 422 | """ 423 | 424 | def __init__(self, noun): 425 | 426 | """ Class constructor 427 | :return: None 428 | """ 429 | 430 | super().__init__(name="Display Octal component 3 in R1", verb_number="03", noun=noun) 431 | 432 | def execute(self): 433 | 434 | """ Executes the verb 435 | :return: None 436 | """ 437 | 438 | 439 | super().execute() 440 | noun_function = Verb.computer.nouns[self.noun]() 441 | noun_data = noun_function.return_data() 442 | if noun_data is False: 443 | # No data returned from noun, noun should have raised a program alarm, all we need to do it quit here 444 | return 445 | output = self._format_output_data(noun_data) 446 | Verb.computer.dsky.set_register(output[2], "data_3") 447 | 448 | class Verb04(DisplayVerb): 449 | 450 | """ Displays Octal components 1, 2 in R1, R2 451 | """ 452 | 453 | def __init__(self, noun): 454 | 455 | """ Class constructor 456 | :return: None 457 | """ 458 | 459 | super().__init__(name="Display Octal components 1, 2 in R1, R2", verb_number="04", noun=noun) 460 | 461 | def execute(self): 462 | 463 | """ Executes the verb. 464 | :return: None 465 | """ 466 | 467 | super().execute() 468 | noun_function = Verb.computer.nouns[Verb.computer.dsky.state["requested_noun"]] 469 | noun_data = noun_function(calling_verb=self) 470 | output = self._format_output_data(noun_data) 471 | Verb.computer.dsky.set_register(output[0], "data_1") 472 | Verb.computer.dsky.set_register(output[1], "data_2") 473 | 474 | 475 | class Verb05(DisplayVerb): 476 | 477 | """ Displays Octal components 1, 2, 3 in R1, R2, R3 478 | """ 479 | 480 | def __init__(self, noun): 481 | 482 | """ Class constructor 483 | :return: None 484 | """ 485 | 486 | super().__init__(name="Display Octal components 1, 2, 3 in R1, R2, R3", verb_number="05", noun=noun) 487 | self.illegal_nouns = [] 488 | 489 | def execute(self): 490 | 491 | """ Executes the verb. 492 | :return: None 493 | """ 494 | 495 | super().execute() 496 | noun_function = Verb.computer.nouns[Verb.computer.keyboard_state["requested_noun"]]() 497 | noun_data = noun_function.return_data() 498 | if not noun_data: 499 | # No data returned from noun, noun should have raised a program alarm, all we need to do it quit here 500 | return 501 | output = self._format_output_data(noun_data) 502 | Verb.computer.dsky.set_register(output[0], "data_1") 503 | Verb.computer.dsky.set_register(output[1], "data_2") 504 | Verb.computer.dsky.set_register(output[2], "data_3") 505 | 506 | 507 | class Verb06(DisplayVerb): 508 | 509 | """ Displays Decimal in R1 or in R1, R2 or in R1, R2, R3 510 | """ 511 | 512 | def __init__(self, noun): 513 | 514 | """ Class constructor 515 | :return: None 516 | """ 517 | 518 | super().__init__(name="Display Decimal in R1 or in R1, R2 or in R1, R2, R3", verb_number="06", 519 | noun=noun) 520 | 521 | def execute(self): 522 | 523 | """ Executes the verb. 524 | :return: None 525 | """ 526 | 527 | super().execute() 528 | noun_function = Verb.computer.nouns[self.noun]() 529 | noun_data = noun_function.return_data() 530 | if not noun_data: 531 | # No data returned from noun, noun should have raised a program alarm, all we need to do it quit here 532 | return 533 | output = self._format_output_data(noun_data) 534 | 535 | 536 | Verb.computer.dsky.set_tooltip("data_1", noun_data["tooltips"][0]) 537 | Verb.computer.dsky.set_tooltip("data_2", noun_data["tooltips"][1]) 538 | Verb.computer.dsky.set_tooltip("data_3", noun_data["tooltips"][2]) 539 | Verb.computer.dsky.set_register(output[0], "data_1") 540 | Verb.computer.dsky.set_register(output[1], "data_2") 541 | Verb.computer.dsky.set_register(output[2], "data_3") 542 | 543 | # no verb 8 544 | 545 | # no verb 9 546 | 547 | # no verb 10 548 | 549 | 550 | class Verb11(MonitorVerb): 551 | 552 | """ Monitors Octal component 1 in R1 553 | """ 554 | 555 | def __init__(self, noun): 556 | 557 | """ Class constructor 558 | :return: None 559 | """ 560 | 561 | super().__init__(name="Monitor Octal component 1 in R1", verb_number="11", noun=noun) 562 | 563 | 564 | class Verb12(MonitorVerb): 565 | 566 | """ Monitors Octal component 2 in R1 567 | """ 568 | 569 | def __init__(self, noun): 570 | 571 | """ Class constructor 572 | :return: None 573 | """ 574 | 575 | super().__init__(name="Monitor Octal component 2 in R1", verb_number="12", noun=noun) 576 | 577 | 578 | class Verb13(MonitorVerb): 579 | 580 | """ Monitors Octal component 3 in R1 581 | """ 582 | 583 | def __init__(self, noun): 584 | 585 | """ Class constructor 586 | :return: None 587 | """ 588 | 589 | super().__init__(name="Monitor Octal component 3 in R1", verb_number="13", noun=noun) 590 | 591 | 592 | class Verb14(MonitorVerb): 593 | 594 | """ Monitors Octal components 1, 2 in R1, R2 595 | """ 596 | 597 | def __init__(self, noun): 598 | 599 | """ Class constructor 600 | :return: None 601 | """ 602 | 603 | super().__init__(name="Monitor Octal components 1, 2 in R1, R2", verb_number="14", noun=noun) 604 | 605 | 606 | class Verb15(MonitorVerb): 607 | 608 | """ Monitors Octal components 1, 2, 3 in R1, R2, R3 609 | """ 610 | 611 | def __init__(self, noun): 612 | 613 | """ Class constructor 614 | :return: None 615 | """ 616 | 617 | super().__init__(name="Monitor Octal components 1, 2, 3 in R1, R2, R3", verb_number="15", noun=noun) 618 | 619 | 620 | class Verb16(MonitorVerb): 621 | 622 | """ Monitors Decimal in R1 or in R1, R2 or in R1, R2, R3 623 | """ 624 | 625 | def __init__(self, noun): 626 | 627 | """ Class constructor 628 | :return: None 629 | """ 630 | 631 | super().__init__(name="Monitor Decimal in R1 or in R1, R2 or in R1, R2, R3", verb_number="16", noun=noun) 632 | 633 | def execute(self): 634 | 635 | """ Executes the verb. 636 | :return: None 637 | """ 638 | super().execute() 639 | self.start_monitor() 640 | 641 | 642 | class Verb17(MonitorVerb): 643 | 644 | """ Monitors Double Precision Decimal in R1, R2 (test only) 645 | """ 646 | 647 | def __init__(self, noun): 648 | 649 | """ Class constructor 650 | :return: None 651 | """ 652 | 653 | super().__init__(name="Monitor Double Precision Decimal in R1, R2 (test only)", verb_number="17", 654 | noun=noun) 655 | 656 | # no verb 18 657 | 658 | # no verb 19 659 | 660 | # no verb 20 661 | 662 | 663 | class Verb21(LoadVerb): 664 | 665 | """ Loads component 1 into R1 666 | """ 667 | 668 | def __init__(self, noun): 669 | 670 | """ Class constructor 671 | :return: None 672 | """ 673 | 674 | super().__init__(name="Load component 1 into R1", verb_number="21", noun=noun) 675 | 676 | def execute(self): 677 | 678 | """ Executes the verb. 679 | :return: None 680 | """ 681 | super().execute() 682 | Verb.computer.dsky.request_data(self.accept_input, display_location="data_1") 683 | 684 | 685 | class Verb22(LoadVerb): 686 | 687 | """ Loads component 2 into R2 688 | """ 689 | 690 | def __init__(self, noun): 691 | 692 | """ Class constructor 693 | :return: None 694 | """ 695 | 696 | super().__init__(name="Load component 2 into R2", verb_number="22", noun=noun) 697 | 698 | def execute(self): 699 | 700 | """ Executes the verb. 701 | :return: None 702 | """ 703 | super().execute() 704 | Verb.computer.dsky.request_data(self.accept_input, display_location="data_2") 705 | 706 | 707 | class Verb23(LoadVerb): 708 | 709 | """ Loads component 3 into R3 710 | """ 711 | 712 | def __init__(self, noun): 713 | 714 | """ Class constructor 715 | :return: None 716 | """ 717 | 718 | super().__init__(name="Load component 3 into R3", verb_number="23", noun=noun) 719 | 720 | def execute(self): 721 | 722 | """ Executes the verb. 723 | :return: None 724 | """ 725 | super().execute() 726 | Verb.computer.dsky.request_data(self.accept_input, display_location="data_3") 727 | 728 | 729 | #class Verb24(LoadVerb): 730 | 731 | #""" Loads component 1, 2 into R1, R2 732 | #""" 733 | 734 | #def __init__(self, noun): 735 | 736 | #""" Class constructor 737 | #:return: None 738 | #""" 739 | 740 | #super().__init__(name="Load component 1, 2 into R1, R2", verb_number="24", noun=noun) 741 | 742 | #def execute(self): 743 | 744 | #""" Executes the verb. 745 | #:return: None 746 | #""" 747 | 748 | #pass 749 | 750 | 751 | #class Verb25(LoadVerb): 752 | 753 | #""" Loads component 1, 2, 3 into R1, R2, R3 754 | #""" 755 | 756 | #def __init__(self, noun): 757 | 758 | #""" Class constructor 759 | #:return: None 760 | #""" 761 | 762 | #super().__init__(name="Load component 1, 2, 3 into R1, R2, R3", verb_number="25", noun=noun) 763 | 764 | #def execute(self): 765 | 766 | #""" Executes the verb. 767 | #:return: None 768 | #""" 769 | 770 | #pass 771 | 772 | # no verb 26 773 | 774 | # no verb 28 775 | 776 | # no verb 29 777 | 778 | #class Verb32(Verb): 779 | 780 | #""" Recycle program 781 | #""" 782 | 783 | #def __init__(self, noun): 784 | 785 | #""" Class constructor 786 | #:return: None 787 | #""" 788 | 789 | #super().__init__(name="Recycle program", verb_number="32", noun=noun) 790 | 791 | #def execute(self): 792 | 793 | #""" Executes the verb. 794 | #:return: None 795 | #""" 796 | 797 | #if isinstance(Verb.computer.keyboard_state["backgrounded_update"], MonitorVerb): 798 | #Verb.computer.keyboard_state["backgrounded_update"].terminate() # TODO 799 | #else: 800 | #utils.log("V32 called, but nothing to recycle!") 801 | 802 | 803 | #class Verb33(Verb): 804 | 805 | #""" Proceed without DSKY inputs 806 | #""" 807 | 808 | #def __init__(self, noun): 809 | 810 | #""" Class constructor 811 | #:return: None 812 | #""" 813 | 814 | #super().__init__(name="Proceed without DSKY inputs", verb_number="33", noun=noun) 815 | 816 | #def execute(self): 817 | 818 | #""" Executes the verb. 819 | #:return: None 820 | #""" 821 | 822 | #if isinstance(Verb.computer.keyboard_state["backgrounded_update"], MonitorVerb): 823 | #Verb.computer.keyboard_state["backgrounded_update"].terminate() 824 | #else: 825 | #utils.log("V33 called, but nothing to proceed with!") 826 | 827 | 828 | class Verb34(Verb): 829 | 830 | """ Terminate program 831 | """ 832 | 833 | def __init__(self): 834 | 835 | """ Class constructor 836 | :return: None 837 | """ 838 | 839 | super().__init__(name="Terminate function", verb_number="34") 840 | 841 | def execute(self): 842 | 843 | """ Executes the verb. 844 | :return: None 845 | """ 846 | 847 | if Verb.computer.keyboard_state["backgrounded_update"]: 848 | utils.log("Terminating backgrounded update") 849 | Verb.computer.keyboard_state["backgrounded_update"].terminate() 850 | Verb.computer.dsky.stop_annunciator_blink("key_rel") 851 | if Verb.computer.running_program: 852 | utils.log("Terminating active program {}".format(Verb.computer.running_program.number)) 853 | Verb.computer.running_program.terminate() 854 | else: 855 | utils.log("V34 called, but nothing to terminate!") 856 | 857 | 858 | class Verb35(Verb): 859 | 860 | """Lamp test""" 861 | 862 | def __init__(self): 863 | 864 | """ Class constructor 865 | :return: None 866 | """ 867 | 868 | super().__init__(name="Test lights", verb_number="35") 869 | self.flash_timer = QTimer() 870 | 871 | def execute(self): 872 | 873 | """ Executes the verb. 874 | :return: None 875 | """ 876 | # commands the annunciators 877 | 878 | for annunciator in self.dsky.annunciators.values(): 879 | annunciator.on() 880 | # commands the data registers 881 | for register in ["1", "2", "3"]: 882 | self.dsky.set_register(value="+88888", register="data_{}".format(register)) 883 | # commands the control registers 884 | for register in ["verb", "noun", "program"]: 885 | self.dsky.set_register(value="88", register=register) 886 | # blinks the verb/noun registers 887 | self.dsky.verb_noun_flash_on() 888 | self.dsky.start_annunciator_blink("opr_err") 889 | self.dsky.start_annunciator_blink("key_rel") 890 | self.flash_timer.singleShot(5000, self.terminate) 891 | self.computer.flash_comp_acty(500) 892 | self.computer.memory_hack = self 893 | 894 | def terminate(self): 895 | ''' 896 | Terminates the verb updates 897 | :returns: None 898 | ''' 899 | for annunciator in self.dsky.annunciators.values(): 900 | annunciator.off() 901 | self.dsky.verb_noun_flash_off() 902 | self.dsky.set_register("88", "verb") 903 | self.dsky.set_register("88", "noun") 904 | self.dsky.stop_annunciator_blink("opr_err") 905 | self.dsky.stop_annunciator_blink("key_rel") 906 | self.dsky.set_register(value="bb", register="program") 907 | #self.computer.remove_job(self) 908 | self.computer.memory_hack = None 909 | 910 | 911 | class Verb36(Verb): 912 | 913 | """ Request fresh start 914 | """ 915 | 916 | def __init__(self, noun): 917 | 918 | """ Class constructor 919 | :return: None 920 | """ 921 | 922 | super().__init__(name="Request fresh start", verb_number="36", noun=noun) 923 | 924 | def execute(self): 925 | 926 | """ Executes the verb. 927 | :return: None 928 | """ 929 | 930 | Verb.computer.fresh_start() 931 | 932 | 933 | class Verb37(Verb): 934 | 935 | """ Change program (Major Mode) 936 | """ 937 | 938 | def __init__(self): 939 | 940 | """ Class constructor 941 | :return: None 942 | """ 943 | 944 | super().__init__(name="Change program (Major Mode)", verb_number="37") 945 | 946 | def execute(self): 947 | 948 | """ Executes the verb. 949 | :return: None 950 | """ 951 | 952 | super().execute() 953 | self.dsky.request_data(requesting_object=self.receive_data, display_location="noun") 954 | 955 | def receive_data(self, data): 956 | 957 | """ Accepts data provided by user via DSKY 958 | :param data: the data from DSKY 959 | :return: None 960 | """ 961 | if len(data) != 2: 962 | self.computer.operator_error("Expected exactly two digits, received {} digits".format(len(data))) 963 | self.terminate() 964 | return 965 | Verb.computer.execute_program(data) 966 | 967 | ############################################################################### 968 | # BEGIN EXTENDED VERBS 969 | ############################################################################### 970 | 971 | class Verb75(ExtendedVerb): 972 | 973 | """ Backup liftoff 974 | """ 975 | 976 | def __init__(self): 977 | 978 | """ Class constructor 979 | :return: None 980 | """ 981 | 982 | super().__init__(name="Backup liftoff", verb_number="75") 983 | 984 | def execute(self): 985 | 986 | """ Executes the verb. 987 | :return: None 988 | """ 989 | program = Verb.computer.programs["11"]() 990 | program.execute() 991 | 992 | 993 | class Verb82(ExtendedVerb): 994 | 995 | """ Request orbital parameters display (R30) 996 | """ 997 | 998 | def __init__(self): 999 | 1000 | """ Class constructor 1001 | :return: None 1002 | """ 1003 | 1004 | super().__init__(name="Request orbital parameters display (R30)", verb_number="82") 1005 | 1006 | def execute(self): 1007 | 1008 | """ Executes the verb. 1009 | :return: None 1010 | """ 1011 | 1012 | #super(Verb82, self).execute() 1013 | #computer.routines[30]() 1014 | Verb.computer.execute_verb(verb="16", noun="44") 1015 | 1016 | 1017 | class Verb93(ExtendedVerb): 1018 | 1019 | ''' 1020 | Disables autopilot. 1021 | ''' 1022 | 1023 | def __init__(self): 1024 | 1025 | ''' 1026 | Instance constructor. 1027 | :returns: None 1028 | ''' 1029 | 1030 | super().__init__(name="Disable Autopilot", verb_number="93") 1031 | 1032 | def execute(self): 1033 | 1034 | ''' 1035 | Executes the verb. 1036 | :returns: None 1037 | ''' 1038 | 1039 | Verb.computer.disable_direction_autopilot() 1040 | 1041 | 1042 | class Verb98(ExtendedVerb): 1043 | ''' 1044 | Debug verb 1045 | ''' 1046 | 1047 | def __init__(self): 1048 | ''' 1049 | Instance constructor. 1050 | :returns: None 1051 | ''' 1052 | super().__init__(name="Debug", verb_number="98") 1053 | 1054 | def execute(self): 1055 | 1056 | ''' 1057 | Executes the verb. 1058 | :returns: None 1059 | ''' 1060 | 1061 | super().execute() 1062 | self.dsky.request_data(requesting_object=self.receive_data, display_location="noun") 1063 | 1064 | def receive_data(self, data): 1065 | 1066 | if data == "01": 1067 | Verb.computer.accept_uplink() 1068 | elif data == "02": 1069 | data = telemachus.get_telemetry("maneuverNodes") 1070 | data = data[0] 1071 | for key, value in sorted(data.items()): 1072 | if key == "orbitPatches": 1073 | print("-" * 40) 1074 | print("Orbit patches:") 1075 | print() 1076 | for index in range(len(value)): 1077 | print("Patch {}:".format(index)) 1078 | for a, b in sorted(value[index].items()): 1079 | print("{}: {}".format(a, b)) 1080 | print() 1081 | #for a, b in data[key].items(): 1082 | #print("{}: {}".format(a, b)) 1083 | print("-" * 40) 1084 | else: 1085 | print("{}: {}".format(key, value)) 1086 | 1087 | class Verb99(ExtendedVerb): 1088 | 1089 | """ Please enable engine 1090 | """ 1091 | 1092 | def __init__(self, **kwargs): 1093 | 1094 | """ Class constructor 1095 | :return: None 1096 | """ 1097 | self.object_requesting_proceed = kwargs["object_requesting_proceed"] 1098 | super().__init__(name="Please enable engine", verb_number="99") 1099 | 1100 | def execute(self): 1101 | 1102 | """ Executes the verb. 1103 | :return: None 1104 | """ 1105 | 1106 | # stop any display updates 1107 | if self.dsky.current_verb: 1108 | self.dsky.current_verb.terminate() 1109 | super().execute() 1110 | 1111 | 1112 | 1113 | # blank the DSKY 1114 | self.dsky.blank_all_registers() 1115 | 1116 | # re-display the verb number since the register has been blanked 1117 | Verb.computer.dsky.set_register("99", "verb") 1118 | self.dsky.request_data(requesting_object=self.object_requesting_proceed, display_location=None, 1119 | is_proceed_available=True) 1120 | 1121 | 1122 | verbs = OrderedDict() 1123 | clsmembers = inspect.getmembers(sys.modules[__name__], inspect.isclass) 1124 | for class_tuple in clsmembers: 1125 | if class_tuple[0][-1].isdigit(): 1126 | verbs[class_tuple[0][-2:]] = class_tuple[1] 1127 | -------------------------------------------------------------------------------- /doc/changelog.md: -------------------------------------------------------------------------------- 1 | basaGC changelog 2 | ---------------- 3 | 4 | Please note that this file only contains changes from version 0.5.0 onwards. All dates are in dd/mm/yy format. 5 | 6 | Future: version 2.3.0: 7 | - Modified noun 95 to display burn duration rather than delta v at cutoff 8 | - Added uplink capability 9 | - refactored P15, P40, and maneuver calculator classes 10 | 11 | 17/04/16: version 2.2.0: 12 | - Fixed programs 15 and 40 13 | - Added correct calculation for burn start time 14 | - Nouns and verbs added 15 | - Bugfixes 16 | 17 | 14/04/16: version 2.1.0: 18 | - bug fixes 19 | - Added Programs 01 and 02 20 | - Added IMU module 21 | 22 | 13/04/16: version 2.0.1: 23 | - bug fixes 24 | - tweaked some behaviour to act more like AGC 25 | - added secret debug verb 26 | 27 | 12/04/16: version 2.0: 28 | - Changed widget toolkit to PyQt5 from wxPython 29 | - Now uses Python 3 30 | - Many bugfixes and refactorings 31 | 32 | 16/12/14: version 0.5.6: 33 | - Added Program 40 34 | - Added Noun 14, Noun 40 35 | - Made help viewer lists sorted again 36 | - Nouns, verbs and programs are now added dynamically at runtime 37 | - Bug fixes 38 | - Added noun 49, alarm code 120 39 | - Some more stuff ive forgotten about :) 40 | 41 | 02/12/14: version 0.5.5: 42 | - Added alarm codes to help menu 43 | - Many small bug fixes 44 | - Program 15 mostly complete, needs fine tuning 45 | 46 | 17/11/14: version 0.5.4: 47 | - Fixed a but where if user clicked on the log viewer (or help) dialog system close icon the window would get destroyed 48 | rather than hidden 49 | - Changed default log level to DEBUG 50 | - Changed Noun 50 to display the first digit after decimal dot 51 | - Fixed a bug where a backgrounded Monitor Verb couldn't be recalled with KEY REL button 52 | - If there is no connection available to KSP, the NO ATT annunciator is illuminated and logged 53 | - If KSP is paused or there is a problem with the Telemachus antenna, the STBY annunciator will illuminate and details 54 | will be logged. 55 | 56 | 12/11/14: version 0.5.3: 57 | - Added help viewer 58 | - Added user-selectable display update interval 59 | - Bug fixes 60 | 61 | 20/10/14: version 0.5.0: 62 | - Added Program 15 (TMI initiate/cutoff) 63 | - Lots of docstrings added 64 | - Many bugfixes 65 | - Added nouns: 17 (roll, pitch, yaw), 43 (latitude, longitude, altitude), 50 (surface velocity X, Y, Z) 66 | - Added manubar to GUI 67 | - Added items to menubar: settings, log viewer, quit, help/about 68 | - Switched to Git Flow development model 69 | - Code cleanup 70 | - Code refactoring for better PEP8 compliance 71 | -------------------------------------------------------------------------------- /doc/checklists/Launch Checklist.md: -------------------------------------------------------------------------------- 1 | Launch Checklist: 2 | ----------------- 3 | 4 | - Key in V37E01E (execute P01) 5 | - Observe NO ATT annunciator is lit 6 | - Observe NO ATT extinguished after approx 10 seconds 7 | - Observe P02 running (look for 02 in PROG register) 8 | - Observe all registers blank at liftoff 9 | - Observe DSKY and confirm that P11 is running, and V16N62 is up. Display reads: 10 | - R1: Orbital velocity (XXXX.X m/s) 11 | - R2: Altitude rate (vertical speed) (XXXX.X m/s) 12 | - R3: Altitude (XXXX.X km) 13 | - Observe DSKY and confirm that R1 and R2 are increasing 14 | - When desired, key in V82 to bring up Orbit Insertion Monitor 15 | - Observe DSKY and confirm that V16N44 is up. Display reads: 16 | - R1: Apoapsis altitude (xxx.xx km) 17 | - R2: Periapsis altitude (xxx.xx km) 18 | - R3: Time to apoapsis (hmmss) 19 | 20 | Sorry, not complete yet! Watch this space :) 21 | -------------------------------------------------------------------------------- /doc/checklists/TMI Calc.md: -------------------------------------------------------------------------------- 1 | TMI (Trans-Munar Injection) (P15): 2 | --------- 3 | 4 | - Key in V37E15E (Execute Program 15: Calculate TMI burn) 5 | - Check P15 is running 6 | - Check V21 N25 (Load data into data register 1: vessel mass (whole part)) flashing 7 | - Key in (without sign) vessel mass (whole part), followed by ENTR 8 | - Check V22 N25 (Load data into data register 2: vessel mass (fractional part)) flashing 9 | - Key in (without sign) vessel mass (fractional part), followed by ENTR 10 | - Check V21 N31 (Load data into data register 1: total thrust (whole part)) flashing 11 | - Key in (without sign) total thrust (whole part), followed by ENTR 12 | - Check V22 N31 (Load data into data register 2: total thrust (fractional part)) flashing 13 | - Key in (without sign) total thrust (fractional part), followed by ENTR 14 | - Check V21 N38 (Load data into data register 1: Specific impulse (Isp)) flashing 15 | - Key in (without sign) Specific impulse, followed by ENTR 16 | - Calculation complete 17 | - Check Program 00 is running 18 | - Check V06 N95 (Display on 3 data registers: TMI Burn Data Display) 19 | - R1: Time to ignition (TIG) (mmbss minutes, blank, seconds,) 20 | - R2: Delta-V for burn (xxxxx m/s) 21 | - R3: Predicted Velocity at cutoff (xxxxx m/s) 22 | 23 | - Key in V37E40E (Execute Program 40: Execute burn) 24 | - Check P40 running 25 | - Check V16 N40 (monitor on 3 data registers: Burn Data) 26 | - R1: Time to ignition (TIG) (mmbss minutes, blank, seconds,) 27 | - R2: Delta-V for burn (xxxxx m/s) 28 | - R3: Accumulated delta-V (xxxxx m/s) 29 | - Check R1 counting down 30 | - ENSURE timewarp is halted before T-2 minutes 31 | - At T-1m45s, display blanks 32 | - After 5 seconds, display returns. Attitude autopilot engages 33 | - Check vessel maneuvering to point to maneuver node 34 | - At T-10 seconds, display blanks 35 | - Check flashing V99 (Please enable engine) 36 | - Key in PRO 37 | - Display resumes, engine ignites at t=0 38 | - Check R3 (Accumulated delta-V) imcreasing 39 | - Check for engine cutoff at approx R2 value 40 | - Check autopilot disarmed 41 | - Check P00 running 42 | - Check V06 N14 (Display on 3 registers: Burn error display) 43 | - R1: Expected Δv at cutoff (xxxxx m/s) 44 | - R2: Actual Δv at cutoff (xxxxx m/s) 45 | - R3: Difference (xxxx.x m/s) 46 | - Check for Mun intercept 47 | -------------------------------------------------------------------------------- /doc/credits.md: -------------------------------------------------------------------------------- 1 | Project by Tim Buchanan (cashelcomputers@gmail.com). 2 | 3 | Includes code and images from the Virtual AGC Project (http://www.ibiblio.org/apollo/index.html) 4 | by Ronald S. Burkey (info@sandroid.org) 5 | -------------------------------------------------------------------------------- /doc/installation.md: -------------------------------------------------------------------------------- 1 | Installation: 2 | ==== 3 | 4 | All OS: 5 | ---- 6 | 7 | - NEW: basaGC now requires [this fork of Telemachus](https://github.com/tcannonfodder/Telemachus) which is a fork of 8 | [Telemachus mod](https://github.com/richardbunt/Telemachus). Install as per usual in /GameData. For KSP 1.0.5, 9 | use [this release](https://github.com/tcannonfodder/Telemachus/releases/tag/v1.4.32.0), 1.1 users can [use this release] 10 | (https://github.com/tcannonfodder/Telemachus/releases/tag/v1.5.32.1). 11 | - Install [MechJeb](http://mods.curse.com/ksp-mods/kerbal/220221-mechjeb) as per usual 12 | 13 | Linux: 14 | ---- 15 | 16 | * Install Python 3.4 (sometimes already installed) using your package manager eg for Fedora do "dnf install python3" 17 | * Install PyQt5 using your package manager or pip3 18 | * If installing from zip file, just extract the contents into any directory. If using git, clone the program by using 19 | "git clone https://github.com/cashelcomputers/basaGC.git" 20 | * Either click on basagc.py in the top-level directory or in a terminal, cd to the base directory and run 21 | "python3 basagc.py" 22 | * Start navigating :) 23 | 24 | Windows: 25 | --- 26 | 27 | * Install Python 3.4 from [here](https://www.python.org/downloads/release/python-344/), scroll down to bottom of page 28 | and select windows installer 29 | * Install PyQt5 from [here](https://www.riverbankcomputing.com/software/pyqt/download5) ([direct link](http://sourceforge.net/projects/pyqt/files/PyQt5/PyQt-5.5.1/PyQt5-5.5.1-gpl-Py3.4-Qt5.5.1-x64.exe)) 30 | * Get the latest release of basaGC [here](https://github.com/cashelcomputers/basaGC/releases) 31 | * Unzip the folder into a location of your choice (doesn't have to be in GameData) 32 | * Open the filder and double-click on the file "basagc.py" 33 | * Start navigating :) 34 | 35 | Mac: 36 | --- 37 | 38 | * Buy a PC 39 | * Install as above :) 40 | * Seriously I have no idea how you would install it on a mac. You're on your own here. 41 | -------------------------------------------------------------------------------- /doc/known_issues.md: -------------------------------------------------------------------------------- 1 | Known issues: 2 | 3 | 13/10/14: Program 15 (and all programs in general) need to be able to recompute on a regular basis ie every 50ms - DONE 4 | 2/12/14: Program 15 burn parameters need adjusting, and a function needs to be created that can dynamically readjust 5 | the burn parameters *during* the burn, based perhaps on: 6 | - Apoapsis and periapsis 7 | - Orbital period 8 | - Orbital Velocity 9 | Currently, P15 will *probably* get you in the neighborhood of a mun encounter on a free-return trajectory. I still 10 | need to implement a burn at Mun periapsis to circularize. 11 | 12 | -------------------------------------------------------------------------------- /doc/manual.md: -------------------------------------------------------------------------------- 1 | Intro to the display: 2 | ===================== 3 | 4 | Astronauts interact with the computer by entering verb and noun pairs (or sometimes just verbs, e.g. V35). By pressing 5 | [VERB], the astronaut indicated to the computer that the next two digits are a command to do something, e.g. entering 6 | [VERB]16 tells the computer we want to monitor data in the three data display registers (from the top down, 7 | R1, R2 and R3). Next the astronaut would enter the data to act on, e.g. [NOUN]44 tells the computer we want to act 8 | on apoapsis, periapsis and time to apoapsis. Finally the astronaut hits [ENTR] to tell the computer to execute the 9 | request. So for example entering [VERB]16[NOUN]44[ENTR] tells the computer we want to display apoapsis, periapis and 10 | time to apoapsis in the three data registers. 11 | 12 | If there is either a + or - sign in the data registers, the data displayed is in decimal, if the sign is absent then the 13 | data displayed is in octal. 14 | 15 | You can enter the noun first if you so wish ie N44V16E is equivalent to V16N44E. 16 | 17 | In this manual, I shall abbreviate the keypresses as follows: 18 | V16N44E means key in [VERB]16[NOUN]44[ENTR]. 19 | 20 | *PLEASE NOTE:* the following lists may lag behind the program. For the latest info on verbs, nouns and programs, 21 | consult the source. 22 | 23 | Currently implemented verbs: 24 | --------------------------- 25 | 26 | - Verb 01: Display Octal component 1 in R1 27 | - Verb 02: Display Octal component 2 in R1 28 | - Verb 03: Display Octal component 3 in R1 29 | - Verb 04: Display Octal component 1 and 2 in R1 and R2 30 | - Verb 05: Display Octal component 1, 2 and 3 in R1, R2 and R3 31 | - Verb 06: Display decimal in R1 or R1, R2 or R1, R2, R3 32 | 33 | - Verb 11: Monitor Octal component 1 in R1 34 | - Verb 12: Monitor Octal component 2 in R1 35 | - Verb 13: Monitor Octal component 3 in R1 36 | - Verb 14: Monitor Octal component 1 and 2 in R1 and R2 37 | - Verb 15: Monitor Octal component 1, 2 and 3 in R1, R2 and R3 38 | - Verb 16: Monitor decimal in R1 or R1, R2 or R1, R2, R3 39 | - Verb 21: Load component 1 into R1 40 | - Verb 22: Load component 2 into R2 41 | - Verb 23: Load component 3 into R3 42 | 43 | - Verb 35: Lamp Test (this verb is a bit different, to run it we simply key in V35E). 44 | - Verb 36: Request fresh start 45 | - Verb 37: Change program (major mode) 46 | 47 | - Verb 75: Backup Liftoff Discrete 48 | - Verb 82: Request orbital parameters display 49 | - Verb 99: Please enable engine 50 | 51 | Currently implemented nouns: 52 | ---------------------------- 53 | 54 | - Noun 09: Alarm codes 55 | - Noun 14: Burn error display 56 | - Noun 17: Spacecraft attitude 57 | - Noun 25: Spacecraft mass 58 | - Noun 30: Target ID 59 | - Noun 31: Max thrust 60 | - Noun 33: Time to ignition (TIG) 61 | - Noun 36: Mission Elapsed Time 62 | - Noun 38: Specific Impulse (Isp) 63 | - Noun 43: Geographic Position 64 | - Noun 44: Apoapsis as XXXX.X, periapsis as XXXX.X, time to apoapsis in HMMSS 65 | - Noun 50: Surface velocity display 66 | - Noun 62: Surface speed, altitude ASL in meters, Vertical speed 67 | - Noun 95: TMI burn data display 68 | 69 | Currently implemented programs (major modes): 70 | --------------------------------------------- 71 | 72 | - Program 00: AGC idling (called POO by mission control :) 73 | - Program 01: Prelaunch or service - Initialization program 74 | - Program 02: Prelaunch or service - Gyrocompassing program 75 | - Program 11: Earth orbit insertion monitor 76 | - Program 15: TMI calculate 77 | - Program 31: MOI burn calc 78 | - Program 40: TMI execute 79 | 80 | How to set up basaGC for launch: 81 | ---------------------------- 82 | 83 | Key in V37E01E (start program 01) to start the IMU. After 10 seconds, confirm NO ATT annunciator is extinguished. When 84 | complete, P01 will start P02. P02 waits until it detects a liftoff, then automatically runs P11 (launch monitor.) The 85 | data registers show, from top to bottom, orbital velocity (xxxx.x) ms-1, altitude rate (xxxx.x) ms-1, and altitude 86 | above pad (xxxx.x) km. 87 | 88 | At any time during ascent, the user can run V82 to bring up the orbit insertion display. Apoapsis and periapsis should 89 | be read as XXXX.X km, and time to apoapsis should be read in HMMSS. The original AGC didnt't have decimal places in the 90 | display registers, so we don't have them here. 91 | 92 | How to get to Mun: 93 | ---- 94 | 95 | Orbit around Kerbin must be circular, or neally so (eccentricity < 0.003). Key in V37E15E, which will start Program 11, 96 | TMI burn calculator. V21 N25 is flashing, asking us to load (key in) the first component of N25, which is the whole 97 | part of your vessels mass (get mass from MechJeb) in tonnes, with leading zeros. For example, if your mass is 12.345 98 | tonnes, the whole part is 12 (the part up to the decimal point). So we would key in 00012, then ENTR. Now V22 N25 is 99 | flashing, asking us to enter the second component of N25, which is the fractional part of your vessels mass. In the 100 | given abobe, this would be 345, so we would key in 34500 (add trailing zeros) followed by ENTR. 101 | 102 | Next up, V21 N31 is flashing, asking for the whole part of your vessels total thrust (get it from MechJeb), in kN. For 103 | example, if your vessels thrust is 34.567 kN, key in 00034 followed by ENTR. Then V22 N31 flashes, which is the 104 | fractional part of your vessels thrust. In the given abobe, this would be 567, so we would key in 56700 (add trailing 105 | zeros) followed by ENTR. 106 | 107 | Next, V21 N38 is flashing, asking us for specific impulse (Isp) of our engines (right-click on rocket engines to find 108 | Isp). Key in the value for Isp with leading zeros, eg 00350 where Isp = 350. Key in ENTR. 109 | 110 | From here, follow the TMI Calc checklist in the checklists folder from "Calculation complete" onwards. More details 111 | to be added here Soon(tm) 112 | 113 | Alarm codes: 114 | ------------ 115 | 116 | When there is an program alarm, the PROG indicator will illuminate. Keying in V05N09E will display the alarm codes, with 117 | R1 displaying the most recent alarm code, R2 the previous alarm code, and R3 displaying the last alarm code, regardless 118 | if the alarm codes have been reset or not. Alarm codes are displayed in octal. 119 | 120 | The second digit indicates the action performed: 121 | - X1XXX: basic program alarm, essentially a warning 122 | - X2XXX: so-called P00DOO abort: terminates the running program and executes Program 00 (P00) 123 | - X3XXX: program restart: the currently running program will terminate and attempt to restart itself. 124 | - X4XXX: computer restart: a serious error has occurred which the computer cannot recover from. The computer will flush 125 | its memory and perform a hardware restart. 126 | 127 | Alarm codes currently implemented are listed below: 128 | 129 | - 0X110: Error contacting KSP 130 | - 0X111: Telemetry not available 131 | - 0X115: No burn data loaded 132 | - 0X223: Invalid target selected 133 | - 0X224: Orbit not circular 134 | - 0X225: Vessel and target orbits inclination too far apart 135 | -------------------------------------------------------------------------------- /doc/todo.md: -------------------------------------------------------------------------------- 1 | - Complete this file :) 2 | - Finish P15/P?? (Need to do Munar injection burn / CSI / CDH coding) 3 | - inclination change maneuver 4 | - Orbit height maneuver change maneuver (shouldn't be too hard) 5 | 6 | - move images folder to "assets" folder - DONE 7 | - use os.path in config file rather than string cat - DONE 8 | -------------------------------------------------------------------------------- /licence: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /ui/basaGC_dialog_info_display.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | dialog_info_display 4 | 5 | 6 | 7 | 0 8 | 0 9 | 488 10 | 515 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 140 20 | 480 21 | 341 22 | 32 23 | 24 | 25 | 26 | Qt::Horizontal 27 | 28 | 29 | QDialogButtonBox::Close 30 | 31 | 32 | 33 | 34 | 35 | 3 36 | 4 37 | 481 38 | 471 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | buttonBox 47 | accepted() 48 | dialog_info_display 49 | accept() 50 | 51 | 52 | 248 53 | 254 54 | 55 | 56 | 157 57 | 274 58 | 59 | 60 | 61 | 62 | buttonBox 63 | rejected() 64 | dialog_info_display 65 | reject() 66 | 67 | 68 | 316 69 | 260 70 | 71 | 72 | 286 73 | 274 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /ui/basaGC_dialog_log.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 707 10 | 422 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 610 20 | 390 21 | 91 22 | 32 23 | 24 | 25 | 26 | Qt::Horizontal 27 | 28 | 29 | QDialogButtonBox::Close 30 | 31 | 32 | 33 | 34 | 35 | 3 36 | 4 37 | 701 38 | 381 39 | 40 | 41 | 42 | true 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | buttonBox 53 | accepted() 54 | Dialog 55 | accept() 56 | 57 | 58 | 248 59 | 254 60 | 61 | 62 | 157 63 | 274 64 | 65 | 66 | 67 | 68 | buttonBox 69 | rejected() 70 | Dialog 71 | reject() 72 | 73 | 74 | 316 75 | 260 76 | 77 | 78 | 286 79 | 274 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /ui/basaGC_dialog_settings.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | dialog_settings 4 | 5 | 6 | 7 | 0 8 | 0 9 | 307 10 | 176 11 | 12 | 13 | 14 | basaGC settings 15 | 16 | 17 | 18 | ../images/icon.png../images/icon.png 19 | 20 | 21 | 22 | 23 | -40 24 | 140 25 | 341 26 | 32 27 | 28 | 29 | 30 | Qt::Horizontal 31 | 32 | 33 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 34 | 35 | 36 | 37 | 38 | 39 | 5 40 | 16 41 | 161 42 | 23 43 | 44 | 45 | 46 | Telemachus IP address: 47 | 48 | 49 | 50 | 51 | 52 | 180 53 | 10 54 | 120 55 | 25 56 | 57 | 58 | 59 | 127.0.0.1 60 | 61 | 62 | 63 | 64 | 65 | 180 66 | 40 67 | 120 68 | 25 69 | 70 | 71 | 72 | 8085 73 | 74 | 75 | 76 | 77 | 78 | 5 79 | 46 80 | 161 81 | 23 82 | 83 | 84 | 85 | Telemachus port: 86 | 87 | 88 | 89 | 90 | 91 | 180 92 | 70 93 | 120 94 | 25 95 | 96 | 97 | 98 | 100 99 | 100 | 101 | 102 | 103 | 104 | 5 105 | 76 106 | 161 107 | 23 108 | 109 | 110 | 111 | DSKY update interval: 112 | 113 | 114 | 115 | 116 | 117 | 5 118 | 106 119 | 161 120 | 23 121 | 122 | 123 | 124 | Log level: 125 | 126 | 127 | 128 | 129 | 130 | 180 131 | 100 132 | 121 133 | 25 134 | 135 | 136 | 137 | 5 138 | 139 | 140 | QComboBox::AdjustToContents 141 | 142 | 143 | 144 | 145 | 146 | 147 | buttonBox 148 | accepted() 149 | dialog_settings 150 | accept() 151 | 152 | 153 | 248 154 | 254 155 | 156 | 157 | 157 158 | 274 159 | 160 | 161 | 162 | 163 | buttonBox 164 | rejected() 165 | dialog_settings 166 | reject() 167 | 168 | 169 | 316 170 | 260 171 | 172 | 173 | 286 174 | 274 175 | 176 | 177 | 178 | 179 | 180 | --------------------------------------------------------------------------------