From f8b2ce39312d793deb1c11fbd65d82b8a1ba50cc Mon Sep 17 00:00:00 2001 From: Denis Freund Date: Mon, 6 Nov 2023 11:52:30 +0100 Subject: [PATCH] update(systems): change api client --- Cargo.toml | 23 - .../systems/datalink/router/src/vhf/VHF.ts | 329 ++++++++++++++ .../src/webinterfaces/HoppieConnector.ts | 4 +- .../src/webinterfaces/NXApiConnector.ts | 226 ++++++++++ .../systems/instruments/src/EFB/ATC/ATC.tsx | 320 ++++++++++++++ .../EFB/Dashboard/Widgets/WeatherWidget.tsx | 319 ++++++++++++++ .../EFB/Performance/Widgets/LandingWidget.tsx | 90 ++-- .../shared/src/AircraftVersionChecker.ts | 2 +- .../systems/datalink/router/src/vhf/VHF.ts | 329 ++++++++++++++ .../src/webinterfaces/HoppieConnector.ts | 4 +- .../src/webinterfaces/NXApiConnector.ts | 226 ++++++++++ .../systems/instruments/src/EFB/ATC/ATC.tsx | 320 ++++++++++++++ .../EFB/Dashboard/Widgets/WeatherWidget.tsx | 319 ++++++++++++++ .../EFB/Performance/Widgets/LandingWidget.tsx | 2 +- .../src/EFB/Settings/Pages/AtsuAocPage.tsx | 2 +- .../shared/src/AircraftVersionChecker.ts | 2 +- package-lock.json | 405 +++++------------- package.json | 2 +- 18 files changed, 2549 insertions(+), 375 deletions(-) delete mode 100644 Cargo.toml create mode 100644 hdw-a333x-common/src/systems/datalink/router/src/vhf/VHF.ts create mode 100644 hdw-a333x-common/src/systems/datalink/router/src/webinterfaces/NXApiConnector.ts create mode 100644 hdw-a333x/src/systems/instruments/src/EFB/ATC/ATC.tsx create mode 100644 hdw-a333x/src/systems/instruments/src/EFB/Dashboard/Widgets/WeatherWidget.tsx create mode 100644 hdw-a339x-common/src/systems/datalink/router/src/vhf/VHF.ts create mode 100644 hdw-a339x-common/src/systems/datalink/router/src/webinterfaces/NXApiConnector.ts create mode 100644 hdw-a339x/src/systems/instruments/src/EFB/ATC/ATC.tsx create mode 100644 hdw-a339x/src/systems/instruments/src/EFB/Dashboard/Widgets/WeatherWidget.tsx diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 7e335062d..000000000 --- a/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[workspace] -exclude = ["a32nx"] -resolver = "2" - -members = [ - "build-a333x/src/wasm/systems/a320_systems", - "build-a333x/src/wasm/systems/a320_systems_wasm", - "build-a333x/src/wasm/systems/a320_hydraulic_simulation_graphs", - "build-a333x-common/src/wasm/systems/systems", - "build-a333x-common/src/wasm/systems/systems_wasm", -] - -[profile.release] -lto = true -strip = true - -[profile.test] -# We enable some basic optimization to prevent stack overflows during testing (flybywiresim/a32nx#7631) -# However, if this interferes with your ability to debug locally you can disable it here -opt-level=1 -lto = false -debug-assertions=true -debug=true diff --git a/hdw-a333x-common/src/systems/datalink/router/src/vhf/VHF.ts b/hdw-a333x-common/src/systems/datalink/router/src/vhf/VHF.ts new file mode 100644 index 000000000..64009bc6d --- /dev/null +++ b/hdw-a333x-common/src/systems/datalink/router/src/vhf/VHF.ts @@ -0,0 +1,329 @@ +// Copyright (c) 2021 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +import { DatalinkModeCode, DatalinkStatusCode } from '@datalink/common'; +import { ATC } from '@headwindsimulations/api-client'; +import { FmgcFlightPhase } from '@shared/flightphase'; +import { NXDataStore } from '@flybywiresim/fbw-sdk'; +import { DatalinkConfiguration, DatalinkProviders, MaxSearchRange, OwnAircraft, VdlMaxDatarate } from './Common'; + +// worldwide international airports +// assumptions: international airports provide VHDL communication (i.e. USA) +// not perfectly realistic, but realistic enough for a frequency occupancy calculation +const VhfDatalinkAirports: string[] = [ + 'DAUA', 'DAAG', 'DABB', 'DABT', 'DAAE', 'DAUB', 'DAOI', 'DABC', 'DAUH', 'DAAV', 'DAOO', 'DAAS', 'DAAT', 'DAON', 'HEBA', 'HEAT', 'HESN', 'HECA', + 'HEAR', 'HEAL', 'HEGN', 'HELX', 'HEMA', 'HEMM', 'HESC', 'HESH', 'HEMK', 'HETB', 'HLLB', 'HLLS', 'HLLT', 'HLLM', 'GMAD', 'GMMN', 'GMFF', 'GMMX', + 'GMMW', 'GMFO', 'GMME', 'GMTT', 'GMTN', 'GMMH', 'GMML', 'HSSS', 'HSPN', 'DTTJ', 'DTNH', 'DTMB', 'DTTX', 'DTKA', 'DTTZ', 'DTTA', 'HBBA', 'FMCH', + 'HFFF', 'HHAS', 'HAAB', 'HADR', 'HKED', 'HKMO', 'HKKI', 'HKJK', 'FMMI', 'FMNA', 'FMNM', 'FMNN', 'FMMT', 'FMSD', 'FMST', 'FWCL', 'FWLI', 'FIMP', + 'FMCZ', 'FQMA', 'FQBR', 'FQIN', 'FQNP', 'FQPB', 'FQTT', 'FQVL', 'FMEE', 'HRYR', 'FSIA', 'HCMF', 'HCMH', 'HCMK', 'HCMM', 'HSSJ', 'HTAR', 'HTDA', + 'HTKJ', 'HTMW', 'HTZA', 'HUAR', 'HUEN', 'HUGU', 'FLLI', 'FLLS', 'FLND', 'FVHA', 'FVFA', 'FVBU', 'FNLU', 'FNUB', 'FKKD', 'FKYS', 'FEFF', 'FTTJ', + 'FZNA', 'FZAA', 'FZIC', 'FZQA', 'FCBB', 'FCPP', 'FGSL', 'FOON', 'FOOL', 'FOOG', 'FPST', 'DBBB', 'RKND', 'FXMM', 'FYWH', 'FYWB', 'FACT', 'FADN', + 'FAJS', 'FAKN', 'FABL', 'FAEL', 'FBSK', 'FBMN', 'FBFT', 'FBKE', 'DFOO', 'DFFD', 'GVBA', 'GVAC', 'GVFM', 'GVSV', 'DIAP', 'GBYD', 'DGAA', 'DGSI', + 'DGTK', 'DGSN', 'DGLW', 'DGLE', 'GUCY', 'GGOV', 'GGBU', 'GLRB', 'GABS', 'GQNN', 'GQPP', 'DRRN', 'DNAA', 'DNCA', 'DNAS', 'DNKN', 'DNMM', 'DNPO', + 'DNEN', 'DNSO', 'FHSH', 'GOBD', 'GFLL', 'DXXX', 'TQPF', 'TAPA', 'TNCA', 'MYNN', 'MYBC', 'MYEF', 'MYGF', 'MYER', 'TBPB', 'TUPJ', 'TNCB', 'TNCE', + 'TNCS', 'MWCB', 'MWCR', 'MUCM', 'MUOC', 'MUCL', 'MUCF', 'MUHA', 'MUHG', 'MUSC', 'MUCU', 'MUVR', 'TNCC', 'TDPD', 'MDBH', 'MDLR', 'MDPC', 'MDCY', + 'MDPP', 'MDST', 'MDSD', 'TGPY', 'TFFR', 'MTCH', 'MTPP', 'MKJP', 'MKJS', 'TFFF', 'TRPG', 'TJBQ', 'TJSJ', 'TFFJ', 'TKPK', 'TLPL', 'TVSV', 'TVSC', + 'TNCM', 'TTPP', 'TTCP', 'MBPV', 'TIST', 'TISX', 'MZBZ', 'MRLB', 'MROC', 'MSLP', 'MGTK', 'MGGT', 'MHLC', 'MHRO', 'MHLM', 'MHTG', 'MNMG', 'MNBL', + 'MNCI', 'MPBO', 'MPDA', 'MPTO', 'TXKF', 'CYXX', 'CYYC', 'CYEG', 'CYFC', 'CYQX', 'CYHZ', 'CYHM', 'CYLW', 'CYXU', 'CYQM', 'CYUL', 'CYOW', 'CYQB', + 'CYQR', 'CYXE', 'CYYT', 'CYQT', 'CYYZ', 'CYVR', 'CYYJ', 'CYXY', 'CYHA', 'CYWG', 'BGSF', 'BGGH', 'BGJN', 'BGBW', 'MMAA', 'MMAS', 'MMUN', 'MMCU', + 'MMCE', 'MMCZ', 'MMCL', 'MMDO', 'MMGL', 'MMHO', 'MMBT', 'MMZH', 'MMLO', 'MMLT', 'MMSD', 'MMZO', 'MMMZ', 'MMMD', 'MMMX', 'MMMY', 'MMMM', 'MMOX', + 'MMPB', 'MMPR', 'MMQT', 'MMRX', 'MMIO', 'MMSP', 'MMTM', 'MMTJ', 'MMTO', 'MMTC', 'MMTG', 'MMPN', 'MMVR', 'MMVA', 'MMZC', 'LFVP', 'KAKR', 'KALB', + 'KABQ', 'PANC', 'KATW', 'KATL', 'KACY', 'KAUS', 'KBWI', 'KBGR', 'KBLI', 'KBHM', 'KBOI', 'KBOS', 'KBUF', 'KCLT', 'KCHS', 'KMDW', 'KCVG', 'KCLE', + 'KCMH', 'KDFW', 'KDAY', 'KDEN', 'KDSM', 'KDTW', 'KELP', 'PAFA', 'KFLL', 'KRSW', 'KFAT', 'KGRR', 'KGRB', 'KGSO', 'KMDT', 'KBDL', 'PHTO', 'PHNL', + 'KIAH', 'KHSV', 'KIND', 'KJAN', 'KJAX', 'PAJN', 'KMCI', 'PAKT', 'KEYW', 'PHKO', 'KTYS', 'KLAL', 'KLAN', 'KLAS', 'KLIT', 'KLAX', 'KSDF', 'KMLB', + 'KMEM', 'KMIA', 'KMAF', 'KMKE', 'KMSP', 'KMYR', 'KBNA', 'KMSY', 'KJFK', 'KEWR', 'KSWF', 'KORF', 'KOAK', 'KOKC', 'KOMA', 'KONT', 'KSNA', 'KMCO', + 'KSFB', 'KPSP', 'KECP', 'KPNS', 'KPHL', 'KPHX', 'KIWA', 'KPIT', 'KPWM', 'KPDX', 'KPVD', 'KRAC', 'KRDU', 'KRNO', 'KRIC', 'KRST', 'KROC', 'KRFD', + 'KSMF', 'KSLC', 'KSAT', 'KSBD', 'KSAN', 'KSFO', 'KSJC', 'KSRQ', 'KSAV', 'KSBM', 'KPAE', 'KGEG', 'KSTL', 'KPIE', 'KSYR', 'KTLH', 'KTPA', 'KTUS', + 'KTUL', 'KDCA', 'KPBI', 'KAVP', 'KILM', 'SAEZ', 'SAZS', 'SACO', 'SAME', 'SARI', 'SARE', 'SAWG', 'SAWH', 'SLLP', 'SLVR', 'SLCB', 'SBAR', 'SBBE', + 'SBCF', 'SBBV', 'SBBR', 'SBKP', 'SBCG', 'SBCY', 'SBCT', 'SBFL', 'SBFZ', 'SBFI', 'SBGO', 'SBJP', 'SBMO', 'SBEG', 'SBNT', 'SBPL', 'SBPA', 'SBPV', + 'SBRF', 'SBRB', 'SBGL', 'SBSV', 'SBSL', 'SBSP', 'SBTE', 'SBUL', 'SBVT', 'SCFA', 'SCIE', 'SCTE', 'SCCI', 'SCEL', 'SKAR', 'SKBQ', 'SKBO', 'SKBG', + 'SKBU', 'SKCL', 'SKCG', 'SKCC', 'SKIB', 'SKIP', 'SKFL', 'SKLT', 'SKAO', 'SKMZ', 'SKRG', 'SKMU', 'SKMR', 'SKNV', 'SKPS', 'SKPE', 'SKPP', 'SKPV', + 'SKUI', 'SKRH', 'SKSP', 'SKTL', 'SKCO', 'SKSM', 'SKCZ', 'SKVP', 'SKVV', 'SKYP', 'SECU', 'SETN', 'SEGU', 'SERO', 'SEMT', 'SEQU', 'SETU', 'EGYP', + 'SOCA', 'SYCJ', 'SGAS', 'SGES', 'SPQU', 'SPZO', 'SPIM', 'SMJP', 'SUMU', 'SULS', 'SURV', 'SVMI', 'SVMC', 'SVVA', 'UATE', 'UATT', 'UAAA', 'UATG', + 'UAKK', 'UACK', 'UAUU', 'UAOO', 'UACC', 'UARR', 'UASK', 'UASP', 'UACP', 'UASS', 'UAII', 'UADD', 'UAFM', 'UCFL', 'UAFO', 'UTDT', 'UTDD', 'UTDL', + 'UTDK', 'UTAA', 'UTAT', 'UTAM', 'UTAK', 'UTAV', 'UTFA', 'UTSB', 'UTKF', 'UTSL', 'UTFN', 'UTSA', 'UTNN', 'UTSS', 'UTTT', 'UTST', 'UTNU', 'ZKPY', + 'RJSK', 'RJSA', 'RJFF', 'RJCH', 'RJFK', 'RJNK', 'RJOA', 'RJFR', 'RJFU', 'ROAH', 'RJGG', 'RJSN', 'RJFO', 'RJOB', 'RJBB', 'RJCC', 'RJSS', 'RJNS', + 'RJTT', 'RJAA', 'ZMUB', 'ZBOW', 'ZGBH', 'ZBAA', 'ZYCC', 'ZGHA', 'ZSCG', 'ZUUU', 'ZUCK', 'ZYTL', 'ZYDD', 'ZBDT', 'ZLDH', 'ZHES', 'ZSFZ', 'ZSGZ', + 'ZGGG', 'ZGKL', 'ZUGY', 'ZJHK', 'ZSHC', 'ZYHB', 'ZSOF', 'ZYHE', 'ZBHH', 'ZSSH', 'ZSTX', 'ZBLA', 'ZYJM', 'ZGOW', 'ZSJN', 'ZPPP', 'ZLAN', 'ZULS', + 'ZSLG', 'ZPLJ', 'ZSLY', 'ZHLY', 'ZPMS', 'ZBMZ', 'ZGMX', 'ZYMD', 'ZSCN', 'ZSNJ', 'ZGNN', 'ZSNT', 'ZSNB', 'ZBDS', 'ZSQD', 'ZBDH', 'ZYQQ', 'ZSQZ', + 'ZGSY', 'ZSSS', 'ZYTX', 'ZGSZ', 'ZBSJ', 'ZBYN', 'ZBTJ', 'ZWWW', 'ZUWX', 'ZSWH', 'ZSWZ', 'ZHHH', 'ZSWX', 'ZSWY', 'ZSAM', 'ZLXY', 'ZLXN', 'ZBXZ', + 'ZPJH', 'ZSXZ', 'ZSYN', 'ZSYA', 'ZYYJ', 'ZSYT', 'ZHYC', 'ZLIC', 'ZSYW', 'ZBYC', 'ZGDY', 'ZGZJ', 'ZHCC', 'ZGSD', 'ZUZY', 'VHHH', 'VMMC', 'RCYU', + 'RCKH', 'RCMQ', 'RCNN', 'RCSS', 'RCTP', 'RKPK', 'RKTN', 'RKPC', 'RKSS', 'RKSI', 'RKTU', 'RKJB', 'RKNY', 'VGEG', 'VGHS', 'VGSY', 'VQPR', 'VEAT', + 'VAAH', 'VIAR', 'VOBG', 'VEBS', 'VOMM', 'VOCB', 'VIDP', 'VAGO', 'VEGY', 'VEGT', 'VOHY', 'VEIM', 'VAID', 'VIJP', 'UELL', 'VOCI', 'VECC', 'VOCL', + 'VILK', 'VOMD', 'VOML', 'VABB', 'VANP', 'VAPO', 'VEBD', 'VISR', 'VASU', 'VOTV', 'VOTR', 'VABO', 'VIBN', 'VOBZ', 'VEVZ', 'VRMM', 'VRMG', 'VRMH', + 'VNKT', 'OPBW', 'OPFA', 'OPGD', 'OPRN', 'OPKC', 'OPLA', 'OPMT', 'OPPS', 'OPQT', 'OPRK', 'OPST', 'OPTU', 'VCBI', 'VCRI', 'VCCJ', 'WBSB', 'VDPP', + 'VDSR', 'VDSV', 'WPDL', 'WALL', 'WITT', 'WIIT', 'WIIB', 'WRBB', 'WADY', 'WIKB', 'WABB', 'WADD', 'WIIH', 'WRKK', 'WAAA', 'WAMM', 'WADL', 'WIMM', + 'WIPT', 'WIPP', 'WIBB', 'WIOO', 'WIIS', 'WIMN', 'WRSJ', 'WRSQ', 'WIKD', 'WRLR', 'WAHI', 'VLLB', 'VLPS', 'VLSK', 'VLVT', 'WMKA', 'WMKI', 'WMKJ', + 'WMKC', 'WBKK', 'WMKK', 'WMKN', 'WMKD', 'WBGG', 'WMKL', 'WMKP', 'WMSA', 'VYCZ', 'VYYY', 'VYNT', 'RPUO', 'RPLH', 'RPVM', 'RPLC', 'RPMD', 'RPMR', + 'RPVI', 'RPVK', 'RPLI', 'RPLL', 'RPVT', 'RPVP', 'RPLB', 'RPMZ', 'WSSS', 'VTBD', 'VTBD', 'VTCC', 'VTCT', 'VTUD', 'VTSS', 'VTSG', 'VTSP', 'VTSB', + 'VTSM', 'VTUD', 'VVDN', 'VVNB', 'VVTS', 'VVCT', 'VVCI', 'VVPB', 'VVPQ', 'VVCR', 'VVCA', 'OAKB', 'OAHR', 'OAKN', 'OAMS', 'OBBI', 'OIAA', 'OIAW', + 'OIHR', 'OITL', 'OIBP', 'OIKB', 'OIMB', 'OIBB', 'OING', 'OIHH', 'OICI', 'OIFM', 'OIKK', 'OICC', 'OIBK', 'OIZC', 'OISR', 'OISL', 'OIMM', 'OIKQ', + 'OIGG', 'OINZ', 'OISS', 'OITT', 'OIIE', 'OITR', 'OIYY', 'OIZH', 'ORNI', 'ORBI', 'ORMM', 'ORER', 'ORBM', 'ORTL', 'ORSU', 'LLER', 'LLHA', 'LLBG', + 'OJAQ', 'OJAI', 'OKBK', 'OLBA', 'OOMS', 'OOSA', 'OOSH', 'OTBD', 'OEAB', 'OEAH', 'OESK', 'OEGS', 'OEDF', 'OEHL', 'OEJN', 'OEGN', 'OEMA', 'OENG', + 'OERK', 'OETB', 'OETF', 'OEYN', 'OSAP', 'OSDI', 'OSLK', 'OSKL', 'OMAA', 'OMAL', 'OMDW', 'OMRK', 'OMSJ', 'OYAA', 'OYSN', 'OYSY', 'EBAW', 'EBBR', + 'EBCI', 'EBLG', 'EBOS', 'LFKJ', 'LFKB', 'LFOB', 'LFBE', 'LFMU', 'LFBZ', 'LFBD', 'LFRB', 'LFMK', 'LFOK', 'LFLB', 'LFRD', 'LFKF', 'LFLS', 'LFBH', + 'LFQQ', 'LFBL', 'LFLL', 'LFML', 'LFSB', 'LFRS', 'LFMN', 'LFTW', 'LFPG', 'LFBP', 'LFMP', 'LFBI', 'LFCR', 'LFMH', 'LFST', 'LFTH', 'LFBO', 'LFOT', + 'LXGB', 'EGJA', 'EGJB', 'EGJJ', 'EICK', 'EIDW', 'EIKY', 'EIKN', 'EINN', 'EGNS', 'ELLX', 'EHAM', 'EHEH', 'EHGG', 'EHBK', 'EHRD', 'LOWG', 'LOWK', + 'LOWI', 'LOWL', 'LOWS', 'LOWW', 'LKTB', 'LKKV', 'LKMT', 'LKPR', 'LKPD', 'EDSB', 'EDDB', 'EDDW', 'EDDK', 'EDLW', 'EDDL', 'EDDF', 'EDNY', 'EDDH', + 'EDDV', 'EDDP', 'EDHL', 'EDJA', 'EDDM', 'EDDN', 'EDDS', 'EDLV', 'LHBP', 'LHDC', 'LHSM', 'LHPR', 'LZIB', 'LZKZ', 'LZPP', 'LZTT', 'LZSL', 'LZZI', + 'LFSB', 'LSZB', 'LSGG', 'LSZA', 'LSZR', 'LSZH', 'EPBY', 'EPGD', 'EPKT', 'EPKK', 'EPLB', 'EPLL', 'EPPO', 'EPRZ', 'EPSC', 'EPWA', 'EPWR', 'LDSB', + 'LDDU', 'LDLO', 'LDOS', 'LDPL', 'LDRI', 'LDSP', 'LDZD', 'LDZA', 'LCLK', 'LCPH', 'LCEN', 'LGAV', 'LGKF', 'LGSA', 'LGHI', 'LGKR', 'LGIR', 'LGKL', + 'LGKP', 'LGKV', 'LGKO', 'LGMK', 'LGMT', 'LGPZ', 'LGRP', 'LGSM', 'LGSR', 'LGSK', 'LGSY', 'LGTS', 'LGBL', 'LGZA', 'LIEA', 'LIPY', 'LIBD', 'LIME', + 'LIPE', 'LIPO', 'LIBR', 'LIEE', 'LICC', 'LIMZ', 'LIRQ', 'LIMJ', 'LICA', 'LIML', 'LIRN', 'LIEO', 'LICJ', 'LIMP', 'LIRZ', 'LIBP', 'LIRP', 'LIPR', + 'LIRF', 'LICT', 'LIPQ', 'LIMF', 'LIPZ', 'LIPX', 'LMML', 'LPBJ', 'LPFR', 'LPMA', 'LPPS', 'LPPT', 'LPPR', 'LPPD', 'LPLA', 'LJLJ', 'LJMB', 'LJPZ', + 'LECO', 'LEAL', 'LEAM', 'LEAS', 'LEBL', 'LEBB', 'LECS', 'GCFV', 'LEGE', 'GCLP', 'LEGR', 'LEHC', 'LEIB', 'LEJR', 'GCLA', 'GCRR', 'LEDA', 'LEMD', + 'LEMG', 'LEMH', 'LEMI', 'LEPA', 'LEPP', 'LERS', 'LEXJ', 'LEST', 'LEZL', 'GCXO', 'LEVC', 'LEVD', 'LEVX', 'LEVT', 'LEZG', 'LATI', 'LAKU', 'UGEE', + 'UDSG', 'UBBB', 'UBBG', 'UBBN', 'UBBQ', 'UBBL', 'UBBY', 'UMMG', 'UMGG', 'UMMS', 'LQBK', 'LQSA', 'LQTZ', 'LQMO', 'LBBG', 'LBPD', 'LBSF', 'LBWN', + 'EETN', 'EETU', 'UGSB', 'UGKO', 'UGSS', 'UGGG', 'LYPR', 'EVRA', 'EVVA', 'EYKA', 'EYPA', 'EYSA', 'EYVI', 'LUKK', 'LRAR', 'LRBC', 'LRBM', 'LROP', + 'LRCL', 'LRCK', 'LRCV', 'LRIA', 'LROD', 'LRSM', 'LRSB', 'LRSV', 'LRTM', 'LRTR', 'LYPG', 'LYTV', 'LWOH', 'LWSK', 'UNAA', 'UHMA', 'URKA', 'ULAA', + 'URWA', 'UNBB', 'UUOB', 'UHBB', 'UIBB', 'UUBP', 'UWKS', 'USCC', 'ULWC', 'UIAA', 'URWI', 'UUII', 'URMG', 'UMKK', 'UWKD', 'UHHH', 'UHKK', 'URKK', + 'UNKL', 'UUOK', 'UHMM', 'USCM', 'URML', 'URMM', 'UUDD', 'ULMM', 'URMN', 'USNN', 'UWKE', 'UWGG', 'UNWW', 'UNNT', 'UNOO', 'UWOO', 'UWOR', 'USPP', + 'ULPB', 'UHMD', 'UHPP', 'ULOO', 'URRR', 'ULLI', 'UWWW', 'URSS', 'URMT', 'USRR', 'UUYY', 'UNTT', 'USTR', 'UIUU', 'UWLL', 'UWUU', 'UHWW', 'URMO', + 'URWW', 'UUOO', 'UEEE', 'UUDL', 'USSS', 'UHSS', 'LYBE', 'LYNI', 'LYKV', 'LTAF', 'LTFG', 'LTAC', 'LTAI', 'LTFE', 'LTBR', 'LTBS', 'LTAY', 'LTCC', + 'LTCA', 'LTAJ', 'LTBA', 'LTBJ', 'LTAU', 'LTAN', 'LTBZ', 'LTAT', 'LTAZ', 'LTFH', 'LTCG', 'LTAS', 'UKLN', 'UKDD', 'UKLI', 'UKHH', 'UKDR', 'UKBB', + 'UKLL', 'UKON', 'UKOO', 'UKHP', 'UKFF', 'UKLU', 'UKDE', 'EKYT', 'EKAH', 'EKBI', 'EKCH', 'EKVG', 'EFMA', 'EFHK', 'EFKT', 'EFKU', 'EFKS', 'EFLP', + 'EFOU', 'EFRO', 'EFTP', 'EFTU', 'EFVA', 'BIAR', 'BIKF', 'ENAL', 'ENBR', 'ENBO', 'ENHD', 'ENCN', 'ENGM', 'ENZV', 'ENTC', 'ENVA', 'ESGG', 'ESPA', + 'ESMS', 'ESSP', 'ESPC', 'ESSA', 'ESNN', 'ESNU', 'ESMX', 'ESSV', 'EGBB', 'EGHH', 'EGGD', 'EGFF', 'EGCN', 'EGNV', 'EGNX', 'EGTE', 'EGNM', 'EGGP', + 'EGLC', 'EGCC', 'EGNT', 'EGDQ', 'EGSH', 'EGHI', 'EGPD', 'EGPH', 'EGPF', 'EGPE', 'EGAA', 'EGAE', 'NSTU', 'YPAD', 'YBBN', 'YBRM', 'YBCS', 'YSCB', + 'YPDN', 'YAVV', 'YBCG', 'YMHB', 'YMML', 'YWLM', 'YPPH', 'YPPD', 'YBMC', 'YSSY', 'YBTL', 'YPXM', 'YPCC', 'NCRG', 'SCIP', 'NFFN', 'NFNA', 'NTAA', + 'PGUM', 'PLCH', 'NGTA', 'PKWA', 'PKMJ', 'PTKK', 'PTSA', 'PTPN', 'PTYA', 'ANAU', 'NWWW', 'NZAA', 'NZCH', 'NZQN', 'NZWN', 'YSNF', 'PGRO', 'PGSN', + 'PGWT', 'NIUE', 'PTRO', 'AYPY', 'AYMH', 'NSFA', 'AGGH', 'NFTF', 'NFTV', 'NGFU', 'NVSS', 'NVVV', 'NLWF', 'NLWW']; + +// filter parameters to find preselect conditional relevant stations +const MaxAirportsInRange = 50; + +// physical parameters to simulate the signal quality +const AdditiveNoiseOverlapDB = 1.4; +const MaximumDampingDB = -75.0; +const ReceiverAntennaGainDBI = 25.0; +// is equal to 50W emitter power +const SignalStrengthDBW = 39.1202; + +class Airport { + public Icao = ''; + + public Elevation = 0.0; + + public Distance = 0.0; + + public Datarates: [ boolean, number ][] = Array(DatalinkProviders.ProviderCount).fill([false, 0]); +} + +/* + * Simulates the physical effects of the VHF communication + * - All international airports in a LoS range are taken into account + * - The SNR is simulated and the resulting datarate is defined per airport + */ +export class Vhf { + public stationsUpperAirspace: number = 0; + + public datarates: number[] = []; + + private presentPosition: OwnAircraft = new OwnAircraft(); + + private frequencyOverlap: number[] = []; + + public relevantAirports: Airport[] = []; + + public datalinkStatus: DatalinkStatusCode = DatalinkStatusCode.Inop; + + public datalinkMode: DatalinkModeCode = DatalinkModeCode.None; + + private updatePresentPosition() { + this.presentPosition.Latitude = SimVar.GetSimVarValue('PLANE LATITUDE', 'degree latitude'); + this.presentPosition.Longitude = SimVar.GetSimVarValue('PLANE LONGITUDE', 'degree longitude'); + this.presentPosition.Altitude = SimVar.GetSimVarValue('PLANE ALTITUDE', 'feet'); + this.presentPosition.AltitudeAboveGround = SimVar.GetSimVarValue('PLANE ALT ABOVE GROUND', 'feet'); + this.presentPosition.PressureAltitude = SimVar.GetSimVarValue('INDICATED ALTITUDE:3', 'feet'); + } + + // calculates the freespace path loss for a certain distance + // reference: https://en.wikipedia.org/wiki/Free-space_path_loss + private freespacePathLoss(frequency: number, distance: number): number { + // convert to meters + const meters = distance * 1852; + return 10.0 * Math.log10((4.0 * Math.PI * meters * (frequency * 1000000) / 299792458) ** 2.0); + } + + private estimateDatarate(type: DatalinkProviders, distance: number, flightPhase: FmgcFlightPhase, airport: Airport): void { + const maximumFreespaceLoss = SignalStrengthDBW + ReceiverAntennaGainDBI - AdditiveNoiseOverlapDB * (this.frequencyOverlap[type]) - MaximumDampingDB; + let freespaceLoss = this.freespacePathLoss(DatalinkConfiguration[type], distance); + + // simulate the influence of buildings + if (flightPhase === FmgcFlightPhase.Preflight || flightPhase === FmgcFlightPhase.Done) { + // assume that buildings are close the aircraft -> add a loss of 30 dB to simulate the influence of buildings + freespaceLoss += 30; + } else if (flightPhase === FmgcFlightPhase.Takeoff || flightPhase === FmgcFlightPhase.GoAround || flightPhase === FmgcFlightPhase.Approach) { + // assume that high buildings are in the vicinity of the aircraft -> add a loss of 15 dB to simulate the influence of buildings + freespaceLoss += 15; + } + + if (maximumFreespaceLoss >= freespaceLoss) { + const lossDelta = maximumFreespaceLoss - freespaceLoss; + + // get the quality ratio normalized by the simulated signal power range + const qualityRatio = Math.min(1.0, lossDelta / Math.abs(MaximumDampingDB)); + + // use a sigmoid function to estimate the scaling of the datarate + // parametrized to jump from 1.0 to 0.02 (y) between 0.0 and 1.0 (x) + // minimum scaling is 10% of the optimal datarate + // inverse of quality ratio is needed to estimate the quality loss + const scaling = Math.max(0.1, 1.0 / (Math.exp(9.0 * (1.0 - qualityRatio) - 5.0) + 1.0)); + + airport.Datarates[type][0] = true; + airport.Datarates[type][1] = VdlMaxDatarate * scaling; + } + } + + private async updateRelevantAirports(flightPhase: FmgcFlightPhase): Promise { + // use a simple line of sight algorithm to calculate the maximum distance + // it ignores the topolography, but simulates the earth curvature + // reference: https://audio.vatsim.net/storage/AFV%20User%20Guide.pdf + const maximumDistanceLoS = (altitude0: number, altitude1: number): number => 1.23 * Math.sqrt(Math.abs(altitude0 - altitude1)); + + this.stationsUpperAirspace = 0; + this.relevantAirports = []; + + // prepare the request with the information + const requestBatch = new SimVar.SimVarBatch('C:fs9gps:NearestAirportItemsNumber', 'C:fs9gps:NearestAirportCurrentLine'); + requestBatch.add('C:fs9gps:NearestAirportCurrentICAO', 'string', 'string'); + requestBatch.add('C:fs9gps:NearestAirportSelectedLatitude', 'degree latitude'); + requestBatch.add('C:fs9gps:NearestAirportSelectedLongitude', 'degree longitude'); + requestBatch.add('C:fs9gps:WaypointAirportElevation', 'feet'); + requestBatch.add('C:fs9gps:NearestAirportCurrentDistance', 'meters'); + + SimVar.SetSimVarValue('C:fs9gps:NearestAirportCurrentLatitude', 'degree latitude', this.presentPosition.Latitude); + SimVar.SetSimVarValue('C:fs9gps:NearestAirportCurrentLongitude', 'degree longitude', this.presentPosition.Longitude); + SimVar.SetSimVarValue('C:fs9gps:NearestAirportMaximumItems', 'number', MaxAirportsInRange); + SimVar.SetSimVarValue('C:fs9gps:NearestAirportMaximumDistance', 'nautical miles', 100000); + + // get all airports + return new Promise((resolve) => { + SimVar.GetSimVarArrayValues(requestBatch, (airports) => { + airports.forEach((fetched) => { + // format: 'TYPE(one char) ICAO ' + const icao = fetched[0].substr(2).trim(); + + // found an international airport + if (VhfDatalinkAirports.findIndex((elem) => elem === icao) !== -1) { + const maxDistance = maximumDistanceLoS(this.presentPosition.PressureAltitude, fetched[3]); + const distanceNM = fetched[4] * 0.000539957; + + if (distanceNM <= maxDistance) { + const airport = new Airport(); + airport.Icao = icao; + airport.Elevation = fetched[3]; + airport.Distance = distanceNM; + + let validAirport = false; + for (let i = 0; i < DatalinkProviders.ProviderCount; ++i) { + this.estimateDatarate(i as DatalinkProviders, distanceNM, flightPhase, airport); + validAirport = validAirport || airport.Datarates[i as DatalinkProviders][0]; + } + + if (validAirport) { + this.relevantAirports.push(airport); + } + } + + // assume that all upper stations are reachable within the maximum range + if (distanceNM <= MaxSearchRange) { + this.stationsUpperAirspace += 1; + } + } + }); + + resolve(); + }); + }); + } + + private greatCircleDistance(latitude: number, longitude: number): number { + const deg2rad = (deg) => deg * (Math.PI / 180); + + const R = 6371; // Radius of the earth in km + const dLat = deg2rad(this.presentPosition.Latitude - latitude); // deg2rad below + const dLon = deg2rad(this.presentPosition.Longitude - longitude); + const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(deg2rad(latitude)) * Math.cos(deg2rad(this.presentPosition.Latitude)) + * Math.sin(dLon / 2) * Math.sin(dLon / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + const d = R * c * 0.5399568; // Distance in nm + + return d; + } + + private async updateUsedVoiceFrequencies(): Promise { + const storedAtisSrc = NXDataStore.get('CONFIG_ATIS_SRC', 'FAA').toLowerCase(); + this.frequencyOverlap = Array(DatalinkProviders.ProviderCount).fill(0); + + if (storedAtisSrc === 'vatsim' || storedAtisSrc === 'ivao') { + await ATC.get(storedAtisSrc).then((res) => { + if (!res) return; + + res = res.filter((a) => a.callsign.indexOf('_OBS') === -1 && parseFloat(a.frequency) <= 136.975 && this.greatCircleDistance(a.latitude, a.longitude) <= MaxSearchRange); + res.forEach((controller) => { + const frequency = parseFloat(controller.frequency); + + for (const key in DatalinkConfiguration) { + if ({}.hasOwnProperty.call(DatalinkConfiguration, key)) { + const datalinkFrequency = DatalinkConfiguration[key]; + + if (frequency >= datalinkFrequency - 0.009 && frequency <= datalinkFrequency + 0.009) { + // check 8.33 kHz spacing + this.frequencyOverlap[key] += 1; + } else if (frequency >= datalinkFrequency - 0.025 && frequency <= datalinkFrequency + 0.025) { + // check the direct 25 kHz neighbors for SITA + this.frequencyOverlap[key] += 1; + } + } + } + }); + }); + } + } + + /** + * Simulates the data rates for the different datalink providers + * @param flightPhase Actual flight phase to simulate the building based interferences + * @returns A promise to provide the possibilty to run it in sequence + */ + public async simulateDatarates(flightPhase: FmgcFlightPhase): Promise { + this.updatePresentPosition(); + + return this.updateUsedVoiceFrequencies().then(() => this.updateRelevantAirports(flightPhase).then(() => { + // use the average over all reachable stations to estimate the datarate + this.datarates = Array(DatalinkProviders.ProviderCount).fill(0.0); + const stationCount = Array(DatalinkProviders.ProviderCount).fill(0); + + this.relevantAirports.forEach((airport) => { + for (let i = 0; i < DatalinkProviders.ProviderCount; ++i) { + if (airport.Datarates[0]) { + this.datarates[i] += airport.Datarates[i][1]; + stationCount[i] += 1; + } + } + }); + + for (let i = 0; i < DatalinkProviders.ProviderCount; ++i) { + if (stationCount[i] !== 0) this.datarates[i] /= stationCount[i]; + } + })); + } + + public updateDatalinkStates(interfacePowered: boolean, datalinkMode: boolean) { + if (!interfacePowered) { + this.datalinkStatus = DatalinkStatusCode.Inop; + this.datalinkMode = DatalinkModeCode.None; + } else if (!datalinkMode) { + this.datalinkStatus = DatalinkStatusCode.DlkNotAvail; + this.datalinkMode = DatalinkModeCode.None; + } else { + this.datalinkStatus = DatalinkStatusCode.DlkAvail; + if (SimVar.GetSimVarValue('L:A32NX_HOPPIE_ACTIVE', 'number') === 1) { + this.datalinkMode = DatalinkModeCode.AtcAoc; + } + this.datalinkMode = DatalinkModeCode.Aoc; + } + } +} \ No newline at end of file diff --git a/hdw-a333x-common/src/systems/datalink/router/src/webinterfaces/HoppieConnector.ts b/hdw-a333x-common/src/systems/datalink/router/src/webinterfaces/HoppieConnector.ts index 5d32d1b4f..ff0c6a1b2 100644 --- a/hdw-a333x-common/src/systems/datalink/router/src/webinterfaces/HoppieConnector.ts +++ b/hdw-a333x-common/src/systems/datalink/router/src/webinterfaces/HoppieConnector.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0 import { NXDataStore } from '@flybywiresim/fbw-sdk'; -import { Hoppie } from '@flybywiresim/api-client'; +import { Hoppie } from '@headwindsimulations/api-client'; import { AtsuStatusCodes, CpdlcMessage, @@ -392,4 +392,4 @@ export class HoppieConnector { const pollTimeSeconds = Math.floor(Math.random() * (75 - 45 + 1)) + 45; return pollTimeSeconds * 1000; } -} \ No newline at end of file +} diff --git a/hdw-a333x-common/src/systems/datalink/router/src/webinterfaces/NXApiConnector.ts b/hdw-a333x-common/src/systems/datalink/router/src/webinterfaces/NXApiConnector.ts new file mode 100644 index 000000000..82f13c19a --- /dev/null +++ b/hdw-a333x-common/src/systems/datalink/router/src/webinterfaces/NXApiConnector.ts @@ -0,0 +1,226 @@ +// Copyright (c) 2022 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +import { Atis, Metar, Taf, Telex, AircraftStatus } from '@headwindsimulations/api-client'; +import { ConfigWeatherMap, NXDataStore } from '@flybywiresim/fbw-sdk'; +import { + AtsuStatusCodes, + AtsuMessage, + AtsuMessageComStatus, + AtsuMessageNetwork, + AtsuMessageDirection, + FreetextMessage, + WeatherMessage, + AtisMessage, + AtisType, +} from '@datalink/common'; + +/** + * Defines the NXApi connector for the AOC system + */ +export class NXApiConnector { + private static flightNumber: string = ''; + + private static connected: boolean = false; + + private static updateCounter: number = 0; + + private static createAircraftStatus(): AircraftStatus | undefined { + const lat = SimVar.GetSimVarValue('PLANE LATITUDE', 'degree latitude'); + const long = SimVar.GetSimVarValue('PLANE LONGITUDE', 'degree longitude'); + const alt = SimVar.GetSimVarValue('PLANE ALTITUDE', 'feet'); + const heading = SimVar.GetSimVarValue('PLANE HEADING DEGREES TRUE', 'degree'); + const acType = SimVar.GetSimVarValue('TITLE', 'string'); + const origin = NXDataStore.get('PLAN_ORIGIN', ''); + const destination = NXDataStore.get('PLAN_DESTINATION', ''); + const freetext = NXDataStore.get('CONFIG_ONLINE_FEATURES_STATUS', 'DISABLED') === 'ENABLED'; + + return { + location: { + long, + lat, + }, + trueAltitude: alt, + heading, + origin, + destination, + freetextEnabled: freetext, + flight: NXApiConnector.flightNumber, + aircraftType: acType, + }; + } + + public static async connect(flightNo: string): Promise { + if (NXDataStore.get('CONFIG_ONLINE_FEATURES_STATUS', 'DISABLED') !== 'ENABLED') { + return AtsuStatusCodes.TelexDisabled; + } + + // deactivate old connection + await NXApiConnector.disconnect(); + + NXApiConnector.flightNumber = flightNo; + const status = NXApiConnector.createAircraftStatus(); + if (status !== undefined) { + return Telex.connect(status).then((res) => { + if (res.accessToken !== '') { + NXApiConnector.connected = true; + NXApiConnector.updateCounter = 0; + return AtsuStatusCodes.Ok; + } + return AtsuStatusCodes.NoTelexConnection; + }).catch(() => AtsuStatusCodes.CallsignInUse); + } + + return AtsuStatusCodes.Ok; + } + + public static async disconnect(): Promise { + if (NXDataStore.get('CONFIG_ONLINE_FEATURES_STATUS', 'DISABLED') !== 'ENABLED') { + return AtsuStatusCodes.TelexDisabled; + } + + if (NXApiConnector.connected) { + return Telex.disconnect().then(() => { + NXApiConnector.connected = false; + NXApiConnector.flightNumber = ''; + return AtsuStatusCodes.Ok; + }).catch(() => AtsuStatusCodes.ProxyError); + } + + return AtsuStatusCodes.NoTelexConnection; + } + + public static isConnected(): boolean { + return NXApiConnector.connected; + } + + public static async sendTelexMessage(message: FreetextMessage): Promise { + if (NXApiConnector.connected) { + const content = message.Message.replace('\n', ';'); + return Telex.sendMessage(message.Station, content).then(() => { + message.ComStatus = AtsuMessageComStatus.Sent; + return AtsuStatusCodes.Ok; + }).catch(() => { + message.ComStatus = AtsuMessageComStatus.Failed; + return AtsuStatusCodes.ComFailed; + }); + } + return AtsuStatusCodes.NoTelexConnection; + } + + public static async receiveMetar(icao: string, message: WeatherMessage): Promise { + const storedMetarSrc = NXDataStore.get('CONFIG_METAR_SRC', 'MSFS'); + + return Metar.get(icao, ConfigWeatherMap[storedMetarSrc]) + .then((data) => { + let metar = data.metar; + if (!metar || metar === undefined || metar === '') { + metar = 'NO METAR AVAILABLE'; + } + + message.Reports.push({ airport: icao, report: metar }); + return AtsuStatusCodes.Ok; + }).catch(() => { + message.Reports.push({ airport: icao, report: 'NO METAR AVAILABLE' }); + return AtsuStatusCodes.Ok; + }); + } + + public static async receiveTaf(icao: string, message: WeatherMessage): Promise { + const storedTafSrc = NXDataStore.get('CONFIG_TAF_SRC', 'NOAA'); + + return Taf.get(icao, ConfigWeatherMap[storedTafSrc]) + .then((data) => { + let taf = data.taf; + if (!taf || taf === undefined || taf === '') { + taf = 'NO TAF AVAILABLE'; + } + + message.Reports.push({ airport: icao, report: taf }); + return AtsuStatusCodes.Ok; + }).catch(() => { + message.Reports.push({ airport: icao, report: 'NO TAF AVAILABLE' }); + return AtsuStatusCodes.Ok; + }); + } + + public static async receiveAtis(icao: string, type: AtisType, message: AtisMessage): Promise { + const storedAtisSrc = NXDataStore.get('CONFIG_ATIS_SRC', 'FAA'); + + await Atis.get(icao, ConfigWeatherMap[storedAtisSrc]) + .then((data) => { + let atis = undefined; + + if (type === AtisType.Arrival) { + if ('arr' in data) { + atis = data.arr; + } else { + atis = data.combined; + } + } else if (type === AtisType.Departure) { + if ('dep' in data) { + atis = data.dep; + } else { + atis = data.combined; + } + } else if (type === AtisType.Enroute) { + if ('combined' in data) { + atis = data.combined; + } else if ('arr' in data) { + atis = data.arr; + } + } + + if (!atis || atis === undefined) { + atis = 'D-ATIS NOT AVAILABLE'; + } + + message.Reports.push({ airport: icao, report: atis }); + }).catch(() => { + message.Reports.push({ airport: icao, report: 'D-ATIS NOT AVAILABLE' }); + }); + + return AtsuStatusCodes.Ok; + } + + public static async poll(): Promise<[AtsuStatusCodes, AtsuMessage[]]> { + const retval: AtsuMessage[] = []; + + if (NXApiConnector.connected) { + if (NXApiConnector.updateCounter++ % 4 === 0) { + const status = NXApiConnector.createAircraftStatus(); + if (status !== undefined) { + const code = await Telex.update(status).then(() => AtsuStatusCodes.Ok).catch(() => AtsuStatusCodes.ProxyError); + if (code !== AtsuStatusCodes.Ok) { + return [AtsuStatusCodes.ComFailed, retval]; + } + } + } + + // Fetch new messages + try { + const data = await Telex.fetchMessages(); + for (const msg of data) { + const message = new FreetextMessage(); + message.Network = AtsuMessageNetwork.FBW; + message.Direction = AtsuMessageDirection.Uplink; + message.Station = msg.from.flight; + message.Message = msg.message.replace(/;/i, ' '); + + retval.push(message); + } + } catch (_e) { + return [AtsuStatusCodes.ComFailed, retval]; + } + } + + return [AtsuStatusCodes.Ok, retval]; + } + + public static pollInterval(): number { + return 15000; + } +} + +NXDataStore.set('PLAN_ORIGIN', ''); +NXDataStore.set('PLAN_DESTINATION', ''); \ No newline at end of file diff --git a/hdw-a333x/src/systems/instruments/src/EFB/ATC/ATC.tsx b/hdw-a333x/src/systems/instruments/src/EFB/ATC/ATC.tsx new file mode 100644 index 000000000..fcdaa9d94 --- /dev/null +++ b/hdw-a333x/src/systems/instruments/src/EFB/ATC/ATC.tsx @@ -0,0 +1,320 @@ +// Copyright (c) 2021-2023 FlyByWire Simulations +// +// SPDX-License-Identifier: GPL-3.0 + +/* eslint-disable max-len */ +import React, { useCallback, useEffect, useState } from 'react'; +import * as apiClient from '@headwindsimulations/api-client'; +import { AtcType } from '@headwindsimulations/api-client'; +import { useSimVar, useInterval, useSplitSimVar, usePersistentProperty } from '@flybywiresim/fbw-sdk'; +import { Link } from 'react-router-dom'; +import { CloudArrowDown, Gear, InfoCircle } from 'react-bootstrap-icons'; +import { toast } from 'react-toastify'; +import { t } from '../translation'; +import { pathify } from '../Utils/routing'; +import { ScrollableContainer } from '../UtilComponents/ScrollableContainer'; +import { SimpleInput } from '../UtilComponents/Form/SimpleInput/SimpleInput'; +import { SelectGroup, SelectItem } from '../UtilComponents/Form/Select'; +import { TooltipWrapper } from '../UtilComponents/TooltipWrapper'; + +export declare class ATCInfoExtended extends apiClient.ATCInfo { + distance: number; +} + +interface FrequencyCardProps { + className?: string; + callsign: string; + frequency: string; + setActive: () => void; + setCurrent: () => void; + setStandby: () => void; +} + +const FrequencyCard = ({ className, callsign, frequency, setActive, setCurrent, setStandby }: FrequencyCardProps) => ( +
+
+

