-
Notifications
You must be signed in to change notification settings - Fork 5
/
easysimconnect.go
456 lines (421 loc) · 14.3 KB
/
easysimconnect.go
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
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
package simconnect
import (
"fmt"
"time"
"unsafe"
"github.com/sirupsen/logrus"
)
// EasySimConnectLogLevel is a type of Log level
type EasySimConnectLogLevel int
// Log Level
const (
LogNo EasySimConnectLogLevel = iota
LogError
LogWarn
LogInfo
)
// EasySimConnect for easy use of SimConnect in golang
// Please show example_test.go for use case
type EasySimConnect struct {
sc *SimConnect
delay time.Duration
listSimVar [][]SimVar
listChan []chan []SimVar
indexEvent uint32
listEvent map[uint32]func(interface{})
listSimEvent map[KeySimEvent]SimEvent
logLevel EasySimConnectLogLevel
cOpen chan bool
alive bool
cException chan *SIMCONNECT_RECV_EXCEPTION
}
// NewEasySimConnect create instance of EasySimConnect
func NewEasySimConnect() (*EasySimConnect, error) {
sc, err := NewSimConnect()
if err != nil {
return nil, err
}
logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true})
return &EasySimConnect{
sc,
100 * time.Millisecond,
make([][]SimVar, 0),
make([]chan []SimVar, 0),
0,
make(map[uint32]func(interface{})),
make(map[KeySimEvent]SimEvent),
LogNo,
make(chan bool, 1),
true,
make(chan *SIMCONNECT_RECV_EXCEPTION),
}, nil
}
// SetLoggerLevel you can set log level in EasySimConnect
func (esc *EasySimConnect) SetLoggerLevel(level EasySimConnectLogLevel) {
esc.logLevel = level
}
// Close Finishing EasySimConnect, All object created with this EasySimConnect's instance is perished after call this function
func (esc *EasySimConnect) Close() <-chan bool {
esc.alive = false
return esc.cOpen
}
// IsAlive return true if connected
func (esc *EasySimConnect) IsAlive() bool {
return esc.alive
}
// SetDelay Select delay update SimVar and
func (esc *EasySimConnect) SetDelay(t time.Duration) {
esc.delay = t
}
// Connect to sim and run dispatch or return error
func (esc *EasySimConnect) Connect(appName string) (<-chan bool, error) {
err, _ := esc.sc.Open(appName)
if err != nil {
return nil, err
}
go esc.runDispatch()
return esc.cOpen, nil
}
func (esc *EasySimConnect) logf(level EasySimConnectLogLevel, format string, args ...interface{}) {
if level > esc.logLevel {
return
}
if level == LogInfo {
logrus.Infof(format, args...)
}
if level == LogWarn {
logrus.Warnf(format, args...)
}
if level == LogError {
logrus.Errorf(format, args...)
}
}
func (esc *EasySimConnect) runDispatch() {
for esc.alive {
var ppdata unsafe.Pointer
var pcbData uint32
err, _ := esc.sc.GetNextDispatch(&ppdata, &pcbData)
//créer un buffer en copy les data ppdata avec longueur pcbdata et utiliser le buffer pour la suite
if err != nil {
time.Sleep(esc.delay / 2)
continue
}
buf, err := convCBytesToGoBytes(ppdata, int(pcbData))
if err != nil {
esc.logf(LogError, "%v#", err)
continue
}
recvInfo := *(*SIMCONNECT_RECV)(ppdata)
switch recvInfo.dwID {
case SIMCONNECT_RECV_ID_OPEN:
recv := *(*SIMCONNECT_RECV_OPEN)(ppdata)
esc.logf(LogInfo, "Connected to %s", convStrToGoString(recv.szApplicationName[:]))
esc.cOpen <- true
case SIMCONNECT_RECV_ID_EVENT:
recv := *(*SIMCONNECT_RECV_EVENT)(ppdata)
cb, found := esc.listEvent[recv.uEventID]
if !found {
esc.logf(LogInfo, "Ignored event : %#v\n", recv)
continue
}
cb(recv)
case SIMCONNECT_RECV_ID_QUIT:
esc.sc.Close()
esc.cOpen <- false
return
case SIMCONNECT_RECV_ID_EVENT_FILENAME:
recv := *(*SIMCONNECT_RECV_EVENT_FILENAME)(ppdata)
esc.listEvent[recv.uEventID](recv)
case SIMCONNECT_RECV_ID_EXCEPTION:
recv := (*SIMCONNECT_RECV_EXCEPTION)(ppdata)
select {
case esc.cException <- recv:
case <-time.After(100 * time.Millisecond):
}
esc.logf(LogInfo, "SimConnect Exception : %s %#v\n", getTextException(recv.dwException), *recv)
case SIMCONNECT_RECV_ID_SIMOBJECT_DATA, SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE:
recv := *(*SIMCONNECT_RECV_SIMOBJECT_DATA)(ppdata)
if len(esc.listSimVar) < int(recv.dwDefineID) {
esc.logf(LogWarn, "ListSimVar not found: %#v\n %#v\n %d>=%d", recv, esc.listSimVar, len(esc.listSimVar), int(recv.dwDefineID))
continue
}
listSimVar := esc.listSimVar[recv.dwDefineID]
if len(listSimVar) != int(recv.dwDefineCount) {
esc.logf(LogWarn, "ListSimVar size not equal %#v ?= %#v\n", int(recv.dwDefineCount), len(listSimVar))
continue
}
position := int(unsafe.Offsetof(recv.dwData))
returnSimVar := make([]SimVar, len(listSimVar))
for i, simVar := range listSimVar {
size := simVar.GetSize()
if position+size > int(pcbData) {
esc.logf(LogError, "slice bounds out of range")
break
}
simVar.data = buf[position : position+size]
returnSimVar[i] = simVar
position = position + size
}
select {
case esc.listChan[recv.dwDefineID] <- returnSimVar:
case <-time.After(esc.delay):
}
go func() {
time.Sleep(esc.delay)
esc.sc.RequestDataOnSimObjectType(uint32(0), recv.dwDefineID, uint32(0), uint32(0))
}()
default:
esc.logf(LogInfo, "%#v\n", recvInfo)
}
}
esc.sc.Close()
esc.cOpen <- false
}
// ConnectToSimVar return a chan. This chan return an array when updating they SimVars in order of argument of this function
func (esc *EasySimConnect) ConnectToSimVar(listSimVar ...SimVar) (<-chan []SimVar, error) {
defineID := uint32(len(esc.listSimVar))
addedSimVar := make([]SimVar, 0)
for i, simVar := range listSimVar {
err, id := esc.sc.AddToDataDefinition(defineID, simVar.getNameForDataDefinition(), simVar.getUnitForDataDefinition(), simVar.GetDatumType(), 0, uint32(i))
if err != nil {
esc.logf(LogInfo, "Error add SimVar ( %s ) in AddToDataDefinition error : %#v", simVar.Name, err)
return nil, fmt.Errorf(
"Error add SimVar ( %s ) in AddToDataDefinition error : %#v",
simVar.Name,
err,
)
}
var exception *SIMCONNECT_RECV_EXCEPTION
select {
case exception = <-esc.cException:
case <-time.After(100 * time.Millisecond):
}
if exception != nil && exception.dwSendID == id {
return nil, fmt.Errorf(
"Error add SimVar ( %s ) in AddToDataDefinition : %s. Please control name ( %s ) and unit ( %s )",
simVar.Name,
getTextException(exception.dwException),
simVar.Name,
simVar.Unit,
)
}
addedSimVar = append(addedSimVar, simVar)
}
esc.listSimVar = append(esc.listSimVar, addedSimVar)
chanSimVar := make(chan []SimVar)
esc.listChan = append(esc.listChan, chanSimVar)
esc.sc.RequestDataOnSimObjectType(uint32(0), defineID, uint32(0), uint32(0))
return chanSimVar, nil
}
// ConnectToSimVarObject return a chan. This chan return an array when updating they SimVars in order of argument of this function
//
// Deprecated: Use ConnectToSimVar instead.
func (esc *EasySimConnect) ConnectToSimVarObject(listSimVar ...SimVar) <-chan []SimVar {
c, err := esc.ConnectToSimVar(listSimVar...)
if err != nil {
esc.logf(LogError, err.Error())
return make(<-chan []SimVar)
}
return c
}
// ConnectInterfaceToSimVar return a chan. This chan return interface when updating
func (esc *EasySimConnect) ConnectInterfaceToSimVar(iFace interface{}) (<-chan interface{}, error) {
simVars, err := SimVarGenerator(iFace)
if err != nil {
return nil, err
}
csimVars, err := esc.ConnectToSimVar(simVars...)
if err != nil {
return nil, err
}
cInterface := make(chan interface{})
go func() {
for {
cInterface <- SimVarAssignInterface(iFace, <-csimVars)
}
}()
return cInterface, nil
}
func (esc *EasySimConnect) SetSimVarInterfaceInSim(iFace interface{}) error {
simvars, err := SimVarGenerator(iFace)
if err != nil {
return err
}
InterfaceAssignSimVar(simvars, iFace)
for _, simvar := range simvars {
esc.SetSimObject(simvar)
}
return nil
}
// SetSimObject edit the SimVar in the simulator
func (esc *EasySimConnect) SetSimObject(simVar SimVar) {
defineID := uint32(1 << 30)
err, _ := esc.sc.AddToDataDefinition(defineID, simVar.Name, simVar.getUnitForDataDefinition(), simVar.GetDatumType(), 0, 0)
if err != nil {
esc.logf(LogInfo, "Error add SimVar ( %s ) in AddToDataDefinition error : %#v", simVar.Name, err)
return
}
//esc.listSimVar = append(esc.listSimVar, []*SimVar{&simVar})
err, _ = esc.sc.SetDataOnSimObject(defineID, SIMCONNECT_OBJECT_ID_USER, 0, 0, uint32(len(simVar.data)), simVar.data)
if err != nil {
esc.logf(LogInfo, "Error add SimVar ( %s ) in SetDataOnSimObject error : %#v", simVar.Name, err)
return
}
err, _ = esc.sc.ClearDataDefinition(uint32(defineID))
if err != nil {
esc.logf(LogInfo, "Error add SimVar ( %s ) in ClearDataDefinition error : %#v", simVar.Name, err)
return
}
}
func (esc *EasySimConnect) connectSysEvent(name SystemEvent, cb func(interface{})) {
esc.indexEvent++
esc.listEvent[esc.indexEvent] = cb
err, _ := esc.sc.SubscribeToSystemEvent(uint32(esc.indexEvent), name)
if err != nil {
esc.logf(LogInfo, "Error connect to Event %s in ConnectSysEventCrashed error : %#v", name, err)
}
}
// ConnectSysEventCrashed Request a notification if the user aircraft crashes.
func (esc *EasySimConnect) ConnectSysEventCrashed() <-chan bool {
c := make(chan bool)
esc.connectSysEvent(SystemEventCrashed, func(data interface{}) {
c <- true
})
return c
}
// ConnectSysEventCrashReset Request a notification when the crash cut-scene has completed.
func (esc *EasySimConnect) ConnectSysEventCrashReset() <-chan bool {
c := make(chan bool)
esc.connectSysEvent(SystemEventCrashReset, func(data interface{}) {
c <- true
})
return c
}
// ConnectSysEventPause Request notifications when the flight is paused or unpaused, and also immediately returns the current pause state (1 = paused or 0 = unpaused). The state is returned in the dwData parameter.
func (esc *EasySimConnect) ConnectSysEventPause() <-chan bool {
c := make(chan bool)
esc.connectSysEvent(SystemEventPause, func(data interface{}) {
event := data.(SIMCONNECT_RECV_EVENT)
c <- event.dwData > 0
})
return c
}
// ConnectSysEventPaused Request a notification when the flight is paused.
func (esc *EasySimConnect) ConnectSysEventPaused() <-chan bool {
c := make(chan bool)
esc.connectSysEvent(SystemEventPaused, func(data interface{}) {
c <- true
})
return c
}
// ConnectSysEventSim Request a notification when Sim start and stop.
func (esc *EasySimConnect) ConnectSysEventSim() <-chan bool {
c := make(chan bool)
esc.connectSysEvent(SystemEventSim, func(data interface{}) {
event := data.(SIMCONNECT_RECV_EVENT)
c <- event.dwData > 0
})
return c
}
// ConnectSysEventFlightPlanDeactivated Request a notification when the active flight plan is de-activated.
func (esc *EasySimConnect) ConnectSysEventFlightPlanDeactivated() <-chan bool {
c := make(chan bool)
esc.connectSysEvent(SystemEventFlightPlanDeactivated, func(data interface{}) {
c <- true
})
return c
}
// ConnectSysEventAircraftLoaded Request a notification when the aircraft flight dynamics file is changed. These files have a .AIR extension. The filename is returned in a string.
func (esc *EasySimConnect) ConnectSysEventAircraftLoaded() <-chan string {
c := make(chan string)
esc.connectSysEvent(SystemEventAircraftLoaded, func(data interface{}) {
event := data.(SIMCONNECT_RECV_EVENT_FILENAME)
c <- convStrToGoString(event.szFileName[:])
})
return c
}
// ConnectSysEventFlightLoaded Request a notification when a flight is loaded. Note that when a flight is ended, a default flight is typically loaded, so these events will occur when flights and missions are started and finished. The filename of the flight loaded is returned in a string
func (esc *EasySimConnect) ConnectSysEventFlightLoaded() <-chan string {
c := make(chan string)
esc.connectSysEvent(SystemEventFlightLoaded, func(data interface{}) {
event := data.(SIMCONNECT_RECV_EVENT_FILENAME)
c <- convStrToGoString(event.szFileName[:])
})
return c
}
// ConnectSysEventFlightSaved Request a notification when a flight is saved correctly. The filename of the flight saved is returned in a string
func (esc *EasySimConnect) ConnectSysEventFlightSaved() <-chan string {
c := make(chan string)
esc.connectSysEvent(SystemEventFlightSaved, func(data interface{}) {
event := data.(SIMCONNECT_RECV_EVENT_FILENAME)
c <- convStrToGoString(event.szFileName[:])
})
return c
}
// ConnectSysEventFlightPlanActivated Request a notification when a new flight plan is activated. The filename of the activated flight plan is returned in a string.
func (esc *EasySimConnect) ConnectSysEventFlightPlanActivated() <-chan string {
c := make(chan string)
esc.connectSysEvent(SystemEventFlightPlanActivated, func(data interface{}) {
event := data.(SIMCONNECT_RECV_EVENT_FILENAME)
c <- convStrToGoString(event.szFileName[:])
})
return c
}
// ShowText display a text on the screen in the simulator.
//
// ime is in second and return chan a confirmation for the simulator
func (esc *EasySimConnect) ShowText(str string, time float32, color PrintColor) (<-chan int, error) {
cReturn := make(chan int)
esc.indexEvent++
esc.listEvent[esc.indexEvent] = func(data interface{}) {
cReturn <- int(data.(SIMCONNECT_RECV_EVENT).dwData)
}
err, _ := esc.sc.Text(uint32(color), time, esc.indexEvent, str)
return cReturn, err
}
func (esc *EasySimConnect) runSimEvent(simEvent SimEvent) {
esc.sc.TransmitClientEvent(SIMCONNECT_OBJECT_ID_USER, simEvent.eventID, simEvent.Value, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY)
}
// NewSimEvent return new instance of SimEvent and you can run SimEvent.Run()
func (esc *EasySimConnect) NewSimEvent(simEventStr KeySimEvent) SimEvent {
instance, found := esc.listSimEvent[simEventStr]
if found {
return instance
}
esc.indexEvent++
c := make(chan int32)
simEvent := SimEvent{
simEventStr,
0,
esc.runSimEvent,
c,
esc.indexEvent,
}
esc.listEvent[esc.indexEvent] = func(data interface{}) {
recv := data.(SIMCONNECT_RECV_EVENT)
c <- int32(recv.dwData)
}
esc.sc.MapClientEventToSimEvent(esc.indexEvent, string(simEventStr))
esc.sc.AddClientEventToNotificationGroup(0, esc.indexEvent, false)
esc.sc.SetNotificationGroupPriority(0, SIMCONNECT_GROUP_PRIORITY_HIGHEST)
esc.listSimEvent[simEventStr] = simEvent
return simEvent
}
// SimEvent Use for generate action in the simulator
type SimEvent struct {
Mapping KeySimEvent
Value int
run func(simEvent SimEvent)
cb <-chan int32
eventID uint32
}
// Run return chan bool when receive the event is finish
func (s SimEvent) Run() <-chan int32 {
s.run(s)
return s.cb
}
// RunWithValue return chan bool when receive the event is finish
func (s SimEvent) RunWithValue(value int) <-chan int32 {
s.Value = value
return s.Run()
}