-
Notifications
You must be signed in to change notification settings - Fork 0
/
Serial-to-SimConnect.py
339 lines (298 loc) · 9.6 KB
/
Serial-to-SimConnect.py
1
2
3
4
5
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
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
#################################################################
# Serial Port to Simconnect for Microsoft Flight Simulator 2020
# Serial 1 is main port for rotary encoders from esp32
# Serial 2 will be used for all sorts of buttons and switches
#
# Program automatically waits for MSFS to fully start
#
# Serial messages are UTF-8 encoded and have to be terminated by '\n'
# Can also contain '\r' which gets discarded automatically
#
# May not work reliable as it is still in development
#
# Spacing uses tab and not spaces
# Some IDE's may be confused if tab and space are mixed
#
# Only tested on GA aicraft up to the CJ4
# May not or only partially work on airliners
#################################################################
import sys
from time import sleep
import serial
from SimConnect import *
import time
import threading
import queue
# helper for getting current time in milliseconds
current_milli_time = lambda: int(round(time.time() * 1000))
# events and requests, global because they are used in another function
ae = None
aq = None
# queue for serial commands / messages
command1 = queue.Queue()
command2 = queue.Queue()
# Definitions for the Serial Ports, change to the COM Port in use and used baudrate
com1 = 'COM4'
com2 = 'COM6'
baud1 = 115200
baud2 = 115200
# thread function for first serial port
def serial1():
try:
s1 = serial.Serial(com1, baud1)
except:
print("Could not open Serial1 on ", com1)
sys.exit()
print("Serial1")
try:
while True:
if s1.in_waiting:
res = s1.readline()
res = res.decode("utf-8").replace("\n", "").replace("\r", "")
command1.put(res)
except KeyboardInterrupt:
s1.close()
# thread function for first serial port
def serial2():
try:
s2 = serial.Serial(com2, baud2)
except:
print("Could not open Serial2 on ", com2)
sys.exit()
print("Serial2")
try:
while True:
if s2.in_waiting:
res = s2.readline()
res = res.decode("utf-8").replace("\n", "").replace("\r", "")
command2.put(res)
except KeyboardInterrupt:
s2.close()
# Main function, starts the Simconnect connection, regularly pulls new data from sim
# and executes the events, requested by the serial device
# ToDo Maybe also have the continuous requests of Aircraft data in thread
def main():
sm = None
while True:
try:
sm = SimConnect()
except:
print("MSFS not started")
sleep(1)
continue
else:
print("MSFS started")
break
# Objects used for Events and Requests of SimVars
global ae, aq
ae = AircraftEvents(sm)
aq = AircraftRequests(sm, _time=10)
# after the Sim has started the threads can start executing their respective functions
threading.Thread(target=serial1, daemon=True).start()
threading.Thread(target=serial2, daemon=True).start()
# Used to time the polling of SimVars
t = current_milli_time()
# SimVars to be polled
vs_active = False
flc_active = False
autothrottle_active = False # maybe not needed
autothrottle_mach = False
autothrottle_spd = False
# var set by serial 2, can be used to increment altitude by 1000ft instead of 100ft
altitude_use_thousand = False
# main loop
try:
while True:
# Every second we update the used SimVars
# as the requests are resource heavy we may only do them at most every 100ms
if current_milli_time() - t > 1000:
vs_active = aq.get("AUTOPILOT_VERTICAL_HOLD")
autothrottle_active = aq.get("AUTOTHROTTLE_ACTIVE")
autothrottle_mach = aq.get("AUTOPILOT_MACH_HOLD")
autothrottle_spd = aq.get("AUTOPILOT_AIRSPEED_HOLD")
flc_active = aq.get("AUTOPILOT_FLIGHT_LEVEL_CHANGE")
t = current_milli_time()
# These variables need to be set by serial 2, but aren't implemented right now
# Switch in VS panel to use either vertical speed and flc or autothrottle
autothrottle_switch = False # Probably not used
# Switch in CRS/VOR panel if we change CRS/VOR 1 or 2
crs_vor_1 = True
# First check serial 2 if it has new commands
m = command2.qsize()
if m:
res = command2.get()
print(res)
if res == "100":
altitude_use_thousand = False
elif res == "1000":
altitude_use_thousand = True
# Do the same for serial 1, only with much more commands
n = command1.qsize()
if n:
res = command1.get()
# print(res)
# Switch Case of commands, sorted by function area
# These currently are only rotary encoders and therefore have three different signal types
# Increment for clockwise rotation, decrement for counterclockwise rotation and sync for button
###################################################
# Altitude
###################################################
if res == "alt+":
# Check if we add 100 or thousand feet per step
if altitude_use_thousand:
print("ALT+ 1000:")
for i in range(10):
trigger_event("AP_ALT_VAR_INC")
else:
print("ALT+")
trigger_event("AP_ALT_VAR_INC")
elif res == "alt-":
# Check if we add 100 or thousand feet per step
if altitude_use_thousand:
print("ALT- 1000:")
for i in range(10):
trigger_event("AP_ALT_VAR_DEC")
else:
print("ALT-")
trigger_event("AP_ALT_VAR_DEC")
elif res == "alt_sync":
altitude = aq.get("INDICATED_ALTITUDE")
print("ALT SYNC: ", altitude)
trigger_event("AP_ALT_VAR_SET_ENGLISH", altitude)
###################################################
# Heading
###################################################
elif res == "hdg+":
print("HDG+")
trigger_event("HEADING_BUG_INC")
elif res == "hdg-":
print("HDG-")
trigger_event("HEADING_BUG_DEC")
elif res == "hdg_sync":
heading = aq.get("HEADING_INDICATOR")*180/3.14159265359
print("HDG SYNC: ", heading)
trigger_event("HEADING_BUG_SET", heading)
###################################################
# Vertical Speed
###################################################
elif res == "vs+":
if vs_active:
print("VS+")
trigger_event("AP_VS_VAR_INC")
elif flc_active:
print("SPD INC")
trigger_event("AP_SPD_VAR_INC")
elif res == "vs-":
if vs_active:
print("VS-")
trigger_event("AP_VS_VAR_DEC")
elif flc_active:
print("SPD DEC")
trigger_event("AP_SPD_VAR_DEC")
elif res == "vs_sync":
if vs_active:
print("VS LVL")
trigger_event("AP_VS_VAR_SET_ENGLISH", 0.0)
elif flc_active:
speed = aq.get("AIRSPEED_INDICATED")
print("FLC SPD SYNC")
trigger_event("AP_SPD_VAR_SET", speed)
###################################################
# Speed only for autothrottle in a320, may not work perfectly as it wasn't tested enough
###################################################
elif res == "spd+":
if autothrottle_spd:
print("SPD+")
trigger_event("AP_SPD_VAR_INC")
elif autothrottle_mach:
print("MACH+")
trigger_event("AP_MACH_VAR_INC")
elif res == "spd-":
if autothrottle_spd:
print("SPD-")
trigger_event("AP_SPD_VAR_DEC")
elif autothrottle_mach:
print("MACH-")
trigger_event("AP_MACH_VAR_DEC")
elif res == "spd_sync":
if autothrottle_spd:
speed = aq.get("AIRSPEED_INDICATED")
print("SPD SYNC")
trigger_event("AP_SPD_VAR_SET", speed)
elif autothrottle_mach:
mach = aq.get("AIRSPEED_MACH")
print("MACH SYNC")
trigger_event("AP_MACH_VAR_SET", mach)
###################################################
# CRS 1 / CRS 2
# just like heading
###################################################
elif res == "crs+":
if crs_vor_1:
print("CRS/VOR1+")
trigger_event("VOR1_OBI_INC")
else:
print("CRS/VOR2+")
trigger_event("VOR2_OBI_INC")
elif res == "crs-":
if crs_vor_1:
print("CRS/VOR1-")
trigger_event("VOR1_OBI_DEC")
else:
print("CRS/VOR2-")
trigger_event("VOR2_OBI_DEC")
elif res == "crs_sync":
heading = aq.get("HEADING_INDICATOR") * 180 / 3.14159265359
if crs_vor_1:
print("CRS/VOR1 SYNC: ", heading)
trigger_event("VOR1_SET", heading)
else:
print("CRS/VOR2 SYNC: ", heading)
trigger_event("VOR2_SET", heading)
###################################################
# QNH
# Sync sets QNH to STD
###################################################
elif res == "baro+":
print("BARO+")
trigger_event("KOHLSMAN_INC")
elif res == "baro-":
print("BARO-")
trigger_event("KOHLSMAN_DEC")
elif res == "baro_sync":
print("STD BARO")
# x = 1013.25 hPa * 16
trigger_event("KOHLSMAN_SET", 1013.25*16)
###################################################
# COM 1 / Com2
# not done right now but should be simple
###################################################
###################################################
# NAV COM 1 und 2
# not done right now but should be simple
###################################################
else:
# safeguard if somehow there was a wrong message sent
print(res)
# Maybe needed to make time for threads to execute? Not tested without yet
sleep(0.0001)
except KeyboardInterrupt:
if sm is not None:
sm.quit()
print("MSFS Stopped!")
return 0
# This function actually does the work of triggering the event
def trigger_event(event_name, value_to_use=None):
EVENT_TO_TRIGGER = ae.find(event_name)
if EVENT_TO_TRIGGER is not None:
if value_to_use is None:
EVENT_TO_TRIGGER()
else:
EVENT_TO_TRIGGER(int(value_to_use))
status = "success"
else:
status = "Error: %s is not an Event" % (event_name)
return status
# This is needed to make the file know what function to execute as main function
if __name__ == '__main__':
main()