+ {callsign} +

+

+ {frequency} +

+ +
+
+

{t('AirTrafficControl.SetActive')}

+
+
+

{t('AirTrafficControl.SetStandby')}

+
+
+ +
+
+
+
+); + +export const ATC = () => { + const [controllers, setControllers] = useState(); + const [activeFrequency, setActiveFrequency] = useSplitSimVar('COM ACTIVE FREQUENCY:1', 'Hz', 'K:COM_RADIO_SET_HZ', 'Hz', 500); + const [stanbdyFrequency, setStandbyFrequency] = useSplitSimVar('COM STANDBY FREQUENCY:1', 'Hz', 'K:COM_STBY_RADIO_SET_HZ', 'Hz', 500); + const [displayedActiveFrequency, setDisplayedActiveFrequency] = useState(); + const [displayedStandbyFrequency, setDisplayedStandbyFrequency] = useState(); + const [currentAtc, setCurrentAtc] = useState(); + const [currentLatitude] = useSimVar('GPS POSITION LAT', 'Degrees', 10_000); + const [currentLongitude] = useSimVar('GPS POSITION LON', 'Degrees', 10_000); + const [atisSource] = usePersistentProperty('CONFIG_ATIS_SRC', 'FAA'); + const [atcDataPending, setAtcDataPending] = useState(true); + + const [controllerTypeFilter, setControllerTypeFilter] = useState(undefined); + const [controllerCallSignFilter, setControllerCallSignFilter] = useState(''); + + const loadAtc = useCallback(async () => { + if (atisSource.toLowerCase() !== 'vatsim' && atisSource.toLowerCase() !== 'ivao') return; + const atisSourceReq = atisSource.toLowerCase(); + + try { + const atcRes = await apiClient.ATC.get(atisSourceReq); + if (!atcRes) return; + let allAtc : ATCInfoExtended[] = atcRes as ATCInfoExtended[]; + + allAtc = allAtc.filter((a) => a.callsign.indexOf('_OBS') === -1 && parseFloat(a.frequency) <= 136.975); + + for (const a of allAtc) { + a.distance = getDistanceFromLatLonInNm(a.latitude, a.longitude, currentLatitude, currentLongitude); + if (a.visualRange === 0 && a.type === apiClient.AtcType.ATIS) { + a.visualRange = 100; + } + } + + allAtc.sort((a1, a2) => (a1.distance > a2.distance ? 1 : -1)); + allAtc = allAtc.slice(0, 26); + allAtc.push({ callsign: 'UNICOM', frequency: '122.800', type: apiClient.AtcType.RADAR, visualRange: 999999, distance: 0, latitude: 0, longitude: 0, textAtis: [] }); + + setControllers(allAtc.filter((a) => a.distance <= a.visualRange)); + } catch (e) { + toast.error(e.message); + } + + setAtcDataPending(false); + }, [currentLatitude, currentLongitude, atisSource]); + + const getDistanceFromLatLonInNm = (lat1, lon1, lat2, lon2) : number => { + const R = 6371; // Radius of the earth in km + const dLat = deg2Rad(lat2 - lat1); // deg2Rad below + const dLon = deg2Rad(lon2 - lon1); + const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(deg2Rad(lat1)) * Math.cos(deg2Rad(lat2)) + * Math.sin(dLon / 2) * Math.sin(dLon / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + return R * c * 0.5399568; // Distance in nm + }; + + const deg2Rad = (deg) => deg * (Math.PI / 180); + + const toFrequency = (frequency:string) : number => { + if (frequency) { + return parseFloat(`${frequency.replace('.', '').padEnd(9, '0')}.000`); + } + return 0; + }; + + const fromFrequency = (frequency: number): string => { + if (frequency) { + let converted: string = frequency.toString().replace('.', ''); + converted = `${converted.substring(0, 3)}.${converted.substring(3)}`; + return parseFloat(converted).toFixed(3); + } + return ''; + }; + + useEffect(() => { + loadAtc(); + }, [loadAtc]); + + useEffect(() => { + const converted = fromFrequency(activeFrequency); + setDisplayedActiveFrequency(converted); + setCurrentAtc(controllers?.find((c) => c.frequency === converted)); + }, [activeFrequency]); + + useEffect(() => { + const converted = fromFrequency(stanbdyFrequency); + setDisplayedStandbyFrequency(converted); + setCurrentAtc(controllers?.find((c) => c.frequency === converted)); + }, [stanbdyFrequency]); + + // Update selected controller info when controllers change + useEffect(() => { + const currentControllerFrequency = currentAtc?.frequency; + + if (currentControllerFrequency) { + const controllerWithFrequency = controllers?.find((c) => c.frequency === currentControllerFrequency); + + if (controllerWithFrequency) { + setCurrentAtc(controllerWithFrequency); + } + } + }, [controllers]); + + useInterval(() => { + loadAtc(); + }, 60_000); + + const filterControllers = (c: ATCInfoExtended): boolean => !((controllerTypeFilter && c.type !== controllerTypeFilter) + || (controllerCallSignFilter !== '' + && !c.callsign.toUpperCase().includes(controllerCallSignFilter.toUpperCase()))); + + const atcTypeOptions = [ + { typeName: t('AirTrafficControl.ShowAll'), atcType: undefined }, + { typeName: t('AirTrafficControl.ShowAtis'), atcType: AtcType.ATIS }, + { typeName: t('AirTrafficControl.ShowDelivery'), atcType: AtcType.DELIVERY }, + { typeName: t('AirTrafficControl.ShowGround'), atcType: AtcType.GROUND }, + { typeName: t('AirTrafficControl.ShowTower'), atcType: AtcType.TOWER }, + { typeName: t('AirTrafficControl.ShowApproach'), atcType: AtcType.APPROACH }, + { typeName: t('AirTrafficControl.ShowDeparture'), atcType: AtcType.DEPARTURE }, + { typeName: t('AirTrafficControl.ShowRadar'), atcType: AtcType.RADAR }, + ]; + + return ( +
+
+

+ {t('AirTrafficControl.Title')} + {(atisSource === 'IVAO' || atisSource === 'VATSIM') && ` (${atisSource})`} +

+
+ { (atisSource === 'IVAO' || atisSource === 'VATSIM') ? ( +
+ +
+ +
+ +
+ setControllerCallSignFilter(value)} + /> + +
+
+ + {atcTypeOptions.map((option) => ( + +
+ setControllerTypeFilter(option.atcType)} + > + {option.typeName} + +
+
+ ))} +
+
+ + + {controllers && controllers + .filter((c) => filterControllers(c)) + .map((controller, index) => ( + = 2 && 'mt-4'}`} + callsign={controller.callsign} + frequency={controller.frequency} + setActive={() => setActiveFrequency(toFrequency(controller.frequency))} + setCurrent={() => setCurrentAtc(controllers?.find((c) => c.frequency === controller.frequency))} + setStandby={() => setStandbyFrequency(toFrequency(controller.frequency))} + /> + ))} + + +
+ {atcDataPending && ( + + )} +
+
+ +
+
+
+

{t('AirTrafficControl.Active')}

+
+ {displayedActiveFrequency && displayedActiveFrequency} +
+
+
+

{t('AirTrafficControl.Standby')}

+
+ {displayedStandbyFrequency && displayedStandbyFrequency} +
+
+
+ {currentAtc?.textAtis ? ( + + ) : ( +
+

{t('AirTrafficControl.NoInformationAvailableForThisFrequency').toUpperCase()}

+
+ )} +
+
+ ) : ( +
+
+

{t('AirTrafficControl.SelectCorrectATISATCSource')}

+ + +

{t('AirTrafficControl.ChangeATISATCSourceButton')}

+ +
+
+ )} +
+ ); +}; + +interface ControllerInformationProps { + currentAtc?: ATCInfoExtended; +} + +const ControllerInformation = ({ currentAtc }: ControllerInformationProps) => ( + +

{currentAtc?.callsign}

+ {currentAtc?.textAtis.map((line) => ( +

{line}

+ ))} +
+); + +export default ATC; diff --git a/hdw-a333x/src/systems/instruments/src/EFB/Dashboard/Widgets/WeatherWidget.tsx b/hdw-a333x/src/systems/instruments/src/EFB/Dashboard/Widgets/WeatherWidget.tsx new file mode 100644 index 000000000..c047addc6 --- /dev/null +++ b/hdw-a333x/src/systems/instruments/src/EFB/Dashboard/Widgets/WeatherWidget.tsx @@ -0,0 +1,319 @@ +// Copyright (c) 2022 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +import React, { FC, useEffect, useState } from 'react'; +import { Metar as FbwApiMetar } from '@headwindsimulations/api-client'; +import { Droplet, Speedometer2, ThermometerHalf, Wind } from 'react-bootstrap-icons'; +import { ConfigWeatherMap, MetarParserType, parseMetar, useInterval, usePersistentNumberProperty, usePersistentProperty } from '@flybywiresim/fbw-sdk'; +import { Metar as MsfsMetar } from '@microsoft/msfs-sdk'; +import { t } from '../../translation'; +import { SimpleInput } from '../../UtilComponents/Form/SimpleInput/SimpleInput'; +import { ColoredMetar } from './ColorMetar'; +import { useAppDispatch, useAppSelector } from '../../Store/store'; +import { + setDepartureMetar, + setDestinationMetar, + setUserDepartureIcao, + setUserDestinationIcao, +} from '../../Store/features/dashboard'; +import { Toggle } from '../../UtilComponents/Form/Toggle'; +import { TooltipWrapper } from '../../UtilComponents/TooltipWrapper'; + +const MetarParserTypeProp: MetarParserType = { + raw_text: '', + raw_parts: [], + color_codes: [], + icao: '', + observed: new Date(0), + wind: { + degrees: 0, + degrees_from: 0, + degrees_to: 0, + speed_kts: 0, + speed_mps: 0, + gust_kts: 0, + gust_mps: 0, + }, + visibility: { + miles: '', + miles_float: 0.0, + meters: '', + meters_float: 0.0, + }, + conditions: [], + clouds: [], + ceiling: { + code: '', + feet_agl: 0, + meters_agl: 0, + }, + temperature: { + celsius: 0, + fahrenheit: 0, + }, + dewpoint: { + celsius: 0, + fahrenheit: 0, + }, + humidity_percent: 0, + barometer: { + hg: 0, + kpa: 0, + mb: 0, + }, + flight_category: '', +}; + +interface WeatherWidgetProps { + name: 'origin'|'destination'; + simbriefIcao: string; + userIcao?: string +} + +export const WeatherWidget: FC = ({ name, simbriefIcao, userIcao }) => { + const [baroType] = usePersistentProperty('CONFIG_INIT_BARO_UNIT', 'HPA'); + const dispatch = useAppDispatch(); + const [simbriefIcaoAtLoading, setSimbriefIcaoAtLoading] = useState(simbriefIcao); + const [metarSource] = usePersistentProperty('CONFIG_METAR_SRC', 'MSFS'); + const [metarError, setErrorMetar] = useState(''); + const [usingColoredMetar] = usePersistentNumberProperty('EFB_USING_COLOREDMETAR', 1); + const source = metarSource; + + const getBaroTypeForAirport = (icao: string) => (['K', 'C', 'M', 'P', 'RJ', 'RO', 'TI', 'TJ'] + .some((r) => icao.toUpperCase().startsWith(r)) ? 'IN HG' : 'HPA'); + + const metar = useAppSelector((state) => (name === 'origin' ? state.dashboard.departureMetar : state.dashboard.destinationMetar)) ?? MetarParserTypeProp; + const setMetar = name === 'origin' ? setDepartureMetar : setDestinationMetar; + + const [showMetar, setShowMetar] = usePersistentNumberProperty(`CONFIG_SHOW_METAR_${name}`, 0); + + const BaroValue = () => { + const displayedBaroType = baroType === 'AUTO' ? getBaroTypeForAirport(metar.icao) : baroType; + if (displayedBaroType === 'IN HG') { + return ( + <> + {metar.barometer.hg.toFixed(2)} + {' '} + inHg + + ); + } + return ( + <> + {metar.barometer.mb.toFixed(0)} + {' '} + mb + + ); + }; + + const handleIcao = (icao: string) => { + if (name === 'origin') { + dispatch(setUserDepartureIcao(icao)); + } else { + dispatch(setUserDestinationIcao(icao)); + } + + if (icao.length > 0) { + getMetar(icao, source); + } else if (icao.length === 0) { + getMetar(simbriefIcao, source); + } + }; + + async function getMetar(icao: string, source: string): Promise { + if (icao.length !== 4 || icao === '----') { + setErrorMetar(t('Dashboard.ImportantInformation.Weather.NoIcaoProvided')); + dispatch(setMetar(MetarParserTypeProp)); + return Promise.resolve(); + } + + // Comes from the sim rather than the FBW API + if (source === 'MSFS') { + let metar: MsfsMetar; + // Catch parsing error separately + try { + metar = await Coherent.call('GET_METAR_BY_IDENT', icao); + if (metar.icao !== icao.toUpperCase()) { + throw new Error('No METAR available'); + } + } catch (err) { + console.log(`Error while retrieving Metar: ${err}`); + setErrorMetar(`${err.toString()}`); + dispatch(setMetar(MetarParserTypeProp)); + } + try { + const metarParse = parseMetar(metar.metarString); + dispatch(setMetar(metarParse)); + } catch (err) { + console.log(`Error while parsing Metar ("${metar.metarString}"): ${err}`); + setErrorMetar(`${t('Dashboard.ImportantInformation.Weather.MetarParsingError')}: ${err.toString().replace(/^Error: /, '').toUpperCase()}`); + dispatch(setMetar(MetarParserTypeProp)); + } + return Promise.resolve(); + } + + return FbwApiMetar.get(icao, ConfigWeatherMap[source]) + .then((result) => { + // For METAR source Microsoft result.metar is undefined without throwing an error. + // For the other METAR sources an error is thrown (Request failed with status code 404) + // and caught in the catch clause. + if (!result.metar) { + setErrorMetar(t('Dashboard.ImportantInformation.Weather.IcaoInvalid')); + dispatch(setMetar(MetarParserTypeProp)); + return; + } + // Catch parsing error separately + try { + const metarParse = parseMetar(result.metar); + dispatch(setMetar(metarParse)); + } catch (err) { + console.log(`Error while parsing Metar ("${result.metar}"): ${err}`); + setErrorMetar(`${t('Dashboard.ImportantInformation.Weather.MetarParsingError')}: ${err.toString().replace(/^Error: /, '').toUpperCase()}`); + dispatch(setMetar(MetarParserTypeProp)); + } + }) + // catch retrieving metar errors + .catch((err) => { + console.log(`Error while retrieving Metar: ${err}`); + if (err.toString().match(/^Error:/)) { + setErrorMetar(t('Dashboard.ImportantInformation.Weather.IcaoInvalid')); + } else { + setErrorMetar(`${err.toString().replace(/^Error: /, '')}`); + } + dispatch(setMetar(MetarParserTypeProp)); + }); + } + + useEffect(() => { + // if we have new simbrief data that is different from the simbrief data at + // loading of the widget we overwrite the user input once. After that + // user input has priority. + if (simbriefIcao !== simbriefIcaoAtLoading) { + dispatch(setUserDepartureIcao('')); + dispatch(setUserDestinationIcao('')); + getMetar(simbriefIcao, source); + setSimbriefIcaoAtLoading(simbriefIcao); + } else { + getMetar(userIcao || simbriefIcao, source); + } + }, [simbriefIcao, userIcao, source]); + + useInterval(() => { + handleIcao(userIcao ?? simbriefIcao); + }, 60_000); + + return ( +
+ {metar === undefined + ?

{t('Dashboard.ImportantInformation.Weather.Loading')}

+ : ( + <> +
+ handleIcao(value)} + maxLength={4} + /> + +
+

{t('Dashboard.ImportantInformation.Weather.Raw')}

+ setShowMetar(value ? 1 : 0)} /> +
+
+
+
+ {!showMetar + ? ( + <> +
+
+ +

{t('Dashboard.ImportantInformation.Weather.AirPressure')}

+ {metar.raw_text ? ( + <> + {metar.barometer ? : 'N/A'} + + ) : t('Dashboard.ImportantInformation.Weather.NotAvailableShort')} +
+
+ +

{t('Dashboard.ImportantInformation.Weather.WindSpeed')}

+ {metar.raw_text + ? ( + <> + {metar.wind.degrees.toFixed(0)} + ° + {' '} + / + {' '} + {metar.wind.speed_kts.toFixed(0)} + {' '} + kts + + ) : t('Dashboard.ImportantInformation.Weather.NotAvailableShort')} +
+
+ +

{t('Dashboard.ImportantInformation.Weather.Temperature')}

+ {metar.raw_text + ? ( + <> + {metar.temperature.celsius.toFixed(0)} + {' '} + °C + + ) : t('Dashboard.ImportantInformation.Weather.NotAvailableShort')} +
+
+ +

{t('Dashboard.ImportantInformation.Weather.DewPoint')}

+ {metar.raw_text + ? ( + <> + {metar.dewpoint.celsius.toFixed(0)} + {' '} + °C + + ) : t('Dashboard.ImportantInformation.Weather.NotAvailableShort')} +
+
+ + ) + : ( + <> + {metar.raw_text + ? ( +
+ {usingColoredMetar + ? ( + <> + + + ) : ( + <> + {metar.raw_text} + + )} +
+ ) : ( +
+ {metarError} +
+ )} + + )} +
+ + )} +
+ ); +}; \ No newline at end of file diff --git a/hdw-a333x/src/systems/instruments/src/EFB/Performance/Widgets/LandingWidget.tsx b/hdw-a333x/src/systems/instruments/src/EFB/Performance/Widgets/LandingWidget.tsx index 218aa33ba..47582f5ec 100644 --- a/hdw-a333x/src/systems/instruments/src/EFB/Performance/Widgets/LandingWidget.tsx +++ b/hdw-a333x/src/systems/instruments/src/EFB/Performance/Widgets/LandingWidget.tsx @@ -18,7 +18,9 @@ */ import React, { FC, useState } from 'react'; -import { Units, MetarParserType, useSimVar, usePersistentProperty, parseMetar } from '@flybywiresim/fbw-sdk'; +import { Metar as FbwApiMetar } from '@headwindsimulations/api-client'; +import { Metar as MsfsMetar } from '@microsoft/msfs-sdk'; +import { Units, MetarParserType, useSimVar, usePersistentProperty, parseMetar, ConfigWeatherMap } from '@flybywiresim/fbw-sdk'; import { toast } from 'react-toastify'; import { Calculator, CloudArrowDown, Trash } from 'react-bootstrap-icons'; import { t } from '../../translation'; @@ -39,8 +41,8 @@ interface OutputDisplayProps { } const OutputDisplay = (props: OutputDisplayProps) => ( -
-

{props.label}

+
+

{props.label}

{props.value}

@@ -53,7 +55,7 @@ interface LabelProps { } const Label: FC = ({ text, className, children }) => ( -
+

{text}

{children}
@@ -66,6 +68,7 @@ export const LandingWidget = () => { const [totalWeight] = useSimVar('TOTAL WEIGHT', 'Pounds', 1000); const [autoFillSource, setAutoFillSource] = useState<'METAR' | 'OFP'>('OFP'); + const [metarSource] = usePersistentProperty('CONFIG_METAR_SRC', 'MSFS'); const { usingMetric } = Units; @@ -145,28 +148,45 @@ export const LandingWidget = () => { const syncValuesWithApiMetar = async (icao: string): Promise => { if (!isValidIcao(icao)) return; - fetch(`https://api.flybywiresim.com/metar/${icao}`) - .then((res) => { - if (res.ok) { - return res.json(); + let parsedMetar: MetarParserType | undefined = undefined; + + // Comes from the sim rather than the FBW API + if (metarSource === 'MSFS') { + let metar: MsfsMetar; + try { + metar = await Coherent.call('GET_METAR_BY_IDENT', icao); + if (metar.icao !== icao.toUpperCase()) { + throw new Error('No METAR available'); } - return res.json().then((json) => { - throw new Error(json.message); - }); - }).then((json) => { - const parsedMetar: MetarParserType = parseMetar(json.metar); + parsedMetar = parseMetar(metar.metarString); + } catch (err) { + toast.error(err.message); + } + } else { + try { + const response = await FbwApiMetar.get(icao, ConfigWeatherMap[metarSource]); + if (!response.metar) { + throw new Error('No METAR available'); + } + parsedMetar = parseMetar(response.metar); + } catch (err) { + toast.error(err.message); + } + } - const weightKgs = Math.round(Units.poundToKilogram(totalWeight)); + if (parsedMetar === undefined) { + return; + } - dispatch(setLandingValues({ - weight: weightKgs, - windDirection: parsedMetar.wind.degrees, - windMagnitude: parsedMetar.wind.speed_kts, - temperature: parsedMetar.temperature.celsius, - pressure: parsedMetar.barometer.mb, - })); - }) - .catch((err) => toast.error(err.message)); + const weightKgs = Math.round(Units.poundToKilogram(totalWeight)); + + dispatch(setLandingValues({ + weight: weightKgs, + windDirection: parsedMetar.wind.degrees, + windMagnitude: parsedMetar.wind.speed_kts, + temperature: parsedMetar.temperature.celsius, + pressure: parsedMetar.barometer.mb, + })); }; const isValidIcao = (icao: string): boolean => icao.length === 4; @@ -411,11 +431,11 @@ export const LandingWidget = () => { }; return ( -
+
-
+
-
+

{t('Performance.Landing.AirportIcao')}

@@ -423,7 +443,7 @@ export const LandingWidget = () => {
-
+
add a loss of 30 dB to simulate the influence of buildings + freespaceLoss += 30; + } else if (flightPhase === FmgcFlightPhase.Takeoff || flightPhase === FmgcFlightPhase.GoAround || flightPhase === FmgcFlightPhase.Approach) { + // assume that high buildings are in the vicinity of the aircraft -> add a loss of 15 dB to simulate the influence of buildings + freespaceLoss += 15; + } + + if (maximumFreespaceLoss >= freespaceLoss) { + const lossDelta = maximumFreespaceLoss - freespaceLoss; + + // get the quality ratio normalized by the simulated signal power range + const qualityRatio = Math.min(1.0, lossDelta / Math.abs(MaximumDampingDB)); + + // use a sigmoid function to estimate the scaling of the datarate + // parametrized to jump from 1.0 to 0.02 (y) between 0.0 and 1.0 (x) + // minimum scaling is 10% of the optimal datarate + // inverse of quality ratio is needed to estimate the quality loss + const scaling = Math.max(0.1, 1.0 / (Math.exp(9.0 * (1.0 - qualityRatio) - 5.0) + 1.0)); + + airport.Datarates[type][0] = true; + airport.Datarates[type][1] = VdlMaxDatarate * scaling; + } + } + + private async updateRelevantAirports(flightPhase: FmgcFlightPhase): Promise { + // use a simple line of sight algorithm to calculate the maximum distance + // it ignores the topolography, but simulates the earth curvature + // reference: https://audio.vatsim.net/storage/AFV%20User%20Guide.pdf + const maximumDistanceLoS = (altitude0: number, altitude1: number): number => 1.23 * Math.sqrt(Math.abs(altitude0 - altitude1)); + + this.stationsUpperAirspace = 0; + this.relevantAirports = []; + + // prepare the request with the information + const requestBatch = new SimVar.SimVarBatch('C:fs9gps:NearestAirportItemsNumber', 'C:fs9gps:NearestAirportCurrentLine'); + requestBatch.add('C:fs9gps:NearestAirportCurrentICAO', 'string', 'string'); + requestBatch.add('C:fs9gps:NearestAirportSelectedLatitude', 'degree latitude'); + requestBatch.add('C:fs9gps:NearestAirportSelectedLongitude', 'degree longitude'); + requestBatch.add('C:fs9gps:WaypointAirportElevation', 'feet'); + requestBatch.add('C:fs9gps:NearestAirportCurrentDistance', 'meters'); + + SimVar.SetSimVarValue('C:fs9gps:NearestAirportCurrentLatitude', 'degree latitude', this.presentPosition.Latitude); + SimVar.SetSimVarValue('C:fs9gps:NearestAirportCurrentLongitude', 'degree longitude', this.presentPosition.Longitude); + SimVar.SetSimVarValue('C:fs9gps:NearestAirportMaximumItems', 'number', MaxAirportsInRange); + SimVar.SetSimVarValue('C:fs9gps:NearestAirportMaximumDistance', 'nautical miles', 100000); + + // get all airports + return new Promise((resolve) => { + SimVar.GetSimVarArrayValues(requestBatch, (airports) => { + airports.forEach((fetched) => { + // format: 'TYPE(one char) ICAO ' + const icao = fetched[0].substr(2).trim(); + + // found an international airport + if (VhfDatalinkAirports.findIndex((elem) => elem === icao) !== -1) { + const maxDistance = maximumDistanceLoS(this.presentPosition.PressureAltitude, fetched[3]); + const distanceNM = fetched[4] * 0.000539957; + + if (distanceNM <= maxDistance) { + const airport = new Airport(); + airport.Icao = icao; + airport.Elevation = fetched[3]; + airport.Distance = distanceNM; + + let validAirport = false; + for (let i = 0; i < DatalinkProviders.ProviderCount; ++i) { + this.estimateDatarate(i as DatalinkProviders, distanceNM, flightPhase, airport); + validAirport = validAirport || airport.Datarates[i as DatalinkProviders][0]; + } + + if (validAirport) { + this.relevantAirports.push(airport); + } + } + + // assume that all upper stations are reachable within the maximum range + if (distanceNM <= MaxSearchRange) { + this.stationsUpperAirspace += 1; + } + } + }); + + resolve(); + }); + }); + } + + private greatCircleDistance(latitude: number, longitude: number): number { + const deg2rad = (deg) => deg * (Math.PI / 180); + + const R = 6371; // Radius of the earth in km + const dLat = deg2rad(this.presentPosition.Latitude - latitude); // deg2rad below + const dLon = deg2rad(this.presentPosition.Longitude - longitude); + const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(deg2rad(latitude)) * Math.cos(deg2rad(this.presentPosition.Latitude)) + * Math.sin(dLon / 2) * Math.sin(dLon / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + const d = R * c * 0.5399568; // Distance in nm + + return d; + } + + private async updateUsedVoiceFrequencies(): Promise { + const storedAtisSrc = NXDataStore.get('CONFIG_ATIS_SRC', 'FAA').toLowerCase(); + this.frequencyOverlap = Array(DatalinkProviders.ProviderCount).fill(0); + + if (storedAtisSrc === 'vatsim' || storedAtisSrc === 'ivao') { + await ATC.get(storedAtisSrc).then((res) => { + if (!res) return; + + res = res.filter((a) => a.callsign.indexOf('_OBS') === -1 && parseFloat(a.frequency) <= 136.975 && this.greatCircleDistance(a.latitude, a.longitude) <= MaxSearchRange); + res.forEach((controller) => { + const frequency = parseFloat(controller.frequency); + + for (const key in DatalinkConfiguration) { + if ({}.hasOwnProperty.call(DatalinkConfiguration, key)) { + const datalinkFrequency = DatalinkConfiguration[key]; + + if (frequency >= datalinkFrequency - 0.009 && frequency <= datalinkFrequency + 0.009) { + // check 8.33 kHz spacing + this.frequencyOverlap[key] += 1; + } else if (frequency >= datalinkFrequency - 0.025 && frequency <= datalinkFrequency + 0.025) { + // check the direct 25 kHz neighbors for SITA + this.frequencyOverlap[key] += 1; + } + } + } + }); + }); + } + } + + /** + * Simulates the data rates for the different datalink providers + * @param flightPhase Actual flight phase to simulate the building based interferences + * @returns A promise to provide the possibilty to run it in sequence + */ + public async simulateDatarates(flightPhase: FmgcFlightPhase): Promise { + this.updatePresentPosition(); + + return this.updateUsedVoiceFrequencies().then(() => this.updateRelevantAirports(flightPhase).then(() => { + // use the average over all reachable stations to estimate the datarate + this.datarates = Array(DatalinkProviders.ProviderCount).fill(0.0); + const stationCount = Array(DatalinkProviders.ProviderCount).fill(0); + + this.relevantAirports.forEach((airport) => { + for (let i = 0; i < DatalinkProviders.ProviderCount; ++i) { + if (airport.Datarates[0]) { + this.datarates[i] += airport.Datarates[i][1]; + stationCount[i] += 1; + } + } + }); + + for (let i = 0; i < DatalinkProviders.ProviderCount; ++i) { + if (stationCount[i] !== 0) this.datarates[i] /= stationCount[i]; + } + })); + } + + public updateDatalinkStates(interfacePowered: boolean, datalinkMode: boolean) { + if (!interfacePowered) { + this.datalinkStatus = DatalinkStatusCode.Inop; + this.datalinkMode = DatalinkModeCode.None; + } else if (!datalinkMode) { + this.datalinkStatus = DatalinkStatusCode.DlkNotAvail; + this.datalinkMode = DatalinkModeCode.None; + } else { + this.datalinkStatus = DatalinkStatusCode.DlkAvail; + if (SimVar.GetSimVarValue('L:A32NX_HOPPIE_ACTIVE', 'number') === 1) { + this.datalinkMode = DatalinkModeCode.AtcAoc; + } + this.datalinkMode = DatalinkModeCode.Aoc; + } + } +} \ No newline at end of file diff --git a/hdw-a339x-common/src/systems/datalink/router/src/webinterfaces/HoppieConnector.ts b/hdw-a339x-common/src/systems/datalink/router/src/webinterfaces/HoppieConnector.ts index d78efbd74..dfe4fa634 100644 --- a/hdw-a339x-common/src/systems/datalink/router/src/webinterfaces/HoppieConnector.ts +++ b/hdw-a339x-common/src/systems/datalink/router/src/webinterfaces/HoppieConnector.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0 import { NXDataStore } from '@flybywiresim/fbw-sdk'; -import { Hoppie } from '@flybywiresim/api-client'; +import { Hoppie } from '@headwindsimulations/api-client'; import { AtsuStatusCodes, CpdlcMessage, @@ -392,4 +392,4 @@ export class HoppieConnector { const pollTimeSeconds = Math.floor(Math.random() * (75 - 45 + 1)) + 45; return pollTimeSeconds * 1000; } -} \ No newline at end of file +} diff --git a/hdw-a339x-common/src/systems/datalink/router/src/webinterfaces/NXApiConnector.ts b/hdw-a339x-common/src/systems/datalink/router/src/webinterfaces/NXApiConnector.ts new file mode 100644 index 000000000..82f13c19a --- /dev/null +++ b/hdw-a339x-common/src/systems/datalink/router/src/webinterfaces/NXApiConnector.ts @@ -0,0 +1,226 @@ +// Copyright (c) 2022 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +import { Atis, Metar, Taf, Telex, AircraftStatus } from '@headwindsimulations/api-client'; +import { ConfigWeatherMap, NXDataStore } from '@flybywiresim/fbw-sdk'; +import { + AtsuStatusCodes, + AtsuMessage, + AtsuMessageComStatus, + AtsuMessageNetwork, + AtsuMessageDirection, + FreetextMessage, + WeatherMessage, + AtisMessage, + AtisType, +} from '@datalink/common'; + +/** + * Defines the NXApi connector for the AOC system + */ +export class NXApiConnector { + private static flightNumber: string = ''; + + private static connected: boolean = false; + + private static updateCounter: number = 0; + + private static createAircraftStatus(): AircraftStatus | undefined { + const lat = SimVar.GetSimVarValue('PLANE LATITUDE', 'degree latitude'); + const long = SimVar.GetSimVarValue('PLANE LONGITUDE', 'degree longitude'); + const alt = SimVar.GetSimVarValue('PLANE ALTITUDE', 'feet'); + const heading = SimVar.GetSimVarValue('PLANE HEADING DEGREES TRUE', 'degree'); + const acType = SimVar.GetSimVarValue('TITLE', 'string'); + const origin = NXDataStore.get('PLAN_ORIGIN', ''); + const destination = NXDataStore.get('PLAN_DESTINATION', ''); + const freetext = NXDataStore.get('CONFIG_ONLINE_FEATURES_STATUS', 'DISABLED') === 'ENABLED'; + + return { + location: { + long, + lat, + }, + trueAltitude: alt, + heading, + origin, + destination, + freetextEnabled: freetext, + flight: NXApiConnector.flightNumber, + aircraftType: acType, + }; + } + + public static async connect(flightNo: string): Promise { + if (NXDataStore.get('CONFIG_ONLINE_FEATURES_STATUS', 'DISABLED') !== 'ENABLED') { + return AtsuStatusCodes.TelexDisabled; + } + + // deactivate old connection + await NXApiConnector.disconnect(); + + NXApiConnector.flightNumber = flightNo; + const status = NXApiConnector.createAircraftStatus(); + if (status !== undefined) { + return Telex.connect(status).then((res) => { + if (res.accessToken !== '') { + NXApiConnector.connected = true; + NXApiConnector.updateCounter = 0; + return AtsuStatusCodes.Ok; + } + return AtsuStatusCodes.NoTelexConnection; + }).catch(() => AtsuStatusCodes.CallsignInUse); + } + + return AtsuStatusCodes.Ok; + } + + public static async disconnect(): Promise { + if (NXDataStore.get('CONFIG_ONLINE_FEATURES_STATUS', 'DISABLED') !== 'ENABLED') { + return AtsuStatusCodes.TelexDisabled; + } + + if (NXApiConnector.connected) { + return Telex.disconnect().then(() => { + NXApiConnector.connected = false; + NXApiConnector.flightNumber = ''; + return AtsuStatusCodes.Ok; + }).catch(() => AtsuStatusCodes.ProxyError); + } + + return AtsuStatusCodes.NoTelexConnection; + } + + public static isConnected(): boolean { + return NXApiConnector.connected; + } + + public static async sendTelexMessage(message: FreetextMessage): Promise { + if (NXApiConnector.connected) { + const content = message.Message.replace('\n', ';'); + return Telex.sendMessage(message.Station, content).then(() => { + message.ComStatus = AtsuMessageComStatus.Sent; + return AtsuStatusCodes.Ok; + }).catch(() => { + message.ComStatus = AtsuMessageComStatus.Failed; + return AtsuStatusCodes.ComFailed; + }); + } + return AtsuStatusCodes.NoTelexConnection; + } + + public static async receiveMetar(icao: string, message: WeatherMessage): Promise { + const storedMetarSrc = NXDataStore.get('CONFIG_METAR_SRC', 'MSFS'); + + return Metar.get(icao, ConfigWeatherMap[storedMetarSrc]) + .then((data) => { + let metar = data.metar; + if (!metar || metar === undefined || metar === '') { + metar = 'NO METAR AVAILABLE'; + } + + message.Reports.push({ airport: icao, report: metar }); + return AtsuStatusCodes.Ok; + }).catch(() => { + message.Reports.push({ airport: icao, report: 'NO METAR AVAILABLE' }); + return AtsuStatusCodes.Ok; + }); + } + + public static async receiveTaf(icao: string, message: WeatherMessage): Promise { + const storedTafSrc = NXDataStore.get('CONFIG_TAF_SRC', 'NOAA'); + + return Taf.get(icao, ConfigWeatherMap[storedTafSrc]) + .then((data) => { + let taf = data.taf; + if (!taf || taf === undefined || taf === '') { + taf = 'NO TAF AVAILABLE'; + } + + message.Reports.push({ airport: icao, report: taf }); + return AtsuStatusCodes.Ok; + }).catch(() => { + message.Reports.push({ airport: icao, report: 'NO TAF AVAILABLE' }); + return AtsuStatusCodes.Ok; + }); + } + + public static async receiveAtis(icao: string, type: AtisType, message: AtisMessage): Promise { + const storedAtisSrc = NXDataStore.get('CONFIG_ATIS_SRC', 'FAA'); + + await Atis.get(icao, ConfigWeatherMap[storedAtisSrc]) + .then((data) => { + let atis = undefined; + + if (type === AtisType.Arrival) { + if ('arr' in data) { + atis = data.arr; + } else { + atis = data.combined; + } + } else if (type === AtisType.Departure) { + if ('dep' in data) { + atis = data.dep; + } else { + atis = data.combined; + } + } else if (type === AtisType.Enroute) { + if ('combined' in data) { + atis = data.combined; + } else if ('arr' in data) { + atis = data.arr; + } + } + + if (!atis || atis === undefined) { + atis = 'D-ATIS NOT AVAILABLE'; + } + + message.Reports.push({ airport: icao, report: atis }); + }).catch(() => { + message.Reports.push({ airport: icao, report: 'D-ATIS NOT AVAILABLE' }); + }); + + return AtsuStatusCodes.Ok; + } + + public static async poll(): Promise<[AtsuStatusCodes, AtsuMessage[]]> { + const retval: AtsuMessage[] = []; + + if (NXApiConnector.connected) { + if (NXApiConnector.updateCounter++ % 4 === 0) { + const status = NXApiConnector.createAircraftStatus(); + if (status !== undefined) { + const code = await Telex.update(status).then(() => AtsuStatusCodes.Ok).catch(() => AtsuStatusCodes.ProxyError); + if (code !== AtsuStatusCodes.Ok) { + return [AtsuStatusCodes.ComFailed, retval]; + } + } + } + + // Fetch new messages + try { + const data = await Telex.fetchMessages(); + for (const msg of data) { + const message = new FreetextMessage(); + message.Network = AtsuMessageNetwork.FBW; + message.Direction = AtsuMessageDirection.Uplink; + message.Station = msg.from.flight; + message.Message = msg.message.replace(/;/i, ' '); + + retval.push(message); + } + } catch (_e) { + return [AtsuStatusCodes.ComFailed, retval]; + } + } + + return [AtsuStatusCodes.Ok, retval]; + } + + public static pollInterval(): number { + return 15000; + } +} + +NXDataStore.set('PLAN_ORIGIN', ''); +NXDataStore.set('PLAN_DESTINATION', ''); \ No newline at end of file diff --git a/hdw-a339x/src/systems/instruments/src/EFB/ATC/ATC.tsx b/hdw-a339x/src/systems/instruments/src/EFB/ATC/ATC.tsx new file mode 100644 index 000000000..fcdaa9d94 --- /dev/null +++ b/hdw-a339x/src/systems/instruments/src/EFB/ATC/ATC.tsx @@ -0,0 +1,320 @@ +// Copyright (c) 2021-2023 FlyByWire Simulations +// +// SPDX-License-Identifier: GPL-3.0 + +/* eslint-disable max-len */ +import React, { useCallback, useEffect, useState } from 'react'; +import * as apiClient from '@headwindsimulations/api-client'; +import { AtcType } from '@headwindsimulations/api-client'; +import { useSimVar, useInterval, useSplitSimVar, usePersistentProperty } from '@flybywiresim/fbw-sdk'; +import { Link } from 'react-router-dom'; +import { CloudArrowDown, Gear, InfoCircle } from 'react-bootstrap-icons'; +import { toast } from 'react-toastify'; +import { t } from '../translation'; +import { pathify } from '../Utils/routing'; +import { ScrollableContainer } from '../UtilComponents/ScrollableContainer'; +import { SimpleInput } from '../UtilComponents/Form/SimpleInput/SimpleInput'; +import { SelectGroup, SelectItem } from '../UtilComponents/Form/Select'; +import { TooltipWrapper } from '../UtilComponents/TooltipWrapper'; + +export declare class ATCInfoExtended extends apiClient.ATCInfo { + distance: number; +} + +interface FrequencyCardProps { + className?: string; + callsign: string; + frequency: string; + setActive: () => void; + setCurrent: () => void; + setStandby: () => void; +} + +const FrequencyCard = ({ className, callsign, frequency, setActive, setCurrent, setStandby }: FrequencyCardProps) => ( +
+
+

+ {callsign} +

+

+ {frequency} +

+ +
+
+

{t('AirTrafficControl.SetActive')}

+
+
+

{t('AirTrafficControl.SetStandby')}

+
+
+ +
+
+
+
+); + +export const ATC = () => { + const [controllers, setControllers] = useState(); + const [activeFrequency, setActiveFrequency] = useSplitSimVar('COM ACTIVE FREQUENCY:1', 'Hz', 'K:COM_RADIO_SET_HZ', 'Hz', 500); + const [stanbdyFrequency, setStandbyFrequency] = useSplitSimVar('COM STANDBY FREQUENCY:1', 'Hz', 'K:COM_STBY_RADIO_SET_HZ', 'Hz', 500); + const [displayedActiveFrequency, setDisplayedActiveFrequency] = useState(); + const [displayedStandbyFrequency, setDisplayedStandbyFrequency] = useState(); + const [currentAtc, setCurrentAtc] = useState(); + const [currentLatitude] = useSimVar('GPS POSITION LAT', 'Degrees', 10_000); + const [currentLongitude] = useSimVar('GPS POSITION LON', 'Degrees', 10_000); + const [atisSource] = usePersistentProperty('CONFIG_ATIS_SRC', 'FAA'); + const [atcDataPending, setAtcDataPending] = useState(true); + + const [controllerTypeFilter, setControllerTypeFilter] = useState(undefined); + const [controllerCallSignFilter, setControllerCallSignFilter] = useState(''); + + const loadAtc = useCallback(async () => { + if (atisSource.toLowerCase() !== 'vatsim' && atisSource.toLowerCase() !== 'ivao') return; + const atisSourceReq = atisSource.toLowerCase(); + + try { + const atcRes = await apiClient.ATC.get(atisSourceReq); + if (!atcRes) return; + let allAtc : ATCInfoExtended[] = atcRes as ATCInfoExtended[]; + + allAtc = allAtc.filter((a) => a.callsign.indexOf('_OBS') === -1 && parseFloat(a.frequency) <= 136.975); + + for (const a of allAtc) { + a.distance = getDistanceFromLatLonInNm(a.latitude, a.longitude, currentLatitude, currentLongitude); + if (a.visualRange === 0 && a.type === apiClient.AtcType.ATIS) { + a.visualRange = 100; + } + } + + allAtc.sort((a1, a2) => (a1.distance > a2.distance ? 1 : -1)); + allAtc = allAtc.slice(0, 26); + allAtc.push({ callsign: 'UNICOM', frequency: '122.800', type: apiClient.AtcType.RADAR, visualRange: 999999, distance: 0, latitude: 0, longitude: 0, textAtis: [] }); + + setControllers(allAtc.filter((a) => a.distance <= a.visualRange)); + } catch (e) { + toast.error(e.message); + } + + setAtcDataPending(false); + }, [currentLatitude, currentLongitude, atisSource]); + + const getDistanceFromLatLonInNm = (lat1, lon1, lat2, lon2) : number => { + const R = 6371; // Radius of the earth in km + const dLat = deg2Rad(lat2 - lat1); // deg2Rad below + const dLon = deg2Rad(lon2 - lon1); + const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(deg2Rad(lat1)) * Math.cos(deg2Rad(lat2)) + * Math.sin(dLon / 2) * Math.sin(dLon / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + return R * c * 0.5399568; // Distance in nm + }; + + const deg2Rad = (deg) => deg * (Math.PI / 180); + + const toFrequency = (frequency:string) : number => { + if (frequency) { + return parseFloat(`${frequency.replace('.', '').padEnd(9, '0')}.000`); + } + return 0; + }; + + const fromFrequency = (frequency: number): string => { + if (frequency) { + let converted: string = frequency.toString().replace('.', ''); + converted = `${converted.substring(0, 3)}.${converted.substring(3)}`; + return parseFloat(converted).toFixed(3); + } + return ''; + }; + + useEffect(() => { + loadAtc(); + }, [loadAtc]); + + useEffect(() => { + const converted = fromFrequency(activeFrequency); + setDisplayedActiveFrequency(converted); + setCurrentAtc(controllers?.find((c) => c.frequency === converted)); + }, [activeFrequency]); + + useEffect(() => { + const converted = fromFrequency(stanbdyFrequency); + setDisplayedStandbyFrequency(converted); + setCurrentAtc(controllers?.find((c) => c.frequency === converted)); + }, [stanbdyFrequency]); + + // Update selected controller info when controllers change + useEffect(() => { + const currentControllerFrequency = currentAtc?.frequency; + + if (currentControllerFrequency) { + const controllerWithFrequency = controllers?.find((c) => c.frequency === currentControllerFrequency); + + if (controllerWithFrequency) { + setCurrentAtc(controllerWithFrequency); + } + } + }, [controllers]); + + useInterval(() => { + loadAtc(); + }, 60_000); + + const filterControllers = (c: ATCInfoExtended): boolean => !((controllerTypeFilter && c.type !== controllerTypeFilter) + || (controllerCallSignFilter !== '' + && !c.callsign.toUpperCase().includes(controllerCallSignFilter.toUpperCase()))); + + const atcTypeOptions = [ + { typeName: t('AirTrafficControl.ShowAll'), atcType: undefined }, + { typeName: t('AirTrafficControl.ShowAtis'), atcType: AtcType.ATIS }, + { typeName: t('AirTrafficControl.ShowDelivery'), atcType: AtcType.DELIVERY }, + { typeName: t('AirTrafficControl.ShowGround'), atcType: AtcType.GROUND }, + { typeName: t('AirTrafficControl.ShowTower'), atcType: AtcType.TOWER }, + { typeName: t('AirTrafficControl.ShowApproach'), atcType: AtcType.APPROACH }, + { typeName: t('AirTrafficControl.ShowDeparture'), atcType: AtcType.DEPARTURE }, + { typeName: t('AirTrafficControl.ShowRadar'), atcType: AtcType.RADAR }, + ]; + + return ( +
+
+

+ {t('AirTrafficControl.Title')} + {(atisSource === 'IVAO' || atisSource === 'VATSIM') && ` (${atisSource})`} +

+
+ { (atisSource === 'IVAO' || atisSource === 'VATSIM') ? ( +
+ +
+ +
+ +
+ setControllerCallSignFilter(value)} + /> + +
+
+ + {atcTypeOptions.map((option) => ( + +
+ setControllerTypeFilter(option.atcType)} + > + {option.typeName} + +
+
+ ))} +
+
+ + + {controllers && controllers + .filter((c) => filterControllers(c)) + .map((controller, index) => ( + = 2 && 'mt-4'}`} + callsign={controller.callsign} + frequency={controller.frequency} + setActive={() => setActiveFrequency(toFrequency(controller.frequency))} + setCurrent={() => setCurrentAtc(controllers?.find((c) => c.frequency === controller.frequency))} + setStandby={() => setStandbyFrequency(toFrequency(controller.frequency))} + /> + ))} + + +
+ {atcDataPending && ( + + )} +
+
+ +
+
+
+

{t('AirTrafficControl.Active')}

+
+ {displayedActiveFrequency && displayedActiveFrequency} +
+
+
+

{t('AirTrafficControl.Standby')}

+
+ {displayedStandbyFrequency && displayedStandbyFrequency} +
+
+
+ {currentAtc?.textAtis ? ( + + ) : ( +
+

{t('AirTrafficControl.NoInformationAvailableForThisFrequency').toUpperCase()}

+
+ )} +
+
+ ) : ( +
+
+

{t('AirTrafficControl.SelectCorrectATISATCSource')}

+ + +

{t('AirTrafficControl.ChangeATISATCSourceButton')}

+ +
+
+ )} +
+ ); +}; + +interface ControllerInformationProps { + currentAtc?: ATCInfoExtended; +} + +const ControllerInformation = ({ currentAtc }: ControllerInformationProps) => ( + +

{currentAtc?.callsign}

+ {currentAtc?.textAtis.map((line) => ( +

{line}

+ ))} +
+); + +export default ATC; diff --git a/hdw-a339x/src/systems/instruments/src/EFB/Dashboard/Widgets/WeatherWidget.tsx b/hdw-a339x/src/systems/instruments/src/EFB/Dashboard/Widgets/WeatherWidget.tsx new file mode 100644 index 000000000..c047addc6 --- /dev/null +++ b/hdw-a339x/src/systems/instruments/src/EFB/Dashboard/Widgets/WeatherWidget.tsx @@ -0,0 +1,319 @@ +// Copyright (c) 2022 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +import React, { FC, useEffect, useState } from 'react'; +import { Metar as FbwApiMetar } from '@headwindsimulations/api-client'; +import { Droplet, Speedometer2, ThermometerHalf, Wind } from 'react-bootstrap-icons'; +import { ConfigWeatherMap, MetarParserType, parseMetar, useInterval, usePersistentNumberProperty, usePersistentProperty } from '@flybywiresim/fbw-sdk'; +import { Metar as MsfsMetar } from '@microsoft/msfs-sdk'; +import { t } from '../../translation'; +import { SimpleInput } from '../../UtilComponents/Form/SimpleInput/SimpleInput'; +import { ColoredMetar } from './ColorMetar'; +import { useAppDispatch, useAppSelector } from '../../Store/store'; +import { + setDepartureMetar, + setDestinationMetar, + setUserDepartureIcao, + setUserDestinationIcao, +} from '../../Store/features/dashboard'; +import { Toggle } from '../../UtilComponents/Form/Toggle'; +import { TooltipWrapper } from '../../UtilComponents/TooltipWrapper'; + +const MetarParserTypeProp: MetarParserType = { + raw_text: '', + raw_parts: [], + color_codes: [], + icao: '', + observed: new Date(0), + wind: { + degrees: 0, + degrees_from: 0, + degrees_to: 0, + speed_kts: 0, + speed_mps: 0, + gust_kts: 0, + gust_mps: 0, + }, + visibility: { + miles: '', + miles_float: 0.0, + meters: '', + meters_float: 0.0, + }, + conditions: [], + clouds: [], + ceiling: { + code: '', + feet_agl: 0, + meters_agl: 0, + }, + temperature: { + celsius: 0, + fahrenheit: 0, + }, + dewpoint: { + celsius: 0, + fahrenheit: 0, + }, + humidity_percent: 0, + barometer: { + hg: 0, + kpa: 0, + mb: 0, + }, + flight_category: '', +}; + +interface WeatherWidgetProps { + name: 'origin'|'destination'; + simbriefIcao: string; + userIcao?: string +} + +export const WeatherWidget: FC = ({ name, simbriefIcao, userIcao }) => { + const [baroType] = usePersistentProperty('CONFIG_INIT_BARO_UNIT', 'HPA'); + const dispatch = useAppDispatch(); + const [simbriefIcaoAtLoading, setSimbriefIcaoAtLoading] = useState(simbriefIcao); + const [metarSource] = usePersistentProperty('CONFIG_METAR_SRC', 'MSFS'); + const [metarError, setErrorMetar] = useState(''); + const [usingColoredMetar] = usePersistentNumberProperty('EFB_USING_COLOREDMETAR', 1); + const source = metarSource; + + const getBaroTypeForAirport = (icao: string) => (['K', 'C', 'M', 'P', 'RJ', 'RO', 'TI', 'TJ'] + .some((r) => icao.toUpperCase().startsWith(r)) ? 'IN HG' : 'HPA'); + + const metar = useAppSelector((state) => (name === 'origin' ? state.dashboard.departureMetar : state.dashboard.destinationMetar)) ?? MetarParserTypeProp; + const setMetar = name === 'origin' ? setDepartureMetar : setDestinationMetar; + + const [showMetar, setShowMetar] = usePersistentNumberProperty(`CONFIG_SHOW_METAR_${name}`, 0); + + const BaroValue = () => { + const displayedBaroType = baroType === 'AUTO' ? getBaroTypeForAirport(metar.icao) : baroType; + if (displayedBaroType === 'IN HG') { + return ( + <> + {metar.barometer.hg.toFixed(2)} + {' '} + inHg + + ); + } + return ( + <> + {metar.barometer.mb.toFixed(0)} + {' '} + mb + + ); + }; + + const handleIcao = (icao: string) => { + if (name === 'origin') { + dispatch(setUserDepartureIcao(icao)); + } else { + dispatch(setUserDestinationIcao(icao)); + } + + if (icao.length > 0) { + getMetar(icao, source); + } else if (icao.length === 0) { + getMetar(simbriefIcao, source); + } + }; + + async function getMetar(icao: string, source: string): Promise { + if (icao.length !== 4 || icao === '----') { + setErrorMetar(t('Dashboard.ImportantInformation.Weather.NoIcaoProvided')); + dispatch(setMetar(MetarParserTypeProp)); + return Promise.resolve(); + } + + // Comes from the sim rather than the FBW API + if (source === 'MSFS') { + let metar: MsfsMetar; + // Catch parsing error separately + try { + metar = await Coherent.call('GET_METAR_BY_IDENT', icao); + if (metar.icao !== icao.toUpperCase()) { + throw new Error('No METAR available'); + } + } catch (err) { + console.log(`Error while retrieving Metar: ${err}`); + setErrorMetar(`${err.toString()}`); + dispatch(setMetar(MetarParserTypeProp)); + } + try { + const metarParse = parseMetar(metar.metarString); + dispatch(setMetar(metarParse)); + } catch (err) { + console.log(`Error while parsing Metar ("${metar.metarString}"): ${err}`); + setErrorMetar(`${t('Dashboard.ImportantInformation.Weather.MetarParsingError')}: ${err.toString().replace(/^Error: /, '').toUpperCase()}`); + dispatch(setMetar(MetarParserTypeProp)); + } + return Promise.resolve(); + } + + return FbwApiMetar.get(icao, ConfigWeatherMap[source]) + .then((result) => { + // For METAR source Microsoft result.metar is undefined without throwing an error. + // For the other METAR sources an error is thrown (Request failed with status code 404) + // and caught in the catch clause. + if (!result.metar) { + setErrorMetar(t('Dashboard.ImportantInformation.Weather.IcaoInvalid')); + dispatch(setMetar(MetarParserTypeProp)); + return; + } + // Catch parsing error separately + try { + const metarParse = parseMetar(result.metar); + dispatch(setMetar(metarParse)); + } catch (err) { + console.log(`Error while parsing Metar ("${result.metar}"): ${err}`); + setErrorMetar(`${t('Dashboard.ImportantInformation.Weather.MetarParsingError')}: ${err.toString().replace(/^Error: /, '').toUpperCase()}`); + dispatch(setMetar(MetarParserTypeProp)); + } + }) + // catch retrieving metar errors + .catch((err) => { + console.log(`Error while retrieving Metar: ${err}`); + if (err.toString().match(/^Error:/)) { + setErrorMetar(t('Dashboard.ImportantInformation.Weather.IcaoInvalid')); + } else { + setErrorMetar(`${err.toString().replace(/^Error: /, '')}`); + } + dispatch(setMetar(MetarParserTypeProp)); + }); + } + + useEffect(() => { + // if we have new simbrief data that is different from the simbrief data at + // loading of the widget we overwrite the user input once. After that + // user input has priority. + if (simbriefIcao !== simbriefIcaoAtLoading) { + dispatch(setUserDepartureIcao('')); + dispatch(setUserDestinationIcao('')); + getMetar(simbriefIcao, source); + setSimbriefIcaoAtLoading(simbriefIcao); + } else { + getMetar(userIcao || simbriefIcao, source); + } + }, [simbriefIcao, userIcao, source]); + + useInterval(() => { + handleIcao(userIcao ?? simbriefIcao); + }, 60_000); + + return ( +
+ {metar === undefined + ?

{t('Dashboard.ImportantInformation.Weather.Loading')}

+ : ( + <> +
+ handleIcao(value)} + maxLength={4} + /> + +
+

{t('Dashboard.ImportantInformation.Weather.Raw')}

+ setShowMetar(value ? 1 : 0)} /> +
+
+
+
+ {!showMetar + ? ( + <> +
+
+ +

{t('Dashboard.ImportantInformation.Weather.AirPressure')}

+ {metar.raw_text ? ( + <> + {metar.barometer ? : 'N/A'} + + ) : t('Dashboard.ImportantInformation.Weather.NotAvailableShort')} +
+
+ +

{t('Dashboard.ImportantInformation.Weather.WindSpeed')}

+ {metar.raw_text + ? ( + <> + {metar.wind.degrees.toFixed(0)} + ° + {' '} + / + {' '} + {metar.wind.speed_kts.toFixed(0)} + {' '} + kts + + ) : t('Dashboard.ImportantInformation.Weather.NotAvailableShort')} +
+
+ +

{t('Dashboard.ImportantInformation.Weather.Temperature')}

+ {metar.raw_text + ? ( + <> + {metar.temperature.celsius.toFixed(0)} + {' '} + °C + + ) : t('Dashboard.ImportantInformation.Weather.NotAvailableShort')} +
+
+ +

{t('Dashboard.ImportantInformation.Weather.DewPoint')}

+ {metar.raw_text + ? ( + <> + {metar.dewpoint.celsius.toFixed(0)} + {' '} + °C + + ) : t('Dashboard.ImportantInformation.Weather.NotAvailableShort')} +
+
+ + ) + : ( + <> + {metar.raw_text + ? ( +
+ {usingColoredMetar + ? ( + <> + + + ) : ( + <> + {metar.raw_text} + + )} +
+ ) : ( +
+ {metarError} +
+ )} + + )} +
+ + )} +
+ ); +}; \ No newline at end of file diff --git a/hdw-a339x/src/systems/instruments/src/EFB/Performance/Widgets/LandingWidget.tsx b/hdw-a339x/src/systems/instruments/src/EFB/Performance/Widgets/LandingWidget.tsx index 895eb8a41..47582f5ec 100644 --- a/hdw-a339x/src/systems/instruments/src/EFB/Performance/Widgets/LandingWidget.tsx +++ b/hdw-a339x/src/systems/instruments/src/EFB/Performance/Widgets/LandingWidget.tsx @@ -18,7 +18,7 @@ */ import React, { FC, useState } from 'react'; -import { Metar as FbwApiMetar } from '@flybywiresim/api-client'; +import { Metar as FbwApiMetar } from '@headwindsimulations/api-client'; import { Metar as MsfsMetar } from '@microsoft/msfs-sdk'; import { Units, MetarParserType, useSimVar, usePersistentProperty, parseMetar, ConfigWeatherMap } from '@flybywiresim/fbw-sdk'; import { toast } from 'react-toastify'; diff --git a/hdw-a339x/src/systems/instruments/src/EFB/Settings/Pages/AtsuAocPage.tsx b/hdw-a339x/src/systems/instruments/src/EFB/Settings/Pages/AtsuAocPage.tsx index aa3dd7a09..e48f9f2e9 100644 --- a/hdw-a339x/src/systems/instruments/src/EFB/Settings/Pages/AtsuAocPage.tsx +++ b/hdw-a339x/src/systems/instruments/src/EFB/Settings/Pages/AtsuAocPage.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { usePersistentProperty } from '@flybywiresim/fbw-sdk'; -import { Hoppie } from '@flybywiresim/api-client'; +import { Hoppie } from '@headwindsimulations/api-client'; import { toast } from 'react-toastify'; import { HoppieConnector } from '@datalink/router'; import { SENTRY_CONSENT_KEY, SentryConsentState } from '@sentry/FbwAircraftSentryClient'; diff --git a/hdw-a339x/src/systems/shared/src/AircraftVersionChecker.ts b/hdw-a339x/src/systems/shared/src/AircraftVersionChecker.ts index e0d749df4..887a24e78 100644 --- a/hdw-a339x/src/systems/shared/src/AircraftVersionChecker.ts +++ b/hdw-a339x/src/systems/shared/src/AircraftVersionChecker.ts @@ -3,7 +3,7 @@ /* eslint-disable no-console */ import Compare from 'semver/functions/compare'; -import { CommitInfo, GitVersions, ReleaseInfo } from '@flybywiresim/api-client'; +import { CommitInfo, GitVersions, ReleaseInfo } from '@headwindsimulations/api-client'; import { NotificationManager, PopUpDialog } from '@flybywiresim/fbw-sdk'; /** diff --git a/package-lock.json b/package-lock.json index a37fe4acd..486274c7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { - "name": "a339x", + "name": "aircraft", "version": "0.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "a339x", + "name": "aircraft", "version": "0.6.0", "dependencies": { - "@flybywiresim/api-client": "^0.16.2", "@flybywiresim/react-components": "^0.3.1", "@flybywiresim/tailwind-config": "^0.5.0", + "@headwindsimulations/api-client": "^1.1.0", "@localazy/cli": "^1.6.0", "@microsoft/msfs-sdk": "^0.6.0", "@reduxjs/toolkit": "^1.6.2", @@ -120,6 +120,15 @@ "vite-tsconfig-paths": "^4.2.1" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -2455,23 +2464,15 @@ } }, "node_modules/@eslint/js": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz", - "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true, "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@flybywiresim/api-client": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@flybywiresim/api-client/-/api-client-0.16.2.tgz", - "integrity": "sha512-iADn/qWAThQQk9vtt9DfsyE3dayo2hP/DsXmEbSkBxImn5W8UkifWbkl4xcLfSsnu4iEVEb1jzok6mhprvaYrg==", - "dependencies": { - "axios": "^0.21.4" - } - }, "node_modules/@flybywiresim/eslint-config": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@flybywiresim/eslint-config/-/eslint-config-0.1.0.tgz", @@ -2553,6 +2554,14 @@ "resolved": "https://registry.npmjs.org/@flybywiresim/tailwind-config/-/tailwind-config-0.5.2.tgz", "integrity": "sha512-MV0IZltNoHm2vAhpL+rOIgK6G7tUu4w7wQf+vPTQDmyCbNfpe5QwfbtF+yjKmqryclm3DQ8smPDDRPFuBAIgbQ==" }, + "node_modules/@headwindsimulations/api-client": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@headwindsimulations/api-client/-/api-client-1.1.0.tgz", + "integrity": "sha512-/pNf4+FI0FQ0vBoV9HPV867IKBMEdU2UrhzdI2IeUmF83bE6XLbUiVWNs2xsMI4xsvYx2E4FZhaZqLyi3ID+lA==", + "dependencies": { + "axios": "^0.21.4" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", @@ -3412,18 +3421,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", - "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", @@ -3896,15 +3893,15 @@ } }, "node_modules/@synaptic-simulations/mach/node_modules/@eslint/eslintrc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", - "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "peer": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.1", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -3920,13 +3917,13 @@ } }, "node_modules/@synaptic-simulations/mach/node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "peer": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" }, @@ -3934,6 +3931,13 @@ "node": ">=10.10.0" } }, + "node_modules/@synaptic-simulations/mach/node_modules/@humanwhocodes/object-schema": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "dev": true, + "peer": true + }, "node_modules/@synaptic-simulations/mach/node_modules/@synaptic-simulations/eslint-config": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@synaptic-simulations/eslint-config/-/eslint-config-1.0.2.tgz", @@ -4088,9 +4092,9 @@ } }, "node_modules/@synaptic-simulations/mach/node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true, "peer": true, "bin": { @@ -4216,28 +4220,29 @@ } }, "node_modules/@synaptic-simulations/mach/node_modules/eslint": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz", - "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", "dev": true, "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.38.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -4245,22 +4250,19 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -4323,9 +4325,9 @@ } }, "node_modules/@synaptic-simulations/mach/node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "peer": true, "dependencies": { @@ -4334,12 +4336,15 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/@synaptic-simulations/mach/node_modules/eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4349,15 +4354,15 @@ } }, "node_modules/@synaptic-simulations/mach/node_modules/espree": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", - "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "peer": true, "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4397,9 +4402,9 @@ } }, "node_modules/@synaptic-simulations/mach/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "peer": true, "dependencies": { @@ -4430,16 +4435,6 @@ "node": ">= 4" } }, - "node_modules/@synaptic-simulations/mach/node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/@synaptic-simulations/mach/node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -5369,6 +5364,13 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true, + "peer": true + }, "node_modules/@vitejs/plugin-react": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-1.3.2.tgz", @@ -5765,20 +5767,6 @@ "node": ">= 4.0.0" } }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "optional": true, - "peer": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/autoprefixer": { "version": "10.4.14", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", @@ -6746,20 +6734,6 @@ "node": ">= 8" } }, - "node_modules/css": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", - "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.3", - "source-map": "^0.6.1", - "source-map-resolve": "^0.5.2", - "urix": "^0.1.0" - } - }, "node_modules/css-declaration-sorter": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.0.tgz", @@ -6772,17 +6746,6 @@ "postcss": "^8.0.9" } }, - "node_modules/css-parse": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz", - "integrity": "sha512-UNIFik2RgSbiTwIW1IsFwXWn6vs+bYdq83LKTSOsx7NJR7WII9dxewkHLltfTLVppoUApHV0118a4RZRI9FLwA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "css": "^2.0.0" - } - }, "node_modules/css-select": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", @@ -6994,17 +6957,6 @@ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10" - } - }, "node_modules/dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", @@ -9889,6 +9841,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-object": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", @@ -12199,17 +12161,6 @@ "jiti": "bin/jiti.js" } }, - "node_modules/js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", - "dev": true, - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -12389,9 +12340,9 @@ "integrity": "sha512-xT/iSClTVi7vLoF8dCWTBhCuOWqsLXCMPa6ucVmVAk6hyNCM5JeS1NLhXqIrJktUg+caEYKlqSOUU4u3cpXzKg==" }, "node_modules/leaflet": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.3.tgz", - "integrity": "sha512-iB2cR9vAkDOu5l3HAay2obcUHZ7xwUBBjph8+PGtmW/2lYhbLizWtG7nTeYht36WfOslixQF9D/uSIzhZgGMfQ==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", "peer": true }, "node_modules/leven": { @@ -13533,17 +13484,17 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -15334,15 +15285,6 @@ "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/resolve.exports": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", @@ -16025,43 +15967,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "deprecated": "See https://github.com/lydell/source-map-url#deprecated", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", @@ -16316,71 +16221,6 @@ "postcss": "^8.2.15" } }, - "node_modules/stylus": { - "version": "0.54.8", - "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.8.tgz", - "integrity": "sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "css-parse": "~2.0.0", - "debug": "~3.1.0", - "glob": "^7.1.6", - "mkdirp": "~1.0.4", - "safer-buffer": "^2.1.2", - "sax": "~1.2.4", - "semver": "^6.3.0", - "source-map": "^0.7.3" - }, - "bin": { - "stylus": "bin/stylus" - }, - "engines": { - "node": "*" - } - }, - "node_modules/stylus/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/stylus/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/stylus/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "optional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/stylus/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">= 8" - } - }, "node_modules/sucrase": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", @@ -16674,48 +16514,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/terser": { - "version": "5.16.8", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.8.tgz", - "integrity": "sha512-QI5g1E/ef7d+PsDifb+a6nnVgC4F22Bg6T0xrBrz6iloVB4PUkkunp6V8nzoOOZJIzjWVdAGqCdlKlhLq/TbIA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true, - "optional": true, - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -17208,15 +17006,6 @@ "punycode": "^2.1.0" } }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/url-join": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", diff --git a/package.json b/package.json index f1c527b49..b1311baae 100644 --- a/package.json +++ b/package.json @@ -177,7 +177,7 @@ "vite-tsconfig-paths": "^4.2.1" }, "dependencies": { - "@flybywiresim/api-client": "^0.16.2", + "@headwindsimulations/api-client": "^1.1.0", "@flybywiresim/react-components": "^0.3.1", "@flybywiresim/tailwind-config": "^0.5.0", "@localazy/cli": "^1.6.0",