├── LICENSE
├── README.md
├── doc
├── mobiflightVarAccess.drawio
└── mobiflightVarAccess.png
├── prototype
├── example.py
├── mobiflight_variable_requests.py
└── simconnect_mobiflight.py
└── src
├── example.py
├── mobiflight_variable_requests.py
└── simconnect_mobiflight.py
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Koseng
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MSFS Python SimConnect MobiFlight Extension
2 | Extends the [python simconnect library](https://github.com/odwdinc/Python-SimConnect) for MSFS2020 with the capability to read A and L variables with help of the [MobiFlight](https://www.mobiflight.com) WASM module.
3 |
4 | To just execute Mobiflight SimConnect Events this extension is NOT necessary. This can already be done with the Python-SimConnect library.
5 |
6 | * ```SimConnectMobiFlight``` inherits from ```SimConnect```, fixes an issue and adds the feature to register additional client data handlers.
7 | * ```MobiFlightVariableRequests``` implements the variable access via the MobiFlight WASM module.
8 | * A collection of possible variables can be found [here](https://github.com/Mobiflight/MobiFlight-Connector/blob/main/Presets/msfs2020_simvars.cip) in the [MobiFlight repository](https://github.com/Mobiflight/MobiFlight-Connector).
9 | * ATTENTION: At the moment it is only possible for one client at a time to access variables via MobiFlight WASM. There is not yet multi client support.
10 |
11 | Instead of the SimConnect class you need to use the SimConnectMobiFlight class in your application.
12 |
13 | ## Example
14 |
15 | **Example code on how to use the library and read variables:**
16 | ```python
17 | from simconnect_mobiflight import SimConnectMobiFlight
18 | from mobiflight_variable_requests import MobiFlightVariableRequests
19 | from time import sleep
20 |
21 | sm = SimConnectMobiFlight()
22 | vr = MobiFlightVariableRequests(sm)
23 | vr.clear_sim_variables()
24 |
25 | # Example write variable
26 | vr.set("0 (>L:A32NX_COCKPIT_DOOR_LOCKED)")
27 |
28 | while True:
29 | alt_ground = vr.get("(A:GROUND ALTITUDE,Meters)")
30 | alt_plane = vr.get("(A:PLANE ALTITUDE,Feet)")
31 | # FlyByWire A320
32 | ap1 = vr.get("(L:A32NX_AUTOPILOT_1_ACTIVE)")
33 | hdg = vr.get("(L:A32NX_AUTOPILOT_HEADING_SELECTED)")
34 | mode = vr.get("(L:A32NX_FMA_LATERAL_MODE)")
35 | sleep(1)
36 | ```
37 |
38 | ## MobiFlight WASM working principle
39 |
40 | 
41 |
--------------------------------------------------------------------------------
/doc/mobiflightVarAccess.drawio:
--------------------------------------------------------------------------------
1 | 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
--------------------------------------------------------------------------------
/doc/mobiflightVarAccess.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Koseng/MSFSPythonSimConnectMobiFlightExtension/d1a2f4f2acc1a24771e9e95248918d36e163289d/doc/mobiflightVarAccess.png
--------------------------------------------------------------------------------
/prototype/example.py:
--------------------------------------------------------------------------------
1 | import logging, logging.handlers
2 | from simconnect_mobiflight import SimConnectMobiFlight
3 | from mobiflight_variable_requests import MobiFlightVariableRequests
4 | from time import sleep
5 |
6 | def setupLogging(logFileName):
7 | logFormatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s] %(message)s")
8 | rootLogger = logging.getLogger()
9 | rootLogger.setLevel(logging.DEBUG)
10 | fileHandler = logging.handlers.RotatingFileHandler(logFileName, maxBytes=500000, backupCount=7)
11 | fileHandler.setFormatter(logFormatter)
12 | rootLogger.addHandler(fileHandler)
13 | consoleHandler = logging.StreamHandler()
14 | consoleHandler.setFormatter(logFormatter)
15 | rootLogger.addHandler(consoleHandler)
16 |
17 | # MAIN
18 | setupLogging("SimConnectMobiFlight.log")
19 | sm = SimConnectMobiFlight()
20 | vr = MobiFlightVariableRequests(sm)
21 | vr.clear_sim_variables()
22 |
23 |
24 | while True:
25 | alt_ground = vr.get("(A:GROUND ALTITUDE,Meters)")
26 | alt_plane = vr.get("(A:PLANE ALTITUDE,Feet)")
27 | # FlyByWire A320
28 | ap1 = vr.get("(L:A32NX_AUTOPILOT_1_ACTIVE)")
29 | hdg = vr.get("(L:A32NX_AUTOPILOT_HEADING_SELECTED)")
30 | mode = vr.get("(L:A32NX_FMA_LATERAL_MODE)")
31 | sleep(1)
--------------------------------------------------------------------------------
/prototype/mobiflight_variable_requests.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import struct
3 | import time
4 | from ctypes import sizeof
5 | from ctypes.wintypes import FLOAT
6 | from SimConnect.Enum import SIMCONNECT_CLIENT_DATA_PERIOD, SIMCONNECT_UNUSED
7 | import pathlib
8 |
9 | class SimVariable:
10 | def __init__(self, id, name, float_value = 0):
11 | self.id = id
12 | self.name = name
13 | self.float_value = float_value
14 | def __str__(self):
15 | return f"Id={self.id}, value={self.float_value}, name={self.name}"
16 |
17 | class MobiClient:
18 |
19 | def __init__(self, client_name):
20 | self.CLIENT_NAME = client_name
21 | self.CLIENT_DATA_AREA_LVARS = None
22 | self.CLIENT_DATA_AREA_CMD = None
23 | self.CLIENT_DATA_AREA_RESPONSE = None
24 | self.DATA_STRING_DEFINITION_ID = None
25 | def __str__(self):
26 | s = f"Name={self.CLIENT_NAME}, LVARS_ID={self.CLIENT_DATA_AREA_LVARS}, CMD_ID={self.CLIENT_DATA_AREA_CMD}, "
27 | return s + f"RESPONSE_ID={self.CLIENT_DATA_AREA_RESPONSE}, DEF_ID={self.DATA_STRING_DEFINITION_ID}"
28 |
29 |
30 | class MobiFlightVariableRequests:
31 |
32 | def __init__(self, simConnect):
33 | logging.info("MobiFlightVariableRequests: __init__")
34 |
35 | self.init_ready = False
36 | self.sm = simConnect
37 | self.sim_vars = {}
38 | self.sim_var_name_to_id = {}
39 |
40 | self.init_client = MobiClient("MobiFlight")
41 | self.init_client.CLIENT_DATA_AREA_LVARS = 0
42 | self.init_client.CLIENT_DATA_AREA_CMD = 1
43 | self.init_client.CLIENT_DATA_AREA_RESPONSE = 2
44 | self.init_client.DATA_STRING_DEFINITION_ID = 0
45 |
46 | working_dir_hash = hash(pathlib.Path.cwd()) % 100000000 # limit to 8 digits
47 | last_folder_name = pathlib.Path.cwd().name
48 | if len(last_folder_name) > 12:
49 | last_folder_name = last_folder_name[:12]
50 | client_name = last_folder_name + str(working_dir_hash)
51 |
52 | self.my_client = MobiClient(client_name)
53 | self.my_client.CLIENT_DATA_AREA_LVARS = 3
54 | self.my_client.CLIENT_DATA_AREA_CMD = 4
55 | self.my_client.CLIENT_DATA_AREA_RESPONSE = 5
56 | self.my_client.DATA_STRING_DEFINITION_ID = 1
57 |
58 | self.FLAG_DEFAULT = 0
59 | self.FLAG_CHANGED = 1
60 | self.DATA_STRING_SIZE = 1024
61 | self.DATA_STRING_OFFSET = 0
62 | self.SIMVAR_DEF_OFFSET = 1000
63 |
64 | self.sm.register_client_data_handler(self._client_data_callback_handler)
65 | self._initialize_client_data_areas(self.init_client)
66 |
67 | # Sometimes first command after reconnect is ignored. Therefore send just some arbitrary command.
68 | self._send_command("Do Nothing", self.init_client)
69 | self._send_command(("MF.Clients.Add." + client_name), self.init_client)
70 | # Wait for init ready
71 | while not self.init_ready:
72 | time.sleep(0.05)
73 |
74 |
75 | def _add_to_client_data_definition(self, definition_id, offset, size):
76 | logging.info("add_to_client_data_definition: definition_id=%s, offset=%s, size=%s", definition_id, offset, size)
77 | self.sm.dll.AddToClientDataDefinition(
78 | self.sm.hSimConnect,
79 | definition_id,
80 | offset,
81 | size,
82 | 0, # fEpsilon
83 | SIMCONNECT_UNUSED) # DatumId
84 |
85 |
86 | def _subscribe_to_data_change(self, data_area_id, request_id, definition_id, interval=0):
87 | logging.info("subscribe_to_data_change: data_area_id=%s, request_id=%s, definition_id=%s", data_area_id, request_id, definition_id)
88 | self.sm.dll.RequestClientData(
89 | self.sm.hSimConnect,
90 | data_area_id,
91 | request_id,
92 | definition_id,
93 | SIMCONNECT_CLIENT_DATA_PERIOD.SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET,
94 | self.FLAG_CHANGED,
95 | 0, # origin
96 | interval, # interval
97 | 0) # limit
98 |
99 |
100 | def _send_data(self, client, size, dataBytes):
101 | logging.info("send_data: client: %s, size=%s, dataBytes=%s", client, size, dataBytes)
102 | self.sm.dll.SetClientData(
103 | self.sm.hSimConnect,
104 | client.CLIENT_DATA_AREA_CMD,
105 | client.DATA_STRING_DEFINITION_ID,
106 | self.FLAG_DEFAULT,
107 | 0, # dwReserved
108 | size,
109 | dataBytes)
110 |
111 |
112 | def _send_command(self, command, client):
113 | logging.info("send_command: command=%s", command)
114 | data_byte_array = bytearray(command, "ascii")
115 | data_byte_array.extend(bytearray(self.DATA_STRING_SIZE - len(data_byte_array))) # extend to fix DATA_STRING_SIZE
116 | my_bytes = bytes(data_byte_array)
117 | self._send_data(client, self.DATA_STRING_SIZE, my_bytes)
118 |
119 |
120 | def _initialize_client_data_areas(self, client):
121 | logging.info("initialize_client_data_areas: Client: %s", client)
122 | # register client data area for receiving simvars, sending commands and receiving responses
123 | dataAreaLVarsName = (client.CLIENT_NAME + ".Lvars").encode("ascii")
124 | dataAreaCmdName = (client.CLIENT_NAME + ".Command").encode("ascii")
125 | dataAreaResponseName = (client.CLIENT_NAME + ".Response").encode("ascii")
126 | self.sm.dll.MapClientDataNameToID(self.sm.hSimConnect, dataAreaLVarsName, client.CLIENT_DATA_AREA_LVARS)
127 | self.sm.dll.MapClientDataNameToID(self.sm.hSimConnect, dataAreaCmdName, client.CLIENT_DATA_AREA_CMD)
128 | self.sm.dll.MapClientDataNameToID(self.sm.hSimConnect, dataAreaResponseName, client.CLIENT_DATA_AREA_RESPONSE)
129 | # subscribe to WASM Module responses
130 | self._add_to_client_data_definition(client.DATA_STRING_DEFINITION_ID, self.DATA_STRING_OFFSET, self.DATA_STRING_SIZE)
131 | self._subscribe_to_data_change(client.CLIENT_DATA_AREA_RESPONSE, client.DATA_STRING_DEFINITION_ID, client.DATA_STRING_DEFINITION_ID)
132 |
133 |
134 | def _c_string_bytes_to_string(self, data_bytes):
135 | return data_bytes[0:data_bytes.index(0)].decode(encoding='ascii') # index(0) for end of c string
136 |
137 |
138 | # simconnect library callback
139 | def _client_data_callback_handler(self, callback_data):
140 | # SimVar Data
141 | if callback_data.dwDefineID in self.sim_vars:
142 | data_bytes = struct.pack("I", callback_data.dwData[0])
143 | float_data = struct.unpack(' [0]
144 | self.sim_vars[callback_data.dwDefineID].float_value = round(float_data, 5)
145 | logging.debug("client_data_callback_handler: %s", self.sim_vars[callback_data.dwDefineID])
146 |
147 | # Response string of init_client
148 | elif callback_data.dwDefineID == self.init_client.DATA_STRING_DEFINITION_ID:
149 | response = self._c_string_bytes_to_string(bytes(callback_data.dwData))
150 | logging.debug("client_data_callback_handler: init_client response string: %s", response)
151 | # Check for response of registering new client
152 | if (self.my_client.CLIENT_NAME in response):
153 | self._initialize_client_data_areas(self.my_client)
154 | self.init_ready = True
155 |
156 | # Response string of my_client
157 | elif callback_data.dwDefineID == self.my_client.DATA_STRING_DEFINITION_ID:
158 | response = self._c_string_bytes_to_string(bytes(callback_data.dwData))
159 | logging.debug("client_data_callback_handler: get my_client response string: %s", response)
160 | else:
161 | logging.warning("client_data_callback_handler: DefinitionID %s not found!", callback_data.dwDefineID)
162 |
163 |
164 | def get(self, variableString):
165 | client = self.my_client
166 | if variableString not in self.sim_var_name_to_id:
167 | # add new variable
168 | id = len(self.sim_vars) + self.SIMVAR_DEF_OFFSET
169 | self.sim_vars[id] = SimVariable(id, variableString)
170 | self.sim_var_name_to_id[variableString] = id
171 | # subscribe to variable data change
172 | offset = (id-self.SIMVAR_DEF_OFFSET)*sizeof(FLOAT)
173 | self._add_to_client_data_definition(id, offset, sizeof(FLOAT))
174 | self._subscribe_to_data_change(client.CLIENT_DATA_AREA_LVARS, id, id)
175 | self._send_command(("MF.SimVars.Add." + variableString), client)
176 | # determine id and return value
177 | variable_id = self.sim_var_name_to_id[variableString]
178 | float_value = self.sim_vars[variable_id].float_value
179 | logging.debug("get: %s. Return=%s", variableString, float_value)
180 | return float_value
181 |
182 |
183 | def set(self, variableString):
184 | logging.debug("set: %s", variableString)
185 | self._send_command(("MF.SimVars.Set." + variableString), self.my_client)
186 |
187 |
188 | def _list_sim_variables(self):
189 | logging.info("list_sim_variables MF.LVars.List")
190 | self._send_command("MF.LVars.List", self.my_client)
191 |
192 |
193 | def clear_sim_variables(self):
194 | logging.info("clear_sim_variables")
195 | self.sim_vars.clear()
196 | self.sim_var_name_to_id.clear()
197 | self._send_command("MF.SimVars.Clear", self.my_client)
198 |
199 |
200 |
--------------------------------------------------------------------------------
/prototype/simconnect_mobiflight.py:
--------------------------------------------------------------------------------
1 | import logging, logging.handlers
2 | import ctypes
3 | from ctypes import wintypes
4 | from SimConnect import SimConnect
5 | from SimConnect.Enum import SIMCONNECT_CLIENT_DATA_ID, SIMCONNECT_RECV_ID, SIMCONNECT_RECV_CLIENT_DATA
6 |
7 |
8 | class SimConnectMobiFlight(SimConnect):
9 |
10 | def __init__(self, auto_connect=True, library_path=None):
11 | self.client_data_handlers = []
12 | if library_path:
13 | super().__init__(auto_connect, library_path)
14 | else:
15 | super().__init__(auto_connect)
16 | # Fix missing types
17 | self.dll.MapClientDataNameToID.argtypes = [wintypes.HANDLE, ctypes.c_char_p, SIMCONNECT_CLIENT_DATA_ID]
18 |
19 |
20 | def register_client_data_handler(self, handler):
21 | if not handler in self.client_data_handlers:
22 | logging.info("Register new client data handler")
23 | self.client_data_handlers.append(handler)
24 |
25 |
26 | def unregister_client_data_handler(self, handler):
27 | if handler in self.client_data_handlers:
28 | logging.info("Unregister client data handler")
29 | self.client_data_handlers.remove(handler)
30 |
31 |
32 | def my_dispatch_proc(self, pData, cbData, pContext):
33 | dwID = pData.contents.dwID
34 | if dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_CLIENT_DATA:
35 | client_data = ctypes.cast(pData, ctypes.POINTER(SIMCONNECT_RECV_CLIENT_DATA)).contents
36 | for handler in self.client_data_handlers:
37 | handler(client_data)
38 | else:
39 | super().my_dispatch_proc(pData, cbData, pContext)
--------------------------------------------------------------------------------
/src/example.py:
--------------------------------------------------------------------------------
1 | import logging, logging.handlers
2 | from simconnect_mobiflight import SimConnectMobiFlight
3 | from mobiflight_variable_requests import MobiFlightVariableRequests
4 | from time import sleep
5 |
6 | def setupLogging(logFileName):
7 | logFormatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s] %(message)s")
8 | rootLogger = logging.getLogger()
9 | rootLogger.setLevel(logging.DEBUG)
10 | fileHandler = logging.handlers.RotatingFileHandler(logFileName, maxBytes=500000, backupCount=7)
11 | fileHandler.setFormatter(logFormatter)
12 | rootLogger.addHandler(fileHandler)
13 | consoleHandler = logging.StreamHandler()
14 | consoleHandler.setFormatter(logFormatter)
15 | rootLogger.addHandler(consoleHandler)
16 |
17 | # MAIN
18 | setupLogging("SimConnectMobiFlight.log")
19 | sm = SimConnectMobiFlight()
20 | vr = MobiFlightVariableRequests(sm)
21 | vr.clear_sim_variables()
22 |
23 | # Example write variable
24 | vr.set("0 (>L:A32NX_COCKPIT_DOOR_LOCKED)")
25 |
26 | while True:
27 | alt_ground = vr.get("(A:GROUND ALTITUDE,Meters)")
28 | alt_plane = vr.get("(A:PLANE ALTITUDE,Feet)")
29 | # FlyByWire A320
30 | ap1 = vr.get("(L:A32NX_AUTOPILOT_1_ACTIVE)")
31 | hdg = vr.get("(L:A32NX_AUTOPILOT_HEADING_SELECTED)")
32 | mode = vr.get("(L:A32NX_FMA_LATERAL_MODE)")
33 | sleep(1)
--------------------------------------------------------------------------------
/src/mobiflight_variable_requests.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import struct
3 | from time import sleep
4 | from ctypes import sizeof
5 | from ctypes.wintypes import FLOAT
6 | from SimConnect.Enum import SIMCONNECT_CLIENT_DATA_PERIOD, SIMCONNECT_UNUSED
7 |
8 |
9 | class SimVariable:
10 | def __init__(self, id, name, float_value = None):
11 | self.id = id
12 | self.name = name
13 | self.float_value = float_value
14 | self.initialized = False
15 | def __str__(self):
16 | return f"Id={self.id}, value={self.float_value}, name={self.name}"
17 |
18 |
19 | class MobiFlightVariableRequests:
20 |
21 | def __init__(self, simConnect):
22 | logging.info("MobiFlightVariableRequests __init__")
23 | self.sm = simConnect
24 | self.sim_vars = {}
25 | self.sim_var_name_to_id = {}
26 | self.CLIENT_DATA_AREA_LVARS = 0
27 | self.CLIENT_DATA_AREA_CMD = 1
28 | self.CLIENT_DATA_AREA_RESPONSE = 2
29 | self.FLAG_DEFAULT = 0
30 | self.FLAG_CHANGED = 1
31 | self.DATA_STRING_SIZE = 256
32 | self.DATA_STRING_OFFSET = 0
33 | self.DATA_STRING_DEFINITION_ID = 0
34 | self.sm.register_client_data_handler(self.client_data_callback_handler)
35 | self.initialize_client_data_areas()
36 |
37 |
38 | def add_to_client_data_definition(self, definition_id, offset, size):
39 | logging.info("add_to_client_data_definition definition_id=%s, offset=%s, size=%s", definition_id, offset, size)
40 | self.sm.dll.AddToClientDataDefinition(
41 | self.sm.hSimConnect,
42 | definition_id,
43 | offset,
44 | size,
45 | 0, # fEpsilon
46 | SIMCONNECT_UNUSED) # DatumId
47 |
48 |
49 | def subscribe_to_data_change(self, data_area_id, request_id, definition_id):
50 | logging.info("subscribe_to_data_change data_area_id=%s, request_id=%s, definition_id=%s", data_area_id, request_id, definition_id)
51 | self.sm.dll.RequestClientData(
52 | self.sm.hSimConnect,
53 | data_area_id,
54 | request_id,
55 | definition_id,
56 | SIMCONNECT_CLIENT_DATA_PERIOD.SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET,
57 | self.FLAG_CHANGED,
58 | 0, # origin
59 | 0, # interval
60 | 0) # limit
61 |
62 |
63 | def send_data(self, data_area_id, definition_id, size, dataBytes):
64 | logging.info("send_data data_area_id=%s, definition_id=%s, size=%s, dataBytes=%s", data_area_id, definition_id, size, dataBytes)
65 | self.sm.dll.SetClientData(
66 | self.sm.hSimConnect,
67 | data_area_id,
68 | definition_id,
69 | self.FLAG_DEFAULT,
70 | 0, # dwReserved
71 | size,
72 | dataBytes)
73 |
74 |
75 | def send_command(self, command):
76 | logging.info("send_command command=%s", command)
77 | data_byte_array = bytearray(command, "ascii")
78 | data_byte_array.extend(bytearray(self.DATA_STRING_SIZE - len(data_byte_array))) # extend to fix DATA_STRING_SIZE
79 | self.send_data(self.CLIENT_DATA_AREA_CMD, self.DATA_STRING_DEFINITION_ID, self.DATA_STRING_SIZE, bytes(data_byte_array))
80 |
81 |
82 | def initialize_client_data_areas(self):
83 | logging.info("initialize_client_data_areas")
84 | # register client data area for receiving simvars
85 | self.sm.dll.MapClientDataNameToID(self.sm.hSimConnect, "MobiFlight.LVars".encode("ascii"), self.CLIENT_DATA_AREA_LVARS)
86 | self.sm.dll.CreateClientData(self.sm.hSimConnect, self.CLIENT_DATA_AREA_LVARS, 4096, self.FLAG_DEFAULT)
87 | # register client data area for sending commands
88 | self.sm.dll.MapClientDataNameToID(self.sm.hSimConnect, "MobiFlight.Command".encode("ascii"), self.CLIENT_DATA_AREA_CMD)
89 | self.sm.dll.CreateClientData(self.sm.hSimConnect, self.CLIENT_DATA_AREA_CMD, self.DATA_STRING_SIZE, self.FLAG_DEFAULT)
90 | # register client data area for receiving responses
91 | self.sm.dll.MapClientDataNameToID(self.sm.hSimConnect, "MobiFlight.Response".encode("ascii"), self.CLIENT_DATA_AREA_RESPONSE)
92 | self.sm.dll.CreateClientData(self.sm.hSimConnect, self.CLIENT_DATA_AREA_RESPONSE, self.DATA_STRING_SIZE, self.FLAG_DEFAULT)
93 | # subscribe to WASM Module responses
94 | self.add_to_client_data_definition(self.DATA_STRING_DEFINITION_ID, self.DATA_STRING_OFFSET, self.DATA_STRING_SIZE)
95 | self.subscribe_to_data_change(self.CLIENT_DATA_AREA_RESPONSE, self.DATA_STRING_DEFINITION_ID, self.DATA_STRING_DEFINITION_ID)
96 |
97 |
98 | # simconnect library callback
99 | def client_data_callback_handler(self, client_data):
100 | if client_data.dwDefineID in self.sim_vars:
101 | data_bytes = struct.pack("I", client_data.dwData[0])
102 | float_data = struct.unpack(' [0]
103 | float_value = round(float_data, 5)
104 | sim_var = self.sim_vars[client_data.dwDefineID]
105 | if not sim_var.initialized and float_value == 0.0:
106 | sim_var.initialized = True
107 | else:
108 | self.sim_vars[client_data.dwDefineID].float_value = float_value
109 | logging.debug("client_data_callback_handler %s, raw=%s", sim_var, float_value)
110 | else:
111 | logging.warning("client_data_callback_handler DefinitionID %s not found!", client_data.dwDefineID)
112 |
113 |
114 | def get(self, variableString):
115 | if variableString not in self.sim_var_name_to_id:
116 | # add new variable
117 | id = len(self.sim_vars) + 1
118 | self.sim_vars[id] = SimVariable(id, variableString)
119 | self.sim_var_name_to_id[variableString] = id
120 | # subscribe to variable data change
121 | offset = (id-1)*sizeof(FLOAT)
122 | self.add_to_client_data_definition(id, offset, sizeof(FLOAT))
123 | self.subscribe_to_data_change(self.CLIENT_DATA_AREA_LVARS, id, id)
124 | self.send_command("MF.SimVars.Add." + variableString)
125 | # determine id and return value
126 | variable_id = self.sim_var_name_to_id[variableString]
127 | sim_var = self.sim_vars[variable_id]
128 | wait_counter = 0
129 | while wait_counter < 50: # wait max 500ms
130 | if sim_var.float_value is None:
131 | sleep(0.01) # wait 10ms
132 | wait_counter = wait_counter + 1
133 | else:
134 | break
135 | if sim_var.float_value is None and sim_var.initialized:
136 | sim_var.float_value = 0.0
137 | logging.debug("get %s. wait_counter=%s, Return=%s", variableString, wait_counter, sim_var.float_value)
138 | return sim_var.float_value
139 |
140 |
141 | def set(self, variableString):
142 | logging.debug("set: %s", variableString)
143 | self.send_command("MF.SimVars.Set." + variableString)
144 |
145 |
146 | def clear_sim_variables(self):
147 | logging.info("clear_sim_variables")
148 | self.sim_vars.clear()
149 | self.sim_var_name_to_id.clear()
150 | self.send_command("MF.SimVars.Clear")
151 |
152 |
153 |
--------------------------------------------------------------------------------
/src/simconnect_mobiflight.py:
--------------------------------------------------------------------------------
1 | import logging, logging.handlers
2 | import ctypes
3 | from ctypes import wintypes
4 | from SimConnect import SimConnect
5 | from SimConnect.Enum import SIMCONNECT_CLIENT_DATA_ID, SIMCONNECT_RECV_ID, SIMCONNECT_RECV_CLIENT_DATA
6 |
7 |
8 | class SimConnectMobiFlight(SimConnect):
9 |
10 | def __init__(self, auto_connect=True, library_path=None):
11 | self.client_data_handlers = []
12 | if library_path:
13 | super().__init__(auto_connect, library_path)
14 | else:
15 | super().__init__(auto_connect)
16 | # Fix missing types
17 | self.dll.MapClientDataNameToID.argtypes = [wintypes.HANDLE, ctypes.c_char_p, SIMCONNECT_CLIENT_DATA_ID]
18 |
19 |
20 | def register_client_data_handler(self, handler):
21 | if not handler in self.client_data_handlers:
22 | logging.info("Register new client data handler")
23 | self.client_data_handlers.append(handler)
24 |
25 |
26 | def unregister_client_data_handler(self, handler):
27 | if handler in self.client_data_handlers:
28 | logging.info("Unregister client data handler")
29 | self.client_data_handlers.remove(handler)
30 |
31 |
32 | def my_dispatch_proc(self, pData, cbData, pContext):
33 | dwID = pData.contents.dwID
34 | if dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_CLIENT_DATA:
35 | client_data = ctypes.cast(pData, ctypes.POINTER(SIMCONNECT_RECV_CLIENT_DATA)).contents
36 | for handler in self.client_data_handlers:
37 | handler(client_data)
38 | else:
39 | super().my_dispatch_proc(pData, cbData, pContext)
--------------------------------------------------------------------------------