From f5f1c9043b52466d3d9414388ce5f45d82fe37f0 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 18 Oct 2019 21:15:53 +0200 Subject: [PATCH] ATIS v0.4.1 ATIS v0.4.1 - Fixed schedulers being deallocated by lua garbage collector. RADIOQUEUE - Added alias optional name. - Minor. --- Moose Development/Moose/Core/RadioQueue.lua | 128 ++++-- Moose Development/Moose/Ops/ATIS.lua | 440 ++++++++++---------- 2 files changed, 314 insertions(+), 254 deletions(-) diff --git a/Moose Development/Moose/Core/RadioQueue.lua b/Moose Development/Moose/Core/RadioQueue.lua index 4dd101b95..dabaf50f3 100644 --- a/Moose Development/Moose/Core/RadioQueue.lua +++ b/Moose Development/Moose/Core/RadioQueue.lua @@ -17,33 +17,45 @@ -- -- @type RADIOQUEUE -- @field #string ClassName Name of the class "RADIOQUEUE". +-- @field #boolean Debug Debug mode. More info. -- @field #string lid ID for dcs.log. -- @field #number frequency The radio frequency in Hz. -- @field #number modulation The radio modulation. Either radio.modulation.AM or radio.modulation.FM. -- @field Core.Scheduler#SCHEDULER scheduler The scheduler. -- @field #string RQid The radio queue scheduler ID. --- @field #table queue The queue of transmissions. +-- @field #table queue The queue of transmissions. +-- @field #string alias Name of the radio. +-- @field #number dt Time interval in seconds for checking the radio queue. +-- @field #number delay Time delay before starting the radio queue. -- @field #number Tlast Time (abs) when the last transmission finished. -- @field Core.Point#COORDINATE sendercoord Coordinate from where transmissions are broadcasted. -- @field #number sendername Name of the sending unit or static. --- @field Wrapper.Positionable#POSITIONABLE positionable The positionable to broadcast the message. +-- @field #boolean senderinit Set frequency was initialized. -- @field #number power Power of radio station in Watts. Default 100 W. -- @field #table numbers Table of number transmission parameters. +-- @field #boolean checking Scheduler is checking the radio queue. +-- @field #boolean schedonce Call ScheduleOnce instead of normal scheduler. -- @extends Core.Base#BASE RADIOQUEUE = { - ClassName = "RADIOQUEUE", - lid=nil, - frequency=nil, - modulation=nil, - scheduler=nil, - RQid=nil, - queue={}, - Tlast=nil, - sendercoord=nil, - sendername=nil, - positionable=nil, - power=100, - numbers={}, + ClassName = "RADIOQUEUE", + Debug = nil, + lid = nil, + frequency = nil, + modulation = nil, + scheduler = nil, + RQid = nil, + queue = {}, + alias = nil, + dt = nil, + delay = nil, + Tlast = nil, + sendercoord = nil, + sendername = nil, + senderinit = nil, + power = 100, + numbers = {}, + checking = nil, + schedonce = nil, } --- Radio queue transmission data. @@ -63,13 +75,16 @@ RADIOQUEUE = { -- @param #RADIOQUEUE self -- @param #number frequency The radio frequency in MHz. -- @param #number modulation (Optional) The radio modulation. Default radio.modulation.AM. +-- @param #string alias (Optional) Name of the radio queue. -- @return #RADIOQUEUE self The RADIOQUEUE object. -function RADIOQUEUE:New(frequency, modulation) +function RADIOQUEUE:New(frequency, modulation, alias) -- Inherit base local self=BASE:Inherit(self, BASE:New()) -- #RADIOQUEUE - self.lid="RADIOQUEUE | " + self.alias=alias or "My Radio" + + self.lid=string.format("RADIOQUEUE %s | ", self.alias) if frequency==nil then self:E(self.lid.."ERROR: No frequency specified as first parameter!") @@ -82,7 +97,7 @@ function RADIOQUEUE:New(frequency, modulation) -- Modulation. self.modulation=modulation or radio.modulation.AM - -- Scheduler + -- Scheduler. self.scheduler=SCHEDULER:New() self.scheduler:NoTrace() @@ -96,13 +111,19 @@ end -- @return #RADIOQUEUE self The RADIOQUEUE object. function RADIOQUEUE:Start(delay, dt) - delay=delay or 1 + self.delay=delay or 1 - dt=dt or 0.01 + self.dt=dt or 0.01 - self:I(self.lid..string.format("Starting RADIOQUEUE on Frequency %.2f MHz [modulation=%d] in %.1f seconds (dt=%.3f sec)", self.frequency/1000000, self.modulation, delay, dt)) + self:I(self.lid..string.format("Starting RADIOQUEUE %s on Frequency %.2f MHz [modulation=%d] in %.1f seconds (dt=%.3f sec)", self.alias, self.frequency/1000000, self.modulation, delay, dt)) - self.RQid=self.scheduler:Schedule(self, self._CheckRadioQueue, {}, delay, dt) + + if self.schedonce then + self:_CheckRadioQueueDelayed(self.delta) + else + --self.RQid=self.scheduler:Schedule(self, self._CheckRadioQueue, {}, delay, dt) + self.RQid=self.scheduler:Schedule(nil, RADIOQUEUE._CheckRadioQueue, {self}, delay, dt) + end return self end @@ -178,7 +199,10 @@ function RADIOQUEUE:AddTransmission(transmission) -- Add to queue. table.insert(self.queue, transmission) - --TODO: Start scheduler. + -- Start checking. + if self.schedonce and not self.checking then + self:_CheckRadioQueueDelayed() + end return self end @@ -298,13 +322,22 @@ function RADIOQUEUE:Broadcast(transmission) -- Broadcasting from aircraft. Only players tuned in to the right frequency will see the message. self:T(self.lid..string.format("Broadcasting from aircraft %s", sender:GetName())) - -- Command to set the Frequency for the transmission. - local commandFrequency={ - id="SetFrequency", - params={ - frequency=self.frequency, -- Frequency in Hz. - modulation=self.modulation, - }} + + if not self.senderinit then + + -- Command to set the Frequency for the transmission. + local commandFrequency={ + id="SetFrequency", + params={ + frequency=self.frequency, -- Frequency in Hz. + modulation=self.modulation, + }} + + -- Set commend for frequency + sender:SetCommand(commandFrequency) + + self.senderinit=true + end -- Command to tranmit the call. local commandTransmit={ @@ -314,15 +347,14 @@ function RADIOQUEUE:Broadcast(transmission) duration=transmission.subduration, subtitle=transmission.subtitle or "", loop=false, - }} - - -- Set commend for frequency - sender:SetCommand(commandFrequency) + }} -- Set command for radio transmission. sender:SetCommand(commandTransmit) - --MESSAGE:New(string.format("transmissing file %s duration=%.2f sec, subtitle=%s", filename, transmission.duration, transmission.subtitle or ""), 5, "RADIOQUEUE"):ToAll() + -- Debug message. + local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s", filename, self.frequency/1000000, transmission.duration, transmission.subtitle or "") + MESSAGE:New(text, 2, "RADIOQUEUE "..self.alias):ToAllIf(self.Debug) else @@ -349,20 +381,35 @@ function RADIOQUEUE:Broadcast(transmission) if vec3 then self:T("Sending") self:T( { filename = filename, vec3 = vec3, modulation = self.modulation, frequency = self.frequency, power = self.power } ) - --MESSAGE:New(string.format("transmissing file %s duration=%.2f sec, subtitle=%s", filename, transmission.duration, transmission.subtitle or ""), 5, "RADIOQUEUE (trigger)"):ToAll() + + -- Trigger transmission. trigger.action.radioTransmission(filename, vec3, self.modulation, false, self.frequency, self.power) + + -- Debug message. + local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s", filename, self.frequency/1000000, transmission.duration, transmission.subtitle or "") + MESSAGE:New(string.format(text, filename, transmission.duration, transmission.subtitle or ""), 5, "RADIOQUEUE "..self.alias):ToAllIf(self.Debug) end end end +--- Start checking the radio queue. +-- @param #RADIOQUEUE self +-- @param #number delay Delay in seconds before checking. +function RADIOQUEUE:_CheckRadioQueueDelayed(delay) + self.checking=true + self:ScheduleOnce(delay or self.dt, RADIOQUEUE._CheckRadioQueue, self) +end + --- Check radio queue for transmissions to be broadcasted. -- @param #RADIOQUEUE self function RADIOQUEUE:_CheckRadioQueue() + --env.info("FF check radio queue "..self.alias) -- Check if queue is empty. if #self.queue==0 then - --TODO: stop scheduler. + -- Queue is now empty. Nothing to else to do. + self.checking=false return end @@ -445,7 +492,12 @@ function RADIOQUEUE:_CheckRadioQueue() if remove then table.remove(self.queue, remove) end - + + -- Check queue. + if self.schedonce then + self:_CheckRadioQueueDelayed() + end + end --- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work. diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index b1f9df0d6..59aa48a24 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -1,5 +1,5 @@ --- **Ops** - (R2.5) - Automatic Terminal Information Service (ATIS). --- +-- -- === -- -- **Main Features:** @@ -29,9 +29,9 @@ -- === -- -- ## Missions: Example missions can be found [here](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20ATIS) --- +-- -- === --- +-- -- ## Sound files: Check out the pinned messages in the Moose discord #ops-atis channel. -- -- === @@ -64,7 +64,7 @@ -- @field #number subduration Duration how long subtitles are displayed in seconds. -- @field #boolean metric If true, use metric units. If false, use imperial (default). -- @field #boolean PmmHg If true, give pressure in millimeters of Mercury. Default is inHg for imperial and hecto Pascal (=mili Bars) for metric units. --- @field #boolean TDegF If true, give temperature in degrees Fahrenheit. Default is in degrees Celsius independent of chosen unit system. +-- @field #boolean TDegF If true, give temperature in degrees Fahrenheit. Default is in degrees Celsius independent of chosen unit system. -- @field #number zuludiff Time difference local vs. zulu in hours. -- @field #number magvar Magnetic declination/variation at the airport in degrees. -- @field #table ils Table of ILS frequencies (can be runway specific). @@ -80,88 +80,88 @@ -- @field #boolean windtrue Report true (from) heading of wind. Default is magnetic. -- @extends Core.Fsm#FSM ---- Be informed! +--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde -- -- === -- -- ![Banner Image](..\Presentations\ATIS\ATIS_Main.png) -- -- # The ATIS Concept --- +-- -- Automatic terminal information service, or ATIS, is a continuous broadcast of recorded aeronautical information in busier terminal areas, i.e. airports and their immediate surroundings. -- ATIS broadcasts contain essential information, such as current weather information, active runways, and any other information required by the pilots. --- +-- -- # DCS Limitations --- +-- -- Unfortunately, the DCS API only allow to get the temperature, pressure as well as wind direction and speed. Therefore, some other information such as cloud coverage, base and ceiling are not available -- when dynamic weather is used. --- +-- -- # Scripting --- +-- -- The lua script to create an ATIS at an airport is pretty easy: --- +-- -- -- ATIS at Batumi Airport on 143.00 MHz AM. -- atisBatumi=ATIS:New("Batumi", 143.00) -- atisBatumi:Start() --- +-- -- The @{#ATIS.New}(*airbasename*, *frequency*) creates a new ATIS object. The parameter *airbasename* is the name of the airbase or airport. Note that this has to be spelled exactly as in the DCS mission editor. -- The parameter *frequency* is the frequency the ATIS broadcasts in MHz. --- --- Broadcasting is started via the @{#ATIS.Start}() function. The start can be delayed by useing @{#ATIS.__Start}(*delay*), where *delay* is the delay in seconds. --- +-- +-- Broadcasting is started via the @{#ATIS.Start}() function. The start can be delayed by useing @{#ATIS.__Start}(*delay*), where *delay* is the delay in seconds. +-- -- ## Subtitles --- --- Currently, DCS allows for displaying subtitles of radio transmissions only from airborne units, i.e. airplanes and helicopters. Therefore, if you want to have subtitles, it is necessary to place an +-- +-- Currently, DCS allows for displaying subtitles of radio transmissions only from airborne units, i.e. airplanes and helicopters. Therefore, if you want to have subtitles, it is necessary to place an -- additonal aircraft on the ATIS airport and set it to uncontrolled. This unit can then function as a radio relay to transmit messages with subtitles. These subtitles will only be displayed, if the -- player has tuned in the correct ATIS frequency. --- +-- -- Radio transmissions via an airborne unit can be set via the @{#ATIS.SetRadioRelayUnitName}(*unitname*) function, where the parameter *unitname* is the name of the unit passed as string, e.g. --- +-- -- atisBatumi:SetRadioRelayUnitName("Radio Relay Batumi") --- +-- -- With a unit set in the mission editor with name "Radio Relay Batumi". --- +-- -- **Note** that you should use a different relay unit for each ATIS! --- +-- -- By default, subtitles are displayed for 10 seconds. This can be changed using @{#ATIS.SetSubtitleDuration}(*duration*) with *duration* being the duration in seconds. --- +-- -- ## Active Runway --- +-- -- By default, the currently active runway is determined automatically by analysing the wind direction. Therefore, you should obviously set the wind speed to be greater zero in your mission. --- +-- -- Note however, there are a few special cases, where automatic detection does not yield the correct or desired result. -- For example, there are airports with more than one runway facing in the same direction (usually denoted left and right). In this case, there is obviously no *unique* result depending on the wind vector. --- +-- -- If the automatic runway detection fails, the active runway can be specified manually in the script via the @{#ATIS.SetActiveRunway}(*runway*) function. -- The parameter *runway* is a string which can be used to specify the runway heading and, if applicable, whether the left or right runway is in use. --- +-- -- For example, setting runway 21L would be --- +-- -- atisNellis:SetActiveRunway("21L") --- +-- -- The script will examine the string and search for the characters "L" (left) and "R" (right). --- +-- -- If only left or right should be set and the direction determined by the wind vector, the runway heading can be left out, e.g. --- +-- -- atisAbuDhabi:SetActiveRunway("L") --- +-- -- The first two digits of the runway are determined by converting the *true* runway heading into its magnetic heading. The magnetic declination (or variation) is assumed to be constant on the given map. -- An explicit correction factor can be set via @{#ATIS.SetRunwayCorrectionMagnetic2True}. --- +-- -- ## Tower Frequencies --- +-- -- The tower frequency (or frequencies) can also be included in the ATIS information. However, there is no way to get these automatically. Therefore, it is necessary to manually specify them in the script via the -- @{#ATIS.SetTowerFrequencies}(*frequencies*) function. The parameter *frequencies* can be a plain number if only one frequency is necessary or it can be a table of frequencies. --- +-- -- ## Nav Aids --- +-- -- Frequencies or channels of navigation aids can be specified by the user and are then provided as additional information. Unfortunately, it is **not possible** to aquire this information via the DCS API -- we have access to. --- +-- -- As they say, all road lead to Rome but (for me) the easiest way to obtain the available nav aids data of an airport, is to start a mission and click on an airport symbol. --- +-- -- For example, the *AIRDROME DATA* for **Batumi** reads: --- +-- -- * **TACAN** *16X* - set via @{#ATIS.SetTACAN} -- * **VOR** *N/A* - set via @{#ATIS.SetVOR} -- * **RSBN** *N/A* - set via @{#ATIS.SetRSBN} @@ -171,11 +171,11 @@ -- * **PRMG** *N/A* - set via @{#ATIS.AddPRMG} -- * **OUTER NDB** *N/A* - set via @{#ATIS.AddNDBinner} -- * **INNER NDB** *N/A* - set via @{#ATIS.AddNDBinner} --- +-- -- ![Banner Image](..\Presentations\ATIS\NavAid_Batumi.png) --- +-- -- And the *AIRDROME DATA* for **Kobuleti** reads: --- +-- -- * **TACAN** *67X* - set via @{#ATIS.SetTACAN} -- * **VOR** *N/A* - set via @{#ATIS.SetVOR} -- * **RSBN** *N/A* - set via @{#ATIS.SetRSBN} @@ -183,70 +183,70 @@ -- * **Runways** *25* and *07* - automatic but can be set manually via @{#ATIS.SetRunwayHeadingsMagnetic} -- * **ILS** *111.50* for runway *07* - set via @{#ATIS.AddILS} -- * **PRMG** *N/A* - set via @{#ATIS.AddPRMG} --- * **OUTER NDB** *870.00* - set via @{#ATIS.AddNDBinner} +-- * **OUTER NDB** *870.00* - set via @{#ATIS.AddNDBouter} -- * **INNER NDB** *490.00* - set via @{#ATIS.AddNDBinner} --- +-- -- ![Banner Image](..\Presentations\ATIS\NavAid_Kobuleti.png) --- +-- -- ### TACAN --- +-- -- The TACtical Air Navigation system [(TACAN)](https://en.wikipedia.org/wiki/Tactical_air_navigation_system) channel can be set via the @{#ATIS.SetTACAN}(*channel*) function, where *channel* is the TACAN channel. Band is always assumed to be X-ray. --- +-- -- ### VOR --- +-- -- The Very high frequency Omni-directional Range [(VOR)](https://en.wikipedia.org/wiki/VHF_omnidirectional_range) frequency can be set via the @{#ATIS.SetVOR}(*frequency*) function, where *frequency* is the VOR frequency. --- +-- -- ### ILS --- +-- -- The Instrument Landing System [(ILS)](https://en.wikipedia.org/wiki/Instrument_landing_system) frequency can be set via the @{#ATIS.AddILS}(*frequency*, *runway*) function, where *frequency* is the ILS frequency and *runway* the two letter string of the corresponding runway, e.g. "31". -- If the parameter *runway* is omitted (nil) then the frequency is supposed to be valid for all runways of the airport. --- +-- -- ### NDB --- +-- -- Inner and outer Non-Directional (radio) Beacons [NDBs](https://en.wikipedia.org/wiki/Non-directional_beacon) can be set via the @{#ATIS.AddNDBinner}(*frequency*, *runway*) and @{#ATIS.AddNDBouter}(*frequency*, *runway*) functions, respectively. --- +-- -- In both cases, the parameter *frequency* is the NDB frequency and *runway* the two letter string of the corresponding runway, e.g. "31". -- If the parameter *runway* is omitted (nil) then the frequency is supposed to be valid for all runways of the airport. --- +-- -- ## RSBN --- +-- -- The RSBN channel can be set via the @{#ATIS.SetRSBN}(*channel*) function. --- +-- -- ## PRMG --- +-- -- The PRMG channel can be set via the @{#ATIS.AddPRMG}(*channel*, *runway*) function for each *runway*. --- +-- -- ## Unit System --- +-- -- By default, information is given in imperial units, i.e. wind speed in knots, pressure in inches of mercury, visibility in Nautical miles, etc. --- +-- -- If you prefer metric units, you can enable this via the @{#ATIS.SetMetricUnits}() function, --- +-- -- atisBatumi:SetMetricUnits() --- +-- -- With this, wind speed is given in meters per second, pressure in hecto Pascal (mbar), visibility in kilometers etc. --- +-- -- # Sound Files --- --- More than 180 individual sound files have been created using a text-to-speech program. All ATIS information is given with en-US accent. --- +-- +-- More than 180 individual sound files have been created using a text-to-speech program. All ATIS information is given with en-US accent. +-- -- Check out the pinned messages in the Moose discord #ops-atis channel. --- +-- -- To include the files, open the mission (.miz) file with, e.g., 7-zip. Then just drag-n-drop the file into the miz. --- +-- -- ![Banner Image](..\Presentations\ATIS\ATIS_SoundFolder.png) --- +-- -- **Note** that the default folder name is *ATIS Soundfiles/*. If you want to change it, you can use the @{#ATIS.SetSoundfilesPath}(*path*), where *path* is the path of the directory. This must end with a slash "/"! --- +-- -- # Examples --- +-- -- ## Caucasus: Batumi --- +-- -- -- ATIS Batumi Airport on 143.00 MHz AM. -- atisBatumi=ATIS:New(AIRBASE.Caucasus.Batumi, 143.00) -- atisBatumi:SetRadioRelayUnitName("Radio Relay Batumi") -- atisBatumi:Start() --- +-- -- ## Nevada: Nellis AFB -- -- -- ATIS Nellis AFB on 270.10 MHz AM. @@ -257,7 +257,7 @@ -- atisNellis:SetTACAN(12) -- atisNellis:AddILS(109.1, "21") -- atisNellis:Start() --- +-- -- ## Persian Gulf: Abu Dhabi International Airport -- -- -- ATIS Abu Dhabi International on 125.1 MHz AM. @@ -268,7 +268,7 @@ -- atisAbuDhabi:SetTowerFrequencies({250.5, 119.2}) -- atisAbuDhabi:SetVOR(114.25) -- atisAbuDhabi:Start() --- +-- -- -- @field #ATIS ATIS = { @@ -496,9 +496,14 @@ ATIS.Sound = { RSBNChannel={filename="RSBNChannel.ogg", duration=1.14}, } + +--- ATIS table containing all defined ATISes. +-- @field #table _ATIS +_ATIS={} + --- ATIS class version. -- @field #string version -ATIS.version="0.4.0" +ATIS.version="0.4.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -523,26 +528,29 @@ ATIS.version="0.4.0" -- @return #ATIS self function ATIS:New(airbasename, frequency, modulation) - -- Inherit everything from WAREHOUSE class. + -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #ATIS - + self.airbasename=airbasename self.airbase=AIRBASE:FindByName(airbasename) - + if self.airbase==nil then self:E("ERROR: Airbase %s for ATIS could not be found!", tostring(airbasename)) end - + -- Default freq and modulation. self.frequency=frequency or 143.00 self.modulation=modulation or 0 - + -- Get map. self.theatre=env.mission.theatre -- Set some string id for output to DCS.log file. self.lid=string.format("ATIS %s | ", self.airbasename) + -- This is just to hinder the garbage collector deallocating the ATIS object. + _ATIS[#_ATIS+1]=self + -- Defaults: self:SetSoundfilesPath() self:SetSubtitleDuration() @@ -558,7 +566,7 @@ function ATIS:New(airbasename, frequency, modulation) self:AddTransition("*", "Status", "*") -- Update status. self:AddTransition("*", "Broadcast", "*") -- Broadcast ATIS message. self:AddTransition("*", "CheckQueue", "*") -- Check if radio queue is empty. - + ------------------------ --- Pseudo Functions --- ------------------------ @@ -623,7 +631,7 @@ end function ATIS:SetTowerFrequencies(freqs) if type(freqs)=="table" then -- nothing to do - else + else freqs={freqs} end self.towerfrequency=freqs @@ -655,23 +663,23 @@ end function ATIS:SetRunwayHeadingsMagnetic(headings) if type(headings)=="table" then -- nothing to do - else + else headings={headings} end - + for _,heading in pairs(headings) do heading=tonumber(heading) table.insert(self.runwaymag, heading) - + local head2=heading-18 if head2<0 then head2=head2+36 end - + self:I(self.lid..string.format("Adding magnetic runway heading %d", head2)) table.insert(self.runwaymag, head2) end - + return self end @@ -701,7 +709,7 @@ function ATIS:SetImperialUnits() end --- Set pressure unit to millimeters of mercury (mmHg). --- Default is inHg for imperial and hPa (=mBar) for metric units. +-- Default is inHg for imperial and hPa (=mBar) for metric units. -- @param #ATIS self -- @return #ATIS self function ATIS:SetPressureMillimetersMercury() @@ -718,25 +726,25 @@ function ATIS:SetTemperatureFahrenheit() end --- Set magnetic declination/variation at the airport. --- +-- -- Default is per map: --- +-- -- * Caucasus +6 (East), year ~ 2011 -- * NTTR +12 (East), year ~ 2011 -- * Normandy -10 (West), year ~ 1944 -- * Persian Gulf +2 (East), year ~ 2011 --- +-- -- To get *true* from *magnetic* heading one has to add easterly or substract westerly variation, e.g --- --- A magnetic heading of 180° corresponds to a true heading of --- +-- +-- A magnetic heading of 180° corresponds to a true heading of +-- -- * 186° on the Caucaus map -- * 192° on the Nevada map -- * 170° on the Normany map -- * 182° on the Persian Gulf map --- +-- -- Likewise, to convert *magnetic* into *true* heading, one has to substract easterly and add westerly variation. --- +-- -- @param #ATIS self -- @param #number magvar Magnetic variation in degrees. Positive for easterly and negative for westerly variation. Default is magnatic declinaton of the used map, c.f. @{Utilities.UTils#UTILS.GetMagneticDeclination}. -- @return #ATIS self @@ -764,12 +772,12 @@ end --- Set time local difference with respect to Zulu time. -- Default is per map: --- +-- -- * Caucasus +4 -- * Nevada -7 -- * Normandy +1 -- * Persian Gulf +4 --- +-- -- @param #ATIS self -- @param #number delta Time difference in hours. -- @return #ATIS self @@ -881,16 +889,16 @@ function ATIS:onafterStart(From, Event, To) -- Info. self:I(self.lid..string.format("Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d", ATIS.version, self.airbasename, self.frequency, self.modulation)) - + -- Start radio queue. - self.radioqueue=RADIOQUEUE:New(self.frequency, self.modulation) - + self.radioqueue=RADIOQUEUE:New(self.frequency, self.modulation, string.format("ATIS %s", self.airbasename)) + -- Send coordinate is airbase coord. self.radioqueue:SetSenderCoordinate(self.airbase:GetCoordinate()) - + -- Set relay unit if we have one. self.radioqueue:SetSenderUnitName(self.relayunitname) - + -- Init numbers. self.radioqueue:SetDigit(0, ATIS.Sound.N0.filename, ATIS.Sound.N0.duration, self.soundpath) self.radioqueue:SetDigit(1, ATIS.Sound.N1.filename, ATIS.Sound.N1.duration, self.soundpath) @@ -902,7 +910,7 @@ function ATIS:onafterStart(From, Event, To) self.radioqueue:SetDigit(7, ATIS.Sound.N7.filename, ATIS.Sound.N7.duration, self.soundpath) self.radioqueue:SetDigit(8, ATIS.Sound.N8.filename, ATIS.Sound.N8.duration, self.soundpath) self.radioqueue:SetDigit(9, ATIS.Sound.N9.filename, ATIS.Sound.N9.duration, self.soundpath) - + -- Start radio queue. self.radioqueue:Start(1, 0.1) @@ -917,12 +925,12 @@ function ATIS:onafterStatus(From, Event, To) -- Get FSM state. local fsmstate=self:GetState() - + -- Info text. local text=string.format("State %s", fsmstate) self:I(self.lid..text) - - self:__Status(60) + + self:__Status(-60) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -939,9 +947,9 @@ function ATIS:onafterCheckQueue(From, Event, To) else self:T2(self.lid..string.format("Radio queue %d transmissions queued.", #self.radioqueue.queue)) end - + -- Check back in 5 seconds. - self:__CheckQueue(5) + self:__CheckQueue(-5) end --- Broadcast ATIS radio message. @@ -950,29 +958,29 @@ function ATIS:onafterBroadcast(From, Event, To) -- Get current coordinate. local coord=self.airbase:GetCoordinate() - + -- Get elevation. local height=coord:GetLandHeight()+10 - + ---------------- --- Pressure --- ---------------- - + -- Pressure in hPa. local qfe=coord:GetPressure(height) local qnh=coord:GetPressure(0) - + -- Convert to inHg. if self.PmmHg then qfe=UTILS.hPa2mmHg(qfe) qnh=UTILS.hPa2mmHg(qnh) - else + else if not self.metric then qfe=UTILS.hPa2inHg(qfe) qnh=UTILS.hPa2inHg(qnh) end end - + local QFE=UTILS.Split(string.format("%.2f", qfe), ".") local QNH=UTILS.Split(string.format("%.2f", qnh), ".") @@ -985,41 +993,41 @@ function ATIS:onafterBroadcast(From, Event, To) QNH=UTILS.Split(string.format("%.1f", qnh), ".") end end - + ------------ --- Wind --- ------------ - + -- Get wind direction and speed in m/s. local windFrom, windSpeed=coord:GetWind(height) - + -- Wind in magnetic or true. local magvar=self.magvar if self.windtrue then magvar=0 end - + local WINDFROM=string.format("%03d", windFrom-magvar) local WINDSPEED=string.format("%d", UTILS.MpsToKnots(windSpeed)) - + if self.metric then WINDSPEED=string.format("%d", windSpeed) end - + -------------- --- Runway --- -------------- - + -- Active runway data based on wind direction. local runact=self.airbase:GetActiveRunway(self.runwaym2t) - + -- Active runway "31". local runway=self:GetMagneticRunway(windFrom) or runact.idx - + -- Left or right in case there are two runways with the same heading. local rleft=false local rright=false - + -- Check if user explicitly specified a runway. if self.activerunway then local runwayno=self.activerunway:gsub("%D+", "") @@ -1029,12 +1037,12 @@ function ATIS:onafterBroadcast(From, Event, To) rleft=self.activerunway:lower():find("l") rright=self.activerunway:lower():find("r") end - + ------------ --- Time --- ------------ local time=timer.getAbsTime() - + -- Conversion to Zulu time. if self.zuludiff then -- User specified. @@ -1050,48 +1058,48 @@ function ATIS:onafterBroadcast(From, Event, To) time=time-1*60*60 -- Calais UTC+1 hour end end - + local clock=UTILS.SecondsToClock(time) local zulu=UTILS.Split(clock, ":") local ZULU=string.format("%s%s", zulu[1], zulu[2]) - - + + -- NATO time stamp. 0=Alfa, 1=Bravo, 2=Charlie, etc. local NATO=ATIS.Alphabet[tonumber(zulu[1])+1] - + -- Debug. self:T3(string.format("clock=%s", tostring(clock))) self:T3(string.format("zulu1=%s", tostring(zulu[1]))) self:T3(string.format("zulu2=%s", tostring(zulu[2]))) self:T3(string.format("ZULU =%s", tostring(ZULU))) self:T3(string.format("NATO =%s", tostring(NATO))) - + ------------------- --- Temperature --- ------------------- - + -- Temperature in °C (or °F). local temperature=coord:GetTemperature(height) - + -- Convert to °F. if self.TDegF then temperature=UTILS.CelciusToFarenheit(temperature) end - + local TEMPERATURE=string.format("%d", math.abs(temperature)) - + --------------- --- Weather --- --------------- - + -- Get mission weather info. Most of this is static. local clouds, visibility, turbulence, fog, dust, static=self:GetMissionWeather() - + -- Check that fog is actually "thick" enough to reach the airport. If an airport is in the mountains, fog might not affect it as it is measured from sea level. if fog and fog.thicknessUTILS.FeetToMeters(1500) then dust=nil @@ -1100,63 +1108,63 @@ function ATIS:onafterBroadcast(From, Event, To) ------------------ --- Visibility --- ------------------ - + -- Get min visibility. local visibilitymin=visibility - + if fog then if fog.visibility=9 then @@ -1183,31 +1191,31 @@ function ATIS:onafterBroadcast(From, Event, To) CLOUDSsub="No clouds" end end - + -------------------- --- Transmission --- -------------------- - + -- Subtitle local subtitle="" - + --Airbase name subtitle=string.format("%s", self.airbasename) if self.airbasename:find("AFB")==nil and self.airbasename:find("Airport")==nil and self.airbasename:find("Airstrip")==nil and self.airbasename:find("airfield")==nil and self.airbasename:find("AB")==nil then subtitle=subtitle.." Airport" end self.radioqueue:NewTransmission(string.format("%s/%s.ogg", self.theatre, self.airbasename), 3.0, self.soundpath, nil, nil, subtitle, self.subduration) - + -- Information tag subtitle=string.format("Information %s", NATO) self:Transmission(ATIS.Sound.Information, 0.5, subtitle) self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath) - + -- Zulu Time subtitle=string.format("%s Zulu Time", ZULU) self.radioqueue:Number2Transmission(ZULU, nil, 0.5) self:Transmission(ATIS.Sound.TimeZulu, 0.2, subtitle) - + -- Visibility if self.metric then subtitle=string.format("Visibility %s km", VISIBILITY) @@ -1219,9 +1227,9 @@ function ATIS:onafterBroadcast(From, Event, To) if self.metric then self:Transmission(ATIS.Sound.Kilometers, 0.2) else - self:Transmission(ATIS.Sound.NauticalMiles, 0.2) + self:Transmission(ATIS.Sound.NauticalMiles, 0.2) end - + -- Cloud base self:Transmission(CloudCover, 1.0, CLOUDSsub) if CLOUDBASE and static then @@ -1235,7 +1243,7 @@ function ATIS:onafterBroadcast(From, Event, To) if tonumber(CLOUDBASE1000)>0 then self.radioqueue:Number2Transmission(CLOUDBASE1000) self:Transmission(ATIS.Sound.Thousand, 0.1) - end + end if tonumber(CLOUDBASE0100)>0 then self.radioqueue:Number2Transmission(CLOUDBASE0100) self:Transmission(ATIS.Sound.Hundred, 0.1) @@ -1245,7 +1253,7 @@ function ATIS:onafterBroadcast(From, Event, To) if tonumber(CLOUDCEIL1000)>0 then self.radioqueue:Number2Transmission(CLOUDCEIL1000) self:Transmission(ATIS.Sound.Thousand, 0.1) - end + end if tonumber(CLOUDCEIL0100)>0 then self.radioqueue:Number2Transmission(CLOUDCEIL0100) self:Transmission(ATIS.Sound.Hundred, 0.1) @@ -1256,7 +1264,7 @@ function ATIS:onafterBroadcast(From, Event, To) self:Transmission(ATIS.Sound.Feet, 0.1) end end - + -- Weather phenomena local wp=false local wpsub="" @@ -1274,21 +1282,21 @@ function ATIS:onafterBroadcast(From, Event, To) wp=true elseif precepitation==4 then wpsub=wpsub.." snowstorm" - wp=true + wp=true end if fog then if wp then wpsub=wpsub.."," - end + end wpsub=wpsub.." fog" - wp=true + wp=true end if dust then if wp then wpsub=wpsub.."," - end + end wpsub=wpsub.." dust" - wp=true + wp=true end -- Actual output if wp then @@ -1307,14 +1315,14 @@ function ATIS:onafterBroadcast(From, Event, To) self:Transmission(ATIS.Sound.Fog, 0.5) end if dust then - self:Transmission(ATIS.Sound.Dust, 0.5) - end - end - + self:Transmission(ATIS.Sound.Dust, 0.5) + end + end + -- Altimeter QNH/QFE. if self.PmmHg then subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s mmHg", QNH[1], QNH[2], QFE[1], QFE[2]) - else + else if self.metric then subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s hPa", QNH[1], QNH[2], QFE[1], QFE[2]) else @@ -1339,7 +1347,7 @@ function ATIS:onafterBroadcast(From, Event, To) self:Transmission(ATIS.Sound.InchesOfMercury, 0.1) end end - + -- Temperature if self.TDegF then if temperature<0 then @@ -1357,14 +1365,14 @@ function ATIS:onafterBroadcast(From, Event, To) self:Transmission(ATIS.Sound.Temperature, 1.0, subtitle) if temperature<0 then self:Transmission(ATIS.Sound.Minus, 0.2) - end + end self.radioqueue:Number2Transmission(TEMPERATURE) if self.TDegF then self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2) else self:Transmission(ATIS.Sound.DegreesCelsius, 0.2) end - + -- Wind if self.metric then subtitle=string.format("Wind from %s at %s m/s", WINDFROM, WINDSPEED) @@ -1386,7 +1394,7 @@ function ATIS:onafterBroadcast(From, Event, To) if turbulence>0 then self:Transmission(ATIS.Sound.Gusting, 0.2) end - + -- Active runway. local subtitle=string.format("Active runway %s", runway) if rleft then @@ -1401,19 +1409,19 @@ function ATIS:onafterBroadcast(From, Event, To) elseif rright then self:Transmission(ATIS.Sound.Right, 0.2) end - + -- Runway length. if self.rwylength then - + local length=runact.length if not self.metric then length=UTILS.MetersToFeet(length) end - + -- Length in thousands and hundrets of ft/meters. local L1000, L0100=self:_GetThousandsAndHundreds(length) - - -- Subtitle. + + -- Subtitle. local subtitle=string.format("Runway length %d", length) if self.metric then subtitle=subtitle.." meters" @@ -1426,7 +1434,7 @@ function ATIS:onafterBroadcast(From, Event, To) if tonumber(L1000)>0 then self.radioqueue:Number2Transmission(L1000) self:Transmission(ATIS.Sound.Thousand, 0.1) - end + end if tonumber(L0100)>0 then self.radioqueue:Number2Transmission(L0100) self:Transmission(ATIS.Sound.Hundred, 0.1) @@ -1436,9 +1444,9 @@ function ATIS:onafterBroadcast(From, Event, To) else self:Transmission(ATIS.Sound.Feet, 0.1) end - + end - + -- Tower frequency. if self.towerfrequency then local freqs="" @@ -1452,42 +1460,42 @@ function ATIS:onafterBroadcast(From, Event, To) self:Transmission(ATIS.Sound.TowerFrequency, 1.0, subtitle) for _,freq in pairs(self.towerfrequency) do local f=string.format("%.3f", freq) - f=UTILS.Split(f, ".") - self.radioqueue:Number2Transmission(f[1], nil, 0.5) + f=UTILS.Split(f, ".") + self.radioqueue:Number2Transmission(f[1], nil, 0.5) if tonumber(f[2])>0 then - self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) + self:Transmission(ATIS.Sound.Decimal, 0.2) + self.radioqueue:Number2Transmission(f[2]) end self:Transmission(ATIS.Sound.MegaHertz, 0.2) - end + end end - + -- ILS local ils=self:GetNavPoint(self.ils, runway) if ils then subtitle=string.format("ILS frequency %.2f MHz", ils.frequency) self:Transmission(ATIS.Sound.ILSFrequency, 1.0, subtitle) local f=string.format("%.2f", ils.frequency) - f=UTILS.Split(f, ".") + f=UTILS.Split(f, ".") self.radioqueue:Number2Transmission(f[1], nil, 0.5) if tonumber(f[2])>0 then self:Transmission(ATIS.Sound.Decimal, 0.2) self.radioqueue:Number2Transmission(f[2]) end self:Transmission(ATIS.Sound.MegaHertz, 0.2) - end - + end + -- Outer NDB local ndb=self:GetNavPoint(self.ndbouter, runway) if ndb then subtitle=string.format("Outer NDB frequency %.2f MHz", ndb.frequency) self:Transmission(ATIS.Sound.OuterNDBFrequency, 1.0, subtitle) local f=string.format("%.2f", ndb.frequency) - f=UTILS.Split(f, ".") + f=UTILS.Split(f, ".") self.radioqueue:Number2Transmission(f[1], nil, 0.5) if tonumber(f[2])>0 then self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) + self.radioqueue:Number2Transmission(f[2]) end self:Transmission(ATIS.Sound.MegaHertz, 0.2) end @@ -1498,7 +1506,7 @@ function ATIS:onafterBroadcast(From, Event, To) subtitle=string.format("Inner NDB frequency %.2f MHz", ndb.frequency) self:Transmission(ATIS.Sound.InnerNDBFrequency, 1.0, subtitle) local f=string.format("%.2f", ndb.frequency) - f=UTILS.Split(f, ".") + f=UTILS.Split(f, ".") self.radioqueue:Number2Transmission(f[1], nil, 0.5) if tonumber(f[2])>0 then self:Transmission(ATIS.Sound.Decimal, 0.2) @@ -1506,13 +1514,13 @@ function ATIS:onafterBroadcast(From, Event, To) end self:Transmission(ATIS.Sound.MegaHertz, 0.2) end - + -- VOR if self.vor then subtitle=string.format("VOR frequency %.2f MHz", self.vor) self:Transmission(ATIS.Sound.VORFrequency, 1.0, subtitle) local f=string.format("%.2f", self.vor) - f=UTILS.Split(f, ".") + f=UTILS.Split(f, ".") self.radioqueue:Number2Transmission(f[1], nil, 0.5) if tonumber(f[2])>0 then self:Transmission(ATIS.Sound.Decimal, 0.2) @@ -1520,7 +1528,7 @@ function ATIS:onafterBroadcast(From, Event, To) end self:Transmission(ATIS.Sound.MegaHertz, 0.2) end - + -- TACAN if self.tacan then subtitle=string.format("TACAN channel %dX", self.tacan) @@ -1528,14 +1536,14 @@ function ATIS:onafterBroadcast(From, Event, To) self.radioqueue:Number2Transmission(tostring(self.tacan), nil, 0.2) self.radioqueue:NewTransmission("NATO Alphabet/Xray.ogg", 0.75, self.soundpath, nil, 0.2) end - + -- RSBN if self.rsbn then subtitle=string.format("RSBN channel %d", self.rsbn) self:Transmission(ATIS.Sound.RSBNChannel, 1.0, subtitle) - self.radioqueue:Number2Transmission(tostring(self.rsbn), nil, 0.2) + self.radioqueue:Number2Transmission(tostring(self.rsbn), nil, 0.2) end - + -- PRMG local ndb=self:GetNavPoint(self.prmg, runway) if ndb then @@ -1543,7 +1551,7 @@ function ATIS:onafterBroadcast(From, Event, To) self:Transmission(ATIS.Sound.PRMGChannel, 1.0, subtitle) self.radioqueue:Number2Transmission(tostring(ndb.frequency), nil, 0.5) end - + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1559,13 +1567,13 @@ function ATIS:GetMagneticRunway(windfrom) local diffmin=nil local runway=nil for _,heading in pairs(self.runwaymag) do - + local diff=UTILS.HdgDiff(windfrom, tonumber(heading)*10) if diffmin==nil or diff