# Copyright 2018 Stuart Buchanan # This file is part of FlightGear. # # FlightGear is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # FlightGear is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with FlightGear. If not, see . # # Surround Controller var SurroundController = { new : func (page, svg, pfd) { var obj = { parents : [ SurroundController ], _recipient : nil, _page : page, _pfd : pfd, _comselected : 1, _navselected : 1, _com1active : 0.0, _com1standby : 0.0, _com2active : 0.0, _com2standby : 0.0, _commvolume : 0.0, _nav1active : 0.0, _nav1standby : 0.0, _nav1radial : 0.0, _nav1_heading_deg : 0.0, _nav2active : 0.0, _nav2standby : 0.0, _nav2radial : 0.0, _nav2_heading_deg : 0.0, _navvolume : 0.0, _pressure_settings_inhg : 0.0, _selected_alt_ft : 0.0, _heading_bug_deg : 0.0, _heading_deg : 0.0, }; obj.RegisterWithEmesary(); return obj; }, del : func() { me.DeRegisterWithEmesary(); }, # Helper function to notify the Emesary bridge of a NavComData update. sendNavComDataNotification : func(data) { var notification = notifications.PFDEventNotification.new( "MFD", me._page.mfd.getDeviceID(), notifications.PFDEventNotification.NavComData, data); me.transmitter.NotifyAll(notification); }, # Helper function to notify the Emesary bridge of a FMSData update. sendFMSDataNotification : func(data) { var notification = notifications.PFDEventNotification.new( "MFD", me._page.mfd.getDeviceID(), notifications.PFDEventNotification.FMSData, data); me.transmitter.NotifyAll(notification); }, handleNavComData : func(data) { # Store off particularly important data for control. if (data["CommSelected"] != nil) me._comselected = data["CommSelected"]; if (data["NavSelected"] != nil) me._navselected = data["NavSelected"]; if (data["Comm1SelectedFreq"] != nil) me._com1active = data["Comm1SelectedFreq"]; if (data["Comm1StandbyFreq"] != nil) me._com1standby = data["Comm1StandbyFreq"]; if (data["Comm1Volume"] != nil and me._commvolume != data["Comm1Volume"]) me._commvolume = data["Comm1Volume"]; if (data["Comm2SelectedFreq"] != nil) me._com2active = data["Comm2SelectedFreq"]; if (data["Comm2StandbyFreq"] != nil) me._com2standby = data["Comm2StandbyFreq"]; if (data["Comm2Volume"] != nil and me._commvolume != data["Comm2Volume"]) me._commvolume = data["Comm2Volume"]; if (data["Nav1SelectedFreq"] != nil) me._nav1active = data["Nav1SelectedFreq"]; if (data["Nav1StandbyFreq"] != nil) me._nav1standby = data["Nav1StandbyFreq"]; if (data["Nav1RadialDeg"] != nil) me._nav1radial = data["Nav1RadialDeg"]; if (data["Nav1HeadingDeg"] != nil) me._nav1_heading_deg = data["Nav1HeadingDeg"]; if (data["Nav1Volume"] != nil and me._navvolume != data["Nav1Volume"]) me._navvolume = data["Nav1Volume"]; if (data["Nav2SelectedFreq"] != nil) me._nav2active = data["Nav2SelectedFreq"]; if (data["Nav2StandbyFreq"] != nil) me._nav2standby = data["Nav2StandbyFreq"]; if (data["Nav2RadialDeg"] != nil) me._nav2radial = data["Nav2RadialDeg"]; if (data["Nav2HeadingDeg"] != nil) me._nav2_heading_deg = data["Nav2HeadingDeg"]; if (data["Nav2Volume"] != nil and me._navvolume != data["Nav2Volume"]) me._navvolume = data["Nav2Volume"]; # pass through to the page me._page.handleNavComData(data); return emesary.Transmitter.ReceiptStatus_OK; }, handleFMSADCData : func(data) { if (data["ADCPressureSettingInHG"] != nil) me._pressure_settings_inhg = data["ADCPressureSettingInHG"]; if (data["FMSSelectedAlt"] != nil) me._selected_alt_ft = data["FMSSelectedAlt"]; if (data["FMSHeadingBug"] != nil) me._heading_bug_deg = data["FMSHeadingBug"]; if (data["ADCHeadingMagneticDeg"] != nil) me._heading_deg = data["ADCHeadingMagneticDeg"]; # Pass FMS and ADC data straight to the page to display in the header fields me._page.updateHeaderData(data); return emesary.Transmitter.ReceiptStatus_OK; }, handleSelectPageByID : func(notification) { me._page.goToPage(notification.Group, notification.Page); return emesary.Transmitter.ReceiptStatus_Finished; }, # # Handle the various COM and NAV controls at the top left and top right of the Fascia # handleNavVol : func (value) { var data={}; var incr_or_decr = (value > 0) ? 0.01 : -0.01; var vol = math.max(0,math.min(1,me._navvolume + incr_or_decr)); data["Nav1Volume"] = vol; data["Nav2Volume"] = vol; me.sendNavComDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, handleNavID : func (value) { var data={}; if (me._navselected == 1) { data["Nav1AudioID"] = value; } else { data["Nav1AudioID"] = value; } me.sendNavComDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, # Swap active and standby Nav frequencies. Note that we don't update internal state here - we # instead pass updated NavComData notification data which will be picked up by the underlying # updaters to map to properties, and this controller itself. handleNavFreqTransfer : func (value) { var data={}; if (me._navselected == 1) { data["Nav1SelectedFreq"] = me._nav1standby; data["Nav1StandbyFreq"] = me._nav1active; } else { data["Nav2SelectedFreq"] = me._nav2standby; data["Nav2StandbyFreq"] = me._nav2active; } me.sendNavComDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, # Outer Nav dial updates the integer portion of the selected standby # NAV frequency only, and wrap on limits - leaving the fractional part unchanged. handleNavOuter : func (value) { var incr_or_decr = (value > 0) ? 1000.0 : -1000.0; var data={}; # Determine the new value, wrapping within the limits. var datakey = ""; var freq = 0; var old_freq = 0; if (me._navselected == 1) { datakey = "Nav1StandbyFreq"; old_freq = me._nav1standby; } else { datakey = "Nav2StandbyFreq"; old_freq = me._nav2standby; } old_freq = math.round(old_freq * 1000); freq = old_freq + incr_or_decr; # Wrap if out of bounds if (freq > (fg1000.MAX_NAV_FREQ * 1000)) freq = freq - (fg1000.MAX_NAV_FREQ - fg1000.MIN_NAV_FREQ) * 1000; if (freq < (fg1000.MIN_NAV_FREQ * 1000)) freq = freq + (fg1000.MAX_NAV_FREQ - fg1000.MIN_NAV_FREQ) * 1000; # Convert back to a frequency to 3 decimal places. data[datakey] = sprintf("%.3f", freq/1000.0); me.sendNavComDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, # Inner Nav dial updates the fractional portion of the selected standby # NAV frequency only - leaving the integer part unchanged. Even if it # increments past 0.975. handleNavInner : func (value) { var incr_or_decr = (value > 0) ? 25 : -25; var data={}; # Determine the new value, wrapping within the limits. var datakey = ""; var freq = 0; var old_freq = 0; if (me._navselected == 1) { datakey = "Nav1StandbyFreq"; old_freq = me._nav1standby; } else { datakey = "Nav2StandbyFreq"; old_freq = me._nav2standby; } old_freq = math.round(old_freq * 1000); freq = old_freq + incr_or_decr; # Wrap on decimal by handling case where the integer part has changed if (int(old_freq/1000) < int(freq/1000)) freq = freq - 1000; if (int(old_freq/1000) > int(freq/1000)) freq = freq + 1000; data[datakey] = sprintf("%.3f", freq/1000.0); me.sendNavComDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, # Switch between Nav1 and Nav2. handleNavToggle : func () { var data={}; if (me._navselected == 1) { data["NavSelected"] = 2; } else { data["NavSelected"] = 1; } me.sendNavComDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, setNav : func(value) { var data={}; data["NavSelected"] = value; me.sendNavComDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, # Outer COM dial changes the integer value of the standby selected COM # frequency, wrapping on limits. Leaves the fractional part unchanged handleComOuter : func (value) { var incr_or_decr = (value > 0) ? 1000.0 : -1000.0; var data={}; # Determine the new value, wrapping within the limits. var datakey = ""; var freq = 0; var old_freq = 0; if (me._comselected == 1) { datakey = "Comm1StandbyFreq"; old_freq = me._com1standby; } else { datakey = "Comm2StandbyFreq"; old_freq = me._com2standby; } old_freq = math.round(old_freq * 1000); freq = old_freq + incr_or_decr; # Wrap if out of bounds if (freq > (fg1000.MAX_COM_FREQ * 1000)) freq = freq - (fg1000.MAX_COM_FREQ - fg1000.MIN_COM_FREQ) * 1000; if (freq < (fg1000.MIN_COM_FREQ * 1000)) freq = freq + (fg1000.MAX_COM_FREQ - fg1000.MIN_COM_FREQ) * 1000; # Convert back to a frequency to 3 decimal places. data[datakey] = sprintf("%.3f", freq/1000.0); me.sendNavComDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, # Inner COM dial changes the fractional part of the standby selected COM frequency, # wrapping on limits and leaving the integer part unchanged. handleComInner : func (value) { var incr_or_decr = (value > 0) ? 1 : -1; var data={}; var datakey = ""; var freq = 0; var old_freq = 0; if (me._comselected == 1) { datakey = "Comm1StandbyFreq"; old_freq = me._com1standby; } else { datakey = "Comm2StandbyFreq"; old_freq = me._com2standby; } old_freq = math.round(old_freq * 1000); var integer_part = int(old_freq / 1000) * 1000; var fractional_part = old_freq - integer_part; # 8.33kHz frequencies are complicated - we need to do a lookup to find # the current and next frequencies var idx = 0; for (var i=0; i < size(fg1000.COM_833_SPACING); i = i + 1) { if (math.round(fg1000.COM_833_SPACING[i] * 1000) == fractional_part) { idx = i; break; } } idx = math.mod(idx + incr_or_decr, size(fg1000.COM_833_SPACING)); freq = integer_part + fg1000.COM_833_SPACING[idx] * 1000; data[datakey] = sprintf("%.3f", freq/1000.0); me.sendNavComDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, # Switch between COM1 and COM2 handleComToggle : func (value) { var data={}; if (me._comselected == 1) { data["CommSelected"] = 2; } else { data["CommSelected"] = 1; } me.sendNavComDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, # Swap active and standby Com frequencies. Note that we don't update internal state here - we # instead pass updated NavComData notification data which will be picked up by the underlying # updaters to map to properties, and this controller itself. handleComFreqTransfer : func (value) { var data={}; if (me._comselected == 1) { data["Comm1SelectedFreq"] = me._com1standby; data["Comm1StandbyFreq"] = me._com1active; } else { data["Comm2SelectedFreq"] = me._com2standby; data["Comm2StandbyFreq"] = me._com2active; } me.sendNavComDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, # Auto-tunes the ACTIVE COM channel to 121.2 when pressed for 2 seconds handleComFreqTransferHold : func (value) { var data={}; if (me._comselected == 1) { data["Comm1SelectedFreq"] = 121.00; } else { data["Comm2SelectedFreq"] = 121.00; } me.sendNavComDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, handleComVol : func (value) { var data={}; var incr_or_decr = (value > 0) ? 0.01 : -0.01; var vol = math.max(0,math.min(1,me._commvolume + incr_or_decr)); data["Comm1Volume"] = vol; data["Comm2Volume"] = vol; me.sendNavComDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, handleComVolToggle : func (value) { }, handleBaro : func(value) { var incr_or_decr = (value > 0) ? 1 : -1; var press = me._pressure_settings_inhg + (incr_or_decr * 0.01); var data = {}; data["FMSPressureSettingInHG"] = sprintf("%.2f", press); me.sendFMSDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, handleCRS : func(value) { var incr_or_decr = (value > 0) ? 1 : -1; var data={}; if (me._navselected == 1) { data["Nav1RadialDeg"] = math.mod(me._nav1radial + incr_or_decr, 360); } else { data["Nav2RadialDeg"] = math.mod(me._nav2radial + incr_or_decr, 360); } me.sendNavComDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, handleCRSCenter : func(value) { var data = {}; if (me._navselected == 1) { data["Nav1RadialDeg"] = me._nav1_heading_deg; } else { data["Nav2RadialDeg"] = me._nav2_heading_deg; } me.sendNavComDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, handleAltInner : func(value) { var incr_or_decr = (value > 0) ? 1 : -1; var alt = int(me._selected_alt_ft + incr_or_decr * 100); if (alt < 0) alt = 0; var data = {}; data["FMSSelectedAlt"] = alt; me.sendFMSDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, handleAltOuter : func(value) { var incr_or_decr = (value > 0) ? 1 : -1; var alt = int(me._selected_alt_ft + incr_or_decr * 1000); if (alt < 0) alt = 0; var data = {}; data["FMSSelectedAlt"] = alt; me.sendFMSDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, handleHeading : func(value) { var incr_or_decr = (value > 0) ? 1 : -1; var hdg = me._heading_bug_deg + incr_or_decr; hdg = math.mod(hdg, 360); var data = {}; data["FMSHeadingBug"] = sprintf("%i", hdg); me.sendFMSDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, handleHeadingPress : func(value) { var data = {}; data["FMSHeadingBug"] = me._heading_deg; me.sendFMSDataNotification(data); return emesary.Transmitter.ReceiptStatus_Finished; }, # These methods are slightly unusual in that they are called by other # controllers when the CRSR is not active. Hence they aren't referenced # in the RegisterWithEmesary call below. # handleFMSOuter : func(val) { if (me._pfd) return emesary.Transmitter.ReceiptStatus_NotProcessed; if (me._page.isMenuVisible()) { # Change page group me._page.incrPageGroup(val); } me._page.showMenu(); return emesary.Transmitter.ReceiptStatus_Finished; }, handleFMSInner : func(val) { if (me._pfd) return emesary.Transmitter.ReceiptStatus_NotProcessed; if (me._page.isMenuVisible()) { # Change page group me._page.incrPage(val); } me._page.showMenu(); return emesary.Transmitter.ReceiptStatus_Finished; }, RegisterWithEmesary : func(transmitter = nil){ if (transmitter == nil) transmitter = emesary.GlobalTransmitter; if (me._recipient == nil){ me._recipient = emesary.Recipient.new("SurroundController_" ~ me._page.device.designation); var pfd_obj = me._page.device; var controller = me; me._recipient.Receive = func(notification) { # Note that in general we don't care about the device that the data comes from. if (notification.NotificationType == notifications.PFDEventNotification.DefaultType) { if (notification.Event_Id == notifications.PFDEventNotification.NavComData and notification.EventParameter != nil) { return controller.handleNavComData(notification.EventParameter); } if (((notification.Event_Id == notifications.PFDEventNotification.FMSData) or (notification.Event_Id == notifications.PFDEventNotification.ADCData) ) and notification.EventParameter != nil) { return controller.handleFMSADCData(notification.EventParameter); } if (notification.Device_Id == pfd_obj.device_id and notification.Event_Id == notifications.PFDEventNotification.SelectPageById) { return controller.handleSelectPageByID(notification.EventParameter); } } return emesary.Transmitter.ReceiptStatus_NotProcessed; }; } transmitter.Register(me._recipient); me.transmitter = transmitter; }, DeRegisterWithEmesary : func(transmitter = nil){ # remove registration from transmitter; but keep the recipient once it is created. if (me.transmitter != nil) me.transmitter.DeRegister(me._recipient); me.transmitter = nil; }, # Used by other pages to set the current standby NAV or COM frequency by pressing ENT setStandbyNavComFreq : func(value) { var data={}; # Determine whether this is NAV or COM based on the frequency itself if (value < fg1000.MAX_NAV_FREQ) { # Nav frequency if (value > fg1000.MAX_NAV_FREQ) return; if (value < fg1000.MIN_NAV_FREQ) return; # TODO: If we're in approach phase then this should update the Active # frequency if (me._navselected == 1) { data["Nav1StandbyFreq"] = value; } else { data["Nav2StandbyFreq"] = value; } } else { # COM frequency if (value > fg1000.MAX_COM_FREQ) return; if (value < fg1000.MIN_COM_FREQ) return; if (me._comselected == 1) { data["Comm1StandbyFreq"] = value; } else { data["Comm2StandbyFreq"] = value; } } me.sendNavComDataNotification(data); }, # Used by other pages to set the current standby NAV frequency by pressing ENT setStandbyNavFreq : func(value) { var data={}; if (value > fg1000.MAX_NAV_FREQ) return; if (value < fg1000.MIN_NAV_FREQ) return; if (me._navselected == 1) { data["Nav1StandbyFreq"] = value; } else { data["Nav2StandbyFreq"] = value; } me.sendNavComDataNotification(data); }, };