# helpers for working with GoFlight input devices # map decimal digits 0..9 to standard 7-segment LCD pattern var translateDigitToSevenSegment = [0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x67]; var formatFrequencyMHz = func(freqMhz, fieldWidth) { return bytesForString(sprintf("%.3f", freqMhz), fieldWidth); } var formatFrequencyKHz = func(freqKhz, fieldWidth) { return bytesForString(sprintf("%6.2f", freqKhz), fieldWidth); } var bytesForString = func(s, fieldWidth) { var padCount = fieldWidth - size(s); var r = ""; while (padCount > 0) { r ~= chr(0); padCount -=1; } for (var i=0; i < size(s); i += 1) { if (s[i] == `.`) { # set the high bit to correspond to the decimal var lastIndex = size(r) - 1; r[lastIndex] = r[lastIndex] + 0x80; } else if (s[i] == ` `) { # spaces r ~= chr(0); } else if (s[i] == `-`) { # negative r ~= chr(0x40); } else { var digitCode = s[i] - `0`; r ~= chr(translateDigitToSevenSegment[digitCode]); } } return r; } # TEST # STBY # OFF # XPDR # TA # TA/RA var translateTo14Segment = { 32: [0x0, 0x0], # space 65: [0x22, 0x37], # A 66: [0x0A, 0x8f], 67: [0x00, 0x39], 68: [0x08, 0x8f], # uppercase D 69: [0x22, 0x39], 70: [0x20, 0x31], # F 77: [0x00, 0x00], # upper M 78: [0x00, 0x00], # upper N 79: [0x00, 0x3f], # upper O 80: [0x22, 0x33], # upper P 82: [0x26, 0x33], # upper R 83: [0x22, 0x2d], # upper S 84: [0x08, 0x81], # T 88: [0x15, 0x40], # X 89: [0x09, 0x40] # Y }; var formatFourteenSegment = func(s, fieldWidth) { var r = []; for (var i=0; i < size(s); i += 1) { var ch = s[i]; if (!contains(translateTo14Segment, ch)) { debug.dump('No 14 segment mapping for:', ch); } else { var t = translateTo14Segment[s[i]]; append(r, t[0]); append(r, t[1]); } } return r; } var reverseBytes = func(bytes) { var r=[]; for (var i = size(bytes) - 1; i >=0; i -=1) { append(r, bytes[i]); } return r; } var MFRController = { new: func(prefix) { var m = { parents: [MFRController] }; # m._hideTimer = maketimer(m.DELAY, m, Tooltip._hideTimeout); # m._hideTimer.singleShot = 1; return m; } }; var mcp = { init: func() { me._speedKnotsProp = props.globals.getNode("/autopilot/settings/target-speed-kt", 1); me._speedMachProp = props.globals.getNode("/autopilot/settings/target-speed-mach", 1); me._altitudeFtProp = props.globals.getNode("/autopilot/settings/target-altitude-ft", 1); me._vsFPMProp = props.globals.getNode("/autopilot/settings/vertical-speed-fpm", 1); me._headingProp = props.globals.getNode("/autopilot/settings/heading-bug-deg", 1); me._course1Prop = props.globals.getNode("/instrumentation/nav[0]/radials/selected-deg", 1); me._course2Prop = props.globals.getNode("/instrumentation/nav[1]/radials/selected-deg", 1); me._useMach = 0; me._refreshProp = props.globals.getNode("/input/goflight/mcp/refresh", 1); me._refreshHeadingProp = props.globals.getNode("/input/goflight/mcp/refresh-headings", 1); me._blankVSWindow = props.globals.getNode("/input/goflight/mcp/blank-vs-window", 1); me._ledProps = []; for (var l=0; l<4; l+=1) { var node = props.globals.getNode("/input/goflight/mcp/led[" ~ l ~ "]", 1); node.setIntValue(0); append(me._ledProps, node); } setlistener(me._speedKnotsProp, func { me.doRefresh(); } ); setlistener(me._speedMachProp, func { me.doRefresh(); }); setlistener(me._altitudeFtProp, func { me.doRefresh(); }); setlistener(me._vsFPMProp, func { me.doRefresh(); }); setlistener(me._blankVSWindow, func { me.doRefresh(); }); setlistener(me._headingProp, func { me.doRefreshHeading(); }); setlistener(me._course1Prop, func { me.doRefreshHeading(); }); setlistener(me._course2Prop, func { me.doRefreshHeading(); }); me.doRefresh(); me.doRefreshHeading(); print("GoFlight MCP init done"); }, setAltitudeFtProp: func(path) { me._altitudeFtProp = props.globals.getNode(path, 1); setlistener(me._altitudeFtProp, func { me.doRefresh(); }); me.doRefresh(); }, doRefresh: func() { me._refreshProp.setIntValue(0); }, doRefreshHeading: func() { me._refreshHeadingProp.setIntValue(0); }, setMachMode: func(useMach) { me._useMach = useMach; me.doRefresh(); }, altitudeData: func() { # if window is blanked, return empty data var alt = me._altitudeFtProp.getValue(); return bytesForString(sprintf("%d", alt), 5); }, vsData: func() { # if window is blanked, return empty data if (me._blankVSWindow.getValue()) { return bytesForString(" ", 5); } var vs = me._vsFPMProp.getValue(); return bytesForString(sprintf("%d", vs), 5); }, speedData: func() { if (me._useMach) { var mach = me._speedMachProp.getValue(); return bytesForString(sprintf("%0.3f ", mach), 5); } var spd = me._speedKnotsProp.getValue(); return bytesForString(sprintf("%d", spd), 5); }, adjustSpeed: func(val) { if (me._useMach) { var mach = me._speedMachProp.getValue(); me._speedMachProp.setDoubleValue(mach + (val * 0.01)); return; } var spd = me._speedKnotsProp.getValue(); me._speedKnotsProp.setIntValue(spd + val); }, adjustAltitude: func(val) { var alt = me._altitudeFtProp.getValue(); me._altitudeFtProp.setIntValue(alt + val); }, headingData: func() { var h = me._headingProp.getValue(); return bytesForString(sprintf("%0d", h), 3); }, course1Data: func() { var h = me._course1Prop.getValue(); return bytesForString(sprintf("%0d", h), 3); }, course2Data: func() { var h = me._course2Prop.getValue(); return bytesForString(sprintf("%0d", h), 3); }, _ledNames: { 'SPEED': [1, 0], 'LVL-CHG': [1, 1], 'HDG-SEL': [1, 2], 'APP': [1,3], 'ALT-HLD': [1,4], 'V/S': [1,5], 'F/O F/D': [1,7], # bank 2 'CWS A': [2,1], 'CWS B': [2,2], 'CAP F/D': [2,6], 'N1': [2, 7], # bank 3 'VNAV': [3,0], 'LNAV': [3,1], 'CMD A': [3,2], 'CMD B': [3,3], 'A/T ARM': [3,4], 'VOR-LOC': [3,7], }, watchPropertyForLED: func(prop, ledName) { if (!contains(me._ledNames, ledName)) { printlog('warn', 'Unknown GoFlight MCP LED:' ~ ledName); return; } var ledData = me._ledNames[ledName]; setlistener(prop, func(n) { me.setLED(ledData, n.getValue()); }); }, setLED: func(data, b) { # data is a pair of ints; the LED node and the bit within var node = me._ledProps[data[0]]; var ledBits = node.getValue(); node.setIntValue(bits.switch(ledBits, data[1], b)); } };