├── .github └── workflows │ └── test-and-build.yml ├── .gitignore ├── ESP32_Client_[WIP].py ├── LICENSE ├── Makefile ├── Pipfile ├── Pipfile.lock ├── README.md ├── SimConnect ├── Attributes.py ├── Constants.py ├── Enum.py ├── EventList.py ├── FacilitiesList.py ├── RequestList.py ├── SimConnect.dll ├── SimConnect.py └── __init__.py ├── glass_server.py ├── local_example.py ├── requirements.txt ├── requirements_dev.txt ├── setup.cfg ├── setup.py ├── static ├── css │ └── attind_style.css ├── img │ ├── metal.jpg │ └── plane.png ├── js │ ├── attind_script.js │ └── custom │ │ ├── getSimData.js │ │ └── plane.png └── vendor │ ├── bootstrap │ ├── bootstrap.min.css │ └── bootstrap.min.js │ ├── jquery-ui-slider │ ├── jquery-ui-slider-pips.css │ └── jquery-ui-slider-pips.js │ └── leaflet-providers │ └── leaflet-providers.js ├── templates ├── attitude-indicator │ ├── CNAME │ ├── README.md │ └── index.html └── glass.html └── tests ├── __init__.py ├── glass_dummy_server.py ├── test_entity_plane.py ├── test_request.py └── test_simconnect.py /.github/workflows/test-and-build.yml: -------------------------------------------------------------------------------- 1 | name: Test and Publish 2 | on: 3 | push: 4 | paths: 5 | - 'SimConnect/__init__.py' 6 | 7 | jobs: 8 | build-n-publish: 9 | name: Build and publish Python distributions to PyPI 10 | runs-on: ubuntu-18.04 11 | steps: 12 | - uses: actions/checkout@master 13 | - name: Set up Python 3.8 14 | uses: actions/setup-python@v1 15 | with: 16 | python-version: 3.8 17 | - name: Install dependencies 18 | run: python3 -m pip install --user --upgrade setuptools wheel twine 19 | - name: Build source 20 | run: python3 setup.py sdist bdist_wheel 21 | - name: Publish to PyPI 22 | uses: pypa/gh-action-pypi-publish@master 23 | with: 24 | user: __token__ 25 | password: ${{ secrets.pypi_password }} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | 132 | #vscode 133 | .vscode 134 | .idea/inspectionProfiles/profiles_settings.xml 135 | .idea/misc.xml 136 | .idea/modules.xml 137 | .idea/Python-SimConnect.iml 138 | .idea/vcs.xml 139 | .idea/workspace.xml 140 | .DS_Store 141 | .idea/inspectionProfiles/Project_Default.xml 142 | .idea/CodeStyles/codeStyleConfig.xml 143 | .idea/MSFS2020-cockpit-companion.iml 144 | *.FLT 145 | *.FSSAVE 146 | *.SPB 147 | -------------------------------------------------------------------------------- /ESP32_Client_[WIP].py: -------------------------------------------------------------------------------- 1 | # This file is executed on every boot (including wake-boot from deepsleep) 2 | import esp 3 | import webrepl 4 | import network 5 | import socket 6 | import json 7 | import machine 8 | from machine import Pin, ADC 9 | from time import sleep 10 | 11 | esp.osdebug(None) 12 | 13 | server_address = 'http://' 14 | server_port = 5000 15 | 16 | 17 | def do_connect(): 18 | wlan = network.WLAN(network.STA_IF) 19 | wlan.active(True) 20 | if not wlan.isconnected(): 21 | print('connecting to network...') 22 | wlan.connect('', '') 23 | while not wlan.isconnected(): 24 | pass 25 | print('network config:', wlan.ifconfig()) 26 | 27 | 28 | def http_get(url): 29 | _, _, host, path = url.split('/', 3) 30 | addr = socket.getaddrinfo(host, server_port)[0][-1] 31 | s = socket.socket() 32 | s.connect(addr) 33 | s.send(bytes('GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n' % (path, host), 'utf8')) 34 | while True: 35 | data = s.recv(100) 36 | if data: 37 | print(str(data, 'utf8'), end='') 38 | else: 39 | break 40 | s.close() 41 | 42 | 43 | def http_post(url, body): 44 | _, _, host, path = url.split('/', 3) 45 | addr = socket.getaddrinfo(host, server_port)[0][-1] 46 | s = socket.socket() 47 | s.connect(addr) 48 | s.send(bytes('POST /%s HTTP/1.0\r\nHost: %s\r\nContent-Type: application/json\r\nContent-Length: %d\r\n\r\n%s' % ( 49 | path, host, len(body), body), 'utf8') 50 | ) 51 | while True: 52 | data = s.recv(100) 53 | if not data: 54 | break 55 | s.close() 56 | 57 | 58 | def scale(val, src, dst): 59 | """ 60 | Scale the given value from the scale of src to the scale of dst. 61 | """ 62 | return ((val - src[0]) / (src[1] - src[0])) * (dst[1] - dst[0]) + dst[0] 63 | 64 | 65 | def set_throttle_lever(value, index=1): 66 | http_post(server_address + '/datapoint/GENERAL_ENG_THROTTLE_LEVER_POSITION:index/set', '{"index":%d, "value_to_use": %d}' % (index, value)) 67 | 68 | 69 | def get_scaled_adc(_pin): 70 | adc = ADC(Pin(_pin)) # create ADC object on ADC pin 71 | adc.atten(ADC.ATTN_11DB) # set 11dB input attenuation (voltage range roughly 0.0v - 3.6v) 72 | adc.width(ADC.WIDTH_11BIT) # set 9 bit return values (returned range 0-511) 73 | return scale(adc.read(), (0, 520), (0, 100)) # read value using the newly configured attenuation and width 74 | 75 | 76 | do_connect() 77 | webrepl.start() 78 | 79 | buff = 2 80 | o_te = 0 81 | pin_to_read = 32 82 | while True: 83 | c_te = int(get_scaled_adc(pin_to_read)) 84 | if c_te > (o_te + buff) or c_te < (o_te - buff): 85 | set_throttle_lever(c_te) 86 | o_te = c_te 87 | sleep(1) 88 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | python setup.py build 3 | 4 | install: 5 | pip install -r requirements.txt 6 | 7 | install-dev: 8 | pip install -r requirements_dev.txt 9 | 10 | test-sim: 11 | python example.py 12 | 13 | test: install install-dev 14 | python -m pytest --cov=SimConnect --cov-report xml 15 | 16 | update-dependencies: 17 | pipenv update 18 | pipenv lock -r > requirements.txt 19 | pipenv lock -r -d > requirements_dev.txt 20 | 21 | sdist: 22 | python setup.py sdist 23 | 24 | clean: 25 | rm -r dist src/*.egg-info build .coverage coverage.xml .pytest_cache -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest-cov = "*" 8 | pylint = "*" 9 | black = "*" 10 | 11 | [packages] 12 | flask = "*" 13 | 14 | [requires] 15 | python_version = "3.8" 16 | 17 | [pipenv] 18 | allow_prereleases = true 19 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "75da094b7fd42cf27c1385797b0df42d11341a540f6defb40d93eba46794181a" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.8" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "click": { 20 | "hashes": [ 21 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", 22 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" 23 | ], 24 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 25 | "version": "==7.1.2" 26 | }, 27 | "flask": { 28 | "hashes": [ 29 | "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", 30 | "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" 31 | ], 32 | "index": "pypi", 33 | "version": "==1.1.2" 34 | }, 35 | "itsdangerous": { 36 | "hashes": [ 37 | "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", 38 | "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" 39 | ], 40 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 41 | "version": "==1.1.0" 42 | }, 43 | "jinja2": { 44 | "hashes": [ 45 | "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", 46 | "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" 47 | ], 48 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 49 | "version": "==2.11.2" 50 | }, 51 | "markupsafe": { 52 | "hashes": [ 53 | "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", 54 | "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", 55 | "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", 56 | "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", 57 | "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", 58 | "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", 59 | "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", 60 | "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", 61 | "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", 62 | "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", 63 | "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", 64 | "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", 65 | "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", 66 | "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", 67 | "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", 68 | "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", 69 | "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", 70 | "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", 71 | "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", 72 | "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", 73 | "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", 74 | "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", 75 | "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", 76 | "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", 77 | "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", 78 | "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", 79 | "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", 80 | "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", 81 | "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", 82 | "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", 83 | "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", 84 | "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", 85 | "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" 86 | ], 87 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 88 | "version": "==1.1.1" 89 | }, 90 | "werkzeug": { 91 | "hashes": [ 92 | "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", 93 | "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" 94 | ], 95 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 96 | "version": "==1.0.1" 97 | } 98 | }, 99 | "develop": { 100 | "appdirs": { 101 | "hashes": [ 102 | "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", 103 | "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" 104 | ], 105 | "version": "==1.4.4" 106 | }, 107 | "astroid": { 108 | "hashes": [ 109 | "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703", 110 | "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386" 111 | ], 112 | "markers": "python_version >= '3.5'", 113 | "version": "==2.4.2" 114 | }, 115 | "atomicwrites": { 116 | "hashes": [ 117 | "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197", 118 | "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a" 119 | ], 120 | "markers": "sys_platform == 'win32'", 121 | "version": "==1.4.0" 122 | }, 123 | "attrs": { 124 | "hashes": [ 125 | "sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a", 126 | "sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff" 127 | ], 128 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 129 | "version": "==20.1.0" 130 | }, 131 | "black": { 132 | "hashes": [ 133 | "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea", 134 | "sha256:70b62ef1527c950db59062cda342ea224d772abdf6adc58b86a45421bab20a6b" 135 | ], 136 | "index": "pypi", 137 | "version": "==20.8b1" 138 | }, 139 | "click": { 140 | "hashes": [ 141 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", 142 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" 143 | ], 144 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 145 | "version": "==7.1.2" 146 | }, 147 | "colorama": { 148 | "hashes": [ 149 | "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", 150 | "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" 151 | ], 152 | "markers": "sys_platform == 'win32' and sys_platform == 'win32'", 153 | "version": "==0.4.3" 154 | }, 155 | "coverage": { 156 | "hashes": [ 157 | "sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb", 158 | "sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3", 159 | "sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716", 160 | "sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034", 161 | "sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3", 162 | "sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8", 163 | "sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0", 164 | "sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f", 165 | "sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4", 166 | "sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962", 167 | "sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d", 168 | "sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b", 169 | "sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4", 170 | "sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3", 171 | "sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258", 172 | "sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59", 173 | "sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01", 174 | "sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd", 175 | "sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b", 176 | "sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d", 177 | "sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89", 178 | "sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd", 179 | "sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b", 180 | "sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d", 181 | "sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46", 182 | "sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546", 183 | "sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082", 184 | "sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b", 185 | "sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4", 186 | "sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8", 187 | "sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811", 188 | "sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd", 189 | "sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651", 190 | "sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0" 191 | ], 192 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", 193 | "version": "==5.2.1" 194 | }, 195 | "iniconfig": { 196 | "hashes": [ 197 | "sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437", 198 | "sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69" 199 | ], 200 | "version": "==1.0.1" 201 | }, 202 | "isort": { 203 | "hashes": [ 204 | "sha256:92533892058de0306e51c88f22ece002a209dc8e80288aa3cec6d443060d584f", 205 | "sha256:a200d47b7ee8b7f7d0a9646650160c4a51b6a91a9413fd31b1da2c4de789f5d3" 206 | ], 207 | "markers": "python_version >= '3.6' and python_version < '4.0'", 208 | "version": "==5.5.1" 209 | }, 210 | "lazy-object-proxy": { 211 | "hashes": [ 212 | "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", 213 | "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", 214 | "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", 215 | "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", 216 | "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", 217 | "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", 218 | "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", 219 | "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", 220 | "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", 221 | "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", 222 | "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", 223 | "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", 224 | "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", 225 | "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", 226 | "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", 227 | "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", 228 | "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", 229 | "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", 230 | "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", 231 | "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", 232 | "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0" 233 | ], 234 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 235 | "version": "==1.4.3" 236 | }, 237 | "mccabe": { 238 | "hashes": [ 239 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 240 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 241 | ], 242 | "version": "==0.6.1" 243 | }, 244 | "more-itertools": { 245 | "hashes": [ 246 | "sha256:6f83822ae94818eae2612063a5101a7311e68ae8002005b5e05f03fd74a86a20", 247 | "sha256:9b30f12df9393f0d28af9210ff8efe48d10c94f73e5daf886f10c4b0b0b4f03c" 248 | ], 249 | "markers": "python_version >= '3.5'", 250 | "version": "==8.5.0" 251 | }, 252 | "mypy-extensions": { 253 | "hashes": [ 254 | "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", 255 | "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" 256 | ], 257 | "version": "==0.4.3" 258 | }, 259 | "packaging": { 260 | "hashes": [ 261 | "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", 262 | "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" 263 | ], 264 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 265 | "version": "==20.4" 266 | }, 267 | "pathspec": { 268 | "hashes": [ 269 | "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0", 270 | "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061" 271 | ], 272 | "version": "==0.8.0" 273 | }, 274 | "pluggy": { 275 | "hashes": [ 276 | "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", 277 | "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" 278 | ], 279 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 280 | "version": "==0.13.1" 281 | }, 282 | "py": { 283 | "hashes": [ 284 | "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2", 285 | "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342" 286 | ], 287 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 288 | "version": "==1.9.0" 289 | }, 290 | "pylint": { 291 | "hashes": [ 292 | "sha256:bb4a908c9dadbc3aac18860550e870f58e1a02c9f2c204fdf5693d73be061210", 293 | "sha256:bfe68f020f8a0fece830a22dd4d5dddb4ecc6137db04face4c3420a46a52239f" 294 | ], 295 | "index": "pypi", 296 | "version": "==2.6.0" 297 | }, 298 | "pyparsing": { 299 | "hashes": [ 300 | "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", 301 | "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" 302 | ], 303 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 304 | "version": "==2.4.7" 305 | }, 306 | "pytest": { 307 | "hashes": [ 308 | "sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4", 309 | "sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad" 310 | ], 311 | "markers": "python_version >= '3.5'", 312 | "version": "==6.0.1" 313 | }, 314 | "pytest-cov": { 315 | "hashes": [ 316 | "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191", 317 | "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e" 318 | ], 319 | "index": "pypi", 320 | "version": "==2.10.1" 321 | }, 322 | "regex": { 323 | "hashes": [ 324 | "sha256:0dc64ee3f33cd7899f79a8d788abfbec168410be356ed9bd30bbd3f0a23a7204", 325 | "sha256:1269fef3167bb52631ad4fa7dd27bf635d5a0790b8e6222065d42e91bede4162", 326 | "sha256:14a53646369157baa0499513f96091eb70382eb50b2c82393d17d7ec81b7b85f", 327 | "sha256:3a3af27a8d23143c49a3420efe5b3f8cf1a48c6fc8bc6856b03f638abc1833bb", 328 | "sha256:46bac5ca10fb748d6c55843a931855e2727a7a22584f302dd9bb1506e69f83f6", 329 | "sha256:4c037fd14c5f4e308b8370b447b469ca10e69427966527edcab07f52d88388f7", 330 | "sha256:51178c738d559a2d1071ce0b0f56e57eb315bcf8f7d4cf127674b533e3101f88", 331 | "sha256:5ea81ea3dbd6767873c611687141ec7b06ed8bab43f68fad5b7be184a920dc99", 332 | "sha256:6961548bba529cac7c07af2fd4d527c5b91bb8fe18995fed6044ac22b3d14644", 333 | "sha256:75aaa27aa521a182824d89e5ab0a1d16ca207318a6b65042b046053cfc8ed07a", 334 | "sha256:7a2dd66d2d4df34fa82c9dc85657c5e019b87932019947faece7983f2089a840", 335 | "sha256:8a51f2c6d1f884e98846a0a9021ff6861bdb98457879f412fdc2b42d14494067", 336 | "sha256:9c568495e35599625f7b999774e29e8d6b01a6fb684d77dee1f56d41b11b40cd", 337 | "sha256:9eddaafb3c48e0900690c1727fba226c4804b8e6127ea409689c3bb492d06de4", 338 | "sha256:bbb332d45b32df41200380fff14712cb6093b61bd142272a10b16778c418e98e", 339 | "sha256:bc3d98f621898b4a9bc7fecc00513eec8f40b5b83913d74ccb445f037d58cd89", 340 | "sha256:c11d6033115dc4887c456565303f540c44197f4fc1a2bfb192224a301534888e", 341 | "sha256:c50a724d136ec10d920661f1442e4a8b010a4fe5aebd65e0c2241ea41dbe93dc", 342 | "sha256:d0a5095d52b90ff38592bbdc2644f17c6d495762edf47d876049cfd2968fbccf", 343 | "sha256:d6cff2276e502b86a25fd10c2a96973fdb45c7a977dca2138d661417f3728341", 344 | "sha256:e46d13f38cfcbb79bfdb2964b0fe12561fe633caf964a77a5f8d4e45fe5d2ef7" 345 | ], 346 | "version": "==2020.7.14" 347 | }, 348 | "six": { 349 | "hashes": [ 350 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", 351 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" 352 | ], 353 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 354 | "version": "==1.15.0" 355 | }, 356 | "toml": { 357 | "hashes": [ 358 | "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", 359 | "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" 360 | ], 361 | "version": "==0.10.1" 362 | }, 363 | "typed-ast": { 364 | "hashes": [ 365 | "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", 366 | "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", 367 | "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", 368 | "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", 369 | "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", 370 | "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", 371 | "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", 372 | "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", 373 | "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", 374 | "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", 375 | "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", 376 | "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", 377 | "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", 378 | "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", 379 | "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", 380 | "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", 381 | "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", 382 | "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", 383 | "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", 384 | "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", 385 | "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" 386 | ], 387 | "version": "==1.4.1" 388 | }, 389 | "typing-extensions": { 390 | "hashes": [ 391 | "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", 392 | "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", 393 | "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" 394 | ], 395 | "version": "==3.7.4.3" 396 | }, 397 | "wrapt": { 398 | "hashes": [ 399 | "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7" 400 | ], 401 | "version": "==1.12.1" 402 | } 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![PyPI version](https://badge.fury.io/py/SimConnect.svg)](https://badge.fury.io/py/SimConnect) 2 | # Python-SimConnect 3 | 4 | Python interface for Microsoft Flight Simulator 2020 (MSFS2020) using SimConnect 5 | 6 | This library allows Python scripts to read and set variables within MSFS2020 and trigger events within the simulation. 7 | 8 | It also includes, as an example, "Cockpit Companion", a flask mini http server which runs locally. It provides a web UI with a moving map and simulation variables. It also provides simulation data in JSON format in response to REST API requests. 9 | 10 | Full documentation for this example can be found at [https://msfs2020.cc](https://msfs2020.cc) and it is included in a standalone repo here on Github as [MSFS2020-cockpit-companion](https://github.com/hankhank10/MSFS2020-cockpit-companion). 11 | 12 | 13 | ## Mobiflight Simconnect events: 14 | 15 | Yes this supports the new [SimConnect commands that DocMoebiuz](https://forums.flightsimulator.com/t/full-g1000-control-now-with-mobiflight/348509) of [MobiFlight](https://www.mobiflight.com/en/index.html) developed. 16 | A full list of [commands and install instructions](https://pastebin.com/fMdB7at2) 17 | 18 | At this time MobiFlight SimConnect commands are not include in the AircraftEvents class and as so the AircraftEvents.find() and AircraftEvents.get() will not work. You will need to pass the Event ID to a new Event class as the Example below shows. 19 | 20 | 21 | ```py 22 | from SimConnect import * 23 | # Create SimConnect link 24 | sm = SimConnect() 25 | # Creat a function to call the MobiFlight AS1000_MFD_SOFTKEYS_3 event. 26 | Sk3 = Event(b'MobiFlight.AS1000_MFD_SOFTKEYS_3', sm) 27 | # Call the Event. 28 | Sk3() 29 | sm.exit() 30 | quit() 31 | ``` 32 | 33 | ## Python interface example 34 | 35 | ```py 36 | from SimConnect import * 37 | 38 | # Create SimConnect link 39 | sm = SimConnect() 40 | # Note the default _time is 2000 to be refreshed every 2 seconds 41 | aq = AircraftRequests(sm, _time=2000) 42 | # Use _time=ms where ms is the time in milliseconds to cache the data. 43 | # Setting ms to 0 will disable data caching and always pull new data from the sim. 44 | # There is still a timeout of 4 tries with a 10ms delay between checks. 45 | # If no data is received in 40ms the value will be set to None 46 | # Each request can be fine tuned by setting the time param. 47 | 48 | # To find and set timeout of cached data to 200ms: 49 | altitude = aq.find("PLANE_ALTITUDE") 50 | altitude.time = 200 51 | 52 | # Get the aircraft's current altitude 53 | altitude = aq.get("PLANE_ALTITUDE") 54 | altitude = altitude + 1000 55 | 56 | # Set the aircraft's current altitude 57 | aq.set("PLANE_ALTITUDE", altitude) 58 | 59 | ae = AircraftEvents(sm) 60 | # Trigger a simple event 61 | event_to_trigger = ae.find("AP_MASTER") # Toggles autopilot on or off 62 | event_to_trigger() 63 | 64 | # Trigger an event while passing a variable 65 | target_altitude = 15000 66 | event_to_trigger = ae.find("AP_ALT_VAR_SET_ENGLISH") # Sets AP autopilot hold level 67 | event_to_trigger(target_altitude) 68 | sm.exit() 69 | quit() 70 | ``` 71 | 72 | ## HTTP interface example 73 | 74 | Run `glass_server.py` using Python 3. 75 | 76 | #### `http://localhost:5000` 77 | Method: GET 78 | 79 | Variables: None 80 | 81 | Output: Web interface with moving map and aircraft information 82 | 83 | #### `http://localhost:5000/dataset/` 84 | Method: GET 85 | 86 | Arguments to pass: 87 | 88 | |Argument|Location|Description| 89 | |---|---|---| 90 | |dataset_name|in path|can be navigation, airspeed compass, vertical_speed, fuel, flaps, throttle, gear, trim, autopilot, cabin| 91 | 92 | Description: Returns set of variables from simulator in JSON format 93 | 94 | 95 | #### `http://localhost:5000/datapoint//get` 96 | Method: GET 97 | 98 | Arguments to pass: 99 | 100 | |Argument|Location|Description| 101 | |---|---|---| 102 | |datapoint_name|in path|any variable name from MS SimConnect documentation| 103 | 104 | Description: Returns individual variable from simulator in JSON format 105 | 106 | 107 | #### `http://localhost:5000/datapoint//set` 108 | Method: POST 109 | 110 | Arguments to pass: 111 | 112 | |Argument|Location|Description| 113 | |---|---|---| 114 | |datapoint_name|in path|any variable name from MS SimConnect documentation| 115 | |index (optional)|form or json|the relevant index if required (eg engine number) - if not passed defaults to None| 116 | |value_to_use (optional)|value to set variable to - if not passed defaults to 0| 117 | 118 | Description: Sets datapoint in the simulator 119 | 120 | #### `http://localhost:5000/event//trigger` 121 | Method: POST 122 | 123 | Arguments to pass: 124 | 125 | |Argument|Location|Description| 126 | |---|---|---| 127 | |event_name|in path|any event name from MS SimConnect documentation| 128 | |value_to_use (optional)|value to pass to the event| 129 | 130 | Description: Triggers an event in the simulator 131 | 132 | ## Running SimConnect on a separate system. 133 | 134 | #### Note: At this time SimConnect can only run on Windows hosts. 135 | 136 | Create a file called SimConnect.cfg in the same folder as your script. 137 | #### Sample SimConnect.cfg: 138 | ```ini 139 | ; Example SimConnect client configurations 140 | [SimConnect] 141 | Protocol=IPv4 142 | Address= 143 | Port=500 144 | ``` 145 | To enable the host running the sim to share over network, 146 | 147 | add \0.0.0.0\ 148 | 149 | under the \500\ in SimConnect.xml 150 | 151 | SimConnect.xml can be located at 152 | #### `%AppData%\Microsoft Flight Simulator\SimConnect.xml` 153 | 154 | #### Sample SimConnect.xml: 155 | ```xml 156 | 157 | 158 | 159 | SimConnect Server Configuration 160 | SimConnect.xml 161 | 162 | Static IP4 port 163 | IPv4 164 | local 165 | 500 166 |
0.0.0.0
167 | 64 168 | 41088 169 |
170 | ... 171 | ``` 172 | ## Notes: 173 | 174 | Python 64-bit is needed. You may see this Error if running 32-bit python: 175 | 176 | ```OSError: [WinError 193] %1 is not a valid Win32 application``` 177 | 178 | Per mracko on COM_RADIO_SET: 179 | 180 | MSFS uses the European COM frequency spacing of 8.33kHz for all default aircraft. 181 | This means that in practice, you increment the frequency by 0.005 MHz and 182 | skip x.x20, x.x45, x.x70, and x.x95 MHz frequencies. 183 | Have a look here http://g3asr.co.uk/calculators/833kHz.htm 184 | 185 | 186 | ## Events and Variables 187 | 188 | Below are links to the Microsoft documentation 189 | 190 | [Function](https://docs.microsoft.com/en-us/previous-versions/microsoft-esp/cc526983(v=msdn.10)) 191 | 192 | [Event IDs](https://docs.microsoft.com/en-us/previous-versions/microsoft-esp/cc526980(v=msdn.10)) 193 | 194 | [Simulation Variables](https://docs.flightsimulator.com/html/Programming_Tools/SimVars/Simulation_Variables.htm) 195 | -------------------------------------------------------------------------------- /SimConnect/Constants.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from ctypes.wintypes import * 3 | from ctypes import * 4 | 5 | LOGGER = logging.getLogger(__name__) 6 | 7 | 8 | # //---------------------------------------------------------------------------- 9 | # // Constants 10 | # //---------------------------------------------------------------------------- 11 | 12 | DWORD_MAX = DWORD(0xFFFFFFFF) 13 | SIMCONNECT_UNUSED = DWORD_MAX 14 | SIMCONNECT_OBJECT_ID_USER = DWORD(0) # proxy value for User vehicle ObjectID 15 | SIMCONNECT_UNUSED = DWORD_MAX # special value to indicate unused event, ID 16 | 17 | SIMCONNECT_CAMERA_IGNORE_FIELD = c_float( 18 | -1 19 | ) # Used to tell the Camera API to NOT modify the value in this part of the argument. 20 | 21 | SIMCONNECT_CLIENTDATA_MAX_SIZE = DWORD( 22 | 8192 23 | ) # maximum value for SimConnect_CreateClientData dwSize parameter 24 | 25 | 26 | # Notification Group priority values 27 | SIMCONNECT_GROUP_PRIORITY_HIGHEST = DWORD(1) # highest priority 28 | SIMCONNECT_GROUP_PRIORITY_HIGHEST_MASKABLE = DWORD( 29 | 10000000 30 | ) # highest priority that allows events to be masked 31 | SIMCONNECT_GROUP_PRIORITY_STANDARD = DWORD(1900000000) # standard priority 32 | SIMCONNECT_GROUP_PRIORITY_DEFAULT = DWORD(2000000000) # default priority 33 | SIMCONNECT_GROUP_PRIORITY_LOWEST = DWORD( 34 | 4000000000 35 | ) # priorities lower than this will be ignored 36 | 37 | # Weather observations Metar strings 38 | MAX_METAR_LENGTH = DWORD(2000) 39 | 40 | # Maximum thermal size is 100 km. 41 | MAX_THERMAL_SIZE = c_float(100000) 42 | MAX_THERMAL_RATE = c_float(1000) 43 | 44 | # SIMCONNECT_DATA_INITPOSITION.Airspeed 45 | INITPOSITION_AIRSPEED_CRUISE = DWORD(-1) # aircraft's cruise airspeed 46 | INITPOSITION_AIRSPEED_KEEP = DWORD(-2) # keep current airspeed 47 | 48 | # AddToClientDataDefinition dwSizeOrType parameter type values 49 | SIMCONNECT_CLIENTDATATYPE_INT8 = DWORD(-1) # 8-bit integer number 50 | SIMCONNECT_CLIENTDATATYPE_INT16 = DWORD(-2) # 16-bit integer number 51 | SIMCONNECT_CLIENTDATATYPE_INT32 = DWORD(-3) # 32-bit integer number 52 | SIMCONNECT_CLIENTDATATYPE_INT64 = DWORD(-4) # 64-bit integer number 53 | SIMCONNECT_CLIENTDATATYPE_FLOAT32 = DWORD(-5) # 32-bit floating-point number (float) 54 | SIMCONNECT_CLIENTDATATYPE_FLOAT64 = DWORD(-6) # 64-bit floating-point number (double) 55 | 56 | # AddToClientDataDefinition dwOffset parameter special values 57 | SIMCONNECT_CLIENTDATAOFFSET_AUTO = DWORD( 58 | -1 59 | ) # automatically compute offset of the ClientData variable 60 | 61 | # Open ConfigIndex parameter special value 62 | SIMCONNECT_OPEN_CONFIGINDEX_LOCAL = DWORD( 63 | -1 64 | ) # ignore SimConnect.cfg settings, and force local connection 65 | SIMCONNECT_OBJECT_ID = DWORD 66 | -------------------------------------------------------------------------------- /SimConnect/Enum.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum, IntFlag, Enum, auto 2 | from ctypes.wintypes import * 3 | from ctypes import * 4 | from .Constants import * 5 | 6 | import logging 7 | 8 | LOGGER = logging.getLogger(__name__) 9 | 10 | # ---------------------------------------------------------------------------- 11 | # Enum definitions 12 | # ---------------------------------------------------------------------------- 13 | 14 | 15 | # Define the types we need. 16 | class CtypesEnum(IntEnum): 17 | """A ctypes-compatible IntEnum superclass.""" 18 | 19 | @classmethod 20 | def from_param(cls, obj): 21 | return int(obj) 22 | 23 | 24 | # Define the types we need. 25 | class CtypesFlagEnum(IntFlag): 26 | """A ctypes-compatible Enum superclass.""" 27 | 28 | @classmethod 29 | def from_param(cls, obj): 30 | return int(obj) 31 | 32 | 33 | class AutoName(CtypesEnum): 34 | def _generate_next_value_(name, start, count, last_values): 35 | return count 36 | 37 | 38 | # Receive data types 39 | class SIMCONNECT_RECV_ID(CtypesEnum): 40 | SIMCONNECT_RECV_ID_NULL = 0 41 | SIMCONNECT_RECV_ID_EXCEPTION = 1 42 | SIMCONNECT_RECV_ID_OPEN = 2 43 | SIMCONNECT_RECV_ID_QUIT = 3 44 | SIMCONNECT_RECV_ID_EVENT = 4 45 | SIMCONNECT_RECV_ID_EVENT_OBJECT_ADDREMOVE = 5 46 | SIMCONNECT_RECV_ID_EVENT_FILENAME = 6 47 | SIMCONNECT_RECV_ID_EVENT_FRAME = 7 48 | SIMCONNECT_RECV_ID_SIMOBJECT_DATA = 8 49 | SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE = 9 50 | SIMCONNECT_RECV_ID_WEATHER_OBSERVATION = 10 51 | SIMCONNECT_RECV_ID_CLOUD_STATE = 11 52 | SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID = 12 53 | SIMCONNECT_RECV_ID_RESERVED_KEY = 13 54 | SIMCONNECT_RECV_ID_CUSTOM_ACTION = 14 55 | SIMCONNECT_RECV_ID_SYSTEM_STATE = 15 56 | SIMCONNECT_RECV_ID_CLIENT_DATA = 16 57 | SIMCONNECT_RECV_ID_EVENT_WEATHER_MODE = 17 58 | SIMCONNECT_RECV_ID_AIRPORT_LIST = 18 59 | SIMCONNECT_RECV_ID_VOR_LIST = 19 60 | SIMCONNECT_RECV_ID_NDB_LIST = 20 61 | SIMCONNECT_RECV_ID_WAYPOINT_LIST = 21 62 | SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_SERVER_STARTED = 22 63 | SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_CLIENT_STARTED = 23 64 | SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_SESSION_ENDED = 24 65 | SIMCONNECT_RECV_ID_EVENT_RACE_END = 25 66 | SIMCONNECT_RECV_ID_EVENT_RACE_LAP = 26 67 | 68 | 69 | # Data data types 70 | class SIMCONNECT_DATATYPE(CtypesEnum): 71 | SIMCONNECT_DATATYPE_INVALID = 0 # invalid data type 72 | SIMCONNECT_DATATYPE_INT32 = 1 # 32-bit integer number 73 | SIMCONNECT_DATATYPE_INT64 = 2 # 64-bit integer number 74 | SIMCONNECT_DATATYPE_FLOAT32 = 3 # 32-bit floating-point number (float) 75 | SIMCONNECT_DATATYPE_FLOAT64 = 4 # 64-bit floating-point number (double) 76 | SIMCONNECT_DATATYPE_STRING8 = 5 # 8-byte string 77 | SIMCONNECT_DATATYPE_STRING32 = 6 # 32-byte string 78 | SIMCONNECT_DATATYPE_STRING64 = 7 # 64-byte string 79 | SIMCONNECT_DATATYPE_STRING128 = 8 # 128-byte string 80 | SIMCONNECT_DATATYPE_STRING256 = 9 # 256-byte string 81 | SIMCONNECT_DATATYPE_STRING260 = 10 # 260-byte string 82 | SIMCONNECT_DATATYPE_STRINGV = 11 # variable-length string 83 | 84 | SIMCONNECT_DATATYPE_INITPOSITION = 12 # see SIMCONNECT_DATA_INITPOSITION 85 | SIMCONNECT_DATATYPE_MARKERSTATE = 13 # see SIMCONNECT_DATA_MARKERSTATE 86 | SIMCONNECT_DATATYPE_WAYPOINT = 14 # see SIMCONNECT_DATA_WAYPOINT 87 | SIMCONNECT_DATATYPE_LATLONALT = 15 # see SIMCONNECT_DATA_LATLONALT 88 | SIMCONNECT_DATATYPE_XYZ = 16 # see SIMCONNECT_DATA_XYZ 89 | 90 | SIMCONNECT_DATATYPE_MAX = 17 # enum limit 91 | 92 | 93 | # Exception error types 94 | class SIMCONNECT_EXCEPTION(CtypesEnum): 95 | SIMCONNECT_EXCEPTION_NONE = 0 96 | 97 | SIMCONNECT_EXCEPTION_ERROR = 1 98 | SIMCONNECT_EXCEPTION_SIZE_MISMATCH = 2 99 | SIMCONNECT_EXCEPTION_UNRECOGNIZED_ID = 3 100 | SIMCONNECT_EXCEPTION_UNOPENED = 4 101 | SIMCONNECT_EXCEPTION_VERSION_MISMATCH = 5 102 | SIMCONNECT_EXCEPTION_TOO_MANY_GROUPS = 6 103 | SIMCONNECT_EXCEPTION_NAME_UNRECOGNIZED = 7 104 | SIMCONNECT_EXCEPTION_TOO_MANY_EVENT_NAMES = 8 105 | SIMCONNECT_EXCEPTION_EVENT_ID_DUPLICATE = 9 106 | SIMCONNECT_EXCEPTION_TOO_MANY_MAPS = 10 107 | SIMCONNECT_EXCEPTION_TOO_MANY_OBJECTS = 11 108 | SIMCONNECT_EXCEPTION_TOO_MANY_REQUESTS = 12 109 | SIMCONNECT_EXCEPTION_WEATHER_INVALID_PORT = 13 110 | SIMCONNECT_EXCEPTION_WEATHER_INVALID_METAR = 14 111 | SIMCONNECT_EXCEPTION_WEATHER_UNABLE_TO_GET_OBSERVATION = 15 112 | SIMCONNECT_EXCEPTION_WEATHER_UNABLE_TO_CREATE_STATION = 16 113 | SIMCONNECT_EXCEPTION_WEATHER_UNABLE_TO_REMOVE_STATION = 17 114 | SIMCONNECT_EXCEPTION_INVALID_DATA_TYPE = 18 115 | SIMCONNECT_EXCEPTION_INVALID_DATA_SIZE = 19 116 | SIMCONNECT_EXCEPTION_DATA_ERROR = 20 117 | SIMCONNECT_EXCEPTION_INVALID_ARRAY = 21 118 | SIMCONNECT_EXCEPTION_CREATE_OBJECT_FAILED = 22 119 | SIMCONNECT_EXCEPTION_LOAD_FLIGHTPLAN_FAILED = 23 120 | SIMCONNECT_EXCEPTION_OPERATION_INVALID_FOR_OBJECT_TYPE = 24 121 | SIMCONNECT_EXCEPTION_ILLEGAL_OPERATION = 24 122 | SIMCONNECT_EXCEPTION_ALREADY_SUBSCRIBED = 26 123 | SIMCONNECT_EXCEPTION_INVALID_ENUM = 27 124 | SIMCONNECT_EXCEPTION_DEFINITION_ERROR = 28 125 | SIMCONNECT_EXCEPTION_DUPLICATE_ID = 29 126 | SIMCONNECT_EXCEPTION_DATUM_ID = 30 127 | SIMCONNECT_EXCEPTION_OUT_OF_BOUNDS = 31 128 | SIMCONNECT_EXCEPTION_ALREADY_CREATED = 32 129 | SIMCONNECT_EXCEPTION_OBJECT_OUTSIDE_REALITY_BUBBLE = 33 130 | SIMCONNECT_EXCEPTION_OBJECT_CONTAINER = 34 131 | SIMCONNECT_EXCEPTION_OBJECT_AI = 35 132 | SIMCONNECT_EXCEPTION_OBJECT_ATC = 36 133 | SIMCONNECT_EXCEPTION_OBJECT_SCHEDULE = 37 134 | 135 | 136 | # Object types 137 | class SIMCONNECT_SIMOBJECT_TYPE(CtypesEnum): 138 | SIMCONNECT_SIMOBJECT_TYPE_USER = 0 139 | SIMCONNECT_SIMOBJECT_TYPE_ALL = 1 140 | SIMCONNECT_SIMOBJECT_TYPE_AIRCRAFT = 2 141 | SIMCONNECT_SIMOBJECT_TYPE_HELICOPTER = 3 142 | SIMCONNECT_SIMOBJECT_TYPE_BOAT = 4 143 | SIMCONNECT_SIMOBJECT_TYPE_GROUND = 5 144 | 145 | 146 | # EventState values 147 | class SIMCONNECT_STATE(CtypesEnum): 148 | SIMCONNECT_STATE_OFF = 0 149 | SIMCONNECT_STATE_ON = 1 150 | 151 | 152 | # Object Data Request Period values 153 | class SIMCONNECT_PERIOD(CtypesEnum): # 154 | SIMCONNECT_PERIOD_NEVER = 0 155 | SIMCONNECT_PERIOD_ONCE = 1 156 | SIMCONNECT_PERIOD_VISUAL_FRAME = 2 157 | SIMCONNECT_PERIOD_SIM_FRAME = 3 158 | SIMCONNECT_PERIOD_SECOND = 4 159 | 160 | 161 | class SIMCONNECT_MISSION_END(CtypesEnum): # 162 | SIMCONNECT_MISSION_FAILED = 0 163 | SIMCONNECT_MISSION_CRASHED = 1 164 | SIMCONNECT_MISSION_SUCCEEDED = 2 165 | 166 | 167 | # ClientData Request Period values 168 | class SIMCONNECT_CLIENT_DATA_PERIOD(CtypesEnum): # 169 | SIMCONNECT_CLIENT_DATA_PERIOD_NEVER = 0 170 | SIMCONNECT_CLIENT_DATA_PERIOD_ONCE = 1 171 | SIMCONNECT_CLIENT_DATA_PERIOD_VISUAL_FRAME = 2 172 | SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET = 3 173 | SIMCONNECT_CLIENT_DATA_PERIOD_SECOND = 4 174 | 175 | 176 | class SIMCONNECT_TEXT_TYPE(CtypesEnum): # 177 | SIMCONNECT_TEXT_TYPE_SCROLL_BLACK = 0 178 | SIMCONNECT_TEXT_TYPE_SCROLL_WHITE = 1 179 | SIMCONNECT_TEXT_TYPE_SCROLL_RED = 2 180 | SIMCONNECT_TEXT_TYPE_SCROLL_GREEN = 3 181 | SIMCONNECT_TEXT_TYPE_SCROLL_BLUE = 4 182 | SIMCONNECT_TEXT_TYPE_SCROLL_YELLOW = 5 183 | SIMCONNECT_TEXT_TYPE_SCROLL_MAGENTA = 6 184 | SIMCONNECT_TEXT_TYPE_SCROLL_CYAN = 7 185 | SIMCONNECT_TEXT_TYPE_PRINT_BLACK = 0x100 186 | SIMCONNECT_TEXT_TYPE_PRINT_WHITE = 0x101 187 | SIMCONNECT_TEXT_TYPE_PRINT_RED = 0x102 188 | SIMCONNECT_TEXT_TYPE_PRINT_GREEN = 0x103 189 | SIMCONNECT_TEXT_TYPE_PRINT_BLUE = 0x104 190 | SIMCONNECT_TEXT_TYPE_PRINT_YELLOW = 0x105 191 | SIMCONNECT_TEXT_TYPE_PRINT_MAGENTA = 0x106 192 | SIMCONNECT_TEXT_TYPE_PRINT_CYAN = 0x107 193 | SIMCONNECT_TEXT_TYPE_MENU = 0x0200 194 | 195 | 196 | class SIMCONNECT_TEXT_RESULT(CtypesEnum): # 197 | SIMCONNECT_TEXT_RESULT_MENU_SELECT_1 = 0 198 | SIMCONNECT_TEXT_RESULT_MENU_SELECT_2 = 1 199 | SIMCONNECT_TEXT_RESULT_MENU_SELECT_3 = 2 200 | SIMCONNECT_TEXT_RESULT_MENU_SELECT_4 = 3 201 | SIMCONNECT_TEXT_RESULT_MENU_SELECT_5 = 4 202 | SIMCONNECT_TEXT_RESULT_MENU_SELECT_6 = 5 203 | SIMCONNECT_TEXT_RESULT_MENU_SELECT_7 = 6 204 | SIMCONNECT_TEXT_RESULT_MENU_SELECT_8 = 7 205 | SIMCONNECT_TEXT_RESULT_MENU_SELECT_9 = 8 206 | SIMCONNECT_TEXT_RESULT_MENU_SELECT_10 = 9 207 | SIMCONNECT_TEXT_RESULT_DISPLAYED = 0x10000 208 | SIMCONNECT_TEXT_RESULT_QUEUED = 0x10001 209 | SIMCONNECT_TEXT_RESULT_REMOVED = 0x1002 210 | SIMCONNECT_TEXT_RESULT_REPLACED = 0x10003 211 | SIMCONNECT_TEXT_RESULT_TIMEOUT = 0x10004 212 | 213 | 214 | class SIMCONNECT_WEATHER_MODE(CtypesEnum): # 215 | SIMCONNECT_WEATHER_MODE_THEME = 0 216 | SIMCONNECT_WEATHER_MODE_RWW = 1 217 | SIMCONNECT_WEATHER_MODE_CUSTOM = 2 218 | SIMCONNECT_WEATHER_MODE_GLOBAL = 3 219 | 220 | 221 | class SIMCONNECT_FACILITY_LIST_TYPE(CtypesEnum): # 222 | SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT = 0 223 | SIMCONNECT_FACILITY_LIST_TYPE_WAYPOINT = 1 224 | SIMCONNECT_FACILITY_LIST_TYPE_NDB = 2 225 | SIMCONNECT_FACILITY_LIST_TYPE_VOR = 3 226 | SIMCONNECT_FACILITY_LIST_TYPE_COUNT = 4 # invalid 227 | 228 | 229 | class SIMCONNECT_VOR_FLAGS(CtypesFlagEnum): # flags for SIMCONNECT_RECV_ID_VOR_LIST 230 | SIMCONNECT_RECV_ID_VOR_LIST_HAS_NAV_SIGNAL = 0x00000001 # Has Nav signal 231 | SIMCONNECT_RECV_ID_VOR_LIST_HAS_LOCALIZER = 0x00000002 # Has localizer 232 | SIMCONNECT_RECV_ID_VOR_LIST_HAS_GLIDE_SLOPE = 0x00000004 # Has Nav signal 233 | SIMCONNECT_RECV_ID_VOR_LIST_HAS_DME = 0x00000008 # Station has DME 234 | 235 | 236 | # bits for the Waypoint Flags field: may be combined 237 | class SIMCONNECT_WAYPOINT_FLAGS(CtypesFlagEnum): # 238 | SIMCONNECT_WAYPOINT_NONE = 0x00 # 239 | SIMCONNECT_WAYPOINT_SPEED_REQUESTED = 0x04 # requested speed at waypoint is valid 240 | SIMCONNECT_WAYPOINT_THROTTLE_REQUESTED = 0x08 # request a specific throttle percentage 241 | SIMCONNECT_WAYPOINT_COMPUTE_VERTICAL_SPEED = 0x10 # compute vertical to speed to reach waypoint altitude when crossing the waypoint 242 | SIMCONNECT_WAYPOINT_ALTITUDE_IS_AGL = 0x20 # AltitudeIsAGL 243 | SIMCONNECT_WAYPOINT_ON_GROUND = 0x00100000 # place this waypoint on the ground 244 | SIMCONNECT_WAYPOINT_REVERSE = 0x00200000 # Back up to this waypoint. Only valid on first waypoint 245 | SIMCONNECT_WAYPOINT_WRAP_TO_FIRST = 0x00400000 246 | 247 | 248 | class SIMCONNECT_EVENT_FLAG(CtypesFlagEnum): # 249 | SIMCONNECT_EVENT_FLAG_DEFAULT = 0x00000000 # 250 | SIMCONNECT_EVENT_FLAG_FAST_REPEAT_TIMER = 0x00000001 # set event repeat timer to simulate fast repeat 251 | SIMCONNECT_EVENT_FLAG_SLOW_REPEAT_TIMER = 0x00000002 # set event repeat timer to simulate slow repeat 252 | SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY = 0x00000010 # interpret GroupID parameter as priority value 253 | 254 | 255 | class SIMCONNECT_DATA_REQUEST_FLAG(CtypesFlagEnum): # 256 | SIMCONNECT_DATA_REQUEST_FLAG_DEFAULT = 0x00000000 257 | SIMCONNECT_DATA_REQUEST_FLAG_CHANGED = 0x00000001 # send requested data when value(s) change 258 | SIMCONNECT_DATA_REQUEST_FLAG_TAGGED = 0x00000002 # send requested data in tagged format 259 | 260 | 261 | class SIMCONNECT_DATA_SET_FLAG(CtypesFlagEnum): # 262 | SIMCONNECT_DATA_SET_FLAG_DEFAULT = 0x00000000 263 | SIMCONNECT_DATA_SET_FLAG_TAGGED = 0x00000001 # data is in tagged format 264 | 265 | 266 | class SIMCONNECT_CREATE_CLIENT_DATA_FLAG(CtypesFlagEnum): # 267 | SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT = 0x00000000 # 268 | SIMCONNECT_CREATE_CLIENT_DATA_FLAG_READ_ONLY = 0x00000001 # permit only ClientData creator to write into ClientData 269 | 270 | 271 | class SIMCONNECT_CLIENT_DATA_REQUEST_FLAG(CtypesFlagEnum): # 272 | SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT = 0x00000000 # 273 | SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_CHANGED = 0x00000001 # send requested ClientData when value(s) change 274 | SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_TAGGED = 0x00000002 # send requested ClientData in tagged format 275 | 276 | 277 | class SIMCONNECT_CLIENT_DATA_SET_FLAG(CtypesFlagEnum): # 278 | SIMCONNECT_CLIENT_DATA_SET_FLAG_DEFAULT = 0x00000000 # 279 | SIMCONNECT_CLIENT_DATA_SET_FLAG_TAGGED = 0x00000001 # data is in tagged format 280 | 281 | 282 | class SIMCONNECT_VIEW_SYSTEM_EVENT_DATA(CtypesFlagEnum): # dwData contains these flags for the "View" System Event 283 | SIMCONNECT_VIEW_SYSTEM_EVENT_DATA_COCKPIT_2D = 0x00000001 # 2D Panels in cockpit view 284 | SIMCONNECT_VIEW_SYSTEM_EVENT_DATA_COCKPIT_VIRTUAL = 0x00000002 # Virtual (3D) panels in cockpit view 285 | SIMCONNECT_VIEW_SYSTEM_EVENT_DATA_ORTHOGONAL = 0x00000004 # Orthogonal (Map) view 286 | 287 | 288 | class SIMCONNECT_SOUND_SYSTEM_EVENT_DATA(CtypesFlagEnum): # dwData contains these flags for the "Sound" System Event 289 | SIMCONNECT_SOUND_SYSTEM_EVENT_DATA_MASTER = 0x00000001# Sound Master 290 | 291 | 292 | class SIMCONNECT_PICK_FLAGS(CtypesFlagEnum): 293 | SIMCONNECT_PICK_GROUND = 0x01 # pick ground/ pick result item is ground location 294 | SIMCONNECT_PICK_AI = 0x02 # pick AI / pick result item is AI, (dwSimObjectID is valid) 295 | SIMCONNECT_PICK_SCENERY = 0x04 # pick scenery/ pick result item is scenery object (hSceneryObject is valid) 296 | SIMCONNECT_PICK_ALL = 0x04 | 0x02 | 0x01 # pick all / (not valid on pick result item) 297 | SIMCONNECT_PICK_COORDSASPIXELS = 0x08 # 298 | 299 | 300 | # ---------------------------------------------------------------------------- 301 | # User-defined enums 302 | # ---------------------------------------------------------------------------- 303 | class SIMCONNECT_NOTIFICATION_GROUP_ID( 304 | AutoName 305 | ): # client-defined notification group ID 306 | pass 307 | 308 | 309 | class SIMCONNECT_INPUT_GROUP_ID(AutoName): # client-defined input group ID 310 | pass 311 | 312 | 313 | class SIMCONNECT_DATA_DEFINITION_ID(AutoName): # client-defined data definition ID 314 | pass 315 | 316 | 317 | class SIMCONNECT_DATA_REQUEST_ID(AutoName): # client-defined request data ID 318 | pass 319 | 320 | 321 | class SIMCONNECT_CLIENT_EVENT_ID(AutoName): # client-defined client event ID 322 | EVENT_SIM_START = auto() 323 | EVENT_SIM_STOP = auto() 324 | EVENT_SIM_PAUSED = auto() 325 | EVENT_SIM_UNPAUSED = auto() 326 | pass 327 | 328 | 329 | class SIMCONNECT_CLIENT_DATA_ID(AutoName): # client-defined client data ID 330 | pass 331 | 332 | 333 | class SIMCONNECT_CLIENT_DATA_DEFINITION_ID( 334 | AutoName 335 | ): # client-defined client data definition ID 336 | pass 337 | 338 | 339 | # ---------------------------------------------------------------------------- 340 | # Struct definitions 341 | # ---------------------------------------------------------------------------- 342 | 343 | 344 | class SIMCONNECT_RECV(Structure): 345 | _fields_ = [("dwSize", DWORD), ("dwVersion", DWORD), ("dwID", DWORD)] 346 | 347 | 348 | class SIMCONNECT_RECV_EXCEPTION( 349 | SIMCONNECT_RECV 350 | ): # when dwID == SIMCONNECT_RECV_ID_EXCEPTION 351 | _fields_ = [ 352 | ("dwException", DWORD), # see SIMCONNECT_EXCEPTION 353 | ("UNKNOWN_SENDID", DWORD), # 354 | ("dwSendID", DWORD), # see SimConnect_GetLastSentPacketID 355 | ("UNKNOWN_INDEX", DWORD), # 356 | ("dwIndex", DWORD), # index of parameter that was source of error 357 | ] 358 | 359 | 360 | class SIMCONNECT_RECV_OPEN(SIMCONNECT_RECV): # when dwID == SIMCONNECT_RECV_ID_OPEN 361 | _fields_ = [ 362 | ("szApplicationName", c_char * 256), 363 | ("dwApplicationVersionMajor", DWORD), 364 | ("dwApplicationVersionMinor", DWORD), 365 | ("dwApplicationBuildMajor", DWORD), 366 | ("dwApplicationBuildMinor", DWORD), 367 | ("dwSimConnectVersionMajor", DWORD), 368 | ("dwSimConnectVersionMinor", DWORD), 369 | ("dwSimConnectBuildMajor", DWORD), 370 | ("dwSimConnectBuildMinor", DWORD), 371 | ("dwReserved1", DWORD), 372 | ("dwReserved2", DWORD), 373 | ] 374 | 375 | 376 | class SIMCONNECT_RECV_QUIT(SIMCONNECT_RECV): # when dwID == SIMCONNECT_RECV_ID_QUIT 377 | pass 378 | 379 | 380 | class SIMCONNECT_RECV_EVENT(SIMCONNECT_RECV): # when dwID == SIMCONNECT_RECV_ID_EVENT 381 | UNKNOWN_GROUP = DWORD_MAX 382 | _fields_ = [ 383 | ("uGroupID", DWORD), 384 | ("uEventID", DWORD), 385 | ("dwData", DWORD), # uEventID-dependent context 386 | ] 387 | 388 | 389 | class SIMCONNECT_RECV_EVENT_FILENAME( 390 | SIMCONNECT_RECV_EVENT 391 | ): # when dwID == SIMCONNECT_RECV_ID_EVENT_FILENAME 392 | _fields_ = [ 393 | ("zFileName", c_char * MAX_PATH), # uEventID-dependent context 394 | ("dwFlags", DWORD), 395 | ] 396 | 397 | 398 | class SIMCONNECT_RECV_EVENT_OBJECT_ADDREMOVE( 399 | SIMCONNECT_RECV_EVENT 400 | ): # when dwID == SIMCONNECT_RECV_ID_EVENT_FILENAME 401 | eObjType = SIMCONNECT_SIMOBJECT_TYPE 402 | 403 | 404 | class SIMCONNECT_RECV_EVENT_FRAME( 405 | SIMCONNECT_RECV_EVENT 406 | ): # when dwID == SIMCONNECT_RECV_ID_EVENT_FRAME 407 | _fields_ = [("fFrameRate", c_float), ("fSimSpeed", c_float)] 408 | 409 | 410 | class SIMCONNECT_RECV_EVENT_MULTIPLAYER_SERVER_STARTED(SIMCONNECT_RECV_EVENT): 411 | # when dwID == SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_SERVER_STARTED 412 | # No event specific data, for now 413 | pass 414 | 415 | 416 | class SIMCONNECT_RECV_EVENT_MULTIPLAYER_CLIENT_STARTED(SIMCONNECT_RECV_EVENT): 417 | # when dwID == SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_CLIENT_STARTED 418 | # No event specific data, for now 419 | pass 420 | 421 | 422 | class SIMCONNECT_RECV_EVENT_MULTIPLAYER_SESSION_ENDED(SIMCONNECT_RECV_EVENT): 423 | # when dwID == SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_SESSION_ENDED 424 | # No event specific data, for now 425 | pass 426 | 427 | 428 | # SIMCONNECT_DATA_RACE_RESULT 429 | class SIMCONNECT_DATA_RACE_RESULT(Structure): 430 | _fields_ = [ 431 | ("dwNumberOfRacers", DWORD), # The total number of racers 432 | ("szPlayerName", c_char * MAX_PATH), # The name of the player 433 | ( 434 | "szSessionType", 435 | c_char * MAX_PATH, 436 | ), # The type of the multiplayer session: "LAN", "GAMESPY") 437 | ("szAircraft", c_char * MAX_PATH), # The aircraft type 438 | ("szPlayerRole", c_char * MAX_PATH), # The player role in the mission 439 | ("fTotalTime", c_double), # Total time in seconds, 0 means DNF 440 | ("fPenaltyTime", c_double), # Total penalty time in seconds 441 | ( 442 | "MissionGUID", 443 | DWORD, 444 | ), # The name of the mission to execute, NULL if no mission 445 | ("dwIsDisqualified", c_double), # non 0 - disqualified, 0 - not disqualified 446 | ] 447 | 448 | 449 | class SIMCONNECT_RECV_EVENT_RACE_END( 450 | SIMCONNECT_RECV_EVENT 451 | ): # when dwID == SIMCONNECT_RECV_ID_EVENT_RACE_END 452 | RacerData = SIMCONNECT_DATA_RACE_RESULT 453 | _fields_ = [("dwRacerNumber", DWORD)] # The index of the racer the results are for 454 | 455 | 456 | class SIMCONNECT_RECV_EVENT_RACE_LAP( 457 | SIMCONNECT_RECV_EVENT 458 | ): # when dwID == SIMCONNECT_RECV_ID_EVENT_RACE_LAP 459 | RacerData = SIMCONNECT_DATA_RACE_RESULT 460 | _fields_ = [("dwLapIndex", DWORD)] # The index of the lap the results are for 461 | 462 | 463 | class SIMCONNECT_RECV_SIMOBJECT_DATA(SIMCONNECT_RECV): 464 | _fields_ = [ 465 | ("dwRequestID", DWORD), 466 | ("dwObjectID", DWORD), 467 | ("dwDefineID", DWORD), 468 | ("dwFlags", DWORD), 469 | ("dwentrynumber", DWORD), 470 | ("dwoutof", DWORD), 471 | ("dwDefineCount", DWORD), 472 | ("dwData", DWORD * 8192), 473 | ] 474 | 475 | 476 | class SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE(SIMCONNECT_RECV_SIMOBJECT_DATA): 477 | _fields_ = [] 478 | 479 | 480 | class SIMCONNECT_RECV_CLIENT_DATA( 481 | SIMCONNECT_RECV_SIMOBJECT_DATA 482 | ): # when dwID == SIMCONNECT_RECV_ID_CLIENT_DATA 483 | _fields_ = [] 484 | 485 | 486 | class SIMCONNECT_RECV_WEATHER_OBSERVATION( 487 | SIMCONNECT_RECV 488 | ): # when dwID == SIMCONNECT_RECV_ID_WEATHER_OBSERVATION 489 | _fields_ = [ 490 | ("dwRequestID", DWORD), 491 | ( 492 | "szMetar", 493 | c_char * MAX_METAR_LENGTH.value, 494 | ), # Variable length string whose maximum size is MAX_METAR_LENGTH 495 | ] 496 | 497 | 498 | SIMCONNECT_CLOUD_STATE_ARRAY_WIDTH = 64 499 | SIMCONNECT_CLOUD_STATE_ARRAY_SIZE = ( 500 | SIMCONNECT_CLOUD_STATE_ARRAY_WIDTH * SIMCONNECT_CLOUD_STATE_ARRAY_WIDTH 501 | ) 502 | 503 | 504 | class SIMCONNECT_RECV_CLOUD_STATE(SIMCONNECT_RECV): 505 | # when dwID == SIMCONNECT_RECV_ID_CLOUD_STATE 506 | _fields_ = [ 507 | ("dwRequestID", DWORD), 508 | ("dwArraySize", DWORD), 509 | # SIMCONNECT_FIXEDTYPE_DATAV(BYTE, rgbData, dwArraySize, U1 /*member of UnmanagedType enum*/ , System::Byte /*cli type*/); 510 | ] 511 | 512 | 513 | class SIMCONNECT_RECV_ASSIGNED_OBJECT_ID( 514 | SIMCONNECT_RECV 515 | ): # when dwID == SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID 516 | _fields_ = [("dwRequestID", DWORD), ("dwObjectID", DWORD)] 517 | 518 | 519 | class SIMCONNECT_RECV_RESERVED_KEY( 520 | SIMCONNECT_RECV 521 | ): # when dwID == SIMCONNECT_RECV_ID_RESERVED_KEY 522 | _fields_ = [("szChoiceReserved", c_char * 30), ("szReservedKey", c_char * 30)] 523 | 524 | 525 | class SIMCONNECT_RECV_SYSTEM_STATE( 526 | SIMCONNECT_RECV 527 | ): # when dwID == SIMCONNECT_RECV_ID_SYSTEM_STATE 528 | _fields_ = [ 529 | ("dwRequestID", DWORD), 530 | ("dwInteger", DWORD), 531 | ("fFloat", c_float), 532 | ("szString", c_char * MAX_PATH), 533 | ] 534 | 535 | 536 | class SIMCONNECT_RECV_CUSTOM_ACTION(SIMCONNECT_RECV_EVENT): # 537 | _fields_ = [ 538 | ("guidInstanceId", DWORD), # Instance id of the action that executed 539 | ("dwWaitForCompletion", DWORD), # Wait for completion flag on the action 540 | ( 541 | "szPayLoad", 542 | c_char, 543 | ), # Variable length string payload associated with the mission action. 544 | ] 545 | 546 | 547 | class SIMCONNECT_RECV_EVENT_WEATHER_MODE(SIMCONNECT_RECV_EVENT): # 548 | _fields_ = ( 549 | [] 550 | ) # No event specific data - the new weather mode is in the base structure dwData member. 551 | 552 | 553 | # SIMCONNECT_RECV_FACILITIES_LIST 554 | class SIMCONNECT_RECV_FACILITIES_LIST(SIMCONNECT_RECV): # 555 | _fields_ = [ 556 | ("dwRequestID", DWORD), 557 | ("dwArraySize", DWORD), 558 | ( 559 | "dwEntryNumber", 560 | DWORD, 561 | ), # when the array of items is too big for one send, which send this is (0..dwOutOf-1) 562 | ("dwOutOf", DWORD), # total number of transmissions the list is chopped into 563 | ] 564 | 565 | 566 | # SIMCONNECT_DATA_FACILITY_AIRPORT 567 | class SIMCONNECT_DATA_FACILITY_AIRPORT(Structure): # 568 | _fields_ = [ 569 | ("Icao", c_char * 9), # ICAO of the object 570 | ("Latitude", c_double), # degrees 571 | ("Longitude", c_double), # degrees 572 | ("Altitude", c_double), # meters 573 | ] 574 | 575 | 576 | # SIMCONNECT_RECV_AIRPORT_LIST 577 | # class SIMCONNECT_RECV_AIRPORT_LIST(SIMCONNECT_RECV_FACILITIES_LIST): # 578 | # _fields_ = [ 579 | # ("SIMCONNECT_DATA_FACILITY_AIRPORT", rgData * dwArraySize) 580 | # ] 581 | # SIMCONNECT_FIXEDTYPE_DATAV(SIMCONNECT_DATA_FACILITY_AIRPORT, rgData, dwArraySize, 582 | # U1 /*member of UnmanagedType enum*/, SIMCONNECT_DATA_FACILITY_AIRPORT /*cli type*/); 583 | 584 | 585 | # SIMCONNECT_DATA_FACILITY_WAYPOINT 586 | class SIMCONNECT_DATA_FACILITY_WAYPOINT(SIMCONNECT_DATA_FACILITY_AIRPORT): # 587 | _fields_ = [("fMagVar", c_float)] # Magvar in degrees 588 | 589 | 590 | # SIMCONNECT_RECV_WAYPOINT_LIST 591 | # class SIMCONNECT_RECV_WAYPOINT_LIST(SIMCONNECT_RECV_FACILITIES_LIST): # 592 | # _fields_ = [ 593 | # ("", ) 594 | # ] 595 | # SIMCONNECT_FIXEDTYPE_DATAV(SIMCONNECT_DATA_FACILITY_WAYPOINT, 596 | # rgData 597 | # dwArraySize, 598 | # U1 /*member of UnmanagedType enum*/, 599 | # SIMCONNECT_DATA_FACILITY_WAYPOINT /*cli type*/); 600 | 601 | 602 | # SIMCONNECT_DATA_FACILITY_NDB 603 | class SIMCONNECT_DATA_FACILITY_NDB(SIMCONNECT_DATA_FACILITY_WAYPOINT): # 604 | _fields_ = [("fFrequency", DWORD)] # frequency in Hz 605 | 606 | 607 | # SIMCONNECT_RECV_NDB_LIST 608 | # class SIMCONNECT_RECV_NDB_LIST(SIMCONNECT_RECV_FACILITIES_LIST): # 609 | # _fields_ = [ 610 | # ("", ) 611 | # ] 612 | # SIMCONNECT_FIXEDTYPE_DATAV(SIMCONNECT_DATA_FACILITY_NDB, 613 | # rgData 614 | # dwArraySize, 615 | # U1 /*member of UnmanagedType enum*/, 616 | # SIMCONNECT_DATA_FACILITY_NDB /*cli type*/); 617 | 618 | 619 | # SIMCONNECT_DATA_FACILITY_VOR 620 | class SIMCONNECT_DATA_FACILITY_VOR(SIMCONNECT_DATA_FACILITY_NDB): # 621 | _fields_ = [ 622 | ("Flags", DWORD), # SIMCONNECT_VOR_FLAGS 623 | ("fLocalizer", c_float), # Localizer in degrees 624 | ("GlideLat", c_double), # Glide Slope Location (deg, deg, meters) 625 | ("GlideLon", c_double), # 626 | ("GlideAlt", c_double), # 627 | ("fGlideSlopeAngle", c_float), # Glide Slope in degrees 628 | ] 629 | 630 | 631 | # SIMCONNECT_RECV_VOR_LIST 632 | # class SIMCONNECT_RECV_VOR_LIST(SIMCONNECT_RECV_FACILITIES_LIST): # 633 | # _fields_ = [ 634 | # ("", ) 635 | # ] 636 | # SIMCONNECT_FIXEDTYPE_DATAV(SIMCONNECT_DATA_FACILITY_VOR, 637 | # rgData 638 | # dwArraySize, 639 | # U1 /*member of UnmanagedType enum*/, SIMCONNECT_DATA_FACILITY_VOR /*cli type*/); 640 | 641 | 642 | class SIMCONNECT_RECV_PICK( 643 | SIMCONNECT_RECV 644 | ): # when dwID == SIMCONNECT_RECV_ID_RESERVED_KEY 645 | _fields_ = [ 646 | ("hContext", HANDLE), 647 | ("dwFlags", DWORD), 648 | ("Latitude", c_double), # degrees 649 | ("Longitude", c_double), # degrees 650 | ("Altitude", c_double), # feet 651 | ("xPos", c_int), # reserved 652 | ("yPos", c_int), # reserved 653 | ("dwSimObjectID", DWORD), 654 | ("hSceneryObject", HANDLE), 655 | ( 656 | "dwentrynumber", 657 | DWORD, 658 | ), # if multiple objects returned, this is number out of . 659 | ("dwoutof", DWORD), # note: starts with 1, not 0. 660 | ] 661 | 662 | 663 | # SIMCONNECT_DATATYPE_INITPOSITION 664 | class SIMCONNECT_DATA_INITPOSITION(Structure): # 665 | _fields_ = [ 666 | ("Latitude", c_double), # degrees 667 | ("Longitude", c_double), # degrees 668 | ("Altitude", c_double), # feet 669 | ("Pitch", c_double), # degrees 670 | ("Bank", c_double), # degrees 671 | ("Heading", c_double), # degrees 672 | ("OnGround", DWORD), # 1=force to be on the ground 673 | ("Airspeed", DWORD), # knots 674 | ] 675 | 676 | 677 | # SIMCONNECT_DATATYPE_MARKERSTATE 678 | class SIMCONNECT_DATA_MARKERSTATE(Structure): # 679 | _fields_ = [("szMarkerName", c_char * 64), ("dwMarkerState", DWORD)] 680 | 681 | 682 | # SIMCONNECT_DATATYPE_WAYPOINT 683 | class SIMCONNECT_DATA_WAYPOINT(Structure): # 684 | _fields_ = [ 685 | ("Latitude", c_double), # degrees 686 | ("Longitude", c_double), # degrees 687 | ("Altitude", c_double), # feet 688 | ("Flags", c_ulong), 689 | ("ktsSpeed", c_double), # knots 690 | ("percentThrottle", c_double), 691 | ] 692 | 693 | 694 | # SIMCONNECT_DATA_LATLONALT 695 | class SIMCONNECT_DATA_LATLONALT(Structure): # 696 | _fields_ = [("Latitude", c_double), ("Longitude", c_double), ("Altitude", c_double)] 697 | 698 | 699 | # SIMCONNECT_DATA_XYZ 700 | class SIMCONNECT_DATA_XYZ(Structure): # 701 | _fields_ = [("x", c_double), ("y", c_double), ("z", c_double)] 702 | -------------------------------------------------------------------------------- /SimConnect/FacilitiesList.py: -------------------------------------------------------------------------------- 1 | from SimConnect import * 2 | from .Enum import * 3 | from .Constants import * 4 | 5 | 6 | class Facilitie(object): 7 | def __init__(self): 8 | pass 9 | 10 | 11 | class FacilitiesHelper: 12 | def __init__(self, _sm, _parent): 13 | self.sm = _sm 14 | self.parent = _parent 15 | self.REQUEST_ID = _sm.new_request_id() 16 | self.item = None 17 | self.sm.Facilities.append(self) 18 | 19 | def subscribe(self, _cbfunc): 20 | if self.item < SIMCONNECT_FACILITY_LIST_TYPE.SIMCONNECT_FACILITY_LIST_TYPE_COUNT: 21 | self.cb = _cbfunc 22 | hr = self.sm.dll.SubscribeToFacilities( 23 | self.sm.hSimConnect, 24 | SIMCONNECT_FACILITY_LIST_TYPE(self.item), 25 | self.REQUEST_ID.value 26 | ) 27 | 28 | def unsubscribe(self): 29 | self.cb = None 30 | hr = self.sm.dll.UnsubscribeToFacilities( 31 | self.sm.hSimConnect, 32 | SIMCONNECT_FACILITY_LIST_TYPE(self.item) 33 | ) 34 | 35 | def get(self): 36 | # Get the current cached list of airports, waypoints, etc, as the item indicates 37 | if self.item < SIMCONNECT_FACILITY_LIST_TYPE.SIMCONNECT_FACILITY_LIST_TYPE_COUNT: 38 | hr = self.sm.dll.RequestFacilitiesList( 39 | self.sm.hSimConnect, 40 | SIMCONNECT_FACILITY_LIST_TYPE(self.item), 41 | self.REQUEST_ID.value 42 | ) 43 | # self.sm.run() 44 | 45 | 46 | class FacilitiesRequests(): 47 | def __init__(self, _sm): 48 | self.sm = _sm 49 | self.list = [] 50 | self.Airports = self.__FACILITY_AIRPORT(_sm, self) 51 | self.list.append(self.Airports) 52 | self.Waypoints = self.__FACILITY_WAYPOINT(_sm, self) 53 | self.list.append(self.Waypoints) 54 | self.NDBs = self.__FACILITY_NDB(_sm, self) 55 | self.list.append(self.NDBs) 56 | self.VORs = self.__FACILITY_VOR(_sm, self) 57 | self.list.append(self.VORs) 58 | 59 | def dump(self, pList): 60 | pList = cast(pList, POINTER(SIMCONNECT_RECV_FACILITIES_LIST)) 61 | List = pList.contents 62 | print("RequestID: %d dwArraySize: %d dwEntryNumber: %d dwOutOf: %d" % ( 63 | List.dwRequestID, List.dwArraySize, List.dwEntryNumber, List.dwOutOf) 64 | ) 65 | 66 | # Dump various facility elements 67 | class __FACILITY_AIRPORT(FacilitiesHelper): 68 | def __init__(self, _sm, _parent): 69 | super().__init__(_sm, _parent) 70 | self.item = SIMCONNECT_FACILITY_LIST_TYPE.SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT 71 | 72 | def dump(self, pFac): 73 | pFac = cast(pFac, POINTER(SIMCONNECT_DATA_FACILITY_AIRPORT)) 74 | Fac = pFac.contents 75 | print("Icao: %s Latitude: %lg Longitude: %lg Altitude: %lg" % ( 76 | Fac.Icao.decode(), Fac.Latitude, Fac.Longitude, Fac.Altitude) 77 | ) 78 | 79 | class __FACILITY_WAYPOINT(FacilitiesHelper): 80 | def __init__(self, _sm, _parent): 81 | super().__init__(_sm, _parent) 82 | self.item = SIMCONNECT_FACILITY_LIST_TYPE.SIMCONNECT_FACILITY_LIST_TYPE_WAYPOINT 83 | 84 | def dump(self, pFac): 85 | pFac = cast(pFac, POINTER(SIMCONNECT_DATA_FACILITY_WAYPOINT)) 86 | Fac = pFac.contents 87 | self.parent.Airports.dump(pFac) 88 | print("\tfMagVar: %g" % (Fac.fMagVar)) 89 | 90 | class __FACILITY_NDB(FacilitiesHelper): 91 | def __init__(self, _sm, _parent): 92 | super().__init__(_sm, _parent) 93 | self.item = SIMCONNECT_FACILITY_LIST_TYPE.SIMCONNECT_FACILITY_LIST_TYPE_NDB 94 | 95 | def dump(self, pFac): 96 | pFac = cast(pFac, POINTER(SIMCONNECT_DATA_FACILITY_NDB)) 97 | Fac = pFac.contents 98 | self.parent.Waypoints.dump(pFac) 99 | print("\t\tfFrequency: %d" % (Fac.fFrequency)) 100 | 101 | class __FACILITY_VOR(FacilitiesHelper): 102 | def __init__(self, _sm, _parent): 103 | super().__init__(_sm, _parent) 104 | self.item = SIMCONNECT_FACILITY_LIST_TYPE.SIMCONNECT_FACILITY_LIST_TYPE_VOR 105 | 106 | def dump(self, pFac): 107 | pFac = cast(pFac, POINTER(SIMCONNECT_DATA_FACILITY_VOR)) 108 | Fac = pFac.contents 109 | self.parent.NDBs.dump(pFac) 110 | print("\t\t\tFlags: %x fLocalizer: %f GlideLat: %lg GlideLon: %lg GlideAlt: %lg fGlideSlopeAngle: %f" % ( 111 | Fac.Flags, Fac.fLocalizer, Fac.GlideLat, Fac.GlideLon, Fac.GlideAlt, Fac.fGlideSlopeAngle) 112 | ) 113 | -------------------------------------------------------------------------------- /SimConnect/SimConnect.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odwdinc/Python-SimConnect/af3eb9deedcf88fde422da2ac12fa41b96984942/SimConnect/SimConnect.dll -------------------------------------------------------------------------------- /SimConnect/SimConnect.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | from ctypes.wintypes import * 3 | import logging 4 | import time 5 | from .Enum import * 6 | from .Constants import * 7 | from .Attributes import * 8 | import os 9 | import threading 10 | 11 | _library_path = os.path.splitext(os.path.abspath(__file__))[0] + '.dll' 12 | 13 | LOGGER = logging.getLogger(__name__) 14 | 15 | 16 | def millis(): 17 | return int(round(time.time() * 1000)) 18 | 19 | 20 | class SimConnect: 21 | 22 | def IsHR(self, hr, value): 23 | _hr = ctypes.HRESULT(hr) 24 | return ctypes.c_ulong(_hr.value).value == value 25 | 26 | def handle_id_event(self, event): 27 | uEventID = event.uEventID 28 | if uEventID == self.dll.EventID.EVENT_SIM_START: 29 | LOGGER.info("SIM START") 30 | self.running = True 31 | if uEventID == self.dll.EventID.EVENT_SIM_STOP: 32 | LOGGER.info("SIM Stop") 33 | self.running = False 34 | # Unknow whay not reciving 35 | if uEventID == self.dll.EventID.EVENT_SIM_PAUSED: 36 | LOGGER.info("SIM Paused") 37 | self.paused = True 38 | if uEventID == self.dll.EventID.EVENT_SIM_UNPAUSED: 39 | LOGGER.info("SIM Unpaused") 40 | self.paused = False 41 | 42 | def handle_simobject_event(self, ObjData): 43 | dwRequestID = ObjData.dwRequestID 44 | if dwRequestID in self.Requests: 45 | _request = self.Requests[dwRequestID] 46 | rtype = _request.definitions[0][1].decode() 47 | if 'string' in rtype.lower(): 48 | pS = cast(ObjData.dwData, c_char_p) 49 | _request.outData = pS.value 50 | else: 51 | _request.outData = cast( 52 | ObjData.dwData, POINTER(c_double * len(_request.definitions)) 53 | ).contents[0] 54 | else: 55 | LOGGER.warn("Event ID: %d Not Handled." % (dwRequestID)) 56 | 57 | def handle_exception_event(self, exc): 58 | _exception = SIMCONNECT_EXCEPTION(exc.dwException).name 59 | _unsendid = exc.UNKNOWN_SENDID 60 | _sendid = exc.dwSendID 61 | _unindex = exc.UNKNOWN_INDEX 62 | _index = exc.dwIndex 63 | 64 | # request exceptions 65 | for _reqin in self.Requests: 66 | _request = self.Requests[_reqin] 67 | if _request.LastID == _unsendid: 68 | LOGGER.warn("%s: in %s" % (_exception, _request.definitions[0])) 69 | return 70 | 71 | LOGGER.warn(_exception) 72 | 73 | def handle_state_event(self, pData): 74 | print("I:", pData.dwInteger, "F:", pData.fFloat, "S:", pData.szString) 75 | 76 | # TODO: update callbackfunction to expand functions. 77 | def my_dispatch_proc(self, pData, cbData, pContext): 78 | # print("my_dispatch_proc") 79 | dwID = pData.contents.dwID 80 | if dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_EVENT: 81 | evt = cast(pData, POINTER(SIMCONNECT_RECV_EVENT)).contents 82 | self.handle_id_event(evt) 83 | 84 | elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_SYSTEM_STATE: 85 | state = cast(pData, POINTER(SIMCONNECT_RECV_SYSTEM_STATE)).contents 86 | self.handle_state_event(state) 87 | 88 | elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE: 89 | pObjData = cast( 90 | pData, POINTER(SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE) 91 | ).contents 92 | self.handle_simobject_event(pObjData) 93 | 94 | elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_OPEN: 95 | LOGGER.info("SIM OPEN") 96 | self.ok = True 97 | 98 | elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_EXCEPTION: 99 | exc = cast(pData, POINTER(SIMCONNECT_RECV_EXCEPTION)).contents 100 | self.handle_exception_event(exc) 101 | 102 | elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID: 103 | pObjData = cast( 104 | pData, POINTER(SIMCONNECT_RECV_ASSIGNED_OBJECT_ID) 105 | ).contents 106 | objectId = pObjData.dwObjectID 107 | os.environ["SIMCONNECT_OBJECT_ID"] = str(objectId) 108 | 109 | elif (dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_AIRPORT_LIST) or ( 110 | dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_WAYPOINT_LIST) or ( 111 | dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_NDB_LIST) or ( 112 | dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_VOR_LIST): 113 | pObjData = cast( 114 | pData, POINTER(SIMCONNECT_RECV_FACILITIES_LIST) 115 | ).contents 116 | dwRequestID = pObjData.dwRequestID 117 | for _facilitie in self.Facilities: 118 | if dwRequestID == _facilitie.REQUEST_ID.value: 119 | _facilitie.parent.dump(pData) 120 | _facilitie.dump(pData) 121 | 122 | elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_QUIT: 123 | self.quit = 1 124 | else: 125 | LOGGER.debug("Received:", SIMCONNECT_RECV_ID(dwID)) 126 | return 127 | 128 | def __init__(self, auto_connect=True, library_path=_library_path): 129 | 130 | self.Requests = {} 131 | self.Facilities = [] 132 | self.dll = SimConnectDll(library_path) 133 | self.hSimConnect = HANDLE() 134 | self.quit = 0 135 | self.ok = False 136 | self.running = False 137 | self.paused = False 138 | self.DEFINITION_POS = None 139 | self.DEFINITION_WAYPOINT = None 140 | self.my_dispatch_proc_rd = self.dll.DispatchProc(self.my_dispatch_proc) 141 | if auto_connect: 142 | self.connect() 143 | 144 | def connect(self): 145 | try: 146 | err = self.dll.Open( 147 | byref(self.hSimConnect), LPCSTR(b"Request Data"), None, 0, 0, 0 148 | ) 149 | if self.IsHR(err, 0): 150 | LOGGER.debug("Connected to Flight Simulator!") 151 | # Request an event when the simulation starts 152 | 153 | # The user is in control of the aircraft 154 | self.dll.SubscribeToSystemEvent( 155 | self.hSimConnect, self.dll.EventID.EVENT_SIM_START, b"SimStart" 156 | ) 157 | # The user is navigating the UI. 158 | self.dll.SubscribeToSystemEvent( 159 | self.hSimConnect, self.dll.EventID.EVENT_SIM_STOP, b"SimStop" 160 | ) 161 | # Request a notification when the flight is paused 162 | self.dll.SubscribeToSystemEvent( 163 | self.hSimConnect, self.dll.EventID.EVENT_SIM_PAUSED, b"Paused" 164 | ) 165 | # Request a notification when the flight is un-paused. 166 | self.dll.SubscribeToSystemEvent( 167 | self.hSimConnect, self.dll.EventID.EVENT_SIM_UNPAUSED, b"Unpaused" 168 | ) 169 | self.timerThread = threading.Thread(target=self._run) 170 | self.timerThread.daemon = True 171 | self.timerThread.start() 172 | while self.ok is False: 173 | pass 174 | except OSError: 175 | LOGGER.debug("Did not find Flight Simulator running.") 176 | raise ConnectionError("Did not find Flight Simulator running.") 177 | 178 | def _run(self): 179 | while self.quit == 0: 180 | try: 181 | self.dll.CallDispatch(self.hSimConnect, self.my_dispatch_proc_rd, None) 182 | time.sleep(.002) 183 | except OSError as err: 184 | print("OS error: {0}".format(err)) 185 | 186 | def exit(self): 187 | self.quit = 1 188 | self.timerThread.join() 189 | self.dll.Close(self.hSimConnect) 190 | 191 | def map_to_sim_event(self, name): 192 | for m in self.dll.EventID: 193 | if name.decode() == m.name: 194 | LOGGER.debug("Already have event: ", m) 195 | return m 196 | 197 | names = [m.name for m in self.dll.EventID] + [name.decode()] 198 | self.dll.EventID = Enum(self.dll.EventID.__name__, names) 199 | evnt = list(self.dll.EventID)[-1] 200 | err = self.dll.MapClientEventToSimEvent(self.hSimConnect, evnt.value, name) 201 | if self.IsHR(err, 0): 202 | return evnt 203 | else: 204 | LOGGER.error("Error: MapToSimEvent") 205 | return None 206 | 207 | def add_to_notification_group(self, group, evnt, bMaskable=False): 208 | self.dll.AddClientEventToNotificationGroup( 209 | self.hSimConnect, group, evnt, bMaskable 210 | ) 211 | 212 | def request_data(self, _Request): 213 | _Request.outData = None 214 | self.dll.RequestDataOnSimObjectType( 215 | self.hSimConnect, 216 | _Request.DATA_REQUEST_ID.value, 217 | _Request.DATA_DEFINITION_ID.value, 218 | 0, 219 | SIMCONNECT_SIMOBJECT_TYPE.SIMCONNECT_SIMOBJECT_TYPE_USER, 220 | ) 221 | temp = DWORD(0) 222 | self.dll.GetLastSentPacketID(self.hSimConnect, temp) 223 | _Request.LastID = temp.value 224 | 225 | def set_data(self, _Request): 226 | rtype = _Request.definitions[0][1].decode() 227 | if 'string' in rtype.lower(): 228 | pyarr = bytearray(_Request.outData) 229 | dataarray = (ctypes.c_char * len(pyarr))(*pyarr) 230 | else: 231 | pyarr = list([_Request.outData]) 232 | dataarray = (ctypes.c_double * len(pyarr))(*pyarr) 233 | 234 | pObjData = cast( 235 | dataarray, c_void_p 236 | ) 237 | err = self.dll.SetDataOnSimObject( 238 | self.hSimConnect, 239 | _Request.DATA_DEFINITION_ID.value, 240 | SIMCONNECT_SIMOBJECT_TYPE.SIMCONNECT_SIMOBJECT_TYPE_USER, 241 | 0, 242 | 0, 243 | sizeof(ctypes.c_double) * len(pyarr), 244 | pObjData 245 | ) 246 | if self.IsHR(err, 0): 247 | # LOGGER.debug("Request Sent") 248 | return True 249 | else: 250 | return False 251 | 252 | def get_data(self, _Request): 253 | self.request_data(_Request) 254 | # self.run() 255 | attemps = 0 256 | while _Request.outData is None and attemps < _Request.attemps: 257 | # self.run() 258 | time.sleep(.01) 259 | attemps += 1 260 | if _Request.outData is None: 261 | return False 262 | 263 | return True 264 | 265 | def send_event(self, evnt, data=DWORD(0)): 266 | err = self.dll.TransmitClientEvent( 267 | self.hSimConnect, 268 | SIMCONNECT_OBJECT_ID_USER, 269 | evnt.value, 270 | data, 271 | SIMCONNECT_GROUP_PRIORITY_HIGHEST, 272 | DWORD(16), 273 | ) 274 | 275 | if self.IsHR(err, 0): 276 | # LOGGER.debug("Event Sent") 277 | return True 278 | else: 279 | return False 280 | 281 | def new_def_id(self): 282 | _name = "Definition" + str(len(list(self.dll.DATA_DEFINITION_ID))) 283 | names = [m.name for m in self.dll.DATA_DEFINITION_ID] + [_name] 284 | 285 | self.dll.DATA_DEFINITION_ID = Enum(self.dll.DATA_DEFINITION_ID.__name__, names) 286 | DEFINITION_ID = list(self.dll.DATA_DEFINITION_ID)[-1] 287 | return DEFINITION_ID 288 | 289 | def new_request_id(self): 290 | name = "Request" + str(len(self.dll.DATA_REQUEST_ID)) 291 | names = [m.name for m in self.dll.DATA_REQUEST_ID] + [name] 292 | self.dll.DATA_REQUEST_ID = Enum(self.dll.DATA_REQUEST_ID.__name__, names) 293 | REQUEST_ID = list(self.dll.DATA_REQUEST_ID)[-1] 294 | 295 | return REQUEST_ID 296 | 297 | def add_waypoints(self, _waypointlist): 298 | if self.DEFINITION_WAYPOINT is None: 299 | self.DEFINITION_WAYPOINT = self.new_def_id() 300 | err = self.dll.AddToDataDefinition( 301 | self.hSimConnect, 302 | self.DEFINITION_WAYPOINT.value, 303 | b'AI WAYPOINT LIST', 304 | b'number', 305 | SIMCONNECT_DATATYPE.SIMCONNECT_DATATYPE_WAYPOINT, 306 | 0, 307 | SIMCONNECT_UNUSED, 308 | ) 309 | pyarr = [] 310 | for waypt in _waypointlist: 311 | for e in waypt._fields_: 312 | pyarr.append(getattr(waypt, e[0])) 313 | dataarray = (ctypes.c_double * len(pyarr))(*pyarr) 314 | pObjData = cast( 315 | dataarray, c_void_p 316 | ) 317 | sx = int(sizeof(ctypes.c_double) * (len(pyarr) / len(_waypointlist))) 318 | return 319 | hr = self.dll.SetDataOnSimObject( 320 | self.hSimConnect, 321 | self.DEFINITION_WAYPOINT.value, 322 | SIMCONNECT_OBJECT_ID_USER, 323 | 0, 324 | len(_waypointlist), 325 | sx, 326 | pObjData 327 | ) 328 | if self.IsHR(err, 0): 329 | return True 330 | else: 331 | return False 332 | 333 | def set_pos( 334 | self, 335 | _Altitude, 336 | _Latitude, 337 | _Longitude, 338 | _Airspeed, 339 | _Pitch=0.0, 340 | _Bank=0.0, 341 | _Heading=0, 342 | _OnGround=0, 343 | ): 344 | Init = SIMCONNECT_DATA_INITPOSITION() 345 | Init.Altitude = _Altitude 346 | Init.Latitude = _Latitude 347 | Init.Longitude = _Longitude 348 | Init.Pitch = _Pitch 349 | Init.Bank = _Bank 350 | Init.Heading = _Heading 351 | Init.OnGround = _OnGround 352 | Init.Airspeed = _Airspeed 353 | 354 | if self.DEFINITION_POS is None: 355 | self.DEFINITION_POS = self.new_def_id() 356 | err = self.dll.AddToDataDefinition( 357 | self.hSimConnect, 358 | self.DEFINITION_POS.value, 359 | b'Initial Position', 360 | b'', 361 | SIMCONNECT_DATATYPE.SIMCONNECT_DATATYPE_INITPOSITION, 362 | 0, 363 | SIMCONNECT_UNUSED, 364 | ) 365 | 366 | hr = self.dll.SetDataOnSimObject( 367 | self.hSimConnect, 368 | self.DEFINITION_POS.value, 369 | SIMCONNECT_OBJECT_ID_USER, 370 | 0, 371 | 0, 372 | sizeof(Init), 373 | pointer(Init) 374 | ) 375 | if self.IsHR(hr, 0): 376 | return True 377 | else: 378 | return False 379 | 380 | def load_flight(self, flt_path): 381 | hr = self.dll.FlightLoad(self.hSimConnect, flt_path.encode()) 382 | if self.IsHR(hr, 0): 383 | return True 384 | else: 385 | return False 386 | 387 | def load_flight_plan(self, pln_path): 388 | hr = self.dll.FlightPlanLoad(self.hSimConnect, pln_path.encode()) 389 | if self.IsHR(hr, 0): 390 | return True 391 | else: 392 | return False 393 | 394 | def save_flight( 395 | self, 396 | flt_path, 397 | flt_title, 398 | flt_description, 399 | flt_mission_type='FreeFlight', 400 | flt_mission_location='Custom departure', 401 | flt_original_flight='', 402 | flt_flight_type='NORMAL'): 403 | hr = self.dll.FlightSave(self.hSimConnect, flt_path.encode(), flt_title.encode(), flt_description.encode(), 0) 404 | if not self.IsHR(hr, 0): 405 | return False 406 | 407 | dicp = self.flight_to_dic(flt_path) 408 | if 'MissionType' not in dicp['Main']: 409 | dicp['Main']['MissionType'] = flt_mission_type 410 | 411 | if 'MissionLocation' not in dicp['Main']: 412 | dicp['Main']['MissionLocation'] = flt_mission_location 413 | 414 | if 'FlightType' not in dicp['Main']: 415 | dicp['Main']['FlightType'] = flt_flight_type 416 | 417 | if 'OriginalFlight' not in dicp['Main']: 418 | dicp['Main']['OriginalFlight'] = flt_original_flight 419 | self.dic_to_flight(dicp, flt_path) 420 | 421 | return False 422 | 423 | def get_paused(self): 424 | hr = self.dll.RequestSystemState( 425 | self.hSimConnect, 426 | self.dll.EventID.EVENT_SIM_PAUSED, 427 | b"Sim" 428 | ) 429 | 430 | def dic_to_flight(self, dic, fpath): 431 | with open(fpath, "w") as tempfile: 432 | for root in dic: 433 | tempfile.write("\n[%s]\n" % root) 434 | for member in dic[root]: 435 | tempfile.write("%s=%s\n" % (member, dic[root][member])) 436 | 437 | def flight_to_dic(self, fpath): 438 | while not os.path.isfile(fpath): 439 | pass 440 | time.sleep(0.5) 441 | dic = {} 442 | index = "" 443 | with open(fpath, "r") as tempfile: 444 | for line in tempfile.readlines(): 445 | if line[0] == '[': 446 | index = line[1:-2] 447 | dic[index] = {} 448 | else: 449 | if index != "" and line != '\n': 450 | temp = line.split("=") 451 | dic[index][temp[0]] = temp[1].strip() 452 | return dic 453 | 454 | def sendText(self, text, timeSeconds=5, TEXT_TYPE=SIMCONNECT_TEXT_TYPE.SIMCONNECT_TEXT_TYPE_PRINT_WHITE): 455 | pyarr = bytearray(text.encode()) 456 | dataarray = (ctypes.c_char * len(pyarr))(*pyarr) 457 | pObjData = cast(dataarray, c_void_p) 458 | self.dll.Text( 459 | self.hSimConnect, 460 | TEXT_TYPE, 461 | timeSeconds, 462 | 0, 463 | sizeof(ctypes.c_double) * len(pyarr), 464 | pObjData 465 | ) 466 | 467 | def createSimulatedObject(self, name, lat, lon, rqst, hdg=0, gnd=1, alt=0, pitch=0, bank=0, speed=0): 468 | simInitPos = SIMCONNECT_DATA_INITPOSITION() 469 | simInitPos.Altitude = alt 470 | simInitPos.Latitude = lat 471 | simInitPos.Longitude = lon 472 | simInitPos.Pitch = pitch 473 | simInitPos.Bank = bank 474 | simInitPos.Heading = hdg 475 | simInitPos.OnGround = gnd 476 | simInitPos.Airspeed = speed 477 | self.dll.AICreateSimulatedObject( 478 | self.hSimConnect, 479 | name.encode(), 480 | simInitPos, 481 | rqst.value 482 | ) 483 | -------------------------------------------------------------------------------- /SimConnect/__init__.py: -------------------------------------------------------------------------------- 1 | from .SimConnect import SimConnect, millis, DWORD 2 | from .RequestList import AircraftRequests, Request 3 | from .EventList import AircraftEvents, Event 4 | from .FacilitiesList import FacilitiesRequests, Facilitie 5 | 6 | 7 | def int_or_str(value): 8 | try: 9 | return int(value) 10 | except TypeError: 11 | return value 12 | 13 | 14 | __version__ = "0.4.26" 15 | VERSION = tuple(map(int_or_str, __version__.split("."))) 16 | 17 | __all__ = ["SimConnect", "Request", "Event", "millis", "DWORD", "AircraftRequests", "AircraftEvents", "FacilitiesRequests"] 18 | -------------------------------------------------------------------------------- /glass_server.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify, render_template, request 2 | from SimConnect import * 3 | import random 4 | 5 | # 6 | # glass_server.py is an example web app which demonstrates how data can be read and set in the simulator 7 | # 8 | # When run this code will start an http server running on http://localhost:5000/ which can be accessed. It includes both 9 | # an HTML/JS front end which can be accessed through a browser and the ability to read/write datapoints and datasets 10 | # via API requests using JSON 11 | # 12 | # The server runs using Flask: https://flask.palletsprojects.com/en/1.1.x/ 13 | # 14 | # This is intended to be a demonstration of the Python-SimConnect library rather than a fully fledged implementation. 15 | # This code has been forked into more fully worked projects including: 16 | # - MSFS 2020 Cockpit Companion: https://msfs2020.cc/ 17 | # - MSFS Mobile Companion App: https://github.com/mracko/MSFS-Mobile-Companion-App 18 | # 19 | 20 | 21 | app = Flask(__name__) 22 | 23 | # SIMCONNECTION RELATED STARTUPS 24 | 25 | # Create simconnection 26 | sm = SimConnect() 27 | ae = AircraftEvents(sm) 28 | aq = AircraftRequests(sm, _time=10) 29 | 30 | # Create request holders 31 | # These are groups of datapoints which it is convenient to call as a group because they fulfill a specific function 32 | request_location = [ 33 | 'ALTITUDE', 34 | 'LATITUDE', 35 | 'LONGITUDE', 36 | 'KOHLSMAN', 37 | ] 38 | 39 | request_airspeed = [ 40 | 'AIRSPEED_TRUE', 41 | 'AIRSPEED_INDICATE', 42 | 'AIRSPEED_TRUE CALIBRATE', 43 | 'AIRSPEED_BARBER POLE', 44 | 'AIRSPEED_MACH', 45 | ] 46 | 47 | request_compass = [ 48 | 'WISKEY_COMPASS_INDICATION_DEGREES', 49 | 'PARTIAL_PANEL_COMPASS', 50 | 'ADF_CARD', # ADF compass rose setting 51 | 'MAGNETIC_COMPASS', # Compass reading 52 | 'INDUCTOR_COMPASS_PERCENT_DEVIATION', # Inductor compass deviation reading 53 | 'INDUCTOR_COMPASS_HEADING_REF', # Inductor compass heading 54 | ] 55 | 56 | request_vertical_speed = [ 57 | 'VELOCITY_BODY_Y', # True vertical speed, relative to aircraft axis 58 | 'RELATIVE_WIND_VELOCITY_BODY_Y', # Vertical speed relative to wind 59 | 'VERTICAL_SPEED', # Vertical speed indication 60 | 'GPS_WP_VERTICAL_SPEED', # Vertical speed to waypoint 61 | ] 62 | 63 | request_fuel = [ 64 | 'FUEL_TANK_CENTER_LEVEL', # Percent of maximum capacity 65 | 'FUEL_TANK_CENTER2_LEVEL', # Percent of maximum capacity 66 | 'FUEL_TANK_CENTER3_LEVEL', # Percent of maximum capacity 67 | 'FUEL_TANK_LEFT_MAIN_LEVEL', # Percent of maximum capacity 68 | 'FUEL_TANK_LEFT_AUX_LEVEL', # Percent of maximum capacity 69 | 'FUEL_TANK_LEFT_TIP_LEVEL', # Percent of maximum capacity 70 | 'FUEL_TANK_RIGHT_MAIN_LEVEL', # Percent of maximum capacity 71 | 'FUEL_TANK_RIGHT_AUX_LEVEL', # Percent of maximum capacity 72 | 'FUEL_TANK_RIGHT_TIP_LEVEL', # Percent of maximum capacity 73 | 'FUEL_TANK_EXTERNAL1_LEVEL', # Percent of maximum capacity 74 | 'FUEL_TANK_EXTERNAL2_LEVEL', # Percent of maximum capacity 75 | 'FUEL_TANK_CENTER_CAPACITY', # Maximum capacity in volume 76 | 'FUEL_TANK_CENTER2_CAPACITY', # Maximum capacity in volume 77 | 'FUEL_TANK_CENTER3_CAPACITY', # Maximum capacity in volume 78 | 'FUEL_TANK_LEFT_MAIN_CAPACITY', # Maximum capacity in volume 79 | 'FUEL_TANK_LEFT_AUX_CAPACITY', # Maximum capacity in volume 80 | 'FUEL_TANK_LEFT_TIP_CAPACITY', # Maximum capacity in volume 81 | 'FUEL_TANK_RIGHT_MAIN_CAPACITY', # Maximum capacity in volume 82 | 'FUEL_TANK_RIGHT_AUX_CAPACITY', # Maximum capacity in volume 83 | 'FUEL_TANK_RIGHT_TIP_CAPACITY', # Maximum capacity in volume 84 | 'FUEL_TANK_EXTERNAL1_CAPACITY', # Maximum capacity in volume 85 | 'FUEL_TANK_EXTERNAL2_CAPACITY', # Maximum capacity in volume 86 | 'FUEL_LEFT_CAPACITY', # Maximum capacity in volume 87 | 'FUEL_RIGHT_CAPACITY', # Maximum capacity in volume 88 | 'FUEL_TANK_CENTER_QUANTITY', # Current quantity in volume 89 | 'FUEL_TANK_CENTER2_QUANTITY', # Current quantity in volume 90 | 'FUEL_TANK_CENTER3_QUANTITY', # Current quantity in volume 91 | 'FUEL_TANK_LEFT_MAIN_QUANTITY', # Current quantity in volume 92 | 'FUEL_TANK_LEFT_AUX_QUANTITY', # Current quantity in volume 93 | 'FUEL_TANK_LEFT_TIP_QUANTITY', # Current quantity in volume 94 | 'FUEL_TANK_RIGHT_MAIN_QUANTITY', # Current quantity in volume 95 | 'FUEL_TANK_RIGHT_AUX_QUANTITY', # Current quantity in volume 96 | 'FUEL_TANK_RIGHT_TIP_QUANTITY', # Current quantity in volume 97 | 'FUEL_TANK_EXTERNAL1_QUANTITY', # Current quantity in volume 98 | 'FUEL_TANK_EXTERNAL2_QUANTITY', # Current quantity in volume 99 | 'FUEL_LEFT_QUANTITY', # Current quantity in volume 100 | 'FUEL_RIGHT_QUANTITY', # Current quantity in volume 101 | 'FUEL_TOTAL_QUANTITY', # Current quantity in volume 102 | 'FUEL_WEIGHT_PER_GALLON', # Fuel weight per gallon 103 | 'FUEL_TOTAL_CAPACITY', # Total capacity of the aircraft 104 | 'FUEL_SELECTED_QUANTITY_PERCENT', # Percent or capacity for selected tank 105 | 'FUEL_SELECTED_QUANTITY', # Quantity of selected tank 106 | 'FUEL_TOTAL_QUANTITY_WEIGHT', # Current total fuel weight of the aircraft 107 | 'NUM_FUEL_SELECTORS', # Number of selectors on the aircraft 108 | 'UNLIMITED_FUEL', # Unlimited fuel flag 109 | 'ESTIMATED_FUEL_FLOW', # Estimated fuel flow at cruise 110 | ] 111 | 112 | request_flaps = [ 113 | 'FLAPS_HANDLE_PERCENT', # Percent flap handle extended 114 | 'FLAPS_HANDLE_INDEX', # Index of current flap position 115 | 'FLAPS_NUM_HANDLE_POSITIONS', # Number of flap positions 116 | 'TRAILING_EDGE_FLAPS_LEFT_PERCENT', # Percent left trailing edge flap extended 117 | 'TRAILING_EDGE_FLAPS_RIGHT_PERCENT', # Percent right trailing edge flap extended 118 | 'TRAILING_EDGE_FLAPS_LEFT_ANGLE', # Angle left trailing edge flap extended. Use TRAILING EDGE FLAPS LEFT PERCENT to set a value. 119 | 'TRAILING_EDGE_FLAPS_RIGHT_ANGLE', # Angle right trailing edge flap extended. Use TRAILING EDGE FLAPS RIGHT PERCENT to set a value. 120 | 'LEADING_EDGE_FLAPS_LEFT_PERCENT', # Percent left leading edge flap extended 121 | 'LEADING_EDGE_FLAPS_RIGHT_PERCENT', # Percent right leading edge flap extended 122 | 'LEADING_EDGE_FLAPS_LEFT_ANGLE', # Angle left leading edge flap extended. Use LEADING EDGE FLAPS LEFT PERCENT to set a value. 123 | 'LEADING_EDGE_FLAPS_RIGHT_ANGLE', # Angle right leading edge flap extended. Use LEADING EDGE FLAPS RIGHT PERCENT to set a value. 124 | 'FLAPS_AVAILABLE', # True if flaps available 125 | 'FLAP_DAMAGE_BY_SPEED', # True if flagps are damaged by excessive speed 126 | 'FLAP_SPEED_EXCEEDED', # True if safe speed limit for flaps exceeded 127 | ] 128 | 129 | request_throttle = [ 130 | 'AUTOPILOT_THROTTLE_ARM', # Autothrottle armed 131 | 'AUTOPILOT_TAKEOFF_POWER_ACTIVE', # Takeoff / Go Around power mode active 132 | 'AUTOTHROTTLE_ACTIVE', # Auto-throttle active 133 | 'FULL_THROTTLE_THRUST_TO_WEIGHT_RATIO', # Full throttle thrust to weight ratio 134 | 'THROTTLE_LOWER_LIMIT', 135 | 'GENERAL_ENG_THROTTLE_LEVER_POSITION:index', # Percent of max throttle position 136 | 'AUTOPILOT_THROTTLE_ARM', # Autothrottle armed 137 | 'AUTOTHROTTLE_ACTIVE', # Auto-throttle active 138 | 'FULL_THROTTLE_THRUST_TO_WEIGHT_RATIO', # Full throttle thrust to weight ratio 139 | ] 140 | 141 | request_gear = [ 142 | 'IS_GEAR_RETRACTABLE', # True if gear can be retracted 143 | 'IS_GEAR_SKIS', # True if landing gear is skis 144 | 'IS_GEAR_FLOATS', # True if landing gear is floats 145 | 'IS_GEAR_SKIDS', # True if landing gear is skids 146 | 'IS_GEAR_WHEELS', # True if landing gear is wheels 147 | 'GEAR_HANDLE_POSITION', # True if gear handle is applied 148 | 'GEAR_HYDRAULIC_PRESSURE', # Gear hydraulic pressure 149 | 'TAILWHEEL_LOCK_ON', # True if tailwheel lock applied 150 | 'GEAR_CENTER_POSITION', # Percent center gear extended 151 | 'GEAR_LEFT_POSITION', # Percent left gear extended 152 | 'GEAR_RIGHT_POSITION', # Percent right gear extended 153 | 'GEAR_TAIL_POSITION', # Percent tail gear extended 154 | 'GEAR_AUX_POSITION', # Percent auxiliary gear extended 155 | 'GEAR_TOTAL_PCT_EXTENDED', # Percent total gear extended 156 | 'AUTO_BRAKE_SWITCH_CB', # Auto brake switch position 157 | 'WATER_RUDDER_HANDLE_POSITION', 158 | 'WATER_LEFT_RUDDER_EXTENDED', # Percent extended 159 | 'WATER_RIGHT_RUDDER_EXTENDED', # Percent extended 160 | 'GEAR_CENTER_STEER_ANGLE', # Center wheel angle, negative to the left, positive to the right. 161 | 'GEAR_LEFT_STEER_ANGLE', # Left wheel angle, negative to the left, positive to the right. 162 | 'GEAR_RIGHT_STEER_ANGLE', # Right wheel angle, negative to the left, positive to the right. 163 | 'GEAR_AUX_STEER_ANGLE', # Aux wheel angle, negative to the left, positive to the right. The aux wheel is the fourth set of gear, sometimes used on helicopters. 164 | 'WATER_LEFT_RUDDER_STEER_ANGLE', # Water left rudder angle, negative to the left, positive to the right. 165 | 'WATER_RIGHT_RUDDER_STEER_ANGLE', # Water right rudder angle, negative to the left, positive to the right. 166 | 'GEAR_CENTER_STEER_ANGLE_PCT', # Center steer angle as a percentage 167 | 'GEAR_LEFT_STEER_ANGLE_PCT', # Left steer angle as a percentage 168 | 'GEAR_RIGHT_STEER_ANGLE_PCT', # Right steer angle as a percentage 169 | 'GEAR_AUX_STEER_ANGLE_PCT', # Aux steer angle as a percentage 170 | 'WATER_LEFT_RUDDER_STEER_ANGLE_PCT', # Water left rudder angle as a percentage 171 | 'WATER_RIGHT_RUDDER_STEER_ANGLE_PCT', # Water right rudder as a percentage 172 | 'CENTER_WHEEL_RPM', # Center landing gear rpm 173 | 'LEFT_WHEEL_RPM', # Left landing gear rpm 174 | 'RIGHT_WHEEL_RPM', # Right landing gear rpm 175 | 'AUX_WHEEL_RPM', # Rpm of fourth set of gear wheels. 176 | 'CENTER_WHEEL_ROTATION_ANGLE', # Center wheel rotation angle 177 | 'LEFT_WHEEL_ROTATION_ANGLE', # Left wheel rotation angle 178 | 'RIGHT_WHEEL_ROTATION_ANGLE', # Right wheel rotation angle 179 | 'AUX_WHEEL_ROTATION_ANGLE', # Aux wheel rotation angle 180 | 'GEAR_EMERGENCY_HANDLE_POSITION', # True if gear emergency handle applied 181 | 'ANTISKID_BRAKES_ACTIVE', # True if antiskid brakes active 182 | 'RETRACT_FLOAT_SWITCH', # True if retract float switch on 183 | 'RETRACT_LEFT_FLOAT_EXTENDED', # If aircraft has retractable floats. 184 | 'RETRACT_RIGHT_FLOAT_EXTENDED', # If aircraft has retractable floats. 185 | 'STEER_INPUT_CONTROL', # Position of steering tiller 186 | 'GEAR_DAMAGE_BY_SPEED', # True if gear has been damaged by excessive speed 187 | 'GEAR_SPEED_EXCEEDED', # True if safe speed limit for gear exceeded 188 | 'NOSEWHEEL_LOCK_ON', # True if the nosewheel lock is engaged. 189 | ] 190 | 191 | request_trim = [ 192 | 'ROTOR_LATERAL_TRIM_PCT', # Trim percent 193 | 'ELEVATOR_TRIM_POSITION', # Elevator trim deflection 194 | 'ELEVATOR_TRIM_INDICATOR', 195 | 'ELEVATOR_TRIM_PCT', # Percent elevator trim 196 | 'AILERON_TRIM', # Angle deflection 197 | 'AILERON_TRIM_PCT', # The trim position of the ailerons. Zero is fully retracted. 198 | 'RUDDER_TRIM_PCT', # The trim position of the rudder. Zero is no trim. 199 | 'RUDDER_TRIM', # Angle deflection 200 | ] 201 | 202 | request_autopilot = [ 203 | 'AUTOPILOT_MASTER', 204 | 'AUTOPILOT_AVAILABLE', 205 | 'AUTOPILOT_NAV_SELECTED', 206 | 'AUTOPILOT_WING_LEVELER', 207 | 'AUTOPILOT_NAV1_LOCK', 208 | 'AUTOPILOT_HEADING_LOCK', 209 | 'AUTOPILOT_HEADING_LOCK_DIR', 210 | 'AUTOPILOT_ALTITUDE_LOCK', 211 | 'AUTOPILOT_ALTITUDE_LOCK_VAR', 212 | 'AUTOPILOT_ATTITUDE_HOLD', 213 | 'AUTOPILOT_GLIDESLOPE_HOLD', 214 | 'AUTOPILOT_PITCH_HOLD_REF', 215 | 'AUTOPILOT_APPROACH_HOLD', 216 | 'AUTOPILOT_BACKCOURSE_HOLD', 217 | 'AUTOPILOT_VERTICAL_HOLD_VAR', 218 | 'AUTOPILOT_PITCH_HOLD', 219 | 'AUTOPILOT_FLIGHT_DIRECTOR_ACTIVE', 220 | 'AUTOPILOT_FLIGHT_DIRECTOR_PITCH', 221 | 'AUTOPILOT_FLIGHT_DIRECTOR_BANK', 222 | 'AUTOPILOT_AIRSPEED_HOLD', 223 | 'AUTOPILOT_AIRSPEED_HOLD_VAR', 224 | 'AUTOPILOT_MACH_HOLD', 225 | 'AUTOPILOT_MACH_HOLD_VAR', 226 | 'AUTOPILOT_YAW_DAMPER', 227 | 'AUTOPILOT_RPM_HOLD_VAR', 228 | 'AUTOPILOT_THROTTLE_ARM', 229 | 'AUTOPILOT_TAKEOFF_POWER ACTIVE', 230 | 'AUTOTHROTTLE_ACTIVE', 231 | 'AUTOPILOT_VERTICAL_HOLD', 232 | 'AUTOPILOT_RPM_HOLD', 233 | 'AUTOPILOT_MAX_BANK', 234 | 'FLY_BY_WIRE_ELAC_SWITCH', 235 | 'FLY_BY_WIRE_FAC_SWITCH', 236 | 'FLY_BY_WIRE_SEC_SWITCH', 237 | 'FLY_BY_WIRE_ELAC_FAILED', 238 | 'FLY_BY_WIRE_FAC_FAILED', 239 | 'FLY_BY_WIRE_SEC_FAILED' 240 | ] 241 | 242 | request_cabin = [ 243 | 'CABIN_SEATBELTS_ALERT_SWITCH', 244 | 'CABIN_NO_SMOKING_ALERT_SWITCH' 245 | ] 246 | 247 | # This is a helper function which just adds a comma in the right place for readability, 248 | # for instance converting 30000 to 30,000 249 | def thousandify(x): 250 | return f"{x:,}" 251 | 252 | 253 | @app.route('/') 254 | def glass(): 255 | return render_template("glass.html") 256 | 257 | 258 | @app.route('/attitude-indicator') 259 | def AttInd(): 260 | return render_template("attitude-indicator/index.html") 261 | 262 | 263 | def get_dataset(data_type): 264 | if data_type == "navigation": request_to_action = request_location 265 | if data_type == "airspeed": request_to_action = request_airspeed 266 | if data_type == "compass": request_to_action = request_compass 267 | if data_type == "vertical_speed": request_to_action = request_vertical_speed 268 | if data_type == "fuel": request_to_action = request_fuel 269 | if data_type == "flaps": request_to_action = request_flaps 270 | if data_type == "throttle": request_to_action = request_throttle 271 | if data_type == "gear": request_to_action = request_gear 272 | if data_type == "trim": request_to_action = request_trim 273 | if data_type == "autopilot": request_to_action = request_autopilot 274 | if data_type == 'cabin': request_to_action = request_cabin 275 | 276 | return request_to_action 277 | 278 | 279 | # In addition to the datapoints which can be pulled individually or as groups via JSON, the UI endpoint returns JSON 280 | # with the datapoints which the HTML / JS uses in a friendly format 281 | @app.route('/ui') 282 | def output_ui_variables(): 283 | 284 | # Initialise dictionary 285 | ui_friendly_dictionary = {} 286 | ui_friendly_dictionary["STATUS"] = "success" 287 | 288 | # Fuel 289 | fuel_percentage = (aq.get("FUEL_TOTAL_QUANTITY") / aq.get("FUEL_TOTAL_CAPACITY")) * 100 290 | ui_friendly_dictionary["FUEL_PERCENTAGE"] = round(fuel_percentage) 291 | 292 | # Airspeed and altitude 293 | ui_friendly_dictionary["AIRSPEED_INDICATE"] = round(aq.get("AIRSPEED_INDICATED")) 294 | ui_friendly_dictionary["ALTITUDE"] = thousandify(round(aq.get("PLANE_ALTITUDE"))) 295 | 296 | # Control surfaces 297 | if aq.get("GEAR_HANDLE_POSITION") == 1: 298 | ui_friendly_dictionary["GEAR_HANDLE_POSITION"] = "DOWN" 299 | else: 300 | ui_friendly_dictionary["GEAR_HANDLE_POSITION"] = "UP" 301 | ui_friendly_dictionary["FLAPS_HANDLE_PERCENT"] = round(aq.get("FLAPS_HANDLE_PERCENT") * 100) 302 | 303 | ui_friendly_dictionary["ELEVATOR_TRIM_PCT"] = round(aq.get("ELEVATOR_TRIM_PCT") * 100) 304 | ui_friendly_dictionary["RUDDER_TRIM_PCT"] = round(aq.get("RUDDER_TRIM_PCT") * 100) 305 | 306 | # Navigation 307 | ui_friendly_dictionary["LATITUDE"] = aq.get("PLANE_LATITUDE") 308 | ui_friendly_dictionary["LONGITUDE"] = aq.get("PLANE_LONGITUDE") 309 | ui_friendly_dictionary["MAGNETIC_COMPASS"] = round(aq.get("MAGNETIC_COMPASS")) 310 | ui_friendly_dictionary["MAGVAR"] = round(aq.get("MAGVAR")) 311 | ui_friendly_dictionary["VERTICAL_SPEED"] = round(aq.get("VERTICAL_SPEED")) 312 | 313 | # Autopilot 314 | ui_friendly_dictionary["AUTOPILOT_MASTER"] = aq.get("AUTOPILOT_MASTER") 315 | ui_friendly_dictionary["AUTOPILOT_NAV_SELECTED"] = aq.get("AUTOPILOT_NAV_SELECTED") 316 | ui_friendly_dictionary["AUTOPILOT_WING_LEVELER"] = aq.get("AUTOPILOT_WING_LEVELER") 317 | ui_friendly_dictionary["AUTOPILOT_HEADING_LOCK"] = aq.get("AUTOPILOT_HEADING_LOCK") 318 | ui_friendly_dictionary["AUTOPILOT_HEADING_LOCK_DIR"] = round(aq.get("AUTOPILOT_HEADING_LOCK_DIR")) 319 | ui_friendly_dictionary["AUTOPILOT_ALTITUDE_LOCK"] = aq.get("AUTOPILOT_ALTITUDE_LOCK") 320 | ui_friendly_dictionary["AUTOPILOT_ALTITUDE_LOCK_VAR"] = thousandify(round(aq.get("AUTOPILOT_ALTITUDE_LOCK_VAR"))) 321 | ui_friendly_dictionary["AUTOPILOT_ATTITUDE_HOLD"] = aq.get("AUTOPILOT_ATTITUDE_HOLD") 322 | ui_friendly_dictionary["AUTOPILOT_GLIDESLOPE_HOLD"] = aq.get("AUTOPILOT_GLIDESLOPE_HOLD") 323 | ui_friendly_dictionary["AUTOPILOT_APPROACH_HOLD"] = aq.get("AUTOPILOT_APPROACH_HOLD") 324 | ui_friendly_dictionary["AUTOPILOT_BACKCOURSE_HOLD"] = aq.get("AUTOPILOT_BACKCOURSE_HOLD") 325 | ui_friendly_dictionary["AUTOPILOT_VERTICAL_HOLD"] = aq.get("AUTOPILOT_VERTICAL_HOLD") 326 | ui_friendly_dictionary["AUTOPILOT_VERTICAL_HOLD_VAR"] = aq.get("AUTOPILOT_VERTICAL_HOLD_VAR") 327 | ui_friendly_dictionary["AUTOPILOT_PITCH_HOLD"] = aq.get("AUTOPILOT_PITCH_HOLD") 328 | ui_friendly_dictionary["AUTOPILOT_PITCH_HOLD_REF"] = aq.get("AUTOPILOT_PITCH_HOLD_REF") 329 | ui_friendly_dictionary["AUTOPILOT_FLIGHT_DIRECTOR_ACTIVE"] = aq.get("AUTOPILOT_FLIGHT_DIRECTOR_ACTIVE") 330 | ui_friendly_dictionary["AUTOPILOT_AIRSPEED_HOLD"] = aq.get("AUTOPILOT_AIRSPEED_HOLD") 331 | ui_friendly_dictionary["AUTOPILOT_AIRSPEED_HOLD_VAR"] = round(aq.get("AUTOPILOT_AIRSPEED_HOLD_VAR")) 332 | 333 | # Cabin 334 | ui_friendly_dictionary["CABIN_SEATBELTS_ALERT_SWITCH"] = aq.get("CABIN_SEATBELTS_ALERT_SWITCH") 335 | ui_friendly_dictionary["CABIN_NO_SMOKING_ALERT_SWITCH"] = aq.get("CABIN_NO_SMOKING_ALERT_SWITCH") 336 | 337 | return jsonify(ui_friendly_dictionary) 338 | 339 | 340 | @app.route('/dataset//', methods=["GET"]) 341 | def output_json_dataset(dataset_name): 342 | dataset_map = {} 343 | 344 | # This uses get_dataset() to pull in a bunch of different datapoint names into a dictionary which means they can 345 | # then be requested from the sim 346 | data_dictionary = get_dataset(dataset_name) 347 | 348 | for datapoint_name in data_dictionary: 349 | dataset_map[datapoint_name] = aq.get(datapoint_name) 350 | 351 | return jsonify(dataset_map) 352 | 353 | 354 | # This function actually does the work of getting an individual datapoint from the sim 355 | def get_datapoint(datapoint_name, index=None): 356 | 357 | if index is not None and ':index' in datapoint_name: 358 | dp = aq.find(datapoint_name) 359 | if dp is not None: 360 | dp.setIndex(int(index)) 361 | 362 | return aq.get(datapoint_name) 363 | 364 | 365 | # This is the http endpoint wrapper for getting an individual datapoint 366 | @app.route('/datapoint//get', methods=["GET"]) 367 | def get_datapoint_endpoint(datapoint_name): 368 | 369 | ds = request.get_json() if request.is_json else request.form 370 | index = ds.get('index') 371 | 372 | output = get_datapoint(datapoint_name, index) 373 | 374 | if isinstance(output, bytes): 375 | output = output.decode('ascii') 376 | 377 | return jsonify(output) 378 | 379 | 380 | # This function actually does the work of setting an individual datapoint 381 | def set_datapoint(datapoint_name, index=None, value_to_use=None): 382 | 383 | if index is not None and ':index' in datapoint_name: 384 | clas = aq.find(datapoint_name) 385 | if clas is not None: 386 | clas.setIndex(int(index)) 387 | 388 | sent = False 389 | if value_to_use is None: 390 | sent = aq.set(datapoint_name, 0) 391 | else: 392 | sent = aq.set(datapoint_name, int(value_to_use)) 393 | 394 | if sent is True: 395 | status = "success" 396 | else: 397 | status = "Error with sending request: %s" % (datapoint_name) 398 | 399 | return status 400 | 401 | 402 | # This is the http endpoint wrapper for setting a datapoint 403 | @app.route('/datapoint//set', methods=["POST"]) 404 | def set_datapoint_endpoint(datapoint_name): 405 | 406 | ds = request.get_json() if request.is_json else request.form 407 | index = ds.get('index') 408 | value_to_use = ds.get('value_to_use') 409 | 410 | status = set_datapoint (datapoint_name, index, value_to_use) 411 | 412 | return jsonify(status) 413 | 414 | 415 | # This function actually does the work of triggering an event 416 | def trigger_event(event_name, value_to_use = None): 417 | 418 | EVENT_TO_TRIGGER = ae.find(event_name) 419 | if EVENT_TO_TRIGGER is not None: 420 | if value_to_use is None: 421 | EVENT_TO_TRIGGER() 422 | else: 423 | EVENT_TO_TRIGGER(int(value_to_use)) 424 | 425 | status = "success" 426 | else: 427 | status = "Error: %s is not an Event" % (event_name) 428 | 429 | return status 430 | 431 | 432 | # This is the http endpoint wrapper for triggering an event 433 | @app.route('/event//trigger', methods=["POST"]) 434 | def trigger_event_endpoint(event_name): 435 | 436 | ds = request.get_json() if request.is_json else request.form 437 | value_to_use = ds.get('value_to_use') 438 | 439 | status = trigger_event(event_name, value_to_use) 440 | 441 | return jsonify(status) 442 | 443 | 444 | @app.route('/custom_emergency/', methods=["GET", "POST"]) 445 | def custom_emergency(emergency_type): 446 | 447 | text_to_return = "No valid emergency type passed" 448 | 449 | if emergency_type == "random_engine_fire": 450 | # Calculate number of engines 451 | number_of_engines = aq.get("NUMBER_OF_ENGINES") 452 | 453 | if number_of_engines < 0: return "error, no engines found - is sim running?" 454 | engine_to_set_on_fire = random.randint(1,number_of_engines) 455 | 456 | set_datapoint("ENG_ON_FIRE:index", engine_to_set_on_fire, 1) 457 | 458 | text_to_return = "Engine " + str(engine_to_set_on_fire) + " on fire" 459 | 460 | return text_to_return 461 | 462 | 463 | # Main loop to run the flask app 464 | app.run(host='0.0.0.0', port=5000, debug=True) 465 | -------------------------------------------------------------------------------- /local_example.py: -------------------------------------------------------------------------------- 1 | from SimConnect import * 2 | import logging 3 | from SimConnect.Enum import * 4 | from time import sleep 5 | 6 | 7 | logging.basicConfig(level=logging.DEBUG) 8 | LOGGER = logging.getLogger(__name__) 9 | LOGGER.info("START") 10 | # time holder for inline commands 11 | ct_g = millis() 12 | 13 | # creat simconnection and pass used user classes 14 | sm = SimConnect() 15 | aq = AircraftRequests(sm) 16 | ae = AircraftEvents(sm) 17 | 18 | 19 | mc = aq.find("MAGNETIC_COMPASS") 20 | mv = aq.find("MAGVAR") 21 | print(mc.get() + mv.get()) 22 | 23 | sm.exit() 24 | quit() 25 | 26 | # Set pos arund space nedle in WA. 27 | sm.set_pos( 28 | _Altitude=1000.0, 29 | _Latitude=47.614699, 30 | _Longitude=-122.358473, 31 | _Airspeed=130, 32 | _Heading=70.0, 33 | # _Pitch=0.0, 34 | # _Bank=0.0, 35 | # _OnGround=0 36 | ) 37 | 38 | # PARKING_BRAKES = Event(b'PARKING_BRAKES', sm) 39 | # long path 40 | PARKING_BRAKES = ae.Miscellaneous_Systems.PARKING_BRAKES 41 | # using get 42 | GEAR_TOGGLE = ae.Miscellaneous_Systems.get("GEAR_TOGGLE") 43 | # Using find to lookup Event 44 | AP_MASTER = ae.find("AP_MASTER") 45 | 46 | # THROTTLE1 Event 47 | THROTTLE1 = ae.Engine.THROTTLE1_SET 48 | 49 | 50 | # THROTTLE1 Request 51 | Throttle = aq.find('GENERAL_ENG_THROTTLE_LEVER_POSITION:1') 52 | 53 | # If useing 54 | # Throttle = aq.find('GENERAL_ENG_THROTTLE_LEVER_POSITION:index') 55 | # Need to set index befor read/write 56 | # Note to set index 2 vs 1 just re-run 57 | # Throttle.setIndex(1) 58 | 59 | 60 | # print the built in description 61 | # AP_MASTER Toggles AP on/off 62 | print("AP_MASTER", AP_MASTER.description) 63 | # Throttle Percent of max throttle position 64 | print("Throttle", Throttle.description) 65 | # THROTTLE1 Set throttle 1 exactly (0 to 16383) 66 | print("THROTTLE1", THROTTLE1.description) 67 | 68 | 69 | while not sm.quit: 70 | print("Throttle:", Throttle.value) 71 | print("Alt=%f Lat=%f Lon=%f Kohlsman=%.2f" % ( 72 | aq.PositionandSpeedData.get('PLANE_ALTITUDE'), 73 | aq.PositionandSpeedData.get('PLANE_LATITUDE'), 74 | aq.PositionandSpeedData.get('PLANE_LONGITUDE'), 75 | aq.FlightInstrumentationData.get('KOHLSMAN_SETTING_HG') 76 | )) 77 | sleep(2) 78 | 79 | # Send Event with value 80 | # THROTTLE1(1500) 81 | 82 | # Send Event toggle AP_MASTER 83 | # AP_MASTER() 84 | 85 | # PARKING_BRAKES() 86 | 87 | # send new data inine @ 5s 88 | if ct_g + 5000 < millis(): 89 | if Throttle.value < 100: 90 | Throttle.value += 5 91 | print("THROTTLE SET") 92 | ct_g = millis() 93 | 94 | sm.exit() 95 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -i https://pypi.org/simple 2 | 3 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | -i https://pypi.org/simple 2 | atomicwrites==1.4.0; sys_platform == 'win32' 3 | attrs==20.1.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' 4 | colorama==0.4.3; sys_platform == 'win32' 5 | coverage==5.2.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4' 6 | iniconfig==1.0.1 7 | more-itertools==8.5.0; python_version >= '3.5' 8 | packaging==20.4; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' 9 | pluggy==0.13.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' 10 | py==1.9.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' 11 | pyparsing==2.4.7; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2' 12 | pytest-cov==2.10.1 13 | pytest==6.0.1; python_version >= '3.5' 14 | six==1.15.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2' 15 | toml==0.10.1 16 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = SimConnect 3 | version = attr: SimConnect.__version__ 4 | description = Adds a pythonic wrapper for SimConnect SDK. 5 | long_description = file: README.md 6 | long_description_content_type = text/markdown 7 | url = https://github.com/odwdinc/Python-SimConnect 8 | author = Anthony Pray 9 | author_email = anthony.pray@gmail.com 10 | maintainer = Anthony Pray 11 | maintainer_email = anthony.pray@gmail.com 12 | keywords = ctypes, FlightSim, SimConnect, Flight, Simulator 13 | license = AGPL 3.0 14 | classifiers = 15 | Development Status :: 2 - Pre-Alpha 16 | Environment :: Console 17 | Intended Audience :: Developers 18 | License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+) 19 | Operating System :: OS Independent 20 | Programming Language :: Python 21 | Programming Language :: Python :: 3 22 | Programming Language :: Python :: 3.6 23 | Programming Language :: Python :: 3.7 24 | Programming Language :: Python :: 3.8 25 | Programming Language :: Python :: Implementation :: CPython 26 | 27 | 28 | [options] 29 | packages = SimConnect 30 | python_requires = >=3.6 31 | include_package_data=True 32 | test_suite="tests" 33 | 34 | [flake8] 35 | exclude = .venv,.tox,dist,docs,build,*.egg,env,venv,.undodir 36 | 37 | [bdist_wheel] 38 | universal = 1 39 | 40 | [build-system] 41 | requires = ["setuptools >= 40.6.0", "wheel"] 42 | build-backend = "setuptools.build_meta" 43 | 44 | [tool:pytest] 45 | minversion = 6.0 46 | testpaths = 47 | tests 48 | log_level = DEBUG 49 | log_cli_level = DEBUG -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup 3 | import distutils.sysconfig 4 | 5 | with open("requirements.txt") as f: 6 | install_requires = f.read()[1:].splitlines()[1:] 7 | 8 | with open("requirements_dev.txt") as f: 9 | tests_require = f.read().splitlines()[1:] 10 | 11 | setup( 12 | install_requires=install_requires, 13 | tests_require=tests_require, 14 | package_data={"": ["*.dll"]} 15 | ) -------------------------------------------------------------------------------- /static/css/attind_style.css: -------------------------------------------------------------------------------- 1 | .casing { 2 | background: #111 url("../img/metal.jpg") repeat; 3 | border: 1px solid #aaa; 4 | border-radius: 10px; 5 | box-shadow: 0 0 10px #000; 6 | padding: 20px; 7 | height: 334px; 8 | width: 334px; 9 | } 10 | 11 | .inner-case { 12 | background: #222; 13 | border: 2px solid #333; 14 | border-radius: 165px; 15 | box-shadow: 0 0 10px #000; 16 | height: 300px; 17 | overflow: hidden; 18 | padding: 15px; 19 | } 20 | 21 | .case-control { 22 | box-shadow: 0px 2px 0px #333; 23 | border-bottom: 1px solid #555; 24 | height: 230px; 25 | overflow: hidden; 26 | } 27 | 28 | /* panel */ 29 | .panel { 30 | box-shadow: 0 0 10px #111; 31 | border-radius: 30px 30px 300px 300px; 32 | background: #333; 33 | color: #aaa; 34 | font-family: Arial; 35 | margin: 13px 0 0 55px; 36 | width: 190px; 37 | height: 40px; 38 | } 39 | .panel ul { 40 | padding: 0; 41 | } 42 | .panel li { 43 | list-style-type: none; 44 | float: left; 45 | } 46 | 47 | /* labels */ 48 | .labels li { 49 | margin: 3px 8px; 50 | } 51 | .labels li:nth-child(3) { 52 | margin-left: 25px; 53 | } 54 | 55 | /* lights */ 56 | .light { 57 | background: #555; 58 | border-radius: 10px; 59 | height: 10px; 60 | margin: 0 23px; 61 | width: 10px; 62 | } 63 | .light:nth-child(3) { 64 | margin-left: 42px; 65 | } 66 | .light.on { 67 | box-shadow: 0 0 8px #3f3; 68 | background: #6f6; 69 | } 70 | .light.off { 71 | box-shadow: 0 0 8px #f33; 72 | background: #f66; 73 | } 74 | 75 | .weight { 76 | position: absolute; 77 | height: 300px; 78 | width: 300px; 79 | z-index: 3; 80 | } 81 | 82 | .aircraft div { 83 | background: #e98219; 84 | border-radius: 5px; 85 | box-shadow: 0 0 6px #000; 86 | position: absolute; 87 | top: 150px; 88 | height: 5px; 89 | } 90 | 91 | /* up-chevron */ 92 | .up-chevron { 93 | position: relative; 94 | top: 50px; 95 | left: 140px; 96 | } 97 | .up-chevron div { 98 | background: #e98219; 99 | border-radius: 5px; 100 | box-shadow: 0 0 6px #000; 101 | position: absolute; 102 | width: 5px; 103 | } 104 | .up-chevron div:nth-child(1), 105 | .up-chevron div:nth-child(2) { 106 | height: 35px; 107 | } 108 | .up-chevron div:nth-child(3) { 109 | height: 25px; 110 | } 111 | .up-chevron div:nth-child(1) { 112 | box-shadow: 2px 0px 2px #555; 113 | transform: rotate(-20deg); 114 | top: 0; 115 | left: 11px; 116 | } 117 | .up-chevron div:nth-child(2) { 118 | box-shadow: -2px 0px 2px #555; 119 | transform: rotate(20deg); 120 | top: 0; 121 | left: 0; 122 | } 123 | .up-chevron div:nth-child(3) { 124 | box-shadow: 2px 0px 2px #555; 125 | transform: rotate(90deg); 126 | top: 19px; 127 | left: 5px; 128 | } 129 | 130 | .left { 131 | left: 80px; 132 | width: 50px; 133 | } 134 | 135 | .centre { 136 | left: 150px; 137 | width: 5px; 138 | } 139 | 140 | .right { 141 | left: 175px; 142 | width: 50px; 143 | } 144 | 145 | .sky { 146 | background: #558ebb; 147 | } 148 | 149 | .terrain { 150 | background: #503723; 151 | } 152 | 153 | .mechanism { 154 | transform: rotate(0deg); /* dynamic */ 155 | } 156 | 157 | /* back */ 158 | .back.sky { 159 | border-radius: 150px 150px 0 0; 160 | height: 150px; 161 | width: 300px; 162 | } 163 | .back.terrain { 164 | border-radius: 0 0 150px 150px; 165 | height: 150px; 166 | width: 300px; 167 | } 168 | 169 | .roll { 170 | border-top: 50px solid #558ebb; 171 | border-right: 50px solid #558ebb; 172 | border-bottom: 50px solid #503723; 173 | border-left: 50px solid #503723; 174 | border-radius: 200px; 175 | box-shadow: inset 0 0 20px #000; 176 | transform: rotate(-45deg); 177 | position: absolute; 178 | top: 0; 179 | left: 0; 180 | height: 200px; 181 | width: 200px; 182 | z-index: 2; 183 | } 184 | 185 | /* roll-lines */ 186 | .roll-lines { 187 | transform: rotate(45deg); 188 | position: absolute; 189 | } 190 | .roll-lines div { 191 | background: #fff; 192 | position: absolute; 193 | height: 4px; 194 | width: 40px; 195 | } 196 | 197 | #ninety-left, 198 | #ninety-right { 199 | width: 50px; 200 | top: -1px; 201 | } 202 | #ninety-left { 203 | left: -9px; 204 | } 205 | #ninety-right { 206 | left: 242px; 207 | } 208 | 209 | #zero { 210 | background: none; 211 | border-left: 15px solid transparent; 212 | border-right: 15px solid transparent; 213 | border-top: 40px solid #fff; 214 | top: -140px; 215 | left: 126px; 216 | height: 0; 217 | width: 0; 218 | } 219 | 220 | #fortyfive-left, 221 | #fortyfive-right { 222 | background: none; 223 | border-left: 5px solid transparent; 224 | border-right: 5px solid transparent; 225 | border-top: 15px solid #fff; 226 | top: -81px; 227 | left: 57px; 228 | height: 0; 229 | width: 0; 230 | } 231 | #fortyfive-left { 232 | transform: rotate(-45deg); 233 | } 234 | #fortyfive-right { 235 | transform: rotate(45deg); 236 | left: 215px; 237 | } 238 | #thirty-left, 239 | #thirty-right { 240 | top: -60px; 241 | } 242 | #thirty-left { 243 | transform: rotate(30deg); 244 | left: 16px; 245 | } 246 | #thirty-right { 247 | transform: rotate(-30deg); 248 | left: 227px; 249 | } 250 | #sixty-left, 251 | #sixty-right { 252 | top: -105px; 253 | } 254 | #sixty-left { 255 | transform: rotate(60deg); 256 | left: 60px; 257 | } 258 | #sixty-right { 259 | transform: rotate(-60deg); 260 | left: 183px; 261 | } 262 | 263 | .ball { 264 | position: absolute; 265 | border-radius: 75px; 266 | box-shadow: 0 0 10px #000; 267 | left: 50px; 268 | top: 75px; /* dynamic */ 269 | z-index: 1; 270 | } 271 | 272 | /* pitch-lines */ 273 | .pitch-lines { 274 | position: absolute; 275 | padding: 8px 70px; 276 | } 277 | .pitch-lines div { 278 | background: #fff; 279 | margin-top: 12px; 280 | height: 2px; 281 | width: 25px; 282 | } 283 | .pitch-lines .small { 284 | margin-left: 18px; 285 | } 286 | 287 | #ten, 288 | #minus-ten { 289 | margin-left: 10px; 290 | width: 40px; 291 | } 292 | #twenty, 293 | #minus-twenty { 294 | width: 60px; 295 | } 296 | #minus-five { 297 | margin-top: 24px; 298 | } 299 | 300 | /* ball */ 301 | .ball .sky { 302 | width: 200px; 303 | height: 75px; 304 | background-image: linear-gradient(top, #558ebb, #9ccbe5); 305 | border-radius: 100px 100px 0 0; 306 | border-bottom: 2px solid #fff; 307 | } 308 | .ball .terrain { 309 | width: 200px; 310 | height: 75px; 311 | background-image: linear-gradient(top, #553a29, #3d2618); 312 | border-radius: 0 0 100px 100px; 313 | } 314 | 315 | .cage-toggle { 316 | background: #222; 317 | border: 2px solid #333; 318 | border-radius: 40px; 319 | box-shadow: 0 0 10px #000; 320 | color: #aaa; 321 | font-family: Arial; 322 | font-size: 10px; 323 | height: 40px; 324 | line-height: 13px; 325 | left: 315px; 326 | padding: 5px; 327 | position: absolute; 328 | top: 315px; 329 | text-align: center; 330 | transform: rotate(-34deg); 331 | width: 40px; 332 | } 333 | -------------------------------------------------------------------------------- /static/img/metal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odwdinc/Python-SimConnect/af3eb9deedcf88fde422da2ac12fa41b96984942/static/img/metal.jpg -------------------------------------------------------------------------------- /static/img/plane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odwdinc/Python-SimConnect/af3eb9deedcf88fde422da2ac12fa41b96984942/static/img/plane.png -------------------------------------------------------------------------------- /static/js/attind_script.js: -------------------------------------------------------------------------------- 1 | // Cache DOM elements 2 | const standby = document.querySelector(".standby"); 3 | const power = document.querySelector(".power"); 4 | const test = document.querySelector(".test"); 5 | 6 | const mechanism = document.querySelector(".mechanism"); 7 | const ball = document.querySelector(".ball"); 8 | let roll; 9 | let pitch; 10 | 11 | standby.classList.remove("off"); 12 | power.classList.add("on"); 13 | 14 | function handleOrientation(event) { 15 | var doc = document, 16 | x = Math.round(event.beta), 17 | z = Math.round(event.gamma); 18 | 19 | var roll = -1*z; 20 | var pitch = 75+x; 21 | 22 | mechanism.style.transform = "rotate(" + roll + "deg)"; 23 | ball.style.top = pitch + "px"; 24 | } 25 | 26 | var temp = false; 27 | 28 | function getSimulatorData(){ 29 | $.getJSON($SCRIPT_ROOT + '/datapoint/PLANE_BANK_DEGREES/get', {}, function(data) { 30 | var z = Math.round(data * (180/Math.PI)); 31 | if ((z > 100) || (z < -100)){ 32 | return; 33 | } 34 | roll = z; 35 | mechanism.style.transform = "rotate(" + roll + "deg)"; 36 | 37 | }); 38 | $.getJSON($SCRIPT_ROOT + '/datapoint/PLANE_PITCH_DEGREES/get', {}, function(data) { 39 | var x = Math.round(data * (180/Math.PI)); 40 | if ((x > 10) || (x < -10)){ 41 | return; 42 | } 43 | pitch = 75+(-5*x); 44 | ball.style.top = pitch + "px"; 45 | }); 46 | } 47 | 48 | function displayData(){ 49 | if (temp){ 50 | temp = false; 51 | //test.classList.remove("on"); 52 | }else{ 53 | temp = true; 54 | //test.classList.add("on"); 55 | } 56 | 57 | } 58 | 59 | window.setInterval(function(){ 60 | getSimulatorData(); 61 | displayData(); 62 | }, 500); 63 | -------------------------------------------------------------------------------- /static/js/custom/getSimData.js: -------------------------------------------------------------------------------- 1 | let altitude; 2 | let fuel_percentage; 3 | let vertical_speed; 4 | let compass; 5 | let airspeed; 6 | let latitude; 7 | let longitude; 8 | 9 | let autopilot_master; 10 | let autopilot_nav_selected; 11 | let autopilot_wing_leveler; 12 | let autopilot_heading_lock; 13 | let autopilot_heading_lock_dir; 14 | let autopilot_altitude_lock; 15 | let autopilot_altitude_lock_var; 16 | let autopilot_attitude_hold; 17 | let autopilot_glidescope_hold; 18 | let autopilot_approach_hold; 19 | let autopilot_backcourse_hold; 20 | let autopilot_vertical_hold; 21 | let autopilot_vertical_hold_var; 22 | let autopilot_pitch_hold; 23 | let autopilot_pitch_hold_ref; 24 | let autopilot_flight_director_active; 25 | let autopilot_airspeed_hold; 26 | let autopilot_airspeed_hold_var; 27 | 28 | let gear_handle_position; 29 | let elevator_trim_pct; 30 | let elevator_trim_pct_reversed; 31 | let rudder_trim_pct; 32 | let flaps_handle_pct; 33 | let flaps_handle_pct_reversed; 34 | 35 | let cabin_seatbelts_alert_switch; 36 | let cabin_no_smoking_alert_switch; 37 | 38 | window.setInterval(function(){ 39 | getSimulatorData(); 40 | displayData() 41 | updateMap() 42 | }, 2000); 43 | 44 | 45 | function getSimulatorData() { 46 | $.getJSON($SCRIPT_ROOT + '/ui', {}, function(data) { 47 | 48 | //Navigation 49 | altitude = data.ALTITUDE; 50 | vertical_speed = data.VERTICAL_SPEED; 51 | compass = data.MAGNETIC_COMPASS + data.MAGVAR; 52 | airspeed = data.AIRSPEED_INDICATE; 53 | latitude = data.LATITUDE; 54 | longitude = data.LONGITUDE; 55 | 56 | //Fuel 57 | fuel_percentage = data.FUEL_PERCENTAGE; 58 | 59 | //Autopilot 60 | autopilot_master = data.AUTOPILOT_MASTER; 61 | autopilot_nav_selected = data.AUTOPILOT_NAV_SELECTED; 62 | autopilot_wing_leveler = data.AUTOPILOT_WING_LEVELER; 63 | autopilot_heading_lock = data.AUTOPILOT_HEADING_LOCK; 64 | autopilot_heading_lock_dir = data.AUTOPILOT_HEADING_LOCK_DIR; 65 | autopilot_altitude_lock = data.AUTOPILOT_ALTITUDE_LOCK; 66 | autopilot_altitude_lock_var = data.AUTOPILOT_ALTITUDE_LOCK_VAR; 67 | autopilot_attitude_hold = data.AUTOPILOT_ATTITUDE_HOLD; 68 | autopilot_glidescope_hold = data.AUTOPILOT_GLIDESLOPE_HOLD; 69 | autopilot_approach_hold = data.AUTOPILOT_APPROACH_HOLD; 70 | autopilot_backcourse_hold = data.AUTOPILOT_BACKCOURSE_HOLD; 71 | autopilot_vertical_hold = data.AUTOPILOT_VERTICAL_HOLD 72 | autopilot_vertical_hold_var = data.AUTOPILOT_VERTICAL_HOLD_VAR; 73 | autopilot_pitch_hold = data.AUTOPILOT_PITCH_HOLD; 74 | autopilot_pitch_hold_ref = data.AUTOPILOT_PITCH_HOLD_REF; 75 | autopilot_flight_director_active = data.AUTOPILOT_FLIGHT_DIRECTOR_ACTIVE; 76 | autopilot_airspeed_hold = data.AUTOPILOT_AIRSPEED_HOLD; 77 | autopilot_airspeed_hold_var = data.AUTOPILOT_AIRSPEED_HOLD_VAR; 78 | 79 | //Control surfaces 80 | gear_handle_position = data.GEAR_HANDLE_POSITION; 81 | elevator_trim_pct = data.ELEVATOR_TRIM_PCT; 82 | elevator_trim_pct_reversed = - elevator_trim_pct 83 | //rudder_trim_pct = data.RUDDER_TRIM_PCT; 84 | flaps_handle_pct = data.FLAPS_HANDLE_PERCENT; 85 | flaps_handle_pct_reversed = - flaps_handle_pct; 86 | 87 | //Cabin 88 | cabin_no_smoking_alert_switch = data.CABIN_NO_SMOKING_ALERT_SWITCH; 89 | cabin_seatbelts_alert_switch = data.CABIN_SEATBELTS_ALERT_SWITCH; 90 | 91 | }); 92 | return false; 93 | } 94 | 95 | 96 | function displayData() { 97 | //Navigation 98 | $("#altitude").text(altitude); 99 | $("#compass").text(compass); 100 | $("#vertical-speed").text(vertical_speed); 101 | $("#airspeed").text(airspeed); 102 | 103 | //Fuel 104 | $("#fuel-percentage").text(fuel_percentage); 105 | $("#fuel-percentage-bar").css("width", fuel_percentage+"%"); 106 | 107 | //Autopilot 108 | checkAndUpdateButton("#autopilot-master", autopilot_master, "Engaged", "Disengaged"); 109 | checkAndUpdateButton("#autopilot-wing-leveler", autopilot_wing_leveler); 110 | checkAndUpdateButton("#autopilot-heading-lock", autopilot_heading_lock); 111 | checkAndUpdateButton("#autopilot-altitude-lock", autopilot_altitude_lock); 112 | checkAndUpdateButton("#autopilot-airspeed-hold", autopilot_airspeed_hold); 113 | checkAndUpdateButton("#autopilot-attitude-hold", autopilot_attitude_hold); 114 | checkAndUpdateButton("#autopilot-backcourse-hold", autopilot_backcourse_hold); 115 | checkAndUpdateButton("#autopilot-approach-hold", autopilot_approach_hold); 116 | checkAndUpdateButton("#autopilot-vertical-hold", autopilot_vertical_hold); 117 | 118 | $("#autopilot-heading-lock-dir").attr('placeholder', autopilot_heading_lock_dir); 119 | $("#autopilot-altitude-lock-var").attr('placeholder', autopilot_altitude_lock_var); 120 | $("#autopilot-airspeed-hold-var").attr('placeholder', autopilot_airspeed_hold_var); 121 | $("#autopilot-pitch-hold-ref").attr('placeholder', autopilot_pitch_hold_ref); 122 | $("#autopilot-vertical-hold-ref").attr('placeholder', autopilot_vertical_hold_var); 123 | 124 | //Control surfaces 125 | $("#gear-handle-position").html(gear_handle_position); 126 | if (gear_handle_position === "UP"){ 127 | $("#gear-handle-position").removeClass("btn-success").addClass("btn-danger"); 128 | } else { 129 | $("#gear-handle-position").removeClass("btn-danger").addClass("btn-success"); 130 | } 131 | 132 | $("#flaps-handle-pct").text(flaps_handle_pct); 133 | $("#flaps-slider").slider({values: [flaps_handle_pct_reversed]}) 134 | 135 | $("#elevator-trim-pct").text(elevator_trim_pct); 136 | $("#elevator-trim-slider").slider({values: [elevator_trim_pct_reversed]}) 137 | 138 | //$("#rudder-trim-pct").text(rudder_trim_pct); 139 | //$("#rudder-trim-slider").slider({values: [rudder_trim_pct]}) 140 | 141 | //Cabin 142 | if (cabin_seatbelts_alert_switch === 1){ 143 | $("#seatbelt-sign").removeClass("btn-outline-danger").addClass("btn-danger").html("Seatbelt sign on"); 144 | } else { 145 | $("#seatbelt-sign").removeClass("btn-danger").addClass("btn-outline-danger").html("Seatbelt sign off"); 146 | } 147 | 148 | if (cabin_no_smoking_alert_switch === 1){ 149 | $("#no-smoking-sign").removeClass("btn-outline-danger").addClass("btn-danger").html("No smoking sign on"); 150 | } else { 151 | $("#no-smoking-sign").removeClass("btn-danger").addClass("btn-outline-danger").html("No smoking sign off"); 152 | }} 153 | 154 | function checkAndUpdateButton(buttonName, variableToCheck, onText="On", offText="Off") { 155 | if (variableToCheck === 1) { 156 | $(buttonName).removeClass("btn-danger").addClass("btn-success").html(onText); 157 | } else { 158 | $(buttonName).removeClass("btn-success").addClass("btn-danger").html(offText); 159 | } 160 | } 161 | 162 | 163 | function toggleFollowPlane() { 164 | followPlane = !followPlane; 165 | if (followPlane === true) { 166 | $("#followMode").text("Moving map enabled") 167 | $("#followModeButton").removeClass("btn-outline-danger").addClass("btn-primary") 168 | } 169 | if (followPlane === false) { 170 | $("#followMode").text("Moving map disabled") 171 | $("#followModeButton").removeClass("btn-primary").addClass("btn-outline-danger") 172 | } 173 | } 174 | 175 | function updateMap() { 176 | var pos = L.latLng(latitude, longitude); 177 | 178 | marker.slideTo( pos, { 179 | duration: 1500, 180 | }); 181 | marker.setRotationAngle(compass); 182 | 183 | if (followPlane === true) { 184 | map.panTo(pos); 185 | } 186 | } 187 | 188 | function setSimDatapoint(datapointToSet, valueToUse) { 189 | url_to_call = "/datapoint/"+datapointToSet+"/set"; 190 | $.post( url_to_call, { value_to_use: valueToUse } ); 191 | } 192 | 193 | function triggerSimEvent(eventToTrigger, valueToUse, hideAlert = false){ 194 | url_to_call = "/event/"+eventToTrigger+"/trigger"; 195 | $.post( url_to_call, { value_to_use: valueToUse } ); 196 | 197 | if (!hideAlert) { 198 | temporaryAlert('', "Sending instruction", "success") 199 | } 200 | } 201 | 202 | function triggerSimEventFromField(eventToTrigger, fieldToUse, messageToDisplay = null){ 203 | // Get the field and the value in there 204 | fieldToUse = "#" + fieldToUse 205 | valueToUse = $(fieldToUse).val(); 206 | 207 | // Pass it to the API 208 | url_to_call = "/event/"+eventToTrigger+"/trigger"; 209 | $.post( url_to_call, { value_to_use: valueToUse } ); 210 | 211 | // Clear the field so it can be repopulated with the placeholder 212 | $(fieldToUse).val("") 213 | 214 | if (messageToDisplay) { 215 | temporaryAlert('', messageToDisplay + " to " + valueToUse, "success") 216 | } 217 | 218 | } 219 | 220 | function triggerCustomEmergency(emergency_type) { 221 | url_to_call = "/custom_emergency/" + emergency_type 222 | $.post (url_to_call) 223 | 224 | if (emergency_type === "random_engine_fire") { 225 | temporaryAlert("Fire!", "Random engine fire trigger sent", "error") 226 | } 227 | } 228 | 229 | 230 | function temporaryAlert(title, message, icon) { 231 | let timerInterval 232 | 233 | Swal.fire({ 234 | title: title, 235 | html: message, 236 | icon: icon, 237 | timer: 2000, 238 | timerProgressBar: true, 239 | onBeforeOpen: () => { 240 | Swal.showLoading() 241 | timerInterval = setInterval(() => { 242 | const content = Swal.getContent() 243 | if (content) { 244 | const b = content.querySelector('b') 245 | if (b) { 246 | b.textContent = Swal.getTimerLeft() 247 | } 248 | } 249 | }, 100) 250 | }, 251 | onClose: () => { 252 | clearInterval(timerInterval) 253 | } 254 | }).then((result) => { 255 | /* Read more about handling dismissals below */ 256 | if (result.dismiss === Swal.DismissReason.timer) { 257 | console.log('I was closed by the timer') 258 | } 259 | }) 260 | } -------------------------------------------------------------------------------- /static/js/custom/plane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odwdinc/Python-SimConnect/af3eb9deedcf88fde422da2ac12fa41b96984942/static/js/custom/plane.png -------------------------------------------------------------------------------- /static/vendor/jquery-ui-slider/jquery-ui-slider-pips.css: -------------------------------------------------------------------------------- 1 | /*! jQuery-ui-Slider-Pips - v1.11.4 - 2016-09-04 2 | * Copyright (c) 2016 Simon Goellner ; Licensed MIT */ 3 | 4 | /* HORIZONTAL */ 5 | /* increase bottom margin to fit the pips */ 6 | .ui-slider-horizontal.ui-slider-pips { 7 | margin-bottom: 1.4em; 8 | } 9 | 10 | /* default hide the labels and pips that arnt visible */ 11 | /* we just use css to hide incase we want to show certain */ 12 | /* labels/pips individually later */ 13 | .ui-slider-pips .ui-slider-label, 14 | .ui-slider-pips .ui-slider-pip-hide { 15 | display: none; 16 | } 17 | 18 | /* now we show any labels that we've set to show in the options */ 19 | .ui-slider-pips .ui-slider-pip-label .ui-slider-label { 20 | display: block; 21 | } 22 | 23 | /* PIP/LABEL WRAPPER */ 24 | /* position each pip absolutely just below the default slider */ 25 | /* and also prevent accidental selection */ 26 | .ui-slider-pips .ui-slider-pip { 27 | width: 2em; 28 | height: 1em; 29 | line-height: 1em; 30 | position: absolute; 31 | font-size: 0.8em; 32 | color: #999; 33 | overflow: visible; 34 | text-align: center; 35 | top: 20px; 36 | left: 20px; 37 | margin-left: -1em; 38 | cursor: pointer; 39 | -webkit-touch-callout: none; 40 | -webkit-user-select: none; 41 | -moz-user-select: none; 42 | -ms-user-select: none; 43 | user-select: none; 44 | } 45 | 46 | .ui-state-disabled.ui-slider-pips .ui-slider-pip { 47 | cursor: default; 48 | } 49 | 50 | /* little pip/line position & size */ 51 | .ui-slider-pips .ui-slider-line { 52 | background: #999; 53 | width: 1px; 54 | height: 3px; 55 | position: absolute; 56 | left: 50%; 57 | } 58 | 59 | /* the text label postion & size */ 60 | /* it overflows so no need for width to be accurate */ 61 | .ui-slider-pips .ui-slider-label { 62 | position: absolute; 63 | top: 5px; 64 | left: 50%; 65 | margin-left: -1em; 66 | width: 2em; 67 | } 68 | 69 | /* make it easy to see when we hover a label */ 70 | .ui-slider-pips:not(.ui-slider-disabled) .ui-slider-pip:hover .ui-slider-label { 71 | color: black; 72 | font-weight: bold; 73 | } 74 | 75 | /* VERTICAL */ 76 | /* vertical slider needs right-margin, not bottom */ 77 | .ui-slider-vertical.ui-slider-pips { 78 | margin-bottom: 1em; 79 | margin-right: 2em; 80 | } 81 | 82 | /* align vertical pips left and to right of the slider */ 83 | .ui-slider-vertical.ui-slider-pips .ui-slider-pip { 84 | text-align: left; 85 | top: auto; 86 | left: 20px; 87 | margin-left: 0; 88 | margin-bottom: -0.5em; 89 | } 90 | 91 | /* vertical line/pip should be horizontal instead */ 92 | .ui-slider-vertical.ui-slider-pips .ui-slider-line { 93 | width: 3px; 94 | height: 1px; 95 | position: absolute; 96 | top: 50%; 97 | left: 0; 98 | } 99 | 100 | .ui-slider-vertical.ui-slider-pips .ui-slider-label { 101 | top: 50%; 102 | left: 0.5em; 103 | margin-left: 0; 104 | margin-top: -0.5em; 105 | width: 2em; 106 | } 107 | 108 | /* FLOATING HORIZTONAL TOOLTIPS */ 109 | /* remove the godawful looking focus outline on handle and float */ 110 | .ui-slider-float .ui-slider-handle:focus, 111 | .ui-slider-float .ui-slider-handle.ui-state-focus .ui-slider-tip-label, 112 | .ui-slider-float .ui-slider-handle:focus .ui-slider-tip, 113 | .ui-slider-float .ui-slider-handle.ui-state-focus .ui-slider-tip-label, 114 | .ui-slider-float .ui-slider-handle:focus .ui-slider-tip-label 115 | .ui-slider-float .ui-slider-handle.ui-state-focus .ui-slider-tip-label { 116 | outline: none; 117 | } 118 | 119 | /* style tooltips on handles and on labels */ 120 | /* also has a nice transition */ 121 | .ui-slider-float .ui-slider-tip, 122 | .ui-slider-float .ui-slider-tip-label { 123 | position: absolute; 124 | visibility: hidden; 125 | top: -40px; 126 | display: block; 127 | width: 34px; 128 | margin-left: -18px; 129 | left: 50%; 130 | height: 20px; 131 | line-height: 20px; 132 | background: white; 133 | border-radius: 3px; 134 | border: 1px solid #888; 135 | text-align: center; 136 | font-size: 12px; 137 | opacity: 0; 138 | color: #333; 139 | -webkit-transition-property: opacity, top, visibility; 140 | transition-property: opacity, top, visibility; 141 | -webkit-transition-timing-function: ease-in; 142 | transition-timing-function: ease-in; 143 | -webkit-transition-duration: 200ms, 200ms, 0ms; 144 | transition-duration: 200ms, 200ms, 0ms; 145 | -webkit-transition-delay: 0ms, 0ms, 200ms; 146 | transition-delay: 0ms, 0ms, 200ms; 147 | } 148 | 149 | /* show the tooltip on hover or focus */ 150 | /* also switch transition delay around */ 151 | .ui-slider-float .ui-slider-handle:hover .ui-slider-tip, 152 | .ui-slider-float .ui-slider-handle.ui-state-hover .ui-slider-tip, 153 | .ui-slider-float .ui-slider-handle:focus .ui-slider-tip, 154 | .ui-slider-float .ui-slider-handle.ui-state-focus .ui-slider-tip, 155 | .ui-slider-float .ui-slider-handle.ui-state-active .ui-slider-tip, 156 | .ui-slider-float .ui-slider-pip:hover .ui-slider-tip-label { 157 | opacity: 1; 158 | top: -30px; 159 | visibility: visible; 160 | -webkit-transition-timing-function: ease-out; 161 | transition-timing-function: ease-out; 162 | -webkit-transition-delay: 200ms, 200ms, 0ms; 163 | transition-delay: 200ms, 200ms, 0ms; 164 | } 165 | 166 | /* put label tooltips below slider */ 167 | .ui-slider-float .ui-slider-pip .ui-slider-tip-label { 168 | top: 42px; 169 | } 170 | 171 | .ui-slider-float .ui-slider-pip:hover .ui-slider-tip-label { 172 | top: 32px; 173 | font-weight: normal; 174 | } 175 | 176 | /* give the tooltip a css triangle arrow */ 177 | .ui-slider-float .ui-slider-tip:after, 178 | .ui-slider-float .ui-slider-pip .ui-slider-tip-label:after { 179 | content: " "; 180 | width: 0; 181 | height: 0; 182 | border: 5px solid rgba(255, 255, 255, 0); 183 | border-top-color: white; 184 | position: absolute; 185 | bottom: -10px; 186 | left: 50%; 187 | margin-left: -5px; 188 | } 189 | 190 | /* put a 1px border on the tooltip arrow to match tooltip border */ 191 | .ui-slider-float .ui-slider-tip:before, 192 | .ui-slider-float .ui-slider-pip .ui-slider-tip-label:before { 193 | content: " "; 194 | width: 0; 195 | height: 0; 196 | border: 5px solid rgba(255, 255, 255, 0); 197 | border-top-color: #888; 198 | position: absolute; 199 | bottom: -11px; 200 | left: 50%; 201 | margin-left: -5px; 202 | } 203 | 204 | /* switch the arrow to top on labels */ 205 | .ui-slider-float .ui-slider-pip .ui-slider-tip-label:after { 206 | border: 5px solid rgba(255, 255, 255, 0); 207 | border-bottom-color: white; 208 | top: -10px; 209 | } 210 | 211 | .ui-slider-float .ui-slider-pip .ui-slider-tip-label:before { 212 | border: 5px solid rgba(255, 255, 255, 0); 213 | border-bottom-color: #888; 214 | top: -11px; 215 | } 216 | 217 | /* FLOATING VERTICAL TOOLTIPS */ 218 | /* tooltip floats to left of handle */ 219 | .ui-slider-vertical.ui-slider-float .ui-slider-tip, 220 | .ui-slider-vertical.ui-slider-float .ui-slider-tip-label { 221 | top: 50%; 222 | margin-top: -11px; 223 | width: 34px; 224 | margin-left: 0px; 225 | left: -60px; 226 | color: #333; 227 | -webkit-transition-duration: 200ms, 200ms, 0; 228 | transition-duration: 200ms, 200ms, 0; 229 | -webkit-transition-property: opacity, left, visibility; 230 | transition-property: opacity, left, visibility; 231 | -webkit-transition-delay: 0, 0, 200ms; 232 | transition-delay: 0, 0, 200ms; 233 | } 234 | 235 | .ui-slider-vertical.ui-slider-float .ui-slider-handle:hover .ui-slider-tip, 236 | .ui-slider-vertical.ui-slider-float .ui-slider-handle.ui-state-hover .ui-slider-tip, 237 | .ui-slider-vertical.ui-slider-float .ui-slider-handle:focus .ui-slider-tip, 238 | .ui-slider-vertical.ui-slider-float .ui-slider-handle.ui-state-focus .ui-slider-tip, 239 | .ui-slider-vertical.ui-slider-float .ui-slider-handle.ui-state-active .ui-slider-tip, 240 | .ui-slider-vertical.ui-slider-float .ui-slider-pip:hover .ui-slider-tip-label { 241 | top: 50%; 242 | margin-top: -11px; 243 | left: -50px; 244 | } 245 | 246 | /* put label tooltips to right of slider */ 247 | .ui-slider-vertical.ui-slider-float .ui-slider-pip .ui-slider-tip-label { 248 | left: 47px; 249 | } 250 | 251 | .ui-slider-vertical.ui-slider-float .ui-slider-pip:hover .ui-slider-tip-label { 252 | left: 37px; 253 | } 254 | 255 | /* give the tooltip a css triangle arrow */ 256 | .ui-slider-vertical.ui-slider-float .ui-slider-tip:after, 257 | .ui-slider-vertical.ui-slider-float .ui-slider-pip .ui-slider-tip-label:after { 258 | border: 5px solid rgba(255, 255, 255, 0); 259 | border-left-color: white; 260 | border-top-color: transparent; 261 | position: absolute; 262 | bottom: 50%; 263 | margin-bottom: -5px; 264 | right: -10px; 265 | margin-left: 0; 266 | top: auto; 267 | left: auto; 268 | } 269 | 270 | .ui-slider-vertical.ui-slider-float .ui-slider-tip:before, 271 | .ui-slider-vertical.ui-slider-float .ui-slider-pip .ui-slider-tip-label:before { 272 | border: 5px solid rgba(255, 255, 255, 0); 273 | border-left-color: #888; 274 | border-top-color: transparent; 275 | position: absolute; 276 | bottom: 50%; 277 | margin-bottom: -5px; 278 | right: -11px; 279 | margin-left: 0; 280 | top: auto; 281 | left: auto; 282 | } 283 | 284 | .ui-slider-vertical.ui-slider-float .ui-slider-pip .ui-slider-tip-label:after { 285 | border: 5px solid rgba(255, 255, 255, 0); 286 | border-right-color: white; 287 | right: auto; 288 | left: -10px; 289 | } 290 | 291 | .ui-slider-vertical.ui-slider-float .ui-slider-pip .ui-slider-tip-label:before { 292 | border: 5px solid rgba(255, 255, 255, 0); 293 | border-right-color: #888; 294 | right: auto; 295 | left: -11px; 296 | } 297 | 298 | /* SELECTED STATES */ 299 | /* Comment out this chuck of code if you don't want to have 300 | the new label colours shown */ 301 | .ui-slider-pips [class*=ui-slider-pip-initial] { 302 | font-weight: bold; 303 | color: #14CA82; 304 | } 305 | 306 | .ui-slider-pips .ui-slider-pip-initial-2 { 307 | color: #1897C9; 308 | } 309 | 310 | .ui-slider-pips [class*=ui-slider-pip-selected] { 311 | font-weight: bold; 312 | color: #FF7A00; 313 | } 314 | 315 | .ui-slider-pips .ui-slider-pip-inrange { 316 | color: black; 317 | } 318 | 319 | .ui-slider-pips .ui-slider-pip-selected-2 { 320 | color: #E70081; 321 | } 322 | 323 | .ui-slider-pips [class*=ui-slider-pip-selected] .ui-slider-line, 324 | .ui-slider-pips .ui-slider-pip-inrange .ui-slider-line { 325 | background: black; 326 | } 327 | -------------------------------------------------------------------------------- /static/vendor/jquery-ui-slider/jquery-ui-slider-pips.js: -------------------------------------------------------------------------------- 1 | /*! jQuery-ui-Slider-Pips - v1.11.4 - 2016-09-04 2 | * Copyright (c) 2016 Simon Goellner ; Licensed MIT */ 3 | 4 | (function($) { 5 | 6 | "use strict"; 7 | 8 | var extensionMethods = { 9 | 10 | 11 | 12 | 13 | 14 | // pips 15 | 16 | pips: function( settings ) { 17 | 18 | var slider = this, 19 | i, j, p, 20 | collection = "", 21 | mousedownHandlers, 22 | min = slider._valueMin(), 23 | max = slider._valueMax(), 24 | pips = ( max - min ) / slider.options.step, 25 | $handles = slider.element.find(".ui-slider-handle"), 26 | $pips; 27 | 28 | var options = { 29 | 30 | first: "label", 31 | /* "label", "pip", false */ 32 | 33 | last: "label", 34 | /* "label", "pip", false */ 35 | 36 | rest: "pip", 37 | /* "label", "pip", false */ 38 | 39 | labels: false, 40 | /* [array], { first: "string", rest: [array], last: "string" }, false */ 41 | 42 | prefix: "", 43 | /* "", string */ 44 | 45 | suffix: "", 46 | /* "", string */ 47 | 48 | step: ( pips > 100 ) ? Math.floor( pips * 0.05 ) : 1, 49 | /* number */ 50 | 51 | formatLabel: function(value) { 52 | return this.prefix + value + this.suffix; 53 | } 54 | /* function 55 | must return a value to display in the pip labels */ 56 | 57 | }; 58 | 59 | if ( $.type( settings ) === "object" || $.type( settings ) === "undefined" ) { 60 | 61 | $.extend( options, settings ); 62 | slider.element.data("pips-options", options ); 63 | 64 | } else { 65 | 66 | if ( settings === "destroy" ) { 67 | 68 | destroy(); 69 | 70 | } else if ( settings === "refresh" ) { 71 | 72 | slider.element.slider( "pips", slider.element.data("pips-options") ); 73 | 74 | } 75 | 76 | return; 77 | 78 | } 79 | 80 | 81 | // we don't want the step ever to be a floating point or negative 82 | // (or 0 actually, so we'll set it to 1 in that case). 83 | slider.options.pipStep = Math.abs( Math.round( options.step ) ) || 1; 84 | 85 | // get rid of all pips that might already exist. 86 | slider.element 87 | .off( ".selectPip" ) 88 | .addClass("ui-slider-pips") 89 | .find(".ui-slider-pip") 90 | .remove(); 91 | 92 | // small object with functions for marking pips as selected. 93 | 94 | var selectPip = { 95 | 96 | single: function(value) { 97 | 98 | this.resetClasses(); 99 | 100 | $pips 101 | .filter(".ui-slider-pip-" + this.classLabel(value) ) 102 | .addClass("ui-slider-pip-selected"); 103 | 104 | if ( slider.options.range ) { 105 | 106 | $pips.each(function(k, v) { 107 | 108 | var pipVal = $(v).children(".ui-slider-label").data("value"); 109 | 110 | if (( slider.options.range === "min" && pipVal < value ) || 111 | ( slider.options.range === "max" && pipVal > value )) { 112 | 113 | $(v).addClass("ui-slider-pip-inrange"); 114 | 115 | } 116 | 117 | }); 118 | 119 | } 120 | 121 | }, 122 | 123 | range: function(values) { 124 | 125 | this.resetClasses(); 126 | 127 | for ( i = 0; i < values.length; i++ ) { 128 | 129 | $pips 130 | .filter(".ui-slider-pip-" + this.classLabel(values[i]) ) 131 | .addClass("ui-slider-pip-selected-" + ( i + 1 ) ); 132 | 133 | } 134 | 135 | if ( slider.options.range ) { 136 | 137 | $pips.each(function(k, v) { 138 | 139 | var pipVal = $(v).children(".ui-slider-label").data("value"); 140 | 141 | if ( pipVal > values[0] && pipVal < values[1] ) { 142 | 143 | $(v).addClass("ui-slider-pip-inrange"); 144 | 145 | } 146 | 147 | }); 148 | 149 | } 150 | 151 | }, 152 | 153 | classLabel: function(value) { 154 | 155 | return value.toString().replace(".", "-"); 156 | 157 | }, 158 | 159 | resetClasses: function() { 160 | 161 | var regex = /(^|\s*)(ui-slider-pip-selected|ui-slider-pip-inrange)(-{1,2}\d+|\s|$)/gi; 162 | 163 | $pips.removeClass( function(index, css) { 164 | return ( css.match(regex) || [] ).join(" "); 165 | }); 166 | 167 | } 168 | 169 | }; 170 | 171 | function getClosestHandle( val ) { 172 | 173 | var h, k, 174 | sliderVals, 175 | comparedVals, 176 | closestVal, 177 | tempHandles = [], 178 | closestHandle = 0; 179 | 180 | if ( slider.values() && slider.values().length ) { 181 | 182 | // get the current values of the slider handles 183 | sliderVals = slider.values(); 184 | 185 | // find the offset value from the `val` for each 186 | // handle, and store it in a new array 187 | comparedVals = $.map( sliderVals, function(v) { 188 | return Math.abs( v - val ); 189 | }); 190 | 191 | // figure out the closest handles to the value 192 | closestVal = Math.min.apply( Math, comparedVals ); 193 | 194 | // if a comparedVal is the closestVal, then 195 | // set the value accordingly, and set the closest handle. 196 | for ( h = 0; h < comparedVals.length; h++ ) { 197 | if ( comparedVals[h] === closestVal ) { 198 | tempHandles.push(h); 199 | } 200 | } 201 | 202 | // set the closest handle to the first handle in array, 203 | // just incase we have no _lastChangedValue to compare to. 204 | closestHandle = tempHandles[0]; 205 | 206 | // now we want to find out if any of the closest handles were 207 | // the last changed handle, if so we specify that handle to change 208 | for ( k = 0; k < tempHandles.length; k++ ) { 209 | if ( slider._lastChangedValue === tempHandles[k] ) { 210 | closestHandle = tempHandles[k]; 211 | } 212 | } 213 | 214 | if ( slider.options.range && tempHandles.length === 2 ) { 215 | 216 | if ( val > sliderVals[1] ) { 217 | 218 | closestHandle = tempHandles[1]; 219 | 220 | } else if ( val < sliderVals[0] ) { 221 | 222 | closestHandle = tempHandles[0]; 223 | 224 | } 225 | 226 | } 227 | 228 | } 229 | 230 | return closestHandle; 231 | 232 | } 233 | 234 | function destroy() { 235 | 236 | slider.element 237 | .off(".selectPip") 238 | .on("mousedown.slider", slider.element.data("mousedown-original") ) 239 | .removeClass("ui-slider-pips") 240 | .find(".ui-slider-pip") 241 | .remove(); 242 | 243 | } 244 | 245 | // when we click on a label, we want to make sure the 246 | // slider's handle actually goes to that label! 247 | // so we check all the handles and see which one is closest 248 | // to the label we clicked. If 2 handles are equidistant then 249 | // we move both of them. We also want to trigger focus on the 250 | // handle. 251 | 252 | // without this method the label is just treated like a part 253 | // of the slider and there's no accuracy in the selected value 254 | 255 | function labelClick( label, e ) { 256 | 257 | if (slider.option("disabled")) { 258 | return; 259 | } 260 | 261 | var val = $(label).data("value"), 262 | indexToChange = getClosestHandle( val ); 263 | 264 | if ( slider.values() && slider.values().length ) { 265 | 266 | slider.options.values[ indexToChange ] = slider._trimAlignValue( val ); 267 | 268 | } else { 269 | 270 | slider.options.value = slider._trimAlignValue( val ); 271 | 272 | } 273 | 274 | slider._refreshValue(); 275 | slider._change( e, indexToChange ); 276 | 277 | } 278 | 279 | // method for creating a pip. We loop this for creating all 280 | // the pips. 281 | 282 | function createPip( which ) { 283 | 284 | var label, 285 | percent, 286 | number = which, 287 | classes = "ui-slider-pip", 288 | css = "", 289 | value = slider.value(), 290 | values = slider.values(), 291 | labelValue, 292 | classLabel, 293 | labelIndex; 294 | 295 | if ( which === "first" ) { 296 | 297 | number = 0; 298 | 299 | } else if ( which === "last" ) { 300 | 301 | number = pips; 302 | 303 | } 304 | 305 | // labelValue is the actual value of the pip based on the min/step 306 | labelValue = min + ( slider.options.step * number ); 307 | 308 | // classLabel replaces any decimals with hyphens 309 | classLabel = labelValue.toString().replace(".", "-"); 310 | 311 | // get the index needed for selecting labels out of the array 312 | labelIndex = ( number + min ) - min; 313 | 314 | // we need to set the human-readable label to either the 315 | // corresponding element in the array, or the appropriate 316 | // item in the object... or an empty string. 317 | 318 | if ( $.type(options.labels) === "array" ) { 319 | 320 | label = options.labels[ labelIndex ] || ""; 321 | 322 | } else if ( $.type( options.labels ) === "object" ) { 323 | 324 | if ( which === "first" ) { 325 | 326 | // set first label 327 | label = options.labels.first || ""; 328 | 329 | } else if ( which === "last" ) { 330 | 331 | // set last label 332 | label = options.labels.last || ""; 333 | 334 | } else if ( $.type( options.labels.rest ) === "array" ) { 335 | 336 | // set other labels, but our index should start at -1 337 | // because of the first pip. 338 | label = options.labels.rest[ labelIndex - 1 ] || ""; 339 | 340 | } else { 341 | 342 | // urrggh, the options must be f**ked, just show nothing. 343 | label = labelValue; 344 | 345 | } 346 | 347 | } else { 348 | 349 | label = labelValue; 350 | 351 | } 352 | 353 | 354 | 355 | 356 | if ( which === "first" ) { 357 | 358 | // first Pip on the Slider 359 | percent = "0%"; 360 | 361 | classes += " ui-slider-pip-first"; 362 | classes += ( options.first === "label" ) ? " ui-slider-pip-label" : ""; 363 | classes += ( options.first === false ) ? " ui-slider-pip-hide" : ""; 364 | 365 | } else if ( which === "last" ) { 366 | 367 | // last Pip on the Slider 368 | percent = "100%"; 369 | 370 | classes += " ui-slider-pip-last"; 371 | classes += ( options.last === "label" ) ? " ui-slider-pip-label" : ""; 372 | classes += ( options.last === false ) ? " ui-slider-pip-hide" : ""; 373 | 374 | } else { 375 | 376 | // all other Pips 377 | percent = (( 100 / pips ) * which ).toFixed(4) + "%"; 378 | 379 | classes += ( options.rest === "label" ) ? " ui-slider-pip-label" : ""; 380 | classes += ( options.rest === false ) ? " ui-slider-pip-hide" : ""; 381 | 382 | } 383 | 384 | classes += " ui-slider-pip-" + classLabel; 385 | 386 | 387 | // add classes for the initial-selected values. 388 | if ( values && values.length ) { 389 | 390 | for ( i = 0; i < values.length; i++ ) { 391 | 392 | if ( labelValue === values[i] ) { 393 | 394 | classes += " ui-slider-pip-initial-" + ( i + 1 ); 395 | classes += " ui-slider-pip-selected-" + ( i + 1 ); 396 | 397 | } 398 | 399 | } 400 | 401 | if ( slider.options.range ) { 402 | 403 | if ( labelValue > values[0] && 404 | labelValue < values[1] ) { 405 | 406 | classes += " ui-slider-pip-inrange"; 407 | 408 | } 409 | 410 | } 411 | 412 | } else { 413 | 414 | if ( labelValue === value ) { 415 | 416 | classes += " ui-slider-pip-initial"; 417 | classes += " ui-slider-pip-selected"; 418 | 419 | } 420 | 421 | if ( slider.options.range ) { 422 | 423 | if (( slider.options.range === "min" && labelValue < value ) || 424 | ( slider.options.range === "max" && labelValue > value )) { 425 | 426 | classes += " ui-slider-pip-inrange"; 427 | 428 | } 429 | 430 | } 431 | 432 | } 433 | 434 | 435 | 436 | css = ( slider.options.orientation === "horizontal" ) ? 437 | "left: " + percent : 438 | "bottom: " + percent; 439 | 440 | 441 | // add this current pip to the collection 442 | return "" + 443 | "" + 444 | "" + options.formatLabel(label) + "" + 446 | ""; 447 | 448 | } 449 | 450 | // create our first pip 451 | collection += createPip("first"); 452 | 453 | // for every stop in the slider where we need a pip; create one. 454 | for ( p = slider.options.pipStep; p < pips; p += slider.options.pipStep ) { 455 | collection += createPip( p ); 456 | } 457 | 458 | // create our last pip 459 | collection += createPip("last"); 460 | 461 | // append the collection of pips. 462 | slider.element.append( collection ); 463 | 464 | // store the pips for setting classes later. 465 | $pips = slider.element.find(".ui-slider-pip"); 466 | 467 | 468 | 469 | // store the mousedown handlers for later, just in case we reset 470 | // the slider, the handler would be lost! 471 | 472 | if ( $._data( slider.element.get(0), "events").mousedown && 473 | $._data( slider.element.get(0), "events").mousedown.length ) { 474 | 475 | mousedownHandlers = $._data( slider.element.get(0), "events").mousedown; 476 | 477 | } else { 478 | 479 | mousedownHandlers = slider.element.data("mousedown-handlers"); 480 | 481 | } 482 | 483 | slider.element.data("mousedown-handlers", mousedownHandlers.slice() ); 484 | 485 | // loop through all the mousedown handlers on the slider, 486 | // and store the original namespaced (.slider) event handler so 487 | // we can trigger it later. 488 | for ( j = 0; j < mousedownHandlers.length; j++ ) { 489 | if ( mousedownHandlers[j].namespace === "slider" ) { 490 | slider.element.data("mousedown-original", mousedownHandlers[j].handler ); 491 | } 492 | } 493 | 494 | // unbind the mousedown.slider event, because it interferes with 495 | // the labelClick() method (stops smooth animation), and decide 496 | // if we want to trigger the original event based on which element 497 | // was clicked. 498 | slider.element 499 | .off("mousedown.slider") 500 | .on("mousedown.selectPip", function(e) { 501 | 502 | var $target = $(e.target), 503 | closest = getClosestHandle( $target.data("value") ), 504 | $handle = $handles.eq( closest ); 505 | 506 | $handle.addClass("ui-state-active"); 507 | 508 | if ( $target.is(".ui-slider-label") ) { 509 | 510 | labelClick( $target, e ); 511 | 512 | slider.element 513 | .one("mouseup.selectPip", function() { 514 | 515 | $handle 516 | .removeClass("ui-state-active") 517 | .focus(); 518 | 519 | }); 520 | 521 | } else { 522 | 523 | var originalMousedown = slider.element.data("mousedown-original"); 524 | originalMousedown(e); 525 | 526 | } 527 | 528 | }); 529 | 530 | 531 | 532 | 533 | slider.element.on( "slide.selectPip slidechange.selectPip", function(e, ui) { 534 | 535 | var $slider = $(this), 536 | value = $slider.slider("value"), 537 | values = $slider.slider("values"); 538 | 539 | if ( ui ) { 540 | 541 | value = ui.value; 542 | values = ui.values; 543 | 544 | } 545 | 546 | if ( slider.values() && slider.values().length ) { 547 | 548 | selectPip.range( values ); 549 | 550 | } else { 551 | 552 | selectPip.single( value ); 553 | 554 | } 555 | 556 | }); 557 | 558 | 559 | 560 | 561 | }, 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | // floats 571 | 572 | float: function( settings ) { 573 | 574 | var i, 575 | slider = this, 576 | min = slider._valueMin(), 577 | max = slider._valueMax(), 578 | value = slider._value(), 579 | values = slider._values(), 580 | tipValues = [], 581 | $handles = slider.element.find(".ui-slider-handle"); 582 | 583 | var options = { 584 | 585 | handle: true, 586 | /* false */ 587 | 588 | pips: false, 589 | /* true */ 590 | 591 | labels: false, 592 | /* [array], { first: "string", rest: [array], last: "string" }, false */ 593 | 594 | prefix: "", 595 | /* "", string */ 596 | 597 | suffix: "", 598 | /* "", string */ 599 | 600 | event: "slidechange slide", 601 | /* "slidechange", "slide", "slidechange slide" */ 602 | 603 | formatLabel: function(value) { 604 | return this.prefix + value + this.suffix; 605 | } 606 | /* function 607 | must return a value to display in the floats */ 608 | 609 | }; 610 | 611 | if ( $.type( settings ) === "object" || $.type( settings ) === "undefined" ) { 612 | 613 | $.extend( options, settings ); 614 | slider.element.data("float-options", options ); 615 | 616 | } else { 617 | 618 | if ( settings === "destroy" ) { 619 | 620 | destroy(); 621 | 622 | } else if ( settings === "refresh" ) { 623 | 624 | slider.element.slider( "float", slider.element.data("float-options") ); 625 | 626 | } 627 | 628 | return; 629 | 630 | } 631 | 632 | 633 | 634 | 635 | if ( value < min ) { 636 | value = min; 637 | } 638 | 639 | if ( value > max ) { 640 | value = max; 641 | } 642 | 643 | if ( values && values.length ) { 644 | 645 | for ( i = 0; i < values.length; i++ ) { 646 | 647 | if ( values[i] < min ) { 648 | values[i] = min; 649 | } 650 | 651 | if ( values[i] > max ) { 652 | values[i] = max; 653 | } 654 | 655 | } 656 | 657 | } 658 | 659 | // add a class for the CSS 660 | slider.element 661 | .addClass("ui-slider-float") 662 | .find(".ui-slider-tip, .ui-slider-tip-label") 663 | .remove(); 664 | 665 | 666 | 667 | function destroy() { 668 | 669 | slider.element 670 | .off(".sliderFloat") 671 | .removeClass("ui-slider-float") 672 | .find(".ui-slider-tip, .ui-slider-tip-label") 673 | .remove(); 674 | 675 | } 676 | 677 | 678 | function getPipLabels( values ) { 679 | 680 | // when checking the array we need to divide 681 | // by the step option, so we store those values here. 682 | 683 | var vals = [], 684 | steppedVals = $.map( values, function(v) { 685 | return Math.ceil(( v - min ) / slider.options.step); 686 | }); 687 | 688 | // now we just get the values we need to return 689 | // by looping through the values array and assigning the 690 | // label if it exists. 691 | 692 | if ( $.type( options.labels ) === "array" ) { 693 | 694 | for ( i = 0; i < values.length; i++ ) { 695 | 696 | vals[i] = options.labels[ steppedVals[i] ] || values[i]; 697 | 698 | } 699 | 700 | } else if ( $.type( options.labels ) === "object" ) { 701 | 702 | for ( i = 0; i < values.length; i++ ) { 703 | 704 | if ( values[i] === min ) { 705 | 706 | vals[i] = options.labels.first || min; 707 | 708 | } else if ( values[i] === max ) { 709 | 710 | vals[i] = options.labels.last || max; 711 | 712 | } else if ( $.type( options.labels.rest ) === "array" ) { 713 | 714 | vals[i] = options.labels.rest[ steppedVals[i] - 1 ] || values[i]; 715 | 716 | } else { 717 | 718 | vals[i] = values[i]; 719 | 720 | } 721 | 722 | } 723 | 724 | } else { 725 | 726 | for ( i = 0; i < values.length; i++ ) { 727 | 728 | vals[i] = values[i]; 729 | 730 | } 731 | 732 | } 733 | 734 | return vals; 735 | 736 | } 737 | 738 | // apply handle tip if settings allows. 739 | if ( options.handle ) { 740 | 741 | // we need to set the human-readable label to either the 742 | // corresponding element in the array, or the appropriate 743 | // item in the object... or an empty string. 744 | 745 | tipValues = ( slider.values() && slider.values().length ) ? 746 | getPipLabels( values ) : 747 | getPipLabels( [ value ] ); 748 | 749 | for ( i = 0; i < tipValues.length; i++ ) { 750 | 751 | $handles 752 | .eq( i ) 753 | .append( $(""+ options.formatLabel(tipValues[i]) +"") ); 754 | 755 | } 756 | 757 | } 758 | 759 | if ( options.pips ) { 760 | 761 | // if this slider also has pip-labels, we make those into tips, too. 762 | slider.element.find(".ui-slider-label").each(function(k, v) { 763 | 764 | var $this = $(v), 765 | val = [ $this.data("value") ], 766 | label, 767 | $tip; 768 | 769 | 770 | label = options.formatLabel( getPipLabels( val )[0] ); 771 | 772 | // create a tip element 773 | $tip = 774 | $("" + label + "") 775 | .insertAfter( $this ); 776 | 777 | }); 778 | 779 | } 780 | 781 | // check that the event option is actually valid against our 782 | // own list of the slider's events. 783 | if ( options.event !== "slide" && 784 | options.event !== "slidechange" && 785 | options.event !== "slide slidechange" && 786 | options.event !== "slidechange slide" ) { 787 | 788 | options.event = "slidechange slide"; 789 | 790 | } 791 | 792 | // when slider changes, update handle tip label. 793 | slider.element 794 | .off(".sliderFloat") 795 | .on( options.event + ".sliderFloat", function( e, ui ) { 796 | 797 | var uiValue = ( $.type( ui.value ) === "array" ) ? ui.value : [ ui.value ], 798 | val = options.formatLabel( getPipLabels( uiValue )[0] ); 799 | 800 | $(ui.handle) 801 | .find(".ui-slider-tip") 802 | .html( val ); 803 | 804 | }); 805 | 806 | } 807 | 808 | }; 809 | 810 | $.extend(true, $.ui.slider.prototype, extensionMethods); 811 | 812 | })(jQuery); 813 | -------------------------------------------------------------------------------- /templates/attitude-indicator/CNAME: -------------------------------------------------------------------------------- 1 | attitude-indicator.igneosaur.co.uk 2 | -------------------------------------------------------------------------------- /templates/attitude-indicator/README.md: -------------------------------------------------------------------------------- 1 | https://dannyedwards.gitlab.io/attitude-indicator/ 2 | https://github.com/saasmath/attitude-indicator 3 | -------------------------------------------------------------------------------- /templates/attitude-indicator/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Attitude Indicator 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | 34 |
35 |
36 |
37 | 38 |
39 |
40 |
41 |
42 |
43 | 44 |
45 |
46 |
47 |
48 | 49 |
50 |
51 |
52 |
53 | 54 |
55 |
56 |
57 |
58 |
59 |
60 | 61 |
62 |
63 |
64 |
65 |
66 | 67 |
68 |
69 |
70 |
71 |
72 | 73 |
74 |
    75 |
  • STBY
  • 76 |
  • PWR
  • 77 |
  • TEST
  • 78 |
79 |
    80 |
  • 81 |
  • 82 |
  • 83 |
84 |
85 |
86 | 87 |
PULL TO CAGE
88 |
89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /templates/glass.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MSFS2020 Cockpit Companion 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 44 | 45 | 71 | 72 | 73 | 74 | 75 |
76 | 77 | 78 | 137 | 138 | 139 |
140 |
141 |
142 |
143 | 144 |
145 | 146 |
147 | 148 | 149 |
150 | 151 |
152 |
153 |
154 |
155 |
156 |
157 | Altitude 158 |

?

159 |
160 |
161 |
162 |
163 |
164 |
165 | V/Speed 166 |

?

167 |
168 |
169 |
170 |
171 |
172 |
173 | Heading 174 |

?

175 |
176 |
177 |
178 |
179 |
180 |
181 | Airspeed 182 |

?

183 |
184 |
185 |
186 |
187 | 188 |
189 |
190 |
191 | 192 | 193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 | Fuel % 201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 | 212 | 213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 | 221 |
222 |
223 |
224 |
225 |
226 | 227 |
228 | 229 | 230 | 231 | 232 | 233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 | 241 | 242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 | 250 | 251 | 252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 | 260 | 261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 | Landing gear 269 | 270 |
271 |
272 |
273 |
274 |
275 |
276 | Flaps (%)

277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 |
?
286 | 287 |
288 |
289 |
290 |
291 |
292 |
293 | Elevator Trim (%)

294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 |
?
302 |
303 |
304 |
305 | 316 | 317 |
318 |
319 |
320 |
321 | 322 | 323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 | Autopilot Master 331 | 332 |
333 |
334 |
335 | 336 |
337 |
338 |
339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 358 | 359 | 360 | 361 | 362 | 370 | 371 | 372 | 373 | 374 | 382 | 383 | 384 | 385 | 386 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 |
Wing Leveler
Heading 351 |
352 | 353 |
354 | 355 |
356 |
357 |
Altitude 363 |
364 | 365 |
366 | 367 |
368 |
369 |
Vertical Speed 375 |
376 | 377 |
378 | 379 |
380 |
381 |
Airspeed 387 |
388 | 389 |
390 | 391 |
392 |
393 |
Attitude
Backcourse
Approach
411 | 412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 | 420 | 421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 | 429 | 430 | 431 | 432 | 439 | 440 | 441 | 442 | 443 | 450 | 451 | 452 | 453 | 454 | 461 | 462 | 463 | 464 | 465 | 472 | 473 |
COM1 Frequency 433 | 434 | 435 | 436 | 437 | 438 |
COM2 Frequency 444 | 445 | 446 | 447 | 448 | 449 |
NAV1 Frequency 455 | 456 | 457 | 458 | 459 | 460 |
NAV2 Frequency 466 | 467 | 468 | 469 | 470 | 471 |
474 | 475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 | 486 |
487 | 488 | 489 | 563 | 564 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odwdinc/Python-SimConnect/af3eb9deedcf88fde422da2ac12fa41b96984942/tests/__init__.py -------------------------------------------------------------------------------- /tests/glass_dummy_server.py: -------------------------------------------------------------------------------- 1 | # 2 | # This is a dummy server which does not connect to MSFS2020 3 | # It just serves up random data which allows testing of the front end without MSFS2020 running 4 | # If you want to connect to MSFS2020 then you are looking for glass_server.py 5 | 6 | 7 | from flask import Flask, jsonify, render_template, request 8 | from time import sleep 9 | import random 10 | 11 | app = Flask(__name__) 12 | 13 | latitude = 47.606209 14 | longitude = -122.332069 15 | 16 | def thousandify(x): 17 | return f"{x:,}" 18 | 19 | 20 | 21 | @app.route ('/') 22 | def glass(): 23 | return render_template("glass.html") 24 | 25 | @app.route('/ui') 26 | def output_ui_variables(): 27 | global latitude, longitude 28 | 29 | ui_friendly_dictionary = {} 30 | ui_friendly_dictionary["STATUS"] = "success" 31 | ui_friendly_dictionary["ALTITUDE"] = thousandify(random.randint(7995,8005)) 32 | ui_friendly_dictionary["LATITUDE"] = latitude 33 | ui_friendly_dictionary["LONGITUDE"] = longitude 34 | ui_friendly_dictionary["AIRSPEED_INDICATE"] = random.randint(395,405) 35 | ui_friendly_dictionary["MAGNETIC_COMPASS"] = random.randint(89,91) 36 | ui_friendly_dictionary["VERTICAL_SPEED"] = random.randint(-5,5) 37 | ui_friendly_dictionary["FUEL_PERCENTAGE"] = random.randint(79,81) 38 | 39 | ui_friendly_dictionary["AUTOPILOT_HEADING_LOCK"] = random.randint(0,1) 40 | ui_friendly_dictionary["AUTOPILOT_HEADING_LOCK_DIR"] = random.randint(1,360) 41 | 42 | ui_friendly_dictionary["AUTOPILOT_ALTITUDE_LOCK"] = random.randint(0,1) 43 | ui_friendly_dictionary["AUTOPILOT_ALTITUDE_LOCK_VAR"] = thousandify(random.randint(5000,25000)) 44 | 45 | ui_friendly_dictionary["AUTOPILOT_AIRSPEED_HOLD"] = random.randint(0,1) 46 | ui_friendly_dictionary["AUTOPILOT_AIRSPEED_HOLD_VAR"] = thousandify(random.randint(100,350)) 47 | 48 | ui_friendly_dictionary["AUTOPILOT_PITCH_HOLD"] = random.randint(0,1) 49 | ui_friendly_dictionary["AUTOPILOT_PITCH_HOLD_REF"] = thousandify(random.randint(-25,25)) 50 | 51 | if random.randint(0,1) == 1: 52 | ui_friendly_dictionary["GEAR_HANDLE_POSITION"] = "UP" 53 | else: 54 | ui_friendly_dictionary["GEAR_HANDLE_POSITION"] = "DOWN" 55 | 56 | ui_friendly_dictionary["ELEVATOR_TRIM_PCT"] = random.randint(-10,10) 57 | ui_friendly_dictionary["RUDDER_TRIM_PCT"] = random.randint(-10,10) 58 | ui_friendly_dictionary["FLAPS_HANDLE_PERCENT"] = random.randint(0,100) 59 | 60 | longitude = longitude + 0.01 61 | 62 | return jsonify(ui_friendly_dictionary) 63 | 64 | 65 | @app.route('/datapoint//set', methods=["POST"]) 66 | def set_datapoint(datapoint_name): 67 | 68 | value_to_use = request.form.get('value_to_use') 69 | 70 | if value_to_use == None: 71 | print(datapoint_name + ": " + "No value passed") 72 | else: 73 | print(datapoint_name + ": " + value_to_use) 74 | 75 | status = "success" 76 | return jsonify(status) 77 | 78 | 79 | app.run(host='0.0.0.0', port=5000, debug=True) -------------------------------------------------------------------------------- /tests/test_entity_plane.py: -------------------------------------------------------------------------------- 1 | import SimConnect 2 | from unittest import TestCase 3 | from unittest.mock import Mock, patch, create_autospec 4 | 5 | import logging 6 | 7 | LOGGER = logging.getLogger(__name__) 8 | 9 | 10 | class sData(dict): 11 | __getattr__ = dict.__getitem__ 12 | __delattr__ = dict.__delitem__ 13 | 14 | def __setattr__(self, key, value): 15 | super(sData, self).__setitem__(key, value) 16 | setattr( 17 | self, 18 | key, 19 | value, 20 | ) 21 | 22 | def __init__(self, data=None): 23 | if data is not None: 24 | try: 25 | for key, value in data.items(): 26 | self[key] = value 27 | except: 28 | pass 29 | 30 | 31 | class TestPlane(TestCase): 32 | def test_init(self): 33 | # SimConnect.Plane() 34 | self.assertTrue(True) 35 | 36 | def test_values(self): 37 | 38 | sm = create_autospec(SimConnect.SimConnect) 39 | 40 | def side_effect(*args, **kwargs): 41 | def val(): 42 | v = 100 43 | x = 100 44 | while True: 45 | yield x 46 | x += v 47 | 48 | data = sData() 49 | val = val() 50 | data["Altitude"] = next(val) 51 | data["Latitude"] = next(val) 52 | data["Longitude"] = next(val) 53 | data["Kohlsman"] = next(val) 54 | return data 55 | 56 | sm.get_data.side_effect = side_effect 57 | 58 | pl = SimConnect.Plane(sm=sm) 59 | self.assertEqual(100, pl.altitude) 60 | self.assertEqual(200, pl.latitude) 61 | self.assertEqual(300, pl.longitude) 62 | self.assertEqual(400, pl.kohlsman) 63 | -------------------------------------------------------------------------------- /tests/test_request.py: -------------------------------------------------------------------------------- 1 | from SimConnect import Request 2 | from unittest import TestCase 3 | from unittest.mock import Mock 4 | 5 | import logging 6 | 7 | LOGGER = logging.getLogger(__name__) 8 | 9 | class TestSimple(TestCase): 10 | def test_init_request(self): 11 | self.assertTrue(True) 12 | -------------------------------------------------------------------------------- /tests/test_simconnect.py: -------------------------------------------------------------------------------- 1 | from SimConnect import * 2 | 3 | from unittest import TestCase 4 | 5 | import logging 6 | 7 | LOGGER = logging.getLogger(__name__) 8 | 9 | class TestSimple(TestCase): 10 | def test_init_simconnect(self): 11 | self.assertTrue(True) 12 | --------------------------------------------------------------------------------