-
Notifications
You must be signed in to change notification settings - Fork 4
/
panel.go
330 lines (299 loc) · 7.42 KB
/
panel.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
// Package fpanels provides an interface to Logitech/Saitek flight panels.
//
// Use the New*Panel() functions to create an instance of the specific panel
// type. When you are done, call the panel's Close() function.
package fpanels
import (
"errors"
"strings"
"sync"
"github.com/google/gousb"
)
// USB vendor and product IDs
const (
USBVendorPanel = 0x06a3
USBProductRadio = 0x0d05
USBProductMulti = 0x0d06
USBProductSwitch = 0x0d67
)
// SwitchID identifies a switch on a panel
type SwitchID uint
// PanelID identifies the panel type
type PanelID int
// PanelIDs
const (
Radio PanelID = iota
Multi
Switch
)
// Panel is the base struct for all panels
type panel struct {
ctx *gousb.Context
device *gousb.Device
intf *gousb.Interface
inEndpoint *gousb.InEndpoint
displayState []byte
displayMutex sync.Mutex
displayCond *sync.Cond
id PanelID
switches PanelSwitches
displayDirty bool
intfDone func()
connected bool
quit bool
wg sync.WaitGroup
switchCh chan SwitchState
}
// SwitchState contains the state of a switch on a panel
type SwitchState struct {
Panel PanelID
Switch SwitchID
On bool
}
// PanelSwitches is the state of all switches on a panel, one bit per switch
type PanelSwitches uint32
// DisplayID identifies a display on a panel
type DisplayID uint
// StringDisplayer provides an interface to panels that can display strings
type StringDisplayer interface {
DisplayString(display DisplayID, s string)
}
// LEDDisplayer priovides an interface to panels that has LEDs
type LEDDisplayer interface {
LEDs(leds byte)
LEDsOn(leds byte)
LEDsOff(leds byte)
LEDsOnOff(leds byte, val float64)
}
// PanelIDMap maps a panel Id string to a PanelID
var PanelIDMap = map[string]PanelID{
"RADIO": Radio,
"MULTI": Multi,
"SWITCH": Switch,
}
// PanelIDString maps a panel string to a PanelID. The string s is case insensitive.
func PanelIDString(s string) (PanelID, error) {
s = strings.ToUpper(s)
p, ok := PanelIDMap[s]
if !ok {
return 0, errors.New("Unknown panel type")
}
return p, nil
}
// SwitchIDMap maps a switch ID string to a SwitchID
var SwitchIDMap = map[string]SwitchID{
// radio
"COM1_1": Rot1COM1,
"COM2_1": Rot1COM2,
"NAV1_1": Rot1NAV1,
"NAV2_1": Rot1NAV2,
"ADF_1": Rot1ADF,
"DME_1": Rot1DME,
"XPDR_1": Rot1XPDR,
"COM1_2": Rot2Com1,
"COM2_2": Rot2Com2,
"NAV1_2": Rot2NAV1,
"NAV2_2": Rot2NAV2,
"ADF_2": Rot2ADF,
"DME_2": Rot2DME,
"XPDR_2": Rot2XPDR,
"ACT_1": SwAct1,
"ACT_2": SwAct2,
"ENC1_CW_1": Enc1CW1,
"ENC1_CCW_1": Enc1CCW1,
"ENC2_CW_1": Enc2CW1,
"ENC2_CCW_1": Enc2CCW1,
"ENC1_CW_2": Enc1CW2,
"ENC1_CCW_2": Enc1CCW2,
"ENC2_CW_2": Enc2CW2,
"ENC2_CCW_2": Enc2CCW2,
// multi
"ALT": RotALT,
"VS": RotVS,
"IAS": RotIAS,
"HDG": RotHDG,
"CRS": RotCRS,
"ENC_CW": EncCW,
"ENC_CCW": EncCCW,
"BTN_AP": BtnAP,
"BTN_HDG": BtnHDG,
"BTN_NAV": BtnNAV,
"BTN_IAS": BtnIAS,
"BTN_ALT": BtnALT,
"BTN_VS": BtnVS,
"BTN_APR": BtnAPR,
"BTN_REV": BtnREV,
"AUTO_THROTTLE": AutoThrottle,
"FLAPS_UP": FlapsUp,
"FLAPS_DOWN": FlapsDown,
"TRIM_DOWN": TrimDown,
"TRIM_UP": TrimUp,
// switch
"BAT": SwBat,
"ALTERNATOR": SwAlternator,
"AVIONICS": SwAvionics,
"FUEL": SwFuel,
"DEICE": SwDeice,
"PITOT": SwPitot,
"COWL": SwCowl,
"PANEL": SwPanel,
"BEACON": SwBeacon,
"NAV": SwNav,
"STROBE": SwStrobe,
"TAXI": SwTaxi,
"LANDING": SwLanding,
"ENG_OFF": RotOff,
"ALT_R": RotR,
"ALT_L": RotL,
"ALT_BOTH": RotBoth,
"ENG_START": RotStart,
"GEAR_UP": GearUp,
"GEAR_DOWN": GearDown,
}
// LEDMap maps a LED Id string to the corresponding LED bits
var LEDMap = map[string]byte{
// switch
"N_GREEN": LEDNGreen,
"L_GREEN": LEDLGreen,
"R_GREEN": LEDRGreen,
"N_RED": LEDNRed,
"L_RED": LEDLRed,
"R_RED": LEDRRed,
"N_YELLOW": LEDNGreen | LEDNRed,
"L_YELLOW": LEDLGreen | LEDLRed,
"R_YELLOW": LEDRGreen | LEDRRed,
// multi
"LED_AP": LEDAP,
"LED_HDG": LEDHDG,
"LED_NAV": LEDNAV,
"LED_IAS": LEDIAS,
"LED_ALT": LEDALT,
"LED_VS": LEDVS,
"LED_APR": LEDAPR,
"LED_REV": LEDREV,
}
// DisplayMap maps the display names to a DisplayID
var DisplayMap = map[string]DisplayID{
// radio
"ACTIVE_1": Display1Active,
"STANDBY_1": Display1Standby,
"ACTIVE_2": Display2Active,
"STANDBY_2": Display2Standby,
// multi
"ROW_1": Row1,
"ROW_2": Row2,
}
// SwitchIDString maps a Switch ID string to a SwitchID. The ID string s
// is case insensitive.
func SwitchIDString(s string) (SwitchID, error) {
s = strings.ToUpper(s)
p, ok := SwitchIDMap[s]
if !ok {
return 0, errors.New("Unknown switch")
}
return p, nil
}
// LEDString maps a LED name to the corresponding LED bits. The string s
// is case insensitive.
func LEDString(s string) (byte, error) {
s = strings.ToUpper(s)
l, ok := LEDMap[s]
if !ok {
return 0, errors.New("Unknown LED")
}
return l, nil
}
// DisplayIDString maps a Display name to the DisplayID. The string s
// is case insesitive.
func DisplayIDString(s string) (DisplayID, error) {
s = strings.ToUpper(s)
d, ok := DisplayMap[s]
if !ok {
return 0, errors.New("Unknown display")
}
return d, nil
}
// IsSet returns true if the switch id is set.
func (switches PanelSwitches) IsSet(id SwitchID) bool {
return uint32(switches)&1<<uint32(id) != 0
}
// SwitchState returns the statee of the switch with ID id, 0 or 1
func (switches PanelSwitches) SwitchState(id SwitchID) uint {
return uint((uint32(switches) >> uint32(id)) & 1)
}
func (panel *panel) readSwitches() error {
var data [3]byte
var state uint32
var newState uint32
stream, err := panel.inEndpoint.NewStream(3, 1)
if err != nil {
return err
}
defer stream.Close()
for {
_, err := stream.Read(data[:])
if err != nil {
return err
}
newState = uint32(data[0]) | uint32(data[1])<<8 | uint32(data[2])<<16
changed := state ^ newState
state = newState
panel.switches = PanelSwitches(state)
for i := SwitchID(0); i < 24; i++ {
if (changed>>i)&1 == 1 {
val := uint(state >> i & 1)
//if val == 0 && panel.noZeroSwitch(i) {
// continue
//}
select {
case panel.switchCh <- SwitchState{panel.ID(), i, val == 1}:
default:
}
}
}
}
}
func (panel *panel) Close() {
panel.displayMutex.Lock()
panel.quit = true
panel.displayMutex.Unlock()
// FIX: Stop threads
if panel.intfDone != nil {
panel.intfDone()
}
if panel.device != nil {
panel.device.Close()
}
if panel.ctx != nil {
panel.ctx.Close()
}
}
func (panel *panel) IsSwitchSet(id SwitchID) bool {
return panel.switches.IsSet(id)
}
func (panel *panel) ID() PanelID {
return panel.id
}
func (panel *panel) refreshDisplay() {
tmpBuf := make([]byte, len(panel.displayState))
for {
panel.displayMutex.Lock()
for !panel.displayDirty {
panel.displayCond.Wait()
}
// 0x09 is REQUEST_SET_CONFIGURATION
// 0x0300 is:
// 0x03 HID_REPORT_TYPE_FEATURE
// 0x00 Report ID 0
copy(tmpBuf, panel.displayState)
panel.displayDirty = false
panel.displayMutex.Unlock()
panel.device.Control(gousb.ControlOut|gousb.ControlClass|gousb.ControlInterface, 0x09,
0x0300, 0x00, tmpBuf)
// FIX: Check if Control() returns an error and return it somehow or exit
}
}
// SwitchCh returns a channel for switch events
func (panel *panel) SwitchCh() chan SwitchState {
return panel.switchCh
}